From 9c953dc18789eb8f5fbf275a6dd8eb85dfe526c7 Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 16:39:52 +0000 Subject: [PATCH 1/7] chore(deps): upgrade bdk_wallet to 2.3.0 and bdk_esplora to 0.22.1 Upgrades: - bdk_wallet: 2.0.0 -> 2.3.0 - bdk_esplora: 0.22.0 -> 0.22.1 Notable changes in bdk_wallet 2.1.0-2.3.0: - BIP-389 two-path multipath descriptor support - TxBuilder::exclude_unconfirmed and exclude_below_confirmations - Wallet events on apply_update (2.2.0) - apply_block_events and apply_block_connected_to_events (2.3.0) - build_fee_bump fix for missing parent txid (2.3.0) - signer module deprecated (2.2.0) --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 609e648..2b552b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,8 @@ web-sys = { version = "0.3.77", default-features = false, features = [ getrandom = { version = "0.2.16", features = ["js"] } # Bitcoin dependencies -bdk_wallet = { version = "2.0.0" } -bdk_esplora = { version = "0.22.0", default-features = false, features = [ +bdk_wallet = { version = "2.3.0" } +bdk_esplora = { version = "0.22.1", default-features = false, features = [ "async-https", ], optional = true } bitcoin = { version = "0.32.6", default-features = false, features = [ From 23c3e0dc32763217a01ba1332c553ef5d5d7e68a Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 16:40:07 +0000 Subject: [PATCH 2/7] feat: wrap Wallet::create_from_two_path_descriptor (BIP-389) Adds support for creating a wallet from a BIP-389 two-path multipath descriptor. This allows users to specify a single descriptor with both receive and change paths (e.g. wpkh(xpub.../<0;1>/*)) instead of providing two separate descriptors. Wraps BdkWallet::create_from_two_path_descriptor introduced in bdk_wallet 2.1.0. --- src/bitcoin/wallet.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/bitcoin/wallet.rs b/src/bitcoin/wallet.rs index 6c585da..5314de7 100644 --- a/src/bitcoin/wallet.rs +++ b/src/bitcoin/wallet.rs @@ -33,6 +33,22 @@ impl Wallet { Ok(Wallet(Rc::new(RefCell::new(wallet)))) } + /// Create a new [`Wallet`] from a BIP-389 two-path multipath descriptor. + /// + /// The descriptor must contain exactly two derivation paths (receive and change), + /// separated by a semicolon in angle brackets, e.g.: + /// `wpkh([fingerprint/path]xpub.../<0;1>/*)` + /// + /// The first path is used for the external (receive) keychain and the second + /// for the internal (change) keychain. + pub fn create_from_two_path_descriptor(network: Network, descriptor: String) -> JsResult { + let wallet = BdkWallet::create_from_two_path_descriptor(descriptor) + .network(network.into()) + .create_wallet_no_persist()?; + + Ok(Wallet(Rc::new(RefCell::new(wallet)))) + } + pub fn load( changeset: ChangeSet, external_descriptor: Option, From efc859dee790ab4a3521fafb1e4ee2f225b715ff Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 16:40:26 +0000 Subject: [PATCH 3/7] feat: wrap TxBuilder exclude_unconfirmed and exclude_below_confirmations Adds two new methods to TxBuilder for filtering UTXOs by confirmation status: - exclude_below_confirmations(min_confirms): excludes outpoints from transactions with fewer than min_confirms confirmations - exclude_unconfirmed(): shorthand for exclude_below_confirmations(1) These methods mirror the BDK wallet 2.1.0 API and compute the excluded set based on the wallet's current chain tip height. --- src/bitcoin/tx_builder.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/bitcoin/tx_builder.rs b/src/bitcoin/tx_builder.rs index e60852c..7290a53 100644 --- a/src/bitcoin/tx_builder.rs +++ b/src/bitcoin/tx_builder.rs @@ -102,6 +102,38 @@ impl TxBuilder { self } + /// Exclude outpoints whose enclosing transaction has fewer than `min_confirms` + /// confirmations. + /// + /// - Passing `0` will include all transactions (no filtering). + /// - Passing `1` will exclude all unconfirmed transactions (equivalent to + /// [`exclude_unconfirmed`]). + /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations. + pub fn exclude_below_confirmations(mut self, min_confirms: u32) -> Self { + let wallet = self.wallet.borrow(); + let tip_height = wallet.latest_checkpoint().height(); + let to_exclude: Vec = wallet + .list_unspent() + .filter(|utxo| { + utxo.chain_position + .confirmation_height_upper_bound() + .map_or(0, |h| tip_height.saturating_add(1).saturating_sub(h)) + < min_confirms + }) + .map(|utxo| utxo.outpoint.into()) + .collect(); + drop(wallet); + self.unspendable.extend(to_exclude); + self + } + + /// Exclude outpoints whose enclosing transaction is unconfirmed. + /// + /// This is a shorthand for [`exclude_below_confirmations(1)`](Self::exclude_below_confirmations). + pub fn exclude_unconfirmed(self) -> Self { + self.exclude_below_confirmations(1) + } + /// Set whether or not the dust limit is checked. /// /// **Note**: by avoiding a dust limit check you may end up with a transaction that is non-standard. From 47cd31aa0458cc5414316a2d367f1ef5d09647ad Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 18:43:33 +0000 Subject: [PATCH 4/7] fix: suppress deprecated SignOptions warnings with allow(deprecated) bdk_wallet 2.2.0 deprecated the signer module (including SignOptions) in favor of bitcoin::psbt::Psbt::sign(). However, Wallet::sign still requires SignOptions internally, so we add #[allow(deprecated)] until BDK provides a full migration path. Added documentation noting the deprecation status and migration plan. --- src/bitcoin/wallet.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/bitcoin/wallet.rs b/src/bitcoin/wallet.rs index 5314de7..9e26ec9 100644 --- a/src/bitcoin/wallet.rs +++ b/src/bitcoin/wallet.rs @@ -1,6 +1,8 @@ use std::{cell::RefCell, rc::Rc}; -use bdk_wallet::{SignOptions as BdkSignOptions, Wallet as BdkWallet}; +#[allow(deprecated)] +use bdk_wallet::SignOptions as BdkSignOptions; +use bdk_wallet::Wallet as BdkWallet; use wasm_bindgen::{prelude::wasm_bindgen, JsError}; use web_sys::js_sys::Date; @@ -217,9 +219,16 @@ impl Wallet { } } +/// Options for signing a PSBT. +/// +/// Note: `bdk_wallet::SignOptions` is deprecated upstream (BDK 2.2.0) in favor of +/// `bitcoin::psbt::Psbt::sign()`. However, `Wallet::sign` still requires `SignOptions` +/// internally, so we continue wrapping it until BDK provides a migration path. +#[allow(deprecated)] #[wasm_bindgen] pub struct SignOptions(BdkSignOptions); +#[allow(deprecated)] #[wasm_bindgen] impl SignOptions { #[wasm_bindgen(constructor)] @@ -288,12 +297,14 @@ impl SignOptions { } } +#[allow(deprecated)] impl From for BdkSignOptions { fn from(options: SignOptions) -> Self { options.0 } } +#[allow(deprecated)] impl Default for SignOptions { fn default() -> Self { Self::new() From 82461b407c21e6de92eb17f0b8fcb68ecfaa9e38 Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 18:44:24 +0000 Subject: [PATCH 5/7] docs: add CLAUDE.md with agent instructions and project conventions Comprehensive guide covering architecture, build/test instructions, CI pipeline, dependency management, coding conventions, and known issues. Useful for both human contributors and AI agents working on the codebase. --- CLAUDE.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..dfb197d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,129 @@ +# CLAUDE.md - Agent Instructions for bdk-wasm + +## Overview + +WASM bindings for [BDK](https://github.com/bitcoindevkit/bdk_wallet) (Bitcoin Dev Kit). +Wraps `bdk_wallet` for use in browsers and Node.js via `wasm-bindgen`. + +**Used in production by MetaMask Bitcoin Snap (~30M+ AUM). Treat all changes with extreme care.** + +## Architecture + +``` +src/ +├── lib.rs # Crate root, re-exports +├── bitcoin/ # Core wallet functionality wrappers +│ ├── wallet.rs # Wallet (create, load, sign, sync, addresses, UTXOs) +│ ├── tx_builder.rs # Transaction builder +│ ├── esplora_client.rs # Esplora blockchain client (behind `esplora` feature) +│ ├── descriptor.rs # Descriptor utilities +│ └── wallet_tx.rs # Wallet transaction wrapper +├── types/ # WASM-compatible type wrappers (From/Into pattern) +│ ├── address.rs, amount.rs, balance.rs, block.rs, chain.rs, +│ │ changeset.rs, checkpoint.rs, error.rs, fee.rs, input.rs, +│ │ keychain.rs, network.rs, output.rs, psbt.rs, script.rs, +│ │ slip10.rs, transaction.rs +│ └── mod.rs +└── utils/ # Helpers (descriptor utils, panic hook, result type) +``` + +### Pattern + +Every BDK type is wrapped with a WASM-compatible struct that: +1. Holds the inner BDK type +2. Implements `From` and `Into` conversions +3. Exposes methods via `#[wasm_bindgen]` + +`Wallet` uses `Rc>` because `wasm_bindgen` doesn't support Rust lifetimes. +`TxBuilder` shares the wallet reference via `Rc>` and builds its own parameter set, +then calls the real BDK builder in `finish()`. + +## Building + +Requires: Rust stable, `wasm-pack`, `wasm32-unknown-unknown` target. + +```bash +# Browser target (default) +wasm-pack build --all-features + +# Node.js target +wasm-pack build --target nodejs --all-features + +# Specific features +wasm-pack build --features esplora +wasm-pack build --features debug,esplora +``` + +## Testing + +### Browser tests (Rust) +```bash +wasm-pack test --chrome --firefox --headless --features debug,default +wasm-pack test --chrome --firefox --headless --features debug,esplora +``` + +### Node.js tests (TypeScript/Jest) +```bash +cd tests/node +yarn install --immutable +yarn build # runs wasm-pack build --target nodejs --all-features +yarn test # runs jest +yarn lint # runs eslint +``` + +Node tests are in `tests/node/integration/`: +- `wallet.test.ts` — Wallet creation, addresses, descriptors +- `esplora.test.ts` — Esplora sync, full scan, transaction sending (uses **Mutinynet signet**) +- `utilities.test.ts` — Amount, Script, Address utilities +- `errors.test.ts` — Error handling and error codes + +**Note:** `esplora.test.ts` depends on Mutinynet signet (`https://mutinynet.com/api`) with a +pre-funded test wallet. This test can be flaky if the faucet/signet is down. + +### CI + +GitHub Actions runs on every PR: +- **Lint:** `cargo fmt --check` + `cargo clippy --all-features --all-targets -- -D warnings` +- **Browser build:** Three matrix configs (all features, debug+default, debug+esplora) +- **Node build + test:** Full wasm-pack build + Jest test suite + +CI must be green before merging. Clippy treats warnings as errors (`-D warnings`). + +## Features + +- `default` — Core wallet functionality only +- `esplora` — Adds `EsploraClient` for blockchain sync (enables `bdk_esplora` + `wasm-bindgen-futures`) +- `debug` — Enables `console_error_panic_hook` for better WASM error messages + +## Dependencies + +Key dependencies (keep these in sync): +- `bdk_wallet` — Core wallet library +- `bdk_esplora` — Esplora client (must match `bdk_wallet` version series) +- `bitcoin` — Bitcoin primitives +- `wasm-bindgen` — Rust/JS interop + +Check https://crates.io/crates/bdk_wallet/versions for latest releases. +BDK uses a monorepo-ish approach: `bdk_wallet` and `bdk_esplora` versions must be compatible. + +## Conventions + +- **Conventional commits:** `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:` +- **Formatting:** `cargo fmt` with default settings +- **All public items must be documented** +- **Safe Rust only** — no `unsafe` without exceptional justification +- **New features require tests** + +## Known Issues + +- `SignOptions` is deprecated in BDK 2.2.0+ (signer module moved to `bitcoin::psbt`). + We use `#[allow(deprecated)]` until BDK provides a migration path, since `Wallet::sign` + still requires it internally. +- Esplora integration tests use Mutinynet signet which can be flaky. + +## Maintenance Notes + +- This repo is maintained by an AI agent (Toshi) with human review by @darioAnongba +- All changes go through PRs — never push to main directly +- One PR at a time to keep review manageable +- Check BDK releases periodically for new APIs to wrap From 621e715cef0e1dffaeb01999983b2a10975dcee2 Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 19:30:23 +0000 Subject: [PATCH 6/7] docs: add conventional commit types reference to CLAUDE.md --- CLAUDE.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index dfb197d..9874fe7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,7 +108,18 @@ BDK uses a monorepo-ish approach: `bdk_wallet` and `bdk_esplora` versions must b ## Conventions -- **Conventional commits:** `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:` +- **Conventional commits** (required for all commits and PR titles): + - `feat:` — New feature or API wrapper + - `fix:` — Bug fix + - `refactor:` — Code restructuring without behavior change + - `docs:` — Documentation only + - `test:` — Adding or updating tests + - `chore:` — Maintenance (deps, config, tooling) + - `ci:` — CI/CD pipeline changes + - `build:` — Build system changes + - Scope is optional but encouraged: `feat(wallet):`, `fix(tx_builder):`, `chore(deps):` + - Breaking changes: add `!` after type, e.g. `feat!:` or `feat(wallet)!:` + - These prefixes feed into CHANGELOG.md generation - **Formatting:** `cargo fmt` with default settings - **All public items must be documented** - **Safe Rust only** — no `unsafe` without exceptional justification From c0cfcb91df2a5abca9dc815ed7622b7c17e2d636 Mon Sep 17 00:00:00 2001 From: Toshi Date: Tue, 24 Feb 2026 20:09:29 +0000 Subject: [PATCH 7/7] fix(tx_builder): delegate exclude_below_confirmations to BDK builder Instead of reimplementing the UTXO filtering logic locally, store the min_confirmations parameter and pass it to BDK's TxBuilder in finish(). This follows the same delegation pattern used by all other builder methods. --- src/bitcoin/tx_builder.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/bitcoin/tx_builder.rs b/src/bitcoin/tx_builder.rs index 7290a53..c7d29ea 100644 --- a/src/bitcoin/tx_builder.rs +++ b/src/bitcoin/tx_builder.rs @@ -23,6 +23,7 @@ pub struct TxBuilder { drain_to: Option, allow_dust: bool, ordering: TxOrdering, + min_confirmations: Option, } #[wasm_bindgen] @@ -38,6 +39,7 @@ impl TxBuilder { allow_dust: false, drain_to: None, ordering: BdkTxOrdering::default().into(), + min_confirmations: None, } } @@ -110,20 +112,7 @@ impl TxBuilder { /// [`exclude_unconfirmed`]). /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations. pub fn exclude_below_confirmations(mut self, min_confirms: u32) -> Self { - let wallet = self.wallet.borrow(); - let tip_height = wallet.latest_checkpoint().height(); - let to_exclude: Vec = wallet - .list_unspent() - .filter(|utxo| { - utxo.chain_position - .confirmation_height_upper_bound() - .map_or(0, |h| tip_height.saturating_add(1).saturating_sub(h)) - < min_confirms - }) - .map(|utxo| utxo.outpoint.into()) - .collect(); - drop(wallet); - self.unspendable.extend(to_exclude); + self.min_confirmations = Some(min_confirms); self } @@ -162,6 +151,10 @@ impl TxBuilder { .fee_rate(self.fee_rate.into()) .allow_dust(self.allow_dust); + if let Some(min_confirms) = self.min_confirmations { + builder.exclude_below_confirmations(min_confirms); + } + if self.drain_wallet { builder.drain_wallet(); }