| name | vultisig |
| description | Use this skill when an agent needs to create crypto wallets, send transactions, swap tokens, check balances, or perform any on-chain operation across 36+ blockchains using threshold signatures (TSS). Vultisig SDK provides self-custodial MPC vaults — no seed phrases, no single point of failure. Fast Vaults (2-of-2 with VultiServer) enable fully autonomous agent operations without human approval. |
| user-invocable | true |
Vultisig SDK Skill (agent-first)
What this Skill is for
- Creating and managing self-custodial crypto vaults (Fast Vault for agents, Secure Vault for multi-device)
- Sending transactions across 36+ blockchains (Bitcoin, Ethereum, Solana, Cosmos, and more)
- Swapping tokens cross-chain via THORChain, MayaChain, 1inch, LiFi, KyberSwap
- Querying balances and gas fees across all supported chains
- Importing/exporting vault backups (.vult files)
- Importing existing wallets via BIP39 seedphrase
- Building automated strategies: DCA, rebalancing, conditional swaps, agent-to-agent payments
Default stack decisions
-
Fast Vault (2-of-2) for all agent use cases
- Agent holds one key share, VultiServer holds the other
- VultiServer auto-co-signs based on policy rules — no human in the loop
- Use Secure Vault only when multi-device human approval is required
-
TypeScript SDK (@vultisig/sdk) as primary interface
-
MemoryStorage for ephemeral agents, implement Storage interface for persistent agents
MemoryStorage is the only storage exported from the SDK
- For persistent vaults, implement the
Storage interface backed by your preferred store
-
3-step transaction flow: prepare → sign → broadcast
- Never skip steps. Always prepare the keysign payload first, then sign, then broadcast.
- Fast Vault signing is automatic (VultiServer co-signs). Secure Vault requires device coordination.
-
Amounts as bigint (smallest unit) for sends, number (human-readable) for swaps
prepareSendTx takes amount: bigint (e.g., BigInt('100000000000000000') for 0.1 ETH)
getSwapQuote takes amount: number (e.g., 0.1 for 0.1 ETH)
Operating procedure
1. Initialize SDK
import { Vultisig, MemoryStorage } from '@vultisig/sdk';
const sdk = new Vultisig({ storage: new MemoryStorage() });
await sdk.initialize();
Source: Vultisig.ts
2. Create a Fast Vault
Two-step process: create (triggers email verification) then verify.
const vaultId = await sdk.createFastVault({
name: 'my-agent-vault',
email: 'agent@example.com',
password: 'secure-password',
});
const vault = await sdk.verifyVault(vaultId, '123456');
Risk notes:
- The password encrypts the vault share. If lost, the vault cannot be recovered.
- The email verification code is required — agents must have email access or an email relay.
2b. Create a Secure Vault (human co-signing)
When agents need human approval before executing transactions (high-value transfers, treasury ops, compliance flows), use a Secure Vault. The agent holds one share, the human holds the other. The human co-signs via the Vultisig mobile app by scanning a QR code — the transaction only executes when both parties agree.
const { vault, vaultId, sessionId } = await sdk.createSecureVault({
name: 'agent-with-human-approval',
onQRCodeReady: (qrPayload) => {
displayQRCode(qrPayload);
},
onDeviceJoined: (deviceId, total, required) => {
console.log(`Device joined: ${total}/${required}`);
},
});
Signing requires the human to participate:
const signature = await vault.sign(payload, {
onQRCodeReady: (qr) => {
displayQRCode(qr);
},
onDeviceJoined: (id, total, required) => {
console.log(`Signing: ${total}/${required} devices ready`);
},
});
Source: SecureVault.ts
When to use Secure Vault over Fast Vault:
- Transactions above a risk threshold that need human sign-off
- Treasury or DAO operations requiring human approval
- Compliance workflows where an agent should not act unilaterally
3. Get addresses
const ethAddress = await vault.address('Ethereum');
const btcAddress = await vault.address('Bitcoin');
const solAddress = await vault.address('Solana');
const allAddresses = await vault.addresses();
Source: VaultBase.ts
Chain identifiers use PascalCase strings matching the Chain enum: 'Bitcoin', 'Ethereum', 'Solana', 'THORChain', 'Cosmos', 'Polygon', 'Arbitrum', 'Base', 'Optimism', 'Avalanche', 'BSC', etc.
Full chain list: Chain.ts
4. Check balances
const ethBalance = await vault.balance('Ethereum');
const allBalances = await vault.balances();
const fresh = await vault.updateBalance('Ethereum');
Token balances (ERC-20, SPL, etc.)
const usdcBalance = await vault.balance('Ethereum', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48');
const ethTokens = await vault.tokenBalances('Ethereum');
const everything = await vault.balances(undefined, true);
Risk notes:
- Native balance and token balances are separate queries.
vault.balance('Ethereum') returns only ETH, not ERC-20s.
- Token balances require the contract address as the
tokenId parameter.
5. Estimate gas
const evmGas = await vault.gas('Ethereum');
const utxoGas = await vault.gas('Bitcoin');
const cosmosGas = await vault.gas('Cosmos');
Source: VaultBase.ts — gas<C extends Chain>(chain: C): Promise<GasInfoForChain<C>>
6. Send a transaction
3-step flow: prepareSendTx → sign → broadcastTx
const payload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: ethAddress,
decimals: 18,
ticker: 'ETH',
},
receiver: '0xRecipientAddress...',
amount: BigInt('100000000000000000'),
memo: '',
});
const signature = await vault.sign(payload);
const txHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: payload,
signature: signature,
});
const url = Vultisig.getTxExplorerUrl('Ethereum', txHash);
Source: VaultBase.prepareSendTx(), FastVault.sign()
Risk notes:
amount is in the chain's smallest unit (wei for ETH, satoshi for BTC). Miscalculating decimals will send wrong amounts.
- Always verify the receiver address. Transactions are irreversible.
- Check gas estimation before sending to avoid stuck transactions.
Sending ERC-20 / tokens
To send tokens instead of native currency, add the id field (contract address) to the coin object:
const tokenPayload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: ethAddress,
decimals: 6,
ticker: 'USDC',
id: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
receiver: '0xRecipientAddress...',
amount: BigInt('10000000'),
});
const sig = await vault.sign(tokenPayload);
const txHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: tokenPayload,
signature: sig,
});
Risk notes:
- The
id field is the token contract address. Without it, the SDK treats it as a native transfer.
- Use the token's decimals, not the chain's. USDC = 6, WETH = 18, WBTC = 8.
- The sender still needs native ETH/gas token to pay transaction fees.
7. Swap tokens
4-step flow: getSwapQuote → prepareSwapTx → sign → broadcastTx
const quote = await vault.getSwapQuote({
fromCoin: {
chain: 'Ethereum',
address: ethAddress,
decimals: 18,
ticker: 'ETH',
},
toCoin: {
chain: 'Ethereum',
address: usdcAddress,
decimals: 6,
ticker: 'USDC',
id: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
amount: 0.1,
});
const swapResult = await vault.prepareSwapTx({
fromCoin: quote.fromCoin,
toCoin: quote.toCoin,
amount: 0.1,
swapQuote: quote,
});
(swapResult.) {
approvalSig = vault.(swapResult.);
vault.({
: ,
: swapResult.,
: approvalSig,
});
}
swapSig = vault.(swapResult.);
swapTxHash = vault.({
: ,
: swapResult.,
: swapSig,
});
Swap providers (auto-routed for best rate):
- THORChain — Native cross-chain (BTC <> ETH, etc.)
- MayaChain — Additional cross-chain pairs
- 1inch — EVM DEX aggregation
- LiFi — Cross-chain + cross-DEX
- KyberSwap — EVM DEX aggregation
Risk notes:
- Swap amounts use human-readable numbers (
0.1), not bigint. The SDK handles decimal conversion.
- Check
quote.warnings before executing — may contain slippage or liquidity warnings.
- ERC-20 token swaps may require a separate approval transaction (
approvalPayload).
- Cross-chain swaps take longer (minutes, not seconds) and have different failure modes.
8. Export / Import vault
const { filename, data } = await vault.export('backup-password');
const importedVault = await sdk.importVault(data, 'backup-password');
9. Create vault from seedphrase
const validation = await sdk.validateSeedphrase('word1 word2 ...');
const discovery = await sdk.discoverChainsFromSeedphrase('word1 word2 ...');
const vaultId = await sdk.createFastVaultFromSeedphrase({
name: 'imported-vault',
email: 'agent@example.com',
password: 'secure-password',
mnemonic: 'word1 word2 ...',
});
const vault = await sdk.verifyVault(vaultId, 'email-code');
Risk notes:
- Seedphrase import creates a new TSS vault from the seed — the original seed-based wallet still exists independently.
- Handle seedphrases with extreme care. Never log, store in plaintext, or transmit unencrypted.
10. Vault lifecycle management
const vaults = await sdk.listVaults();
await sdk.setActiveVault(vault);
const active = await sdk.getActiveVault();
if (Vultisig.isFastVault(vault)) { }
if (Vultisig.isSecureVault(vault)) { }
await sdk.deleteVault(vault);
11. Check transaction status
After broadcasting, use the explorer URL or chain-specific methods to confirm transactions:
const explorerUrl = Vultisig.getTxExplorerUrl('Ethereum', txHash);
const addressUrl = Vultisig.getAddressExplorerUrl('Bitcoin', btcAddress);
For automated strategies that need to confirm completion before the next action, poll the balance or use an external RPC/indexer to check transaction finality. The SDK does not provide a built-in tx status poller — use vault.updateBalance() to force-refresh after a broadcast and compare before/after.
const balanceBefore = await vault.balance('Ethereum');
await new Promise(r => setTimeout(r, 15000));
const balanceAfter = await vault.updateBalance('Ethereum');
12. Address book
Manage recurring recipients for automated transfers:
const allContacts = await sdk.getAddressBook();
const ethContacts = await sdk.getAddressBook('Ethereum');
await sdk.addAddressBookEntry([
{ chain: 'Ethereum', address: '0x...', name: 'Treasury' },
{ chain: 'Bitcoin', address: 'bc1...', name: 'Cold Storage' },
]);
await sdk.updateAddressBookEntry('Ethereum', '0x...', 'Main Treasury');
await sdk.removeAddressBookEntry([
{ chain: 'Ethereum', address: '0x...' },
]);
Source: Vultisig.ts
13. $VULT discount tiers
Holding $VULT tokens reduces swap fees (up to 50%). The SDK can check and update the agent's discount tier:
const tier = await vault.getDiscountTier();
const newTier = await vault.updateDiscountTier();
Token contract: 0xb788144DF611029C60b859DF47e79B7726C4DEBa (Ethereum)
14. Listen to events
sdk.on('vaultCreationProgress', (data) => { });
sdk.on('vaultCreationComplete', (data) => { });
sdk.on('vaultChanged', (data) => { });
vault.on('balanceUpdated', (data) => { });
vault.on('transactionSigned', (data) => { });
vault.on('transactionBroadcast', (data) => { });
vault.on('signingProgress', (data) => { });
vault.on('swapQuoteReceived', (data) => { });
vault.on('qrCodeReady', (data) => { });
vault.on('deviceJoined', { });
vault.(, { });
vault.(, { });
sdk.(, { });
Source: packages/sdk/src/events/
Supported chains
Source: Chain.ts
| Category | Chains | Signature |
|---|
| UTXO | Bitcoin, Litecoin, Dogecoin, Bitcoin Cash, Dash, Zcash | ECDSA |
| EVM | Ethereum, BSC, Polygon, Avalanche, Arbitrum, Optimism, Base, Blast, Cronos, zkSync, Hyperliquid, Mantle, Sei | ECDSA |
| Cosmos/IBC | THORChain, MayaChain, Cosmos Hub, Osmosis, Dydx, Kujira, Noble, Terra, Terra Classic, Akash | ECDSA |
| Other | Solana, Sui, Polkadot, TON, Ripple, Tron, Cardano | EdDSA / Mixed |
Security model
- No seed phrases — vault shares replace 12/24 word seeds
- No single point of failure — no device holds a complete private key
- No on-chain key registration — unlike multi-sig wallets
- DKLS23 protocol — 3-round TSS, co-developed with Silence Laboratories
- Open source and audited
- Docs: Security & Technology
CLI alternative
npm install -g @vultisig/sdk
vsig vault create --name agent-vault --type fast
vsig balance --chain Ethereum
vsig send --chain Ethereum --to 0x... --amount 0.1
vsig swap --from ETH --to USDC --amount 0.1
Source: clients/cli/
Progressive disclosure