Install
openclaw skills install vultisig-sdkUse 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.
openclaw skills install vultisig-sdkFast Vault (2-of-2) for all agent use cases
TypeScript SDK (@vultisig/sdk) as primary interface
npm install @vultisig/sdkdocs/SDK-USERS-GUIDE.mdMemoryStorage for ephemeral agents, implement Storage interface for persistent agents
MemoryStorage is the only storage exported from the SDKStorage interface backed by your preferred store3-step transaction flow: prepare → sign → broadcast
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)import { Vultisig, MemoryStorage } from '@vultisig/sdk';
const sdk = new Vultisig({ storage: new MemoryStorage() });
await sdk.initialize();
Source:
Vultisig.ts
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',
});
// Verify with the code sent to the email
const vault = await sdk.verifyVault(vaultId, '123456');
// Returns: FastVault instance — ready for operations
Risk notes:
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) => {
// Display QR for the human co-signer to scan with Vultisig app
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) => {
// Human must scan this QR with Vultisig app to co-sign
displayQRCode(qr);
},
onDeviceJoined: (id, total, required) => {
console.log(`Signing: ${total}/${required} devices ready`);
},
});
// Completes only when the human co-signer participates
Source:
SecureVault.ts
When to use Secure Vault over Fast Vault:
const ethAddress = await vault.address('Ethereum');
const btcAddress = await vault.address('Bitcoin');
const solAddress = await vault.address('Solana');
// All addresses at once
const allAddresses = await vault.addresses();
// Returns: Record<string, string>
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
// Native chain balance
const ethBalance = await vault.balance('Ethereum');
// Returns Balance: {
// amount: string, // Raw amount in smallest unit
// decimals: number, // Chain decimals (18 for ETH)
// symbol: string, // "ETH"
// chainId: string,
// fiatValue?: number, // USD value if available
// }
// Multiple chains
const allBalances = await vault.balances();
// Returns: Record<string, Balance>
// Force refresh (clears cache)
const fresh = await vault.updateBalance('Ethereum');
// Get a specific token balance by contract address
const usdcBalance = await vault.balance('Ethereum', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48');
// Returns Balance: { amount: "1000000", decimals: 6, symbol: "USDC", ... }
// Get all token balances on a chain
const ethTokens = await vault.tokenBalances('Ethereum');
// Returns: Token[] — all tokens with non-zero balances
// Include tokens when fetching multi-chain balances
const everything = await vault.balances(undefined, true); // includeTokens = true
Risk notes:
vault.balance('Ethereum') returns only ETH, not ERC-20s.tokenId parameter.// Returns chain-specific gas info
const evmGas = await vault.gas('Ethereum');
// EvmGasInfo: { gasPrice, gasPriceGwei, maxFeePerGas, maxPriorityFeePerGas, gasLimit, estimatedCostUSD }
const utxoGas = await vault.gas('Bitcoin');
// UtxoGasInfo: { gasPrice, byteFee, estimatedCostUSD }
const cosmosGas = await vault.gas('Cosmos');
// CosmosGasInfo: { gasPrice, gas, estimatedCostUSD }
Source:
VaultBase.ts—gas<C extends Chain>(chain: C): Promise<GasInfoForChain<C>>
3-step flow: prepareSendTx → sign → broadcastTx
// Step 1: Prepare keysign payload
const payload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: ethAddress, // Sender address (from vault.address())
decimals: 18,
ticker: 'ETH',
},
receiver: '0xRecipientAddress...',
amount: BigInt('100000000000000000'), // 0.1 ETH in wei
memo: '', // Optional
});
// Returns: KeysignPayload
// Step 2: Sign (Fast Vault — VultiServer co-signs automatically)
const signature = await vault.sign(payload);
// Returns: Signature { signature: string, recovery?: number, format: 'DER' | 'ECDSA' | 'EdDSA' }
// Step 3: Broadcast
const txHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: payload,
signature: signature,
});
// Returns: string (transaction hash)
// Explorer URL
const url = Vultisig.getTxExplorerUrl('Ethereum', txHash);
Risk notes:
amount is in the chain's smallest unit (wei for ETH, satoshi for BTC). Miscalculating decimals will send wrong amounts.To send tokens instead of native currency, add the id field (contract address) to the coin object:
// Send 10 USDC on Ethereum
const tokenPayload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: ethAddress,
decimals: 6, // USDC has 6 decimals
ticker: 'USDC',
id: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Token contract address
},
receiver: '0xRecipientAddress...',
amount: BigInt('10000000'), // 10 USDC (6 decimals)
});
const sig = await vault.sign(tokenPayload);
const txHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: tokenPayload,
signature: sig,
});
Risk notes:
id field is the token contract address. Without it, the SDK treats it as a native transfer.4-step flow: getSwapQuote → prepareSwapTx → sign → broadcastTx
// Step 1: Get quote
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', // Token contract
},
amount: 0.1, // Human-readable (NOT bigint)
});
// Returns: SwapQuoteResult {
// provider: string,
// estimatedOutput: bigint,
// estimatedOutputFiat?: number,
// requiresApproval: boolean,
// fees: SwapFees,
// warnings: string[],
// }
// Step 2: Prepare swap transaction
const swapResult = await vault.prepareSwapTx({
fromCoin: quote.fromCoin,
toCoin: quote.toCoin,
amount: 0.1,
swapQuote: quote,
});
// Returns: SwapPrepareResult {
// keysignPayload: KeysignPayload,
// approvalPayload?: KeysignPayload, // If token approval needed
// quote: SwapQuoteResult,
// }
// Step 2.5: If approval required, sign and broadcast approval first
if (swapResult.approvalPayload) {
const approvalSig = await vault.sign(swapResult.approvalPayload);
await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: swapResult.approvalPayload,
signature: approvalSig,
});
}
// Step 3: Sign swap
const swapSig = await vault.sign(swapResult.keysignPayload);
// Step 4: Broadcast swap
const swapTxHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload: swapResult.keysignPayload,
signature: swapSig,
});
Swap providers (auto-routed for best rate):
Risk notes:
0.1), not bigint. The SDK handles decimal conversion.quote.warnings before executing — may contain slippage or liquidity warnings.approvalPayload).// Export to encrypted .vult file
const { filename, data } = await vault.export('backup-password');
// filename: string, data: Base64-encoded vault backup
// Import from .vult file
const importedVault = await sdk.importVault(data, 'backup-password');
// Validate BIP39 seedphrase
const validation = await sdk.validateSeedphrase('word1 word2 ...');
// Returns: { valid: boolean, wordCount: number, error?: string }
// Discover which chains have existing balances
const discovery = await sdk.discoverChainsFromSeedphrase('word1 word2 ...');
// Returns: ChainDiscoveryAggregate
// Create Fast Vault from seedphrase (still needs email verification)
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:
// List all vaults
const vaults = await sdk.listVaults();
// Set active vault
await sdk.setActiveVault(vault);
// Get active vault
const active = await sdk.getActiveVault();
// Check vault type
if (Vultisig.isFastVault(vault)) { /* FastVault methods */ }
if (Vultisig.isSecureVault(vault)) { /* SecureVault methods */ }
// Delete vault
await sdk.deleteVault(vault);
After broadcasting, use the explorer URL or chain-specific methods to confirm transactions:
// Get explorer URL for any chain
const explorerUrl = Vultisig.getTxExplorerUrl('Ethereum', txHash);
// e.g., "https://etherscan.io/tx/0x..."
const addressUrl = Vultisig.getAddressExplorerUrl('Bitcoin', btcAddress);
// e.g., "https://mempool.space/address/bc1..."
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.
// Pattern: confirm send completed
const balanceBefore = await vault.balance('Ethereum');
// ... broadcast transaction ...
await new Promise(r => setTimeout(r, 15000)); // Wait for block confirmation
const balanceAfter = await vault.updateBalance('Ethereum');
// Compare balanceBefore.amount vs balanceAfter.amount
Manage recurring recipients for automated transfers:
// Get saved addresses (optionally filter by chain)
const allContacts = await sdk.getAddressBook();
const ethContacts = await sdk.getAddressBook('Ethereum');
// Add entries
await sdk.addAddressBookEntry([
{ chain: 'Ethereum', address: '0x...', name: 'Treasury' },
{ chain: 'Bitcoin', address: 'bc1...', name: 'Cold Storage' },
]);
// Update a name
await sdk.updateAddressBookEntry('Ethereum', '0x...', 'Main Treasury');
// Remove entries
await sdk.removeAddressBookEntry([
{ chain: 'Ethereum', address: '0x...' },
]);
Source:
Vultisig.ts
Holding $VULT tokens reduces swap fees (up to 50%). The SDK can check and update the agent's discount tier:
// Check current discount tier
const tier = await vault.getDiscountTier();
// Returns: string | null — e.g., "gold", "silver", or null if no discount
// Update tier (after acquiring more $VULT)
const newTier = await vault.updateDiscountTier();
Token contract:
0xb788144DF611029C60b859DF47e79B7726C4DEBa(Ethereum)
// SDK-level events
sdk.on('vaultCreationProgress', (data) => { /* keygen progress */ });
sdk.on('vaultCreationComplete', (data) => { /* vault ready */ });
sdk.on('vaultChanged', (data) => { /* active vault switched */ });
// Vault-level events
vault.on('balanceUpdated', (data) => { /* balance changed */ });
vault.on('transactionSigned', (data) => { /* signature complete */ });
vault.on('transactionBroadcast', (data) => { /* tx submitted */ });
vault.on('signingProgress', (data) => { /* signing steps */ });
vault.on('swapQuoteReceived', (data) => { /* quote ready */ });
// SecureVault only (multi-device coordination)
vault.on('qrCodeReady', (data) => { /* show QR for device pairing */ });
vault.on('deviceJoined', (data) => { /* co-signer connected */ });
vault.on('allDevicesReady', (data) => { /* threshold met, signing can proceed */ });
// Error handling
vault.on('error', (error) => { /* handle errors */ });
sdk.on('error', (error) => { /* handle SDK-level errors */ });
Source:
packages/sdk/src/events/
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 |
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/