Multisig Transactions on ZIGChain
TL;DR: Multisig accounts on ZIGChain require multiple signatures before any transaction broadcasts. This guide covers creating a shared wallet, executing threshold-signed transfers, using Ledger signers, and recovering multisig members from transaction data.
Overview
Multisig accounts on ZIGChain require multiple signatures to authorize transactions. No single signer can move funds or act on the account without meeting the configured threshold. This shared control model is common on Cosmos chains and works well for teams that need joint approval.
This guide is for operators and treasuries that need shared control over ZIG tokens. DAOs, businesses, and teams use multisig when they want multi-party approval before funds move.
Multisig accounts fit several common use cases on ZIGChain. Teams that share treasury funds rely on them for joint control. Groups that want extra security use them so one lost key cannot drain the wallet alone. Corporate treasuries often require multiple approvals before large transfers go out. Some teams also keep several backup keys for access, though that adds convenience rather than stronger security on its own.
Key Concepts
- Threshold: The minimum number of signatures required to authorize a transaction. A 2-of-3 multisig needs any two of the three listed signers before broadcast succeeds.
- Sequence number: The account nonce that must match the next unused value before you broadcast. Off-by-one sequence errors are a common cause of failed multisig sends.
- Unsigned transaction: A JSON file you create with
--generate-onlybefore individual signers attach their signatures. Every signer works from the same unsigned file. - Multisign: The CLI step that merges per-signer JSON outputs into one signed transaction ready to broadcast. You run it after enough signers have signed. The output file is what you pass to the broadcast command.
- Keyring backend: Where
zigchaindstores private keys on your machine. Options includefile,os,test,kwallet, andpass. Examples in this guide use thefilebackend for local development and testing workflows.
Creating a Multisig Account
A multisig wallet groups existing signer keys under one address and one threshold. You first create each signer key, then combine them with zigchaind keys add --multisig. The steps below walk through key creation, multisig assembly, and address verification.
Create individual signer keys
Create the individual signer accounts that will participate in the multisig. Each signer gets its own key name and address before you combine them.
zigchaind keys add signer1 --keyring-backend file
zigchaind keys add signer2 --keyring-backend file
zigchaind keys add signer3 --keyring-backend file
Example output for signer creation:
- address: zig1ykehptq7qfl9m6x7x3dzcp2u825ph4zlehk9rg
name: signer2
pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"ApyZAVGwm2gP8ojw8bbvDb1+TgrkyAkZb1UcM4qCbnP4"}'
type: local
Important: Write this mnemonic phrase in a safe place. It is the only way to recover your account if you ever forget your password.
tackle surround truly salt job wheat letter farm drip table abuse admit path else girl solar leisure knife judge kangaroo chimney inspire guilt update
Never share your complete mnemonic phrase publicly. The example above is for demonstration purposes only.
Keyring Backend Options:
The --keyring-backend parameter determines where your keys are stored:
file: Keys stored in local files (recommended for development)os: Keys stored in operating system credential storetest: In-memory storage (for testing only)kwallet: KDE Wallet (Linux)pass: Pass password manager (Linux)
For more details on keyring backends, see the Cosmos SDK Keyring Documentation.
Multisig Limitations:
- Maximum participants: Up to 7 wallets can participate in a single multisig account
- Wallet uniqueness: You cannot reuse the same signer combination for a second multisig. Use a different set, such as 1, 2, 4 instead of 1, 2, 3
- Wallet reuse: While technically possible to reuse the same wallet in multiple multisigs, this is not recommended for security reasons
- Threshold: The threshold must be between 1 and the total number of signers
Create the multisig wallet
Combine the signer keys into one multisig wallet. Set the threshold to the minimum number of signatures required for each transaction.
zigchaind keys add multisig-wallet --multisig signer1,signer2,signer3 \
--multisig-threshold 2 --keyring-backend file
Example output for multisig creation:
- address: zig1xz0rjzw7kt547c3yhws6nx4mlkeu8d37f2w927
name: multisig-wallet
pubkey: '{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Az1wlHRA135LQQntdot6ETT4LgSG8rnQX92iyqlM/SCI"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"ApyZAVGwm2gP8ojw8bbvDb1+TgrkyAkZb1UcM4qCbnP4"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A3ly+VRiXFopoxt3aCrRkAkFFqWEu+9liAxuq3Obxoer"}]}'
type: multi
Parameters explained:
--multisig-threshold 2: Requires 2 out of 3 signatures to authorize transactions--keyring-backend: Specifies where to store the keys (file, test, os, etc.)
Verify the multisig address
Confirm the multisig address before you fund the account or share it with co-signers.
zigchaind keys show multisig-wallet --address --keyring-backend file
How Multisig Transactions Work
Multisig sends follow a fixed sequence on ZIGChain. You fund the multisig account, build an unsigned JSON file, collect enough signer approvals, merge those signatures, and broadcast the result.
The multisig account pays gas fees at broadcast time. Fees are set when you generate the unsigned transaction, so the account must hold enough ZIG before you start signing. Signers can work offline once they have the unsigned file, which simplifies approval across time zones. Only the final broadcast step needs live RPC access to a ZIGChain node.
This guide walks through a token transfer from a multisig account to another wallet. The same pattern applies to other message types once you adjust the generate command. Each step below includes network-specific commands for mainnet, testnet, and local nodes.
Step 1: Ensure Fee Balance (Fund if Needed)
Fund the multisig account and confirm gas settings before you build the unsigned transaction. Skipping this step leads to failed broadcasts or costly retries when signers have already approved a stale file.
The multisig account pays fees at broadcast time. Fees are set when you generate the unsigned transaction. Fund the account if the balance cannot cover gas.
Check current balance
- Mainnet
- Testnet
- Local
zigchaind q bank balances <multisig-address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz
zigchaind q bank balances <multisig-address> \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind q bank balances <multisig-address> \
--chain-id zigchain-1 --node http://localhost:26657
Fund the multisig
- Mainnet
- Testnet
- Local
zigchaind tx bank send <from-wallet> <multisig-address> <amount> \
--gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz --yes
zigchaind tx bank send <from-wallet> <multisig-address> <amount> \
--gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz --yes
zigchaind tx bank send <from-wallet> <multisig-address> <amount> \
--gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--chain-id zigchain-1 --node http://localhost:26657 --yes
Gas fee considerations
Gas fees matter more in multisig flows because coordination makes retries costly. Confirm sufficient fees before you ask signers to approve a transaction. A failed broadcast after partial signing wastes time for every co-signer involved.
Best practices
- Always use
--gas autowith--gas-adjustment(e.g., 1.3–2.5) - Monitor network conditions and adjust
--gas-adjustmentupward if needed - Test with small amounts first; complex txs (governance, contracts) need more gas
Recommended flags
# Simple transfers
--gas auto --gas-adjustment 1.5
# Complex transactions
--gas auto --gas-adjustment 2.0
# High congestion
--gas auto --gas-adjustment 2.5
Estimate gas before generating and broadcasting
- Mainnet
- Testnet
- Local
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zigchain-1 --node http://localhost:26657
gas estimate: 104665
Step 2: Get Current Sequence Number
Fetch the current sequence number for the multisig address before you generate the unsigned transaction. The sequence must match the account's next unused value or the broadcast will fail.
- Mainnet
- Testnet
- Local
zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 \
--node https://public-zigchain-rpc.numia.xyz
zigchaind query auth account <multisig-address> \
--chain-id zig-test-2 \
--node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 \
--node http://localhost:26657
Example response:
account:
type: /cosmos.auth.v1beta1.BaseAccount
value:
account_number: "11"
address: zig1yh05klcmerdjyessgskjdurh3elqtrway0sfc3
pub_key: null
sequence: "11"
Important: Use the next number after the current sequence. In this example, sequence 11 means you use 12 next. If no sequence exists yet, use 0.
Step 3: Generate Unsigned Transaction
Build an unsigned transaction file for multisig signers to approve. The file is not broadcast until it carries enough valid signatures to meet the threshold.
- Mainnet
- Testnet
- Local
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz > unsigned_tx.json
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz > unsigned_tx.json
zigchaind tx bank send <multisig-wallet-name> <recipient> <amount> \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence <sequence> --keyring-backend file \
--chain-id zigchain-1 --node http://localhost:26657 > unsigned_tx.json
Command Parameters:
<multisig-wallet-name>: Name of the multisig wallet sending the transaction<recipient>: Destination address receiving the tokens<amount>: Amount of tokens to send (e.g.,1000uzig)--generate-only: Creates unsigned transaction without broadcasting--sequence <sequence>: Account sequence number from Step 2--keyring-backend file: Key storage location> unsigned_tx.json: Output file for the unsigned transaction
Batch preparation: Repeat the generate step to create multiple unsigned files (for example, unsigned_tx1.json and unsigned_tx2.json). Merge messages into one batch so signers approve once. Use jq to concatenate body.messages arrays:
jq -s 'reduce .[] as $tx (.[0]; .body.messages += $tx.body.messages)' unsigned_tx*.json > unsigned_batch.json
After merging, re-estimate fees so auth_info.fee covers every message. Confirm the sequence matches the next unused value for the multisig account.
Example batch workflow:
- Mainnet
- Testnet
- Local
Generate two unsigned transfers and then merge them into a single batch for mainnet.
zigchaind tx bank send multisig-wallet zig1abc... 5000uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zigchain-1 \
--node https://public-zigchain-rpc.numia.xyz > unsigned_tx1.json
zigchaind tx bank send multisig-wallet zig1def... 7500uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zigchain-1 \
--node https://public-zigchain-rpc.numia.xyz > unsigned_tx2.json
jq -s 'reduce .[] as $tx (.[0]; .body.messages += $tx.body.messages)' \
unsigned_tx1.json unsigned_tx2.json > unsigned_batch.json
Generate two unsigned transfers and then merge them into a single batch for testnet.
zigchaind tx bank send multisig-wallet zig1abc... 5000uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zig-test-2 \
--node https://public-zigchain-testnet-rpc.numia.xyz > unsigned_tx1.json
zigchaind tx bank send multisig-wallet zig1def... 7500uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zig-test-2 \
--node https://public-zigchain-testnet-rpc.numia.xyz > unsigned_tx2.json
jq -s 'reduce .[] as $tx (.[0]; .body.messages += $tx.body.messages)' \
unsigned_tx1.json unsigned_tx2.json > unsigned_batch.json
Generate two unsigned transfers and then merge them into a single batch for a local validator.
zigchaind tx bank send multisig-wallet zig1abc... 5000uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zigchain-1 \
--node http://localhost:26657 > unsigned_tx1.json
zigchaind tx bank send multisig-wallet zig1def... 7500uzig \
--generate-only --gas-prices 0.0025uzig --gas auto --gas-adjustment 1.3 \
--sequence 12 --keyring-backend file --chain-id zigchain-1 \
--node http://localhost:26657 > unsigned_tx2.json
jq -s 'reduce .[] as $tx (.[0]; .body.messages += $tx.body.messages)' \
unsigned_tx1.json unsigned_tx2.json > unsigned_batch.json
The merged unsigned_batch.json contains both messages under one auth_info and body. A truncated example with three transfers is shown below. Note the shared sequence of 12:
{
"body": {
"messages": [
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "zig1multisig...",
"to_address": "zig1abc...",
"amount": [{ "denom": "uzig", "amount": "5000" }]
},
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "zig1multisig...",
"to_address": "zig1def...",
"amount": [{ "denom": "uzig", "amount": "7500" }]
},
{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": "zig1multisig...",
"to_address": "zig1ghi...",
"amount": [{ "denom": "uzig", "amount": "2500" }]
}
],
"... additional body fields omitted for brevity ..."
},
"auth_info": {
"signer_infos": [],
"fee": {
"amount": [{ "denom": "uzig", "amount": "0" }],
"gas_limit": "104665"
}
},
"signatures": []
}
Re-estimate fees after merging:
-
Run a simulation against the merged file to obtain a fresh gas estimate:
- Mainnet
- Testnet
- Local
zigchaind tx simulate unsigned_batch.json --from multisig-wallet --keyring-backend file \--chain-id zigchain-1 \--node https://public-zigchain-rpc.numia.xyz \--gas-prices 0.0025uzig --gas-adjustment 1.3zigchaind tx simulate unsigned_batch.json --from multisig-wallet --keyring-backend file \--chain-id zig-test-2 \--node https://public-zigchain-testnet-rpc.numia.xyz \--gas-prices 0.0025uzig --gas-adjustment 1.3zigchaind tx simulate unsigned_batch.json --from multisig-wallet --keyring-backend file \--chain-id zigchain-1 \--node http://localhost:26657 \--gas-prices 0.0025uzig --gas-adjustment 1.3Output:
gas_info:gas_used: "122219"gas_wanted: "100000000"The CLI prints a line such as
gas_used: 214380. Setauth_info.fee.gas_limitto this value. Add a buffer for complex or congested networks. -
Multiply gas price by the new gas limit. With
214380gas at0.0025uzig, the minimum fee is536 uzig. Updateauth_info.fee.amount[0].amount:jq '.auth_info.fee.gas_limit = "214380" |.auth_info.fee.amount[0].amount = "536"' \unsigned_batch.json > unsigned_batch_with_fee.json -
Share the updated file (
unsigned_batch_with_fee.json) with signers so the signature step uses the refreshed fee parameters.
Optional Parameter: --nosort
Controls signature ordering in the final transaction:
- Default: Signatures are sorted in canonical order
- With
--nosort: Signatures applied in provided order - Usage: When specific signature order is required for external system compatibility
Important: All signers must use the same sorting preference.
Step 4: Sign with Each Signer
Each signer signs the transaction individually. You only need enough signatures to meet the threshold configured when the multisig wallet was created.
- Mainnet
- Testnet
- Local
zigchaind tx sign unsigned_tx.json --from <wallet_name> \
--multisig <multisig-wallet-name> --keyring-backend file \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz \
--output-document <wallet_name>_signed.json
zigchaind tx sign unsigned_tx.json --from <wallet_name> \
--multisig <multisig-wallet-name> --keyring-backend file \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz \
--output-document <wallet_name>_signed.json
zigchaind tx sign unsigned_tx.json --from <wallet_name> \
--multisig <multisig-wallet-name> --keyring-backend file \
--chain-id zigchain-1 --node http://localhost:26657 \
--output-document <wallet_name>_signed.json
Step 5: Combine Signatures
Merge the individual signer outputs into one signed transaction file. Pass every signed JSON file that contributes to the threshold into the multisign command.
- Mainnet
- Testnet
- Local
zigchaind tx multisign unsigned_tx.json <multisig-wallet-name> <wallet_name1>_signed.json \
<wallet_name2>_signed.json --keyring-backend file \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz \
--output-document multisigned_tx.json
zigchaind tx multisign unsigned_tx.json <multisig-wallet-name> <wallet_name1>_signed.json \
<wallet_name2>_signed.json --keyring-backend file \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz \
--output-document multisigned_tx.json
zigchaind tx multisign unsigned_tx.json <multisig-wallet-name> <wallet_name1>_signed.json \
<wallet_name2>_signed.json --keyring-backend file \
--chain-id zigchain-1 --node http://localhost:26657 \
--output-document multisigned_tx.json
Step 6: Broadcast Transaction
Submit the merged signed file to the network once it contains enough valid signatures. The multisig account pays the gas fee recorded in the unsigned file at this step.
- Mainnet
- Testnet
- Local
zigchaind tx broadcast multisigned_tx.json --chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz
zigchaind tx broadcast multisigned_tx.json --chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind tx broadcast multisigned_tx.json --chain-id zigchain-1 --node http://localhost:26657
Step 7: Verify Transactions
- Mainnet
- Testnet
- Local
zigchaind q bank balances <wallet_address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz
zigchaind q bank balances <wallet_address> \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind q bank balances <wallet_address> \
--chain-id zigchain-1 --node http://localhost:26657
Extracting Multisig Members from a Transaction
You can recover every signer in a multisig wallet from an on-chain transaction hash. This helps when you inherit a treasury address but lack the original signer list. The CLI commands below query the transaction and decode each member public key.
Step 1: Get Pubkey from All Multisig Members
Query the transaction in JSON format and isolate the signer metadata:
- Mainnet
- Testnet
- Local
zigchaind q tx <TX_HASH> \
--chain-id zigchain-1 \
--node https://public-zigchain-rpc.numia.xyz \
-o json > tx.json
cat tx.json | jq '.tx.auth_info.signer_infos'
zigchaind q tx <TX_HASH> \
--chain-id zig-test-2 \
--node https://public-zigchain-testnet-rpc.numia.xyz \
-o json > tx.json
cat tx.json | jq '.tx.auth_info.signer_infos'
zigchaind q tx <TX_HASH> \
--chain-id zigchain-1 \
--node http://localhost:26657 \
-o json > tx.json
cat tx.json | jq '.tx.auth_info.signer_infos'
Response:
{
"public_key": {
"@type": "/cosmos.crypto.multisig.LegacyAminoPubKey",
"threshold": 3,
"public_keys": [
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "A523ynlGdDjRHycNTiaeBvKRXhwJVzgMfvd0hDUkBQft"
},
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "AilMqRZeIIEv6h/MdTvccUwyrq1o1SUmZgzYjxcS0f2K"
},
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "AxXCbPZsBkY+tGE7kX+hrCbeVhNnfrF0wa2tI5woDeR6"
},
{
"@type": "/cosmos.crypto.secp256k1.PubKey",
"key": "A/Wop1RBnxLjo8tDQdKERH8ZA14EZmvWXHWYOlUta1/O"
}
]
}
}
Step 2: Debug PubKey
Run the debug helper against each public key value returned in Step 1:
zigchaind debug pubkey '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"<KEY>"}'
Ex:
zigchaind debug pubkey '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AqWONfbFLlvq8N9w5vdYuNTJyyslI8HXWgYqQ2lud6zq"}'
Response:
Address: 199846F5ECF6318E8BBBC192EDC40EDDDA8F9DFE
PubKey Hex: 02a58e35f6c52e5beaf0df70e6f758b8d4c9cb2b2523c1d75a062a43696e77acea
Step 3: Debug Address
Inspect the address returned in Step 2 to derive its Bech32 variants:
zigchaind debug addr <ADDRESS_FROM_DEBUG_PUBKEY>
Ex:
zigchaind debug addr 5C0DA7BA68A1B4F3EB94BCEE5DBB89C773E93EB8
Example:
Address: [92 13 167 186 104 161 180 243 235 148 188 238 93 187 137 199 115 233 62 184]
Address (hex): 5C0DA7BA68A1B4F3EB94BCEE5DBB89C773E93EB8
Bech32 Acc: zig1tsx60wng5x6086u5hnh9mwufcae7j04c9kxr37
Bech32 Val: zigvaloper1tsx60wng5x6086u5hnh9mwufcae7j04cs329s8
Bech32 Con: zigvalcons1tsx60wng5x6086u5hnh9mwufcae7j04cyzeeux
Multisig with Ledger Hardware Wallets
Prerequisites
- Ledger device with ZIGChain app installed
- Ledger Wallet or compatible software
- ZIGChain CLI configured for Ledger
Creating Multisig with Ledger
- Add Ledger keys to keyring:
zigchaind keys add ledger-signer1 --ledger --keyring-backend file
zigchaind keys add ledger-signer2 --ledger --keyring-backend file
- Create multisig with Ledger signers:
zigchaind keys add ledger-multisig --multisig ledger-signer1,ledger-signer2 \
--multisig-threshold 2 --keyring-backend file
Signing with Ledger
When signing transactions with Ledger devices:
-
Generate unsigned transaction (same as above)
-
Sign with Ledger:
zigchaind tx sign unsigned_tx.json --from ledger-signer1 \
--multisig ledger-multisig --keyring-backend file \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz \
--output-document ledger_signed.json
- Follow Ledger prompts to approve the transaction on the device
- Combine signatures and broadcast (same process as above)
Ledger Best Practices
- Always verify transaction details on the Ledger screen before signing
- Keep your Ledger firmware updated
- Use a secure PIN and enable passphrase protection
- Store recovery phrases in separate, secure locations
Limitations and Considerations
Multisig adds security and shared control, but it also adds operational overhead compared to single-key accounts. Plan for sequence tracking, signer coordination, and file handoffs before you move large balances. The points below summarize technical limits and team practices that keep multisig workflows reliable.
Technical Limitations
- Sequence Management: Must manually track and increment sequence numbers
- Offline Signing: Requires careful coordination between signers
- File Management: Multiple signature files must be managed and combined
Operational Considerations
- Signer Availability: Sufficient signers (meeting the threshold) must be available to complete transactions
- Key Security: Each signer's key must be kept secure
- Coordination Overhead: More complex than single-signature transactions
- Recovery Complexity: Losing signer keys can make funds inaccessible
- Gas Fee Planning: Estimate and allocate gas before starting to avoid costly failures
Best Practices
- Threshold Selection: Choose appropriate threshold (e.g., 2-of-3, 3-of-5)
- Key Distribution: Distribute signer keys across different locations/people
- Backup Strategy: Maintain secure backups of all signer keys
- Testing: Test multisig setup with small amounts first
- Documentation: Keep clear records of signer responsibilities
Common Questions
What should I do when I get a sequence number mismatch error?
Transaction fails due to incorrect sequence number.
Common Error Response:
account sequence mismatch, expected 1, got 0: incorrect account sequence
Always query the current sequence before generating transactions:
- Mainnet
- Testnet
- Local
zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz
zigchaind query auth account <multisig-address> \
--chain-id zig-test-2 --node https://public-zigchain-testnet-rpc.numia.xyz
zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 --node http://localhost:26657
Why does my multisig transaction fail with insufficient signatures?
Not enough signatures were provided for the threshold.
Common Error Response:
signature verification failed; please verify account number (12), sequence (0) and chain-id (zigchain-1): unauthorized
Ensure you have collected signatures from the minimum required number of signers as specified in the multisig threshold.
You can verify the required number of signatures by checking the multisig wallet details:
zigchaind keys show <multisig-wallet-name> --keyring-backend file
Example output:
- address: zig1ctxxsscu23u7kwj9w0khpmn3fny06l5xtw5v4y
name: multisig-wallet
pubkey: '{"@type":"/cosmos.crypto.multisig.LegacyAminoPubKey","threshold":2,"public_keys":[{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AusqvIxXe45X0Hzl0QaNZdgUD2Mf65h29ESlKM9gMAA3"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AuZc4IQMmN4JrqSpyd2kntewbHCSbQ148T9bhkYD/EVD"},{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A3oPncBJ5PzqLQeXk3T3M3K5ZwiynFj9QAIbiMSPSLKS"}]}'
type: multi
In this example, the "threshold":2 indicates that 2 signatures are required out of the 3 available signers.
Why does my multisig transaction fail with an invalid signature?
One or more signatures are invalid.
Common Error Response:
signing key is not a part of multisig key
- Verify each signer used the correct key
- Ensure the unsigned transaction hasn't been modified
- Check that all signers are using the same multisig address
- Ensure you are using a signer wallet that was included when creating the multisig account. Only wallets specified in the
--multisigparameter during multisig creation can sign transactions for that multisig account
Why does my multisig transaction fail with insufficient gas or out of gas?
The transaction fails due to insufficient gas fees.
- Always estimate gas first before generating unsigned transactions
- Increase
--gas-adjustmentto 2.0 or higher for complex transactions - Monitor network conditions and adjust gas accordingly
- Consider the additional complexity of multisig transactions when setting gas limits
Prevention: This is the most frustrating multisig failure. Always test gas requirements with small amounts before executing large multisig transactions.