From c2d51d179fabfa47a032617f52f0fee75ab1e91b Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:20:46 +0200 Subject: [PATCH 01/19] Tweak styles for n messages in TransactionInfo --- .../dataViews/TransactionInfo/index.tsx | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/components/dataViews/TransactionInfo/index.tsx b/components/dataViews/TransactionInfo/index.tsx index 1537739..593550b 100644 --- a/components/dataViews/TransactionInfo/index.tsx +++ b/components/dataViews/TransactionInfo/index.tsx @@ -23,43 +23,46 @@ const TransactionInfo = ({ tx }: Props) => { const { state } = useAppContext(); return ( - + <> - + ); }; From 941f9151d650f275d36ccfb730c03708d7c66489 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:21:20 +0200 Subject: [PATCH 02/19] Support TxMsg in variants validation --- lib/txMsgHelpers.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/txMsgHelpers.ts b/lib/txMsgHelpers.ts index c23bd99..cd35c2c 100644 --- a/lib/txMsgHelpers.ts +++ b/lib/txMsgHelpers.ts @@ -1,5 +1,6 @@ import { EncodeObject } from "@cosmjs/proto-signing"; import { + TxMsg, TxMsgClaimRewards, TxMsgDelegate, TxMsgRedelegate, @@ -7,7 +8,7 @@ import { TxMsgUndelegate, } from "../types/txMsg"; -const isTxMsgSend = (msg: EncodeObject): msg is TxMsgSend => +const isTxMsgSend = (msg: TxMsg | EncodeObject): msg is TxMsgSend => msg.typeUrl === "/cosmos.bank.v1beta1.MsgSend" && "value" in msg && "fromAddress" in msg.value && @@ -17,7 +18,7 @@ const isTxMsgSend = (msg: EncodeObject): msg is TxMsgSend => !!msg.value.toAddress && !!msg.value.amount.length; -const isTxMsgDelegate = (msg: EncodeObject): msg is TxMsgDelegate => +const isTxMsgDelegate = (msg: TxMsg | EncodeObject): msg is TxMsgDelegate => msg.typeUrl === "/cosmos.staking.v1beta1.MsgDelegate" && "value" in msg && "delegatorAddress" in msg.value && @@ -27,7 +28,7 @@ const isTxMsgDelegate = (msg: EncodeObject): msg is TxMsgDelegate => !!msg.value.validatorAddress && !!msg.value.amount.length; -const isTxMsgUndelegate = (msg: EncodeObject): msg is TxMsgUndelegate => +const isTxMsgUndelegate = (msg: TxMsg | EncodeObject): msg is TxMsgUndelegate => msg.typeUrl === "/cosmos.staking.v1beta1.MsgUndelegate" && "value" in msg && "delegatorAddress" in msg.value && @@ -37,7 +38,7 @@ const isTxMsgUndelegate = (msg: EncodeObject): msg is TxMsgUndelegate => !!msg.value.validatorAddress && !!msg.value.amount.length; -const isTxMsgRedelegate = (msg: EncodeObject): msg is TxMsgRedelegate => +const isTxMsgRedelegate = (msg: TxMsg | EncodeObject): msg is TxMsgRedelegate => msg.typeUrl === "/cosmos.staking.v1beta1.MsgBeginRedelegate" && "value" in msg && "delegatorAddress" in msg.value && @@ -49,7 +50,7 @@ const isTxMsgRedelegate = (msg: EncodeObject): msg is TxMsgRedelegate => !!msg.value.validatorDstAddress && !!msg.value.amount.length; -const isTxMsgClaimRewards = (msg: EncodeObject): msg is TxMsgClaimRewards => +const isTxMsgClaimRewards = (msg: TxMsg | EncodeObject): msg is TxMsgClaimRewards => msg.typeUrl === "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" && "value" in msg && "delegatorAddress" in msg.value && From 040d662093774944b34122d518cd68f7b87154b6 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:23:28 +0200 Subject: [PATCH 03/19] Add MsgGetters. Add n msgs. Keyprop calculation --- components/forms/CreateTxForm/index.tsx | 99 +++++++++++++++---------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/components/forms/CreateTxForm/index.tsx b/components/forms/CreateTxForm/index.tsx index 1ccef67..6c9ae41 100644 --- a/components/forms/CreateTxForm/index.tsx +++ b/components/forms/CreateTxForm/index.tsx @@ -2,34 +2,33 @@ import { Account, calculateFee } from "@cosmjs/stargate"; import { assert } from "@cosmjs/utils"; import axios from "axios"; import { NextRouter, withRouter } from "next/router"; -import { useState } from "react"; +import { useRef, useState } from "react"; import { useAppContext } from "../../../context/AppContext"; -import { capitalizeFirstLetter } from "../../../lib/displayHelpers"; import { TxMsg, TxType } from "../../../types/txMsg"; import Button from "../../inputs/Button"; import Input from "../../inputs/Input"; import StackableContainer from "../../layout/StackableContainer"; import MsgForm from "./MsgForm"; -interface CreateTxFormProps { - readonly router: NextRouter; - readonly txType: TxType; - readonly senderAddress: string; - readonly accountOnChain: Account; - readonly closeForm: () => void; +export interface MsgGetter { + readonly isMsgValid: (msg: TxMsg) => msg is TxMsg; + readonly msg: TxMsg; } -const CreateTxForm = ({ - router, - txType, - senderAddress, - accountOnChain, - closeForm, -}: CreateTxFormProps) => { +const getMsgFormKey = (txType: TxType, msg: TxMsg) => JSON.stringify({ txType, msg }); + +interface CreateTxFormProps { + readonly router: NextRouter; + readonly senderAddress: string; + readonly accountOnChain: Account; +} + +const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormProps) => { const { state } = useAppContext(); const [processing, setProcessing] = useState(false); - const [checkAndGetMsg, setCheckAndGetMsg] = useState<() => TxMsg | null>(); + const [msgTypes, setMsgTypes] = useState([]); + const msgGetters = useRef([]); const [memo, setMemo] = useState(""); const [gasLimit, setGasLimit] = useState(200000); const [gasLimitError, setGasLimitError] = useState(""); @@ -37,12 +36,24 @@ const CreateTxForm = ({ const gasPrice = state.chain.gasPrice; assert(gasPrice, "gasPrice missing"); + const addTxType = (newTxType: TxType) => { + setMsgTypes((oldMsgTypes) => { + const newTxTypes = [...oldMsgTypes, newTxType]; + setGasLimit((oldGasLimit) => (oldGasLimit / (oldMsgTypes.length || 1)) * newTxTypes.length); + return newTxTypes; + }); + }; + const createTx = async () => { try { assert(typeof accountOnChain.accountNumber === "number", "accountNumber missing"); - assert(!!checkAndGetMsg, "form filled incorrectly"); - const msg = checkAndGetMsg(); - if (!msg) return; + assert(!!msgGetters.current.length, "form filled incorrectly"); + + const msgs = msgGetters.current + .filter(({ isMsgValid, msg }) => isMsgValid(msg)) + .map(({ msg }) => msg); + + if (!msgs.length || msgs.length !== msgTypes.length) return; if (!Number.isSafeInteger(gasLimit) || gasLimit <= 0) { setGasLimitError("gas limit must be a positive integer"); @@ -55,7 +66,7 @@ const CreateTxForm = ({ accountNumber: accountOnChain.accountNumber, sequence: accountOnChain.sequence, chainId: state.chain.chainId, - msgs: [msg], + msgs, fee: calculateFee(gasLimit, gasPrice), memo, }; @@ -76,15 +87,32 @@ const CreateTxForm = ({ return ( - -

Create New {capitalizeFirstLetter(txType)} Transaction

- +

Create New Transaction

+ {msgTypes.map((txType, index) => ( + { + msgGetters.current = [ + ...msgGetters.current.slice(0, index), + msgGetter, + ...msgGetters.current.slice(index + 1), + ]; + }} + deleteMsg={() => { + msgGetters.current.splice(index, 1); + setMsgTypes((oldMsgTypes) => { + const newMsgTypes: TxType[] = oldMsgTypes.slice(); + newMsgTypes.splice(index, 1); + setGasLimit( + (oldGasLimit) => (oldGasLimit / oldMsgTypes.length) * (newMsgTypes.length || 1), + ); + return newMsgTypes; + }); + }} + /> + ))}
); From 749484c527a51c78ddc1c4738d0950dfabd65649 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:23:51 +0200 Subject: [PATCH 04/19] Add msgs buttons --- components/forms/CreateTxForm/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/forms/CreateTxForm/index.tsx b/components/forms/CreateTxForm/index.tsx index 6c9ae41..67ff3d8 100644 --- a/components/forms/CreateTxForm/index.tsx +++ b/components/forms/CreateTxForm/index.tsx @@ -139,6 +139,13 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro onChange={({ target }) => setMemo(target.value)} />
+ + +

MsgDelegate

- +
); }; From 2e0a80dabff3c24861168024f113ea62f6aba4ac Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:25:24 +0200 Subject: [PATCH 09/19] Use msgGetter and deleteMsg in MsgUndelegateForm --- .../MsgForm/MsgUndelegateForm.tsx | 105 +++++++++++------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgUndelegateForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgUndelegateForm.tsx index 2e32184..3536e99 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgUndelegateForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgUndelegateForm.tsx @@ -1,17 +1,25 @@ import { Decimal } from "@cosmjs/math"; import { assert } from "@cosmjs/utils"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; +import { MsgGetter } from ".."; import { useAppContext } from "../../../../context/AppContext"; import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers"; +import { isTxMsgUndelegate } from "../../../../lib/txMsgHelpers"; import { TxMsg, TxMsgUndelegate } from "../../../../types/txMsg"; import Input from "../../../inputs/Input"; +import StackableContainer from "../../../layout/StackableContainer"; interface MsgUndelegateFormProps { readonly delegatorAddress: string; - readonly setCheckAndGetMsg: Dispatch TxMsg | null) | undefined>>; + readonly setMsgGetter: (msgGetter: MsgGetter) => void; + readonly deleteMsg: () => void; } -const MsgUndelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgUndelegateFormProps) => { +const MsgUndelegateForm = ({ + delegatorAddress, + setMsgGetter, + deleteMsg, +}: MsgUndelegateFormProps) => { const { state } = useAppContext(); assert(state.chain.addressPrefix, "addressPrefix missing"); @@ -21,41 +29,52 @@ const MsgUndelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgUndelegat const [validatorAddressError, setValidatorAddressError] = useState(""); const [amountError, setAmountError] = useState(""); - const checkAndGetMsg = useCallback(() => { - assert(state.chain.addressPrefix, "addressPrefix missing"); - assert(state.chain.denom, "denom missing"); + useEffect(() => { + try { + assert(state.chain.denom, "denom missing"); - const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix); - if (addressErrorMsg) { - setValidatorAddressError( - `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, - ); - return null; - } + setValidatorAddressError(""); + setAmountError(""); - if (!amount || Number(amount) <= 0) { - setAmountError("Amount must be greater than 0"); - return null; - } + const isMsgValid = (msg: TxMsg): msg is TxMsgUndelegate => { + assert(state.chain.addressPrefix, "addressPrefix missing"); - const amountInAtomics = Decimal.fromUserInput( - amount, - Number(state.chain.displayDenomExponent), - ).atomics; + const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix); + if (addressErrorMsg) { + setValidatorAddressError( + `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, + ); + return false; + } - const msg: TxMsgUndelegate = { - typeUrl: "/cosmos.staking.v1beta1.MsgUndelegate", - value: { - delegatorAddress, - validatorAddress, - amount: [{ amount: amountInAtomics, denom: state.chain.denom }], - }, - }; + if (!amount || Number(amount) <= 0) { + setAmountError("Amount must be greater than 0"); + return false; + } - return msg; + return isTxMsgUndelegate(msg); + }; + + const amountInAtomics = Decimal.fromUserInput( + amount || "0", + Number(state.chain.displayDenomExponent), + ).atomics; + + const msg: TxMsgUndelegate = { + typeUrl: "/cosmos.staking.v1beta1.MsgUndelegate", + value: { + delegatorAddress, + validatorAddress, + amount: [{ amount: amountInAtomics, denom: state.chain.denom }], + }, + }; + + setMsgGetter({ isMsgValid, msg }); + } catch {} }, [ amount, delegatorAddress, + setMsgGetter, state.chain.addressPrefix, state.chain.chainId, state.chain.denom, @@ -63,12 +82,12 @@ const MsgUndelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgUndelegat validatorAddress, ]); - useEffect(() => { - setCheckAndGetMsg(() => checkAndGetMsg); - }, [checkAndGetMsg, setCheckAndGetMsg]); - return ( - <> + + +

MsgUndelegate

- +
); }; From 2133aa9c8c36ed75e1905f3855c0765521e53d09 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:25:35 +0200 Subject: [PATCH 10/19] Use msgGetter and deleteMsg in MsgRedelegateForm --- .../MsgForm/MsgRedelegateForm.tsx | 122 +++++++++++------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgRedelegateForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgRedelegateForm.tsx index d0c974f..c95ed81 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgRedelegateForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgRedelegateForm.tsx @@ -1,17 +1,25 @@ import { Decimal } from "@cosmjs/math"; import { assert } from "@cosmjs/utils"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; +import { MsgGetter } from ".."; import { useAppContext } from "../../../../context/AppContext"; import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers"; +import { isTxMsgRedelegate } from "../../../../lib/txMsgHelpers"; import { TxMsg, TxMsgRedelegate } from "../../../../types/txMsg"; import Input from "../../../inputs/Input"; +import StackableContainer from "../../../layout/StackableContainer"; interface MsgRedelegateFormProps { readonly delegatorAddress: string; - readonly setCheckAndGetMsg: Dispatch TxMsg | null) | undefined>>; + readonly setMsgGetter: (msgGetter: MsgGetter) => void; + readonly deleteMsg: () => void; } -const MsgRedelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgRedelegateFormProps) => { +const MsgRedelegateForm = ({ + delegatorAddress, + setMsgGetter, + deleteMsg, +}: MsgRedelegateFormProps) => { const { state } = useAppContext(); assert(state.chain.addressPrefix, "addressPrefix missing"); @@ -23,50 +31,62 @@ const MsgRedelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgRedelegat const [validatorDstAddressError, setValidatorDstAddressError] = useState(""); const [amountError, setAmountError] = useState(""); - const checkAndGetMsg = useCallback(() => { - assert(state.chain.addressPrefix, "addressPrefix missing"); - assert(state.chain.denom, "denom missing"); + useEffect(() => { + try { + assert(state.chain.denom, "denom missing"); - const srcAddressErrorMsg = checkAddress(validatorSrcAddress, state.chain.addressPrefix); - if (srcAddressErrorMsg) { - setValidatorSrcAddressError( - `Invalid address for network ${state.chain.chainId}: ${srcAddressErrorMsg}`, - ); - return null; - } + setValidatorSrcAddressError(""); + setValidatorDstAddressError(""); + setAmountError(""); - const dstAddressErrorMsg = checkAddress(validatorDstAddress, state.chain.addressPrefix); - if (dstAddressErrorMsg) { - setValidatorDstAddressError( - `Invalid address for network ${state.chain.chainId}: ${dstAddressErrorMsg}`, - ); - return null; - } + const isMsgValid = (msg: TxMsg): msg is TxMsgRedelegate => { + assert(state.chain.addressPrefix, "addressPrefix missing"); - if (!amount || Number(amount) <= 0) { - setAmountError("Amount must be greater than 0"); - return null; - } + const srcAddressErrorMsg = checkAddress(validatorSrcAddress, state.chain.addressPrefix); + if (srcAddressErrorMsg) { + setValidatorSrcAddressError( + `Invalid address for network ${state.chain.chainId}: ${srcAddressErrorMsg}`, + ); + return false; + } - const amountInAtomics = Decimal.fromUserInput( - amount, - Number(state.chain.displayDenomExponent), - ).atomics; + const dstAddressErrorMsg = checkAddress(validatorDstAddress, state.chain.addressPrefix); + if (dstAddressErrorMsg) { + setValidatorDstAddressError( + `Invalid address for network ${state.chain.chainId}: ${dstAddressErrorMsg}`, + ); + return false; + } - const msg: TxMsgRedelegate = { - typeUrl: "/cosmos.staking.v1beta1.MsgBeginRedelegate", - value: { - delegatorAddress, - validatorSrcAddress, - validatorDstAddress, - amount: [{ amount: amountInAtomics, denom: state.chain.denom }], - }, - }; + if (!amount || Number(amount) <= 0) { + setAmountError("Amount must be greater than 0"); + return false; + } - return msg; + return isTxMsgRedelegate(msg); + }; + + const amountInAtomics = Decimal.fromUserInput( + amount || "0", + Number(state.chain.displayDenomExponent), + ).atomics; + + const msg: TxMsgRedelegate = { + typeUrl: "/cosmos.staking.v1beta1.MsgBeginRedelegate", + value: { + delegatorAddress, + validatorSrcAddress, + validatorDstAddress, + amount: [{ amount: amountInAtomics, denom: state.chain.denom }], + }, + }; + + setMsgGetter({ isMsgValid, msg }); + } catch {} }, [ amount, delegatorAddress, + setMsgGetter, state.chain.addressPrefix, state.chain.chainId, state.chain.denom, @@ -75,12 +95,12 @@ const MsgRedelegateForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgRedelegat validatorSrcAddress, ]); - useEffect(() => { - setCheckAndGetMsg(() => checkAndGetMsg); - }, [checkAndGetMsg, setCheckAndGetMsg]); - return ( - <> + + +

MsgBeginRedelegate

- +
); }; From b8501be6747b0343f40749a2be3d4f9c4ac9515d Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 5 May 2023 13:25:45 +0200 Subject: [PATCH 11/19] Use msgGetter and deleteMsg in MsgClaimRewardsForm --- .../MsgForm/MsgClaimRewardsForm.tsx | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx index 24247c9..1251886 100644 --- a/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx +++ b/components/forms/CreateTxForm/MsgForm/MsgClaimRewardsForm.tsx @@ -1,47 +1,69 @@ import { assert } from "@cosmjs/utils"; -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; +import { MsgGetter } from ".."; import { useAppContext } from "../../../../context/AppContext"; import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers"; +import { isTxMsgClaimRewards } from "../../../../lib/txMsgHelpers"; import { TxMsg, TxMsgClaimRewards } from "../../../../types/txMsg"; import Input from "../../../inputs/Input"; +import StackableContainer from "../../../layout/StackableContainer"; interface MsgClaimRewardsFormProps { readonly delegatorAddress: string; - readonly setCheckAndGetMsg: Dispatch TxMsg | null) | undefined>>; + readonly setMsgGetter: (msgGetter: MsgGetter) => void; + readonly deleteMsg: () => void; } -const MsgClaimRewardsForm = ({ delegatorAddress, setCheckAndGetMsg }: MsgClaimRewardsFormProps) => { +const MsgClaimRewardsForm = ({ + delegatorAddress, + setMsgGetter, + deleteMsg, +}: MsgClaimRewardsFormProps) => { const { state } = useAppContext(); assert(state.chain.addressPrefix, "addressPrefix missing"); const [validatorAddress, setValidatorAddress] = useState(""); const [validatorAddressError, setValidatorAddressError] = useState(""); - const checkAndGetMsg = useCallback(() => { - assert(state.chain.addressPrefix, "addressPrefix missing"); - - const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix); - if (addressErrorMsg) { - setValidatorAddressError( - `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, - ); - return null; - } - - const msg: TxMsgClaimRewards = { - typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", - value: { delegatorAddress, validatorAddress }, - }; - - return msg; - }, [delegatorAddress, state.chain.addressPrefix, state.chain.chainId, validatorAddress]); - useEffect(() => { - setCheckAndGetMsg(() => checkAndGetMsg); - }, [checkAndGetMsg, setCheckAndGetMsg]); + try { + setValidatorAddressError(""); + + const isMsgValid = (msg: TxMsg): msg is TxMsgClaimRewards => { + assert(state.chain.addressPrefix, "addressPrefix missing"); + + const addressErrorMsg = checkAddress(validatorAddress, state.chain.addressPrefix); + if (addressErrorMsg) { + setValidatorAddressError( + `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, + ); + return false; + } + + return isTxMsgClaimRewards(msg); + }; + + const msg: TxMsgClaimRewards = { + typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + value: { delegatorAddress, validatorAddress }, + }; + + setMsgGetter({ isMsgValid, msg }); + } catch {} + }, [ + delegatorAddress, + setMsgGetter, + state.chain.addressPrefix, + state.chain.chainId, + validatorAddress, + ]); return ( - <> + + +

MsgWithdrawDelegatorReward

- +
); }; From 59699e39416c41ee95210e670b4fdbf0f9b3a5b0 Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 19 May 2023 13:44:19 +0200 Subject: [PATCH 12/19] Add MsgSetWithdrawAddress types and helpers --- lib/txMsgHelpers.ts | 18 +++++++++++++++++- types/txMsg.ts | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/txMsgHelpers.ts b/lib/txMsgHelpers.ts index cd35c2c..7ef92e2 100644 --- a/lib/txMsgHelpers.ts +++ b/lib/txMsgHelpers.ts @@ -5,6 +5,7 @@ import { TxMsgDelegate, TxMsgRedelegate, TxMsgSend, + TxMsgSetWithdrawAddress, TxMsgUndelegate, } from "../types/txMsg"; @@ -58,4 +59,19 @@ const isTxMsgClaimRewards = (msg: TxMsg | EncodeObject): msg is TxMsgClaimReward !!msg.value.delegatorAddress && !!msg.value.validatorAddress; -export { isTxMsgSend, isTxMsgDelegate, isTxMsgUndelegate, isTxMsgRedelegate, isTxMsgClaimRewards }; +const isTxMsgSetWithdrawAddress = (msg: TxMsg | EncodeObject): msg is TxMsgSetWithdrawAddress => + msg.typeUrl === "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress" && + "value" in msg && + "delegatorAddress" in msg.value && + "withdrawAddress" in msg.value && + !!msg.value.delegatorAddress && + !!msg.value.withdrawAddress; + +export { + isTxMsgSend, + isTxMsgDelegate, + isTxMsgUndelegate, + isTxMsgRedelegate, + isTxMsgClaimRewards, + isTxMsgSetWithdrawAddress, +}; diff --git a/types/txMsg.ts b/types/txMsg.ts index b3d2ed4..30dbb4e 100644 --- a/types/txMsg.ts +++ b/types/txMsg.ts @@ -1,11 +1,18 @@ -export type TxType = "send" | "delegate" | "undelegate" | "redelegate" | "claimRewards"; +export type TxType = + | "send" + | "delegate" + | "undelegate" + | "redelegate" + | "claimRewards" + | "setWithdrawAddress"; export type TxMsg = | TxMsgSend | TxMsgDelegate | TxMsgUndelegate | TxMsgRedelegate - | TxMsgClaimRewards; + | TxMsgClaimRewards + | TxMsgSetWithdrawAddress; export interface TxMsgSend { readonly typeUrl: "/cosmos.bank.v1beta1.MsgSend"; @@ -51,3 +58,11 @@ export interface TxMsgClaimRewards { readonly validatorAddress: string; }; } + +export interface TxMsgSetWithdrawAddress { + readonly typeUrl: "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress"; + readonly value: { + readonly delegatorAddress: string; + readonly withdrawAddress: string; + }; +} From dfa32c4ada29c0e7e71cd5a948757c983891538b Mon Sep 17 00:00:00 2001 From: abefernan <44572727+abefernan@users.noreply.github.com> Date: Fri, 19 May 2023 13:45:01 +0200 Subject: [PATCH 13/19] Add and use MsgSetWithdrawAddressForm --- .../MsgForm/MsgSetWithdrawAddressForm.tsx | 97 +++++++++++++++++++ .../forms/CreateTxForm/MsgForm/index.tsx | 3 + components/forms/CreateTxForm/index.tsx | 1 + 3 files changed, 101 insertions(+) create mode 100644 components/forms/CreateTxForm/MsgForm/MsgSetWithdrawAddressForm.tsx diff --git a/components/forms/CreateTxForm/MsgForm/MsgSetWithdrawAddressForm.tsx b/components/forms/CreateTxForm/MsgForm/MsgSetWithdrawAddressForm.tsx new file mode 100644 index 0000000..530ee56 --- /dev/null +++ b/components/forms/CreateTxForm/MsgForm/MsgSetWithdrawAddressForm.tsx @@ -0,0 +1,97 @@ +import { assert } from "@cosmjs/utils"; +import { useEffect, useState } from "react"; +import { MsgGetter } from ".."; +import { useAppContext } from "../../../../context/AppContext"; +import { checkAddress, exampleAddress } from "../../../../lib/displayHelpers"; +import { isTxMsgSetWithdrawAddress } from "../../../../lib/txMsgHelpers"; +import { TxMsg, TxMsgSetWithdrawAddress } from "../../../../types/txMsg"; +import Input from "../../../inputs/Input"; +import StackableContainer from "../../../layout/StackableContainer"; + +interface MsgSetWithdrawAddressFormProps { + readonly delegatorAddress: string; + readonly setMsgGetter: (msgGetter: MsgGetter) => void; + readonly deleteMsg: () => void; +} + +const MsgSetWithdrawAddressForm = ({ + delegatorAddress, + setMsgGetter, + deleteMsg, +}: MsgSetWithdrawAddressFormProps) => { + const { state } = useAppContext(); + assert(state.chain.addressPrefix, "addressPrefix missing"); + + const [withdrawAddress, setWithdrawAddress] = useState(""); + const [withdrawAddressError, setWithdrawAddressError] = useState(""); + + useEffect(() => { + try { + setWithdrawAddressError(""); + + const isMsgValid = (msg: TxMsg): msg is TxMsgSetWithdrawAddress => { + assert(state.chain.addressPrefix, "addressPrefix missing"); + + const addressErrorMsg = checkAddress(withdrawAddress, state.chain.addressPrefix); + if (addressErrorMsg) { + setWithdrawAddressError( + `Invalid address for network ${state.chain.chainId}: ${addressErrorMsg}`, + ); + return false; + } + + return isTxMsgSetWithdrawAddress(msg); + }; + + const msg: TxMsgSetWithdrawAddress = { + typeUrl: "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + value: { delegatorAddress, withdrawAddress }, + }; + + setMsgGetter({ isMsgValid, msg }); + } catch {} + }, [ + delegatorAddress, + setMsgGetter, + state.chain.addressPrefix, + state.chain.chainId, + withdrawAddress, + ]); + + return ( + + +

MsgSetWithdrawAddress

+
+ setWithdrawAddress(target.value)} + error={withdrawAddressError} + placeholder={`E.g. ${exampleAddress(0, state.chain.addressPrefix)}`} + /> +
+ +
+ ); +}; + +export default MsgSetWithdrawAddressForm; diff --git a/components/forms/CreateTxForm/MsgForm/index.tsx b/components/forms/CreateTxForm/MsgForm/index.tsx index fea65d0..a0f6165 100644 --- a/components/forms/CreateTxForm/MsgForm/index.tsx +++ b/components/forms/CreateTxForm/MsgForm/index.tsx @@ -4,6 +4,7 @@ import MsgClaimRewardsForm from "./MsgClaimRewardsForm"; import MsgDelegateForm from "./MsgDelegateForm"; import MsgRedelegateForm from "./MsgRedelegateForm"; import MsgSendForm from "./MsgSendForm"; +import MsgSetWithdrawAddressForm from "./MsgSetWithdrawAddressForm"; import MsgUndelegateForm from "./MsgUndelegateForm"; type MsgFormProps = { @@ -26,6 +27,8 @@ const MsgForm = ({ txType, senderAddress, ...restProps }: MsgFormProps) => { return ; case "claimRewards": return ; + case "setWithdrawAddress": + return ; default: return null; } diff --git a/components/forms/CreateTxForm/index.tsx b/components/forms/CreateTxForm/index.tsx index 67ff3d8..4fce992 100644 --- a/components/forms/CreateTxForm/index.tsx +++ b/components/forms/CreateTxForm/index.tsx @@ -145,6 +145,7 @@ const CreateTxForm = ({ router, senderAddress, accountOnChain }: CreateTxFormPro