Skip to content

Navio SDK v0.3.0


Navio SDK

TypeScript SDK for interacting with the Navio blockchain. Provides wallet management, transaction key synchronization, BLSCT confidential transaction support, and blockchain interaction through Electrum servers or P2P connections.

Features

  • Wallet Management - HD key derivation with BLS CT support
  • Wallet Encryption - Password-based encryption with Argon2id + AES-256-GCM
  • Dual Sync Backends - Electrum protocol or direct P2P node connections
  • Automatic Output Detection - BLSCT output scanning with view tag optimization
  • Amount Recovery - Decrypt confidential transaction amounts
  • Balance Tracking - Query wallet balance and UTXOs
  • Token/NFT Transfer, Creation & Minting - Create collections, mint assets, send tokenized outputs, and inspect owned assets
  • RFQ Atomic-Swap Trading - Trade tokens peer-to-peer as taker or maker over Navio's encrypted p2p messaging bus; swap halves are built and signed locally
  • Recoverable Output Blinding Keys - Prove after the fact that this wallet created a given confidential output, and sign arbitrary messages with its blinding key, even after a seed-only restore
  • Spending Status Tracking - Monitor spent/unspent outputs
  • Blockchain Reorganization Handling - Automatic reorg detection and recovery
  • Cross-Platform Persistence - Efficient SQLite storage (sql.js with IndexedDB for browser, better-sqlite3 for Node.js)
  • Hierarchical Deterministic Sub-addresses - Multiple account support

Installation

npm install navio-sdk

Quick Start

import { NavioClient } from 'navio-sdk';

// Create and initialize client
const client = new NavioClient({
  walletDbPath: './my-wallet.db',
  backend: 'electrum', // or 'p2p'
  electrum: {
    host: 'localhost',
    port: 50005,
    ssl: false,
  },
  network: 'mainnet', // 'mainnet' | 'testnet' | 'signet' | 'regtest'
  createWalletIfNotExists: true,
});

// Initialize (loads wallet, connects to backend)
await client.initialize();

// Sync transaction keys
await client.sync({
  onProgress: (height, tip, blocks, txKeys, isReorg) => {
    console.log(
      `Syncing: ${height}/${tip} (${blocks} blocks, ${txKeys} TX keys${isReorg ? ' [REORG]' : ''})`
    );
  },
});

// Check wallet balance
const balance = await client.getBalanceNav();
console.log(`Balance: ${balance} NAV`);

// Get unspent outputs
const utxos = await client.getUnspentOutputs();
console.log(`UTXOs: ${utxos.length}`);

// Access wallet operations
const keyManager = client.getKeyManager();
const subAddress = keyManager.getSubAddress({ account: 0, address: 0 });
console.log('Sub-address:', subAddress.toString());

// Cleanup
await client.disconnect();

Configuration

interface NavioClientConfig {
  // Required
  walletDbPath: string; // Path to wallet database file

  // Database adapter (auto-detected if not specified)
  databaseAdapter?: 'indexeddb' | 'browser' | 'better-sqlite3';

  // Backend selection (default: 'electrum')
  backend?: 'electrum' | 'p2p';

  // Electrum backend options
  electrum?: {
    host?: string; // Server host (default: 'localhost')
    port?: number; // Server port (default: 50001)
    ssl?: boolean; // Use SSL/TLS (default: false)
    timeout?: number; // Request timeout ms (default: 30000)
    p2pmsgTimeout?: number; // Timeout for RFQ/quote/order broadcasts (daemon grinds PoW; default: 180000)
    clientName?: string; // Client name (default: 'navio-sdk')
    clientVersion?: string; // Protocol version (default: '1.4')
  };

  // P2P backend options
  p2p?: {
    host: string; // Node host
    port?: number; // Node P2P port (default by network: mainnet 48470, testnet 33670, regtest 18444)
    network?: 'mainnet' | 'testnet' | 'regtest'; // Defaults to the client's `network`
    timeout?: number; // Request timeout ms (default: 30000)
    debug?: boolean; // Enable debug logging
    maxBlocksPerRequest?: number; // Blocks per scan batch (default: 16)
    maxConcurrentBlockRequests?: number; // Parallel block downloads (default: 8)
  };

  // Network configuration (for navio-blsct)
  network?: 'mainnet' | 'testnet' | 'signet' | 'regtest';

  // Wallet options
  createWalletIfNotExists?: boolean; // Create wallet if missing (default: false)
  restoreFromSeed?: string; // Restore from seed (hex string)
  restoreFromMnemonic?: string; // Restore from BIP39 mnemonic (12-24 words)
  restoreFromAuditKey?: string; // Restore watch-only wallet from audit key (160 hex chars)
  restoreFromHeight?: number; // Block height when wallet was created (for restore)
  creationHeight?: number; // Creation height for new wallets (default: chainTip - 100)

  // Output blinding keys
  deterministicBlindingKeys?: boolean; // Derive recoverable blinding keys from the seed (default: true)
}

API Reference

Main client class for wallet operations and blockchain synchronization.

Constructor

const client = new NavioClient(config: NavioClientConfig);

Initialization

initialize(): Promise<void>

Initializes the client:

  • Loads or creates wallet from database
  • Connects to backend (Electrum/P2P)
  • Initializes sync manager
  • Sets up KeyManager for output detection

Synchronization

sync(options?: SyncOptions): Promise<number>

Synchronizes transaction keys from the blockchain.

interface SyncOptions {
  startHeight?: number; // Start height (default: last synced + 1)
  endHeight?: number; // End height (default: chain tip)
  onProgress?: (
    currentHeight: number,
    chainTip: number,
    blocksProcessed: number,
    txKeysProcessed: number,
    isReorg: boolean
  ) => void;
  stopOnReorg?: boolean; // Throw ReorgError instead of recovering (default: false)
  verifyHashes?: boolean; // Verify block hashes (default: true)
  saveInterval?: number; // Save every N blocks (default: 100)
  keepTxKeys?: boolean; // Keep TX keys in DB (default: false)
  blockHashRetention?: number; // Keep last N hashes (default: 10000)
}

Returns: Number of transaction keys synced

On a chain reorganization, sync() reverts the orphaned blocks (their outputs, spends and block hashes) and re-syncs from the fork point, calling onProgress once with isReorg = true. With stopOnReorg: true it instead throws a ReorgError whose info gives the fork height and hashes, and reverts nothing. The fork search goes back at most MAX_REORG_DEPTH blocks (and never past blockHashRetention); a deeper reorg, or one that reaches a height whose hash is no longer stored, throws DeepReorgError in either mode: call resetSyncState() on the sync manager (client.getSyncManager()) and sync again.

isSyncNeeded(): Promise<boolean>

Checks if synchronization is needed: the chain tip is past the last synced block, or the block at the tip height has been replaced by a reorg.

getLastSyncedHeight(): number

Returns the last synced block height, or -1 if never synced.

getSyncState(): SyncState

Returns current sync state with statistics.

interface SyncState {
  lastSyncedHeight: number; // Last synced block height
  lastSyncedHash: string; // Last synced block hash
  totalTxKeysSynced: number; // Total transaction keys synced
  lastSyncTime: number; // Last sync timestamp (ms)
  chainTipAtLastSync: number; // Chain tip at last sync
}

Background Sync

startBackgroundSync(options?: BackgroundSyncOptions): Promise<void>

Start continuous background synchronization. The client will poll for new blocks and automatically sync. Callbacks are invoked for new blocks, transactions, and balance changes.

interface BackgroundSyncOptions extends SyncOptions {
  pollInterval?: number; // Polling interval in ms (default: 10000)
  onNewBlock?: (height: number, hash: string) => void;
  onNewTransaction?: (txHash: string, outputHash: string, amount: bigint) => void;
  onBalanceChange?: (newBalance: bigint, oldBalance: bigint) => void;
  onError?: (error: Error) => void;
}
stopBackgroundSync(): void

Stop background synchronization.

isBackgroundSyncActive(): boolean

Check if background sync is running.

Balance & Outputs

getBalance(tokenId?: string | null): Promise<bigint>

Get wallet balance in satoshis.

// NAV balance
const balanceSats = await client.getBalance();

// Token balance
const tokenBalance = await client.getBalance('token-id-hex');
getBalanceNav(tokenId?: string | null): Promise<number>

Get wallet balance in NAV (with 8 decimal places).

const balance = await client.getBalanceNav();
console.log(`Balance: ${balance.toFixed(8)} NAV`);
getUnspentOutputs(tokenId?: string | null): Promise<WalletOutput[]>

Get unspent outputs (UTXOs).

interface WalletOutput {
  outputHash: string;
  txHash: string;
  outputIndex: number;
  blockHeight: number;
  amount: bigint;
  memo: string | null;
  tokenId: string | null; // 64-hex token hash for fungible tokens, 80-hex token ID for NFTs, or null for NAV
  blindingKey: string; // blsctData.blindingKey: k * sk_destination (recipient-bound)
  ephemeralKey: string | null; // blsctData.ephemeralKey: k * G — what signOutput's signatures verify against
  spendingKey: string;
  isSpent: boolean;
  spentTxHash: string | null;
  spentBlockHeight: number | null;
}
getAllOutputs(): Promise<WalletOutput[]>

Get all wallet outputs (spent and unspent).

getTokenBalance(tokenId: string): Promise<bigint>

Get the current balance for a specific token or NFT.

getTokenOutputs(tokenId: string): Promise<WalletOutput[]>

Get unspent outputs for a specific token or NFT.

getAssetBalances(): Promise<WalletAssetBalance[]>

Get current balances for all non-NAV assets owned by the wallet.

interface WalletAssetBalance {
  tokenId: string;
  kind: 'token' | 'nft';
  balance: bigint;
  outputCount: number;
  collectionTokenId: string | null;
  nftId: bigint | null;
}
getTokenBalances(): Promise<WalletAssetBalance[]>

Get current balances for all fungible tokens owned by the wallet.

getNftBalances(): Promise<WalletAssetBalance[]>

Get current balances for all NFTs owned by the wallet.

Accessors

getKeyManager(): KeyManager

Returns the KeyManager instance for wallet operations.

getWalletDB(): WalletDB

Returns the WalletDB instance for database operations.

getSyncProvider(): SyncProvider

Returns the current sync provider (Electrum or P2P).

getSyncManager(): TransactionKeysSync

Returns the TransactionKeysSync instance for sync operations.

getBackendType(): 'electrum' | 'p2p'

Returns the current backend type.

getNetwork(): string

Returns the configured network.

isConnected(): boolean

Check if client is connected to the backend.

Transaction Creation

sendTransaction(options: SendTransactionOptions): Promise<SendTransactionResult>

Build and broadcast a confidential transaction. Selects UTXOs, constructs a BLSCT confidential transaction with change, and broadcasts via the connected backend.

interface SendTransactionOptions {
  address: string; // Destination address (bech32m encoded)
  amount: bigint; // Amount to send in satoshis
  memo?: string; // Optional memo
  subtractFeeFromAmount?: boolean; // Subtract fee from amount (default: false)
  tokenId?: string | null; // Token ID (null for NAV)
}

interface SendTransactionResult {
  txId: string; // Transaction ID (hex)
  rawTx: string; // Serialized transaction (hex)
  fee: bigint; // Fee paid in satoshis
  inputCount: number; // Inputs used
  outputCount: number; // Outputs created (including change)
}
const result = await client.sendTransaction({
  address: 'tnav1...',
  amount: 100_000_000n, // 1 NAV
  memo: 'Payment',
});
console.log('Sent tx:', result.txId);
console.log('Fee:', Number(result.fee) / 1e8, 'NAV');
sendToMany(options: SendToManyOptions): Promise<SendTransactionResult>

Build and broadcast a single confidential NAV transaction with multiple destinations. All recipients share the same set of (auto- or manually-selected) NAV inputs and a single optional change output.

interface SendRecipient {
  address: string; // Destination address (bech32m encoded)
  amount: bigint; // Amount in satoshis (must be > 0)
  memo?: string; // Optional memo for this output
  subtractFeeFromAmount?: boolean; // If true, this recipient pays a share of the fee
}

interface SendToManyOptions {
  recipients: SendRecipient[]; // One or more recipients (must be non-empty)
  selectedUtxos?: string[]; // Optional manual UTXO selection (NAV outputHashes)
}

When subtractFeeFromAmount is set on more than one recipient, the fee is split evenly between them (any rounding remainder goes to the first such recipient). Token and NFT sends are not supported by this helper – use sendToken / sendNft for those.

const result = await client.sendToMany({
  recipients: [
    { address: 'tnav1...', amount: 100_000_000n, memo: 'invoice 1' },
    { address: 'tnav1...', amount: 50_000_000n },
    { address: 'tnav1...', amount: 25_000_000n, subtractFeeFromAmount: true },
  ],
});

console.log('Sent tx:', result.txId);
console.log('Outputs:', result.outputCount); // recipients + change
broadcastRawTransaction(rawTx: string): Promise<string>

Broadcast a pre-built raw transaction hex via the connected backend. Returns the transaction hash.

aggregateTransactions(txHexes: string[]): AggregateTransactionsResult

Aggregate one or more signed transaction hex strings into a single signed transaction using navio-blsct's CTx.aggregateTransactions() helper. This does not broadcast automatically.

const aggregated = client.aggregateTransactions([signedTxHexA, signedTxHexB]);

console.log(aggregated.txId);
await client.broadcastRawTransaction(aggregated.rawTx);
sendToken(options: SendTokenOptions): Promise<SendTransactionResult>

Send a fungible token. tokenId accepts either:

  • a 64-hex navio-core/RPC collection token hash, or
  • an 80-hex token ID in the same byte order (token hash || subid), which is normalized to the 64-hex token hash for fungible sends.
const result = await client.sendToken({
  address: 'tnav1...',
  amount: 25n,
  tokenId: 'fungible-token-id-hex',
  memo: 'Token payment',
});
sendNft(options: SendNftOptions): Promise<SendTransactionResult>

Send a single NFT. You can pass either a full 80-hex NFT tokenId in navio-core/RPC byte order, or a collectionTokenId plus nftId.

const result = await client.sendNft({
  address: 'tnav1...',
  collectionTokenId: 'collection-token-id-hex',
  nftId: 1n,
  memo: 'NFT transfer',
});

Token and NFT receiving is already handled by normal wallet sync and exposed through the asset balance/output queries above.

createTokenCollection(options: CreateTokenCollectionOptions): Promise<CreateCollectionResult>

Create a fungible token collection definition on-chain. The wallet funds the transaction fee with NAV UTXOs and returns the derived collectionTokenId you will use for later minting.

const collection = await client.createTokenCollection({
  metadata: { name: 'Token Collection', symbol: 'TOK' },
  totalSupply: 5_000_000,
});

console.log(collection.collectionTokenId);

Pass initialMint to mint the first supply in the same transaction as the collection creation (consensus executes output predicates in order, so the collection registers before the mint validates — no need to wait for the collection to confirm before minting):

const collection = await client.createTokenCollection({
  metadata: { name: 'Token Collection', symbol: 'TOK' },
  totalSupply: 5_000_000,
  initialMint: { address: 'tnav1...', amount: 1_000_000 },
});

console.log(collection.mintedAmount); // 1000000n
createNftCollection(options: CreateNftCollectionOptions): Promise<CreateCollectionResult>

Create an NFT collection definition on-chain. You can optionally encode a collection max supply; omitting it keeps the previous 0 behavior.

const collection = await client.createNftCollection({
  metadata: { collection: 'Artifacts', creator: 'navio' },
  totalSupply: 250,
});
mintToken(options: MintTokenOptions): Promise<MintAssetResult>

Mint a fungible token output from an existing collection.

const mint = await client.mintToken({
  address: 'tnav1...',
  collectionTokenId: collection.collectionTokenId,
  amount: 123_456,
});

collectionTokenId accepts either the creation id returned by createTokenCollection/createNftCollection (the hash of metadata + supply that the mint key derives from) or the public on-chain token id that explorers and gettoken report. Public ids are resolved automatically when the connected Electrum server bridges blockchain.token.get_token; on servers without the bridge only the creation id works. Minting requires the wallet that created the collection — the mint predicate key derives from the creator's seed.

mintNft(options: MintNftOptions): Promise<MintAssetResult>

Mint a single NFT from an existing collection. The returned tokenId is the full 80-hex NFT token ID in navio-core/RPC byte order.

const mint = await client.mintNft({
  address: 'tnav1...',
  collectionTokenId: collection.collectionTokenId,
  nftId: 42n,
  metadata: { name: 'Artifact', rarity: 'legendary' },
});
listCreatedCollections(options?): Promise<CreatedCollectionInfo[]>

List the token/NFT collections this wallet created. Combines local records (written when the wallet broadcasts a creation) with chain discovery for restored wallets: every distinct token the wallet holds is looked up in the server's token registry, and the wallet's own transactions are scanned for create-token predicates — so even collections that were created but never minted or held are recovered after a sync. A collection is reported when its on-chain token public key re-derives from this wallet's seed. The returned collectionTokenId is the creation id, directly usable with mintToken/mintNft.

const collections = await client.listCreatedCollections();
// [{ kind: 'token', collectionTokenId: '…', publicTokenId: '…',
//    metadata: { name: 'TOK' }, totalSupply: 5000000n, source: 'local', … }]

Every entry carries both ids: collectionTokenId is the creation id (works with mintToken/mintNft on any backend), publicTokenId is the public on-chain id that getAssetBalances/getTokenBalances and the explorer report — join collection and balance rows on it. Balance entries themselves also carry the collection metadata and totalSupply when resolvable (local creation records, or the server token registry, cached).

Chain discovery needs the electrum backend and a server bridging blockchain.token.get_token; pass { discoverFromChain: false } to skip it.

mintNfts(options: MintNftsOptions): Promise<MintNftsResult>

Mint several NFTs from an existing collection in a single transaction — one broadcast, one fee, one block. Each NFT takes its own nftId, metadata, and optionally its own destination address.

const batch = await client.mintNfts({
  address: 'tnav1...',
  collectionTokenId: collection.collectionTokenId,
  nfts: [
    { nftId: 1n, metadata: { name: 'One' } },
    { nftId: 2n, metadata: { name: 'Two' }, address: 'tnav1other...' },
  ],
});
console.log(batch.tokenIds); // full 80-hex NFT token ids, input order

Output Blinding Keys

Prove that this wallet created a given confidential output. See Prove You Sent an Output for the why and the caveats; recovery only works for outputs created by navio-sdk 0.2.0 or later.

recoverBlindingKey(ref: OutputRef): Promise<RecoveredBlindingKey>

Recover the private blinding scalar of an output this wallet created.

Reads the scalar this wallet stored when it built the output, and otherwise recomputes it from the seed — which is what survives a restore from the mnemonic alone. Either way the candidate is checked against the output's on-chain point before being returned, so a success is proof rather than an assumption.

Neither the output's on-chain index nor any input position is trusted. The derivation is anchored on the lexicographically smallest outid among the inputs the sender contributed, because navio-core shuffles vin before broadcast and block aggregation splices in other senders' inputs. Recovery tries that canonical anchor first and then falls back to every input, each against the first 16 ordinals, verifying every candidate against the chain.

const { blindingKey, publicKey, source, counter } = await client.recoverBlindingKey({
  txid,
  vout: 0,
});
Field Meaning
blindingKey The private scalar, 64 hex chars. Treat as a secret.
publicKey k * G, 96 hex chars — the output's blsctData.ephemeralKey.
source 'stored' (from this wallet's database) or 'derived' (from the seed).
counter The sender-assigned counter that matched, when derived.
anchorOutid The input outid the derivation was anchored on, when derived.

Throws when the wallet is locked, when the transaction cannot be fetched, or when the output was not created by this wallet.

signOutput(options: SignOutputOptions): Promise<SignOutputResult>

Sign an arbitrary message with an output's blinding key.

The message is signed exactly as given — no length prefix, no hashing beyond what the BLS scheme does — so the signature verifies under Signature.verify(publicKey, message) in navio-blsct.

const { signature, blindingKey } = await client.signOutput({
  txid,
  vout: 0,
  message: `navio-hl-refund/v1|${txid}|0|wrong address`,
});

blindingKey is the public key the signature verifies against.

Chain & Metadata

getChainTip(): Promise<{ height: number; hash: string }>

Get the current chain tip from the backend.

getCreationHeight(): Promise<number>

Get the wallet creation height (block height to start scanning from).

setCreationHeight(height: number): Promise<void>

Set the wallet creation height.

getWalletMetadata(): Promise<WalletMetadata | null>

Get wallet metadata including creation height, creation time, and version.

getConfig(): NavioClientConfig

Get the current client configuration.

isUsingSubscriptions(): boolean

Check if background sync is using real-time block header subscriptions (Electrum) vs polling (P2P).

Connection

disconnect(): Promise<void>

Disconnect from backend and close database.


KeyManager

Manages BLS CT keys, sub-addresses, and output detection.

Key Generation

// Generate new seed
const seed = keyManager.generateNewSeed();

// Set HD seed
keyManager.setHDSeed(seed);

// Get master seed (for backup)
const masterSeed = keyManager.getMasterSeedKey();
console.log('Seed:', masterSeed.serialize()); // hex string

Mnemonic Support (BIP39)

// Generate a new 24-word mnemonic and set as seed
const mnemonic = keyManager.generateNewMnemonic();
console.log('Mnemonic:', mnemonic);
// "abandon ability able about above absent absorb abstract absurd abuse access accident..."

// Or generate mnemonic with different word counts
const mnemonic12 = KeyManager.generateMnemonic(128); // 12 words
const mnemonic24 = KeyManager.generateMnemonic(256); // 24 words (default)

// Validate a mnemonic
const isValid = KeyManager.validateMnemonic(mnemonic);
console.log('Valid:', isValid); // true

// Restore from mnemonic
keyManager.setHDSeedFromMnemonic(mnemonic);

// Get mnemonic from current seed (for backup)
const backupMnemonic = keyManager.getMnemonic();
console.log('Backup mnemonic:', backupMnemonic);

// Export the BLSCT audit key used by navio-core's getblsctauditkey RPC.
// Format: 32-byte private view key || 48-byte public spending key
const auditKeyHex = keyManager.getAuditKeyHex();
console.log('Audit key:', auditKeyHex);

// Static conversion methods
const scalar = KeyManager.mnemonicToScalar(mnemonic); // Convert to Scalar
const recovered = KeyManager.seedToMnemonic(scalar); // Convert back to mnemonic

Sub-addresses

// Get sub-address by identifier
const subAddr = keyManager.getSubAddress({ account: 0, address: 0 });

// Get bech32m encoded address string (recommended)
const address = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
console.log('Address:', address); // tnav1... (Node.js) or hex fallback (browser)

// Get mainnet address
const mainnetAddress = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'mainnet');
console.log('Address:', mainnetAddress); // nav1...

// Generate new sub-address
const { subAddress, id } = keyManager.generateNewSubAddress(0); // account 0

// Create sub-address pool
keyManager.newSubAddressPool(0); // Main account
keyManager.newSubAddressPool(-1); // Change
keyManager.newSubAddressPool(-2); // Staking

Note: In browser environments using navio-blsct WASM, bech32m encoding may fall back to hex representation due to a known issue. This will be addressed in a future navio-blsct release.

Output Detection

// Check if output belongs to wallet
const isMine = keyManager.isMineByKeys(blindingKey, spendingKey, viewTag);

// Calculate view tag for output detection
const viewTag = keyManager.calculateViewTag(blindingKey);

// Calculate hash ID for sub-address lookup
const hashId = keyManager.calculateHashId(blindingKey, spendingKey);

// Calculate nonce for amount recovery
const nonce = keyManager.calculateNonce(blindingKey);

Wallet Encryption

The KeyManager supports password-based encryption using Argon2id for key derivation and AES-256-GCM for encryption.

// Check encryption status
const isEncrypted = keyManager.isEncrypted();
const isUnlocked = keyManager.isUnlocked();

// Set a password (encrypts the wallet)
await keyManager.setPassword('my-secure-password');

// Lock the wallet (clears cached encryption key)
keyManager.lock();

// Unlock the wallet
const success = await keyManager.unlock('my-secure-password');
if (!success) {
  console.log('Wrong password');
}

// Change password
const changed = await keyManager.changePassword('old-password', 'new-password');

// Get encryption parameters (for database storage)
const params = keyManager.getEncryptionParams();
// { salt: 'hex...', verificationHash: 'hex...' }

// Restore encryption state from database
keyManager.setEncryptionParams(params.salt, params.verificationHash);

WalletDB

Manages wallet database persistence.

Wallet Operations

// Create new wallet
const keyManager = await walletDB.createWallet(creationHeight);

// Load existing wallet
const keyManager = await walletDB.loadWallet();

// Restore wallet from seed
const keyManager = await walletDB.restoreWallet(seedHex, creationHeight);

// Save wallet state
await walletDB.saveWallet(keyManager);

// Close database
await walletDB.close();

Metadata

// Get wallet metadata
const metadata = await walletDB.getWalletMetadata();
// { creationHeight, creationTime, restoredFromSeed, version }

// Get/set creation height
const height = await walletDB.getCreationHeight();
await walletDB.setCreationHeight(height);

Balance Queries

// Get balance
const balance = await walletDB.getBalance();

// Get unspent outputs
const utxos = await walletDB.getUnspentOutputs();

// Get all outputs
const outputs = await walletDB.getAllOutputs();

Database Encryption

The WalletDB supports full database encryption for secure backup and export.

// Export database as encrypted blob
const encryptedData = await walletDB.exportEncrypted('backup-password');
// encryptedData is a Uint8Array that can be saved to file

// Load database from encrypted blob
const restoredDb = await WalletDB.loadEncrypted(encryptedData, 'backup-password');

// Check if a buffer is an encrypted database
import { isEncryptedDatabase } from 'navio-sdk';
const isEncrypted = isEncryptedDatabase(someBuffer);

// Export unencrypted database (for backup without password)
const rawData = walletDB.export();

// Load from raw bytes
const db = await WalletDB.loadFromBytes(rawData);

Cross-Platform Database Storage

The SDK provides efficient, cross-platform SQLite database storage with automatic adapter selection based on the runtime environment:

Platform Adapter Storage Performance
Browser sql.js + IndexedDB IndexedDB Good
Node.js better-sqlite3 File system Excellent
Testing In-memory Memory Fast

The browser adapter uses sql.js (SQLite compiled to WebAssembly) with IndexedDB for persistence. Changes are automatically saved to IndexedDB with debouncing for optimal performance.

import { NavioClient, WalletDB, createDatabaseAdapter } from 'navio-sdk';

// Auto-detect best adapter for environment
const client = new NavioClient({
  walletDbPath: 'wallet.db', // Adapter auto-selected based on environment
  // ... other config
});

// Available adapter types:
// - 'browser': sql.js with IndexedDB persistence
// - 'node': better-sqlite3 native bindings
// - 'memory': In-memory (testing)

Using WalletDB Directly:

// Create with auto-detected adapter
const db = new WalletDB();
await db.open('wallet.db');

// Force specific adapter
const db = new WalletDB({ type: 'better-sqlite3' });
await db.open('./wallet.db');

// In-memory for testing
const db = new WalletDB({ type: 'memory' });
await db.open(':memory:');

// All operations are async
await db.createWallet();
const km = await db.loadWallet();
await db.close();

Migrating Between Adapters:

// Start with in-memory database
const memDb = new WalletDB({ type: 'memory' });
await memDb.open(':memory:');
await memDb.createWallet();

// Migrate to persistent storage
const persistentDb = await memDb.migrate('wallet.db');

Browser Storage:

In browser environments, the SDK uses sql.js (SQLite compiled to WebAssembly) with IndexedDB for persistence. The database is automatically saved to IndexedDB when changes are made, ensuring data survives page reloads and browser restarts.

Encryption Metadata

// Check if encryption is enabled
const isEncrypted = walletDB.isEncrypted();

// Save encryption metadata
walletDB.saveEncryptionMetadata(salt, verificationHash);

// Get encryption metadata
const metadata = walletDB.getEncryptionMetadata();
// { salt: 'hex...', verificationHash: 'hex...' } or null

// Get/save encrypted keys
walletDB.saveEncryptedKey(keyId, publicKey, encryptedSecret);
const key = walletDB.getEncryptedKey(keyId);

// Delete plaintext keys after encryption
walletDB.deletePlaintextKeys();

SyncProvider Interface

Abstract interface for sync backends. Allows switching between Electrum and P2P.

interface SyncProvider {
  type: 'electrum' | 'p2p' | 'custom';

  connect(): Promise<void>;
  disconnect(): void;
  isConnected(): boolean;

  getChainTipHeight(): Promise<number>;
  getChainTip(): Promise<{ height: number; hash: string }>;
  getBlockHeader(height: number): Promise<string>;
  getBlockHeaders(startHeight: number, count: number): Promise<HeadersResult>;
  getBlockTransactionKeysRange(startHeight: number): Promise<TransactionKeysRangeResult>;
  getBlockTransactionKeys(height: number): Promise<TransactionKeys[]>;
  getTransactionOutput(outputHash: string): Promise<string>;
  broadcastTransaction(rawTx: string): Promise<string>;
  getRawTransaction(txHash: string, verbose?: boolean): Promise<string | any>;

  // Optional subscription methods (Electrum only)
  subscribeBlockHeaders?(callback: BlockHeaderCallback): Promise<BlockHeaderNotification>;
  unsubscribeBlockHeaders?(callback: BlockHeaderCallback): boolean;
  unsubscribeAllBlockHeaders?(): void;
  hasBlockHeaderSubscriptions?(): boolean;
}

ElectrumSyncProvider

Electrum protocol implementation of SyncProvider.

import { ElectrumSyncProvider } from 'navio-sdk';

const provider = new ElectrumSyncProvider({
  host: 'localhost',
  port: 50005,
  ssl: false,
});

await provider.connect();
const height = await provider.getChainTipHeight();

P2PSyncProvider

Direct P2P node connection implementation of SyncProvider. Talks the Navio wire protocol to a full node (naviod), so no Electrum server is needed:

  • Headers via getheaders (synced to the tip on connect; every getChainTipHeight() re-checks the tip, and block inv announcements trigger an immediate refresh; reorgs truncate to the fork point).
  • Block scanning via getdata (MSG_WITNESS_BLOCK), parsed locally: BLSCT output keys (spending/blinding/ephemeral key, view tag, output hash) and spent output hashes per transaction, plus the block timestamp / PoS flag.
  • Broadcast by pushing an unsolicited tx message, then confirming the node holds the transaction in its mempool (a rejected transaction throws).
  • Outputs and transactions via getdata — the node serves its mempool and most recent block; outputs from scanned blocks are served from a local cache, and getRawTransaction re-reads older confirmed transactions from the block they were scanned in.
  • Block header subscriptions (subscribeBlockHeaders) driven by the node's inv announcements, so startBackgroundSync reacts to new blocks without waiting for the poll interval.

The node needs no special configuration beyond -listen=1; the SDK advertises no services and relays nothing.

import { P2PSyncProvider } from 'navio-sdk';

const provider = new P2PSyncProvider({
  host: 'localhost',
  network: 'testnet', // port defaults: mainnet 48470, testnet 33670, regtest 18444
  debug: true,
});

await provider.connect();
const height = await provider.getChainTipHeight();
const { blocks, nextHeight } = await provider.getBlockTransactionKeysRange(height - 10);
const txid = await provider.broadcastTransaction(rawTxHex);

Note: navio-core's dedicated getoutputdata message cannot be used on the wire (its name is 13 characters, one more than the 12-byte command field, so nodes drop it as unknown); the SDK uses the node's output-hash lookup for getdata(MSG_WITNESS_TX, outputHash) instead.


Examples

Create New Wallet

import { NavioClient } from 'navio-sdk';

const client = new NavioClient({
  walletDbPath: './new-wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  createWalletIfNotExists: true,
  network: 'testnet',
});

await client.initialize();

// Get and save the seed for backup
const keyManager = client.getKeyManager();
const seed = keyManager.getMasterSeedKey();
console.log('SAVE THIS SEED:', seed.serialize());

// Get receiving address (bech32m encoded)
const address = keyManager.getSubAddressBech32m({ account: 0, address: 0 }, 'testnet');
console.log('Address:', address); // tnav1...

Restore Wallet from Seed

const client = new NavioClient({
  walletDbPath: './restored-wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  restoreFromSeed: 'your-seed-hex-string',
  restoreFromHeight: 50000, // Block height when wallet was created
  network: 'testnet',
});

await client.initialize();

// Full sync from restoration height
await client.sync({
  onProgress: (height, tip) => {
    console.log(`Syncing: ${height}/${tip}`);
  },
});

Restore Wallet from Mnemonic

const client = new NavioClient({
  walletDbPath: './restored-wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  restoreFromMnemonic:
    'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art',
  restoreFromHeight: 50000, // Block height when wallet was created
  network: 'testnet',
});

await client.initialize();

// Full sync from restoration height
await client.sync({
  onProgress: (height, tip) => {
    console.log(`Syncing: ${height}/${tip}`);
  },
});

Restore Watch-Only Wallet from Audit Key

const client = new NavioClient({
  walletDbPath: './watch-only-wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  restoreFromAuditKey: 'your-160-hex-audit-key',
  restoreFromHeight: 50000, // Block height when the wallet first became active
  network: 'testnet',
});

await client.initialize();
await client.sync();

Using P2P Backend

Connect straight to a naviod P2P port; the magic bytes and default port follow the client's network (p2p.network / p2p.port override them).

const client = new NavioClient({
  walletDbPath: './p2p-wallet.db',
  backend: 'p2p',
  network: 'testnet',
  p2p: {
    host: 'localhost', // port defaults to 33670 on testnet
    debug: false,
  },
  createWalletIfNotExists: true,
});

await client.initialize();
await client.sync();

const balance = await client.getBalanceNav();
console.log(`Balance: ${balance} NAV`);

// Spending works the same as with Electrum: the transaction is pushed to the
// node over P2P and confirmed against its mempool.
const { txId } = await client.sendTransaction({ address: 'tnv1...', amount: 100_000_000n });

For local development, run a naviod -chain=blsctregtest node and use network: 'regtest'; npm run test:p2p:regtest spawns such a node and exercises the whole flow (sync, receive, spend, polling).

Sync with Progress

const startTime = Date.now();

await client.sync({
  onProgress: (currentHeight, chainTip, blocksProcessed, txKeysProcessed, isReorg) => {
    const progress = ((currentHeight / chainTip) * 100).toFixed(1);
    const elapsed = (Date.now() - startTime) / 1000;
    const rate = blocksProcessed / elapsed;

    console.log(
      `Progress: ${progress}% | ${blocksProcessed} blocks | ` +
        `${txKeysProcessed} TX keys | ${rate.toFixed(1)} blocks/s`
    );
  },
  saveInterval: 100, // Save every 100 blocks
});

Background Sync (Stay Synced)

Keep the wallet synchronized automatically:

import { NavioClient } from 'navio-sdk';

const client = new NavioClient({
  walletDbPath: './wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  createWalletIfNotExists: true,
});

await client.initialize();

// Start background sync - polls every 10 seconds
await client.startBackgroundSync({
  pollInterval: 10000,

  onNewBlock: (height, hash) => {
    console.log(`New block ${height}: ${hash.substring(0, 16)}...`);
  },

  onBalanceChange: (newBalance, oldBalance) => {
    const diff = Number(newBalance - oldBalance) / 1e8;
    console.log(`Balance changed: ${diff > 0 ? '+' : ''}${diff.toFixed(8)} NAV`);
    console.log(`New balance: ${Number(newBalance) / 1e8} NAV`);
  },

  onError: error => {
    console.error('Sync error:', error.message);
  },
});

console.log('Wallet running. Press Ctrl+C to stop.');

// Handle graceful shutdown
process.on('SIGINT', async () => {
  console.log('\nShutting down...');
  client.stopBackgroundSync();
  await client.disconnect();
  process.exit(0);
});

Send a Transaction

import { NavioClient } from 'navio-sdk';

const client = new NavioClient({
  walletDbPath: './wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  network: 'testnet',
});

await client.initialize();

// Send 1 NAV
const result = await client.sendTransaction({
  address: 'tnav1...',
  amount: 100_000_000n, // 1 NAV in satoshis
  memo: 'Coffee payment',
});

console.log('Transaction ID:', result.txId);
console.log('Fee:', Number(result.fee) / 1e8, 'NAV');
console.log('Inputs:', result.inputCount, 'Outputs:', result.outputCount);

// Send with fee subtracted from amount
const result2 = await client.sendTransaction({
  address: 'tnav1...',
  amount: 50_000_000n,
  subtractFeeFromAmount: true,
});

// Send to multiple destinations in a single transaction
const result3 = await client.sendToMany({
  recipients: [
    { address: 'tnav1...', amount: 100_000_000n, memo: 'rent' },
    { address: 'tnav1...', amount: 50_000_000n },
    { address: 'tnav1...', amount: 25_000_000n, subtractFeeFromAmount: true },
  ],
});

console.log('Multi-send tx:', result3.txId);

Prove You Sent an Output

A confidential output carries nothing that identifies its sender. The blinding scalar is the exception: only the sender ever knew it. This SDK derives that scalar from the wallet seed instead of discarding it, so the sender can recompute it later and sign with it — the proof behind a refund claim for a payment that went to the wrong place.

import { PublicKey, Signature } from '@nav-io/navio-blsct';

// Works from a wallet restored from its mnemonic alone: the scalar is
// re-derived from the seed, not read back from a database.
const message = `navio-hl-refund/v1|${txid}|${vout}|paid the wrong address`;
const { signature, blindingKey } = await client.signOutput({ txid, vout, message });

// Anyone can now check the claim against the output's on-chain key.
const ok = Signature.deserialize(signature).verify(PublicKey.deserialize(blindingKey), message);

// Or take the scalar itself.
const { blindingKey: scalar, source } = await client.recoverBlindingKey({ txid, vout });
console.log(source); // 'stored' (this wallet's database) or 'derived' (from the seed)

Three things worth knowing:

  • Only outputs created by navio-sdk 0.2.0 or later can be recovered. Earlier ones used a random scalar that was thrown away.
  • blindingKey in the result is the public point k * G, which is on chain as the output's blsctData.ephemeralKey. It is deliberately not the field navio-core calls blsctData.blindingKey — that one is k * sk_destination, bound to the recipient's spend key, and no signature made with k verifies against it. The same point is on every synced output as WalletOutput.ephemeralKey, so a verifier can check a claim without going near the sender's wallet:
const output = (await client.getAllOutputs()).find(o => o.outputHash === hash)!;
Signature.deserialize(signature).verify(PublicKey.deserialize(output.ephemeralKey!), message);
  • A signature proves the signer created the output, not that they still own the funds. For a refund that is the right property: the person who paid is the person entitled to the reversal.

To opt out and go back to random, unrecoverable keys, set deterministicBlindingKeys: false on the client or randomBlindingKeys: true on an individual send.

Trade Tokens (RFQ Atomic Swaps)

Light wallets can trade tokens peer-to-peer over Navio's encrypted p2p messaging bus, both as taker (request quotes, accept the best one) and as maker (advertise intents, answer quote requests, publish standing orders). Requires the electrum backend against an ElectrumX server with the RFQ bridge, connected to a daemon with -p2pmsg=1.

Swap halves — deliberately unbalanced BLSCT transactions whose received leg is supplied by the counterparty's half — are always built and signed locally. The server only wraps, encrypts and relays them.

// ---- Taker: buy 500 units of a token, paying NAV ----
const req = await client.requestQuote({
  buyTokenId: tokenId,
  sellTokenId: null, // NAV
  amount: 500n,
  expiry: Math.floor(Date.now() / 1000) + 300,
});

// Poll collected quotes (ranked cheapest first)
const quotes = await client.listQuotes(req.uuid);

// Accept with mandatory slippage bounds — your only trust anchor against a
// malicious quote. The swap either settles atomically or no funds move.
const result = await client.acceptQuote({
  uuid: req.uuid,
  quoteId: quotes[0].quoteId,
  buyTokenId: tokenId,
  sellTokenId: null,
  maxPay: 60n, // reject if charged more
  minRecv: 500n, // reject if delivered less
});
console.log('Swap tx:', result.txId);

// ---- Maker: advertise liquidity and answer matching requests ----
await client.setSwapIntent({
  tokenInId: tokenId, // we deliver this
  tokenOutId: null, // we want NAV
  minSize: 1n,
  maxSize: 10_000n,
  priceMin: 10_000_000n, // 0.1 NAV per unit (scaled 1e8)
  expiry: Math.floor(Date.now() / 1000) + 3600,
});

await client.subscribePendingQuoteRequests(async pending => {
  for (const request of pending) {
    await client.replyQuote({ request }); // builds + signs the maker half
  }
});

// ---- Maker: standing order that peers can fill while you're offline ----
await client.broadcastOrder({
  offerTokenId: tokenId,
  offerAmount: 100n,
  wantTokenId: null,
  wantAmount: 10n,
  expiry: Math.floor(Date.now() / 1000) + 86400,
});

// Standing orders are tracked locally: the coins an order commits are not // offered again by the next broadcastOrder (the network's order cache // rejects a second order spending an input of a stored order). A timed-out // broadcast keeps the reservation — the daemon may still have published it. const orders = await client.listStandingOrders(); // live + unconfirmed, pruned on expiry / spent inputs await client.forgetStandingOrder(orders[0].localId); // release the coins locally (network keeps the order until expiry)

Maker halves spend wallet coins that are not locked while a quote or order is outstanding — don't spend them manually until it expires, or the swap will fail to confirm.

Query Wallet Outputs

// Get balance
const balanceNav = await client.getBalanceNav();
console.log(`Balance: ${balanceNav.toFixed(8)} NAV`);

// Get unspent outputs
const utxos = await client.getUnspentOutputs();
console.log(`\nUnspent Outputs (${utxos.length}):`);

for (const utxo of utxos) {
  const amount = Number(utxo.amount) / 1e8;
  console.log(`  ${utxo.outputHash.substring(0, 16)}...`);
  console.log(`    Amount: ${amount.toFixed(8)} NAV`);
  console.log(`    Block: ${utxo.blockHeight}`);
  if (utxo.memo) {
    console.log(`    Memo: ${utxo.memo}`);
  }
}

Encrypt Wallet with Password

import { NavioClient } from 'navio-sdk';

const client = new NavioClient({
  walletDbPath: './secure-wallet.db',
  electrum: { host: 'localhost', port: 50005 },
  createWalletIfNotExists: true,
  network: 'testnet',
});

await client.initialize();

// Set a password to encrypt the wallet
const keyManager = client.getKeyManager();
await keyManager.setPassword('my-secure-password');
console.log('Wallet encrypted:', keyManager.isEncrypted()); // true

// The wallet is now encrypted but unlocked
console.log('Wallet unlocked:', keyManager.isUnlocked()); // true

// Lock the wallet (recommended when not in use)
keyManager.lock();
console.log('Wallet unlocked:', keyManager.isUnlocked()); // false

// Unlock to use
const success = await keyManager.unlock('my-secure-password');
console.log('Unlock successful:', success); // true

Export Encrypted Backup

// Export encrypted database for backup
const walletDb = client.getWalletDB();
const encryptedBackup = await walletDb.exportEncrypted('backup-password');

// Save to file (Node.js)
const fs = require('fs');
fs.writeFileSync('wallet-backup.enc', encryptedBackup);

// Or download in browser
const blob = new Blob([encryptedBackup], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'wallet-backup.enc';
a.click();

Restore from Encrypted Backup

import { WalletDB } from 'navio-sdk';

// Load encrypted backup
const fs = require('fs');
const encryptedData = fs.readFileSync('wallet-backup.enc');

// Decrypt and load
const walletDb = await WalletDB.loadEncrypted(new Uint8Array(encryptedData), 'backup-password');

// Load the wallet
const keyManager = await walletDb.loadWallet();
console.log('Wallet restored');

Database Schema

The wallet database includes the following tables:

Table Description
keys Key pairs for transactions
out_keys Output-specific keys
crypted_keys Encrypted key pairs (when password set)
crypted_out_keys Encrypted output keys (when password set)
view_key View key for output detection
spend_key Spending public key
hd_chain HD chain information
master_seed Mnemonic phrase for wallet recovery
sub_addresses Sub-address mappings
wallet_outputs Wallet UTXOs with amounts
output_blinding_keys Private blinding scalars of outputs this wallet created
wallet_metadata Wallet creation info
encryption_metadata Encryption parameters (salt, verification hash)
tx_keys Transaction keys (optional)
block_hashes Block hashes for reorg detection
sync_state Synchronization state

output_blinding_keys Schema

Holds the private blinding scalar for each output this wallet created, as the fast path for recoverBlindingKey. Kept out of wallet_outputs on purpose: the row mappers for that table produce WalletOutput, which the public getters hand to callers, and a secret must not ride along by accident. (WalletOutput.blindingKey is the public point from the chain.)

Existing databases gain this table when they are next opened; nothing is rewritten, and a wallet with no rows here falls back to deriving from the seed.

CREATE TABLE output_blinding_keys (
  output_hash TEXT PRIMARY KEY,
  blinding_key TEXT NOT NULL,   -- the private scalar, 64 hex chars
  created_at INTEGER NOT NULL
);

wallet_outputs Schema

CREATE TABLE wallet_outputs (
  output_hash TEXT PRIMARY KEY,
  tx_hash TEXT NOT NULL,
  output_index INTEGER NOT NULL,
  block_height INTEGER NOT NULL,
  output_data TEXT NOT NULL,
  amount INTEGER NOT NULL DEFAULT 0,
  memo TEXT,
  token_id TEXT,
  blinding_key TEXT,     -- blsctData.blindingKey: k * sk_destination
  ephemeral_key TEXT,    -- blsctData.ephemeralKey: k * G (added in 0.2.0)
  spending_key TEXT,
  is_spent INTEGER NOT NULL DEFAULT 0,
  spent_tx_hash TEXT,
  spent_block_height INTEGER,
  created_at INTEGER NOT NULL
);

Optimization

The SDK includes several optimization options:

Option Default Description
keepTxKeys false Don't store TX keys after processing (saves space)
blockHashRetention 10000 Only keep last 10k block hashes (~2.4 MB savings)
saveInterval 100 Save database every 100 blocks
creationHeight chainTip - 100 Skip blocks before wallet creation

Network Configuration

The SDK uses navio-blsct for BLS CT operations. Network configuration is automatic based on the network option:

import { setChain, BlsctChain, getChain } from 'navio-sdk';

// Manual configuration (usually automatic)
setChain(BlsctChain.Testnet);
console.log('Current chain:', getChain());

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm run test              # Run all unit tests (vitest)
npm run test:keymanager   # KeyManager tests
npm run test:walletdb     # WalletDB tests
npm run test:electrum     # Electrum client tests
npm run test:client       # Full client tests (Electrum)
npm run test:p2p          # P2P protocol tests
npm run test:client:p2p   # Full client tests (P2P)
npm run test:p2p:regtest  # End-to-end P2P test against a spawned naviod (blsctregtest)
npm run test:encryption   # Encryption module tests
npm run test:tx-keys-sync # Transaction keys sync tests

# Generate documentation
npm run docs              # Markdown docs (default, via typedoc)
npm run docs:html         # HTML docs

# Type checking
npm run typecheck

# Formatting
npm run format

# Linting
npm run lint
npm run lint:fix

# Analyze database size
npm run analyze:db

Encryption Module

The SDK includes a low-level encryption module for password-based encryption.

import {
  encrypt,
  decrypt,
  deriveKey,
  serializeEncryptedData,
  deserializeEncryptedData,
  createPasswordVerification,
  verifyPassword,
  randomBytes,
} from 'navio-sdk';

// Encrypt arbitrary data
const plaintext = new TextEncoder().encode('secret data');
const encrypted = await encrypt(plaintext, 'password');

// Decrypt data
const decrypted = await decrypt(encrypted, 'password');
console.log(new TextDecoder().decode(decrypted)); // 'secret data'

// Serialize for storage (converts to base64 strings)
const serialized = serializeEncryptedData(encrypted);
const json = JSON.stringify(serialized);

// Deserialize from storage
const parsed = JSON.parse(json);
const deserialized = deserializeEncryptedData(parsed);

// Password verification (for UI feedback)
const salt = randomBytes(16);
const hash = await createPasswordVerification('password', salt);
const isValid = await verifyPassword('password', salt, hash);

Security Properties:

  • Key Derivation: Argon2id with 64MB memory, 3 iterations, 4 parallelism
  • Encryption: AES-256-GCM (authenticated encryption)
  • Random IV: 12 bytes per encryption (never reused)
  • Random Salt: 16 bytes per password derivation

Known Limitations

  1. Token Support: Token transfers are tracked but not fully implemented for spending.

  2. Browser bech32m: In browser environments using navio-blsct WASM, bech32m address encoding may fall back to hex representation.

  3. Blinding key backfill during sync: the sync-time backfill that pre-populates the fast path only fires for transactions this wallet is seen to have spent an input into. A transaction whose inputs predate the wallet's restore height is classified as received, so its outputs are not backfilled — recoverBlindingKey still derives them on demand.


API Documentation

Full API documentation is auto-generated using TypeDoc:

npm run docs

View the generated documentation in the ./docs directory.


Examples

Web Wallet

A basic browser-based wallet example is available in examples/web-wallet/:

cd examples/web-wallet
npm install
npm run dev

Features:

  • Create/restore HD wallet
  • Connect to Electrum server
  • Background synchronization
  • Balance and UTXO display

See examples/web-wallet/README.md for details.


License

MIT