From 7025520d7765d5e1bcb84c94ed0e3ceac228651d Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:21:31 +0200 Subject: [PATCH 1/5] Add vote helpers --- lib/gov.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/gov.ts diff --git a/lib/gov.ts b/lib/gov.ts new file mode 100644 index 0000000..eed7848 --- /dev/null +++ b/lib/gov.ts @@ -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, " "); +}; From 8fb3b3fb3f759c49877e5546ad6984935f6c32dd Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:23:53 +0200 Subject: [PATCH 2/5] Add new msg type --- components/dataViews/TransactionInfo/index.tsx | 3 +++ components/forms/CreateTxForm/MsgForm/index.tsx | 3 +++ components/forms/CreateTxForm/index.tsx | 8 ++++++++ lib/txMsgHelpers.ts | 2 ++ types/txMsg.ts | 3 +++ 5 files changed, 19 insertions(+) diff --git a/components/dataViews/TransactionInfo/index.tsx b/components/dataViews/TransactionInfo/index.tsx index d0737fa..344b7e5 100644 --- a/components/dataViews/TransactionInfo/index.tsx +++ b/components/dataViews/TransactionInfo/index.tsx @@ -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) { @@ -34,6 +35,8 @@ const TxMsgDetails = ({ typeUrl, value: msgValue }: EncodeObject) => { return ; case MsgTypeUrls.CreateVestingAccount: return ; + case MsgTypeUrls.Vote: + return ; case MsgTypeUrls.Transfer: return ; case MsgTypeUrls.Execute: diff --git a/components/forms/CreateTxForm/MsgForm/index.tsx b/components/forms/CreateTxForm/MsgForm/index.tsx index 82c840a..122f12a 100644 --- a/components/forms/CreateTxForm/MsgForm/index.tsx +++ b/components/forms/CreateTxForm/MsgForm/index.tsx @@ -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; @@ -37,6 +38,8 @@ const MsgForm = ({ msgType, senderAddress, ...restProps }: MsgFormProps) => { return ; case MsgTypeUrls.CreateVestingAccount: return ; + case MsgTypeUrls.Vote: + return ; case MsgTypeUrls.Transfer: return ; case MsgTypeUrls.Execute: diff --git a/components/forms/CreateTxForm/index.tsx b/components/forms/CreateTxForm/index.tsx index 10e50a4..96fa82a 100644 --- a/components/forms/CreateTxForm/index.tsx +++ b/components/forms/CreateTxForm/index.tsx @@ -180,6 +180,14 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro +
+ +
    +
  • +
  • +
+
    diff --git a/lib/txMsgHelpers.ts b/lib/txMsgHelpers.ts index 3fa8f2c..838077d 100644 --- a/lib/txMsgHelpers.ts +++ b/lib/txMsgHelpers.ts @@ -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: diff --git a/types/txMsg.ts b/types/txMsg.ts index 00a4f0c..166b96b 100644 --- a/types/txMsg.ts +++ b/types/txMsg.ts @@ -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, @@ -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", @@ -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, From 9ccdd9928656bdd66cb3034b081c8f277f5df210 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:24:04 +0200 Subject: [PATCH 3/5] Add new detail view --- .../TransactionInfo/TxMsgVoteDetails.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 components/dataViews/TransactionInfo/TxMsgVoteDetails.tsx diff --git a/components/dataViews/TransactionInfo/TxMsgVoteDetails.tsx b/components/dataViews/TransactionInfo/TxMsgVoteDetails.tsx new file mode 100644 index 0000000..9f3e4c7 --- /dev/null +++ b/components/dataViews/TransactionInfo/TxMsgVoteDetails.tsx @@ -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 ( + <> +
  • +

    MsgVote

    +
  • +
  • + +
    {msgValue.proposalId.toString()}
    +
  • +
  • + +
    {printVoteOption(msgValue.option)}
    +
  • + + + ); +}; + +export default TxMsgVoteDetails; From 272b053edf00bf7cc752062de1c6bb07845c100c Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:24:16 +0200 Subject: [PATCH 4/5] Add new msg vote form --- .../CreateTxForm/MsgForm/MsgVoteForm.tsx | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 components/forms/CreateTxForm/MsgForm/MsgVoteForm.tsx diff --git a/components/forms/CreateTxForm/MsgForm/MsgVoteForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgVoteForm.tsx new file mode 100644 index 0000000..7618e25 --- /dev/null +++ b/components/forms/CreateTxForm/MsgForm/MsgVoteForm.tsx @@ -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 ( + + +

    MsgVote

    +
    + { + setProposalId(target.value); + setProposalIdError(""); + }} + error={proposalIdError} + /> +
    +
    + +