Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MsgVote #215

Merged
merged 5 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions components/dataViews/TransactionInfo/TxMsgVoteDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { printVoteOption } from "@/lib/gov";
import { MsgVote } from "cosmjs-types/cosmos/gov/v1beta1/tx";

interface TxMsgVoteDetailsProps {
readonly msgValue: MsgVote;
}

const TxMsgVoteDetails = ({ msgValue }: TxMsgVoteDetailsProps) => {
return (
<>
<li>
<h3>MsgVote</h3>
</li>
<li>
<label>Proposal ID:</label>
<div>{msgValue.proposalId.toString()}</div>
</li>
<li>
<label>Voted:</label>
<div>{printVoteOption(msgValue.option)}</div>
</li>
<style jsx>{`
li:not(:has(h3)) {
background: rgba(255, 255, 255, 0.03);
padding: 6px 10px;
border-radius: 8px;
display: flex;
align-items: center;
}
li + li:nth-child(2) {
margin-top: 25px;
}
li + li {
margin-top: 10px;
}
li div {
padding: 3px 6px;
}
label {
font-size: 12px;
background: rgba(255, 255, 255, 0.1);
padding: 3px 6px;
border-radius: 5px;
display: block;
}
`}</style>
</>
);
};

export default TxMsgVoteDetails;
3 changes: 3 additions & 0 deletions components/dataViews/TransactionInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import TxMsgSetWithdrawAddressDetails from "./TxMsgSetWithdrawAddressDetails";
import TxMsgTransferDetails from "./TxMsgTransferDetails";
import TxMsgUndelegateDetails from "./TxMsgUndelegateDetails";
import TxMsgUpdateAdminDetails from "./TxMsgUpdateAdminDetails";
import TxMsgVoteDetails from "./TxMsgVoteDetails";

const TxMsgDetails = ({ typeUrl, value: msgValue }: EncodeObject) => {
switch (typeUrl) {
Expand All @@ -34,6 +35,8 @@ const TxMsgDetails = ({ typeUrl, value: msgValue }: EncodeObject) => {
return <TxMsgSetWithdrawAddressDetails msgValue={msgValue} />;
case MsgTypeUrls.CreateVestingAccount:
return <TxMsgCreateVestingAccountDetails msgValue={msgValue} />;
case MsgTypeUrls.Vote:
return <TxMsgVoteDetails msgValue={msgValue} />;
case MsgTypeUrls.Transfer:
return <TxMsgTransferDetails msgValue={msgValue} />;
case MsgTypeUrls.Execute:
Expand Down
137 changes: 137 additions & 0 deletions components/forms/CreateTxForm/MsgForm/MsgVoteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { printVoteOption, voteOptions } from "@/lib/gov";
import { MsgVoteEncodeObject } from "@cosmjs/stargate";
import { longify } from "@cosmjs/stargate/build/queryclient";
import { voteOptionFromJSON } from "cosmjs-types/cosmos/gov/v1beta1/gov";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";

const selectVoteOptions = voteOptions.map((opt) => {
const voteOptionObj = voteOptionFromJSON(opt);

return {
label: printVoteOption(voteOptionObj),
value: voteOptionObj,
};
});

interface MsgVoteFormProps {
readonly fromAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}

const MsgVoteForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgVoteFormProps) => {
const [proposalId, setProposalId] = useState("0");
const [selectedVote, setSelectedVote] = useState(selectVoteOptions[0]);

const [proposalIdError, setProposalIdError] = useState("");

const trimmedInputs = trimStringsObj({ proposalId });

useEffect(() => {
// eslint-disable-next-line no-shadow
const { proposalId } = trimmedInputs;

const isMsgValid = (): boolean => {
setProposalIdError("");

if (!proposalId || Number(proposalId) <= 0 || !Number.isSafeInteger(Number(proposalId))) {
setProposalIdError("Proposal ID must be an integer greater than 0");
return false;
}

try {
longify(proposalId);
} catch (e: unknown) {
setProposalIdError(e instanceof Error ? e.message : "Proposal ID is not a valid Big Int");
return false;
}

return true;
};

const proposalIdBigInt = (() => {
try {
return longify(proposalId);
} catch {
return 0n;
}
})();

const msgValue = MsgCodecs[MsgTypeUrls.Vote].fromPartial({
voter: fromAddress,
proposalId: proposalIdBigInt,
option: selectedVote.value,
});

const msg: MsgVoteEncodeObject = { typeUrl: MsgTypeUrls.Vote, value: msgValue };

setMsgGetter({ isMsgValid, msg });
}, [fromAddress, selectedVote.value, setMsgGetter, trimmedInputs]);

return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgVote</h2>
<div className="form-item">
<Input
type="number"
label="Proposal ID"
name="proposal-id"
value={proposalId}
onChange={({ target }) => {
setProposalId(target.value);
setProposalIdError("");
}}
error={proposalIdError}
/>
</div>
<div className="form-item form-select">
<label>Choose a vote:</label>
<Select
label="Select vote"
name="vote-select"
options={selectVoteOptions}
value={selectedVote}
onChange={(option: (typeof selectVoteOptions)[number]) => {
setSelectedVote(option);
}}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
button.remove {
background: rgba(255, 255, 255, 0.2);
width: 30px;
height: 30px;
border-radius: 50%;
border: none;
color: white;
position: absolute;
right: 10px;
top: 10px;
}
`}</style>
</StackableContainer>
);
};

export default MsgVoteForm;
3 changes: 3 additions & 0 deletions components/forms/CreateTxForm/MsgForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import MsgSetWithdrawAddressForm from "./MsgSetWithdrawAddressForm";
import MsgTransferForm from "./MsgTransferForm";
import MsgUndelegateForm from "./MsgUndelegateForm";
import MsgUpdateAdminForm from "./MsgUpdateAdminForm";
import MsgVoteForm from "./MsgVoteForm";

interface MsgFormProps {
readonly msgType: MsgTypeUrl;
Expand All @@ -37,6 +38,8 @@ const MsgForm = ({ msgType, senderAddress, ...restProps }: MsgFormProps) => {
return <MsgSetWithdrawAddressForm delegatorAddress={senderAddress} {...restProps} />;
case MsgTypeUrls.CreateVestingAccount:
return <MsgCreateVestingAccountForm fromAddress={senderAddress} {...restProps} />;
case MsgTypeUrls.Vote:
return <MsgVoteForm fromAddress={senderAddress} {...restProps} />;
case MsgTypeUrls.Transfer:
return <MsgTransferForm fromAddress={senderAddress} {...restProps} />;
case MsgTypeUrls.Execute:
Expand Down
19 changes: 15 additions & 4 deletions components/forms/CreateTxForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { loadValidators } from "@/context/ChainsContext/helpers";
import { toastError, toastSuccess } from "@/lib/utils";
import { EncodeObject } from "@cosmjs/proto-signing";
import { Account, calculateFee } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import { assert, sleep } from "@cosmjs/utils";
import { NextRouter, withRouter } from "next/router";
import { useRef, useState } from "react";
import { toast } from "sonner";
Expand Down Expand Up @@ -61,6 +61,9 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro

const createTx = async () => {
const loadingToastId = toast.loading("Creating transaction");
setProcessing(true);
// If it fails too fast, toast.dismiss does not work
await sleep(500);

try {
assert(typeof accountOnChain.accountNumber === "number", "accountNumber missing");
Expand All @@ -70,15 +73,15 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
.filter(({ isMsgValid }) => isMsgValid())
.map(({ msg }) => exportMsgToJson(msg));

if (!msgs.length || msgs.length !== msgTypes.length) return;
if (!msgs.length || msgs.length !== msgTypes.length) {
return;
}

if (!Number.isSafeInteger(gasLimit) || gasLimit <= 0) {
setGasLimitError("gas limit must be a positive integer");
return;
}

setProcessing(true);

const tx: DbTransaction = {
accountNumber: accountOnChain.accountNumber,
sequence: accountOnChain.sequence,
Expand Down Expand Up @@ -180,6 +183,14 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro
</li>
</ul>
</div>
<div className="btn-cluster">
<label>Governance</label>
<ul>
<li>
<Button label="Vote" onClick={() => addMsgType(MsgTypeUrls.Vote)} />
</li>
</ul>
</div>
<div className="btn-cluster">
<label>IBC</label>
<ul>
Expand Down
19 changes: 19 additions & 0 deletions lib/gov.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { VoteOption, voteOptionToJSON } from "cosmjs-types/cosmos/gov/v1beta1/gov";

const voteOptionPrefix = "VOTE_OPTION_";

export const voteOptions = Object.keys(VoteOption).filter(
(key) => isNaN(Number(key)) && key.startsWith(voteOptionPrefix),
);

export const printVoteOption = (voteOption: VoteOption): string => {
const voteStr = voteOptionToJSON(voteOption);

if (!voteStr.startsWith(voteOptionPrefix)) {
return "Unrecognized";
}

const voteNoPrefix = voteStr.split(voteOptionPrefix)[1];

return voteNoPrefix.charAt(0) + voteNoPrefix.slice(1).toLowerCase().replace(/_/g, " ");
};
2 changes: 2 additions & 0 deletions lib/txMsgHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const gasOfMsg = (msgType: MsgTypeUrl): number => {
return 400_000;
case MsgTypeUrls.CreateVestingAccount:
return 100_000;
case MsgTypeUrls.Vote:
return 100_000;
case MsgTypeUrls.Transfer:
return 180_000;
case MsgTypeUrls.Execute:
Expand Down
3 changes: 3 additions & 0 deletions types/txMsg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
MsgSetWithdrawAddress,
MsgWithdrawDelegatorReward,
} from "cosmjs-types/cosmos/distribution/v1beta1/tx";
import { MsgVote } from "cosmjs-types/cosmos/gov/v1beta1/tx";
import {
MsgBeginRedelegate,
MsgDelegate,
Expand All @@ -26,6 +27,7 @@ export const MsgTypeUrls = {
Delegate: "/cosmos.staking.v1beta1.MsgDelegate",
Undelegate: "/cosmos.staking.v1beta1.MsgUndelegate",
CreateVestingAccount: "/cosmos.vesting.v1beta1.MsgCreateVestingAccount",
Vote: "/cosmos.gov.v1beta1.MsgVote",
Transfer: "/ibc.applications.transfer.v1.MsgTransfer",
Execute: "/cosmwasm.wasm.v1.MsgExecuteContract",
Instantiate: "/cosmwasm.wasm.v1.MsgInstantiateContract",
Expand All @@ -44,6 +46,7 @@ export const MsgCodecs = {
[MsgTypeUrls.Delegate]: MsgDelegate,
[MsgTypeUrls.Undelegate]: MsgUndelegate,
[MsgTypeUrls.CreateVestingAccount]: MsgCreateVestingAccount,
[MsgTypeUrls.Vote]: MsgVote,
[MsgTypeUrls.Transfer]: MsgTransfer,
[MsgTypeUrls.Execute]: MsgExecuteContract,
[MsgTypeUrls.Instantiate]: MsgInstantiateContract,
Expand Down
Loading