Research/Bitcoin

Bitcoin Development Kit: Building Production Wallets with BDK in 2026

How the Bitcoin Development Kit (BDK) simplifies wallet development with modular, production-ready Rust libraries for key management and transactions.

bcMaoJul 14, 2026

Building a Bitcoin wallet from scratch means implementing key derivation, address generation, UTXO tracking, coin selection, transaction construction, fee estimation, signing, and blockchain synchronization. Each of these components carries subtle security implications: a single nonce reuse in signing can leak a private key, and a naive coin selection algorithm can destroy user privacy. The Bitcoin Development Kit (BDK) exists to solve this problem: it provides modular, audited Rust libraries that handle the hard parts of on-chain wallet development so teams can focus on user experience rather than cryptographic plumbing.

With the release of BDK 1.0 in December 2024 and the progression to bdk_wallet 3.1.0 as of June 2026, the project has matured from an experimental toolkit into production infrastructure. Over 40 projects now build on BDK, including Bitkey (Block's hardware wallet), ProtonWallet, Bull Bitcoin, MetaMask's native Bitcoin support, and mempool.space. This guide covers BDK's architecture, its descriptor-based design, practical wallet construction, and how it fits alongside Layer 2 tools like LDK and the Spark SDK.

What BDK Handles (and What It Does Not)

BDK is a library, not a wallet application. It provides the building blocks that wallet developers compose into a finished product. The boundary is deliberate: BDK owns everything from key derivation down to transaction serialization, while leaving UI, networking architecture, and business logic to the application layer.

What BDK Provides

  • Hierarchical deterministic key derivation following BIP-32, BIP-44, BIP-84, and BIP-86 standards
  • Output descriptor-based wallet definition (BIPs 380 through 386)
  • Coin selection algorithms: Branch and Bound, Largest First, Oldest First, and Single Random Draw
  • Transaction building with full control over fee rates, RBF signaling, drain operations, and manual UTXO selection
  • PSBT creation, signing, and finalization
  • Blockchain synchronization via Electrum, Esplora, or bitcoind RPC
  • Persistent wallet state through SQLite or file-based storage

What BDK Does Not Provide

  • User interface or frontend components
  • Networking decisions (you choose your chain data source)
  • Fee estimation oracles (you provide fee rate targets)
  • Lightning or Layer 2 channel management
  • Custodial key storage or cloud backup services
Design philosophy: BDK is “descriptor-first.” You define a wallet entirely through its output descriptors rather than configuring key type and script type separately. A single descriptor string encodes the script template, key material, derivation path, and an error-detecting checksum. This makes wallet backup, migration, and interoperability straightforward.

BDK Architecture: the Modular Crate System

The 0.x era of BDK shipped as a single monolithic crate with Blockchain and Database traits that broke across releases. The 1.0 redesign split the library into a workspace of focused crates, each with its own versioning and a stable API contract. This modular structure lets developers pull in only what they need.

CratePurposeWhen to Use
bdk_walletHigh-level wallet: address derivation, tx building, signing, balance queries, UTXO managementEvery BDK project
bdk_chainChain data indexing: TxGraph, LocalChain, KeychainTxOutIndexCustom chain backends or advanced indexing
bdk_electrumFetch chain data from Electrum serversDesktop wallets, server environments
bdk_esploraFetch chain data from Esplora HTTP APIsMobile wallets, browser environments
bdk_bitcoind_rpcFetch data directly from bitcoind's RPC interfaceFull-node operators, maximum sovereignty
bdk_sqliteProduction SQLite persistence (via rusqlite feature)Production deployments
bdk_file_storeSimple file-based persistenceTesting and prototyping only

The bdk_chain crate deserves special attention. It provides three core primitives that power wallet state management: TxGraph stores and indexes transactions for traversal, LocalChain tracks blockchain tip and reorganization state, and KeychainTxOutIndex indexes transaction outputs by keychain for balance computation. These are data-source agnostic and persistence agnostic by design: the same structures work whether your chain source is an Electrum server, an Esplora API, or a local bitcoind instance.

Descriptor-Based Wallet Design

Output descriptors are the central abstraction in BDK. Introduced in BIP-380 and extended through BIP-386, they encode everything a wallet needs to derive addresses and construct transactions in a single, portable string.

Anatomy of a Descriptor

A BIP-84 native SegWit descriptor looks like this:

wpkh([d34db33f/84h/0h/0h]xpub6ERApf.../0/*)#checksum

Each component serves a specific purpose:

  • wpkh() specifies the script type (witness public key hash, producing P2WPKH outputs)
  • [d34db33f/84h/0h/0h] encodes the master key fingerprint and derivation path with hardened steps
  • xpub6ERApf... is the extended public key at the account level
  • /0/* denotes the external (receiving) keychain with a wildcard for index derivation
  • #checksum is a BCH error-detecting code preventing typos

Descriptor Templates for Common Script Types

BDK provides built-in templates that generate descriptors for the four standard address types:

BIP StandardDescriptor FunctionScript TypeAddress Prefix
BIP-44pkh()Legacy P2PKH1...
BIP-49sh(wpkh())Wrapped SegWit3...
BIP-84wpkh()Native SegWit (P2WPKH)bc1q...
BIP-86tr()Taproot (P2TR)bc1p...

For new wallets in 2026, Taproot (BIP-86) is the recommended default. It produces smaller witnesses for single-sig spends, enables future key-path and script-path flexibility, and improves privacy by making all outputs look identical on chain. A BIP-86 Taproot descriptor takes the form tr(internal_key) for key-path-only outputs or tr(internal_key, {script1, script2}) when a script tree is needed.

Building a Taproot Wallet: a Practical Walkthrough

The following walkthrough demonstrates the core BDK workflow: creating a descriptor-based Taproot wallet, syncing it with a chain source, constructing a transaction, and persisting state. The example uses Rust, but the same concepts apply via BDK's language bindings for Swift, Kotlin, and Python.

Step 1: Generate Keys and Descriptors

Key generation starts with a BIP-39 mnemonic (typically 12 or 24 words) that deterministically derives an extended private key. BDK's BIP-86 template then produces a Taproot descriptor pair: one for receiving addresses (external keychain, /0/*) and one for change addresses (internal keychain, /1/*).

use bdk_wallet::bitcoin::Network;
use bdk_wallet::keys::bip39::Mnemonic;
use bdk_wallet::template::Bip86;
use bdk_wallet::KeychainKind;

// Generate a fresh 12-word mnemonic
let mnemonic = Mnemonic::generate(12)?;

// Create Taproot descriptors for mainnet
let external = Bip86(mnemonic.clone(), KeychainKind::External);
let internal = Bip86(mnemonic.clone(), KeychainKind::Internal);

The derivation path m/86'/0'/0' is automatically applied. The first three levels use hardened derivation to isolate the account, while the final two levels (change and index) use normal derivation so an xpub at the account level can generate all addresses without exposing the private key.

Step 2: Create the Wallet with Persistence

BDK wallets default to in-memory state. For production use, enable the rusqlite feature to persist wallet data across sessions. BDK's persistence model uses ChangeSets: each wallet operation (revealing addresses, syncing chain data) generates a staged set of changes that get flushed to storage when you call wallet.persist().

use bdk_wallet::Wallet;
use bdk_wallet::rusqlite::Connection;

let mut conn = Connection::open("wallet.db")?;
let mut wallet = Wallet::create(external, internal)
    .network(Network::Bitcoin)
    .create_wallet(&mut conn)?;

On subsequent launches, Wallet::load() restores the wallet from persisted ChangeSets. This requires only a lightweight sync rather than a full blockchain rescan, keeping startup times fast even on mobile devices.

Step 3: Sync with a Chain Source

BDK separates chain data fetching from wallet logic. You choose your backend based on your deployment constraints:

  • bdk_esplora for mobile and browser (HTTP-based, works behind firewalls and NATs)
  • bdk_electrum for desktop (TCP-based, efficient for continuous sync)
  • bdk_bitcoind_rpc for full-node setups (maximum privacy and sovereignty)
use bdk_esplora::EsploraExt;
use bdk_esplora::esplora_client::Builder;

let client = Builder::new("https://blockstream.info/api")
    .build_blocking();

// Full scan discovers all used addresses
let request = wallet.start_full_scan();
let update = client.full_scan(request, 5, 5)?;
wallet.apply_update(update)?;
wallet.persist(&mut conn)?;

After the initial full scan, subsequent syncs use start_sync_with_revealed_spks() for incremental updates, which only queries for newly revealed script pubkeys.

Step 4: Build, Sign, and Broadcast

The TxBuilder uses a builder pattern to configure every aspect of the transaction before producing a PSBT:

use bdk_wallet::bitcoin::Amount;

let recipient = address.script_pubkey();
let fee_rate = FeeRate::from_sat_per_vb(4);

let mut psbt = wallet.build_tx()
    .add_recipient(recipient, Amount::from_sat(50_000))
    .fee_rate(fee_rate)
    .finish()?;

// Sign the PSBT
wallet.sign(&mut psbt, SignOptions::default())?;

// Extract the signed transaction
let tx = psbt.extract_tx()?;

// Broadcast via your chain source
client.broadcast(&tx)?;

The builder exposes fine-grained control: add_utxo() for manual coin control, add_unspendable() to exclude specific UTXOs, drain_wallet() combined with drain_to() to sweep all funds, and enable_rbf() for replace-by-fee signaling (enabled by default). For fee bumping unconfirmed transactions, wallet.build_fee_bump(txid) returns a new TxBuilder preconfigured for RBF fee bumping.

Coin Selection Strategies

Coin selection determines which UTXOs fund a transaction. The choice affects transaction size (and therefore fees), privacy (which outputs get linked), and UTXO set health over time. BDK implements four algorithms:

AlgorithmStrategyBest ForTradeoff
Branch and BoundDepth-first search for exact-match input sets that avoid creating change outputsMinimizing fees and changeComputationally heavier; falls back to random draw on failure
Largest FirstGreedy selection starting from highest-value UTXOsConsolidating large UTXOsPoor privacy: always links largest holdings
Oldest FirstPrioritizes UTXOs by blockchain confirmation ageUTXO hygiene and consolidationMay spend high-value UTXOs unnecessarily
Single Random DrawRandomly shuffles UTXOs, selects sequentiallyPrivacy (unpredictable patterns)No fee optimization

The default is Branch and Bound with Single Random Draw as fallback, adapted from Bitcoin Core's implementation. This prioritizes changeless transactions (saving fees) while falling back to random selection when an exact match is impossible. Developers can also implement the CoinSelectionAlgorithm trait for custom strategies: for example, a privacy-focused wallet might implement a strategy that avoids combining UTXOs from different sources (similar to coin control heuristics).

BDK vs LDK vs Building from Scratch

Wallet developers face a build-versus-library decision at three levels: raw protocol implementation, on-chain abstraction (BDK), and Lightning abstraction (LDK). These are not competing tools: they operate at different layers and are designed to compose together.

DimensionBDKLDKFrom Scratch
ScopeOn-chain wallet (L1)Lightning channels (L2)Whatever you build
Abstraction levelHigh: wallet-level APIsLow: trait-based, requires 10+ implementationsNone
Time to productionWeeksMonths to over a yearYears
LanguageRust (with Swift, Kotlin, Python bindings)Rust (with Swift, Kotlin bindings)Any
Key managementBuilt-in HD derivation and signingBring your own key managerBuild everything
Chain syncElectrum, Esplora, bitcoind RPCBring your own chain sourceBuild everything
PersistenceSQLite or file storeBring your own storageBuild everything
Production adoption40+ projects (Bitkey, ProtonWallet, MetaMask)~25% of Lightning volumeVaries

The practical difference is stark. BDK provides a high-level Wallet struct with methods like build_tx(), sign(), and balance() that abstract away UTXO tracking and script evaluation. LDK is deliberately low-level: it provides channel state machine logic but requires implementors to supply their own networking, storage, chain monitoring, and key management. This is by design: LDK targets teams building custom Lightning infrastructure who need full control over every component.

For wallet applications that need both on-chain and Lightning capability, the LDK Node project combines LDK with BDK into a higher-level Lightning node library that handles the integration plumbing. For a detailed comparison of Bitcoin wallet SDKs including BDK, LDK, and Spark, see the Bitcoin wallet SDK comparison.

Language Bindings and Mobile Development

While BDK's core is written in Rust, mobile and cross-platform wallets need native access. The bdk-ffi project uses Mozilla's uniffi-rs to generate idiomatic bindings for multiple languages. As of bdk-ffi 3.0.0 (released June 2026), four languages have stable, production-ready bindings:

  • Kotlin and Java/JVM (Android, desktop): published on Maven Central
  • Swift (iOS, macOS): maintained in the bdk-swift repository
  • Python (Linux, macOS, Windows): available as bdkpython on PyPI

Several additional bindings are in active development:

  • Dart/Flutter: reached integration testing in early 2026, targeting cross-platform mobile wallets
  • React Native: also in integration testing, enabling JavaScript-based wallet apps
  • WebAssembly: already used by MetaMask for native Bitcoin support in the browser
Binding limitations: uniffi cannot expose Rust generics or tuple returns directly. The bdk-ffi team wraps these into concrete types and structs to maintain idiomatic APIs in target languages. The binding scope is also deliberately limited for mobile deployment to avoid shipping oversized libraries.

Mobile Wallet Integration Patterns

Mobile wallets using BDK typically follow one of two architectures:

  • Client-side only: the wallet runs BDK directly on-device via Swift or Kotlin bindings, syncing with a public Esplora server. This is the simplest pattern and preserves maximum self-custody. Startup time depends on initial sync, but BDK's ChangeSet persistence ensures that only incremental updates are needed after the first run.
  • Hybrid with backend: the wallet offloads blockchain indexing to a server-side BDK instance (or an Electrum/Esplora endpoint), while signing remains on-device. This pattern reduces mobile battery and bandwidth usage at the cost of trusting the backend for chain data accuracy (but not for custody).

In both cases, private keys never leave the device. BDK's SQLite persistence stores transaction graphs, UTXO state, and address indexes but explicitly excludes private key material from the database.

Persistence and the ChangeSet Model

BDK's approach to persistence centers on ChangeSets: granular, append-only records of wallet state mutations. Rather than serializing the entire wallet state on every operation, BDK tracks what changed and flushes only the delta.

How It Works

Every wallet operation that modifies state (revealing a new address, applying a chain sync update, inserting a transaction) generates a staged ChangeSet. These accumulate in memory until the developer explicitly calls wallet.persist(&mut conn). On reload, Wallet::load() replays persisted ChangeSets to reconstruct the full wallet state.

This design has practical advantages. Batch operations (syncing hundreds of transactions) produce a single persist call rather than hundreds of database writes. Crash recovery is deterministic: you lose at most the unpersisted ChangeSets since the last flush. And migration between storage backends requires only reimporting the ChangeSets into a new store.

For production deployments, bdk_sqlite (enabled via the rusqlite feature flag) provides the recommended persistence backend. The 3.0.0 release added a dedicated bdk_wallet_locked_outpoints table for tracking UTXO lock statuses, along with migration utilities for upgrading from pre-1.0 databases.

Production Users and Ecosystem

The BDK adoption page lists over 40 production integrations spanning consumer wallets, infrastructure, and protocol-level projects:

Consumer Wallets

  • Bitkey: Block's consumer hardware wallet uses BDK for all on-chain operations
  • ProtonWallet: Proton's self-custodial Bitcoin wallet
  • Bull Bitcoin: Canadian self-custodial wallet and exchange
  • Envoy: Foundation Devices' companion app for the Passport hardware wallet
  • Peach Bitcoin: peer-to-peer Bitcoin marketplace
  • Liana: recovery and inheritance wallet by Wizardsardine

Infrastructure and Protocols

  • MetaMask: uses bdk-wasm for native Bitcoin support in the browser extension
  • Fedimint: community custody protocol uses BDK for on-chain peg-in and peg-out
  • mempool.space: the most widely used blockchain explorer and mempool visualizer
  • LDK Node: combines BDK with LDK for a higher-level Lightning node
  • RGB: digital asset protocol on Bitcoin

Recent Developments: 2025 and 2026

BDK's development velocity has accelerated since the 1.0 release, with the project shipping major versions on an 8-week cadence:

2025 Milestones

  • bdk_wallet 1.1.0 (February 2025): added Testnet4 support, single-descriptor wallets, and v2 transactions by default
  • bdk_wallet 2.0.0 (June 2025): fixed stuck and evicted transaction handling, added performance optimizations for large wallets, and reintroduced TxDetails
  • bdk-ffi 1.2: added Compact Block Filter (Kyoto) support for mobile
  • New bdk-sp crate for Silent Payments (BIP-352) support
  • New bdk-tx project for decoupled transaction building

2026 Milestones

  • bdk_wallet 3.0.0 (April 2026): persistent UTXO locking, structured wallet events, multipath descriptor support (BIP-389), Caravan wallet format import/export, and SQLite migration utilities
  • bdk_wallet 3.1.0 (June 2026): the current stable release
  • bdk-ffi 3.0.0 (June 2026): stable bindings for Kotlin, Swift, Python, and JVM
  • bdk-cli 3.0.0: added config file support and Payjoin integration
  • Dart/Flutter and React Native bindings reached integration testing

Combining BDK with Layer 2: the Full Wallet Stack

BDK excels at on-chain wallet functionality, but modern Bitcoin wallets need more than Layer 1. Users expect instant transfers, sub-cent fees, and stablecoin support. This is where Layer 2 SDKs complement BDK.

A common architecture for full-featured Bitcoin wallets in 2026 uses BDK as the on-chain foundation paired with a Layer 2 SDK for daily spending. The Spark SDK is one such complement: it handles instant transfers, Lightning interoperability, and token operations (including stablecoins like USDB) while BDK manages on-chain savings, cold storage, and UTXO operations.

CapabilityBDKSpark SDK
On-chain address generationYes (all address types)No (L2 only)
UTXO management and coin selectionYes (4 algorithms)Not applicable
Instant transfersNo (requires confirmations)Yes (sub-second)
Lightning paymentsNoYes (native interop)
Token/stablecoin supportNoYes (BTKN standard)
Self-custodyYes (user holds keys)Yes (1-of-n trust model)
LanguageRust, Swift, Kotlin, PythonTypeScript (Node.js, browser, React Native)

This two-layer pattern mirrors how traditional finance applications separate savings accounts from spending accounts. BDK manages the on-chain “vault” where larger balances sit in cold storage, while the Layer 2 SDK handles the “hot wallet” for fast, frequent transactions. Deposits move from L2 to L1 via standard Bitcoin transactions that BDK constructs and signs; withdrawals from L1 to Spark involve depositing into a Spark address.

For developers getting started: the Spark developer documentation provides SDK quickstart guides, API references, and integration examples. For on-chain wallet development, the Book of BDK offers comprehensive tutorials covering key generation, transaction building, and persistence patterns.

Getting Started with BDK

For Rust projects, add bdk_wallet and a chain source crate to your Cargo.toml:

[dependencies]
bdk_wallet = { version = "3.1", features = ["rusqlite"] }
bdk_esplora = { version = "0.21", features = ["blocking"] }

For mobile projects, the bdk-ffi bindings are available as native packages: org.bitcoindevkit:bdk-android on Maven Central for Kotlin, the bdk-swift Swift Package for iOS, and bdkpython on PyPI for Python scripting and server-side use.

The Book of BDK provides step-by-step cookbook recipes for common tasks: creating wallets, generating addresses, building transactions, and implementing persistence. The API reference on docs.rs covers every struct and method in detail. For a broader comparison of available Bitcoin development toolkits, see our guide to building a Bitcoin payment app and the Bitcoin wallet SDK comparison.

This article is for educational purposes only. It does not constitute financial or investment advice. Bitcoin and Layer 2 protocols involve technical and financial risk. Always do your own research and understand the tradeoffs before using any protocol.