cosmjs
cosmjs copied to clipboard
Broadcasting a custom multisigned transaction
Hi there.
I'm trying to broadcast a custom transaction from a multisig which has collected the required number of signatures, but for some reason I'm getting Broadcasting transaction failed with code 4 (codespace: sdk). Log: signature verification failed; please verify account number (3), sequence (8) and chain-id (custom-network): unauthorized I've double checked all of the 3 given properties and they're all correct.
I'm assuming it has something to do with the custom registry and custom aminoTypes that I have to pass to the SigningStargateClient.offline/connectWithSigner() functions. Right now I'm not really sure what the prefix needs to be in this case. Here's a sample code
window.keplr.defaultOptions = {
sign: {
preferNoSetMemo: true,
preferNoSetFee: true,
disableBalanceCheck: true,
},
};
const offlineSigner = window.getOfflineSignerOnlyAmino(
process.env.REACT_APP_CHAIN_ID
);
const typeUrl =
"/custom.MsgCustom";
const customRegistry = new Registry(defaultRegistryTypes);
customRegistry.register(
typeUrl,
MsgCustom as GeneratedType
);
const customAminoTypes = new AminoTypes({
prefix: "custom?", // not sure if this has to be "cosmos" or the custom prefix of our bech32addresses
additions: {
"/custom.MsgCustom": {
aminoType: "custom/MsgCustom",
toAmino: ({
initiator,
toAddress,
coins,
}) => {
return {
initiator,
to_address: toAddress,
coins: [...coins],
};
},
fromAmino: ({
initiator,
to_address,
coins,
}) => {
return {
initiator,
toAddress: to_address,
coins: [...coins],
};
},
},
},
});
const signingClient = await SigningStargateClient.offline(
offlineSigner,
{ registry: customRegistry, aminoTypes: customAminoTypes, prefix: "custom?" } // not sure if this has to be "cosmos" or the custom prefix of our bech32addresses
);
await window.keplr.enable(process.env.REACT_APP_CHAIN_ID);
const walletAccount = await window.keplr.getKey(
process.env.REACT_APP_CHAIN_ID
);
const message = {
initiator: "custom1z2all86gvvkluus6c3sv6el9725wk7lfr9yaqy", // Address of the multisig itself
toAddress: "custom1eh3tdj03xmc6re77tyrdqktsx05tacp26vawlp",
coins: [
{
denom: config.denom,
amount: "10000000000000000000",
},
],
};
const messages = [
{
typeUrl,
value: message,
},
];
const msgFee = {
amount: [
{
denom: config.denom,
amount: "180000",
},
],
gas: "200000",
};
const msgMemo = "Custom Transaction";
const signerData = {
accountNumber,
sequence,
chainId
};
const { signatures, bodyBytes } = await signingClient.sign(
walletAccount.bech32Address,
messages,
msgFee,
msgMemo,
signerData
);
I should add that I was able to sign and broadcast this custom transaction with a regular (not a multisig) wallet. Main difference was that we don't need to use window.getOfflineSignerOnlyAmino which means we don't need to pass in aminoTypes to the SigningStargateClient.offline/connectWithSigner() functions. The signing mode ends up being SIGN_MODE_DIRECT and everything goes smoothly
Right now makeMultisignedTx hardcodes SIGN_MODE_LEGACY_AMINO_JSON for all signers. This is an implementation limitation that was used to get something working and it allows Ledger signing. In theory it would be possible to use SIGN_MODE_DIRECT too but this needs to be implemented and tested in CosmJS.
Now what you should do in order to break down the problem is figuring out of SIGN_MODE_LEGACY_AMINO_JSON works at all for your custom type. The way you create the Amino converter above looks roughly right. To do that get rid of all the offline signing and multisig code and try to send your message type from a simple account. Ideally you even use the Secp256k1HdWallet from @cosmjs/amino to do Amino Signing without Ledger.
@webmaster128 Well I've been able to broadcast this type of transaction through the CLI. And the auth_info in the final json file holding the signed transaction looks like this
"auth_info": {
"signer_infos": [
{
"public_key": {
"@type": "/cosmos.crypto.multisig.LegacyAminoPubKey",
"threshold": 2,
"public_keys": [
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "A1VVrpTWviVDpVQA2F99k3SoeqH46nHPWG1EOXe3Upvy"
},
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "Aq+YVg8lhwoif6Fq+UMAOgzVO8O7aFAkADeCWC/x/21r"
}
]
},
"mode_info": {
"multi": {
"bitarray": { "extra_bits_stored": 2, "elems": "wA==" },
"mode_infos": [
{ "single": { "mode": "SIGN_MODE_LEGACY_AMINO_JSON" } },
{ "single": { "mode": "SIGN_MODE_LEGACY_AMINO_JSON" } }
]
}
},
"sequence": "3"
}
],
That should be enough to prove that our custom message works with SIGN_MODE_LEGACY_AMINO_JSON right?
That should be enough to prove that our custom message works with SIGN_MODE_LEGACY_AMINO_JSON right?
Unfortunately no. We have seen 2 examples in the wild where Amino JSON signing with Ledger works in the CLI by accident but is buggy. The first one was the Stargate airdrop claim message and the second one is described here: https://medium.com/confio/authz-and-ledger-signing-an-executive-summary-8754a0dc2a88
@webmaster128 Thanks for clearing that up. One more question. We have the option to generate the transaction using this rest method. The generated tx looks like this
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "admin/AdminSpendCommunityPool",
"value": {
"initiator": "cudos1qy7a8qvmqtqrscz7rf9l3xlllm0l6x3xnmarze",
"to_address": "cudos1w93na52aw66vcp3rscn5s4mf4uu92qhnqn63rp",
"coins": [
{
"denom": "acudos",
"amount": "1000000"
}
]
}
}
],
"fee": {
"amount": [],
"gas": "200000"
},
"signatures": [],
"memo": "",
"timeout_height": "0"
}
}
Can we in any way just sign this given JSON or are the signing methods from the SigningStargateClient the only option?
No, the structure of that JSON is similar but not equal to the structure you need (see makeSignDoc/StdSignDoc). There is no shortcut.
Hi @webmaster128 any idea why we would encounter same issue with the basic signer?
import {lavanet, getSigningLavanetClient} from '@lavanet/lavajs';
import { Secp256k1Wallet, Secp256k1HdWallet } from '@cosmjs/amino'
import { fromHex } from "@cosmjs/encoding";
import Long from 'long';
import { SigningStargateClient } from "@cosmjs/stargate";
// sending queries specifically to lava
async function run() {
// create a lava client factory
const client = await lavanet.ClientFactory.createRPCQueryClient({ rpcEndpoint: publicRpc})
let wallet = await Secp256k1Wallet.fromKey(fromHex(privKey), "lava@")
const [firstAccount] = await wallet.getAccounts();
console.log(firstAccount)
let signingClient = await getSigningLavanetClient({
rpcEndpoint: publicRpc,
signer: wallet,
})
// buy a subscription (lava tx)
const msg = lavanet.lava.subscription.MessageComposer.withTypeUrl.buy({
creator: firstAccount.address,
consumer: firstAccount.address,
index: "explorer",
duration: new Long(1), /* in months */
})
const fee = {
amount: [{ amount: "1", denom: "ulava" }], // Replace with the desired fee amount and token denomination
gas: "50000000", // Replace with the desired gas limit
}
console.log(await signingClient.signAndBroadcast(firstAccount.address,[msg], fee, "Buying subscription on Lava blockchain!"))
}
run()
we recently upgraded to cosmos 47.3 and using cosmology/telescope's compiler we are building the lavajs aminos additionally we upgraded the cosmjs dependencies
"dependencies": {
"@cosmjs/amino": "^0.31.1",
"@cosmjs/proto-signing": "^0.31.1",
"@cosmjs/stargate": "^0.31.1",
"@cosmjs/tendermint-rpc": "^0.31.1",
"@cosmology/lcd": "^0.12.0"
}
I don't know. Signature verification errors can have a lot of different causes. You can try DirectSecp256k1Wallet instead of Secp256k1Wallet to see if it works with direct sign mode instead of Amino JSON signing.
I don't know. Signature verification errors can have a lot of different causes. You can try DirectSecp256k1Wallet instead of Secp256k1Wallet to see if it works with direct sign mode instead of Amino JSON signing.
@webmaster128 the direct wallet worked! any idea why the amino signer wouldn't work?