Bitcoin Wallet SDKs Compared: BDK, LDK, Spark SDK, and More
Technical comparison of Bitcoin wallet SDKs for developers: features, languages, and implementation tradeoffs.
Building a Bitcoin wallet from scratch is a serious undertaking. Descriptor parsing, coin selection, PSBT construction, fee estimation, chain synchronization: each of these is a deep problem on its own. Wallet SDKs exist to encapsulate this complexity so application developers can focus on product rather than protocol internals.
The landscape has matured significantly. Developers now choose between on-chain toolkits like BDK, Lightning-native libraries like LDK, LSP-wrapped solutions like Breez SDK, and Layer 2 SDKs like the Spark SDK. Each targets a different layer of the stack and makes different tradeoffs around control, complexity, and capability. This article walks through the major options, compares them side by side, and discusses when each is the right fit.
BDK: The On-Chain Foundation
Bitcoin Dev Kit (BDK) is a Rust library for building on-chain Bitcoin wallets. Maintained by a dedicated open-source team with funding from Spiral (Block), BDK focuses on descriptor-based wallet construction: you define spending policies using Miniscript descriptors, and BDK handles everything else.
Core architecture
BDK operates around a Wallet struct that tracks UTXOs, applies coin selection algorithms, and builds transactions. The library is modular: you plug in your own blockchain backend (Electrum, Esplora, or Bitcoin Core RPC), your own persistence layer (SQLite, flat file, or in-memory), and your own signer (software keys, hardware wallets, or remote signing services).
A typical BDK integration for an on-chain wallet looks like this:
use bdk_wallet::Wallet;
use bdk_wallet::bitcoin::Network;
use bdk_esplora::EsploraClient;
// Define wallet with descriptors
let descriptor = "wpkh(tprv.../84'/1'/0'/0/*)";
let change_descriptor = "wpkh(tprv.../84'/1'/0'/1/*)";
let mut wallet = Wallet::create(descriptor, change_descriptor)
.network(Network::Testnet)
.create_wallet_no_persist()?;
// Sync with Esplora backend
let client = EsploraClient::new("https://mempool.space/testnet/api");
let request = wallet.start_sync_with_revealed_spks();
let update = client.sync(request, 5).await?;
wallet.apply_update(update)?;
// Build a transaction
let mut tx_builder = wallet.build_tx();
tx_builder
.add_recipient(address.script_pubkey(), Amount::from_sat(50_000))
.fee_rate(FeeRate::from_sat_per_vb(2).unwrap());
let mut psbt = tx_builder.finish()?;
// Sign and broadcast
wallet.sign(&mut psbt, SignOptions::default())?;
let tx = psbt.extract_tx()?;
client.broadcast(&tx).await?;Language bindings
BDK provides FFI bindings through bdk-ffi using Mozilla's UniFFI. This gives you Swift (iOS), Kotlin (Android), and Python bindings generated from the Rust core. The bindings cover the main wallet functionality but may lag behind the Rust API for newer features.
What BDK does well
- Full control over HD wallet derivation, derivation paths, and descriptor types
- Supports complex spending policies through Miniscript
- Pluggable backends: no lock-in to a specific indexer or node
- Mature coin selection algorithms (Branch and Bound, Oldest First, Largest First)
- Built-in support for Taproot, SegWit, and legacy address types
What BDK does not do
BDK is an on-chain toolkit. It has no Lightning support, no off-chain payment channels, and no Layer 2 integration. If your application needs instant payments, you need to combine BDK with another SDK or build additional infrastructure. BDK also requires you to manage your own blockchain backend: there is no hosted service out of the box.
LDK: Lightning Without the Node
Lightning Dev Kit (LDK) is a Rust library for building Lightning-capable applications. Unlike full Lightning implementations (LND, CLN, Eclair) that ship as standalone daemons, LDK is a library you embed directly into your application. This gives you fine-grained control over every aspect of Lightning channel management.
Core architecture
LDK decomposes Lightning into discrete components: a ChannelManager for channel state, a PeerManager for network connections, a Router for pathfinding, and event handlers you implement to react to channel opens, payment receipts, and forwarding requests. You bring your own on-chain wallet (often BDK), your own key storage, and your own persistence backend.
use lightning::ln::channelmanager::ChannelManager;
use lightning::ln::peer_handler::PeerManager;
use lightning::routing::gossip::NetworkGraph;
// Initialize components
let channel_manager = ChannelManager::new(
fee_estimator,
chain_monitor,
broadcaster,
router,
logger,
keys_manager,
user_config,
chain_params,
current_timestamp,
);
// Open a channel with a peer
channel_manager.create_channel(
peer_pubkey,
channel_value_satoshis,
push_msat,
user_channel_id,
None,
override_config,
)?;
// Send a payment
let route = router.find_route(
&our_pubkey,
&route_params,
&network_graph,
None,
logger,
&scorer,
&Default::default(),
&random_bytes,
)?;
channel_manager.send_payment_with_route(
&route, payment_hash, recipient_onion, payment_id
)?;Language bindings
LDK provides bindings for Swift, Kotlin, and Node.js through its ldk-node project, which wraps the low-level Rust library in a simpler, opinionated API. The ldk-node wrapper handles peer connections, chain sync, and persistence internally, reducing the amount of plumbing you need to write.
What LDK does well
- Full control over channel policies, routing, and HTLC handling
- No daemon dependency: runs entirely in-process
- Supports advanced features like anchor outputs, keysend, and BOLT12 offers
- Active maintenance by Spiral with regular releases
- Can be paired with BDK for on-chain wallet functionality
What LDK requires from you
LDK gives you the building blocks, not a finished product. You are responsible for inbound liquidity, channel management, peer discovery, and liquidity planning. For mobile wallets, this means integrating with a Lightning Service Provider or running your own infrastructure for channel opens and liquidity injection. The surface area of a production LDK integration is substantial.
Integration complexity: A minimal LDK wallet requires implementing roughly 10 trait interfaces covering chain monitoring, fee estimation, key management, transaction broadcasting, event persistence, and logging. This is intentional: LDK prioritizes flexibility over convenience.
Breez SDK: LSP-Wrapped Lightning
Breez SDK takes a different approach by wrapping Lightning complexity behind a higher-level API. Rather than exposing channel management directly, the SDK connects to Breez's LSP infrastructure, which handles channel opens, liquidity provision, and routing on behalf of your application.
Core architecture
Breez SDK comes in two variants: Breez SDK (Greenlight), built on Core Lightning's Greenlight service, and Breez SDK (Liquid), built on the Liquid sidechain for faster onboarding. Both abstract away channel management while preserving self-custody of funds.
import breez_sdk
// Connect to Breez service
let config = breez_sdk.default_config(
EnvironmentType.PRODUCTION,
api_key,
breez_sdk.NodeConfig.greenlight(
breez_sdk.GreenlightNodeConfig(None, invite_code)
)
)
let sdk = try breez_sdk.connect(config: config, seed: seed)
// Receive a payment (LSP handles channel)
let invoice = try sdk.receivePayment(
amountMsat: 100_000_000,
description: "Payment for order #123"
)
// Send a payment
try sdk.sendPayment(bolt11: invoice_string, amountMsat: nil)Language bindings
Breez SDK provides bindings for Kotlin, Swift, Dart (Flutter), Python, Go, C#, and React Native. The breadth of language support makes it one of the most accessible options for cross-platform mobile development.
What Breez SDK does well
- Dramatically simpler API compared to raw LDK
- LSP handles channel capacity and inbound liquidity automatically
- Self-custodial: users hold their own keys
- Broad language and platform support
- Supports submarine swaps for on-chain/off-chain interop
What Breez SDK constrains
Breez SDK ties you to the Breez LSP. You get less control over channel policies, routing preferences, and fee structures. The LSP charges fees for channel opens and liquidity provision. If Breez infrastructure is unavailable, your users cannot send or receive until service is restored (though on-chain exits remain possible). The Greenlight variant also depends on Blockstream infrastructure for the backing CLN node.
Spark SDK: Layer 2 Without Channels
The Spark SDK takes a fundamentally different approach. Spark is a Bitcoin Layer 2 built on statechains, where transfers happen by rotating key shares rather than routing payments through channels. There are no channels to open, no liquidity to manage, and no routing to configure.
Core architecture
The SDK provides a SparkWallet class that handles wallet initialization, balance queries, transfers, Lightning interop, and token operations. Under the hood, the SDK manages key material, communicates with Spark operators, and handles the FROST signature coordination required for transfers.
import { SparkWallet } from "@buildonspark/spark-sdk";
// Initialize a wallet
const { wallet, mnemonic } = await SparkWallet.initialize({
options: { network: "MAINNET" }
});
// Get a Spark address
const address = await wallet.getSparkAddress();
// Check balances (BTC + tokens)
const { balance, tokenBalances } = await wallet.getBalance();
console.log(`BTC: ${balance} sats`);
// Send a transfer (no channels, no routing)
await wallet.transfer({
receiverSparkAddress: recipientAddress,
amountSats: 50000,
});
// Deposit from on-chain (0-conf support)
const depositAddress = await wallet.getSingleUseDepositAddress();
// Send BTC to this address; credited instantlyLanguage support
The primary Spark SDK is TypeScript, available as @buildonspark/spark-sdk on npm. It supports Node.js, browser, and React Native environments. For mobile-native development, Breez offers Spark support through their SDK with bindings for Swift, Kotlin, Flutter, and other languages.
What Spark SDK does well
- Minimal integration surface: wallet init, send, receive, check balance
- No channel or liquidity management: transfers use key rotation, not payment routing
- Built-in token support: stablecoins and custom tokens alongside BTC
- Zero-conf deposits: on-chain deposits are credited instantly
- Lightning interop: send and receive Lightning payments from a Spark balance
- Self-custodial: users hold their own seed phrase and key material
What Spark SDK constrains
Spark is a Layer 2 with a 1-of-n trust model: you trust that at least one operator behaves honestly during each transfer. You do not get the fully trustless guarantees of on-chain Bitcoin or Lightning. The SDK does not give you low-level UTXO control or the ability to construct arbitrary Bitcoin transactions. If your application requires direct on-chain operations or complex spending policies, you need BDK alongside or instead of the Spark SDK.
SDK Comparison: Features and Tradeoffs
The following table compares the four SDKs across the dimensions that matter most for wallet developers.
| Feature | BDK | LDK | Breez SDK | Spark SDK |
|---|---|---|---|---|
| Primary layer | On-chain (L1) | Lightning (L2) | Lightning (L2) | Spark (L2) |
| Core language | Rust | Rust | Rust | TypeScript |
| Mobile bindings | Swift, Kotlin | Swift, Kotlin (via ldk-node) | Swift, Kotlin, Dart, React Native | React Native, with Breez bindings for native |
| Channel management | N/A | Full manual control | Automated via LSP | Not needed |
| Liquidity planning | N/A | Required | Handled by LSP | Not needed |
| Instant payments | No (on-chain only) | Yes | Yes | Yes |
| Trust model | Trustless (on-chain) | Trustless (channels) | Trustless (LSP is non-custodial) | 1-of-n operators |
| Token support | No | No | No | Yes (BTKN standard) |
| Stablecoin support | No | No | No | Yes (USDB, etc.) |
| Offline receive | Yes (on-chain) | No | No | Yes |
| PSBT support | Yes | Partial | No | No |
| Miniscript/descriptors | Yes | No | No | No |
| License | Apache 2.0 / MIT | Apache 2.0 / MIT | MIT | Apache 2.0 |
Integration Effort Compared
One of the most practical questions developers face: how much code does it take to go from zero to a working wallet? The following table estimates the effort required for common wallet operations across SDKs.
| Operation | BDK | LDK | Breez SDK | Spark SDK |
|---|---|---|---|---|
| Wallet initialization | ~30 lines (descriptor setup, backend config) | ~100+ lines (channel manager, peer manager, chain monitor) | ~10 lines (config + connect) | ~5 lines (initialize with options) |
| Send payment | ~15 lines (build tx, sign, broadcast) | ~20 lines (route finding, payment dispatch) | ~3 lines (parse invoice, send) | ~5 lines (transfer to address) |
| Receive payment | ~5 lines (generate address) | ~10 lines (create invoice, handle events) | ~5 lines (create invoice) | ~3 lines (get address) |
| Infrastructure required | Electrum/Esplora server | Bitcoin full node, Electrum, gossip data | Breez API key | None (public operators) |
| Ongoing maintenance | Backend uptime, fee estimation tuning | Channel rebalancing, peer management, watchtowers | LSP fee monitoring, SDK updates | SDK updates |
The complexity gradient: These SDKs represent a spectrum from maximum control (BDK + LDK) to maximum simplicity (Spark SDK). The right choice depends on what your application needs to control directly and what it can delegate.
Implementing the Same Feature: Balance Check
To illustrate the practical differences, here is how you check a wallet balance with each SDK. This is one of the simplest operations, but it reveals how each SDK structures developer interaction.
BDK: on-chain balance
// Requires prior sync with blockchain backend
let balance = wallet.balance();
println!("Confirmed: {} sats", balance.confirmed);
println!("Unconfirmed: {} sats", balance.untrusted_pending);
println!("Immature: {} sats", balance.immature);BDK separates confirmed, unconfirmed, and immature (coinbase) balances. You must sync with a blockchain backend before querying: the balance reflects the last-synced chain state, not real-time data.
LDK: channel balances
let channels = channel_manager.list_channels();
let mut total_balance_msat: u64 = 0;
for channel in &channels {
total_balance_msat += channel.balance_msat;
}
println!("Lightning balance: {} msat", total_balance_msat);
// Also need on-chain balance from a separate wallet (e.g., BDK)
let onchain_balance = bdk_wallet.balance();LDK tracks Lightning balances separately from on-chain funds. You iterate over channels to sum up your Lightning balance in millisatoshis and query a separate on-chain wallet for L1 funds.
Breez SDK: unified balance
let node_info = sdk.node_info()?
println!("Balance: {} msat", node_info.channels_balance_msat)
println!("On-chain: {} sats", node_info.onchain_balance_sat)Breez SDK presents a simpler view with Lightning and on-chain balances available from a single call.
Spark SDK: multi-asset balance
const { balance, tokenBalances } = await wallet.getBalance();
console.log(`BTC: ${balance} sats`);
// Token balances are also available
for (const [token, amount] of tokenBalances) {
console.log(`${token}: ${amount}`);
}The Spark SDK returns BTC and token balances in a single call. There is no distinction between confirmed and unconfirmed: Spark transfers settle instantly, and zero-conf deposits are credited immediately.
When to Use Which SDK
There is no single best SDK. The right choice depends on what you are building.
Use BDK when
- You are building an on-chain wallet with custom spending policies or cold storage features
- You need fine-grained control over coin selection, fee bumping via replace-by-fee, or CPFP
- Your application is a signing device, hardware wallet interface, or multi-party coordinator
- You want to support complex policies: multisig, timelocked vaults, or inheritance schemes
Use LDK when
- You are building a Lightning node, routing node, or LSP
- You need custom channel policies, fee structures, or pathfinding logic
- You are building infrastructure (payment processor, exchange) that requires full control over Lightning operations
- You have a team with deep Lightning expertise and can maintain the integration
Use Breez SDK when
- You want Lightning payments without managing channels yourself
- You are building a mobile wallet and need broad platform support
- You want self-custodial Lightning with minimal infrastructure
- Your team lacks deep Lightning protocol expertise but needs Lightning payments
Use Spark SDK when
- You are building a payment application where speed of integration matters
- You need both BTC and stablecoin support in a single SDK
- Your users should not need to understand channels, liquidity, or routing
- You want instant settlement without any infrastructure setup
- You are building a fintech product that embeds Bitcoin payments as a feature
Combining SDKs
These SDKs are not mutually exclusive. Several production wallets combine multiple SDKs to cover different use cases within a single application.
A common pattern is BDK for on-chain savings and cold storage combined with a Layer 2 SDK for daily spending. The on-chain wallet serves as the long-term store of value with full self-custody guarantees, while the Layer 2 wallet handles instant, low-cost transfers for everyday use. This mirrors how traditional banking separates savings accounts from checking accounts.
Another pattern: LDK for a routing node backend combined with Spark SDK for end-user wallets. The LDK node earns routing fees and provides liquidity to the network, while the Spark-based wallet gives end users a simpler experience.
Practical tip: Start with the highest-level SDK that meets your requirements. If you find yourself fighting the abstraction (needing control the SDK does not expose), drop down to a lower-level option. Moving from Spark SDK to BDK+LDK is a significant rewrite; validating product-market fit with a simpler integration first often saves months of engineering.
The Maintenance Dimension
Initial integration effort is only part of the story. Ongoing maintenance costs vary dramatically across SDKs and deserve consideration during technology selection.
BDK maintenance
On-chain wallets built with BDK require maintaining a blockchain indexing backend (Electrum or Esplora), keeping fee estimation calibrated, and handling chain reorganizations. The library itself is stable, with breaking changes concentrated in major version bumps. UTXO management (avoiding dust, optimizing consolidation) adds ongoing operational considerations.
LDK maintenance
Lightning is operationally demanding. Payment channels require active management: rebalancing, monitoring for force closes, managing channel reserves, and ensuring your node stays connected to well-routing peers. You need watchtower infrastructure to protect against fraud while offline. Gossip data must stay current for effective routing. The protocol itself continues to evolve rapidly, requiring regular upgrades.
Breez SDK maintenance
Breez offloads most Lightning operational complexity to the LSP. Your maintenance is primarily SDK version upgrades and monitoring LSP fee changes. The tradeoff: you depend on Breez infrastructure availability and their fee schedule.
Spark SDK maintenance
The Spark SDK has the lightest maintenance footprint. There are no channels to rebalance, no liquidity to manage, no peers to maintain, and no backend infrastructure to run. Maintenance consists of SDK version updates and monitoring operator status.
Future Directions
The Bitcoin wallet SDK ecosystem continues to evolve. BDK is working toward its 1.0 stable release with a refined API. LDK is adding BOLT12 support and improving its mobile experience through ldk-node. Breez is expanding its SDK to support additional Layer 2 protocols alongside Lightning.
The Spark SDK is expanding language support and adding features like built-in swap functionality between BTC and tokens. As more wallets integrate Spark, the developer tooling around the protocol continues to mature.
A broader trend worth watching: convergence. As Layer 2 protocols become interoperable, the distinction between Lightning wallets and other Layer 2 wallets becomes less visible to end users. SDKs that abstract across multiple protocols (letting developers support Lightning, Spark, and on-chain from a single integration) will likely become more common.
Conclusion
Choosing a Bitcoin wallet SDK is an architectural decision that shapes your application for years. BDK gives you surgical control over on-chain operations. LDK gives you the same for Lightning. Breez SDK wraps Lightning in a practical, LSP-backed package. The Spark SDK offers the shortest path to instant Bitcoin payments with built-in token support.
For teams building payment-focused applications where time-to-market matters, starting with a higher-level SDK like Spark and adding on-chain capabilities through BDK as needed is a practical strategy. For teams building infrastructure or needing protocol-level control, BDK and LDK remain the gold standard.
The best wallet SDK is the one that matches your application's actual requirements: not the one with the most features, but the one whose tradeoffs align with what you are building.
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.

