VSC-88: Multi-Sig Wallets
Multi-sig accounts require M-of-N approvals before executing transactions. This is the second tier of VSC-88, providing shared account control for teams, treasuries, and compliance-controlled assets.
Use Cases
- Foundation treasury -- 3-of-5 core team approval for spending
- Stablecoin issuer accounts -- 2-of-3 compliance officers
- Bridge oracle key rotation -- 4-of-7 oracle operators
- Project team treasuries -- Shared control with no single point of failure
- Escrow accounts -- Neutral third-party involved in release
How It Works
- Create -- Define signers (2-20 addresses) and threshold (M approvals required)
- Propose -- Any signer proposes a transaction (proposer auto-approves)
- Approve -- Other signers approve. When threshold is met, the transaction executes automatically
- Execute -- Inner operations run with the multi-sig address as the sender
The multi-sig can execute any operation -- transfers, token creation, staking, DEX operations, bridge actions, and more.
Multi-Sig Address
The multi-sig account address is deterministic -- derived from the sorted signer list and threshold. This means you can compute the address locally before creating it on-chain.
The same set of signers with the same threshold always produces the same multi-sig address, regardless of who creates it or when.
CLI Usage
Create a Multi-Sig Account
# Create a 2-of-3 multi-sig
vexidus multisig create \
--signers Vx0Signer1...,Vx0Signer2...,Vx0Signer3... \
--threshold 2 \
--label "Team Treasury"
Propose a Transaction
# Propose a VXS transfer from the multi-sig
vexidus multisig propose \
--account Vx0MultiSigAddr... \
--operations '[{"Transfer":{"to":"Vx0Recipient...","token_symbol":"VXS","amount":50000000000}}]' \
--description "Monthly operational budget"
Approve a Pending Transaction
# Second signer approves -- if threshold met, tx executes automatically
vexidus multisig approve \
--account Vx0MultiSigAddr... \
--tx-id 0
Revoke an Approval
# Revoke before execution
vexidus multisig revoke \
--account Vx0MultiSigAddr... \
--tx-id 0
View Multi-Sig Details
# Show account info (signers, threshold, label)
vexidus multisig show --account Vx0MultiSigAddr...
# List all transactions (pending, executed)
vexidus multisig list-txs --account Vx0MultiSigAddr...
RPC Methods
| Method | Type | Description |
|---|---|---|
vex_createMultiSig | Write | Create an M-of-N multi-sig account |
vex_proposeMultiSigTx | Write | Propose a transaction (operations as JSON) |
vex_approveMultiSigTx | Write | Approve a pending transaction |
vex_revokeMultiSigApproval | Write | Revoke an approval before execution |
vex_getMultiSig | Read | Get multi-sig account details |
vex_listMultiSigTxs | Read | List transactions for a multi-sig |
Example: Create a Multi-Sig
curl -s https://testnet.vexidus.io \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "vex_createMultiSig",
"params": [
"CREATOR_ADDRESS",
["SIGNER_1", "SIGNER_2", "SIGNER_3"],
2,
"Team Treasury"
],
"id": 1
}' | jq .
Example: Get Multi-Sig Info
curl -s https://testnet.vexidus.io \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "vex_getMultiSig",
"params": ["MULTISIG_ADDRESS"],
"id": 1
}' | jq .
Response:
{
"address": "Vx0MultiSig...",
"signers": ["Vx0Signer1...", "Vx0Signer2...", "Vx0Signer3..."],
"threshold": 2,
"label": "Team Treasury",
"created_at_height": 145000
}
SDK Usage
use vexidus_sdk::BundleBuilder;
use vexidus_types::{Address, Operation};
// 1. Create a 2-of-3 multi-sig
let bundle = BundleBuilder::new(&creator)?
.create_multisig(
vec![signer_a, signer_b, signer_c],
2,
"Team Treasury".to_string(),
)
.nonce(nonce)
.sign(&creator_wallet);
// 2. Propose a transfer from the multi-sig
let ops = vec![Operation::Transfer {
to: recipient,
token_symbol: "VXS".to_string(),
amount: 50_000_000_000, // 50 VXS
}];
let bundle = BundleBuilder::new(&signer_a)?
.propose_multisig_tx(multisig_addr, ops, "Q1 budget".to_string())
.nonce(nonce)
.sign(&signer_a_wallet);
// 3. Approve (second signer -- triggers execution at threshold)
let bundle = BundleBuilder::new(&signer_b)?
.approve_multisig_tx(multisig_addr, 0) // tx_id=0
.nonce(nonce)
.sign(&signer_b_wallet);
// 4. (Optional) Revoke approval before execution
let bundle = BundleBuilder::new(&signer_b)?
.revoke_multisig_approval(multisig_addr, 0)
.nonce(nonce)
.sign(&signer_b_wallet);
Transaction Lifecycle
Proposed (Pending)
| proposer auto-approved (1/M)
|
+-- Signer B approves (2/M)
| |
| +-- Threshold met? --> Executed (operations run)
| |
| +-- Not yet --> still Pending
|
+-- Signer revokes --> remove approval, back to Pending
If threshold is 1 (single-signer multi-sig), the transaction executes immediately on proposal.
Constraints
| Constraint | Value |
|---|---|
| Min signers | 2 |
| Max signers | 20 |
| Min threshold | 2 |
| Max threshold | Number of signers |
| Label length | 1 -- 64 characters |
| Description length | 1 -- 256 characters |
| Duplicate signers | Not allowed |
| Zero addresses | Not allowed |
| Supported inner operations | All (transfer, stake, token create, DEX, bridge, etc.) |
| Self-modification | Not supported (cannot change signers/threshold) |
Create a new multi-sig account with the updated signer set and migrate funds from the old one. This prevents malicious signer reconfiguration.
Best Practices
- Use odd thresholds -- 2-of-3, 3-of-5, 4-of-7 prevent deadlocks where exactly half approve
- Store keys separately -- Different devices, different people, different physical locations
- Label descriptively -- The label is stored on-chain and visible in explorers
- Monitor pending transactions -- Signers should regularly check
vex_listMultiSigTxs - Start with higher thresholds -- You can always create a new multi-sig with a lower threshold, but you can't raise it without migration
- Test on testnet first -- Create a test multi-sig, propose, approve, and verify execution before using for real assets
Events
Multi-sig operations emit the following on-chain events (visible via vex_getTransactionReceipt):
| Event | When |
|---|---|
MultiSigCreated | Account created with signers and threshold |
MultiSigTxProposed | New transaction proposed by a signer |
MultiSigTxApproved | Signer approved (includes current approval count) |
MultiSigTxExecuted | Threshold met, inner operations executed |
MultiSigApprovalRevoked | Signer revoked their approval |