vultisig

Review·Scanned 2/18/2026

This skill lets agents create and manage self-custodial crypto vaults, send transactions, swap tokens, and import/export encrypted backups via the @vultisig/sdk. Examples run shell commands (npm install -g @vultisig/sdk, vsig ...), handle secrets (mnemonic, .vult, passwords), and direct network use of github.com, docs.vultisig.com, and etherscan.io.

from clawhub.ai·vaaab6eb·19.2 KB·0 installs
Scanned from 0.1.0 at aaab6eb · Transparency log ↗
$ vett add clawhub.ai/realpaaao/vultisigReview findings below

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

  1. 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
  2. TypeScript SDK (@vultisig/sdk) as primary interface

  3. 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
  4. 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.
  5. 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',
});

// Verify with the code sent to the email
const vault = await sdk.verifyVault(vaultId, '123456');
// Returns: FastVault instance — ready for operations

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) => {
    // 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:

  • 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');

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

4. Check balances

// 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');

Token balances (ERC-20, SPL, etc.)

// 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:

  • 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

// 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.tsgas<C extends Chain>(chain: C): Promise<GasInfoForChain<C>>

6. Send a transaction

3-step flow: prepareSendTxsignbroadcastTx

// 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);

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:

// 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:

  • 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: getSwapQuoteprepareSwapTxsignbroadcastTx

// 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):

  • 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

// 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');

9. Create vault from seedphrase

// 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:

  • 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

// 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);

11. Check transaction status

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

12. Address book

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

13. $VULT discount tiers

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)

14. Listen to events

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

Supported chains

Source: Chain.ts

CategoryChainsSignature
UTXOBitcoin, Litecoin, Dogecoin, Bitcoin Cash, Dash, ZcashECDSA
EVMEthereum, BSC, Polygon, Avalanche, Arbitrum, Optimism, Base, Blast, Cronos, zkSync, Hyperliquid, Mantle, SeiECDSA
Cosmos/IBCTHORChain, MayaChain, Cosmos Hub, Osmosis, Dydx, Kujira, Noble, Terra, Terra Classic, AkashECDSA
OtherSolana, Sui, Polkadot, TON, Ripple, Tron, CardanoEdDSA / 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