Skip to main content

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-only before 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 zigchaind stores private keys on your machine. Options include file, os, test, kwallet, and pass. Examples in this guide use the file backend 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
Save your mnemonic securely

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 store
  • test: 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

zigchaind q bank balances <multisig-address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz

Fund the multisig

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

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

  1. Always use --gas auto with --gas-adjustment (e.g., 1.3–2.5)
  2. Monitor network conditions and adjust --gas-adjustment upward if needed
  3. Test with small amounts first; complex txs (governance, contracts) need more gas
# 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

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
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.

zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 \
--node https://public-zigchain-rpc.numia.xyz

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.

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

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:

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

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:

  1. Run a simulation against the merged file to obtain a fresh gas estimate:

    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.3

    Output:

    gas_info:
    gas_used: "122219"
    gas_wanted: "100000000"

    The CLI prints a line such as gas_used: 214380. Set auth_info.fee.gas_limit to this value. Add a buffer for complex or congested networks.

  2. Multiply gas price by the new gas limit. With 214380 gas at 0.0025uzig, the minimum fee is 536 uzig. Update auth_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
  3. 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.

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

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.

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

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.

zigchaind tx broadcast multisigned_tx.json --chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz

Step 7: Verify Transactions

zigchaind q bank balances <wallet_address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz

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:

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'

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

  1. Add Ledger keys to keyring:
zigchaind keys add ledger-signer1 --ledger --keyring-backend file
zigchaind keys add ledger-signer2 --ledger --keyring-backend file
  1. 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:

  1. Generate unsigned transaction (same as above)

  2. 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
  1. Follow Ledger prompts to approve the transaction on the device
  2. 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

  1. Sequence Management: Must manually track and increment sequence numbers
  2. Offline Signing: Requires careful coordination between signers
  3. File Management: Multiple signature files must be managed and combined

Operational Considerations

  1. Signer Availability: Sufficient signers (meeting the threshold) must be available to complete transactions
  2. Key Security: Each signer's key must be kept secure
  3. Coordination Overhead: More complex than single-signature transactions
  4. Recovery Complexity: Losing signer keys can make funds inaccessible
  5. Gas Fee Planning: Estimate and allocate gas before starting to avoid costly failures

Best Practices

  1. Threshold Selection: Choose appropriate threshold (e.g., 2-of-3, 3-of-5)
  2. Key Distribution: Distribute signer keys across different locations/people
  3. Backup Strategy: Maintain secure backups of all signer keys
  4. Testing: Test multisig setup with small amounts first
  5. 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:

zigchaind query auth account <multisig-address> \
--chain-id zigchain-1 --node https://public-zigchain-rpc.numia.xyz

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 --multisig parameter 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-adjustment to 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.

References