Hi — first, thanks for bdk-wasm. We've shipped a fair chunk of a browser-first Bitcoin multisig vault on top of 0.3.0 and it's been excellent, receive-address derivation, full scans via EsploraClient, balance reads, all working cleanly end-to-end.
We've hit a wall building the Send flow and wanted to surface it for any guidance and/or as a feature request.
Use case
Our custody descriptor follows the BIP-389 multipath form Marko Bencun (BitBox lead) walked us through earlier this year for BIP-388 compliance:
wsh(or_i(
and_v(v:pk(@0/<0;1>/*),pk(@1/<0;1>/*)),
or_i(
and_v(v:pk(@0/<2;3>/*),and_v(v:pk(@2/<2;3>/*),older(1008))),
and_v(v:pk(@1/<4;5>/*),and_v(v:pk(@2/<4;5>/*),older(52560)))
)
))
Three spending paths under one wsh policy:
- Path 1: Owner + Cosigner, anytime.
- Path 2: Owner + Inheritance, after a short timelock.
- Path 3: Cosigner + Inheritance, after a long timelock.
Receive-address derivation against this descriptor (via Wallet::create_from_two_path_descriptor + peek_address) works perfectly under bdk-wasm 0.3.0. BitBox02 verifies the addresses byte-for-byte under the same BIP-388 policy registered on-device. So far so good.
The wall
Calling Wallet::build_tx().add_recipient(...).fee_rate(...).finish() against a UTXO at this descriptor throws:
SpendingPolicyRequired { keychain: External }
That's BDK correctly saying "I have three valid or_i branches to satisfy and you need to tell me which one." In the Rust API the fix is:
let mut tx_builder = wallet.build_tx();
let policy_path: BTreeMap<String, Vec<usize>> = /* ... */;
tx_builder.policy_path(policy_path, KeychainKind::External);
TxBuilder::policy_path exists in BDK 1.0 — but it's not exposed via wasm_bindgen. I cross-referenced the methods bound through to JS in src/bitcoin/tx_builder.rs on main and confirmed policy_path isn't in the list. Adjacent methods like change_policy and add_utxo are bound, so the absence looks like a coverage gap rather than a deliberate exclusion.
Why we can't work around it from JS
- A wallet built from just the Path 1 sub-descriptor (
wsh(and_v(v:pk(...),pk(...)))) produces a different scriptPubKey hash, so it can't recognise UTXOs at the original multipath address.
- Pre-selecting UTXOs via
add_utxo doesn't disambiguate the witness-script satisfaction — BDK still throws SpendingPolicyRequired.
- Manually constructing the PSBT outside BDK works but means reimplementing UTXO selection, change derivation, fee math, and witness-script reconstruction. Substantial code surface to maintain.
Ask
Bind TxBuilder::policy_path through to wasm with a JS-friendly signature — likely:
policy_path(policy_path: Record<string, Uint32Array>, keychain: KeychainKind): TxBuilder
(The Rust signature is BTreeMap<String, Vec<usize>>; a JS Record<string, number[]> or Record<string, Uint32Array> should marshal cleanly through serde-wasm-bindgen.)
Also helpful, but secondary: expose Wallet::policies(keychain) so callers can introspect the policy tree to build the policy_path map without hardcoding policy IDs.
Thanks for any assistance! SP
Hi — first, thanks for bdk-wasm. We've shipped a fair chunk of a browser-first Bitcoin multisig vault on top of 0.3.0 and it's been excellent, receive-address derivation, full scans via
EsploraClient, balance reads, all working cleanly end-to-end.We've hit a wall building the Send flow and wanted to surface it for any guidance and/or as a feature request.
Use case
Our custody descriptor follows the BIP-389 multipath form Marko Bencun (BitBox lead) walked us through earlier this year for BIP-388 compliance:
Three spending paths under one wsh policy:
Receive-address derivation against this descriptor (via
Wallet::create_from_two_path_descriptor+peek_address) works perfectly under bdk-wasm 0.3.0. BitBox02 verifies the addresses byte-for-byte under the same BIP-388 policy registered on-device. So far so good.The wall
Calling
Wallet::build_tx().add_recipient(...).fee_rate(...).finish()against a UTXO at this descriptor throws:That's BDK correctly saying "I have three valid
or_ibranches to satisfy and you need to tell me which one." In the Rust API the fix is:TxBuilder::policy_pathexists in BDK 1.0 — but it's not exposed via wasm_bindgen. I cross-referenced the methods bound through to JS insrc/bitcoin/tx_builder.rsonmainand confirmedpolicy_pathisn't in the list. Adjacent methods likechange_policyandadd_utxoare bound, so the absence looks like a coverage gap rather than a deliberate exclusion.Why we can't work around it from JS
wsh(and_v(v:pk(...),pk(...)))) produces a different scriptPubKey hash, so it can't recognise UTXOs at the original multipath address.add_utxodoesn't disambiguate the witness-script satisfaction — BDK still throwsSpendingPolicyRequired.Ask
Bind
TxBuilder::policy_paththrough to wasm with a JS-friendly signature — likely:(The Rust signature is
BTreeMap<String, Vec<usize>>; a JSRecord<string, number[]>orRecord<string, Uint32Array>should marshal cleanly through serde-wasm-bindgen.)Also helpful, but secondary: expose
Wallet::policies(keychain)so callers can introspect the policy tree to build thepolicy_pathmap without hardcoding policy IDs.Thanks for any assistance! SP