cosmjs icon indicating copy to clipboard operation
cosmjs copied to clipboard

Failed to execute multisig admin transaction using custom type URL

Open stefanpetkov90 opened this issue 2 years ago • 1 comments

Hi,

I'm trying to find a way to execute an admin transaction(custom transaction) using a custom type URL, you can find the logic I'm using to sign and broadcast an adimn transaction with a single account, which works fine. When I try to broadcast using the multisig functionality I am getting the following error:

Broadcasting transaction failed with code 4 (codespace: sdk). Log: signature verification failed; please verify account number (42), sequence (10) and chain-id (network-testnet-private): unauthorized

Can you spot anything obvious, wrong with the signAdminTransaction & BroadcastTx methods?

-The below implemetation works fine, however it is not using the multisig sign logic:

// Custom transaction
const handleAdminTransaction = async () => {
    const proposalTypePath =
      "/custom.amino.MsgAdminSpendCommunityPool";

    const myAddress = "address1";
    const receiverAddress = "address2";

    const myRegistry = new Registry(defaultRegistryTypes);
    myRegistry.register(
      proposalTypePath,
      MsgAdminSpendCommunityPool as GeneratedType
    );

    window.keplr.enable(process.env.REACT_APP_CHAIN_ID);

    const offlineSigner = window.getOfflineSigner(
      process.env.REACT_APP_CHAIN_ID
    );

    window.keplr.defaultOptions = {
      sign: {
        preferNoSetMemo: true,
        preferNoSetFee: true,
        disableBalanceCheck: true,
      },
    };

    const client = await SigningStargateClient.connectWithSigner(
      process.env.REACT_APP_RPC!,
      offlineSigner,
      { registry: myRegistry }
    );

    const message = {
      typeUrl: proposalTypePath,
      value: MsgAdminSpendCommunityPool.fromPartial({
        initiator: myAddress,
        toAddress: receiverAddress,
        coins: [{ denom: "acudos", amount: "10000000000000000000" }],
      }),
    };
    const fee = {
      amount: [
        {
          denom: "denom", 
          amount: "120000",
        },
      ],
      gas: "200000",
    };

    const response = await client.signAndBroadcast(
      myAddress,
      [message],
      fee,
      "Admin Spend Community Pool"
    );
  };
  • The below implementaition throws the following error when trying to broadcast:
Broadcasting transaction failed with code 4 (codespace: sdk). Log: signature verification failed; please verify account number (42), sequence (10) and chain-id (network-testnet-private): unauthorized
const signAdminTransaction = async (id: number) => {
    const transaction = transactionData.filter((td: any) => td.id === id);

    const signers = transaction[0].hasSigned.filter((signer: any) =>
      multiSigAccount.owners.includes(signer)
    );

    if (
      signers.includes(keplrAccount) ||
      !multiSigAccount.owners.includes(keplrAccount)
    ) {
      return;
    }

    const proposalTypePath =
      "/custom.amino.MsgAdminSpendCommunityPool";

    const myRegistry = new Registry(defaultRegistryTypes);
    myRegistry.register(
      proposalTypePath,
      MsgAdminSpendCommunityPool as GeneratedType
    );

    const offlineSigner = window.getOfflineSigner(
      process.env.REACT_APP_CHAIN_ID
    );

    window.keplr.defaultOptions = {
      sign: {
        preferNoSetMemo: true,
        preferNoSetFee: true,
        disableBalanceCheck: true,
      },
    };

    const signingClient = await SigningStargateClient.connectWithSigner(
      process.env.REACT_APP_RPC!, 
      offlineSigner,
      { registry: myRegistry }
    );

    const signerData = {
      accountNumber: transaction[0].accountNumber,
      sequence: transaction[0].sequence,
      chainId: process.env.REACT_APP_CHAIN_ID!,
    };

    await window.keplr.enable(process.env.REACT_APP_CHAIN_ID);
    const walletAccount = await window.keplr.getKey(
      process.env.REACT_APP_CHAIN_ID
    );

    const msgSend = MsgAdminSpendCommunityPool.fromPartial({
      initiator: transaction[0].msgs[0].value.fromAddress,
      toAddress: transaction[0].msgs[0].value.toAddress,
      coins: [
        {
          denom: CosmosNetworkConfig.CURRENCY_DENOM,
          amount: transaction[0].msgs[0].value.amount[0].amount,
        },
      ],
    });

    const msg = [
      {
        typeUrl: proposalTypePath,
        value: msgSend,
      },
    ];

    const msgFee = {
      amount: [
        {
          denom: CosmosNetworkConfig.CURRENCY_DENOM,
          amount: "0",
        },
      ],
      gas: "180000",
    };

    const msgMemo = "Admin Spend Community Pool";

    const { signatures, bodyBytes } = await signingClient.sign(
      walletAccount.bech32Address,
      msg,
      msgFee,
      msgMemo,
      signerData
    );

    const bases64EncodedSignature = encode(signatures[0]);
    const bases64EncodedBodyBytes = encode(bodyBytes);

    const signature = {
      transactionId: transaction[0].id,
      bodyBytes: bases64EncodedBodyBytes,
      signature: bases64EncodedSignature,
      address: walletAccount.bech32Address,
    };

    await createSignature(signature.transactionId, signature);

    const { data } = await getTransactions(multiSigAccount.address);

    const newTd = data.map((td: any) => {
      return {
        ...td,
        hasSigned:
          td.id === id
            ? [...td.hasSigned, walletAccount.bech32Address]
            : td.hasSigned,
        sequence: td.id === id ? signerData.sequence : td.sequence,
      };
    });

    setTransactionData(newTd);

    const updatedTransaction = newTd.filter((td: any) => td.id === id);

    await updateTransaction(id, updatedTransaction[0]);
  };

  const broadcastTx = async (id: number) => {
    try {
      const { data: response } = await getMultisig(multiSigAccount.address);
      const { pubkey } = response;
      const { data: sigs } = await getSignature(id);
      const multisigBalance = await getMultisigBalance(multiSigAccount.address);

      const signatures = new Map();

      const [transactionToBroadcast] = transactionData.filter(
        (trans: any) => trans.id === id
      );
      const [transactionToReject] = transactionData.filter(
        (trans: any) =>
          trans.sequence === transactionToBroadcast.sequence && trans.id !== id
      );

      const transactionAmount = new BigNumber(
        transactionToBroadcast.msgs[0].value.amount[0].amount
      )
        .dividedBy(CosmosNetworkConfig.CURRENCY_1_CUDO)
        .toString(10);

        const bodyBytes = decode(sigs[0].bodyBytes);

        const signedTx = makeMultisignedTx(
          {
            type: pubkey.type,
            value: {
              threshold: pubkey.value.threshold,
              pubkeys: pubkey.value.pubkeys,
            },
          },
          transactionToBroadcast.sequence,
          transactionToBroadcast.fee,
          bodyBytes,
          signatures
        );

        const broadcaster = await StargateClient.connect(
        process.env.REACT_APP_RPC!
        );

        console.log("broadcasting...", broadcaster);

        const result = await broadcaster.broadcastTx(
          Uint8Array.from(TxRaw.encode(signedTx).finish())
        );
        const updateWalletBalance = await getMultisigBalance(
          multiSigAccount.address
        );

        setMultiSigAccount({
          ...multiSigAccount,
          balance: updateWalletBalance,
        });

        const updatedTransactionToBroadcast = {
          ...transactionToBroadcast,
          hash: result.transactionHash,
          executionTime: new Date(),
          status: TransactionStatus.SUCCESSFUL,
        };
        await updateTransaction(id, updatedTransactionToBroadcast);

        if (transactionToReject) {
          const updatedTransactionToReject = {
            ...transactionToReject,
            status: TransactionStatus.REJECTED,
          };

          await updateTransaction(
            updatedTransactionToReject.id,
            updatedTransactionToReject
          );
        }

        const { data } = await getTransactions(multiSigAccount.address);
        setTransactionData(data);

        setNotificationMessage("Success!");
      } else {
        setErrorMessage("Account balance is insufficient");
      }
    } catch (e) {
      setErrorMessage(e.message);
      const [transactionToBroadcast] = transactionData.filter(
        (trans: any) => trans.id === id
      );
  };

Thank you!

stefanpetkov90 avatar Mar 11 '22 14:03 stefanpetkov90

How do you define MsgAdminSpendCommunityPool? I didn't see your specific import type

Chen120322 avatar Apr 25 '22 12:04 Chen120322