Building Blocks
Discover and install building blocks to extend your WDK integration.
@tetherto/wdk-wallet-btc
Bitcoin SegWit wallet with BIP-39/BIP-44 support
Note: This package is currently in beta. Please test thoroughly in development environments before using in production.
A simple and secure package to manage BIP-84 (SegWit) wallets for the Bitcoin blockchain. This package provides a clean API for creating, managing, and interacting with Bitcoin wallets using BIP-39 seed phrases and Bitcoin-specific derivation paths.
This module is part of the WDK (Wallet Development Kit) project, which empowers developers to build secure, non-custodial wallets with unified blockchain access, stateless architecture, and complete user control.
For detailed documentation about the complete WDK ecosystem, visit docs.wallet.tether.io.
To install the @tetherto/wdk-wallet-btc package, follow these instructions:
You can install it using npm:
@tetherto/wdk-wallet-btcNote: This implementation uses BIP-84 derivation paths by default and generates Native SegWit (bech32) addresses. BIP-44 (legacy) addresses are also supported via the bip configuration option.
Important Note about Electrum Servers:
While the package defaults to electrum.blockstream.info if no host is specified, we strongly recommend configuring your own Electrum server for production use. Public servers like Blockstream's can be significantly slower (10-300x) and may fail when fetching transaction history for popular addresses with many transactions. For better performance, consider using alternative public servers like fulcrum.frznode.com for development, or set up your own Fulcrum server for production environments.
Note: This implementation generates Native SegWit (bech32) addresses by default. All accounts use BIP-84 derivation paths (m/84'/0'/account'/0/index for mainnet, m/84'/1'/account'/0/index for testnet/regtest).
Important Notes:
unconfirmedOutgoing, unconfirmed incoming funds aren't counted (since they aren't spendable yet) but unconfirmed outgoing funds are subtracted immediately. Clients that can't compute unconfirmedOutgoing fall back to netting the raw unconfirmed balance.
blockbook-http) computes unconfirmedOutgoing: a pending spend of yours is subtracted as soon as it's broadcast, following the same trust rule as Bitcoin Core's own wallet - it only counts if every input it draws from is itself confirmed, or comes from another one of your pending transactions that's also trusted by that same rule.electrum, electrum-ws) doesn't compute unconfirmedOutgoing, so it falls back to the server's raw net mempool delta, which is coarser and can include unconfirmed incoming amounts too. Any client that does implement unconfirmedOutgoing should follow Blockbook's same trust rule, so getBalance() behaves consistently across providers.getTransfers() instead of getTransactionHistory() for transaction dataSend Bitcoin and estimate fees using WalletAccountBtc. Uses Electrum servers for broadcasting.
Important Notes:
sendTransaction() returns hash and fee propertiesquoteSendTransaction() returns only the fee estimateSign messages using WalletAccountBtc. Verify messages using WalletAccountReadOnlyBtc (also available on WalletAccountBtc through inheritance).
Retrieve current fee rates using WalletManagerBtc. Rates are provided in satoshis per virtual byte (sat/vB).
Important Notes:
getFeeRates() returns only normal and fast fee rates (no economic or priority)quoteSendTransaction() method, not a separate estimateFee() methodClear sensitive data from memory using dispose methods.
| Class | Description | Methods |
|---|---|---|
| WalletManagerBtc | Main class for managing Bitcoin wallets. Extends WalletManager from @tetherto/wdk-wallet. | Constructor, Methods |
| WalletAccountBtc | Individual Bitcoin wallet account implementation. Extends WalletAccountReadOnlyBtc and implements IWalletAccount from @tetherto/wdk-wallet. | Constructor, Methods, Properties |
The main class for managing Bitcoin wallets.
Extends WalletManager from @tetherto/wdk-wallet.
Parameters:
seed (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytesconfig (BtcWalletConfig, optional): Configuration object
network (string, optional): "bitcoin", "testnet", or "regtest" (default: "bitcoin")bip (number, optional): BIP address type - 44 (legacy) or 84 (native SegWit) (default: 84)client — one of:
IBtcClient instance (pre-built client){ type, clientConfig } where type is 'electrum', 'blockbook-http', or 'electrum-ws'Example:
| Method | Description | Returns |
|---|---|---|
getAccount(index) | Returns a wallet account at the specified index | Promise<WalletAccountBtc> |
getAccountByPath(path) | Returns a wallet account at the specified BIP-84 derivation path | Promise<WalletAccountBtc> |
getFeeRates() | Returns current fee rates for transactions | Promise<{normal: bigint, fast: bigint}> |
dispose() | Disposes all wallet accounts, clearing private keys from memory | void |
getAccount(index)Returns a wallet account at the specified index using BIP-84 derivation.
Parameters:
index (number, optional): The index of the account to get (default: 0)Returns: Promise<WalletAccountBtc> - The wallet account
Example:
getAccountByPath(path)Returns a wallet account at the specified BIP-84 derivation path.
Parameters:
path (string): The derivation path (e.g., "0'/0/0")Returns: Promise<WalletAccountBtc> - The wallet account
Example:
getFeeRates()Returns current fee rates from mempool.space API.
Returns: Promise<{normal: bigint, fast: bigint}> - Object containing fee rates in sat/vB
normal: Standard fee rate for confirmation within ~1 hourfast: Higher fee rate for faster confirmationExample:
dispose()Disposes all wallet accounts and clears sensitive data from memory.
Returns: void
Example:
Represents an individual Bitcoin wallet account. Extends WalletAccountReadOnlyBtc and implements IWalletAccount from @tetherto/wdk-wallet.
Parameters:
seed (string | Uint8Array): BIP-39 mnemonic seed phrase or seed bytespath (string): Derivation path suffix (e.g., "0'/0/0")config (BtcWalletConfig, optional): Configuration object (see WalletManagerBtc constructor for details)| Method | Description | Returns |
|---|---|---|
getAddress() | Returns the account's Bitcoin address | Promise<string> |
getBalance() | Returns the account's balance in satoshis (see Checking Balances) | Promise<bigint> |
sendTransaction(options) | Sends a Bitcoin transaction | Promise<{hash: string, fee: bigint}> |
quoteSendTransaction(options) | Estimates the fee for a transaction | Promise<{fee: bigint}> |
getTransfers(options?) | Returns the account's transfer history | Promise<BtcTransfer[]> |
getTransactionReceipt(hash) | Returns a transaction's receipt | Promise<BtcTransaction | null> |
getMaxSpendable() | Returns the maximum spendable amount | Promise<{amount: bigint, fee: bigint, changeValue: bigint}> |
sign(message) | Signs a message with the account's private key | Promise<string> |
verify(message, signature) | Verifies a message signature | Promise<boolean> |
toReadOnlyAccount() | Creates a read-only version of this account | Promise<WalletAccountReadOnlyBtc> |
dispose() | Disposes the wallet account, clearing private keys from memory | void |
getAddress()Returns the account's Bitcoin address (Native SegWit bech32 by default, or legacy if using BIP-44).
Returns: Promise<string> - The Bitcoin address
Example:
getBalance()Returns the account's balance in satoshis - confirmed funds, plus handling of the account's own pending transactions that varies by provider. See Checking Balances for details.
Returns: Promise<bigint> - Balance in satoshis
Example:
sendTransaction(options)Sends a Bitcoin transaction to a single recipient.
Parameters:
options (object): Transaction options
to (string): Recipient's Bitcoin addressvalue (number | bigint): Amount in satoshisfeeRate (number | bigint, optional): Fee rate in sat/vB (auto-estimated if not provided)confirmationTarget (number, optional): Target blocks for confirmation (default: 1)Returns: Promise<{hash: string, fee: bigint}> - Object containing hash and fee (in satoshis)
Example:
quoteSendTransaction(options)Estimates the fee for a transaction without broadcasting it.
Parameters:
options (object): Same as sendTransaction options
to (string): Recipient's Bitcoin addressvalue (number | bigint): Amount in satoshisfeeRate (number | bigint, optional): Fee rate in sat/vB (auto-estimated if not provided)confirmationTarget (number, optional): Target blocks for confirmation (default: 1)Returns: Promise<{fee: bigint}> - Object containing estimated fee (in satoshis)
Example:
getTransfers(options?)Returns the account's transfer history with detailed transaction information.
Parameters:
options (object, optional): Filter options
direction (string, optional): 'incoming', 'outgoing', or 'all' (default: 'all')limit (number, optional): Maximum number of transfers (default: 10)skip (number, optional): Number of transfers to skip (default: 0)Returns: Promise<BtcTransfer[]> - Array of transfer objects
Example:
getTransactionReceipt(hash)Returns a transaction's receipt if it has been included in a block.
Parameters:
hash (string): The transaction hash (64 hex characters)Returns: Promise<BtcTransaction | null> - The transaction object, or null if not yet confirmed
Example:
getMaxSpendable()Returns the maximum spendable amount that can be sent in a single transaction. The maximum spendable amount can differ from the wallet's total balance for several reasons:
Returns: Promise<{amount: bigint, fee: bigint, changeValue: bigint}> - Maximum spendable result
Example:
sign(message)Signs a message using the account's private key.
Parameters:
message (string): Message to signReturns: Promise<string> - Signature as base64 string
Example:
verify(message, signature)Verifies a message signature using the account's public key.
Parameters:
message (string): Original messagesignature (string): Signature as base64 stringReturns: Promise<boolean> - True if signature is valid
Example:
Note: The verify method is available on WalletAccountReadOnlyBtc and is inherited by WalletAccountBtc.
toReadOnlyAccount()Creates a read-only version of this account that can query balances but cannot sign transactions.
Returns: Promise<WalletAccountReadOnlyBtc> - The read-only account
Example:
dispose()Disposes the wallet account, securely erasing the private key from memory and closing the Electrum connection.
Returns: void
Example:
Note: getTokenBalance(), transfer(), and quoteTransfer() methods are not supported on the Bitcoin blockchain and will throw errors.
| Property | Type | Description |
|---|---|---|
index | number | The derivation path's index of this account |
path | string | The full derivation path of this account |
keyPair | object | The account's key pair (⚠️ Contains sensitive data) |
⚠️ Security Note: The keyPair property contains sensitive cryptographic material. Never log, display, or expose the private key.
Represents a read-only Bitcoin wallet account. Extends WalletAccountReadOnly from @tetherto/wdk-wallet.
Parameters:
address (string): The account's Bitcoin addressconfig (BtcWalletConfig, optional): Configuration object (see WalletManagerBtc constructor for details)| Method | Description | Returns |
|---|---|---|
getBalance() | Returns the account's balance in satoshis (see Checking Balances) | Promise<bigint> |
quoteSendTransaction(options) | Estimates the fee for a transaction | Promise<{fee: bigint}> |
getTransactionReceipt(hash) | Returns a transaction's receipt | Promise<BtcTransaction | null> |
getMaxSpendable() | Returns the maximum spendable amount | Promise<{amount: bigint, fee: bigint, changeValue: bigint}> |
This package works with Bitcoin networks:
"bitcoin")"testnet")"regtest")Important: While the package defaults to electrum.blockstream.info:50001 for convenience, we strongly recommend configuring your own Electrum server for production use.
For Production:
For Development/Testing:
fulcrum.frznode.com:50001 - Generally faster than defaultelectrum.blockstream.info:50001 - Default fallbackPerformance Note: Public Electrum servers may be 10-300x slower and can fail for addresses with many transactions. Always use your own infrastructure for production applications.
This implementation supports the following address types:
Native SegWit (P2WPKH) (default, BIP-84): Addresses starting with 'bc1' (mainnet) or 'tb1' (testnet)
m/84'/0'/account'/0/index for mainnet)Legacy (P2PKH) (BIP-44): Addresses starting with '1' (mainnet) or 'm'/'n' (testnet)
m/44'/0'/account'/0/index for mainnet){ bip: 44 } in configdispose() method to clear private keys from memory when doneThis project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For support, please open an issue on the GitHub repository.
| WalletAccountReadOnlyBtc | Read-only Bitcoin wallet account. Extends WalletAccountReadOnly from @tetherto/wdk-wallet. | Constructor, Methods |
npm install @tetherto/wdk-wallet-btcimport WalletManagerBtc from '@tetherto/wdk-wallet-btc'
// Use a BIP-39 seed phrase (replace with your own secure phrase)
const seedPhrase = 'test only example nut use this real life secret phrase must random'
// Electrum TCP (default if no client is specified)
const wallet = new WalletManagerBtc(seedPhrase, {
client: { type: 'electrum', clientConfig: { host: 'electrum.blockstream.info', port: 50001 } },
network: 'bitcoin' // 'bitcoin', 'testnet', or 'regtest'
})
// Blockbook REST
// const wallet = new WalletManagerBtc(seedPhrase, {
// client: { type: 'blockbook-http', clientConfig: { url: 'https://btc1.trezor.io/api' } },
// network: 'bitcoin'
// })
// WebSocket Electrum
// const wallet = new WalletManagerBtc(seedPhrase, {
// client: { type: 'electrum-ws', clientConfig: { url: 'wss://electrum.example.com:50004' } },
// network: 'bitcoin'
// })
// Pre-built client instance
// import { ElectrumTcp, BlockbookClient } from '@tetherto/wdk-wallet-btc'
// const client = new ElectrumTcp({ host: '...', port: 50001 })
// const wallet = new WalletManagerBtc(seedPhrase, { client })
// Failover — array of clients, tries each in order
// const wallet = new WalletManagerBtc(seedPhrase, {
// client: [
// { type: 'blockbook-http', clientConfig: { url: 'https://btc1.trezor.io/api' } },
// { type: 'electrum', clientConfig: { host: 'electrum.blockstream.info', port: 50001 } },
// ],
// network: 'bitcoin'
// })
// Get a full access account (uses BIP-84 derivation path)
const account = await wallet.getAccount(0)
// Get the account's address (Native SegWit by default)
const address = await account.getAddress()
console.log('Account address:', address)
// Convert to a read-only account
const readOnlyAccount = await account.toReadOnlyAccount()import WalletManagerBtc from '@tetherto/wdk-wallet-btc'
// Assume wallet is already created
// Get the first account (index 0)
const account = await wallet.getAccount(0)
const address = await account.getAddress()
console.log('Account 0 address:', address)
// Get the second account (index 1)
const account1 = await wallet.getAccount(1)
const address1 = await account1.getAddress()
console.log('Account 1 address:', address1)
// Get account by custom derivation path
// Full path will be m/84'/0'/0'/0/5 (mainnet) or m/84'/1'/0'/0/5 (testnet/regtest)
const customAccount = await wallet.getAccountByPath("0'/0/5")
const customAddress = await customAccount.getAddress()
console.log('Custom account address:', customAddress)
// All accounts inherit the provider configuration from the wallet managerimport WalletManagerBtc from '@tetherto/wdk-wallet-btc'
// Assume wallet and account are already created
// Get balance in satoshis
const balance = await account.getBalance()
console.log('Balance:', balance, 'satoshis') // 1 BTC = 100,000,000 satoshis
// Get transfer history (incoming and outgoing transfers)
const allTransfers = await account.getTransfers()
console.log('Recent transfers (last 10):', allTransfers)
// Get transfer history with options
const incomingTransfers = await account.getTransfers({
direction: 'incoming', // 'incoming', 'outgoing', or 'all'
limit: 20, // Number of transfers to fetch
skip: 0 // Number of transfers to skip
})
console.log('Incoming transfers:', incomingTransfers)
// Note: Provider is required for balance checks
// Make sure wallet was created with Electrum server configuration// Send Bitcoin (single recipient only)
const result = await account.sendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', // Recipient's Bitcoin address
value: 50000n, // Amount in satoshis
feeRate: 10n, // Optional: fee rate in sat/vB (auto-estimated if not provided)
confirmationTarget: 1 // Optional: target blocks for confirmation (default: 1)
})
console.log('Transaction hash:', result.hash)
console.log('Transaction fee:', result.fee, 'satoshis')
// Get transaction fee estimate
const quote = await account.quoteSendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
value: 50000n
})
console.log('Estimated fee:', quote.fee, 'satoshis')// Sign a message
const message = 'Hello, Bitcoin!'
const signature = await account.sign(message)
console.log('Signature:', signature)
// Verify a signature
const isValid = await account.verify(message, signature)
console.log('Signature valid:', isValid)// Get current fee rates
const feeRates = await wallet.getFeeRates()
console.log('Normal fee rate:', feeRates.normal, 'sat/vB') // Standard confirmation time (~1 hour)
console.log('Fast fee rate:', feeRates.fast, 'sat/vB') // Faster confirmation time// Dispose wallet account to clear private keys from memory
account.dispose()
// Dispose entire wallet manager
wallet.dispose()new WalletManagerBtc(seed, config)const wallet = new WalletManagerBtc(seedPhrase, {
client: { type: 'electrum', clientConfig: { host: 'electrum.blockstream.info', port: 50001 } },
network: 'bitcoin'
})// Returns the account with derivation path:
// For mainnet (bitcoin): m/84'/0'/0'/0/1
// For testnet or regtest: m/84'/1'/0'/0/1
const account = await wallet.getAccount(1)// Returns the account with derivation path:
// For mainnet (bitcoin): m/84'/0'/0'/0/1
// For testnet or regtest: m/84'/1'/0'/0/1
const account = await wallet.getAccountByPath("0'/0/1")const feeRates = await wallet.getFeeRates()
console.log('Normal fee rate:', feeRates.normal, 'sat/vB')
console.log('Fast fee rate:', feeRates.fast, 'sat/vB')wallet.dispose()new WalletAccountBtc(seed, path, config)const address = await account.getAddress()
console.log('Address:', address) // bc1q... (BIP-84) or 1... (BIP-44)const balance = await account.getBalance()
console.log('Balance:', balance, 'satoshis')const result = await account.sendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
value: 50000n
})
console.log('Transaction hash:', result.hash)
console.log('Fee:', result.fee, 'satoshis')const quote = await account.quoteSendTransaction({
to: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
value: 50000n
})
console.log('Estimated fee:', quote.fee, 'satoshis')const transfers = await account.getTransfers({
direction: 'incoming',
limit: 5
})
console.log('Recent incoming transfers:', transfers)const receipt = await account.getTransactionReceipt('abc123...')
if (receipt) {
console.log('Transaction confirmed')
}const { amount, fee } = await account.getMaxSpendable()
console.log('Max spendable:', amount, 'satoshis')
console.log('Estimated fee:', fee, 'satoshis')const signature = await account.sign('Hello Bitcoin!')
console.log('Signature:', signature)const isValid = await account.verify('Hello Bitcoin!', signature)
console.log('Signature valid:', isValid)const readOnlyAccount = await account.toReadOnlyAccount()
const balance = await readOnlyAccount.getBalance()account.dispose()
// Private key is now securely wiped from memorynew WalletAccountReadOnlyBtc(address, config)# Install dependencies
npm install
# Build TypeScript definitions
npm run build:types
# Lint code
npm run lint
# Fix linting issues
npm run lint:fix# Run tests
npm test
# Run tests with coverage
npm run test:coverage