diff --git a/components/dataViews/MultisigMembers.tsx b/components/dataViews/MultisigMembers.tsx index 5ff09f2..9f6a00e 100644 --- a/components/dataViews/MultisigMembers.tsx +++ b/components/dataViews/MultisigMembers.tsx @@ -6,7 +6,7 @@ import StackableContainer from "../layout/StackableContainer"; interface Props { /** Addresses of the multisig members */ members: string[]; - threshold: number; + threshold: string; } const MultisigMembers = (props: Props) => ( diff --git a/components/dataViews/ThresholdInfo.tsx b/components/dataViews/ThresholdInfo.tsx index fce431d..676e167 100644 --- a/components/dataViews/ThresholdInfo.tsx +++ b/components/dataViews/ThresholdInfo.tsx @@ -1,14 +1,15 @@ import React from "react"; +import { MultisigThresholdPubkey } from "@cosmjs/amino"; + import { DbSignature } from "../../types"; import StackableContainer from "../layout/StackableContainer"; -import { AccountWithPubkey } from "../../lib/multisigHelpers"; interface Props { signatures: DbSignature[]; - account: AccountWithPubkey; + pubkey: MultisigThresholdPubkey; } -const ThresholdInfo = ({ signatures, account }: Props) => ( +const ThresholdInfo = ({ signatures, pubkey }: Props) => ( Signatures @@ -21,7 +22,7 @@ const ThresholdInfo = ({ signatures, account }: Props) => ( {signatures.length} of - {account.pubkey.value.threshold} + {pubkey.value.threshold} signatures complete diff --git a/components/forms/TransactionForm.tsx b/components/forms/TransactionForm.tsx index 21a4b6b..ee28927 100644 --- a/components/forms/TransactionForm.tsx +++ b/components/forms/TransactionForm.tsx @@ -1,5 +1,5 @@ import axios from "axios"; -import { calculateFee } from "@cosmjs/stargate"; +import { Account, calculateFee } from "@cosmjs/stargate"; import { Decimal } from "@cosmjs/math"; import { assert } from "@cosmjs/utils"; import React, { useState } from "react"; @@ -10,11 +10,10 @@ import Button from "../inputs/Button"; import Input from "../inputs/Input"; import StackableContainer from "../layout/StackableContainer"; import { checkAddress, exampleAddress } from "../../lib/displayHelpers"; -import { AccountWithPubkey } from "../../lib/multisigHelpers"; interface Props { address: string | null; - accountOnChain: AccountWithPubkey | null; + accountOnChain: Account | null; router: NextRouter; closeForm: () => void; } diff --git a/lib/multisigHelpers.ts b/lib/multisigHelpers.ts index 9cd3143..3fb5ee0 100644 --- a/lib/multisigHelpers.ts +++ b/lib/multisigHelpers.ts @@ -1,7 +1,13 @@ import axios from "axios"; -import { createMultisigThresholdPubkey, Pubkey, pubkeyToAddress } from "@cosmjs/amino"; +import { + createMultisigThresholdPubkey, + isMultisigThresholdPubkey, + MultisigThresholdPubkey, + pubkeyToAddress, +} from "@cosmjs/amino"; import { Account } from "@cosmjs/stargate"; import { StargateClient } from "@cosmjs/stargate"; +import { assert } from "@cosmjs/utils"; /** * Turns array of compressed Secp256k1 pubkeys @@ -40,31 +46,31 @@ const createMultisigFromCompressedSecp256k1Pubkeys = async ( return res.data.address; }; -/** Like Account but with non-optional pubkey */ -export type AccountWithPubkey = Account & { readonly pubkey: Pubkey }; - /** * This gets a multisigs account (pubkey, sequence, account number, etc) from - * a node and/or the api if the multisig was made on this app + * a node and/or the api if the multisig was made on this app. * - * @param {string} address The multisig address - * @param client A connected stargate cosmoshub client - * @return {object} The multisig account. + * The public key should always be available, either on chain or in the app's database. + * The account is only available when the there was any on-chain activity such as + * receipt of tokens. */ const getMultisigAccount = async ( address: string, client: StargateClient, -): Promise => { +): Promise<[MultisigThresholdPubkey, Account | null]> => { // we need the multisig pubkeys to create transactions, if the multisig // is new, and has never submitted a transaction its pubkeys will not be // available from a node. If the multisig was created with this instance // of this tool its pubkey will be available in the fauna datastore const accountOnChain = await client.getAccount(address); const chainId = await client.getChainId(); - if (!accountOnChain) return null; - let pubkey: Pubkey; - if (accountOnChain.pubkey) { + let pubkey: MultisigThresholdPubkey; + if (accountOnChain?.pubkey) { + assert( + isMultisigThresholdPubkey(accountOnChain.pubkey), + "Pubkey on chain is not of type MultisigThreshold", + ); pubkey = accountOnChain.pubkey; } else { console.log("No pubkey on chain for: ", address); @@ -76,10 +82,7 @@ const getMultisigAccount = async ( pubkey = JSON.parse(res.data.pubkeyJSON); } - return { - ...accountOnChain, - pubkey: pubkey, - }; + return [pubkey, accountOnChain]; }; export { createMultisigFromCompressedSecp256k1Pubkeys, getMultisigAccount }; diff --git a/pages/multi/[address]/index.tsx b/pages/multi/[address]/index.tsx index 5cb226f..16db4c5 100644 --- a/pages/multi/[address]/index.tsx +++ b/pages/multi/[address]/index.tsx @@ -1,13 +1,13 @@ import React, { useState, useEffect } from "react"; -import { pubkeyToAddress, Pubkey } from "@cosmjs/amino"; -import { StargateClient } from "@cosmjs/stargate"; +import { pubkeyToAddress, Pubkey, MultisigThresholdPubkey } from "@cosmjs/amino"; +import { Account, StargateClient } from "@cosmjs/stargate"; import { assert } from "@cosmjs/utils"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { useRouter } from "next/router"; import { useAppContext } from "../../../context/AppContext"; import Button from "../../../components/inputs/Button"; -import { AccountWithPubkey, getMultisigAccount } from "../../../lib/multisigHelpers"; +import { getMultisigAccount } from "../../../lib/multisigHelpers"; import HashView from "../../../components/dataViews/HashView"; import MultisigHoldings from "../../../components/dataViews/MultisigHoldings"; import MultisigMembers from "../../../components/dataViews/MultisigMembers"; @@ -30,7 +30,8 @@ const multipage = () => { const [showTxForm, setShowTxForm] = useState(false); const [holdings, setHoldings] = useState(null); const [multisigAddress, setMultisigAddress] = useState(""); - const [accountOnChain, setAccountOnChain] = useState(null); + const [accountOnChain, setAccountOnChain] = useState(null); + const [pubkey, setPubkey] = useState(); const [accountError, setAccountError] = useState(null); const router = useRouter(); @@ -49,9 +50,10 @@ const multipage = () => { const client = await StargateClient.connect(state.chain.nodeAddress); assert(state.chain.denom, "denom missing"); const tempHoldings = await client.getBalance(address, state.chain.denom); - const tempAccountOnChain = await getMultisigAccount(address, client); setHoldings(tempHoldings); - setAccountOnChain(tempAccountOnChain); + const result = await getMultisigAccount(address, client); + setPubkey(result[0]); + setAccountOnChain(result[1]); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { setAccountError(error.message); @@ -74,13 +76,10 @@ const multipage = () => { )} - {accountOnChain && ( + {pubkey && ( )} {accountError && ( diff --git a/pages/multi/[address]/transaction/[transactionID].tsx b/pages/multi/[address]/transaction/[transactionID].tsx index 52ededc..bf95a98 100644 --- a/pages/multi/[address]/transaction/[transactionID].tsx +++ b/pages/multi/[address]/transaction/[transactionID].tsx @@ -1,7 +1,7 @@ import axios from "axios"; import React from "react"; import { GetServerSideProps } from "next"; -import { StargateClient, makeMultisignedTx } from "@cosmjs/stargate"; +import { StargateClient, makeMultisignedTx, Account } from "@cosmjs/stargate"; import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { useState, useEffect } from "react"; import { useRouter } from "next/router"; @@ -12,7 +12,7 @@ import { DbSignature, DbTransaction } from "../../../../types"; import { useAppContext } from "../../../../context/AppContext"; import Button from "../../../../components/inputs/Button"; import { findTransactionByID } from "../../../../lib/graphqlHelpers"; -import { AccountWithPubkey, getMultisigAccount } from "../../../../lib/multisigHelpers"; +import { getMultisigAccount } from "../../../../lib/multisigHelpers"; import Page from "../../../../components/layout/Page"; import StackableContainer from "../../../../components/layout/StackableContainer"; import ThresholdInfo from "../../../../components/dataViews/ThresholdInfo"; @@ -75,7 +75,8 @@ const transactionPage = ({ const [broadcastError, setBroadcastError] = useState(""); const [isBroadcasting, setIsBroadcasting] = useState(false); const [transactionHash, setTransactionHash] = useState(txHash); - const [accountOnChain, setAccountOnChain] = useState(null); + const [accountOnChain, setAccountOnChain] = useState(null); + const [pubkey, setPubkey] = useState(); const [accountError, setAccountError] = useState(null); const txInfo: DbTransaction = (transactionJSON && JSON.parse(transactionJSON)) || null; const router = useRouter(); @@ -95,8 +96,9 @@ const transactionPage = ({ try { assert(state.chain.nodeAddress, "Node address missing"); const client = await StargateClient.connect(state.chain.nodeAddress); - const tempAccountOnChain = await getMultisigAccount(address, client); - setAccountOnChain(tempAccountOnChain); + const result = await getMultisigAccount(address, client); + setPubkey(result[0]); + setAccountOnChain(result[1]); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { setAccountError(error.toString()); @@ -150,11 +152,11 @@ const transactionPage = ({ )} {transactionHash && } - {!transactionHash && accountOnChain && ( - + {!transactionHash && pubkey && ( + )} - {accountOnChain && - currentSignatures.length >= parseInt(accountOnChain.pubkey.value.threshold, 10) && + {pubkey && + currentSignatures.length >= parseInt(pubkey.value.threshold, 10) && !transactionHash && ( <>