A TypeScript SDK for secure multi-party computation (MPC) and blockchain operations using the Vultisig protocol. Build secure, decentralized applications with threshold signature schemes and multi-chain support.
- 🔐 Multi-Party Computation (MPC) - Secure threshold signatures using DKLS and Schnorr protocols
- 🏦 Fast Vault - Server-assisted 2-of-2 vault for quick setup and instant signing
- 🛡️ Secure Vault - Multi-device N-of-M threshold signing with mobile device pairing
- 📲 QR Code Pairing - Pair with Vultisig mobile apps (iOS/Android) for vault creation and signing
- 🌐 Multi-Chain Support - Bitcoin, Ethereum, Solana, THORChain, and 37 blockchains
- 🔗 Address Derivation - Generate addresses across multiple blockchain networks
- 📱 Cross-Platform - Works in browsers, Node.js, and Electron (React Native coming soon)
- 🔒 Vault Management - Import, export, encrypt, and decrypt vault keyshares
- 🔑 Seedphrase Import - Import existing BIP39 mnemonics with automatic chain discovery
- 💰 VULT Discount Tiers - Automatic swap fee discounts based on VULT token holdings
- 📋 Token Registry - Built-in known token database, fee coin lookup, and on-chain token discovery
- 🛡️ Security Scanning - Transaction validation/simulation via Blockaid, site phishing detection
- 💵 Price Feeds - Fetch token prices via CoinGecko
- 🏪 Fiat On-Ramp - Generate Banxa buy URLs for 23+ supported chains
- 🔔 Push Notifications - Real-time signing coordination via WebSocket or platform push (APNs, FCM, Web Push)
- 🌍 WASM Integration - High-performance cryptographic operations via WebAssembly
npm install @vultisig/sdkimport { Vultisig } from '@vultisig/sdk'
// Storage is auto-configured for your platform:
// - Node.js/Electron: FileStorage (~/.vultisig)
// - Browser: BrowserStorage (IndexedDB)
const sdk = new Vultisig()
// Initialize WASM modules
await sdk.initialize()WARNING — Vault Persistence: Do not use
MemoryStoragein production. It is non-persistent — all vault keyshares are lost when the process exits, resulting in permanent loss of funds. The SDK auto-configures persistent storage for your platform. Always back up vaults withvault.export().
// Create a new vault using VultiServer
const vaultId = await sdk.createFastVault({
name: 'My Secure Wallet',
email: 'user@example.com',
password: 'SecurePassword123!',
})
// User will receive a verification code via email
const code = '1234' // Get from user input
const vault = await sdk.verifyVault(vaultId, code)// Derive addresses for different blockchain networks
const btcAddress = await vault.address('Bitcoin')
const ethAddress = await vault.address('Ethereum')
const solAddress = await vault.address('Solana')
console.log('BTC:', btcAddress) // bc1q...
console.log('ETH:', ethAddress) // 0x...
console.log('SOL:', solAddress) // 9WzD...// Create a secure vault with 2-of-3 threshold
const { vault, vaultId, sessionId } = await sdk.createSecureVault({
name: 'Team Wallet',
devices: 3, // Total number of devices
threshold: 2, // Optional: defaults to ceil((devices+1)/2)
password: 'optional-password', // Optional: encrypt the vault
onQRCodeReady: qrPayload => {
// Display this QR code for other devices to scan with Vultisig app
displayQRCode(qrPayload)
},
onDeviceJoined: (deviceId, totalJoined, required) => {
console.log(`Device joined: ${totalJoined}/${required}`)
},
onProgress: step => {
console.log(`${step.step}: ${step.message}`)
},
})
console.log('Vault created:', vault.name)Sending a transaction is a 3-step process: prepare → extract hashes → sign.
// Step 1: Prepare the transaction (handles gas, nonce, UTXO selection automatically)
const keysignPayload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: await vault.address('Ethereum'),
decimals: 18,
ticker: 'ETH',
},
receiver: '0x742d35Cc6634C0532925a3b844Bc9e7595f42bE',
amount: 1000000000000000000n, // 1 ETH in wei (base units)
})
// Step 2: Extract message hashes
const messageHashes = await vault.extractMessageHashes(keysignPayload)
// Step 3: Sign
const signature = await vault.sign({
transaction: keysignPayload,
chain: 'Ethereum',
messageHashes,
})
// Step 4 (optional): Broadcast
const txHash = await vault.broadcastTx({
chain: 'Ethereum',
keysignPayload,
signature,
})For ERC-20 tokens, add id (the contract address) to the coin:
const keysignPayload = await vault.prepareSendTx({
coin: {
chain: 'Ethereum',
address: await vault.address('Ethereum'),
decimals: 6,
ticker: 'USDC',
id: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
receiver: '0x...',
amount: 1000000n, // 1 USDC (6 decimals)
})For Bitcoin/UTXO chains, UTXO selection and change outputs are handled automatically:
const keysignPayload = await vault.prepareSendTx({
coin: {
chain: 'Bitcoin',
address: await vault.address('Bitcoin'),
decimals: 8,
ticker: 'BTC',
},
receiver: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
amount: 50000000n, // 0.5 BTC in satoshis
})Secure vault signing uses the same prepare/sign flow, but requires device coordination via QR code:
const keysignPayload = await vault.prepareSendTx({
/* same as above */
})
const messageHashes = await vault.extractMessageHashes(keysignPayload)
await vault.sign(
{ transaction: keysignPayload, chain: 'Ethereum', messageHashes },
{
onQRCodeReady: qrPayload => {
displayQRCode(qrPayload) // Other devices scan this to co-sign
},
onDeviceJoined: (deviceId, total, required) => {
console.log(`Signing: ${total}/${required} devices ready`)
},
onProgress: step => {
console.log(`Signing progress: ${step.message}`)
},
}
)// Raw .vult contents as a string (e.g. fs.readFileSync('vault.vult', 'utf-8'))
const vaultContent = '...'
// Check if encrypted (sync)
const isEncrypted = sdk.isVaultEncrypted(vaultContent)
// Import vault
const vault = await sdk.importVault(vaultContent, isEncrypted ? 'password' : undefined)
// Export vault to backup format (as Blob)
const backupBlob = await vault.export('BackupPassword123!')
// Or export as base64 string
const backupBase64 = await vault.exportAsBase64('BackupPassword123!')Import an existing wallet from a BIP39 mnemonic. Supports all 10 BIP39 languages with automatic detection:
// Validate the seedphrase first (auto-detects language)
const validation = await sdk.validateSeedphrase(mnemonic)
if (!validation.valid) {
console.error(validation.error)
return
}
console.log(`Detected language: ${validation.detectedLanguage}`) // 'english', 'japanese', etc.
// Discover which chains have balances
const chains = await sdk.discoverChainsFromSeedphrase(
mnemonic,
[Chain.Bitcoin, Chain.Ethereum, Chain.THORChain],
progress => console.log(`${progress.chain}: ${progress.phase}`)
)
for (const result of chains) {
if (result.hasBalance) {
console.log(`${result.chain}: ${result.balance} ${result.symbol}`)
}
}
// Create FastVault from seedphrase (requires email verification)
const vaultId = await sdk.createFastVaultFromSeedphrase({
mnemonic,
name: 'Imported Wallet',
email: 'user@example.com',
password: 'SecurePassword123!',
discoverChains: true, // Auto-enable chains with balances
onProgress: step => console.log(step.message),
})
// Verify with email code
const vault = await sdk.verifyVault(vaultId, verificationCode)import { Vultisig, Chain, CosmosMsgType } from '@vultisig/sdk'
// Look up known tokens (static, no vault needed)
const tokens = Vultisig.getKnownTokens(Chain.Ethereum)
const usdc = Vultisig.getKnownToken(Chain.Ethereum, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
// Get native fee coin info
const feeCoin = Vultisig.getFeeCoin(Chain.Bitcoin) // { ticker: 'BTC', decimals: 8, ... }
// Fetch prices
const prices = await Vultisig.getCoinPrices({ ids: ['bitcoin', 'ethereum'] })
console.log(`BTC: $${prices.bitcoin}`)
// Discover tokens at vault address
const discovered = await vault.discoverTokens(Chain.Ethereum)
// Resolve token metadata (known registry → chain API fallback)
const token = await vault.resolveToken(Chain.Ethereum, '0x...')
// Cosmos message type constants
const msgType = CosmosMsgType.MsgSend // 'cosmos-sdk/MsgSend'// Scan a website for phishing (static, no vault needed)
const siteScan = await Vultisig.scanSite('https://suspicious-site.com')
if (siteScan.isMalicious) console.warn('Malicious site!')
// Validate a transaction before signing
const validation = await vault.validateTransaction(keysignPayload)
if (validation?.isRisky) {
console.warn(`Risk: ${validation.riskLevel} - ${validation.description}`)
}
// Simulate a transaction to preview asset changes
const simulation = await vault.simulateTransaction(keysignPayload)// Check supported chains
const chains = Vultisig.getBanxaSupportedChains() // 23+ chains
// Generate buy URL for vault address
const buyUrl = await vault.getBuyUrl(Chain.Bitcoin)
if (buyUrl) window.open(buyUrl)Coordinate multi-party signing by notifying vault members when a signing session is initiated.
// Step 1: Register device for vault notifications
// Token comes from your platform's push service (APNs, FCM, or Web Push)
await sdk.notifications.registerDevice({
vaultId: vault.publicKeys.ecdsa,
partyName: vault.localPartyId,
token: myPlatformPushToken,
deviceType: 'ios', // 'ios' | 'android' | 'web'
})
// Step 2: Notify other vault members when initiating a signing session
await sdk.notifications.notifyVaultMembers({
vaultId: vault.publicKeys.ecdsa,
vaultName: vault.name,
localPartyId: vault.localPartyId,
qrCodeData: keysignQrPayload, // session data for joining
})
// Step 3: Handle incoming push notifications
const unsubscribe = sdk.notifications.onSigningRequest(notification => {
console.log(`Signing request for vault: ${notification.vaultName}`)
// Use notification.qrCodeData to join the signing session
})The SDK handles server communication and state management. Your application is responsible for platform-specific push integration:
| Responsibility | Owner | Details |
|---|---|---|
| Obtain push token | You | Use platform APIs (APNs, FCM, Web Push) to get a device token |
| Register token with server | SDK | sdk.notifications.registerDevice() |
| Send notification to vault members | SDK | sdk.notifications.notifyVaultMembers() |
| Wire platform push handler | You | iOS delegate, FCM onMessage, service worker, etc. |
| Parse incoming notification | SDK | sdk.notifications.handleIncomingPush(data) |
| Display notification to user | You | OS notification, in-app alert, etc. |
| Route user to signing flow | You | Use qrCodeData from the parsed notification |
| Persist registration state | SDK | Stored automatically in SDK storage |
iOS — Register for remote notifications, pass APNs device token:
// In your AppDelegate / UNUserNotificationCenter handler:
sdk.notifications.handleIncomingPush(notification.userInfo)Android — Use Firebase Cloud Messaging:
// In your FirebaseMessagingService.onMessageReceived:
sdk.notifications.handleIncomingPush(remoteMessage.data)Browser / Extension — Use WebSocket for real-time delivery (no service worker needed):
// Register device
await sdk.notifications.registerDevice({
vaultId: vault.publicKeys.ecdsa,
partyName: vault.localPartyId,
token: myDeviceToken,
deviceType: 'web',
})
// Connect WebSocket — notifications delivered via onSigningRequest()
sdk.notifications.connect({
vaultId: vault.publicKeys.ecdsa,
partyName: vault.localPartyId,
token: myDeviceToken,
})
// Disconnect when done (also called by sdk.dispose())
sdk.notifications.disconnect()Alternatively, use Web Push API with VAPID key:
const vapidKey = await sdk.notifications.fetchVapidPublicKey()
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidKey,
})
await sdk.notifications.registerDevice({
vaultId: vault.publicKeys.ecdsa,
partyName: vault.localPartyId,
token: JSON.stringify(subscription.toJSON()),
deviceType: 'web',
})
// In your service worker push event:
sdk.notifications.handleIncomingPush(event.data.json())Node.js / CLI — Use WebSocket delivery (connect()) or parseNotificationPayload() manually if you implement your own transport.
For MCP servers, agents, and other code paths that hold raw public keys but no instantiated vault, the SDK exposes atomic prepare*FromKeys helpers that build unsigned KeysignPayloads directly from a VaultIdentity. These helpers do not enable signing — the resulting payload still has to be signed by a vault that holds the key shares. The security boundary is unchanged: only public identity material crosses the API.
import { prepareSendTxFromKeys, type VaultIdentity } from '@vultisig/sdk'
const identity: VaultIdentity = {
ecdsaPublicKey: '02abc...',
eddsaPublicKey: 'd1e2...',
hexChainCode: 'a1b2...',
localPartyId: 'server-1', // must match the value the signing devices were registered with
libType: 'DKLS',
}
const keysignPayload = await prepareSendTxFromKeys(identity, {
coin: { chain: 'Ethereum', address: '0x...', decimals: 18, ticker: 'ETH' },
receiver: '0x742d35Cc6634C0532925a3b844Bc9e7595f42bE',
amount: 1000000000000000000n,
})Companion helpers: prepareSwapTxFromKeys, prepareContractCallTxFromKeys, prepareSignAminoTxFromKeys, prepareSignDirectTxFromKeys, and getMaxSendAmountFromKeys. The atomic chain helpers getCoinBalance and getPublicKey are also re-exported.
The SDK supports address derivation and operations for 37 blockchain networks:
EVM Chains
| Network | Chain Value | Description |
|---|---|---|
| Ethereum | Ethereum |
Ethereum mainnet |
| Arbitrum | Arbitrum |
Arbitrum One L2 |
| Base | Base |
Base L2 (Coinbase) |
| Blast | Blast |
Blast L2 |
| Optimism | Optimism |
Optimism L2 |
| Zksync | Zksync |
zkSync Era |
| Mantle | Mantle |
Mantle L2 |
| Polygon | Polygon |
Polygon (MATIC) |
| BSC | BSC |
BNB Smart Chain |
| Avalanche | Avalanche |
Avalanche C-Chain |
| CronosChain | CronosChain |
Cronos |
| Hyperliquid | Hyperliquid |
Hyperliquid L1 |
| Sei | Sei |
Sei EVM |
UTXO Chains
| Network | Chain Value | Description |
|---|---|---|
| Bitcoin | Bitcoin |
Bitcoin mainnet |
| Bitcoin Cash | BitcoinCash ('Bitcoin-Cash') |
Bitcoin Cash |
| Litecoin | Litecoin |
Litecoin mainnet |
| Dogecoin | Dogecoin |
Dogecoin mainnet |
| Dash | Dash |
Dash mainnet |
| Zcash | Zcash |
Zcash mainnet |
Cosmos Chains
| Network | Chain Value | Description |
|---|---|---|
| THORChain | THORChain |
THORChain mainnet |
| MayaChain | MayaChain |
Maya Protocol |
| Cosmos | Cosmos |
Cosmos Hub |
| Osmosis | Osmosis |
Osmosis DEX |
| Dydx | Dydx |
dYdX chain |
| Kujira | Kujira |
Kujira chain |
| Terra | Terra |
Terra 2.0 |
| TerraClassic | TerraClassic |
Terra Classic |
| Noble | Noble |
Noble (USDC native) |
| Akash | Akash |
Akash Network |
Other Chains
| Network | Chain Value | Description |
|---|---|---|
| Solana | Solana |
Solana mainnet |
| Sui | Sui |
Sui mainnet |
| Polkadot | Polkadot |
Polkadot mainnet |
| Bittensor | Bittensor |
Bittensor TAO |
| Ton | Ton |
TON blockchain |
| Ripple | Ripple |
XRP Ledger |
| Tron | Tron |
TRON mainnet |
| Cardano | Cardano |
Cardano mainnet |
Use Chain values in SDK calls: Chain.Bitcoin, Chain.Ethereum, etc.
The SDK supports two vault types for different security and usability requirements:
| Feature | Fast Vault | Secure Vault |
|---|---|---|
| Threshold | 2-of-2 | N-of-M (configurable) |
| Setup | Server-assisted, instant | Multi-device, requires pairing |
| Signing | Instant via VultiServer | Requires device coordination |
| Use Cases | Personal wallets, quick setup | Team wallets, high security, custody |
| Device Pairing | None required | QR code with Vultisig mobile app |
| Password | Required | Optional |
Fast Vault - Best for:
- Individual users wanting quick setup
- Development and testing
- Situations where server-assisted signing is acceptable
Secure Vault - Best for:
- Team or organizational wallets
- High-value assets requiring multi-party approval
- Scenarios requiring configurable thresholds (2-of-3, 3-of-5, etc.)
- Maximum security without server dependency during signing
The SDK works with any JavaScript framework. Here's a React example:
import { Vultisig } from '@vultisig/sdk'
import type { VaultBase } from '@vultisig/sdk'
import { useState, useEffect } from 'react'
function VaultApp() {
// BrowserStorage (IndexedDB) is used automatically in browser environments
const [sdk] = useState(() => new Vultisig())
const [vault, setVault] = useState<VaultBase | null>(null)
const [addresses, setAddresses] = useState<Record<string, string>>({})
useEffect(() => {
// Initialize SDK on component mount
sdk.initialize().catch(console.error)
}, [sdk])
const createVault = async () => {
try {
const vaultId = await sdk.createFastVault({
name: 'My Wallet',
email: 'user@example.com',
password: 'SecurePassword123!'
})
// User receives verification code via email
const code = prompt('Enter verification code from email:')
const vault = await sdk.verifyVault(vaultId, code!)
setVault(vault)
} catch (error) {
console.error('Vault creation failed:', error)
}
}
const deriveAddresses = async () => {
if (!vault) return
const chains = ['Bitcoin', 'Ethereum', 'Solana']
const results: Record<string, string> = {}
for (const chain of chains) {
try {
results[chain] = await vault.address(chain)
} catch (error) {
console.error(`Failed to derive ${chain} address:`, error)
}
}
setAddresses(results)
}
return (
<div>
<h1>Vultisig SDK Demo</h1>
{!vault && (
<button onClick={createVault}>
Create Fast Vault
</button>
)}
{vault && (
<div>
<h2>Vault: {vault.name}</h2>
<p>Local Party: {vault.localPartyId}</p>
<button onClick={deriveAddresses}>
Derive Addresses
</button>
{Object.keys(addresses).length > 0 && (
<div>
<h3>Addresses</h3>
{Object.entries(addresses).map(([chain, address]) => (
<div key={chain}>
<strong>{chain.toUpperCase()}:</strong> {address}
</div>
))}
</div>
)}
</div>
)}
</div>
)
}
export default VaultAppconst sdk = new Vultisig({
autoInit: true, // Automatically initialize WASM modules on creation
serverUrl: 'https://api.vultisig.com', // Custom VultiServer endpoint
relayUrl: 'https://relay.vultisig.com', // Custom relay endpoint
})Install the peer dependency vite and add the SDK preset. It excludes wasm glue packages from the dev pre-bundler, wires wasm handling, browser shim aliases, and a lightweight process preamble, serves 7zz.wasm and wallet-core.wasm in dev, and emits both wasm assets into the build output. It does not write SDK artifacts into your source public/ directory.
With @vitejs/plugin-react (or your UI plugin), a minimal vite.config looks like this:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import vultisig from '@vultisig/sdk/vite'
export default defineConfig({
plugins: [react(), vultisig()],
})MPC WebAssembly (DKLS, Schnorr, ML-DSA) is loaded from the @vultisig/lib-* packages that ship with @vultisig/sdk — keep those packages in node_modules and do not pre-bundle them; the plugin sets optimizeDeps.exclude for you. You do not need to copy those .wasm files into public/.
7z-wasm is also excluded from pre-bundling so fetches line up with the package layout. @trustwallet/wallet-core is intentionally left available for Vite to pre-bundle because the package is CommonJS and browser dev servers need Vite's CJS interop for exports such as initWasm; the preset serves its wallet-core.wasm payload wherever the loader requests it in dev and emits it next to built chunks. Install the usual browser shims the preset pre-bundles (for example buffer, process, crypto-browserify, stream-browserify, events, util, path-browserify); the browser example shows a full dependency set.
import vultisig from '@vultisig/sdk/vite' returns a Vite PluginOption (a small preset of plugins that Vite flattens in place). Use plugins: [react(), vultisig()] or plugins: [vultisig()] without spreading. Options: sevenZipWasm, walletCoreWasm, nodePolyfills, shimResolver, aliases, optimizeDeps, defineGlobal, browserGlobals, debug.
defineGlobal defaults to true and adds Vite define values for browserified dependencies that still read global or process.env at module evaluation time. Vite applies define as a source replacement, so apps that intentionally read custom process.env.* values from client code should use import.meta.env or set defineGlobal: false and provide equivalent globals themselves. browserGlobals also defaults to true and injects the minimal runtime process preamble through Vite's index.html transform; non-HTML library/SSR entry setups should either keep their own preamble or disable it.
nodePolyfills defaults to false so Vite 8 (Rolldown) works out of the box. On Vite 5/6/7, you can set nodePolyfills: true to enable the full vite-plugin-node-polyfills / node-stdlib-browser layer if you need it.
There is no first-party Next or Webpack plugin. You need the same ideas as the Vite preset:
- Do not pre-bundle (or mangle)
@vultisig/lib-dkls,@vultisig/lib-schnorr,@vultisig/lib-mldsa,@trustwallet/wallet-core, or7z-wasmin a way that moves their.wasmor glue JS away from each other, orimport.meta.urlto the wasm will break. - Serve the 7z payload at
/7zz.wasmand the Trust Wallet Core payload where its Emscripten loader requestswallet-core.wasm, or change your code path to match how you host static assets. - Provide Node polyfills (Buffer,
process,crypto/streamshims) in the same spirit as the Vitevite-plugin-node-polyfillsblock.
next.config usually uses transpilePackages: ['@vultisig/sdk', ...], webpack.config.externals or serverExternalPackages for server builds, and a build-time static asset emission/copy step for 7zz.wasm. Exact values depend on your Next major version and app router; treat this as a checklist, not a drop-in.
| What | Where it comes from in the browser |
|---|---|
| DKLS / Schnorr / ML-DSA | Installed packages @vultisig/lib-dkls, @vultisig/lib-schnorr, @vultisig/lib-mldsa (transitive dependencies of @vultisig/sdk), loaded next to the wasm-bindgen glue. |
| Trust Wallet Core | Package @trustwallet/wallet-core (SDK dependency). The Vite plugin serves it in dev and emits assets/wallet-core.wasm for production builds. |
| 7z (compressed QR / keygen payloads) | 7z-wasm on npm; the file must be fetchable as /7zz.wasm unless you change getSevenZip in a fork. The Vite plugin serves it in dev and emits it into the build output. |
Do not use cp node_modules/@vultisig/sdk/dist/*.wasm public/ for modern releases — those binaries are not shipped that way. Use the Vite plugin (above) or host the 7z-wasm asset from your built/static output.
For Node.js and Electron, WASM and native libs are loaded from the package tree automatically.
Initialize the SDK and load all WASM modules.
Create a new vault using VultiServer assistance. Returns the vaultId.
Parameters:
options.name: string- Vault nameoptions.email: string- Email for verificationoptions.password: string- Vault encryption password
Verify vault creation with email verification code. Returns the verified vault.
Create a multi-device secure vault with N-of-M threshold signing.
Parameters:
options.name: string- Vault nameoptions.devices: number- Number of devices participating (minimum 2)options.threshold?: number- Signing threshold (defaults to ceil((devices+1)/2))options.password?: string- Optional vault encryption passwordoptions.onQRCodeReady?: (qrPayload: string) => void- Called when QR code is ready for device pairingoptions.onDeviceJoined?: (deviceId: string, total: number, required: number) => void- Called when a device joinsoptions.onProgress?: (step: VaultCreationStep) => void- Called with creation progress updates
Returns:
vault: SecureVault- The created vault instancevaultId: string- Unique vault identifiersessionId: string- Session ID used for creation
Validate a BIP39 mnemonic phrase.
Returns:
valid: boolean- Whether the mnemonic is validwordCount: number- Number of words (12 or 24)invalidWords?: string[]- Words not in BIP39 wordlisterror?: string- Error message if invalid
Discover chains with balances for a seedphrase.
Parameters:
mnemonic: string- BIP39 mnemonic phrasechains?: Chain[]- Chains to scan (defaults to common chains)onProgress?: (progress: ChainDiscoveryProgress) => void- Progress callback
Create a FastVault from a BIP39 seedphrase. Returns vaultId for email verification.
Parameters:
options.mnemonic: string- BIP39 mnemonic (12 or 24 words)options.name: string- Vault nameoptions.email: string- Email for verificationoptions.password: string- Vault encryption passwordoptions.discoverChains?: boolean- Auto-enable chains with balancesoptions.onProgress?: (step: VaultCreationStep) => void- Progress callbackoptions.onChainDiscovery?: (progress: ChainDiscoveryProgress) => void- Discovery callback
Create a SecureVault from a BIP39 seedphrase with multi-device MPC.
Parameters:
options.mnemonic: string- BIP39 mnemonic (12 or 24 words)options.name: string- Vault nameoptions.devices: number- Number of participating devicesoptions.threshold?: number- Signing thresholdoptions.password?: string- Optional encryption passwordoptions.onQRCodeReady?: (qrPayload: string) => void- QR callbackoptions.onDeviceJoined?: (deviceId, total, required) => void- Device join callback
Join an existing SecureVault creation session. Auto-detects keygen vs seedphrase mode.
Parameters:
qrPayload: string- QR code content from initiator (vultisig://...)options.mnemonic?: string- Required for seedphrase-based sessions, ignored for keygenoptions.devices: number- Number of participating devices (required)options.password?: string- Optional encryption passwordoptions.onProgress?: (step: VaultCreationStep) => void- Progress callbackoptions.onDeviceJoined?: (deviceId, total, required) => void- Device join callback
Derive a blockchain address for the given chain (called on Vault instance).
Import a vault from .vult file content (string).
Export a vault to encrypted backup format as a Blob.
Export a vault to encrypted backup format as a base64 string.
Build a transaction payload. Handles gas estimation, nonce, and UTXO selection automatically.
Parameters:
params.coin- Token to send:{ chain, address, decimals, ticker, id? }—idis the contract address for tokens (ERC-20, SPL, etc.)params.receiver: string- Recipient addressparams.amount: bigint- Amount in base units (wei, satoshis, etc.)params.memo?: string- Optional transaction memoparams.feeSettings?: FeeSettings- Optional custom fee overrides
Extract pre-signing message digests from a keysign payload. Required by sign().
Sign a transaction using MPC. Instant for Fast Vaults; requires device co-signing for Secure Vaults.
Parameters:
payload.transaction: KeysignPayload- FromprepareSendTx()payload.chain: string- Chain namepayload.messageHashes: string[]- FromextractMessageHashes()options.signal?: AbortSignal- Cancel the operationoptions.onQRCodeReady?: (qrPayload: string) => void- QR code callback (Secure Vault)options.onDeviceJoined?: (deviceId, total, required) => void- Device join callbackoptions.onProgress?: (step: SigningStep) => void- Progress updates
Returns: { signature, recovery?, format, signatures? } — signatures array is present for UTXO chains (one per input).
Broadcast a signed transaction. Returns the transaction hash.
params.chain: string- Chain to broadcast onparams.keysignPayload: KeysignPayload- FromprepareSendTx()params.signature: Signature- Fromsign()
Broadcast a pre-built raw transaction directly. Encoding depends on the chain:
- EVM & UTXO chains — hex-encoded transaction bytes
- Solana — base58 or base64 (auto-detected)
- Cosmos chains — base64-encoded JSON or raw bytes
- TON — base64-encoded BoC (Bag of Cells)
- Sui & Tron — JSON-encoded transaction
Parameters:
params.chain: string- Chain to broadcast onparams.rawTx: string- Raw transaction in the chain-appropriate encoding
Sign arbitrary bytes (not a blockchain transaction).
options.chain: string- Chain for signature algorithm selectionoptions.messages: (Uint8Array | Buffer | string)[]- Messages to signsigningOptions.signal?: AbortSignal- Cancel the operation
Get all known tokens for a chain from the built-in registry.
Look up a specific token by contract address (case-insensitive). Returns null if not found.
Get the native fee coin info for a chain (e.g., ETH for Ethereum, BTC for Bitcoin).
Fetch current token prices by CoinGecko IDs.
Parameters:
params.ids: string[]- CoinGecko price provider IDsparams.fiatCurrency?: string- Fiat currency code (default:'usd')
Get the list of chains supported by the Banxa fiat on-ramp.
Scan a website URL for malicious content via Blockaid.
Returns:
isMalicious: boolean- Whether the site is flagged as maliciousurl: string- The scanned URL
Discover tokens with non-zero balances at this vault's address. Supported: EVM (via 1inch), Solana (via Jupiter), Cosmos (via RPC).
Resolve token metadata by contract address. Checks known tokens registry first, then resolves from chain APIs.
Generate a Banxa fiat on-ramp URL for buying crypto to this vault's address. Returns null if chain is not supported by Banxa.
Validate a transaction for security risks before signing using Blockaid. Supported: EVM chains, Solana, Sui, Bitcoin. Returns null for unsupported chains.
Simulate a transaction to preview asset changes before signing. Supported: EVM chains, Solana. Returns null for unsupported chains.
Check the on-chain status of a previously broadcast transaction. Supports all chain types.
Parameters:
params.chain: Chain- The blockchain the transaction was broadcast onparams.txHash: string- The transaction hash to check
Returns:
status: 'pending' | 'success' | 'error'- Current transaction statusreceipt?: TxReceiptInfo- Fee details if available (feeAmount,feeDecimals,feeTicker)
Example:
const txHash = await vault.broadcastTx({ chain, keysignPayload, signature })
// Poll for confirmation
const result = await vault.getTxStatus({ chain: Chain.Ethereum, txHash })
if (result.status === 'success') {
console.log(`Confirmed! Fee: ${result.receipt?.feeAmount} ${result.receipt?.feeTicker}`)
} else if (result.status === 'error') {
console.log('Transaction failed')
}Emits transactionConfirmed or transactionFailed events for terminal states.
Accessed via sdk.notifications:
Register a device to receive push notifications for a vault.
Parameters:
options.vaultId: string- Vault ID (publicKeys.ecdsa)options.partyName: string- Local party ID of the deviceoptions.token: string- Push token from APNs, FCM, or Web Pushoptions.deviceType: 'ios' | 'android' | 'web'- Platform type
Remove local push registration for a vault.
Send a push notification to all other registered devices for a vault.
Parameters:
options.vaultId: string- Vault IDoptions.vaultName: string- Vault display nameoptions.localPartyId: string- Sender's party ID (excluded from recipients)options.qrCodeData: string- Keysign session data for joining
Register a callback for incoming signing notifications. Returns an unsubscribe function.
Process raw push notification data from a platform handler. Parses and invokes registered callbacks.
Parse raw push data into a typed SigningNotification. Returns null if data doesn't match expected format.
Fetch the VAPID public key for Web Push subscriptions. Only needed for deviceType: 'web'.
Check if a vault is registered locally for push notifications.
Check if any devices are registered for a vault on the server.
Open a WebSocket connection for real-time notification delivery. Messages are dispatched through onSigningRequest() callbacks. Auto-reconnects with exponential backoff (1s → 30s cap). Requires prior registerDevice() call.
Parameters:
options.vaultId: string- Vault ID (publicKeys.ecdsa)options.partyName: string- Local party ID of the deviceoptions.token: string- Same token used forregisterDevice()
Close the WebSocket connection and stop auto-reconnect. Also called automatically by sdk.dispose().
Current WebSocket state: 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
Register a callback for WebSocket connection state changes. Returns an unsubscribe function.
Check if the notification server is reachable.
Check if vault backup content (string) is encrypted (synchronous).
Validate vault structure and integrity.
Get vault metadata and information.
The SDK primarily uses typed errors with machine-readable codes. Check error.code for programmatic handling, with a fallback for unexpected errors:
import { VaultError, VaultErrorCode } from '@vultisig/sdk'
try {
const result = await vault.send({
chain: 'Ethereum',
to: '0x...',
amount: '0.5',
})
} catch (error) {
if (error instanceof VaultError) {
switch (error.code) {
case VaultErrorCode.BalanceFetchFailed:
console.error('Could not fetch balance — check RPC connectivity')
break
case VaultErrorCode.GasEstimationFailed:
console.error('Gas estimation failed — network may be congested')
break
case VaultErrorCode.BroadcastFailed:
console.error('Broadcast rejected:', error.message)
break
case VaultErrorCode.InvalidAmount:
console.error('Invalid amount:', error.message)
break
default:
console.error(`[${error.code}] ${error.message}`)
}
// Access the underlying error if needed
if (error.originalError) console.error('Caused by:', error.originalError)
} else {
// Some internal paths may throw plain errors
console.error('Unexpected error:', (error as Error).message)
}
}| Code | When |
|---|---|
INVALID_CONFIG |
SDK or vault configuration is invalid |
SIGNING_FAILED |
MPC signing round failed |
NOT_IMPLEMENTED |
Feature not yet available for this chain |
ADDRESS_DERIVATION_FAILED |
Could not derive address from public keys |
WALLET_CORE_NOT_INITIALIZED |
WASM modules not loaded — call sdk.initialize() first |
UNSUPPORTED_CHAIN |
Chain not supported by this operation |
CHAIN_NOT_SUPPORTED |
Chain not enabled on this vault |
NETWORK_ERROR |
RPC or server request failed |
INVALID_VAULT |
Vault data is corrupted or incomplete |
INVALID_PUBLIC_KEY |
Public key format is invalid |
INVALID_CHAIN_CODE |
Chain code missing or malformed |
BALANCE_FETCH_FAILED |
Balance query failed (RPC timeout, rate limit) |
UNSUPPORTED_TOKEN |
Token not recognized or not supported on this chain |
GAS_ESTIMATION_FAILED |
Could not estimate gas fees |
BROADCAST_FAILED |
Transaction rejected by the network |
CREATE_FAILED |
Vault creation failed (server error, keygen failure) |
TIMEOUT |
Operation timed out |
INVALID_AMOUNT |
Amount is zero, negative, NaN, or exceeds balance |
| Code | When |
|---|---|
INVALID_FILE_FORMAT |
File is not a valid vault backup |
PASSWORD_REQUIRED |
Vault file is encrypted — provide a password |
INVALID_PASSWORD |
Wrong password for encrypted vault |
CORRUPTED_DATA |
Vault data failed integrity check |
UNSUPPORTED_FORMAT |
Vault format version not supported |
import { VaultImportError, VaultImportErrorCode } from '@vultisig/sdk'
const vaultContent = '...' // raw .vult file contents (string)
try {
const vault = await sdk.importVault(vaultContent, password)
} catch (error) {
if (error instanceof VaultImportError) {
if (error.code === VaultImportErrorCode.INVALID_PASSWORD) {
console.error('Wrong password')
}
}
}The SDK emits typed events for state changes. Use vault.on() to subscribe:
// Track transaction lifecycle
vault.on('transactionBroadcast', ({ chain, txHash }) => {
console.log(`Broadcast on ${chain}: ${txHash}`)
})
vault.on('transactionConfirmed', ({ chain, txHash, receipt }) => {
console.log(`Confirmed: ${txHash} (fee: ${receipt?.feeAmount} ${receipt?.feeTicker})`)
})
vault.on('transactionFailed', ({ chain, txHash }) => {
console.error(`Failed: ${txHash}`)
})
// Monitor signing progress
vault.on('signingProgress', ({ step }) => {
console.log(`Signing: ${step.message}`)
})
// React to balance changes
vault.on('balanceUpdated', ({ chain, balance }) => {
console.log(`${chain} balance updated`)
})| Event | Payload | When |
|---|---|---|
balanceUpdated |
{ chain, balance, tokenId? } |
Balance fetched or changed |
valuesUpdated |
{ chain: Chain | 'all' } |
Fiat values recalculated |
totalValueUpdated |
{ value } |
Portfolio total recalculated |
transactionSigned |
{ signature, payload } |
Transaction signed |
transactionBroadcast |
{ chain, txHash, keysignPayload?, raw? } |
Transaction sent to network |
transactionConfirmed |
{ chain, txHash, receipt? } |
Transaction confirmed on-chain |
transactionFailed |
{ chain, txHash } |
Transaction failed on-chain |
signingProgress |
{ step } |
Signing phase update |
chainAdded |
{ chain } |
Chain enabled on vault |
chainRemoved |
{ chain } |
Chain removed from vault |
tokenAdded |
{ chain, token } |
Token added |
tokenRemoved |
{ chain, tokenId } |
Token removed |
renamed |
{ oldName, newName } |
Vault renamed |
saved |
{ vaultId } |
Vault persisted to storage |
unlocked |
{ vaultId } |
Encrypted vault unlocked |
locked |
{} |
Vault locked |
loaded |
{ vaultId } |
Vault loaded from storage |
deleted |
{ vaultId } |
Vault deleted from storage |
postQuantumKeysAdded |
{ vaultId } |
ML-DSA post-quantum keys added (fast vault) |
error |
Error |
Vault-level error |
| Event | Payload | When |
|---|---|---|
qrCodeReady |
{ qrPayload, action, sessionId } |
QR code ready for device pairing |
deviceJoined |
{ deviceId, totalJoined, required } |
Device joined signing/keygen session |
allDevicesReady |
{ devices, sessionId } |
All required devices connected |
keygenProgress |
{ phase, round?, message? } |
Keygen phase update ('ecdsa' → 'eddsa' → 'complete') |
| Event | Payload | When |
|---|---|---|
swapQuoteReceived |
{ quote } |
Swap quote fetched |
swapApprovalRequired |
{ token, spender, amount, currentAllowance } |
ERC-20 approval needed before swap |
swapApprovalGranted |
{ token, txHash } |
Approval transaction confirmed |
swapPrepared |
{ provider, fromAmount, toAmountExpected, requiresApproval } |
Swap transaction ready for signing |
Listen on the SDK instance for global state changes:
sdk.on('vaultChanged', ({ vaultId }) => console.log(`Active vault: ${vaultId}`))
sdk.on('vaultCreationProgress', ({ step, vault }) => console.log(step.message))
sdk.on('vaultCreationComplete', ({ vault }) => console.log(`Created: ${vault.name}`))
sdk.on('disposed', () => console.log('SDK disposed'))
sdk.on('error', error => console.error('SDK error:', error))See the /examples directory for complete sample applications:
- Browser Example - Complete web application with vault creation, import, and address derivation
- Node.js Example - Server-side vault operations and blockchain interactions
- Node.js 20+
- Modern browser with WebAssembly support
- Electron 20+ (for desktop applications)
- Network access for VultiServer communication (for Fast Vault features)
- Private Keys: The SDK uses threshold signatures - private keys are never stored in a single location
- Encryption: Vault keyshares are encrypted using AES-GCM with user-provided passwords
- Server Trust: Fast Vaults use VultiServer as one party in the MPC protocol
- Secure Vault Independence: Secure Vaults only use the relay server for coordination, not signing
- Configurable Thresholds: Secure Vaults support custom M-of-N thresholds for multi-party approval
- WASM Integrity: Ensure WASM files are served from trusted sources
- Node.js 20+
- Yarn 4.x
This SDK is part of a monorepo. Always install dependencies from the root directory:
# Clone the repository
git clone https://github.com/vultisig/vultisig-sdk.git
cd vultisig-sdk
# IMPORTANT: Install from root (sets up all workspace packages)
yarn installThe SDK bundles functionality from workspace packages (packages/core/ and packages/lib/) into a single distributable package.
# Build the SDK (from root directory)
yarn workspace @vultisig/sdk buildThis creates the distributable package in packages/sdk/dist/ with all dependencies bundled.
# Run tests (from root directory)
yarn workspace @vultisig/sdk test- Make changes to SDK code in
packages/sdk/src/or workspace packages inpackages/core//packages/lib/ - Build:
yarn workspace @vultisig/sdk build - Test:
yarn workspace @vultisig/sdk test - Lint:
yarn lint(from root)
packages/sdk/
├── src/ # SDK source code
│ ├── chains/ # Address derivation and chain management
│ ├── mpc/ # Multi-party computation logic
│ ├── vault/ # Vault creation and management
│ ├── server/ # Fast vault server integration
│ └── wasm/ # WASM module management
├── tests/ # Test suite
└── package.json # SDK package configuration
# Workspace packages (bundled into SDK)
packages/core/ # Core blockchain functionality
packages/lib/ # Shared libraries and utilities
- Fork the repository
- Install dependencies from root:
yarn install - Make your changes in
packages/sdk/src/or workspace packages - Run tests:
yarn workspace @vultisig/sdk test - Build:
yarn workspace @vultisig/sdk build - Submit a pull request
MIT License - see LICENSE file for details.
Built with ❤️ by the Vultisig team