]> Untitled Git - bdk/commitdiff
chore: rename bdk crate to bdk_wallet
authorSteve Myers <steve@notmandatory.org>
Tue, 6 Feb 2024 14:56:31 +0000 (08:56 -0600)
committerSteve Myers <steve@notmandatory.org>
Mon, 13 May 2024 17:10:58 +0000 (12:10 -0500)
69 files changed:
.github/workflows/cont_integration.yml
Cargo.toml
README.md
crates/bdk/Cargo.toml [deleted file]
crates/bdk/README.md [deleted file]
crates/bdk/examples/compiler.rs [deleted file]
crates/bdk/examples/mnemonic_to_descriptors.rs [deleted file]
crates/bdk/examples/policy.rs [deleted file]
crates/bdk/src/descriptor/checksum.rs [deleted file]
crates/bdk/src/descriptor/dsl.rs [deleted file]
crates/bdk/src/descriptor/error.rs [deleted file]
crates/bdk/src/descriptor/mod.rs [deleted file]
crates/bdk/src/descriptor/policy.rs [deleted file]
crates/bdk/src/descriptor/template.rs [deleted file]
crates/bdk/src/keys/bip39.rs [deleted file]
crates/bdk/src/keys/mod.rs [deleted file]
crates/bdk/src/lib.rs [deleted file]
crates/bdk/src/psbt/mod.rs [deleted file]
crates/bdk/src/types.rs [deleted file]
crates/bdk/src/wallet/coin_selection.rs [deleted file]
crates/bdk/src/wallet/error.rs [deleted file]
crates/bdk/src/wallet/export.rs [deleted file]
crates/bdk/src/wallet/hardwaresigner.rs [deleted file]
crates/bdk/src/wallet/mod.rs [deleted file]
crates/bdk/src/wallet/signer.rs [deleted file]
crates/bdk/src/wallet/tx_builder.rs [deleted file]
crates/bdk/src/wallet/utils.rs [deleted file]
crates/bdk/tests/common.rs [deleted file]
crates/bdk/tests/psbt.rs [deleted file]
crates/bdk/tests/wallet.rs [deleted file]
crates/hwi/Cargo.toml
crates/hwi/src/lib.rs
crates/hwi/src/signer.rs
crates/wallet/Cargo.toml [new file with mode: 0644]
crates/wallet/README.md [new file with mode: 0644]
crates/wallet/examples/compiler.rs [new file with mode: 0644]
crates/wallet/examples/mnemonic_to_descriptors.rs [new file with mode: 0644]
crates/wallet/examples/policy.rs [new file with mode: 0644]
crates/wallet/src/descriptor/checksum.rs [new file with mode: 0644]
crates/wallet/src/descriptor/dsl.rs [new file with mode: 0644]
crates/wallet/src/descriptor/error.rs [new file with mode: 0644]
crates/wallet/src/descriptor/mod.rs [new file with mode: 0644]
crates/wallet/src/descriptor/policy.rs [new file with mode: 0644]
crates/wallet/src/descriptor/template.rs [new file with mode: 0644]
crates/wallet/src/keys/bip39.rs [new file with mode: 0644]
crates/wallet/src/keys/mod.rs [new file with mode: 0644]
crates/wallet/src/lib.rs [new file with mode: 0644]
crates/wallet/src/psbt/mod.rs [new file with mode: 0644]
crates/wallet/src/types.rs [new file with mode: 0644]
crates/wallet/src/wallet/coin_selection.rs [new file with mode: 0644]
crates/wallet/src/wallet/error.rs [new file with mode: 0644]
crates/wallet/src/wallet/export.rs [new file with mode: 0644]
crates/wallet/src/wallet/hardwaresigner.rs [new file with mode: 0644]
crates/wallet/src/wallet/mod.rs [new file with mode: 0644]
crates/wallet/src/wallet/signer.rs [new file with mode: 0644]
crates/wallet/src/wallet/tx_builder.rs [new file with mode: 0644]
crates/wallet/src/wallet/utils.rs [new file with mode: 0644]
crates/wallet/tests/common.rs [new file with mode: 0644]
crates/wallet/tests/psbt.rs [new file with mode: 0644]
crates/wallet/tests/wallet.rs [new file with mode: 0644]
example-crates/wallet_electrum/Cargo.toml
example-crates/wallet_electrum/src/main.rs
example-crates/wallet_esplora_async/Cargo.toml
example-crates/wallet_esplora_async/src/main.rs
example-crates/wallet_esplora_blocking/Cargo.toml
example-crates/wallet_esplora_blocking/src/main.rs
example-crates/wallet_rpc/Cargo.toml
example-crates/wallet_rpc/README.md
example-crates/wallet_rpc/src/main.rs

index 1233d7e1729edcb1c868bac5bd0cf40ce4d6c993..7507478ea4d5e24c0264f1c17bca070c293c3ff1 100644 (file)
@@ -58,8 +58,8 @@ jobs:
         working-directory: ./crates/chain
         # TODO "--target thumbv6m-none-eabi" should work but currently does not
         run: cargo check --no-default-features --features bitcoin/no-std,miniscript/no-std,hashbrown
-      - name: Check bdk
-        working-directory: ./crates/bdk
+      - name: Check bdk wallet
+        working-directory: ./crates/wallet
         # TODO "--target thumbv6m-none-eabi" should work but currently does not
         run: cargo check --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown
       - name: Check esplora
@@ -89,8 +89,8 @@ jobs:
             target: "wasm32-unknown-unknown"
       - name: Rust Cache
         uses: Swatinem/rust-cache@v2.2.1
-      - name: Check bdk
-        working-directory: ./crates/bdk
+      - name: Check bdk wallet
+        working-directory: ./crates/wallet
         run: cargo check --target wasm32-unknown-unknown --no-default-features --features bitcoin/no-std,miniscript/no-std,bdk_chain/hashbrown,dev-getrandom-wasm
       - name: Check esplora
         working-directory: ./crates/esplora
index 87428029af21d937db649b82e7fa98e5260ed4f2..7ecc7094c5ab286a7d066cf0c6df58adcfe24168 100644 (file)
@@ -1,7 +1,7 @@
 [workspace]
 resolver = "2"
 members = [
-    "crates/bdk",
+    "crates/wallet",
     "crates/chain",
     "crates/file_store",
     "crates/electrum",
index 030ec2a4e2ea476a5c18f6d1efd61986ac0c5106..f2e0660f5b49edd4a9aaa5fb2fd77c752fadcd94 100644 (file)
--- a/README.md
+++ b/README.md
   </p>
 
   <p>
-    <a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a>
+    <a href="https://crates.io/crates/bdk_wallet"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk_wallet.svg"/></a>
     <a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
     <a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
     <a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
-    <a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
+    <a href="https://docs.rs/bdk_wallet"><img alt="Wallet API Docs" src="https://img.shields.io/badge/docs.rs-bdk_wallet-green"/></a>
     <a href="https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html"><img alt="Rustc Version 1.63.0+" src="https://img.shields.io/badge/rustc-1.63.0%2B-lightgrey.svg"/></a>
     <a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
   </p>
@@ -22,7 +22,7 @@
   <h4>
     <a href="https://bitcoindevkit.org">Project Homepage</a>
     <span> | </span>
-    <a href="https://docs.rs/bdk">Documentation</a>
+    <a href="https://docs.rs/bdk_wallet">Documentation</a>
   </h4>
 </div>
 
@@ -39,7 +39,7 @@ It is built upon the excellent [`rust-bitcoin`] and [`rust-miniscript`] crates.
 
 The project is split up into several crates in the `/crates` directory:
 
-- [`bdk`](./crates/bdk): Contains the central high level `Wallet` type that is built from the low-level mechanisms provided by the other components
+- [`wallet`](./crates/wallet): Contains the central high level `Wallet` type that is built from the low-level mechanisms provided by the other components
 - [`chain`](./crates/chain): Tools for storing and indexing chain data
 - [`persist`](./crates/persist): Types that define data persistence of a BDK wallet
 - [`file_store`](./crates/file_store): A (experimental) persistence backend for storing chain data in a single file.
@@ -47,10 +47,10 @@ The project is split up into several crates in the `/crates` directory:
 - [`electrum`](./crates/electrum): Extends the [`electrum-client`] crate with methods to fetch chain data from an electrum server in the form that [`bdk_chain`] and `Wallet` can consume.
 
 Fully working examples of how to use these components are in `/example-crates`:
-- [`example_cli`](./example-crates/example_cli): Library used by the `example_*` crates. Provides utilities for syncing, showing the balance, generating addresses and creating transactions without using the bdk `Wallet`.
-- [`example_electrum`](./example-crates/example_electrum): A command line Bitcoin wallet application built on top of `example_cli` and the `electrum` crate. It shows the power of the bdk tools (`chain` + `file_store` + `electrum`), without depending on the main `bdk` library.
-- [`example_esplora`](./example-crates/example_esplora): A command line Bitcoin wallet application built on top of `example_cli` and the `esplora` crate. It shows the power of the bdk tools (`chain` + `file_store` + `esplora`), without depending on the main `bdk` library.
-- [`example_bitcoind_rpc_polling`](./example-crates/example_bitcoind_rpc_polling): A command line Bitcoin wallet application built on top of `example_cli` and the `bitcoind_rpc` crate. It shows the power of the bdk tools (`chain` + `file_store` + `bitcoind_rpc`), without depending on the main `bdk` library.
+- [`example_cli`](./example-crates/example_cli): Library used by the `example_*` crates. Provides utilities for syncing, showing the balance, generating addresses and creating transactions without using the bdk_wallet `Wallet`.
+- [`example_electrum`](./example-crates/example_electrum): A command line Bitcoin wallet application built on top of `example_cli` and the `electrum` crate. It shows the power of the bdk tools (`chain` + `file_store` + `electrum`), without depending on the main `bdk_wallet` library.
+- [`example_esplora`](./example-crates/example_esplora): A command line Bitcoin wallet application built on top of `example_cli` and the `esplora` crate. It shows the power of the bdk tools (`chain` + `file_store` + `esplora`), without depending on the main `bdk_wallet` library.
+- [`example_bitcoind_rpc_polling`](./example-crates/example_bitcoind_rpc_polling): A command line Bitcoin wallet application built on top of `example_cli` and the `bitcoind_rpc` crate. It shows the power of the bdk tools (`chain` + `file_store` + `bitcoind_rpc`), without depending on the main `bdk_wallet` library.
 - [`wallet_esplora_blocking`](./example-crates/wallet_esplora_blocking): Uses the `Wallet` to sync and spend using the Esplora blocking interface.
 - [`wallet_esplora_async`](./example-crates/wallet_esplora_async): Uses the `Wallet` to sync and spend using the Esplora asynchronous interface.
 - [`wallet_electrum`](./example-crates/wallet_electrum): Uses the `Wallet` to sync and spend using Electrum.
diff --git a/crates/bdk/Cargo.toml b/crates/bdk/Cargo.toml
deleted file mode 100644 (file)
index 140ab26..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-[package]
-name = "bdk"
-homepage = "https://bitcoindevkit.org"
-version = "1.0.0-alpha.11"
-repository = "https://github.com/bitcoindevkit/bdk"
-documentation = "https://docs.rs/bdk"
-description = "A modern, lightweight, descriptor-based wallet library"
-keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
-readme = "README.md"
-license = "MIT OR Apache-2.0"
-authors = ["Bitcoin Dev Kit Developers"]
-edition = "2021"
-rust-version = "1.63"
-
-[dependencies]
-anyhow = { version = "1", default-features = false }
-rand = "^0.8"
-miniscript = { version = "11.0.0", features = ["serde"], default-features = false }
-bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false }
-serde = { version = "^1.0", features = ["derive"] }
-serde_json = { version = "^1.0" }
-bdk_chain = { path = "../chain", version = "0.14.0", features = ["miniscript", "serde"], default-features = false }
-bdk_persist = { path = "../persist", version = "0.2.0" }
-
-# Optional dependencies
-bip39 = { version = "2.0", optional = true }
-
-[target.'cfg(target_arch = "wasm32")'.dependencies]
-getrandom = "0.2"
-js-sys = "0.3"
-
-[features]
-default = ["std"]
-std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
-compiler = ["miniscript/compiler"]
-all-keys = ["keys-bip39"]
-keys-bip39 = ["bip39"]
-
-# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
-# for libraries to explicitly include the "getrandom/js" feature, so we only do it when
-# necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
-dev-getrandom-wasm = ["getrandom/js"]
-
-[dev-dependencies]
-lazy_static = "1.4"
-assert_matches = "1.5.0"
-tempfile = "3"
-bdk_file_store = { path = "../file_store" }
-anyhow = "1"
-
-[package.metadata.docs.rs]
-all-features = true
-rustdoc-args = ["--cfg", "docsrs"]
-
-[[example]]
-name = "mnemonic_to_descriptors"
-path = "examples/mnemonic_to_descriptors.rs"
-required-features = ["all-keys"]
-
-[[example]]
-name = "miniscriptc"
-path = "examples/compiler.rs"
-required-features = ["compiler"]
diff --git a/crates/bdk/README.md b/crates/bdk/README.md
deleted file mode 100644 (file)
index 4722c58..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-<div align="center">
-  <h1>BDK</h1>
-
-  <img src="https://raw.githubusercontent.com/bitcoindevkit/bdk/master/static/bdk.png" width="220" />
-
-  <p>
-    <strong>A modern, lightweight, descriptor-based wallet library written in Rust!</strong>
-  </p>
-
-  <p>
-    <a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a>
-    <a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
-    <a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
-    <a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
-    <a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
-    <a href="https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html"><img alt="Rustc Version 1.63.0+" src="https://img.shields.io/badge/rustc-1.63.0%2B-lightgrey.svg"/></a>
-    <a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
-  </p>
-
-  <h4>
-    <a href="https://bitcoindevkit.org">Project Homepage</a>
-    <span> | </span>
-    <a href="https://docs.rs/bdk">Documentation</a>
-  </h4>
-</div>
-
-## `bdk`
-
-The `bdk` crate provides the [`Wallet`] type which is a simple, high-level
-interface built from the low-level components of [`bdk_chain`]. `Wallet` is a good starting point
-for many simple applications as well as a good demonstration of how to use the other mechanisms to
-construct a wallet. It has two keychains (external and internal) which are defined by
-[miniscript descriptors][`rust-miniscript`] and uses them to generate addresses. When you give it
-chain data it also uses the descriptors to find transaction outputs owned by them. From there, you
-can create and sign transactions.
-
-For details about the API of `Wallet` see the [module-level documentation][`Wallet`].
-
-### Blockchain data
-
-In order to get blockchain data for `Wallet` to consume, you should configure a client from
-an available chain source. Typically you make a request to the chain source and get a response
-that the `Wallet` can use to update its view of the chain.
-
-**Blockchain Data Sources**
-
-* [`bdk_esplora`]: Grabs blockchain data from Esplora for updating BDK structures.
-* [`bdk_electrum`]: Grabs blockchain data from Electrum for updating BDK structures.
-* [`bdk_bitcoind_rpc`]: Grabs blockchain data from Bitcoin Core for updating BDK structures.
-
-**Examples**
-
-* [`example-crates/wallet_esplora_async`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_esplora_async)
-* [`example-crates/wallet_esplora_blocking`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_esplora_blocking)
-* [`example-crates/wallet_electrum`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_electrum)
-* [`example-crates/wallet_rpc`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_rpc)
-
-### Persistence
-
-To persist the `Wallet` on disk, it must be constructed with a [`PersistBackend`] implementation.
-
-**Implementations**
-
-* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`].
-
-**Example**
-
-<!-- compile_fail because outpoint and txout are fake variables -->
-```rust,compile_fail
-use bdk::{bitcoin::Network, wallet::{ChangeSet, Wallet}};
-
-fn main() {
-    // Create a new file `Store`.
-    let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
-
-    let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
-    let mut wallet = Wallet::new_or_load(descriptor, None, db, Network::Testnet).expect("create or load wallet");
-
-    // Insert a single `TxOut` at `OutPoint` into the wallet.
-    let _ = wallet.insert_txout(outpoint, txout);
-    wallet.commit().expect("must write to database");
-}
-```
-
-<!-- ### Sync the balance of a descriptor -->
-
-<!-- ```rust,no_run -->
-<!-- use bdk::Wallet; -->
-<!-- use bdk::blockchain::ElectrumBlockchain; -->
-<!-- use bdk::SyncOptions; -->
-<!-- use bdk::electrum_client::Client; -->
-<!-- use bdk::bitcoin::Network; -->
-
-<!-- fn main() -> Result<(), bdk::Error> { -->
-<!--     let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
-<!--     let wallet = Wallet::new( -->
-<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
-<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
-<!--         Network::Testnet, -->
-<!--     )?; -->
-
-<!--     wallet.sync(&blockchain, SyncOptions::default())?; -->
-
-<!--     println!("Descriptor balance: {} SAT", wallet.get_balance()?); -->
-
-<!--     Ok(()) -->
-<!-- } -->
-<!-- ``` -->
-<!-- ### Generate a few addresses -->
-
-<!-- ```rust -->
-<!-- use bdk::Wallet; -->
-<!-- use bdk::wallet::AddressIndex::New; -->
-<!-- use bdk::bitcoin::Network; -->
-
-<!-- fn main() -> Result<(), bdk::Error> { -->
-<!--     let wallet = Wallet::new_no_persist( -->
-<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
-<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
-<!--         Network::Testnet, -->
-<!--     )?; -->
-
-<!--     println!("Address #0: {}", wallet.get_address(New)); -->
-<!--     println!("Address #1: {}", wallet.get_address(New)); -->
-<!--     println!("Address #2: {}", wallet.get_address(New)); -->
-
-<!--     Ok(()) -->
-<!-- } -->
-<!-- ``` -->
-
-<!-- ### Create a transaction -->
-
-<!-- ```rust,no_run -->
-<!-- use bdk::{FeeRate, Wallet, SyncOptions}; -->
-<!-- use bdk::blockchain::ElectrumBlockchain; -->
-
-<!-- use bdk::electrum_client::Client; -->
-<!-- use bdk::wallet::AddressIndex::New; -->
-
-<!-- use bitcoin::base64; -->
-<!-- use bdk::bitcoin::consensus::serialize; -->
-<!-- use bdk::bitcoin::Network; -->
-
-<!-- fn main() -> Result<(), bdk::Error> { -->
-<!--     let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
-<!--     let wallet = Wallet::new_no_persist( -->
-<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
-<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
-<!--         Network::Testnet, -->
-<!--     )?; -->
-
-<!--     wallet.sync(&blockchain, SyncOptions::default())?; -->
-
-<!--     let send_to = wallet.get_address(New); -->
-<!--     let (psbt, details) = { -->
-<!--         let mut builder = wallet.build_tx(); -->
-<!--         builder -->
-<!--             .add_recipient(send_to.script_pubkey(), 50_000) -->
-<!--             .enable_rbf() -->
-<!--             .do_not_spend_change() -->
-<!--             .fee_rate(FeeRate::from_sat_per_vb(5.0)); -->
-<!--         builder.finish()? -->
-<!--     }; -->
-
-<!--     println!("Transaction details: {:#?}", details); -->
-<!--     println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); -->
-
-<!--     Ok(()) -->
-<!-- } -->
-<!-- ``` -->
-
-<!-- ### Sign a transaction -->
-
-<!-- ```rust,no_run -->
-<!-- use bdk::{Wallet, SignOptions}; -->
-
-<!-- use bitcoin::base64; -->
-<!-- use bdk::bitcoin::consensus::deserialize; -->
-<!-- use bdk::bitcoin::Network; -->
-
-<!-- fn main() -> Result<(), bdk::Error> { -->
-<!--     let wallet = Wallet::new_no_persist( -->
-<!--         "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", -->
-<!--         Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), -->
-<!--         Network::Testnet, -->
-<!--     )?; -->
-
-<!--     let psbt = "..."; -->
-<!--     let mut psbt = deserialize(&base64::decode(psbt).unwrap())?; -->
-
-<!--     let _finalized = wallet.sign(&mut psbt, SignOptions::default())?; -->
-
-<!--     Ok(()) -->
-<!-- } -->
-<!-- ``` -->
-
-## Testing
-
-### Unit testing
-
-```bash
-cargo test
-```
-
-## License
-
-Licensed under either of
-
-* Apache License, Version 2.0, ([LICENSE-APACHE](../../LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
-* MIT license ([LICENSE-MIT](../../LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
-
-at your option.
-
-### Contribution
-
-Unless you explicitly state otherwise, any contribution intentionally
-submitted for inclusion in the work by you, as defined in the Apache-2.0
-license, shall be dual licensed as above, without any additional terms or
-conditions.
-
-[`Wallet`]: https://docs.rs/bdk/1.0.0-alpha.7/bdk/wallet/struct.Wallet.html
-[`PersistBackend`]: https://docs.rs/bdk_persist/latest/bdk_persist/trait.PersistBackend.html
-[`bdk_chain`]: https://docs.rs/bdk_chain/latest
-[`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
-[`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
-[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
-[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
-[`rust-miniscript`]: https://docs.rs/miniscript/latest/miniscript/index.html
diff --git a/crates/bdk/examples/compiler.rs b/crates/bdk/examples/compiler.rs
deleted file mode 100644 (file)
index 0dbe1dd..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-extern crate bdk;
-extern crate bitcoin;
-extern crate miniscript;
-extern crate serde_json;
-
-use std::error::Error;
-use std::str::FromStr;
-
-use bitcoin::Network;
-use miniscript::policy::Concrete;
-use miniscript::Descriptor;
-
-use bdk::{KeychainKind, Wallet};
-
-/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
-/// rust-miniscript library here  https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
-/// rust-miniscript provides a `compile()` function that can be used to compile any miniscript policy
-/// into a descriptor. This descriptor then in turn can be used in bdk a fully functioning wallet
-/// can be derived from the policy.
-///
-/// This example demonstrates the interaction between a bdk wallet and miniscript policy.
-
-fn main() -> Result<(), Box<dyn Error>> {
-    // We start with a generic miniscript policy string
-    let policy_str = "or(10@thresh(4,pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)),1@and(older(4209713),thresh(2,pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068))))";
-    println!("Compiling policy: \n{}", policy_str);
-
-    // Parse the string as a [`Concrete`] type miniscript policy.
-    let policy = Concrete::<String>::from_str(policy_str)?;
-
-    // Create a `wsh` type descriptor from the policy.
-    // `policy.compile()` returns the resulting miniscript from the policy.
-    let descriptor = Descriptor::new_wsh(policy.compile()?)?;
-
-    println!("Compiled into following Descriptor: \n{}", descriptor);
-
-    // Create a new wallet from this descriptor
-    let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?;
-
-    println!(
-        "First derived address from the descriptor: \n{}",
-        wallet.next_unused_address(KeychainKind::External)?,
-    );
-
-    // BDK also has it's own `Policy` structure to represent the spending condition in a more
-    // human readable json format.
-    let spending_policy = wallet.policies(KeychainKind::External)?;
-    println!(
-        "The BDK spending policy: \n{}",
-        serde_json::to_string_pretty(&spending_policy)?
-    );
-
-    Ok(())
-}
diff --git a/crates/bdk/examples/mnemonic_to_descriptors.rs b/crates/bdk/examples/mnemonic_to_descriptors.rs
deleted file mode 100644 (file)
index d2e59fe..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use anyhow::anyhow;
-use bdk::bitcoin::bip32::DerivationPath;
-use bdk::bitcoin::secp256k1::Secp256k1;
-use bdk::bitcoin::Network;
-use bdk::descriptor;
-use bdk::descriptor::IntoWalletDescriptor;
-use bdk::keys::bip39::{Language, Mnemonic, WordCount};
-use bdk::keys::{GeneratableKey, GeneratedKey};
-use bdk::miniscript::Tap;
-use std::str::FromStr;
-
-/// This example demonstrates how to generate a mnemonic phrase
-/// using BDK and use that to generate a descriptor string.
-fn main() -> Result<(), anyhow::Error> {
-    let secp = Secp256k1::new();
-
-    // In this example we are generating a 12 words mnemonic phrase
-    // but it is also possible generate 15, 18, 21 and 24 words
-    // using their respective `WordCount` variant.
-    let mnemonic: GeneratedKey<_, Tap> =
-        Mnemonic::generate((WordCount::Words12, Language::English))
-            .map_err(|_| anyhow!("Mnemonic generation error"))?;
-
-    println!("Mnemonic phrase: {}", *mnemonic);
-    let mnemonic_with_passphrase = (mnemonic, None);
-
-    // define external and internal derivation key path
-    let external_path = DerivationPath::from_str("m/86h/1h/0h/0").unwrap();
-    let internal_path = DerivationPath::from_str("m/86h/1h/0h/1").unwrap();
-
-    // generate external and internal descriptor from mnemonic
-    let (external_descriptor, ext_keymap) =
-        descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
-            .into_wallet_descriptor(&secp, Network::Testnet)?;
-    let (internal_descriptor, int_keymap) =
-        descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
-            .into_wallet_descriptor(&secp, Network::Testnet)?;
-
-    println!("tpub external descriptor: {}", external_descriptor);
-    println!("tpub internal descriptor: {}", internal_descriptor);
-    println!(
-        "tprv external descriptor: {}",
-        external_descriptor.to_string_with_secret(&ext_keymap)
-    );
-    println!(
-        "tprv internal descriptor: {}",
-        internal_descriptor.to_string_with_secret(&int_keymap)
-    );
-
-    Ok(())
-}
diff --git a/crates/bdk/examples/policy.rs b/crates/bdk/examples/policy.rs
deleted file mode 100644 (file)
index 0f31ccc..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-extern crate bdk;
-use std::error::Error;
-
-use bdk::bitcoin::Network;
-use bdk::descriptor::{policy::BuildSatisfaction, ExtractPolicy, IntoWalletDescriptor};
-use bdk::wallet::signer::SignersContainer;
-
-/// This example describes the use of the BDK's [`bdk::descriptor::policy`] module.
-///
-/// Policy is higher abstraction representation of the wallet descriptor spending condition.
-/// This is useful to express complex miniscript spending conditions into more human readable form.
-/// The resulting `Policy` structure  can be used to derive spending conditions the wallet is capable
-/// to spend from.
-///
-/// This example demos a Policy output for a 2of2 multisig between between 2 parties, where the wallet holds
-/// one of the Extend Private key.
-
-fn main() -> Result<(), Box<dyn Error>> {
-    let secp = bitcoin::secp256k1::Secp256k1::new();
-
-    // The descriptor used in the example
-    // The form is "wsh(multi(2, <privkey>, <pubkey>))"
-    let desc = "wsh(multi(2,tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/*))";
-
-    // Use the descriptor string to derive the full descriptor and a keymap.
-    // The wallet descriptor can be used to create a new bdk::wallet.
-    // While the `keymap` can be used to create a `SignerContainer`.
-    //
-    // The `SignerContainer` can sign for `PSBT`s.
-    // a bdk::wallet internally uses these to handle transaction signing.
-    // But they can be used as independent tools also.
-    let (wallet_desc, keymap) = desc.into_wallet_descriptor(&secp, Network::Testnet)?;
-
-    println!("Example Descriptor for policy analysis : {}", wallet_desc);
-
-    // Create the signer with the keymap and descriptor.
-    let signers_container = SignersContainer::build(keymap, &wallet_desc, &secp);
-
-    // Extract the Policy from the given descriptor and signer.
-    // Note that Policy is a wallet specific structure. It depends on the the descriptor, and
-    // what the concerned wallet with a given signer can sign for.
-    let policy = wallet_desc
-        .extract_policy(&signers_container, BuildSatisfaction::None, &secp)?
-        .expect("We expect a policy");
-
-    println!("Derived Policy for the descriptor {:#?}", policy);
-
-    Ok(())
-}
diff --git a/crates/bdk/src/descriptor/checksum.rs b/crates/bdk/src/descriptor/checksum.rs
deleted file mode 100644 (file)
index 243376b..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptor checksum
-//!
-//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
-//! checksum of a descriptor
-
-use crate::descriptor::DescriptorError;
-use alloc::string::String;
-
-const INPUT_CHARSET: &[u8] = b"0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
-const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
-
-fn poly_mod(mut c: u64, val: u64) -> u64 {
-    let c0 = c >> 35;
-    c = ((c & 0x7ffffffff) << 5) ^ val;
-    if c0 & 1 > 0 {
-        c ^= 0xf5dee51989
-    };
-    if c0 & 2 > 0 {
-        c ^= 0xa9fdca3312
-    };
-    if c0 & 4 > 0 {
-        c ^= 0x1bab10e32d
-    };
-    if c0 & 8 > 0 {
-        c ^= 0x3706b1677a
-    };
-    if c0 & 16 > 0 {
-        c ^= 0x644d626ffd
-    };
-
-    c
-}
-
-/// Compute the checksum bytes of a descriptor, excludes any existing checksum in the descriptor string from the calculation
-pub fn calc_checksum_bytes(mut desc: &str) -> Result<[u8; 8], DescriptorError> {
-    let mut c = 1;
-    let mut cls = 0;
-    let mut clscount = 0;
-
-    let mut original_checksum = None;
-    if let Some(split) = desc.split_once('#') {
-        desc = split.0;
-        original_checksum = Some(split.1);
-    }
-
-    for ch in desc.as_bytes() {
-        let pos = INPUT_CHARSET
-            .iter()
-            .position(|b| b == ch)
-            .ok_or(DescriptorError::InvalidDescriptorCharacter(*ch))? as u64;
-        c = poly_mod(c, pos & 31);
-        cls = cls * 3 + (pos >> 5);
-        clscount += 1;
-        if clscount == 3 {
-            c = poly_mod(c, cls);
-            cls = 0;
-            clscount = 0;
-        }
-    }
-    if clscount > 0 {
-        c = poly_mod(c, cls);
-    }
-    (0..8).for_each(|_| c = poly_mod(c, 0));
-    c ^= 1;
-
-    let mut checksum = [0_u8; 8];
-    for j in 0..8 {
-        checksum[j] = CHECKSUM_CHARSET[((c >> (5 * (7 - j))) & 31) as usize];
-    }
-
-    // if input data already had a checksum, check calculated checksum against original checksum
-    if let Some(original_checksum) = original_checksum {
-        if original_checksum.as_bytes() != checksum {
-            return Err(DescriptorError::InvalidDescriptorChecksum);
-        }
-    }
-
-    Ok(checksum)
-}
-
-/// Compute the checksum of a descriptor, excludes any existing checksum in the descriptor string from the calculation
-pub fn calc_checksum(desc: &str) -> Result<String, DescriptorError> {
-    // unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
-    calc_checksum_bytes(desc).map(|b| unsafe { String::from_utf8_unchecked(b.to_vec()) })
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-    use crate::descriptor::calc_checksum;
-    use assert_matches::assert_matches;
-
-    // test calc_checksum() function; it should return the same value as Bitcoin Core
-    #[test]
-    fn test_calc_checksum() {
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)";
-        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
-
-        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)";
-        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
-    }
-
-    // test calc_checksum() function; it should return the same value as Bitcoin Core even if the
-    // descriptor string includes a checksum hash
-    #[test]
-    fn test_calc_checksum_with_checksum_hash() {
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62";
-        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
-
-        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmfs";
-        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
-        assert_matches!(
-            calc_checksum(desc),
-            Err(DescriptorError::InvalidDescriptorChecksum)
-        );
-
-        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
-        assert_matches!(
-            calc_checksum(desc),
-            Err(DescriptorError::InvalidDescriptorChecksum)
-        );
-    }
-
-    #[test]
-    fn test_calc_checksum_invalid_character() {
-        let sparkle_heart = unsafe { core::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
-        let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
-
-        assert_matches!(
-            calc_checksum(&invalid_desc),
-            Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
-        );
-    }
-}
diff --git a/crates/bdk/src/descriptor/dsl.rs b/crates/bdk/src/descriptor/dsl.rs
deleted file mode 100644 (file)
index aa52ff2..0000000
+++ /dev/null
@@ -1,1216 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptors DSL
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_top_level_sh {
-    // disallow `sortedmulti` in `bare()`
-    ( Bare, new, new, Legacy, sortedmulti $( $inner:tt )* ) => {
-        compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands");
-    };
-    ( Bare, new, new, Legacy, sortedmulti_vec $( $inner:tt )* ) => {
-        compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec()` operands");
-    };
-
-    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti $( $inner:tt )* ) => {{
-        use core::marker::PhantomData;
-
-        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
-        use $crate::miniscript::$ctx;
-
-        let build_desc = |k, pks| {
-            Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
-        };
-
-        $crate::impl_sortedmulti!(build_desc, sortedmulti $( $inner )*)
-    }};
-    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti_vec $( $inner:tt )* ) => {{
-        use core::marker::PhantomData;
-
-        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
-        use $crate::miniscript::$ctx;
-
-        let build_desc = |k, pks| {
-            Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
-        };
-
-        $crate::impl_sortedmulti!(build_desc, sortedmulti_vec $( $inner )*)
-    }};
-
-    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, $( $minisc:tt )* ) => {{
-        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
-
-        $crate::fragment!($( $minisc )*)
-            .and_then(|(minisc, keymap, networks)| Ok(($inner_struct::$constructor(minisc)?, keymap, networks)))
-            .and_then(|(inner, key_map, valid_networks)| Ok((Descriptor::<DescriptorPublicKey>::$inner_struct(inner), key_map, valid_networks)))
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_top_level_pk {
-    ( $inner_type:ident, $ctx:ty, $key:expr ) => {{
-        use $crate::miniscript::descriptor::$inner_type;
-
-        #[allow(unused_imports)]
-        use $crate::keys::{DescriptorKey, IntoDescriptorKey};
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-
-        $key.into_descriptor_key()
-            .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
-            .map_err($crate::descriptor::DescriptorError::Key)
-            .map(|(pk, key_map, valid_networks)| ($inner_type::new(pk), key_map, valid_networks))
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_top_level_tr {
-    ( $internal_key:expr, $tap_tree:expr ) => {{
-        use $crate::miniscript::descriptor::{
-            Descriptor, DescriptorPublicKey, KeyMap, TapTree, Tr,
-        };
-        use $crate::miniscript::Tap;
-
-        #[allow(unused_imports)]
-        use $crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
-
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-
-        $internal_key
-            .into_descriptor_key()
-            .and_then(|key: DescriptorKey<Tap>| key.extract(&secp))
-            .map_err($crate::descriptor::DescriptorError::Key)
-            .and_then(|(pk, mut key_map, mut valid_networks)| {
-                let tap_tree = $tap_tree.map(
-                    |(tap_tree, tree_keymap, tree_networks): (
-                        TapTree<DescriptorPublicKey>,
-                        KeyMap,
-                        ValidNetworks,
-                    )| {
-                        key_map.extend(tree_keymap.into_iter());
-                        valid_networks =
-                            $crate::keys::merge_networks(&valid_networks, &tree_networks);
-
-                        tap_tree
-                    },
-                );
-
-                Ok((
-                    Descriptor::<DescriptorPublicKey>::Tr(Tr::new(pk, tap_tree)?),
-                    key_map,
-                    valid_networks,
-                ))
-            })
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_leaf_opcode {
-    ( $terminal_variant:ident ) => {{
-        use $crate::descriptor::CheckMiniscript;
-
-        $crate::miniscript::Miniscript::from_ast(
-            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
-        )
-        .map_err($crate::descriptor::DescriptorError::Miniscript)
-        .and_then(|minisc| {
-            minisc.check_miniscript()?;
-            Ok(minisc)
-        })
-        .map(|minisc| {
-            (
-                minisc,
-                $crate::miniscript::descriptor::KeyMap::default(),
-                $crate::keys::any_network(),
-            )
-        })
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_leaf_opcode_value {
-    ( $terminal_variant:ident, $value:expr ) => {{
-        use $crate::descriptor::CheckMiniscript;
-
-        $crate::miniscript::Miniscript::from_ast(
-            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
-        )
-        .map_err($crate::descriptor::DescriptorError::Miniscript)
-        .and_then(|minisc| {
-            minisc.check_miniscript()?;
-            Ok(minisc)
-        })
-        .map(|minisc| {
-            (
-                minisc,
-                $crate::miniscript::descriptor::KeyMap::default(),
-                $crate::keys::any_network(),
-            )
-        })
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_leaf_opcode_value_two {
-    ( $terminal_variant:ident, $one:expr, $two:expr ) => {{
-        use $crate::descriptor::CheckMiniscript;
-
-        $crate::miniscript::Miniscript::from_ast(
-            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
-        )
-        .map_err($crate::descriptor::DescriptorError::Miniscript)
-        .and_then(|minisc| {
-            minisc.check_miniscript()?;
-            Ok(minisc)
-        })
-        .map(|minisc| {
-            (
-                minisc,
-                $crate::miniscript::descriptor::KeyMap::default(),
-                $crate::keys::any_network(),
-            )
-        })
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_node_opcode_two {
-    ( $terminal_variant:ident, $( $inner:tt )* ) => ({
-        use $crate::descriptor::CheckMiniscript;
-
-        let inner = $crate::fragment_internal!( @t $( $inner )* );
-        let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
-
-        a
-            .and_then(|a| Ok((a, b?)))
-            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks))| {
-                // join key_maps
-                a_keymap.extend(b_keymap.into_iter());
-
-                let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
-                    $crate::alloc::sync::Arc::new(a_minisc),
-                    $crate::alloc::sync::Arc::new(b_minisc),
-                ))?;
-
-                minisc.check_miniscript()?;
-
-                Ok((minisc, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
-            })
-    });
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_node_opcode_three {
-    ( $terminal_variant:ident, $( $inner:tt )* ) => ({
-        use $crate::descriptor::CheckMiniscript;
-
-        let inner = $crate::fragment_internal!( @t $( $inner )* );
-        let (a, b, c) = $crate::descriptor::dsl::TupleThree::from(inner).flattened();
-
-        a
-            .and_then(|a| Ok((a, b?, c?)))
-            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks), (c_minisc, c_keymap, c_networks))| {
-                // join key_maps
-                a_keymap.extend(b_keymap.into_iter());
-                a_keymap.extend(c_keymap.into_iter());
-
-                let networks = $crate::keys::merge_networks(&a_networks, &b_networks);
-                let networks = $crate::keys::merge_networks(&networks, &c_networks);
-
-                let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
-                    $crate::alloc::sync::Arc::new(a_minisc),
-                    $crate::alloc::sync::Arc::new(b_minisc),
-                    $crate::alloc::sync::Arc::new(c_minisc),
-                ))?;
-
-                minisc.check_miniscript()?;
-
-                Ok((minisc, a_keymap, networks))
-            })
-    });
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! impl_sortedmulti {
-    ( $build_desc:expr, sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-        $crate::keys::make_sortedmulti($thresh, $keys, $build_desc, &secp)
-    });
-    ( $build_desc:expr, sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({
-        use $crate::keys::IntoDescriptorKey;
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-
-        let keys = vec![
-            $(
-                $key.into_descriptor_key(),
-            )*
-        ];
-
-        keys.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
-            .map_err($crate::descriptor::DescriptorError::Key)
-            .and_then(|keys| $crate::keys::make_sortedmulti($thresh, keys, $build_desc, &secp))
-    });
-
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! parse_tap_tree {
-    ( @merge $tree_a:expr, $tree_b:expr) => {{
-        use $crate::miniscript::descriptor::TapTree;
-
-        $tree_a
-            .and_then(|tree_a| Ok((tree_a, $tree_b?)))
-            .and_then(|((a_tree, mut a_keymap, a_networks), (b_tree, b_keymap, b_networks))| {
-                a_keymap.extend(b_keymap.into_iter());
-                Ok((TapTree::combine(a_tree, b_tree), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
-            })
-
-    }};
-
-    // Two sub-trees
-    ( { { $( $tree_a:tt )* }, { $( $tree_b:tt )* } } ) => {{
-        let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
-        let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
-
-        $crate::parse_tap_tree!(@merge tree_a, tree_b)
-    }};
-
-    // One leaf and a sub-tree
-    ( { $op_a:ident ( $( $minisc_a:tt )* ), { $( $tree_b:tt )* } } ) => {{
-        let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
-        let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
-
-        $crate::parse_tap_tree!(@merge tree_a, tree_b)
-    }};
-    ( { { $( $tree_a:tt )* }, $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
-        let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
-        let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
-
-        $crate::parse_tap_tree!(@merge tree_a, tree_b)
-    }};
-
-    // Two leaves
-    ( { $op_a:ident ( $( $minisc_a:tt )* ), $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
-        let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
-        let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
-
-        $crate::parse_tap_tree!(@merge tree_a, tree_b)
-    }};
-
-    // Single leaf
-    ( $op:ident ( $( $minisc:tt )* ) ) => {{
-        use $crate::alloc::sync::Arc;
-        use $crate::miniscript::descriptor::TapTree;
-
-        $crate::fragment!( $op ( $( $minisc )* ) )
-            .map(|(a_minisc, a_keymap, a_networks)| (TapTree::Leaf(Arc::new(a_minisc)), a_keymap, a_networks))
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! apply_modifier {
-    ( $terminal_variant:ident, $inner:expr ) => {{
-        use $crate::descriptor::CheckMiniscript;
-
-        $inner
-            .map_err(|e| -> $crate::descriptor::DescriptorError { e.into() })
-            .and_then(|(minisc, keymap, networks)| {
-                let minisc = $crate::miniscript::Miniscript::from_ast(
-                    $crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
-                        $crate::alloc::sync::Arc::new(minisc),
-                    ),
-                )?;
-
-                minisc.check_miniscript()?;
-
-                Ok((minisc, keymap, networks))
-            })
-    }};
-
-    ( a: $inner:expr ) => {{
-        $crate::apply_modifier!(Alt, $inner)
-    }};
-    ( s: $inner:expr ) => {{
-        $crate::apply_modifier!(Swap, $inner)
-    }};
-    ( c: $inner:expr ) => {{
-        $crate::apply_modifier!(Check, $inner)
-    }};
-    ( d: $inner:expr ) => {{
-        $crate::apply_modifier!(DupIf, $inner)
-    }};
-    ( v: $inner:expr ) => {{
-        $crate::apply_modifier!(Verify, $inner)
-    }};
-    ( j: $inner:expr ) => {{
-        $crate::apply_modifier!(NonZero, $inner)
-    }};
-    ( n: $inner:expr ) => {{
-        $crate::apply_modifier!(ZeroNotEqual, $inner)
-    }};
-
-    // Modifiers expanded to other operators
-    ( t: $inner:expr ) => {{
-        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
-            $crate::impl_leaf_opcode_value_two!(
-                AndV,
-                $crate::alloc::sync::Arc::new(a_minisc),
-                $crate::alloc::sync::Arc::new($crate::fragment!(true).unwrap().0)
-            )
-            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
-        })
-    }};
-    ( l: $inner:expr ) => {{
-        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
-            $crate::impl_leaf_opcode_value_two!(
-                OrI,
-                $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0),
-                $crate::alloc::sync::Arc::new(a_minisc)
-            )
-            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
-        })
-    }};
-    ( u: $inner:expr ) => {{
-        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
-            $crate::impl_leaf_opcode_value_two!(
-                OrI,
-                $crate::alloc::sync::Arc::new(a_minisc),
-                $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0)
-            )
-            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
-        })
-    }};
-}
-
-/// Macro to write full descriptors with code
-///
-/// This macro expands to a `Result` of
-/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`DescriptorError`](crate::descriptor::DescriptorError)
-///
-/// The syntax is very similar to the normal descriptor syntax, with the exception that modifiers
-/// cannot be grouped together. For instance, a descriptor fragment like `sdv:older(144)` has to be
-/// broken up to `s:d:v:older(144)`.
-///
-/// The `pk()`, `pk_k()` and `pk_h()` operands can take as argument any type that implements
-/// [`IntoDescriptorKey`]. This means that keys can also be written inline as strings, but in that
-/// case they must be wrapped in quotes, which is another difference compared to the standard
-/// descriptor syntax.
-///
-/// [`IntoDescriptorKey`]: crate::keys::IntoDescriptorKey
-///
-/// ## Example
-///
-/// Signature plus timelock descriptor:
-///
-/// ```
-/// # use std::str::FromStr;
-/// let (my_descriptor, my_keys_map, networks) = bdk::descriptor!(sh(wsh(and_v(v:pk("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"),older(50)))))?;
-/// # Ok::<(), Box<dyn std::error::Error>>(())
-/// ```
-///
-/// -------
-///
-/// 2-of-3 that becomes a 1-of-3 after a timelock has expired. Both `descriptor_a` and `descriptor_b` are equivalent: the first
-/// syntax is more suitable for a fixed number of items known at compile time, while the other accepts a
-/// [`Vec`] of items, which makes it more suitable for writing dynamic descriptors.
-///
-/// They both produce the descriptor: `wsh(thresh(2,pk(...),s:pk(...),sndv:older(...)))`
-///
-/// ```
-/// # use std::str::FromStr;
-/// let my_key_1 = bitcoin::PublicKey::from_str(
-///     "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
-/// )?;
-/// let my_key_2 =
-///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
-/// let my_timelock = 50;
-///
-/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
-///     wsh (
-///         thresh(2, pk(my_key_1), s:pk(my_key_2), s:n:d:v:older(my_timelock))
-///     )
-/// }?;
-///
-/// #[rustfmt::skip]
-/// let b_items = vec![
-///     bdk::fragment!(pk(my_key_1))?,
-///     bdk::fragment!(s:pk(my_key_2))?,
-///     bdk::fragment!(s:n:d:v:older(my_timelock))?,
-/// ];
-/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2, b_items)))?;
-///
-/// assert_eq!(descriptor_a, descriptor_b);
-/// assert_eq!(key_map_a.len(), key_map_b.len());
-/// # Ok::<(), Box<dyn std::error::Error>>(())
-/// ```
-///
-/// ------
-///
-/// Simple 2-of-2 multi-signature, equivalent to: `wsh(multi(2, ...))`
-///
-/// ```
-/// # use std::str::FromStr;
-/// let my_key_1 = bitcoin::PublicKey::from_str(
-///     "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
-/// )?;
-/// let my_key_2 =
-///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
-///
-/// let (descriptor, key_map, networks) = bdk::descriptor! {
-///     wsh (
-///         multi(2, my_key_1, my_key_2)
-///     )
-/// }?;
-/// # Ok::<(), Box<dyn std::error::Error>>(())
-/// ```
-///
-/// ------
-///
-/// Native-Segwit single-sig, equivalent to: `wpkh(...)`
-///
-/// ```
-/// let my_key =
-///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
-///
-/// let (descriptor, key_map, networks) = bdk::descriptor!(wpkh(my_key))?;
-/// # Ok::<(), Box<dyn std::error::Error>>(())
-/// ```
-///
-/// [`Vec`]: alloc::vec::Vec
-#[macro_export]
-macro_rules! descriptor {
-    ( bare ( $( $minisc:tt )* ) ) => ({
-        $crate::impl_top_level_sh!(Bare, new, new, Legacy, $( $minisc )*)
-    });
-    ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({
-        $crate::descriptor!(shwsh ($( $minisc )*))
-    });
-    ( shwsh ( $( $minisc:tt )* ) ) => ({
-        $crate::impl_top_level_sh!(Sh, new_wsh, new_wsh_sortedmulti, Segwitv0, $( $minisc )*)
-    });
-    ( pk ( $key:expr ) ) => ({
-        // `pk()` is actually implemented as `bare(pk())`
-        $crate::descriptor!( bare ( pk ( $key ) ) )
-    });
-    ( pkh ( $key:expr ) ) => ({
-        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
-
-        $crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key)
-            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
-            .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Pkh(a), b, c))
-    });
-    ( wpkh ( $key:expr ) ) => ({
-        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
-
-        $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
-            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
-            .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
-    });
-    ( sh ( wpkh ( $key:expr ) ) ) => ({
-        $crate::descriptor!(shwpkh ( $key ))
-    });
-    ( shwpkh ( $key:expr ) ) => ({
-        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh};
-
-        $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
-            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
-            .and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
-    });
-    ( sh ( $( $minisc:tt )* ) ) => ({
-        $crate::impl_top_level_sh!(Sh, new, new_sortedmulti, Legacy, $( $minisc )*)
-    });
-    ( wsh ( $( $minisc:tt )* ) ) => ({
-        $crate::impl_top_level_sh!(Wsh, new, new_sortedmulti, Segwitv0, $( $minisc )*)
-    });
-
-    ( tr ( $internal_key:expr ) ) => ({
-        $crate::impl_top_level_tr!($internal_key, None)
-    });
-    ( tr ( $internal_key:expr, $( $taptree:tt )* ) ) => ({
-        let tap_tree = $crate::parse_tap_tree!( $( $taptree )* );
-        tap_tree
-            .and_then(|tap_tree| $crate::impl_top_level_tr!($internal_key, Some(tap_tree)))
-    });
-}
-
-#[doc(hidden)]
-pub struct TupleTwo<A, B> {
-    pub a: A,
-    pub b: B,
-}
-
-impl<A, B> TupleTwo<A, B> {
-    pub fn flattened(self) -> (A, B) {
-        (self.a, self.b)
-    }
-}
-
-impl<A, B> From<(A, (B, ()))> for TupleTwo<A, B> {
-    fn from((a, (b, _)): (A, (B, ()))) -> Self {
-        TupleTwo { a, b }
-    }
-}
-
-#[doc(hidden)]
-pub struct TupleThree<A, B, C> {
-    pub a: A,
-    pub b: B,
-    pub c: C,
-}
-
-impl<A, B, C> TupleThree<A, B, C> {
-    pub fn flattened(self) -> (A, B, C) {
-        (self.a, self.b, self.c)
-    }
-}
-
-impl<A, B, C> From<(A, (B, (C, ())))> for TupleThree<A, B, C> {
-    fn from((a, (b, (c, _))): (A, (B, (C, ())))) -> Self {
-        TupleThree { a, b, c }
-    }
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! group_multi_keys {
-    ( $( $key:expr ),+ ) => {{
-        use $crate::keys::IntoDescriptorKey;
-
-        let keys = vec![
-            $(
-                $key.into_descriptor_key(),
-            )*
-        ];
-
-        keys.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
-            .map_err($crate::descriptor::DescriptorError::Key)
-    }};
-}
-
-#[doc(hidden)]
-#[macro_export]
-macro_rules! fragment_internal {
-    // The @v prefix is used to parse a sequence of operands and return them in a vector. This is
-    // used by operands that take a variable number of arguments, like `thresh()` and `multi()`.
-    ( @v $op:ident ( $( $args:tt )* ) $( $tail:tt )* ) => ({
-        let mut v = vec![$crate::fragment!( $op ( $( $args )* ) )];
-        v.append(&mut $crate::fragment_internal!( @v $( $tail )* ));
-
-        v
-    });
-    // Match modifiers
-    ( @v $modif:tt : $( $tail:tt )* ) => ({
-        let mut v = $crate::fragment_internal!( @v $( $tail )* );
-        let first = v.drain(..1).next().unwrap();
-
-        let first = $crate::apply_modifier!($modif:first);
-
-        let mut v_final = vec![first];
-        v_final.append(&mut v);
-
-        v_final
-    });
-    // Remove commas between operands
-    ( @v , $( $tail:tt )* ) => ({
-        $crate::fragment_internal!( @v $( $tail )* )
-    });
-    ( @v ) => ({
-        vec![]
-    });
-
-    // The @t prefix is used to parse a sequence of operands and return them in a tuple. This
-    // allows checking at compile-time the number of arguments passed to an operand. For this
-    // reason it's used by `and_*()`, `or_*()`, etc.
-    //
-    // Unfortunately, due to the fact that concatenating tuples is pretty hard, the final result
-    // adds in the first spot the parsed operand and in the second spot the result of parsing
-    // all the following ones. For two operands the type then corresponds to: (X, (X, ())). For
-    // three operands it's (X, (X, (X, ()))), etc.
-    //
-    // To check that the right number of arguments has been passed we can "cast" those tuples to
-    // more convenient structures like `TupleTwo`. If the conversion succeeds, the right number of
-    // args was passed. Otherwise the compilation fails entirely.
-    ( @t $op:ident ( $( $args:tt )* ) $( $tail:tt )* ) => ({
-        ($crate::fragment!( $op ( $( $args )* ) ), $crate::fragment_internal!( @t $( $tail )* ))
-    });
-    // Match modifiers
-    ( @t $modif:tt : $( $tail:tt )* ) => ({
-        let (first, tail) = $crate::fragment_internal!( @t $( $tail )* );
-        ($crate::apply_modifier!($modif:first), tail)
-    });
-    // Remove commas between operands
-    ( @t , $( $tail:tt )* ) => ({
-        $crate::fragment_internal!( @t $( $tail )* )
-    });
-    ( @t ) => ({});
-
-    // Fallback to calling `fragment!()`
-    ( $( $tokens:tt )* ) => ({
-        $crate::fragment!($( $tokens )*)
-    });
-}
-
-/// Macro to write descriptor fragments with code
-///
-/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), DescriptorError>`. It allows writing
-/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec(m, ...))`.
-///
-/// The syntax to write macro fragment is the same as documented for the [`descriptor`] macro.
-#[macro_export]
-macro_rules! fragment {
-    // Modifiers
-    ( $modif:tt : $( $tail:tt )* ) => ({
-        let op = $crate::fragment!( $( $tail )* );
-        $crate::apply_modifier!($modif:op)
-    });
-
-    // Miniscript
-    ( true ) => ({
-        $crate::impl_leaf_opcode!(True)
-    });
-    ( false ) => ({
-        $crate::impl_leaf_opcode!(False)
-    });
-    ( pk_k ( $key:expr ) ) => ({
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-        $crate::keys::make_pk($key, &secp)
-    });
-    ( pk ( $key:expr ) ) => ({
-        $crate::fragment!(c:pk_k ( $key ))
-    });
-    ( pk_h ( $key:expr ) ) => ({
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-        $crate::keys::make_pkh($key, &secp)
-    });
-    ( after ( $value:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(After, $crate::miniscript::AbsLockTime::from_consensus($value))
-    });
-    ( older ( $value:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
-    });
-    ( sha256 ( $hash:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(Sha256, $hash)
-    });
-    ( hash256 ( $hash:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(Hash256, $hash)
-    });
-    ( ripemd160 ( $hash:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(Ripemd160, $hash)
-    });
-    ( hash160 ( $hash:expr ) ) => ({
-        $crate::impl_leaf_opcode_value!(Hash160, $hash)
-    });
-    ( and_v ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(AndV, $( $inner )*)
-    });
-    ( and_b ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(AndB, $( $inner )*)
-    });
-    ( and_or ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_three!(AndOr, $( $inner )*)
-    });
-    ( andor ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_three!(AndOr, $( $inner )*)
-    });
-    ( or_b ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(OrB, $( $inner )*)
-    });
-    ( or_d ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(OrD, $( $inner )*)
-    });
-    ( or_c ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(OrC, $( $inner )*)
-    });
-    ( or_i ( $( $inner:tt )* ) ) => ({
-        $crate::impl_node_opcode_two!(OrI, $( $inner )*)
-    });
-    ( thresh_vec ( $thresh:expr, $items:expr ) ) => ({
-        use $crate::miniscript::descriptor::KeyMap;
-
-        let (items, key_maps_networks): ($crate::alloc::vec::Vec<_>, $crate::alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip();
-        let items = items.into_iter().map($crate::alloc::sync::Arc::new).collect();
-
-        let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| {
-            keys_acc.extend(key.into_iter());
-            let net_acc = $crate::keys::merge_networks(&net_acc, &net);
-
-            (keys_acc, net_acc)
-        });
-
-        $crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
-            .map(|(minisc, _, _)| (minisc, key_maps, valid_networks))
-    });
-    ( thresh ( $thresh:expr, $( $inner:tt )* ) ) => ({
-        let items = $crate::fragment_internal!( @v $( $inner )* );
-
-        items.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
-            .and_then(|items| $crate::fragment!(thresh_vec($thresh, items)))
-    });
-    ( multi_vec ( $thresh:expr, $keys:expr ) ) => ({
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-
-        $crate::keys::make_multi($thresh, $crate::miniscript::Terminal::Multi, $keys, &secp)
-    });
-    ( multi ( $thresh:expr $(, $key:expr )+ ) ) => ({
-        $crate::group_multi_keys!( $( $key ),* )
-            .and_then(|keys| $crate::fragment!( multi_vec ( $thresh, keys ) ))
-    });
-    ( multi_a_vec ( $thresh:expr, $keys:expr ) ) => ({
-        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
-
-        $crate::keys::make_multi($thresh, $crate::miniscript::Terminal::MultiA, $keys, &secp)
-    });
-    ( multi_a ( $thresh:expr $(, $key:expr )+ ) ) => ({
-        $crate::group_multi_keys!( $( $key ),* )
-            .and_then(|keys| $crate::fragment!( multi_a_vec ( $thresh, keys ) ))
-    });
-
-    // `sortedmulti()` is handled separately
-    ( sortedmulti ( $( $inner:tt )* ) ) => ({
-        compile_error!("`sortedmulti` can only be used as the root operand of a descriptor");
-    });
-    ( sortedmulti_vec ( $( $inner:tt )* ) ) => ({
-        compile_error!("`sortedmulti_vec` can only be used as the root operand of a descriptor");
-    });
-}
-
-#[cfg(test)]
-mod test {
-    use alloc::string::ToString;
-    use bitcoin::secp256k1::Secp256k1;
-    use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
-    use miniscript::{Descriptor, Legacy, Segwitv0};
-
-    use core::str::FromStr;
-
-    use crate::descriptor::{DescriptorError, DescriptorMeta};
-    use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
-    use bitcoin::bip32;
-    use bitcoin::Network::{Bitcoin, Regtest, Signet, Testnet};
-    use bitcoin::PrivateKey;
-
-    // test the descriptor!() macro
-
-    // verify descriptor generates expected script(s) (if bare or pk) or address(es)
-    fn check(
-        desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
-        is_witness: bool,
-        is_fixed: bool,
-        expected: &[&str],
-    ) {
-        let (desc, _key_map, _networks) = desc.unwrap();
-        assert_eq!(desc.is_witness(), is_witness);
-        assert_eq!(!desc.has_wildcard(), is_fixed);
-        for i in 0..expected.len() {
-            let child_desc = desc
-                .at_derivation_index(i as u32)
-                .expect("i is not hardened");
-            let address = child_desc.address(Regtest);
-            if let Ok(address) = address {
-                assert_eq!(address.to_string(), *expected.get(i).unwrap());
-            } else {
-                let script = child_desc.script_pubkey();
-                assert_eq!(script.to_hex_string(), *expected.get(i).unwrap());
-            }
-        }
-    }
-
-    // - at least one of each "type" of operator; i.e. one modifier, one leaf_opcode, one leaf_opcode_value, etc.
-    // - mixing up key types that implement IntoDescriptorKey in multi() or thresh()
-
-    // expected script for pk and bare manually created
-    // expected addresses created with `bitcoin-cli getdescriptorinfo` (for hash) and `bitcoin-cli deriveaddresses`
-
-    #[test]
-    fn test_fixed_legacy_descriptors() {
-        let pubkey1 = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        let pubkey2 = bitcoin::PublicKey::from_str(
-            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
-        )
-        .unwrap();
-
-        check(
-            descriptor!(bare(multi(1,pubkey1,pubkey2))),
-            false,
-            true,
-            &["512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af52ae"],
-        );
-        check(
-            descriptor!(pk(pubkey1)),
-            false,
-            true,
-            &["2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"],
-        );
-        check(
-            descriptor!(pkh(pubkey1)),
-            false,
-            true,
-            &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
-        );
-        check(
-            descriptor!(sh(multi(1, pubkey1, pubkey2))),
-            false,
-            true,
-            &["2MymURoV1bzuMnWMGiXzyomDkeuxXY7Suey"],
-        );
-    }
-
-    #[test]
-    fn test_fixed_segwitv0_descriptors() {
-        let pubkey1 = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        let pubkey2 = bitcoin::PublicKey::from_str(
-            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
-        )
-        .unwrap();
-
-        check(
-            descriptor!(wpkh(pubkey1)),
-            true,
-            true,
-            &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
-        );
-        check(
-            descriptor!(sh(wpkh(pubkey1))),
-            true,
-            true,
-            &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
-        );
-        check(
-            descriptor!(wsh(multi(1, pubkey1, pubkey2))),
-            true,
-            true,
-            &["bcrt1qgw8jvv2hsrvjfa6q66rk6har7d32lrqm5unnf5cl63q9phxfvgps5fyfqe"],
-        );
-        check(
-            descriptor!(sh(wsh(multi(1, pubkey1, pubkey2)))),
-            true,
-            true,
-            &["2NCidRJysy7apkmE6JF5mLLaJFkrN3Ub9iy"],
-        );
-    }
-
-    #[test]
-    fn test_fixed_threeop_descriptors() {
-        let redeem_key = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        let move_key = bitcoin::PublicKey::from_str(
-            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
-        )
-        .unwrap();
-
-        check(
-            descriptor!(sh(wsh(and_or(pk(redeem_key), older(1000), pk(move_key))))),
-            true,
-            true,
-            &["2MypGwr5eQWAWWJtiJgUEToVxc4zuokjQRe"],
-        );
-    }
-
-    #[test]
-    fn test_bip32_legacy_descriptors() {
-        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-
-        let path = bip32::DerivationPath::from_str("m/0").unwrap();
-        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
-        check(
-            descriptor!(pk(desc_key)),
-            false,
-            false,
-            &[
-                "2102363ad03c10024e1b597a5b01b9982807fb638e00b06f3b2d4a89707de3b93c37ac",
-                "2102063a21fd780df370ed2fc8c4b86aa5ea642630609c203009df631feb7b480dd2ac",
-                "2102ba2685ad1fa5891cb100f1656b2ce3801822ccb9bac0336734a6f8c1b93ebbc0ac",
-            ],
-        );
-
-        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
-        check(
-            descriptor!(pkh(desc_key)),
-            false,
-            false,
-            &[
-                "muvBdsVpJxpFuTHMKA47htJPdCvdt4F9DP",
-                "mxQSHK7DL2t1DN3xFxov1janCoXSSkrSPj",
-                "mfz43r15GiWo4nizmyzMNubsnkDpByFFAn",
-            ],
-        );
-
-        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
-        let desc_key1 = (xprv, path).into_descriptor_key().unwrap();
-        let desc_key2 = (xprv, path2).into_descriptor_key().unwrap();
-
-        check(
-            descriptor!(sh(multi(1, desc_key1, desc_key2))),
-            false,
-            false,
-            &[
-                "2MtMDXsfwefZkEEhVViEPidvcKRUtJamJJ8",
-                "2MwAUZ1NYyWjhVvGTethFL6n7nZhS8WE6At",
-                "2MuT6Bj66HLwZd7s4SoD8XbK4GwriKEA6Gr",
-            ],
-        );
-    }
-
-    #[test]
-    fn test_bip32_segwitv0_descriptors() {
-        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-
-        let path = bip32::DerivationPath::from_str("m/0").unwrap();
-        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
-        check(
-            descriptor!(wpkh(desc_key)),
-            true,
-            false,
-            &[
-                "bcrt1qnhm8w9fhc8cxzgqsmqdf9fyjccyvc0gltnymu0",
-                "bcrt1qhylfd55rn75w9fj06zspctad5w4hz33rf0ttad",
-                "bcrt1qq5sq3a6k9av9d8cne0k9wcldy4nqey5yt6889r",
-            ],
-        );
-
-        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
-        check(
-            descriptor!(sh(wpkh(desc_key))),
-            true,
-            false,
-            &[
-                "2MxvjQCaLqZ5QxZ7XotZDQ63hZw3NPss763",
-                "2NDUoevN4QMzhvHDMGhKuiT2fN9HXbFRMwn",
-                "2NF4BEAY2jF1Fu8vqfN3NVKoFtom77pUxrx",
-            ],
-        );
-
-        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
-        let desc_key1 = (xprv, path.clone()).into_descriptor_key().unwrap();
-        let desc_key2 = (xprv, path2.clone()).into_descriptor_key().unwrap();
-        check(
-            descriptor!(wsh(multi(1, desc_key1, desc_key2))),
-            true,
-            false,
-            &[
-                "bcrt1qfxv8mxmlv5sz8q2mnuyaqdfe9jr4vvmx0csjhn092p6f4qfygfkq2hng49",
-                "bcrt1qerj85g243e6jlcdxpmn9spk0gefcwvu7nw7ee059d5ydzpdhkm2qwfkf5k",
-                "bcrt1qxkl2qss3k58q9ktc8e89pwr4gnptfpw4hju4xstxcjc0hkcae3jstluty7",
-            ],
-        );
-
-        let desc_key1 = (xprv, path).into_descriptor_key().unwrap();
-        let desc_key2 = (xprv, path2).into_descriptor_key().unwrap();
-        check(
-            descriptor!(sh(wsh(multi(1, desc_key1, desc_key2)))),
-            true,
-            false,
-            &[
-                "2NFCtXvx9q4ci2kvKub17iSTgvRXGctCGhz",
-                "2NB2PrFPv5NxWCpygas8tPrGJG2ZFgeuwJw",
-                "2N79ZAGo5cMi5Jt7Wo9L5YmF5GkEw7sjWdC",
-            ],
-        );
-    }
-
-    #[test]
-    fn test_dsl_sortedmulti() {
-        let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let path_1 = bip32::DerivationPath::from_str("m/0").unwrap();
-
-        let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
-        let path_2 = bip32::DerivationPath::from_str("m/1").unwrap();
-
-        let desc_key1 = (key_1, path_1);
-        let desc_key2 = (key_2, path_2);
-
-        check(
-            descriptor!(sh(sortedmulti(1, desc_key1.clone(), desc_key2.clone()))),
-            false,
-            false,
-            &[
-                "2MsxzPEJDBzpGffJXPaDpfXZAUNnZhaMh2N",
-                "2My3x3DLPK3UbGWGpxrXr1RnbD8MNC4FpgS",
-                "2NByEuiQT7YLqHCTNxL5KwYjvtuCYcXNBSC",
-                "2N1TGbP81kj2VUKTSWgrwxoMfuWjvfUdyu7",
-                "2N3Bomq2fpAcLRNfZnD3bCWK9quan28CxCR",
-                "2N9nrZaEzEFDqEAU9RPvDnXGT6AVwBDKAQb",
-            ],
-        );
-
-        check(
-            descriptor!(sh(wsh(sortedmulti(
-                1,
-                desc_key1.clone(),
-                desc_key2.clone()
-            )))),
-            true,
-            false,
-            &[
-                "2NCogc5YyM4N6ruv1hUa7WLMW1BPeCK7N9B",
-                "2N6mkSAKi1V2oaBXby7XHdvBMKEDRQcFpNe",
-                "2NFmTSttm9v6bXeoWaBvpMcgfPQcZhNn3Eh",
-                "2Mvib87RBPUHXNEpX5S5Kv1qqrhBfgBGsJM",
-                "2MtMv5mcK2EjcLsH8Txpx2JxLLzHr4ttczL",
-                "2MsWCB56rb4T6yPv8QudZGHERTwNgesE4f6",
-            ],
-        );
-
-        check(
-            descriptor!(wsh(sortedmulti_vec(1, vec![desc_key1, desc_key2]))),
-            true,
-            false,
-            &[
-                "bcrt1qcvq0lg8q7a47ytrd7zk5y7uls7mulrenjgvflwylpppgwf8029es4vhpnj",
-                "bcrt1q80yn8sdt6l7pjvkz25lglyaqctlmsq9ugk80rmxt8yu0npdsj97sc7l4de",
-                "bcrt1qrvf6024v9s50qhffe3t2fr2q9ckdhx2g6jz32chm2pp24ymgtr5qfrdmct",
-                "bcrt1q6srfmra0ynypym35c7jvsxt2u4yrugeajq95kg2ps7lk6h2gaunsq9lzxn",
-                "bcrt1qhl8rrzzcdpu7tcup3lcg7tge52sqvwy5fcv4k78v6kxtwmqf3v6qpvyjza",
-                "bcrt1ql2elz9mhm9ll27ddpewhxs732xyl2fk2kpkqz9gdyh33wgcun4vstrd49k",
-            ],
-        );
-    }
-
-    // - verify the valid_networks returned is correctly computed based on the keys present in the descriptor
-    #[test]
-    fn test_valid_networks() {
-        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let path = bip32::DerivationPath::from_str("m/0").unwrap();
-        let desc_key = (xprv, path).into_descriptor_key().unwrap();
-
-        let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap();
-        assert_eq!(
-            valid_networks,
-            [Testnet, Regtest, Signet].iter().cloned().collect()
-        );
-
-        let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
-        let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
-        let desc_key = (xprv, path).into_descriptor_key().unwrap();
-
-        let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap();
-        assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect());
-    }
-
-    // - verify the key_maps are correctly merged together
-    #[test]
-    fn test_key_maps_merged() {
-        let secp = Secp256k1::new();
-
-        let xprv1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let path1 = bip32::DerivationPath::from_str("m/0").unwrap();
-        let desc_key1 = (xprv1, path1.clone()).into_descriptor_key().unwrap();
-
-        let xprv2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
-        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
-        let desc_key2 = (xprv2, path2.clone()).into_descriptor_key().unwrap();
-
-        let xprv3 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf").unwrap();
-        let path3 = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
-        let desc_key3 = (xprv3, path3.clone()).into_descriptor_key().unwrap();
-
-        let (_desc, key_map, _valid_networks) =
-            descriptor!(sh(wsh(multi(2, desc_key1, desc_key2, desc_key3)))).unwrap();
-        assert_eq!(key_map.len(), 3);
-
-        let desc_key1: DescriptorKey<Segwitv0> = (xprv1, path1).into_descriptor_key().unwrap();
-        let desc_key2: DescriptorKey<Segwitv0> = (xprv2, path2).into_descriptor_key().unwrap();
-        let desc_key3: DescriptorKey<Segwitv0> = (xprv3, path3).into_descriptor_key().unwrap();
-
-        let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap();
-        let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap();
-        let (key3, _key_map, _valid_networks) = desc_key3.extract(&secp).unwrap();
-        assert_eq!(key_map.get(&key1).unwrap().to_string(), "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*");
-        assert_eq!(key_map.get(&key2).unwrap().to_string(), "tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF/2147483647'/0/*");
-        assert_eq!(key_map.get(&key3).unwrap().to_string(), "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf/10/20/30/40/*");
-    }
-
-    // - verify the ScriptContext is correctly validated (i.e. passing a type that only impl IntoDescriptorKey<Segwitv0> to a pkh() descriptor should throw a compilation error
-    #[test]
-    fn test_script_context_validation() {
-        // this compiles
-        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let path = bip32::DerivationPath::from_str("m/0").unwrap();
-        let desc_key: DescriptorKey<Legacy> = (xprv, path).into_descriptor_key().unwrap();
-
-        let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
-        assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2");
-
-        // as expected this does not compile due to invalid context
-        //let desc_key:DescriptorKey<Segwitv0> = (xprv, path.clone()).into_descriptor_key().unwrap();
-        //let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
-    }
-
-    #[test]
-    fn test_dsl_modifiers() {
-        let private_key =
-            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
-        let (descriptor, _, _) =
-            descriptor!(wsh(thresh(2,n:d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap();
-
-        assert_eq!(descriptor.to_string(), "wsh(thresh(2,ndv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#zzk3ux8g")
-    }
-
-    #[test]
-    #[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")]
-    fn test_dsl_miniscript_checks() {
-        let mut uncompressed_pk =
-            PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
-        uncompressed_pk.compressed = false;
-
-        descriptor!(wsh(v: pk(uncompressed_pk))).unwrap();
-    }
-
-    #[test]
-    fn test_dsl_tr_only_key() {
-        let private_key =
-            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
-        let (descriptor, _, _) = descriptor!(tr(private_key)).unwrap();
-
-        assert_eq!(
-            descriptor.to_string(),
-            "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#heq9m95v"
-        )
-    }
-
-    #[test]
-    fn test_dsl_tr_simple_tree() {
-        let private_key =
-            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
-        let (descriptor, _, _) =
-            descriptor!(tr(private_key, { pk(private_key), pk(private_key) })).unwrap();
-
-        assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,{pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)})#xy5fjw6d")
-    }
-
-    #[test]
-    fn test_dsl_tr_single_leaf() {
-        let private_key =
-            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
-        let (descriptor, _, _) = descriptor!(tr(private_key, pk(private_key))).unwrap();
-
-        assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c))#lzl2vmc7")
-    }
-}
diff --git a/crates/bdk/src/descriptor/error.rs b/crates/bdk/src/descriptor/error.rs
deleted file mode 100644 (file)
index b2809f2..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptor errors
-use core::fmt;
-
-/// Errors related to the parsing and usage of descriptors
-#[derive(Debug)]
-pub enum Error {
-    /// Invalid HD Key path, such as having a wildcard but a length != 1
-    InvalidHdKeyPath,
-    /// The provided descriptor doesn't match its checksum
-    InvalidDescriptorChecksum,
-    /// The descriptor contains hardened derivation steps on public extended keys
-    HardenedDerivationXpub,
-    /// The descriptor contains multipath keys
-    MultiPath,
-
-    /// Error thrown while working with [`keys`](crate::keys)
-    Key(crate::keys::KeyError),
-    /// Error while extracting and manipulating policies
-    Policy(crate::descriptor::policy::PolicyError),
-
-    /// Invalid byte found in the descriptor checksum
-    InvalidDescriptorCharacter(u8),
-
-    /// BIP32 error
-    Bip32(bitcoin::bip32::Error),
-    /// Error during base58 decoding
-    Base58(bitcoin::base58::Error),
-    /// Key-related error
-    Pk(bitcoin::key::Error),
-    /// Miniscript error
-    Miniscript(miniscript::Error),
-    /// Hex decoding error
-    Hex(bitcoin::hex::HexToBytesError),
-}
-
-impl From<crate::keys::KeyError> for Error {
-    fn from(key_error: crate::keys::KeyError) -> Error {
-        match key_error {
-            crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
-            crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
-            e => Error::Key(e),
-        }
-    }
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::InvalidHdKeyPath => write!(f, "Invalid HD key path"),
-            Self::InvalidDescriptorChecksum => {
-                write!(f, "The provided descriptor doesn't match its checksum")
-            }
-            Self::HardenedDerivationXpub => write!(
-                f,
-                "The descriptor contains hardened derivation steps on public extended keys"
-            ),
-            Self::MultiPath => write!(
-                f,
-                "The descriptor contains multipath keys, which are not supported yet"
-            ),
-            Self::Key(err) => write!(f, "Key error: {}", err),
-            Self::Policy(err) => write!(f, "Policy error: {}", err),
-            Self::InvalidDescriptorCharacter(char) => {
-                write!(f, "Invalid descriptor character: {}", char)
-            }
-            Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
-            Self::Base58(err) => write!(f, "Base58 error: {}", err),
-            Self::Pk(err) => write!(f, "Key-related error: {}", err),
-            Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
-            Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for Error {}
-
-impl From<bitcoin::bip32::Error> for Error {
-    fn from(err: bitcoin::bip32::Error) -> Self {
-        Error::Bip32(err)
-    }
-}
-
-impl From<bitcoin::base58::Error> for Error {
-    fn from(err: bitcoin::base58::Error) -> Self {
-        Error::Base58(err)
-    }
-}
-
-impl From<bitcoin::key::Error> for Error {
-    fn from(err: bitcoin::key::Error) -> Self {
-        Error::Pk(err)
-    }
-}
-
-impl From<miniscript::Error> for Error {
-    fn from(err: miniscript::Error) -> Self {
-        Error::Miniscript(err)
-    }
-}
-
-impl From<bitcoin::hex::HexToBytesError> for Error {
-    fn from(err: bitcoin::hex::HexToBytesError) -> Self {
-        Error::Hex(err)
-    }
-}
-
-impl From<crate::descriptor::policy::PolicyError> for Error {
-    fn from(err: crate::descriptor::policy::PolicyError) -> Self {
-        Error::Policy(err)
-    }
-}
diff --git a/crates/bdk/src/descriptor/mod.rs b/crates/bdk/src/descriptor/mod.rs
deleted file mode 100644 (file)
index 4b1135f..0000000
+++ /dev/null
@@ -1,900 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptors
-//!
-//! This module contains generic utilities to work with descriptors, plus some re-exported types
-//! from [`miniscript`].
-
-use crate::collections::BTreeMap;
-use alloc::string::String;
-use alloc::vec::Vec;
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub};
-use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
-use bitcoin::{psbt, taproot};
-use bitcoin::{Network, TxOut};
-
-use miniscript::descriptor::{
-    DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
-    DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
-};
-pub use miniscript::{
-    Descriptor, DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
-};
-use miniscript::{ForEachKey, MiniscriptKey, TranslatePk};
-
-use crate::descriptor::policy::BuildSatisfaction;
-
-pub mod checksum;
-#[doc(hidden)]
-pub mod dsl;
-pub mod error;
-pub mod policy;
-pub mod template;
-
-pub use self::checksum::calc_checksum;
-use self::checksum::calc_checksum_bytes;
-pub use self::error::Error as DescriptorError;
-pub use self::policy::Policy;
-use self::template::DescriptorTemplateOut;
-use crate::keys::{IntoDescriptorKey, KeyError};
-use crate::wallet::signer::SignersContainer;
-use crate::wallet::utils::SecpCtx;
-
-/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
-pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
-
-/// Alias for a [`Descriptor`] that contains extended **derived** keys
-pub type DerivedDescriptor = Descriptor<DefiniteDescriptorKey>;
-
-/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
-/// [`psbt::Output`]
-///
-/// [`psbt::Input`]: bitcoin::psbt::Input
-/// [`psbt::Output`]: bitcoin::psbt::Output
-pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
-
-/// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or
-/// [`psbt::Output`]
-///
-/// [`psbt::Input`]: bitcoin::psbt::Input
-/// [`psbt::Output`]: bitcoin::psbt::Output
-pub type TapKeyOrigins = BTreeMap<XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
-
-/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
-pub trait IntoWalletDescriptor {
-    /// Convert to wallet descriptor
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
-}
-
-impl IntoWalletDescriptor for &str {
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        let descriptor = match self.split_once('#') {
-            Some((desc, original_checksum)) => {
-                let checksum = calc_checksum_bytes(desc)?;
-                if original_checksum.as_bytes() != checksum {
-                    return Err(DescriptorError::InvalidDescriptorChecksum);
-                }
-                desc
-            }
-            None => self,
-        };
-
-        ExtendedDescriptor::parse_descriptor(secp, descriptor)?
-            .into_wallet_descriptor(secp, network)
-    }
-}
-
-impl IntoWalletDescriptor for &String {
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        self.as_str().into_wallet_descriptor(secp, network)
-    }
-}
-
-impl IntoWalletDescriptor for ExtendedDescriptor {
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        (self, KeyMap::default()).into_wallet_descriptor(secp, network)
-    }
-}
-
-impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        use crate::keys::DescriptorKey;
-
-        struct Translator<'s, 'd> {
-            secp: &'s SecpCtx,
-            descriptor: &'d ExtendedDescriptor,
-            network: Network,
-        }
-
-        impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
-            for Translator<'s, 'd>
-        {
-            fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
-                let secp = &self.secp;
-
-                let (_, _, networks) = if self.descriptor.is_taproot() {
-                    let descriptor_key: DescriptorKey<miniscript::Tap> =
-                        pk.clone().into_descriptor_key()?;
-                    descriptor_key.extract(secp)?
-                } else if self.descriptor.is_witness() {
-                    let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
-                        pk.clone().into_descriptor_key()?;
-                    descriptor_key.extract(secp)?
-                } else {
-                    let descriptor_key: DescriptorKey<miniscript::Legacy> =
-                        pk.clone().into_descriptor_key()?;
-                    descriptor_key.extract(secp)?
-                };
-
-                if networks.contains(&self.network) {
-                    Ok(Default::default())
-                } else {
-                    Err(DescriptorError::Key(KeyError::InvalidNetwork))
-                }
-            }
-            fn sha256(
-                &mut self,
-                _sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
-            ) -> Result<String, DescriptorError> {
-                Ok(Default::default())
-            }
-            fn hash256(
-                &mut self,
-                _hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
-            ) -> Result<String, DescriptorError> {
-                Ok(Default::default())
-            }
-            fn ripemd160(
-                &mut self,
-                _ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
-            ) -> Result<String, DescriptorError> {
-                Ok(Default::default())
-            }
-            fn hash160(
-                &mut self,
-                _hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
-            ) -> Result<String, DescriptorError> {
-                Ok(Default::default())
-            }
-        }
-
-        // check the network for the keys
-        use miniscript::TranslateErr;
-        match self.0.translate_pk(&mut Translator {
-            secp,
-            network,
-            descriptor: &self.0,
-        }) {
-            Ok(_) => {}
-            Err(TranslateErr::TranslatorErr(e)) => return Err(e),
-            Err(TranslateErr::OuterError(e)) => return Err(e.into()),
-        }
-
-        Ok(self)
-    }
-}
-
-impl IntoWalletDescriptor for DescriptorTemplateOut {
-    fn into_wallet_descriptor(
-        self,
-        _secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        struct Translator {
-            network: Network,
-        }
-
-        impl miniscript::Translator<DescriptorPublicKey, DescriptorPublicKey, DescriptorError>
-            for Translator
-        {
-            fn pk(
-                &mut self,
-                pk: &DescriptorPublicKey,
-            ) -> Result<DescriptorPublicKey, DescriptorError> {
-                // workaround for xpubs generated by other key types, like bip39: since when the
-                // conversion is made one network has to be chosen, what we generally choose
-                // "mainnet", but then override the set of valid networks to specify that all of
-                // them are valid. here we reset the network to make sure the wallet struct gets a
-                // descriptor with the right network everywhere.
-                let pk = match pk {
-                    DescriptorPublicKey::XPub(ref xpub) => {
-                        let mut xpub = xpub.clone();
-                        xpub.xkey.network = self.network;
-
-                        DescriptorPublicKey::XPub(xpub)
-                    }
-                    other => other.clone(),
-                };
-
-                Ok(pk)
-            }
-            miniscript::translate_hash_clone!(
-                DescriptorPublicKey,
-                DescriptorPublicKey,
-                DescriptorError
-            );
-        }
-
-        let (desc, keymap, networks) = self;
-
-        if !networks.contains(&network) {
-            return Err(DescriptorError::Key(KeyError::InvalidNetwork));
-        }
-
-        // fixup the network for keys that need it in the descriptor
-        use miniscript::TranslateErr;
-        let translated = match desc.translate_pk(&mut Translator { network }) {
-            Ok(descriptor) => descriptor,
-            Err(TranslateErr::TranslatorErr(e)) => return Err(e),
-            Err(TranslateErr::OuterError(e)) => return Err(e.into()),
-        };
-        // ...and in the key map
-        let fixed_keymap = keymap
-            .into_iter()
-            .map(|(mut k, mut v)| {
-                match (&mut k, &mut v) {
-                    (DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
-                        xpub.xkey.network = network;
-                        xprv.xkey.network = network;
-                    }
-                    (_, DescriptorSecretKey::Single(key)) => {
-                        key.key.network = network;
-                    }
-                    _ => {}
-                }
-
-                (k, v)
-            })
-            .collect();
-
-        Ok((translated, fixed_keymap))
-    }
-}
-
-/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
-/// descriptor
-pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
-    inner: T,
-    secp: &SecpCtx,
-    network: Network,
-) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-    let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
-
-    // Ensure the keys don't contain any hardened derivation steps or hardened wildcards
-    let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
-        if let DescriptorPublicKey::XPub(DescriptorXKey {
-            derivation_path,
-            wildcard,
-            ..
-        }) = k
-        {
-            return *wildcard == Wildcard::Hardened
-                || derivation_path.into_iter().any(ChildNumber::is_hardened);
-        }
-
-        false
-    });
-    if descriptor_contains_hardened_steps {
-        return Err(DescriptorError::HardenedDerivationXpub);
-    }
-
-    if descriptor.is_multipath() {
-        return Err(DescriptorError::MultiPath);
-    }
-
-    // Run miniscript's sanity check, which will look for duplicated keys and other potential
-    // issues
-    descriptor.sanity_check()?;
-
-    Ok((descriptor, keymap))
-}
-
-#[doc(hidden)]
-/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
-pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
-    fn check_miniscript(&self) -> Result<(), miniscript::Error>;
-}
-
-impl<Ctx: miniscript::ScriptContext, Pk: miniscript::MiniscriptKey> CheckMiniscript<Ctx>
-    for miniscript::Miniscript<Pk, Ctx>
-{
-    fn check_miniscript(&self) -> Result<(), miniscript::Error> {
-        Ctx::check_global_validity(self)?;
-
-        Ok(())
-    }
-}
-
-/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]
-pub trait ExtractPolicy {
-    /// Extract the spending [`policy`]
-    fn extract_policy(
-        &self,
-        signers: &SignersContainer,
-        psbt: BuildSatisfaction,
-        secp: &SecpCtx,
-    ) -> Result<Option<Policy>, DescriptorError>;
-}
-
-pub(crate) trait XKeyUtils {
-    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
-}
-
-impl<T> XKeyUtils for DescriptorMultiXKey<T>
-where
-    T: InnerXKey,
-{
-    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
-        match self.origin {
-            Some((fingerprint, _)) => fingerprint,
-            None => self.xkey.xkey_fingerprint(secp),
-        }
-    }
-}
-
-impl<T> XKeyUtils for DescriptorXKey<T>
-where
-    T: InnerXKey,
-{
-    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
-        match self.origin {
-            Some((fingerprint, _)) => fingerprint,
-            None => self.xkey.xkey_fingerprint(secp),
-        }
-    }
-}
-
-pub(crate) trait DescriptorMeta {
-    fn is_witness(&self) -> bool;
-    fn is_taproot(&self) -> bool;
-    fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>>;
-    fn derive_from_hd_keypaths(
-        &self,
-        hd_keypaths: &HdKeyPaths,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor>;
-    fn derive_from_tap_key_origins(
-        &self,
-        tap_key_origins: &TapKeyOrigins,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor>;
-    fn derive_from_psbt_key_origins(
-        &self,
-        key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor>;
-    fn derive_from_psbt_input(
-        &self,
-        psbt_input: &psbt::Input,
-        utxo: Option<TxOut>,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor>;
-}
-
-impl DescriptorMeta for ExtendedDescriptor {
-    fn is_witness(&self) -> bool {
-        matches!(
-            self.desc_type(),
-            DescriptorType::Wpkh
-                | DescriptorType::ShWpkh
-                | DescriptorType::Wsh
-                | DescriptorType::ShWsh
-                | DescriptorType::ShWshSortedMulti
-                | DescriptorType::WshSortedMulti
-        )
-    }
-
-    fn is_taproot(&self) -> bool {
-        self.desc_type() == DescriptorType::Tr
-    }
-
-    fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>> {
-        let mut answer = Vec::new();
-
-        self.for_each_key(|pk| {
-            if let DescriptorPublicKey::XPub(xpub) = pk {
-                answer.push(xpub.clone());
-            }
-
-            true
-        });
-
-        answer
-    }
-
-    fn derive_from_psbt_key_origins(
-        &self,
-        key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor> {
-        // Ensure that deriving `xpub` with `path` yields `expected`
-        let verify_key =
-            |xpub: &DescriptorXKey<Xpub>, path: &DerivationPath, expected: &SinglePubKey| {
-                let derived = xpub
-                    .xkey
-                    .derive_pub(secp, path)
-                    .expect("The path should never contain hardened derivation steps")
-                    .public_key;
-
-                match expected {
-                    SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
-                    SinglePubKey::XOnly(pk) if &XOnlyPublicKey::from(derived) == pk => true,
-                    _ => false,
-                }
-            };
-
-        let mut path_found = None;
-
-        // using `for_any_key` should make this stop as soon as we return `true`
-        self.for_any_key(|key| {
-            if let DescriptorPublicKey::XPub(xpub) = key {
-                // Check if the key matches one entry in our `key_origins`. If it does, `matches()` will
-                // return the "prefix" that matched, so we remove that prefix from the full path
-                // found in `key_origins` and save it in `derive_path`. We expect this to be a derivation
-                // path of length 1 if the key is `wildcard` and an empty path otherwise.
-                let root_fingerprint = xpub.root_fingerprint(secp);
-                let derive_path = key_origins
-                    .get_key_value(&root_fingerprint)
-                    .and_then(|(fingerprint, (path, expected))| {
-                        xpub.matches(&(*fingerprint, (*path).clone()), secp)
-                            .zip(Some((path, expected)))
-                    })
-                    .and_then(|(prefix, (full_path, expected))| {
-                        let derive_path = full_path
-                            .into_iter()
-                            .skip(prefix.into_iter().count())
-                            .cloned()
-                            .collect::<DerivationPath>();
-
-                        // `derive_path` only contains the replacement index for the wildcard, if present, or
-                        // an empty path for fixed descriptors. To verify the key we also need the normal steps
-                        // that come before the wildcard, so we take them directly from `xpub` and then append
-                        // the final index
-                        if verify_key(
-                            xpub,
-                            &xpub.derivation_path.extend(derive_path.clone()),
-                            expected,
-                        ) {
-                            Some(derive_path)
-                        } else {
-                            None
-                        }
-                    });
-
-                match derive_path {
-                    Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => {
-                        // Ignore hardened wildcards
-                        if let ChildNumber::Normal { index } = path[0] {
-                            path_found = Some(index);
-                            return true;
-                        }
-                    }
-                    Some(path) if xpub.wildcard == Wildcard::None && path.is_empty() => {
-                        path_found = Some(0);
-                        return true;
-                    }
-                    _ => {}
-                }
-            }
-
-            false
-        });
-
-        path_found.map(|path| {
-            self.at_derivation_index(path)
-                .expect("We ignore hardened wildcards")
-        })
-    }
-
-    fn derive_from_hd_keypaths(
-        &self,
-        hd_keypaths: &HdKeyPaths,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor> {
-        // "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins`
-        let key_origins = hd_keypaths
-            .iter()
-            .map(|(pk, (fingerprint, path))| {
-                (
-                    *fingerprint,
-                    (path, SinglePubKey::FullKey(PublicKey::new(*pk))),
-                )
-            })
-            .collect();
-        self.derive_from_psbt_key_origins(key_origins, secp)
-    }
-
-    fn derive_from_tap_key_origins(
-        &self,
-        tap_key_origins: &TapKeyOrigins,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor> {
-        // "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins`
-        let key_origins = tap_key_origins
-            .iter()
-            .map(|(pk, (_, (fingerprint, path)))| (*fingerprint, (path, SinglePubKey::XOnly(*pk))))
-            .collect();
-        self.derive_from_psbt_key_origins(key_origins, secp)
-    }
-
-    fn derive_from_psbt_input(
-        &self,
-        psbt_input: &psbt::Input,
-        utxo: Option<TxOut>,
-        secp: &SecpCtx,
-    ) -> Option<DerivedDescriptor> {
-        if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
-            return Some(derived);
-        }
-        if let Some(derived) = self.derive_from_tap_key_origins(&psbt_input.tap_key_origins, secp) {
-            return Some(derived);
-        }
-        if self.has_wildcard() {
-            // We can't try to bruteforce the derivation index, exit here
-            return None;
-        }
-
-        let descriptor = self.at_derivation_index(0).expect("0 is not hardened");
-        match descriptor.desc_type() {
-            // TODO: add pk() here
-            DescriptorType::Pkh
-            | DescriptorType::Wpkh
-            | DescriptorType::ShWpkh
-            | DescriptorType::Tr
-                if utxo.is_some()
-                    && descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
-            {
-                Some(descriptor)
-            }
-            DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
-                if psbt_input.redeem_script.is_some()
-                    && &descriptor.explicit_script().unwrap()
-                        == psbt_input.redeem_script.as_ref().unwrap() =>
-            {
-                Some(descriptor)
-            }
-            DescriptorType::Wsh
-            | DescriptorType::ShWsh
-            | DescriptorType::ShWshSortedMulti
-            | DescriptorType::WshSortedMulti
-                if psbt_input.witness_script.is_some()
-                    && &descriptor.explicit_script().unwrap()
-                        == psbt_input.witness_script.as_ref().unwrap() =>
-            {
-                Some(descriptor)
-            }
-            _ => None,
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use alloc::string::ToString;
-    use core::str::FromStr;
-
-    use assert_matches::assert_matches;
-    use bitcoin::hex::FromHex;
-    use bitcoin::secp256k1::Secp256k1;
-    use bitcoin::ScriptBuf;
-    use bitcoin::{bip32, Psbt};
-
-    use super::*;
-    use crate::psbt::PsbtUtils;
-
-    #[test]
-    fn test_derive_from_psbt_input_wpkh_wif() {
-        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
-            "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
-        )
-        .unwrap();
-        let psbt = Psbt::deserialize(
-            &Vec::<u8>::from_hex(
-                "70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
-                 11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
-                 001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa086\
-                 01000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304\
-                 010000000000",
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        assert!(descriptor
-            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
-            .is_some());
-    }
-
-    #[test]
-    fn test_derive_from_psbt_input_pkh_tpub() {
-        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
-            "pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
-        )
-        .unwrap();
-        let psbt = Psbt::deserialize(
-            &Vec::<u8>::from_hex(
-                "70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\
-                 e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\
-                 a91432bb94283282f72b2e034709e348c44d5a4db0ef8700000000000100f902\
-                 0000000001010167e99c0eb67640f3a1b6805f2d8be8238c947f8aaf49eb0a9c\
-                 bee6a42c984200000000171600142b29a22019cca05b9c2b2d283a4c4489e1cf\
-                 9f8ffeffffff02a01dced06100000017a914e2abf033cadbd74f0f4c74946201\
-                 decd20d5c43c8780969800000000001976a9148b0fce5fb1264e599a65387313\
-                 3c95478b902eb288ac02473044022015d9211576163fa5b001e84dfa3d44efd9\
-                 86b8f3a0d3d2174369288b2b750906022048dacc0e5d73ae42512fd2b97e2071\
-                 a8d0bce443b390b1fe0b8128fe70ec919e01210232dad1c5a67dcb0116d407e2\
-                 52584228ab7ec00e8b9779d0c3ffe8114fc1a7d2c80600000103040100000022\
-                 0603433b83583f8c4879b329dd08bbc7da935e4cc02f637ff746e05f0466ffb2\
-                 a6a2180f0569432c00008000000080000000800a000000000000000000",
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        assert!(descriptor
-            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
-            .is_some());
-    }
-
-    #[test]
-    fn test_derive_from_psbt_input_wsh() {
-        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
-            "wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))",
-        )
-        .unwrap();
-        let psbt = Psbt::deserialize(
-            &Vec::<u8>::from_hex(
-                "70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\
-                 67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\
-                 a914ad105f61102e0d01d7af40d06d6a5c3ae2f7fde387000000000001012b80\
-                 969800000000002200203ca72f106a72234754890ca7640c43f65d2174e44d33\
-                 336030f9059345091044010304010000000105252103b6633fef2397a0a9de9d\
-                 7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14ad56b20000",
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        assert!(descriptor
-            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
-            .is_some());
-    }
-
-    #[test]
-    fn test_derive_from_psbt_input_sh() {
-        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
-            "sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))",
-        )
-        .unwrap();
-        let psbt = Psbt::deserialize(
-            &Vec::<u8>::from_hex(
-                "70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\
-                 a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\
-                 a91457b148ba4d3e5fa8608a8657875124e3d1c9390887f09c0900000100e002\
-                 0000000001016ba1bbe05cc93574a0d611ec7d93ad0ab6685b28d0cd80e8a82d\
-                 debb326643c90100000000feffffff02809698000000000017a914d9a6e8c455\
-                 8e16c8253afe53ce37ad61cf4c38c487403504cf6100000017a9144044fb6e0b\
-                 757dfc1b34886b6a95aef4d3db137e870247304402202a9b72d939bcde8ba2a1\
-                 e0980597e47af4f5c152a78499143c3d0a78ac2286a602207a45b1df9e93b8c9\
-                 6f09f5c025fe3e413ca4b905fe65ee55d32a3276439a9b8f012102dc1fcc2636\
-                 4da1aa718f03d8d9bd6f2ff410ed2cf1245a168aa3bcc995ac18e0a806000001\
-                 03040100000001042821021403881a5587297818fcaf17d239cefca22fce84a4\
-                 5b3b1d23e836c4af671dbbad03f09c09b10000",
-            )
-            .unwrap(),
-        )
-        .unwrap();
-
-        assert!(descriptor
-            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
-            .is_some());
-    }
-
-    #[test]
-    fn test_to_wallet_descriptor_fixup_networks() {
-        use crate::keys::{any_network, IntoDescriptorKey};
-
-        let secp = Secp256k1::new();
-
-        let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
-        let path = bip32::DerivationPath::from_str("m/0").unwrap();
-
-        // here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
-        // we are using an "xpub"
-        let key = (xprv, path.clone()).into_descriptor_key().unwrap();
-        // override it with any. this happens in some key conversions, like bip39
-        let key = key.override_valid_networks(any_network());
-
-        // make a descriptor out of it
-        let desc = crate::descriptor!(wpkh(key)).unwrap();
-        // this should convert the key that supports "any_network" to the right network (testnet)
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-
-        let mut xprv_testnet = xprv;
-        xprv_testnet.network = Network::Testnet;
-
-        let xpub_testnet = bip32::Xpub::from_priv(&secp, &xprv_testnet);
-        let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
-            xkey: xpub_testnet,
-            origin: None,
-            derivation_path: path,
-            wildcard: Wildcard::Unhardened,
-        });
-
-        assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
-        assert_eq!(
-            keymap
-                .get(&desc_pubkey)
-                .map(|key| key.to_public(&secp).unwrap()),
-            Some(desc_pubkey)
-        );
-    }
-
-    // test IntoWalletDescriptor trait from &str with and without checksum appended
-    #[test]
-    fn test_descriptor_from_str_with_checksum() {
-        let secp = Secp256k1::new();
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
-    }
-
-    // test IntoWalletDescriptor trait from &str with keys from right and wrong network
-    #[test]
-    fn test_descriptor_from_str_with_keys_network() {
-        let secp = Secp256k1::new();
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Regtest);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Regtest);
-        assert!(desc.is_ok());
-
-        let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
-            .into_wallet_descriptor(&secp, Network::Testnet);
-        assert!(desc.is_ok());
-
-        let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
-            .into_wallet_descriptor(&secp, Network::Bitcoin);
-        assert!(desc.is_ok());
-
-        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Bitcoin);
-        assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
-
-        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .into_wallet_descriptor(&secp, Network::Bitcoin);
-        assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
-    }
-
-    // test IntoWalletDescriptor trait from the output of the descriptor!() macro
-    #[test]
-    fn test_descriptor_from_str_from_output_of_macro() {
-        let secp = Secp256k1::new();
-
-        let tpub = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
-        let path = bip32::DerivationPath::from_str("m/1/2").unwrap();
-        let key = (tpub, path).into_descriptor_key().unwrap();
-
-        // make a descriptor out of it
-        let desc = crate::descriptor!(wpkh(key)).unwrap();
-
-        let (wallet_desc, _) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let wallet_desc_str = wallet_desc.to_string();
-        assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw");
-
-        let (wallet_desc2, _) = wallet_desc_str
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        assert_eq!(wallet_desc, wallet_desc2)
-    }
-
-    #[test]
-    fn test_into_wallet_descriptor_checked() {
-        let secp = Secp256k1::new();
-
-        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
-        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
-
-        assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
-
-        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
-        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
-
-        assert_matches!(result, Err(DescriptorError::MultiPath));
-
-        // repeated pubkeys
-        let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
-        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
-
-        assert!(result.is_err());
-    }
-
-    #[test]
-    fn test_sh_wsh_sortedmulti_redeemscript() {
-        use miniscript::psbt::PsbtInputExt;
-
-        let secp = Secp256k1::new();
-
-        let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))";
-        let (descriptor, _) =
-            into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
-
-        let descriptor = descriptor.at_derivation_index(0).unwrap();
-
-        let script = ScriptBuf::from_hex("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
-
-        let mut psbt_input = psbt::Input::default();
-        psbt_input
-            .update_with_descriptor_unchecked(&descriptor)
-            .unwrap();
-
-        assert_eq!(psbt_input.redeem_script, Some(script.to_p2wsh()));
-        assert_eq!(psbt_input.witness_script, Some(script));
-    }
-}
diff --git a/crates/bdk/src/descriptor/policy.rs b/crates/bdk/src/descriptor/policy.rs
deleted file mode 100644 (file)
index 820bf2d..0000000
+++ /dev/null
@@ -1,1905 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptor policy
-//!
-//! This module implements the logic to extract and represent the spending policies of a descriptor
-//! in a more human-readable format.
-//!
-//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
-//!
-//! ## Example
-//!
-//! ```
-//! # use std::sync::Arc;
-//! # use bdk::descriptor::*;
-//! # use bdk::wallet::signer::*;
-//! # use bdk::bitcoin::secp256k1::Secp256k1;
-//! use bdk::descriptor::policy::BuildSatisfaction;
-//! let secp = Secp256k1::new();
-//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
-//!
-//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
-//! println!("{:?}", extended_desc);
-//!
-//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp));
-//! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
-//! println!("policy: {}", serde_json::to_string(&policy).unwrap());
-//! # Ok::<(), anyhow::Error>(())
-//! ```
-
-use crate::collections::{BTreeMap, HashSet, VecDeque};
-use alloc::string::String;
-use alloc::vec::Vec;
-use core::cmp::max;
-
-use core::fmt;
-
-use serde::ser::SerializeMap;
-use serde::{Serialize, Serializer};
-
-use bitcoin::bip32::Fingerprint;
-use bitcoin::hashes::{hash160, ripemd160, sha256};
-use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
-
-use miniscript::descriptor::{
-    DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
-};
-use miniscript::hash256;
-use miniscript::{
-    Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
-};
-
-use crate::descriptor::ExtractPolicy;
-use crate::keys::ExtScriptContext;
-use crate::wallet::signer::{SignerId, SignersContainer};
-use crate::wallet::utils::{After, Older, SecpCtx};
-
-use super::checksum::calc_checksum;
-use super::error::Error;
-use super::XKeyUtils;
-use bitcoin::psbt::{self, Psbt};
-use miniscript::psbt::PsbtInputSatisfier;
-
-/// A unique identifier for a key
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
-#[serde(rename_all = "snake_case")]
-pub enum PkOrF {
-    /// A legacy public key
-    Pubkey(PublicKey),
-    /// A x-only public key
-    XOnlyPubkey(XOnlyPublicKey),
-    /// An extended key fingerprint
-    Fingerprint(Fingerprint),
-}
-
-impl PkOrF {
-    fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
-        match k {
-            DescriptorPublicKey::Single(SinglePub {
-                key: SinglePubKey::FullKey(pk),
-                ..
-            }) => PkOrF::Pubkey(*pk),
-            DescriptorPublicKey::Single(SinglePub {
-                key: SinglePubKey::XOnly(pk),
-                ..
-            }) => PkOrF::XOnlyPubkey(*pk),
-            DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
-            DescriptorPublicKey::MultiXPub(multi) => {
-                PkOrF::Fingerprint(multi.root_fingerprint(secp))
-            }
-        }
-    }
-}
-
-/// An item that needs to be satisfied
-#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
-#[serde(tag = "type", rename_all = "UPPERCASE")]
-pub enum SatisfiableItem {
-    // Leaves
-    /// ECDSA Signature for a raw public key
-    EcdsaSignature(PkOrF),
-    /// Schnorr Signature for a raw public key
-    SchnorrSignature(PkOrF),
-    /// SHA256 preimage hash
-    Sha256Preimage {
-        /// The digest value
-        hash: sha256::Hash,
-    },
-    /// Double SHA256 preimage hash
-    Hash256Preimage {
-        /// The digest value
-        hash: hash256::Hash,
-    },
-    /// RIPEMD160 preimage hash
-    Ripemd160Preimage {
-        /// The digest value
-        hash: ripemd160::Hash,
-    },
-    /// SHA256 then RIPEMD160 preimage hash
-    Hash160Preimage {
-        /// The digest value
-        hash: hash160::Hash,
-    },
-    /// Absolute timeclock timestamp
-    AbsoluteTimelock {
-        /// The timelock value
-        value: absolute::LockTime,
-    },
-    /// Relative timelock locktime
-    RelativeTimelock {
-        /// The timelock value
-        value: Sequence,
-    },
-    /// Multi-signature public keys with threshold count
-    Multisig {
-        /// The raw public key or extended key fingerprint
-        keys: Vec<PkOrF>,
-        /// The required threshold count
-        threshold: usize,
-    },
-
-    // Complex item
-    /// Threshold items with threshold count
-    Thresh {
-        /// The policy items
-        items: Vec<Policy>,
-        /// The required threshold count
-        threshold: usize,
-    },
-}
-
-impl SatisfiableItem {
-    /// Returns whether the [`SatisfiableItem`] is a leaf item
-    pub fn is_leaf(&self) -> bool {
-        !matches!(
-            self,
-            SatisfiableItem::Thresh {
-                items: _,
-                threshold: _,
-            }
-        )
-    }
-
-    /// Returns a unique id for the [`SatisfiableItem`]
-    pub fn id(&self) -> String {
-        calc_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
-            .expect("Failed to compute a SatisfiableItem id")
-    }
-}
-
-fn combinations(vec: &[usize], size: usize) -> Vec<Vec<usize>> {
-    assert!(vec.len() >= size);
-
-    let mut answer = Vec::new();
-
-    let mut queue = VecDeque::new();
-    for (index, val) in vec.iter().enumerate() {
-        let mut new_vec = Vec::with_capacity(size);
-        new_vec.push(*val);
-        queue.push_back((index, new_vec));
-    }
-
-    while let Some((index, vals)) = queue.pop_front() {
-        if vals.len() >= size {
-            answer.push(vals);
-        } else {
-            for (new_index, val) in vec.iter().skip(index + 1).enumerate() {
-                let mut cloned = vals.clone();
-                cloned.push(*val);
-                queue.push_front((new_index, cloned));
-            }
-        }
-    }
-
-    answer
-}
-
-fn mix<T: Clone>(vec: Vec<Vec<T>>) -> Vec<Vec<T>> {
-    if vec.is_empty() || vec.iter().any(Vec::is_empty) {
-        return vec![];
-    }
-
-    let mut answer = Vec::new();
-    let size = vec.len();
-
-    let mut queue = VecDeque::new();
-    for i in &vec[0] {
-        let mut new_vec = Vec::with_capacity(size);
-        new_vec.push(i.clone());
-        queue.push_back(new_vec);
-    }
-
-    while let Some(vals) = queue.pop_front() {
-        if vals.len() >= size {
-            answer.push(vals);
-        } else {
-            let level = vals.len();
-            for i in &vec[level] {
-                let mut cloned = vals.clone();
-                cloned.push(i.clone());
-                queue.push_front(cloned);
-            }
-        }
-    }
-
-    answer
-}
-
-/// Type for a map of sets of [`Condition`] items keyed by each set's index
-pub type ConditionMap = BTreeMap<usize, HashSet<Condition>>;
-/// Type for a map of folded sets of [`Condition`] items keyed by a vector of the combined set's indexes
-pub type FoldedConditionMap = BTreeMap<Vec<usize>, HashSet<Condition>>;
-
-fn serialize_folded_cond_map<S>(
-    input_map: &FoldedConditionMap,
-    serializer: S,
-) -> Result<S::Ok, S::Error>
-where
-    S: Serializer,
-{
-    let mut map = serializer.serialize_map(Some(input_map.len()))?;
-    for (k, v) in input_map {
-        let k_string = format!("{:?}", k);
-        map.serialize_entry(&k_string, v)?;
-    }
-    map.end()
-}
-
-/// Represent if and how much a policy item is satisfied by the wallet's descriptor
-#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
-#[serde(tag = "type", rename_all = "UPPERCASE")]
-pub enum Satisfaction {
-    /// Only a partial satisfaction of some kind of threshold policy
-    Partial {
-        /// Total number of items
-        n: usize,
-        /// Threshold
-        m: usize,
-        /// The items that can be satisfied by the descriptor or are satisfied in the PSBT
-        items: Vec<usize>,
-        #[serde(skip_serializing_if = "Option::is_none")]
-        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
-        sorted: Option<bool>,
-        #[serde(skip_serializing_if = "BTreeMap::is_empty")]
-        /// Extra conditions that also need to be satisfied
-        conditions: ConditionMap,
-    },
-    /// Can reach the threshold of some kind of threshold policy
-    PartialComplete {
-        /// Total number of items
-        n: usize,
-        /// Threshold
-        m: usize,
-        /// The items that can be satisfied by the descriptor
-        items: Vec<usize>,
-        #[serde(skip_serializing_if = "Option::is_none")]
-        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
-        sorted: Option<bool>,
-        #[serde(
-            serialize_with = "serialize_folded_cond_map",
-            skip_serializing_if = "BTreeMap::is_empty"
-        )]
-        /// Extra conditions that also need to be satisfied
-        conditions: FoldedConditionMap,
-    },
-
-    /// Can satisfy the policy item
-    Complete {
-        /// Extra conditions that also need to be satisfied
-        condition: Condition,
-    },
-    /// Cannot satisfy or contribute to the policy item
-    None,
-}
-
-impl Satisfaction {
-    /// Returns whether the [`Satisfaction`] is a leaf item
-    pub fn is_leaf(&self) -> bool {
-        match self {
-            Satisfaction::None | Satisfaction::Complete { .. } => true,
-            Satisfaction::PartialComplete { .. } | Satisfaction::Partial { .. } => false,
-        }
-    }
-
-    // add `inner` as one of self's partial items. this only makes sense on partials
-    fn add(&mut self, inner: &Satisfaction, inner_index: usize) -> Result<(), PolicyError> {
-        match self {
-            Satisfaction::None | Satisfaction::Complete { .. } => Err(PolicyError::AddOnLeaf),
-            Satisfaction::PartialComplete { .. } => Err(PolicyError::AddOnPartialComplete),
-            Satisfaction::Partial {
-                n,
-                ref mut conditions,
-                ref mut items,
-                ..
-            } => {
-                if inner_index >= *n || items.contains(&inner_index) {
-                    return Err(PolicyError::IndexOutOfRange(inner_index));
-                }
-
-                match inner {
-                    // not relevant if not completed yet
-                    Satisfaction::None | Satisfaction::Partial { .. } => return Ok(()),
-                    Satisfaction::Complete { condition } => {
-                        items.push(inner_index);
-                        conditions.insert(inner_index, vec![*condition].into_iter().collect());
-                    }
-                    Satisfaction::PartialComplete {
-                        conditions: other_conditions,
-                        ..
-                    } => {
-                        items.push(inner_index);
-                        let conditions_set = other_conditions
-                            .values()
-                            .fold(HashSet::new(), |set, i| set.union(i).cloned().collect());
-                        conditions.insert(inner_index, conditions_set);
-                    }
-                }
-
-                Ok(())
-            }
-        }
-    }
-
-    fn finalize(&mut self) {
-        // if partial try to bump it to a partialcomplete
-        if let Satisfaction::Partial {
-            n,
-            m,
-            items,
-            conditions,
-            sorted,
-        } = self
-        {
-            if items.len() >= *m {
-                let mut map = BTreeMap::new();
-                let indexes = combinations(items, *m);
-                // `indexes` at this point is a Vec<Vec<usize>>, with the "n choose k" of items (m of n)
-                indexes
-                    .into_iter()
-                    // .inspect(|x| println!("--- orig --- {:?}", x))
-                    // we map each of the combinations of elements into a tuple of ([chosen items], [conditions]). unfortunately, those items have potentially more than one
-                    // condition (think about ORs), so we also use `mix` to expand those, i.e. [[0], [1, 2]] becomes [[0, 1], [0, 2]]. This is necessary to make sure that we
-                    // consider every possible options and check whether or not they are compatible.
-                    // since this step can turn one item of the iterator into multiple ones, we use `flat_map()` to expand them out
-                    .flat_map(|i_vec| {
-                        mix(i_vec
-                            .iter()
-                            .map(|i| {
-                                conditions
-                                    .get(i)
-                                    .map(|set| set.clone().into_iter().collect())
-                                    .unwrap_or_default()
-                            })
-                            .collect())
-                        .into_iter()
-                        .map(|x| (i_vec.clone(), x))
-                        .collect::<Vec<(Vec<usize>, Vec<Condition>)>>()
-                    })
-                    // .inspect(|x| println!("flat {:?}", x))
-                    // try to fold all the conditions for this specific combination of indexes/options. if they are not compatible, try_fold will be Err
-                    .map(|(key, val)| {
-                        (
-                            key,
-                            val.into_iter()
-                                .try_fold(Condition::default(), |acc, v| acc.merge(&v)),
-                        )
-                    })
-                    // .inspect(|x| println!("try_fold {:?}", x))
-                    // filter out all the incompatible combinations
-                    .filter(|(_, val)| val.is_ok())
-                    // .inspect(|x| println!("filter {:?}", x))
-                    // push them into the map
-                    .for_each(|(key, val)| {
-                        map.entry(key)
-                            .or_insert_with(HashSet::new)
-                            .insert(val.unwrap());
-                    });
-                // TODO: if the map is empty, the conditions are not compatible, return an error?
-                *self = Satisfaction::PartialComplete {
-                    n: *n,
-                    m: *m,
-                    items: items.clone(),
-                    conditions: map,
-                    sorted: *sorted,
-                };
-            }
-        }
-    }
-}
-
-impl From<bool> for Satisfaction {
-    fn from(other: bool) -> Self {
-        if other {
-            Satisfaction::Complete {
-                condition: Default::default(),
-            }
-        } else {
-            Satisfaction::None
-        }
-    }
-}
-
-/// Descriptor spending policy
-#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
-pub struct Policy {
-    /// Identifier for this policy node
-    pub id: String,
-
-    /// Type of this policy node
-    #[serde(flatten)]
-    pub item: SatisfiableItem,
-    /// How much a given PSBT already satisfies this policy node in terms of signatures
-    pub satisfaction: Satisfaction,
-    /// How the wallet's descriptor can satisfy this policy node
-    pub contribution: Satisfaction,
-}
-
-/// An extra condition that must be satisfied but that is out of control of the user
-/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
-#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
-pub struct Condition {
-    /// Optional CheckSequenceVerify condition
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub csv: Option<Sequence>,
-    /// Optional timelock condition
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub timelock: Option<absolute::LockTime>,
-}
-
-impl Condition {
-    fn merge_nlocktime(
-        a: absolute::LockTime,
-        b: absolute::LockTime,
-    ) -> Result<absolute::LockTime, PolicyError> {
-        if !a.is_same_unit(b) {
-            Err(PolicyError::MixedTimelockUnits)
-        } else if a > b {
-            Ok(a)
-        } else {
-            Ok(b)
-        }
-    }
-
-    fn merge_nsequence(a: Sequence, b: Sequence) -> Result<Sequence, PolicyError> {
-        if a.is_time_locked() != b.is_time_locked() {
-            Err(PolicyError::MixedTimelockUnits)
-        } else {
-            Ok(max(a, b))
-        }
-    }
-
-    pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
-        match (self.csv, other.csv) {
-            (Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
-            (None, any) => self.csv = any,
-            _ => {}
-        }
-
-        match (self.timelock, other.timelock) {
-            (Some(a), Some(b)) => self.timelock = Some(Self::merge_nlocktime(a, b)?),
-            (None, any) => self.timelock = any,
-            _ => {}
-        }
-
-        Ok(self)
-    }
-
-    /// Returns `true` if there are no extra conditions to verify
-    pub fn is_null(&self) -> bool {
-        self.csv.is_none() && self.timelock.is_none()
-    }
-}
-
-/// Errors that can happen while extracting and manipulating policies
-#[derive(Debug, PartialEq, Eq)]
-pub enum PolicyError {
-    /// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
-    NotEnoughItemsSelected(String),
-    /// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
-    IndexOutOfRange(usize),
-    /// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
-    AddOnLeaf,
-    /// Can not add to an item that is [`Satisfaction::PartialComplete`]
-    AddOnPartialComplete,
-    /// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000
-    MixedTimelockUnits,
-    /// Incompatible conditions (not currently used)
-    IncompatibleConditions,
-}
-
-impl fmt::Display for PolicyError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::NotEnoughItemsSelected(err) => write!(f, "Not enough items selected: {}", err),
-            Self::IndexOutOfRange(index) => write!(f, "Index out of range: {}", index),
-            Self::AddOnLeaf => write!(f, "Add on leaf"),
-            Self::AddOnPartialComplete => write!(f, "Add on partial complete"),
-            Self::MixedTimelockUnits => write!(f, "Mixed timelock units"),
-            Self::IncompatibleConditions => write!(f, "Incompatible conditions"),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for PolicyError {}
-
-impl Policy {
-    fn new(item: SatisfiableItem) -> Self {
-        Policy {
-            id: item.id(),
-            item,
-            satisfaction: Satisfaction::None,
-            contribution: Satisfaction::None,
-        }
-    }
-
-    fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
-        match (a, b) {
-            (None, None) => Ok(None),
-            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
-            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
-        }
-    }
-
-    fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
-        match (a, b) {
-            (None, None) => Ok(None),
-            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
-            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
-        }
-    }
-
-    fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
-        if threshold == 0 {
-            return Ok(None);
-        }
-
-        let mut contribution = Satisfaction::Partial {
-            n: items.len(),
-            m: threshold,
-            items: vec![],
-            conditions: Default::default(),
-            sorted: None,
-        };
-        let mut satisfaction = contribution.clone();
-        for (index, item) in items.iter().enumerate() {
-            contribution.add(&item.contribution, index)?;
-            satisfaction.add(&item.satisfaction, index)?;
-        }
-
-        contribution.finalize();
-        satisfaction.finalize();
-
-        let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
-        policy.contribution = contribution;
-        policy.satisfaction = satisfaction;
-
-        Ok(Some(policy))
-    }
-
-    fn make_multisig<Ctx: ScriptContext + 'static>(
-        keys: &[DescriptorPublicKey],
-        signers: &SignersContainer,
-        build_sat: BuildSatisfaction,
-        threshold: usize,
-        sorted: bool,
-        secp: &SecpCtx,
-    ) -> Result<Option<Policy>, PolicyError> {
-        if threshold == 0 {
-            return Ok(None);
-        }
-
-        let parsed_keys = keys.iter().map(|k| PkOrF::from_key(k, secp)).collect();
-
-        let mut contribution = Satisfaction::Partial {
-            n: keys.len(),
-            m: threshold,
-            items: vec![],
-            conditions: Default::default(),
-            sorted: Some(sorted),
-        };
-        let mut satisfaction = contribution.clone();
-
-        for (index, key) in keys.iter().enumerate() {
-            if signers.find(signer_id(key, secp)).is_some() {
-                contribution.add(
-                    &Satisfaction::Complete {
-                        condition: Default::default(),
-                    },
-                    index,
-                )?;
-            }
-
-            if let Some(psbt) = build_sat.psbt() {
-                if Ctx::find_signature(psbt, key, secp) {
-                    satisfaction.add(
-                        &Satisfaction::Complete {
-                            condition: Default::default(),
-                        },
-                        index,
-                    )?;
-                }
-            }
-        }
-        satisfaction.finalize();
-        contribution.finalize();
-
-        let mut policy: Policy = SatisfiableItem::Multisig {
-            keys: parsed_keys,
-            threshold,
-        }
-        .into();
-        policy.contribution = contribution;
-        policy.satisfaction = satisfaction;
-
-        Ok(Some(policy))
-    }
-
-    /// Return whether or not a specific path in the policy tree is required to unambiguously
-    /// create a transaction
-    ///
-    /// What this means is that for some spending policies the user should select which paths in
-    /// the tree it intends to satisfy while signing, because the transaction must be created differently based
-    /// on that.
-    pub fn requires_path(&self) -> bool {
-        self.get_condition(&BTreeMap::new()).is_err()
-    }
-
-    /// Return the conditions that are set by the spending policy for a given path in the
-    /// policy tree
-    pub fn get_condition(
-        &self,
-        path: &BTreeMap<String, Vec<usize>>,
-    ) -> Result<Condition, PolicyError> {
-        // if items.len() == threshold, selected can be omitted and we take all of them by default
-        let default = match &self.item {
-            SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
-                (0..*threshold).collect()
-            }
-            SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
-            _ => HashSet::new(),
-        };
-        let selected: HashSet<_> = match path.get(&self.id) {
-            Some(arr) => arr.iter().copied().collect(),
-            _ => default,
-        };
-
-        match &self.item {
-            SatisfiableItem::Thresh { items, threshold } => {
-                let mapped_req = items
-                    .iter()
-                    .map(|i| i.get_condition(path))
-                    .collect::<Vec<_>>();
-
-                // if all the requirements are null we don't care about `selected` because there
-                // are no requirements
-                if mapped_req
-                    .iter()
-                    .all(|cond| matches!(cond, Ok(c) if c.is_null()))
-                {
-                    return Ok(Condition::default());
-                }
-
-                // make sure all the indexes in the `selected` list are within range
-                for index in &selected {
-                    if *index >= items.len() {
-                        return Err(PolicyError::IndexOutOfRange(*index));
-                    }
-                }
-
-                // if we have something, make sure we have enough items. note that the user can set
-                // an empty value for this step in case of n-of-n, because `selected` is set to all
-                // the elements above
-                if selected.len() < *threshold {
-                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
-                }
-
-                // check the selected items, see if there are conflicting requirements
-                mapped_req
-                    .into_iter()
-                    .enumerate()
-                    .filter(|(index, _)| selected.contains(index))
-                    .try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
-            }
-            SatisfiableItem::Multisig { keys, threshold } => {
-                if selected.len() < *threshold {
-                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
-                }
-                if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
-                    return Err(PolicyError::IndexOutOfRange(item));
-                }
-
-                Ok(Condition::default())
-            }
-            SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
-                csv: None,
-                timelock: Some(*value),
-            }),
-            SatisfiableItem::RelativeTimelock { value } => Ok(Condition {
-                csv: Some(*value),
-                timelock: None,
-            }),
-            _ => Ok(Condition::default()),
-        }
-    }
-}
-
-impl From<SatisfiableItem> for Policy {
-    fn from(other: SatisfiableItem) -> Self {
-        Self::new(other)
-    }
-}
-
-fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
-    // For consistency we always compute the key hash in "ecdsa" form (with the leading sign
-    // prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
-    // for a key, so it doesn't really matter how the identifier is computed.
-    match key {
-        DescriptorPublicKey::Single(SinglePub {
-            key: SinglePubKey::FullKey(pk),
-            ..
-        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
-        DescriptorPublicKey::Single(SinglePub {
-            key: SinglePubKey::XOnly(pk),
-            ..
-        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
-        DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
-        DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
-    }
-}
-
-fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
-    key: &DescriptorPublicKey,
-    signers: &SignersContainer,
-    build_sat: BuildSatisfaction,
-    secp: &SecpCtx,
-    make_policy: M,
-    find_sig: F,
-) -> Policy {
-    let mut policy: Policy = make_policy().into();
-
-    policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
-        Satisfaction::Complete {
-            condition: Default::default(),
-        }
-    } else {
-        Satisfaction::None
-    };
-
-    if let Some(psbt) = build_sat.psbt() {
-        policy.satisfaction = if find_sig(psbt) {
-            Satisfaction::Complete {
-                condition: Default::default(),
-            }
-        } else {
-            Satisfaction::None
-        };
-    }
-
-    policy
-}
-
-fn generic_sig_in_psbt<
-    // C is for "check", it's a closure we use to *check* if a psbt input contains the signature
-    // for a specific key
-    C: Fn(&psbt::Input, &SinglePubKey) -> bool,
-    // E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
-    E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
->(
-    psbt: &Psbt,
-    key: &DescriptorPublicKey,
-    secp: &SecpCtx,
-    check: C,
-    extract: E,
-) -> bool {
-    //TODO check signature validity
-    psbt.inputs.iter().all(|input| match key {
-        DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
-        DescriptorPublicKey::XPub(xpub) => {
-            //TODO check actual derivation matches
-            match extract(input, xpub.root_fingerprint(secp)) {
-                Some(pubkey) => check(input, &pubkey),
-                None => false,
-            }
-        }
-        DescriptorPublicKey::MultiXPub(xpub) => {
-            //TODO check actual derivation matches
-            match extract(input, xpub.root_fingerprint(secp)) {
-                Some(pubkey) => check(input, &pubkey),
-                None => false,
-            }
-        }
-    })
-}
-
-trait SigExt: ScriptContext {
-    fn make_signature(
-        key: &DescriptorPublicKey,
-        signers: &SignersContainer,
-        build_sat: BuildSatisfaction,
-        secp: &SecpCtx,
-    ) -> Policy;
-
-    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
-}
-
-impl<T: ScriptContext + 'static> SigExt for T {
-    fn make_signature(
-        key: &DescriptorPublicKey,
-        signers: &SignersContainer,
-        build_sat: BuildSatisfaction,
-        secp: &SecpCtx,
-    ) -> Policy {
-        if T::as_enum().is_taproot() {
-            make_generic_signature(
-                key,
-                signers,
-                build_sat,
-                secp,
-                || SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
-                |psbt| Self::find_signature(psbt, key, secp),
-            )
-        } else {
-            make_generic_signature(
-                key,
-                signers,
-                build_sat,
-                secp,
-                || SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
-                |psbt| Self::find_signature(psbt, key, secp),
-            )
-        }
-    }
-
-    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
-        if T::as_enum().is_taproot() {
-            generic_sig_in_psbt(
-                psbt,
-                key,
-                secp,
-                |input, pk| {
-                    let pk = match pk {
-                        SinglePubKey::XOnly(pk) => pk,
-                        _ => return false,
-                    };
-
-                    if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
-                        true
-                    } else {
-                        input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
-                    }
-                },
-                |input, fing| {
-                    input
-                        .tap_key_origins
-                        .iter()
-                        .find(|(_, (_, (f, _)))| f == &fing)
-                        .map(|(pk, _)| SinglePubKey::XOnly(*pk))
-                },
-            )
-        } else {
-            generic_sig_in_psbt(
-                psbt,
-                key,
-                secp,
-                |input, pk| match pk {
-                    SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
-                    _ => false,
-                },
-                |input, fing| {
-                    input
-                        .bip32_derivation
-                        .iter()
-                        .find(|(_, (f, _))| f == &fing)
-                        .map(|(pk, _)| SinglePubKey::FullKey(PublicKey::new(*pk)))
-                },
-            )
-        }
-    }
-}
-
-impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
-    fn extract_policy(
-        &self,
-        signers: &SignersContainer,
-        build_sat: BuildSatisfaction,
-        secp: &SecpCtx,
-    ) -> Result<Option<Policy>, Error> {
-        Ok(match &self.node {
-            // Leaves
-            Terminal::True | Terminal::False => None,
-            Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
-            Terminal::PkH(pubkey_hash) => {
-                Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
-            }
-            Terminal::After(value) => {
-                let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
-                    value: (*value).into(),
-                }
-                .into();
-                policy.contribution = Satisfaction::Complete {
-                    condition: Condition {
-                        timelock: Some((*value).into()),
-                        csv: None,
-                    },
-                };
-                if let BuildSatisfaction::PsbtTimelocks {
-                    current_height,
-                    psbt,
-                    ..
-                } = build_sat
-                {
-                    let after = After::new(Some(current_height), false);
-                    let after_sat =
-                        Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
-                    let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
-                        Satisfier::<bitcoin::PublicKey>::check_after(&sat, (*value).into())
-                    });
-                    if after_sat && inputs_sat {
-                        policy.satisfaction = policy.contribution.clone();
-                    }
-                }
-
-                Some(policy)
-            }
-            Terminal::Older(value) => {
-                let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
-                policy.contribution = Satisfaction::Complete {
-                    condition: Condition {
-                        timelock: None,
-                        csv: Some(*value),
-                    },
-                };
-                if let BuildSatisfaction::PsbtTimelocks {
-                    current_height,
-                    input_max_height,
-                    psbt,
-                } = build_sat
-                {
-                    let older = Older::new(Some(current_height), Some(input_max_height), false);
-                    let older_sat = Satisfier::<bitcoin::PublicKey>::check_older(&older, *value);
-                    let inputs_sat = psbt_inputs_sat(psbt)
-                        .all(|sat| Satisfier::<bitcoin::PublicKey>::check_older(&sat, *value));
-                    if older_sat && inputs_sat {
-                        policy.satisfaction = policy.contribution.clone();
-                    }
-                }
-
-                Some(policy)
-            }
-            Terminal::Sha256(hash) => Some(SatisfiableItem::Sha256Preimage { hash: *hash }.into()),
-            Terminal::Hash256(hash) => {
-                Some(SatisfiableItem::Hash256Preimage { hash: *hash }.into())
-            }
-            Terminal::Ripemd160(hash) => {
-                Some(SatisfiableItem::Ripemd160Preimage { hash: *hash }.into())
-            }
-            Terminal::Hash160(hash) => {
-                Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
-            }
-            Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => {
-                Policy::make_multisig::<Ctx>(pks, signers, build_sat, *k, false, secp)?
-            }
-            // Identities
-            Terminal::Alt(inner)
-            | Terminal::Swap(inner)
-            | Terminal::Check(inner)
-            | Terminal::DupIf(inner)
-            | Terminal::Verify(inner)
-            | Terminal::NonZero(inner)
-            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, build_sat, secp)?,
-            // Complex policies
-            Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
-                a.extract_policy(signers, build_sat, secp)?,
-                b.extract_policy(signers, build_sat, secp)?,
-            )?,
-            Terminal::AndOr(x, y, z) => Policy::make_or(
-                Policy::make_and(
-                    x.extract_policy(signers, build_sat, secp)?,
-                    y.extract_policy(signers, build_sat, secp)?,
-                )?,
-                z.extract_policy(signers, build_sat, secp)?,
-            )?,
-            Terminal::OrB(a, b)
-            | Terminal::OrD(a, b)
-            | Terminal::OrC(a, b)
-            | Terminal::OrI(a, b) => Policy::make_or(
-                a.extract_policy(signers, build_sat, secp)?,
-                b.extract_policy(signers, build_sat, secp)?,
-            )?,
-            Terminal::Thresh(k, nodes) => {
-                let mut threshold = *k;
-                let mapped: Vec<_> = nodes
-                    .iter()
-                    .map(|n| n.extract_policy(signers, build_sat, secp))
-                    .collect::<Result<Vec<_>, _>>()?
-                    .into_iter()
-                    .flatten()
-                    .collect();
-
-                if mapped.len() < nodes.len() {
-                    threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
-                        None => return Ok(None),
-                        Some(x) => x,
-                    };
-                }
-
-                Policy::make_thresh(mapped, threshold)?
-            }
-
-            // Unsupported
-            Terminal::RawPkH(_) => None,
-        })
-    }
-}
-
-fn psbt_inputs_sat(psbt: &Psbt) -> impl Iterator<Item = PsbtInputSatisfier> {
-    (0..psbt.inputs.len()).map(move |i| PsbtInputSatisfier::new(psbt, i))
-}
-
-/// Options to build the satisfaction field in the policy
-#[derive(Debug, Clone, Copy)]
-pub enum BuildSatisfaction<'a> {
-    /// Don't generate `satisfaction` field
-    None,
-    /// Analyze the given PSBT to check for existing signatures
-    Psbt(&'a Psbt),
-    /// Like `Psbt` variant and also check for expired timelocks
-    PsbtTimelocks {
-        /// Given PSBT
-        psbt: &'a Psbt,
-        /// Current blockchain height
-        current_height: u32,
-        /// The highest confirmation height between the inputs
-        /// CSV should consider different inputs, but we consider the worst condition for the tx as whole
-        input_max_height: u32,
-    },
-}
-impl<'a> BuildSatisfaction<'a> {
-    fn psbt(&self) -> Option<&'a Psbt> {
-        match self {
-            BuildSatisfaction::None => None,
-            BuildSatisfaction::Psbt(psbt) => Some(psbt),
-            BuildSatisfaction::PsbtTimelocks { psbt, .. } => Some(psbt),
-        }
-    }
-}
-
-impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
-    fn extract_policy(
-        &self,
-        signers: &SignersContainer,
-        build_sat: BuildSatisfaction,
-        secp: &SecpCtx,
-    ) -> Result<Option<Policy>, Error> {
-        fn make_sortedmulti<Ctx: ScriptContext + 'static>(
-            keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
-            signers: &SignersContainer,
-            build_sat: BuildSatisfaction,
-            secp: &SecpCtx,
-        ) -> Result<Option<Policy>, Error> {
-            Ok(Policy::make_multisig::<Ctx>(
-                keys.pks.as_ref(),
-                signers,
-                build_sat,
-                keys.k,
-                true,
-                secp,
-            )?)
-        }
-
-        match self {
-            Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
-                pk.as_inner(),
-                signers,
-                build_sat,
-                secp,
-            ))),
-            Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
-                pk.as_inner(),
-                signers,
-                build_sat,
-                secp,
-            ))),
-            Descriptor::Sh(sh) => match sh.as_inner() {
-                ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
-                    pk.as_inner(),
-                    signers,
-                    build_sat,
-                    secp,
-                ))),
-                ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
-                ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
-                ShInner::Wsh(wsh) => match wsh.as_inner() {
-                    WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
-                    WshInner::SortedMulti(ref keys) => {
-                        make_sortedmulti(keys, signers, build_sat, secp)
-                    }
-                },
-            },
-            Descriptor::Wsh(wsh) => match wsh.as_inner() {
-                WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
-                WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
-            },
-            Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
-            Descriptor::Tr(tr) => {
-                // If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
-                // node with threshold = 1 and the key spend signature plus all the tree leaves
-                let key_spend_sig =
-                    miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
-
-                if tr.tap_tree().is_none() {
-                    Ok(Some(key_spend_sig))
-                } else {
-                    let mut items = vec![key_spend_sig];
-                    items.append(
-                        &mut tr
-                            .iter_scripts()
-                            .filter_map(|(_, ms)| {
-                                ms.extract_policy(signers, build_sat, secp).transpose()
-                            })
-                            .collect::<Result<Vec<_>, _>>()?,
-                    );
-
-                    Ok(Policy::make_thresh(items, 1)?)
-                }
-            }
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use crate::descriptor;
-    use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
-
-    use super::*;
-    use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
-    use crate::keys::{DescriptorKey, IntoDescriptorKey};
-    use crate::wallet::signer::SignersContainer;
-    use alloc::{string::ToString, sync::Arc};
-    use assert_matches::assert_matches;
-    use bitcoin::bip32;
-    use bitcoin::secp256k1::Secp256k1;
-    use bitcoin::Network;
-    use core::str::FromStr;
-
-    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
-    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
-
-    const PATH: &str = "m/44'/1'/0'/0";
-
-    fn setup_keys<Ctx: ScriptContext>(
-        tprv: &str,
-        path: &str,
-        secp: &SecpCtx,
-    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
-        let path = bip32::DerivationPath::from_str(path).unwrap();
-        let tprv = bip32::Xpriv::from_str(tprv).unwrap();
-        let tpub = bip32::Xpub::from_priv(secp, &tprv);
-        let fingerprint = tprv.fingerprint(secp);
-        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
-        let pubkey = (tpub, path).into_descriptor_key().unwrap();
-
-        (prvkey, pubkey, fingerprint)
-    }
-
-    // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
-
-    #[test]
-    fn test_extract_policy_for_wpkh() {
-        let secp = Secp256k1::new();
-
-        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
-        let desc = descriptor!(wpkh(pubkey)).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
-        assert_matches!(&policy.contribution, Satisfaction::None);
-
-        let desc = descriptor!(wpkh(prvkey)).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
-        assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
-    }
-
-    // 2 pub keys descriptor, required 2 prv keys
-    #[test]
-    fn test_extract_policy_for_sh_multi_partial_0of2() {
-        let secp = Secp256k1::new();
-        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
-            && keys[0] == PkOrF::Fingerprint(fingerprint0)
-            && keys[1] == PkOrF::Fingerprint(fingerprint1)
-        );
-        // TODO should this be "Satisfaction::None" since we have no prv keys?
-        // TODO should items and conditions not be empty?
-        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
-            && m == &2usize
-            && items.is_empty()
-            && conditions.is_empty()
-        );
-    }
-
-    // 1 prv and 1 pub key descriptor, required 2 prv keys
-    #[test]
-    fn test_extract_policy_for_sh_multi_partial_1of2() {
-        let secp = Secp256k1::new();
-        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
-            && keys[0] == PkOrF::Fingerprint(fingerprint0)
-            && keys[1] == PkOrF::Fingerprint(fingerprint1)
-        );
-
-        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
-             && m == &2usize
-             && items.len() == 1
-             && conditions.contains_key(&0)
-        );
-    }
-
-    // 1 prv and 1 pub key descriptor, required 1 prv keys
-    #[test]
-    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
-    fn test_extract_policy_for_sh_multi_complete_1of2() {
-        let secp = Secp256k1::new();
-
-        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
-            && keys[0] == PkOrF::Fingerprint(fingerprint0)
-            && keys[1] == PkOrF::Fingerprint(fingerprint1)
-        );
-        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
-             && m == &1
-             && items.len() == 2
-             && conditions.contains_key(&vec![0])
-             && conditions.contains_key(&vec![1])
-        );
-    }
-
-    // 2 prv keys descriptor, required 2 prv keys
-    #[test]
-    fn test_extract_policy_for_sh_multi_complete_2of2() {
-        let secp = Secp256k1::new();
-
-        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
-            && keys[0] == PkOrF::Fingerprint(fingerprint0)
-            && keys[1] == PkOrF::Fingerprint(fingerprint1)
-        );
-
-        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
-             && m == &2
-             && items.len() == 2
-             && conditions.contains_key(&vec![0,1])
-        );
-    }
-
-    // test ExtractPolicy trait with extended and single keys
-
-    #[test]
-    fn test_extract_policy_for_single_wpkh() {
-        let secp = Secp256k1::new();
-
-        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
-        let desc = descriptor!(wpkh(pubkey)).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
-        assert_matches!(&policy.contribution, Satisfaction::None);
-
-        let desc = descriptor!(wpkh(prvkey)).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
-        assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
-    }
-
-    // single key, 1 prv and 1 pub key descriptor, required 1 prv keys
-    #[test]
-    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
-    fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
-        let secp = Secp256k1::new();
-
-        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
-            && keys[0] == PkOrF::Fingerprint(fingerprint0)
-            && keys[1] == PkOrF::Fingerprint(fingerprint1)
-        );
-        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
-             && m == 1
-             && items.len() == 2
-             && conditions.contains_key(&vec![0])
-             && conditions.contains_key(&vec![1])
-        );
-    }
-
-    // test ExtractPolicy trait with descriptors containing timelocks in a thresh()
-
-    #[test]
-    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
-    fn test_extract_policy_for_wsh_multi_timelock() {
-        let secp = Secp256k1::new();
-
-        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
-        let sequence = 50;
-        #[rustfmt::skip]
-        let desc = descriptor!(wsh(thresh(
-            2,
-            pk(prvkey0),
-            s:pk(pubkey1),
-            s:d:v:older(sequence)
-        )))
-        .unwrap();
-
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
-
-        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
-             && m == &2
-             && items.len() == 3
-             && conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
-             && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
-             && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
-        );
-    }
-
-    // - mixed timelocks should fail
-
-    #[test]
-    #[ignore]
-    fn test_extract_policy_for_wsh_mixed_timelocks() {
-        let secp = Secp256k1::new();
-        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
-        let locktime_blocks = 100;
-        let locktime_seconds = locktime_blocks + locktime_threshold;
-        let desc = descriptor!(sh(and_v(
-            v: pk(prvkey0),
-            and_v(v: after(locktime_seconds), after(locktime_blocks))
-        )))
-        .unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let _policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-        // println!("desc policy = {:?}", policy); // TODO remove
-        // TODO how should this fail with mixed timelocks?
-    }
-
-    // - multiple timelocks of the same type should be correctly merged together
-    #[test]
-    #[ignore]
-    fn test_extract_policy_for_multiple_same_timelocks() {
-        let secp = Secp256k1::new();
-        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
-        let locktime_blocks0 = 100;
-        let locktime_blocks1 = 200;
-        let desc = descriptor!(sh(and_v(
-            v: pk(prvkey0),
-            and_v(v: after(locktime_blocks0), after(locktime_blocks1))
-        )))
-        .unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let _policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-        // println!("desc policy = {:?}", policy); // TODO remove
-        // TODO how should this merge timelocks?
-        let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
-        let locktime_seconds0 = 500000100;
-        let locktime_seconds1 = 500000200;
-        let desc = descriptor!(sh(and_v(
-            v: pk(prvkey1),
-            and_v(v: after(locktime_seconds0), after(locktime_seconds1))
-        )))
-        .unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-        let _policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        // println!("desc policy = {:?}", policy); // TODO remove
-
-        // TODO how should this merge timelocks?
-    }
-
-    #[test]
-    fn test_get_condition_multisig() {
-        let secp = Secp256k1::new();
-
-        let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
-        let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
-
-        let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        // no args, choose the default
-        let no_args = policy.get_condition(&vec![].into_iter().collect());
-        assert_eq!(no_args, Ok(Condition::default()));
-
-        // enough args
-        let eq_thresh =
-            policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
-        assert_eq!(eq_thresh, Ok(Condition::default()));
-
-        // more args, it doesn't really change anything
-        let gt_thresh =
-            policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
-        assert_eq!(gt_thresh, Ok(Condition::default()));
-
-        // not enough args, error
-        let lt_thresh =
-            policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
-        assert_eq!(
-            lt_thresh,
-            Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
-        );
-
-        // index out of range
-        let out_of_range =
-            policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
-        assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
-    }
-
-    const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
-    const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
-    const CAROL_TPRV_STR:&str = "tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt";
-    const ALICE_BOB_PATH: &str = "m/0'";
-
-    #[test]
-    fn test_extract_satisfaction() {
-        const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
-        const BOB_SIGNED_PSBT: &str =   "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA==";
-        const ALICE_BOB_SIGNED_PSBT: &str =   "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA";
-
-        let secp = Secp256k1::new();
-
-        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
-
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-
-        let addr = wallet_desc
-            .at_derivation_index(0)
-            .unwrap()
-            .address(Network::Testnet)
-            .unwrap();
-        assert_eq!(
-            "tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
-            addr.to_string()
-        );
-
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
-
-        let policy_alice_psbt = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
-            .unwrap()
-            .unwrap();
-        //println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
-
-        assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
-             && m == &2
-             && items == &vec![0]
-        );
-
-        let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
-        let policy_bob_psbt = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
-            .unwrap()
-            .unwrap();
-        //println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
-
-        assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
-             && m == &2
-             && items == &vec![1]
-        );
-
-        let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
-        let policy_alice_bob_psbt = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
-            .unwrap()
-            .unwrap();
-        assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
-             && m == &2
-             && items == &vec![0, 1]
-        );
-    }
-
-    #[test]
-    fn test_extract_satisfaction_timelock() {
-        //const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
-        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED:     &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
-        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED: &str ="cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstIMEUCIQCtZxNm6H3Ux3pnc64DSpgohMdBj+57xhFHcURYt2BpPAIgG3OnI7bcj/3GtWX1HHyYGSI7QGa/zq5YnsmK1Cw29NABAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEIoAQASDBFAiEArWcTZuh91Md6Z3OuA0qYKITHQY/ue8YRR3FEWLdgaTwCIBtzpyO23I/9xrVl9Rx8mBkiO0Bmv86uWJ7JitQsNvTQAQEBUnZjUrJpaHwhA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLrJN8IQL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiKyTUocAAA==";
-
-        let secp = Secp256k1::new();
-
-        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc =
-            descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
-
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let addr = wallet_desc
-            .at_derivation_index(0)
-            .unwrap()
-            .address(Network::Testnet)
-            .unwrap();
-        assert_eq!(
-            "tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58",
-            addr.to_string()
-        );
-
-        let psbt = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap();
-
-        let build_sat = BuildSatisfaction::PsbtTimelocks {
-            psbt: &psbt,
-            current_height: 10,
-            input_max_height: 9,
-        };
-
-        let policy = wallet_desc
-            .extract_policy(&signers_container, build_sat, &secp)
-            .unwrap()
-            .unwrap();
-        assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
-             && m == &2
-             && items.is_empty()
-        );
-        //println!("{}", serde_json::to_string(&policy).unwrap());
-
-        let build_sat_expired = BuildSatisfaction::PsbtTimelocks {
-            psbt: &psbt,
-            current_height: 12,
-            input_max_height: 9,
-        };
-
-        let policy_expired = wallet_desc
-            .extract_policy(&signers_container, build_sat_expired, &secp)
-            .unwrap()
-            .unwrap();
-        assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
-             && m == &2
-             && items == &vec![0]
-        );
-        //println!("{}", serde_json::to_string(&policy_expired).unwrap());
-
-        let psbt_signed = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap();
-
-        let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
-            psbt: &psbt_signed,
-            current_height: 12,
-            input_max_height: 9,
-        };
-
-        let policy_expired_signed = wallet_desc
-            .extract_policy(&signers_container, build_sat_expired_signed, &secp)
-            .unwrap()
-            .unwrap();
-        assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
-             && m == &2
-             && items == &vec![0, 1]
-        );
-        //println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
-    }
-
-    #[test]
-    fn test_extract_pkh() {
-        let secp = Secp256k1::new();
-
-        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (prvkey_carol, _, _) = setup_keys(CAROL_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(wsh(c: andor(
-            pk(prvkey_alice),
-            pk_k(prvkey_bob),
-            pk_h(prvkey_carol),
-        )))
-        .unwrap();
-
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
-        assert!(policy.is_ok());
-    }
-
-    #[test]
-    fn test_extract_tr_key_spend() {
-        let secp = Secp256k1::new();
-
-        let (prvkey, _, fingerprint) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(tr(prvkey)).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap();
-        assert_eq!(
-            policy,
-            Some(Policy {
-                id: "48u0tz0n".to_string(),
-                item: SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(fingerprint)),
-                satisfaction: Satisfaction::None,
-                contribution: Satisfaction::Complete {
-                    condition: Condition::default()
-                }
-            })
-        );
-    }
-
-    #[test]
-    fn test_extract_tr_script_spend() {
-        let secp = Secp256k1::new();
-
-        let (alice_prv, _, alice_fing) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (_, bob_pub, bob_fing) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
-
-        let policy = wallet_desc
-            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
-        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
-
-        let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
-        let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
-
-        let thresh_items = match policy.item {
-            SatisfiableItem::Thresh { items, .. } => items,
-            _ => unreachable!(),
-        };
-
-        assert_eq!(thresh_items[0].item, bob_sig);
-        assert_eq!(thresh_items[1].item, alice_sig);
-    }
-
-    #[test]
-    fn test_extract_tr_satisfaction_key_spend() {
-        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA==";
-        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA=";
-
-        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
-        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
-
-        let secp = Secp256k1::new();
-
-        let (_, pubkey, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(tr(pubkey)).unwrap();
-        let (wallet_desc, _) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-
-        let policy_unsigned = wallet_desc
-            .extract_policy(
-                &SignersContainer::default(),
-                BuildSatisfaction::Psbt(&unsigned_psbt),
-                &secp,
-            )
-            .unwrap()
-            .unwrap();
-        let policy_signed = wallet_desc
-            .extract_policy(
-                &SignersContainer::default(),
-                BuildSatisfaction::Psbt(&signed_psbt),
-                &secp,
-            )
-            .unwrap()
-            .unwrap();
-
-        assert_eq!(policy_unsigned.satisfaction, Satisfaction::None);
-        assert_eq!(
-            policy_signed.satisfaction,
-            Satisfaction::Complete {
-                condition: Default::default()
-            }
-        );
-    }
-
-    #[test]
-    fn test_extract_tr_satisfaction_script_spend() {
-        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
-        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2AQcAAQhCAUALcP9w/+Ddly9DWdhHTnQ9uCDWLPZjR6vKbKePswW2Ee6W5KNfrklus/8z98n7BQ1U4vADHk0FbadeeL8rrbHlARNAC3D/cP/g3ZcvQ1nYR050Pbgg1iz2Y0erymynj7MFthHuluSjX65JbrP/M/fJ+wUNVOLwAx5NBW2nXni/K62x5UEUeEbK57HG1FUp69HHhjBZH9bSvss8e3qhLoMuXPK5hBr2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHUAXNmWieJ80Fs+PMa2C186YOBPZbYG/ieEUkagMwzJ788SoCucNdp5wnxfpuJVygFhglDrXGzujFtC82PrMohwuIhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
-
-        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
-        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
-
-        let secp = Secp256k1::new();
-
-        let (_, alice_pub, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
-        let (_, bob_pub, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
-
-        let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap();
-        let (wallet_desc, _) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-
-        let policy_unsigned = wallet_desc
-            .extract_policy(
-                &SignersContainer::default(),
-                BuildSatisfaction::Psbt(&unsigned_psbt),
-                &secp,
-            )
-            .unwrap()
-            .unwrap();
-        let policy_signed = wallet_desc
-            .extract_policy(
-                &SignersContainer::default(),
-                BuildSatisfaction::Psbt(&signed_psbt),
-                &secp,
-            )
-            .unwrap()
-            .unwrap();
-
-        assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
-        assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
-
-        assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
-        assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
-
-        let satisfied_items = match policy_signed.item {
-            SatisfiableItem::Thresh { items, .. } => items,
-            _ => unreachable!(),
-        };
-
-        assert_eq!(
-            satisfied_items[0].satisfaction,
-            Satisfaction::Complete {
-                condition: Default::default()
-            }
-        );
-        assert_eq!(
-            satisfied_items[1].satisfaction,
-            Satisfaction::Complete {
-                condition: Default::default()
-            }
-        );
-    }
-}
diff --git a/crates/bdk/src/descriptor/template.rs b/crates/bdk/src/descriptor/template.rs
deleted file mode 100644 (file)
index 61f9c4f..0000000
+++ /dev/null
@@ -1,985 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Descriptor templates
-//!
-//! This module contains the definition of various common script templates that are ready to be
-//! used. See the documentation of each template for an example.
-
-use bitcoin::bip32;
-use bitcoin::Network;
-
-use miniscript::{Legacy, Segwitv0, Tap};
-
-use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
-use crate::descriptor::DescriptorError;
-use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworks};
-use crate::wallet::utils::SecpCtx;
-use crate::{descriptor, KeychainKind};
-
-/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
-pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
-
-/// Trait for descriptor templates that can be built into a full descriptor
-///
-/// Since [`IntoWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be
-/// passed directly to the [`Wallet`](crate::Wallet) constructor.
-///
-/// ## Example
-///
-/// ```
-/// use bdk::descriptor::error::Error as DescriptorError;
-/// use bdk::keys::{IntoDescriptorKey, KeyError};
-/// use bdk::miniscript::Legacy;
-/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut};
-/// use bitcoin::Network;
-///
-/// struct MyP2PKH<K: IntoDescriptorKey<Legacy>>(K);
-///
-/// impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for MyP2PKH<K> {
-///     fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-///         Ok(bdk::descriptor!(pkh(self.0))?)
-///     }
-/// }
-/// ```
-pub trait DescriptorTemplate {
-    /// Build the complete descriptor
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError>;
-}
-
-/// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its
-/// [`build`](DescriptorTemplate::build) method
-impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
-    fn into_wallet_descriptor(
-        self,
-        secp: &SecpCtx,
-        network: Network,
-    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        self.build(network)?.into_wallet_descriptor(secp, network)
-    }
-}
-
-/// P2PKH template. Expands to a descriptor `pkh(key)`
-///
-/// ## Example
-///
-/// ```
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::Wallet;
-/// # use bdk::KeychainKind;
-/// use bdk::template::P2Pkh;
-///
-/// let key =
-///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?;
-///
-/// assert_eq!(
-///     wallet
-///         .next_unused_address(KeychainKind::External)?
-///         .to_string(),
-///     "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
-/// );
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct P2Pkh<K: IntoDescriptorKey<Legacy>>(pub K);
-
-impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
-    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        descriptor!(pkh(self.0))
-    }
-}
-
-/// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))`
-///
-/// ## Example
-///
-/// ```
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::Wallet;
-/// # use bdk::KeychainKind;
-/// use bdk::template::P2Wpkh_P2Sh;
-///
-/// let key =
-///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?;
-///
-/// assert_eq!(
-///     wallet
-///         .next_unused_address(KeychainKind::External)?
-///         .to_string(),
-///     "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
-/// );
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-#[allow(non_camel_case_types)]
-pub struct P2Wpkh_P2Sh<K: IntoDescriptorKey<Segwitv0>>(pub K);
-
-impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
-    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        descriptor!(sh(wpkh(self.0)))
-    }
-}
-
-/// P2WPKH template. Expands to a descriptor `wpkh(key)`
-///
-/// ## Example
-///
-/// ```
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet};
-/// # use bdk::KeychainKind;
-/// use bdk::template::P2Wpkh;
-///
-/// let key =
-///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?;
-///
-/// assert_eq!(
-///     wallet
-///         .next_unused_address(KeychainKind::External)?
-///         .to_string(),
-///     "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
-/// );
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct P2Wpkh<K: IntoDescriptorKey<Segwitv0>>(pub K);
-
-impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
-    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        descriptor!(wpkh(self.0))
-    }
-}
-
-/// P2TR template. Expands to a descriptor `tr(key)`
-///
-/// ## Example
-///
-/// ```
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::Wallet;
-/// # use bdk::KeychainKind;
-/// use bdk::template::P2TR;
-///
-/// let key =
-///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?;
-///
-/// assert_eq!(
-///     wallet
-///         .next_unused_address(KeychainKind::External)?
-///         .to_string(),
-///     "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
-/// );
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct P2TR<K: IntoDescriptorKey<Tap>>(pub K);
-
-impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
-    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        descriptor!(tr(self.0))
-    }
-}
-
-/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
-///
-/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
-///
-/// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip44;
-///
-/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip44(key.clone(), KeychainKind::External),
-///     Some(Bip44(key, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
-
-impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network)
-    }
-}
-
-/// BIP44 public template. Expands to `pkh(key/{0,1}/*)`
-///
-/// This assumes that the key used has already been derived with `m/44'/0'/0'` for Mainnet or `m/44'/1'/0'` for Testnet.
-///
-/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
-///
-/// See [`Bip44`] for a template that does the full derivation, but requires private data
-/// for the key.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip44Public;
-///
-/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
-/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip44Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
-
-impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Pkh(legacy::make_bipxx_public(
-            44, self.0, self.1, self.2, network,
-        )?)
-        .build(network)
-    }
-}
-
-/// BIP49 template. Expands to `sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))`
-///
-/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
-///
-/// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip49;
-///
-/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip49(key.clone(), KeychainKind::External),
-///     Some(Bip49(key, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
-
-impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network)
-    }
-}
-
-/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))`
-///
-/// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or `m/49'/1'/0'` for Testnet.
-///
-/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
-///
-/// See [`Bip49`] for a template that does the full derivation, but requires private data
-/// for the key.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip49Public;
-///
-/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
-/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip49Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
-
-impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
-            49, self.0, self.1, self.2, network,
-        )?)
-        .build(network)
-    }
-}
-
-/// BIP84 template. Expands to `wpkh(key/84'/{0,1}'/0'/{0,1}/*)`
-///
-/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
-///
-/// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip84;
-///
-/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip84(key.clone(), KeychainKind::External),
-///     Some(Bip84(key, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
-
-impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network)
-    }
-}
-
-/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
-///
-/// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or `m/84'/1'/0'` for Testnet.
-///
-/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
-///
-/// See [`Bip84`] for a template that does the full derivation, but requires private data
-/// for the key.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip84Public;
-///
-/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
-/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip84Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
-
-impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2Wpkh(segwit_v0::make_bipxx_public(
-            84, self.0, self.1, self.2, network,
-        )?)
-        .build(network)
-    }
-}
-
-/// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)`
-///
-/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
-///
-/// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip86;
-///
-/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip86(key.clone(), KeychainKind::External),
-///     Some(Bip86(key, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip86<K: DerivableKey<Tap>>(pub K, pub KeychainKind);
-
-impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network)
-    }
-}
-
-/// BIP86 public template. Expands to `tr(key/{0,1}/*)`
-///
-/// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or `m/86'/1'/0'` for Testnet.
-///
-/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
-///
-/// See [`Bip86`] for a template that does the full derivation, but requires private data
-/// for the key.
-///
-/// ## Example
-///
-/// ```
-/// # use std::str::FromStr;
-/// # use bdk::bitcoin::{PrivateKey, Network};
-/// # use bdk::{Wallet,  KeychainKind};
-/// use bdk::template::Bip86Public;
-///
-/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
-/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
-/// let mut wallet = Wallet::new_no_persist(
-///     Bip86Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
-///     Network::Testnet,
-/// )?;
-///
-/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub struct Bip86Public<K: DerivableKey<Tap>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
-
-impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86Public<K> {
-    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
-        P2TR(segwit_v1::make_bipxx_public(
-            86, self.0, self.1, self.2, network,
-        )?)
-        .build(network)
-    }
-}
-
-macro_rules! expand_make_bipxx {
-    ( $mod_name:ident, $ctx:ty ) => {
-        mod $mod_name {
-            use super::*;
-
-            pub(super) fn make_bipxx_private<K: DerivableKey<$ctx>>(
-                bip: u32,
-                key: K,
-                keychain: KeychainKind,
-                network: Network,
-            ) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
-                let mut derivation_path = alloc::vec::Vec::with_capacity(4);
-                derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?);
-
-                match network {
-                    Network::Bitcoin => {
-                        derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
-                    }
-                    _ => {
-                        derivation_path.push(bip32::ChildNumber::from_hardened_idx(1)?);
-                    }
-                }
-                derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
-
-                match keychain {
-                    KeychainKind::External => {
-                        derivation_path.push(bip32::ChildNumber::from_normal_idx(0)?)
-                    }
-                    KeychainKind::Internal => {
-                        derivation_path.push(bip32::ChildNumber::from_normal_idx(1)?)
-                    }
-                };
-
-                let derivation_path: bip32::DerivationPath = derivation_path.into();
-
-                Ok((key, derivation_path))
-            }
-            pub(super) fn make_bipxx_public<K: DerivableKey<$ctx>>(
-                bip: u32,
-                key: K,
-                parent_fingerprint: bip32::Fingerprint,
-                keychain: KeychainKind,
-                network: Network,
-            ) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
-                let derivation_path: bip32::DerivationPath = match keychain {
-                    KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
-                    KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(),
-                };
-
-                let source_path = bip32::DerivationPath::from(vec![
-                    bip32::ChildNumber::from_hardened_idx(bip)?,
-                    match network {
-                        Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
-                        _ => bip32::ChildNumber::from_hardened_idx(1)?,
-                    },
-                    bip32::ChildNumber::from_hardened_idx(0)?,
-                ]);
-
-                Ok((key, (parent_fingerprint, source_path), derivation_path))
-            }
-        }
-    };
-}
-
-expand_make_bipxx!(legacy, Legacy);
-expand_make_bipxx!(segwit_v0, Segwitv0);
-expand_make_bipxx!(segwit_v1, Tap);
-
-#[cfg(test)]
-mod test {
-    // test existing descriptor templates, make sure they are expanded to the right descriptors
-
-    use alloc::{string::ToString, vec::Vec};
-    use core::str::FromStr;
-
-    use super::*;
-    use crate::descriptor::{DescriptorError, DescriptorMeta};
-    use crate::keys::ValidNetworks;
-    use assert_matches::assert_matches;
-    use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
-    use miniscript::Descriptor;
-
-    // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
-    #[test]
-    fn test_bip44_template_cointype() {
-        use bitcoin::bip32::ChildNumber::{self, Hardened};
-
-        let xprvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
-        assert_eq!(Network::Bitcoin, xprvkey.network);
-        let xdesc = Bip44(xprvkey, KeychainKind::Internal)
-            .build(Network::Bitcoin)
-            .unwrap();
-
-        if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
-            let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
-            let purpose = path.first().unwrap();
-            assert_matches!(purpose, Hardened { index: 44 });
-            let coin_type = path.get(1).unwrap();
-            assert_matches!(coin_type, Hardened { index: 0 });
-        }
-
-        let tprvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        assert_eq!(Network::Testnet, tprvkey.network);
-        let tdesc = Bip44(tprvkey, KeychainKind::Internal)
-            .build(Network::Testnet)
-            .unwrap();
-
-        if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
-            let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
-            let purpose = path.first().unwrap();
-            assert_matches!(purpose, Hardened { index: 44 });
-            let coin_type = path.get(1).unwrap();
-            assert_matches!(coin_type, Hardened { index: 1 });
-        }
-    }
-
-    // verify template descriptor generates expected address(es)
-    fn check(
-        desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
-        is_witness: bool,
-        is_taproot: bool,
-        is_fixed: bool,
-        network: Network,
-        expected: &[&str],
-    ) {
-        let (desc, _key_map, _networks) = desc.unwrap();
-        assert_eq!(desc.is_witness(), is_witness);
-        assert_eq!(desc.is_taproot(), is_taproot);
-        assert_eq!(!desc.has_wildcard(), is_fixed);
-        for i in 0..expected.len() {
-            let index = i as u32;
-            let child_desc = if !desc.has_wildcard() {
-                desc.at_derivation_index(0).unwrap()
-            } else {
-                desc.at_derivation_index(index).unwrap()
-            };
-            let address = child_desc.address(network).unwrap();
-            assert_eq!(address.to_string(), *expected.get(i).unwrap());
-        }
-    }
-
-    // P2PKH
-    #[test]
-    fn test_p2ph_template() {
-        let prvkey =
-            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
-                .unwrap();
-        check(
-            P2Pkh(prvkey).build(Network::Bitcoin),
-            false,
-            false,
-            true,
-            Network::Regtest,
-            &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
-        );
-
-        let pubkey = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        check(
-            P2Pkh(pubkey).build(Network::Bitcoin),
-            false,
-            false,
-            true,
-            Network::Regtest,
-            &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
-        );
-    }
-
-    // P2WPKH-P2SH `sh(wpkh(key))`
-    #[test]
-    fn test_p2wphp2sh_template() {
-        let prvkey =
-            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
-                .unwrap();
-        check(
-            P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
-            true,
-            false,
-            true,
-            Network::Regtest,
-            &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
-        );
-
-        let pubkey = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        check(
-            P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
-            true,
-            false,
-            true,
-            Network::Regtest,
-            &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
-        );
-    }
-
-    // P2WPKH `wpkh(key)`
-    #[test]
-    fn test_p2wph_template() {
-        let prvkey =
-            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
-                .unwrap();
-        check(
-            P2Wpkh(prvkey).build(Network::Bitcoin),
-            true,
-            false,
-            true,
-            Network::Regtest,
-            &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
-        );
-
-        let pubkey = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        check(
-            P2Wpkh(pubkey).build(Network::Bitcoin),
-            true,
-            false,
-            true,
-            Network::Regtest,
-            &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
-        );
-    }
-
-    // P2TR `tr(key)`
-    #[test]
-    fn test_p2tr_template() {
-        let prvkey =
-            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
-                .unwrap();
-        check(
-            P2TR(prvkey).build(Network::Bitcoin),
-            false,
-            true,
-            true,
-            Network::Regtest,
-            &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"],
-        );
-
-        let pubkey = bitcoin::PublicKey::from_str(
-            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
-        )
-        .unwrap();
-        check(
-            P2TR(pubkey).build(Network::Bitcoin),
-            false,
-            true,
-            true,
-            Network::Regtest,
-            &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"],
-        );
-    }
-
-    // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
-    #[test]
-    fn test_bip44_template() {
-        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        check(
-            Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
-            false,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
-                "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
-                "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ",
-            ],
-        );
-        check(
-            Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
-            false,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
-                "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
-                "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA",
-            ],
-        );
-    }
-
-    // BIP44 public `pkh(key/{0,1}/*)`
-    #[test]
-    fn test_bip44_public_template() {
-        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
-        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
-        check(
-            Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
-            false,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
-                "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
-                "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g",
-            ],
-        );
-        check(
-            Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
-            false,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
-                "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
-                "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo",
-            ],
-        );
-    }
-
-    // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
-    #[test]
-    fn test_bip49_template() {
-        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        check(
-            Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
-                "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
-                "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4",
-            ],
-        );
-        check(
-            Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
-                "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
-                "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn",
-            ],
-        );
-    }
-
-    // BIP49 public `sh(wpkh(key/{0,1}/*))`
-    #[test]
-    fn test_bip49_public_template() {
-        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
-        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
-        check(
-            Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
-                "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
-                "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7",
-            ],
-        );
-        check(
-            Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
-                "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
-                "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp",
-            ],
-        );
-    }
-
-    // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
-    #[test]
-    fn test_bip84_template() {
-        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        check(
-            Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
-                "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
-                "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp",
-            ],
-        );
-        check(
-            Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
-                "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
-                "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce",
-            ],
-        );
-    }
-
-    // BIP84 public `wpkh(key/{0,1}/*)`
-    #[test]
-    fn test_bip84_public_template() {
-        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
-        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
-        check(
-            Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
-                "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
-                "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9",
-            ],
-        );
-        check(
-            Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
-            true,
-            false,
-            false,
-            Network::Regtest,
-            &[
-                "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
-                "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
-                "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp",
-            ],
-        );
-    }
-
-    // BIP86 `tr(key/86'/0'/0'/{0,1}/*)`
-    // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
-    #[test]
-    fn test_bip86_template() {
-        let prvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
-        check(
-            Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin),
-            false,
-            true,
-            false,
-            Network::Bitcoin,
-            &[
-                "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
-                "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
-                "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
-            ],
-        );
-        check(
-            Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
-            false,
-            true,
-            false,
-            Network::Bitcoin,
-            &[
-                "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
-                "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
-                "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
-            ],
-        );
-    }
-
-    // BIP86 public `tr(key/{0,1}/*)`
-    // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
-    #[test]
-    fn test_bip86_public_template() {
-        let pubkey = bitcoin::bip32::Xpub::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
-        let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap();
-        check(
-            Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
-            false,
-            true,
-            false,
-            Network::Bitcoin,
-            &[
-                "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
-                "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
-                "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
-            ],
-        );
-        check(
-            Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
-            false,
-            true,
-            false,
-            Network::Bitcoin,
-            &[
-                "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
-                "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
-                "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
-            ],
-        );
-    }
-}
diff --git a/crates/bdk/src/keys/bip39.rs b/crates/bdk/src/keys/bip39.rs
deleted file mode 100644 (file)
index 7158505..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! BIP-0039
-
-// TODO: maybe write our own implementation of bip39? Seems stupid to have an extra dependency for
-// something that should be fairly simple to re-implement.
-
-use alloc::string::String;
-use bitcoin::bip32;
-use bitcoin::Network;
-
-use miniscript::ScriptContext;
-
-pub use bip39::{Error, Language, Mnemonic};
-
-type Seed = [u8; 64];
-
-/// Type describing entropy length (aka word count) in the mnemonic
-pub enum WordCount {
-    /// 12 words mnemonic (128 bits entropy)
-    Words12 = 128,
-    /// 15 words mnemonic (160 bits entropy)
-    Words15 = 160,
-    /// 18 words mnemonic (192 bits entropy)
-    Words18 = 192,
-    /// 21 words mnemonic (224 bits entropy)
-    Words21 = 224,
-    /// 24 words mnemonic (256 bits entropy)
-    Words24 = 256,
-}
-
-use super::{
-    any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
-};
-
-fn set_valid_on_any_network<Ctx: ScriptContext>(
-    descriptor_key: DescriptorKey<Ctx>,
-) -> DescriptorKey<Ctx> {
-    // We have to pick one network to build the xprv, but since the bip39 standard doesn't
-    // encode the network, the xprv we create is actually valid everywhere. So we override the
-    // valid networks with `any_network()`.
-    descriptor_key.override_valid_networks(any_network())
-}
-
-/// Type for a BIP39 mnemonic with an optional passphrase
-pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
-
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        Ok(bip32::Xpriv::new_master(Network::Bitcoin, &self[..])?.into())
-    }
-
-    fn into_descriptor_key(
-        self,
-        source: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let descriptor_key = self
-            .into_extended_key()?
-            .into_descriptor_key(source, derivation_path)?;
-
-        Ok(set_valid_on_any_network(descriptor_key))
-    }
-}
-
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        let (mnemonic, passphrase) = self;
-        let seed: Seed = mnemonic.to_seed(passphrase.as_deref().unwrap_or(""));
-
-        seed.into_extended_key()
-    }
-
-    fn into_descriptor_key(
-        self,
-        source: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let descriptor_key = self
-            .into_extended_key()?
-            .into_descriptor_key(source, derivation_path)?;
-
-        Ok(set_valid_on_any_network(descriptor_key))
-    }
-}
-
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for (GeneratedKey<Mnemonic, Ctx>, Option<String>) {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        let (mnemonic, passphrase) = self;
-        (mnemonic.into_key(), passphrase).into_extended_key()
-    }
-
-    fn into_descriptor_key(
-        self,
-        source: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let (mnemonic, passphrase) = self;
-        (mnemonic.into_key(), passphrase).into_descriptor_key(source, derivation_path)
-    }
-}
-
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        (self, None).into_extended_key()
-    }
-
-    fn into_descriptor_key(
-        self,
-        source: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let descriptor_key = self
-            .into_extended_key()?
-            .into_descriptor_key(source, derivation_path)?;
-
-        Ok(set_valid_on_any_network(descriptor_key))
-    }
-}
-
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
-    type Entropy = [u8; 32];
-
-    type Options = (WordCount, Language);
-    type Error = Option<bip39::Error>;
-
-    fn generate_with_entropy(
-        (word_count, language): Self::Options,
-        entropy: Self::Entropy,
-    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        let entropy = &entropy[..(word_count as usize / 8)];
-        let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
-
-        Ok(GeneratedKey::new(mnemonic, any_network()))
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use alloc::string::ToString;
-    use core::str::FromStr;
-
-    use bitcoin::bip32;
-
-    use bip39::{Language, Mnemonic};
-
-    use crate::keys::{any_network, GeneratableKey, GeneratedKey};
-
-    use super::WordCount;
-
-    #[test]
-    fn test_keys_bip39_mnemonic() {
-        let mnemonic =
-            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
-        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
-        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
-
-        let key = (mnemonic, path);
-        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
-        assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#0r8v4nkv");
-        assert_eq!(keys.len(), 1);
-        assert_eq!(networks.len(), 4);
-    }
-
-    #[test]
-    fn test_keys_bip39_mnemonic_passphrase() {
-        let mnemonic =
-            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
-        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
-        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
-
-        let key = ((mnemonic, Some("passphrase".into())), path);
-        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
-        assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)#h0j0tg5m");
-        assert_eq!(keys.len(), 1);
-        assert_eq!(networks.len(), 4);
-    }
-
-    #[test]
-    fn test_keys_generate_bip39() {
-        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
-            Mnemonic::generate_with_entropy(
-                (WordCount::Words12, Language::English),
-                crate::keys::test::TEST_ENTROPY,
-            )
-            .unwrap();
-        assert_eq!(generated_mnemonic.valid_networks, any_network());
-        assert_eq!(
-            generated_mnemonic.to_string(),
-            "primary fetch primary fetch primary fetch primary fetch primary fetch primary fever"
-        );
-
-        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
-            Mnemonic::generate_with_entropy(
-                (WordCount::Words24, Language::English),
-                crate::keys::test::TEST_ENTROPY,
-            )
-            .unwrap();
-        assert_eq!(generated_mnemonic.valid_networks, any_network());
-        assert_eq!(generated_mnemonic.to_string(), "primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary foster");
-    }
-
-    #[test]
-    fn test_keys_generate_bip39_random() {
-        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
-            Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
-        assert_eq!(generated_mnemonic.valid_networks, any_network());
-
-        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
-            Mnemonic::generate((WordCount::Words24, Language::English)).unwrap();
-        assert_eq!(generated_mnemonic.valid_networks, any_network());
-    }
-}
diff --git a/crates/bdk/src/keys/mod.rs b/crates/bdk/src/keys/mod.rs
deleted file mode 100644 (file)
index 75abb3a..0000000
+++ /dev/null
@@ -1,1006 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Key formats
-
-use crate::collections::HashSet;
-use alloc::string::{String, ToString};
-use alloc::vec::Vec;
-use core::any::TypeId;
-use core::fmt;
-use core::marker::PhantomData;
-use core::ops::Deref;
-use core::str::FromStr;
-
-use bitcoin::secp256k1::{self, Secp256k1, Signing};
-
-use bitcoin::bip32;
-use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey};
-
-use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
-pub use miniscript::descriptor::{
-    DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey,
-    SortedMultiVec,
-};
-pub use miniscript::ScriptContext;
-use miniscript::{Miniscript, Terminal};
-
-use crate::descriptor::{CheckMiniscript, DescriptorError};
-use crate::wallet::utils::SecpCtx;
-
-#[cfg(feature = "keys-bip39")]
-#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
-pub mod bip39;
-
-/// Set of valid networks for a key
-pub type ValidNetworks = HashSet<Network>;
-
-/// Create a set containing mainnet, testnet, signet, and regtest
-pub fn any_network() -> ValidNetworks {
-    vec![
-        Network::Bitcoin,
-        Network::Testnet,
-        Network::Regtest,
-        Network::Signet,
-    ]
-    .into_iter()
-    .collect()
-}
-/// Create a set only containing mainnet
-pub fn mainnet_network() -> ValidNetworks {
-    vec![Network::Bitcoin].into_iter().collect()
-}
-/// Create a set containing testnet and regtest
-pub fn test_networks() -> ValidNetworks {
-    vec![Network::Testnet, Network::Regtest, Network::Signet]
-        .into_iter()
-        .collect()
-}
-/// Compute the intersection of two sets
-pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks {
-    a.intersection(b).cloned().collect()
-}
-
-/// Container for public or secret keys
-#[derive(Debug)]
-pub enum DescriptorKey<Ctx: ScriptContext> {
-    #[doc(hidden)]
-    Public(DescriptorPublicKey, ValidNetworks, PhantomData<Ctx>),
-    #[doc(hidden)]
-    Secret(DescriptorSecretKey, ValidNetworks, PhantomData<Ctx>),
-}
-
-impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
-    /// Create an instance given a public key and a set of valid networks
-    pub fn from_public(public: DescriptorPublicKey, networks: ValidNetworks) -> Self {
-        DescriptorKey::Public(public, networks, PhantomData)
-    }
-
-    /// Create an instance given a secret key and a set of valid networks
-    pub fn from_secret(secret: DescriptorSecretKey, networks: ValidNetworks) -> Self {
-        DescriptorKey::Secret(secret, networks, PhantomData)
-    }
-
-    /// Override the computed set of valid networks
-    pub fn override_valid_networks(self, networks: ValidNetworks) -> Self {
-        match self {
-            DescriptorKey::Public(key, _, _) => DescriptorKey::Public(key, networks, PhantomData),
-            DescriptorKey::Secret(key, _, _) => DescriptorKey::Secret(key, networks, PhantomData),
-        }
-    }
-
-    // This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
-    // public because it is effectively called by external crates once the macros are expanded,
-    // but since it is not meant to be part of the public api we hide it from the docs.
-    #[doc(hidden)]
-    pub fn extract(
-        self,
-        secp: &SecpCtx,
-    ) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> {
-        match self {
-            DescriptorKey::Public(public, valid_networks, _) => {
-                Ok((public, KeyMap::default(), valid_networks))
-            }
-            DescriptorKey::Secret(secret, valid_networks, _) => {
-                let mut key_map = KeyMap::new();
-
-                let public = secret
-                    .to_public(secp)
-                    .map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
-                key_map.insert(public.clone(), secret);
-
-                Ok((public, key_map, valid_networks))
-            }
-        }
-    }
-}
-
-/// Enum representation of the known valid [`ScriptContext`]s
-#[derive(Debug, Eq, PartialEq, Copy, Clone)]
-pub enum ScriptContextEnum {
-    /// Legacy scripts
-    Legacy,
-    /// Segwitv0 scripts
-    Segwitv0,
-    /// Taproot scripts
-    Tap,
-}
-
-impl ScriptContextEnum {
-    /// Returns whether the script context is [`ScriptContextEnum::Legacy`]
-    pub fn is_legacy(&self) -> bool {
-        self == &ScriptContextEnum::Legacy
-    }
-
-    /// Returns whether the script context is [`ScriptContextEnum::Segwitv0`]
-    pub fn is_segwit_v0(&self) -> bool {
-        self == &ScriptContextEnum::Segwitv0
-    }
-
-    /// Returns whether the script context is [`ScriptContextEnum::Tap`]
-    pub fn is_taproot(&self) -> bool {
-        self == &ScriptContextEnum::Tap
-    }
-}
-
-/// Trait that adds extra useful methods to [`ScriptContext`]s
-pub trait ExtScriptContext: ScriptContext {
-    /// Returns the [`ScriptContext`] as a [`ScriptContextEnum`]
-    fn as_enum() -> ScriptContextEnum;
-
-    /// Returns whether the script context is [`Legacy`](miniscript::Legacy)
-    fn is_legacy() -> bool {
-        Self::as_enum().is_legacy()
-    }
-
-    /// Returns whether the script context is [`Segwitv0`](miniscript::Segwitv0)
-    fn is_segwit_v0() -> bool {
-        Self::as_enum().is_segwit_v0()
-    }
-
-    /// Returns whether the script context is [`Tap`](miniscript::Tap), aka Taproot or Segwit V1
-    fn is_taproot() -> bool {
-        Self::as_enum().is_taproot()
-    }
-}
-
-impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
-    fn as_enum() -> ScriptContextEnum {
-        match TypeId::of::<Ctx>() {
-            t if t == TypeId::of::<miniscript::Legacy>() => ScriptContextEnum::Legacy,
-            t if t == TypeId::of::<miniscript::Segwitv0>() => ScriptContextEnum::Segwitv0,
-            t if t == TypeId::of::<miniscript::Tap>() => ScriptContextEnum::Tap,
-            _ => unimplemented!("Unknown ScriptContext type"),
-        }
-    }
-}
-
-/// Trait for objects that can be turned into a public or secret [`DescriptorKey`]
-///
-/// The generic type `Ctx` is used to define the context in which the key is valid: some key
-/// formats, like the mnemonics used by Electrum wallets, encode internally whether the wallet is
-/// legacy or segwit. Thus, trying to turn a valid legacy mnemonic into a `DescriptorKey`
-/// that would become part of a segwit descriptor should fail.
-///
-/// For key types that do care about this, the [`ExtScriptContext`] trait provides some useful
-/// methods that can be used to check at runtime which `Ctx` is being used.
-///
-/// For key types that can do this check statically (because they can only work within a
-/// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type
-/// checking.
-///
-/// Keys also have control over the networks they support: constructing the return object with
-/// [`DescriptorKey::from_public`] or [`DescriptorKey::from_secret`] allows to specify a set of
-/// [`ValidNetworks`].
-///
-/// ## Examples
-///
-/// Key type valid in any context:
-///
-/// ```
-/// use bdk::bitcoin::PublicKey;
-///
-/// use bdk::keys::{DescriptorKey, IntoDescriptorKey, KeyError, ScriptContext};
-///
-/// pub struct MyKeyType {
-///     pubkey: PublicKey,
-/// }
-///
-/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
-///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-///         self.pubkey.into_descriptor_key()
-///     }
-/// }
-/// ```
-///
-/// Key type that is only valid on mainnet:
-///
-/// ```
-/// use bdk::bitcoin::PublicKey;
-///
-/// use bdk::keys::{
-///     mainnet_network, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError,
-///     ScriptContext, SinglePub, SinglePubKey,
-/// };
-///
-/// pub struct MyKeyType {
-///     pubkey: PublicKey,
-/// }
-///
-/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
-///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-///         Ok(DescriptorKey::from_public(
-///             DescriptorPublicKey::Single(SinglePub {
-///                 origin: None,
-///                 key: SinglePubKey::FullKey(self.pubkey),
-///             }),
-///             mainnet_network(),
-///         ))
-///     }
-/// }
-/// ```
-///
-/// Key type that internally encodes in which context it's valid. The context is checked at runtime:
-///
-/// ```
-/// use bdk::bitcoin::PublicKey;
-///
-/// use bdk::keys::{DescriptorKey, ExtScriptContext, IntoDescriptorKey, KeyError, ScriptContext};
-///
-/// pub struct MyKeyType {
-///     is_legacy: bool,
-///     pubkey: PublicKey,
-/// }
-///
-/// impl<Ctx: ScriptContext + 'static> IntoDescriptorKey<Ctx> for MyKeyType {
-///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-///         if Ctx::is_legacy() == self.is_legacy {
-///             self.pubkey.into_descriptor_key()
-///         } else {
-///             Err(KeyError::InvalidScriptContext)
-///         }
-///     }
-/// }
-/// ```
-///
-/// Key type that can only work within [`miniscript::Segwitv0`] context. Only the specialized version
-/// of the trait is implemented.
-///
-/// This example deliberately fails to compile, to demonstrate how the compiler can catch when keys
-/// are misused. In this case, the "segwit-only" key is used to build a `pkh()` descriptor, which
-/// makes the compiler (correctly) fail.
-///
-/// ```compile_fail
-/// use bdk::bitcoin::PublicKey;
-/// use core::str::FromStr;
-///
-/// use bdk::keys::{DescriptorKey, IntoDescriptorKey, KeyError};
-///
-/// pub struct MySegwitOnlyKeyType {
-///     pubkey: PublicKey,
-/// }
-///
-/// impl IntoDescriptorKey<bdk::miniscript::Segwitv0> for MySegwitOnlyKeyType {
-///     fn into_descriptor_key(self) -> Result<DescriptorKey<bdk::miniscript::Segwitv0>, KeyError> {
-///         self.pubkey.into_descriptor_key()
-///     }
-/// }
-///
-/// let key = MySegwitOnlyKeyType {
-///     pubkey: PublicKey::from_str("...")?,
-/// };
-/// let (descriptor, _, _) = bdk::descriptor!(pkh(key))?;
-/// //                                       ^^^^^ changing this to `wpkh` would make it compile
-///
-/// # Ok::<_, Box<dyn std::error::Error>>(())
-/// ```
-pub trait IntoDescriptorKey<Ctx: ScriptContext>: Sized {
-    /// Turn the key into a [`DescriptorKey`] within the requested [`ScriptContext`]
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError>;
-}
-
-/// Enum for extended keys that can be either `xprv` or `xpub`
-///
-/// An instance of [`ExtendedKey`] can be constructed from an [`Xpriv`](bip32::Xpriv)
-/// or an [`Xpub`](bip32::Xpub) by using the `From` trait.
-///
-/// Defaults to the [`Legacy`](miniscript::Legacy) context.
-pub enum ExtendedKey<Ctx: ScriptContext = miniscript::Legacy> {
-    /// A private extended key, aka an `xprv`
-    Private((bip32::Xpriv, PhantomData<Ctx>)),
-    /// A public extended key, aka an `xpub`
-    Public((bip32::Xpub, PhantomData<Ctx>)),
-}
-
-impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
-    /// Return whether or not the key contains the private data
-    pub fn has_secret(&self) -> bool {
-        match self {
-            ExtendedKey::Private(_) => true,
-            ExtendedKey::Public(_) => false,
-        }
-    }
-
-    /// Transform the [`ExtendedKey`] into an [`Xpriv`](bip32::Xpriv) for the
-    /// given [`Network`], if the key contains the private data
-    pub fn into_xprv(self, network: Network) -> Option<bip32::Xpriv> {
-        match self {
-            ExtendedKey::Private((mut xprv, _)) => {
-                xprv.network = network;
-                Some(xprv)
-            }
-            ExtendedKey::Public(_) => None,
-        }
-    }
-
-    /// Transform the [`ExtendedKey`] into an [`Xpub`](bip32::Xpub) for the
-    /// given [`Network`]
-    pub fn into_xpub<C: Signing>(
-        self,
-        network: bitcoin::Network,
-        secp: &Secp256k1<C>,
-    ) -> bip32::Xpub {
-        let mut xpub = match self {
-            ExtendedKey::Private((xprv, _)) => bip32::Xpub::from_priv(secp, &xprv),
-            ExtendedKey::Public((xpub, _)) => xpub,
-        };
-
-        xpub.network = network;
-        xpub
-    }
-}
-
-impl<Ctx: ScriptContext> From<bip32::Xpub> for ExtendedKey<Ctx> {
-    fn from(xpub: bip32::Xpub) -> Self {
-        ExtendedKey::Public((xpub, PhantomData))
-    }
-}
-
-impl<Ctx: ScriptContext> From<bip32::Xpriv> for ExtendedKey<Ctx> {
-    fn from(xprv: bip32::Xpriv) -> Self {
-        ExtendedKey::Private((xprv, PhantomData))
-    }
-}
-
-/// Trait for keys that can be derived.
-///
-/// When extra metadata are provided, a [`DerivableKey`] can be transformed into a
-/// [`DescriptorKey`]: the trait [`IntoDescriptorKey`] is automatically implemented
-/// for `(DerivableKey, DerivationPath)` and
-/// `(DerivableKey, KeySource, DerivationPath)` tuples.
-///
-/// For key types that don't encode any indication about the path to use (like bip39), it's
-/// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same
-/// rules regarding script context and valid networks apply.
-///
-/// ## Examples
-///
-/// Key types that can be directly converted into an [`Xpriv`] or
-/// an [`Xpub`] can implement only the required `into_extended_key()` method.
-///
-/// ```
-/// use bdk::bitcoin;
-/// use bdk::bitcoin::bip32;
-/// use bdk::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext};
-///
-/// struct MyCustomKeyType {
-///     key_data: bitcoin::PrivateKey,
-///     chain_code: [u8; 32],
-///     network: bitcoin::Network,
-/// }
-///
-/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
-///     fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-///         let xprv = bip32::Xpriv {
-///             network: self.network,
-///             depth: 0,
-///             parent_fingerprint: bip32::Fingerprint::default(),
-///             private_key: self.key_data.inner,
-///             chain_code: bip32::ChainCode::from(&self.chain_code),
-///             child_number: bip32::ChildNumber::Normal { index: 0 },
-///         };
-///
-///         xprv.into_extended_key()
-///     }
-/// }
-/// ```
-///
-/// Types that don't internally encode the [`Network`] in which they are valid need some extra
-/// steps to override the set of valid networks, otherwise only the network specified in the
-/// [`Xpriv`] or [`Xpub`] will be considered valid.
-///
-/// ```
-/// use bdk::bitcoin;
-/// use bdk::bitcoin::bip32;
-/// use bdk::keys::{
-///     any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext,
-/// };
-///
-/// struct MyCustomKeyType {
-///     key_data: bitcoin::PrivateKey,
-///     chain_code: [u8; 32],
-/// }
-///
-/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
-///     fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-///         let xprv = bip32::Xpriv {
-///             network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
-///             depth: 0,
-///             parent_fingerprint: bip32::Fingerprint::default(),
-///             private_key: self.key_data.inner,
-///             chain_code: bip32::ChainCode::from(&self.chain_code),
-///             child_number: bip32::ChildNumber::Normal { index: 0 },
-///         };
-///
-///         xprv.into_extended_key()
-///     }
-///
-///     fn into_descriptor_key(
-///         self,
-///         source: Option<bip32::KeySource>,
-///         derivation_path: bip32::DerivationPath,
-///     ) -> Result<DescriptorKey<Ctx>, KeyError> {
-///         let descriptor_key = self
-///             .into_extended_key()?
-///             .into_descriptor_key(source, derivation_path)?;
-///
-///         // Override the set of valid networks here
-///         Ok(descriptor_key.override_valid_networks(any_network()))
-///     }
-/// }
-/// ```
-///
-/// [`DerivationPath`]: (bip32::DerivationPath)
-/// [`Xpriv`]: (bip32::Xpriv)
-/// [`Xpub`]: (bip32::Xpub)
-pub trait DerivableKey<Ctx: ScriptContext = miniscript::Legacy>: Sized {
-    /// Consume `self` and turn it into an [`ExtendedKey`]
-    #[cfg_attr(
-        feature = "keys-bip39",
-        doc = r##"
-This can be used to get direct access to `xprv`s and `xpub`s for types that implement this trait,
-like [`Mnemonic`](bip39::Mnemonic) when the `keys-bip39` feature is enabled.
-```rust
-use bdk::bitcoin::Network;
-use bdk::keys::{DerivableKey, ExtendedKey};
-use bdk::keys::bip39::{Mnemonic, Language};
-
-# fn main() -> Result<(), Box<dyn std::error::Error>> {
-let xkey: ExtendedKey =
-    Mnemonic::parse_in(
-        Language::English,
-        "jelly crash boy whisper mouse ecology tuna soccer memory million news short",
-    )?
-    .into_extended_key()?;
-let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
-# Ok(()) }
-```
-"##
-    )]
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError>;
-
-    /// Consume `self` and turn it into a [`DescriptorKey`] by adding the extra metadata, such as
-    /// key origin and derivation path
-    fn into_descriptor_key(
-        self,
-        origin: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        match self.into_extended_key()? {
-            ExtendedKey::Private((xprv, _)) => DescriptorSecretKey::XPrv(DescriptorXKey {
-                origin,
-                xkey: xprv,
-                derivation_path,
-                wildcard: Wildcard::Unhardened,
-            })
-            .into_descriptor_key(),
-            ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey {
-                origin,
-                xkey: xpub,
-                derivation_path,
-                wildcard: Wildcard::Unhardened,
-            })
-            .into_descriptor_key(),
-        }
-    }
-}
-
-/// Identity conversion
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for ExtendedKey<Ctx> {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        Ok(self)
-    }
-}
-
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpub {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        Ok(self.into())
-    }
-}
-
-impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpriv {
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        Ok(self.into())
-    }
-}
-
-/// Output of a [`GeneratableKey`] key generation
-pub struct GeneratedKey<K, Ctx: ScriptContext> {
-    key: K,
-    valid_networks: ValidNetworks,
-    phantom: PhantomData<Ctx>,
-}
-
-impl<K, Ctx: ScriptContext> GeneratedKey<K, Ctx> {
-    fn new(key: K, valid_networks: ValidNetworks) -> Self {
-        GeneratedKey {
-            key,
-            valid_networks,
-            phantom: PhantomData,
-        }
-    }
-
-    /// Consumes `self` and returns the key
-    pub fn into_key(self) -> K {
-        self.key
-    }
-}
-
-impl<K, Ctx: ScriptContext> Deref for GeneratedKey<K, Ctx> {
-    type Target = K;
-
-    fn deref(&self) -> &Self::Target {
-        &self.key
-    }
-}
-
-impl<K: Clone, Ctx: ScriptContext> Clone for GeneratedKey<K, Ctx> {
-    fn clone(&self) -> GeneratedKey<K, Ctx> {
-        GeneratedKey {
-            key: self.key.clone(),
-            valid_networks: self.valid_networks.clone(),
-            phantom: self.phantom,
-        }
-    }
-}
-
-// Make generated "derivable" keys themselves "derivable". Also make sure they are assigned the
-// right `valid_networks`.
-impl<Ctx, K> DerivableKey<Ctx> for GeneratedKey<K, Ctx>
-where
-    Ctx: ScriptContext,
-    K: DerivableKey<Ctx>,
-{
-    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
-        self.key.into_extended_key()
-    }
-
-    fn into_descriptor_key(
-        self,
-        origin: Option<bip32::KeySource>,
-        derivation_path: bip32::DerivationPath,
-    ) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let descriptor_key = self.key.into_descriptor_key(origin, derivation_path)?;
-        Ok(descriptor_key.override_valid_networks(self.valid_networks))
-    }
-}
-
-// Make generated keys directly usable in descriptors, and make sure they get assigned the right
-// `valid_networks`.
-impl<Ctx, K> IntoDescriptorKey<Ctx> for GeneratedKey<K, Ctx>
-where
-    Ctx: ScriptContext,
-    K: IntoDescriptorKey<Ctx>,
-{
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let desc_key = self.key.into_descriptor_key()?;
-        Ok(desc_key.override_valid_networks(self.valid_networks))
-    }
-}
-
-/// Trait for keys that can be generated
-///
-/// The same rules about [`ScriptContext`] and [`ValidNetworks`] from [`IntoDescriptorKey`] apply.
-///
-/// This trait is particularly useful when combined with [`DerivableKey`]: if `Self`
-/// implements it, the returned [`GeneratedKey`] will also implement it. The same is true for
-/// [`IntoDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
-/// [`IntoDescriptorKey`].
-pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
-    /// Type specifying the amount of entropy required e.g. `[u8;32]`
-    type Entropy: AsMut<[u8]> + Default;
-
-    /// Extra options required by the `generate_with_entropy`
-    type Options;
-    /// Returned error in case of failure
-    type Error: core::fmt::Debug;
-
-    /// Generate a key given the extra options and the entropy
-    fn generate_with_entropy(
-        options: Self::Options,
-        entropy: Self::Entropy,
-    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error>;
-
-    /// Generate a key given the options with a random entropy
-    fn generate(options: Self::Options) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        use rand::{thread_rng, Rng};
-
-        let mut entropy = Self::Entropy::default();
-        thread_rng().fill(entropy.as_mut());
-        Self::generate_with_entropy(options, entropy)
-    }
-}
-
-/// Trait that allows generating a key with the default options
-///
-/// This trait is automatically implemented if the [`GeneratableKey::Options`] implements [`Default`].
-pub trait GeneratableDefaultOptions<Ctx>: GeneratableKey<Ctx>
-where
-    Ctx: ScriptContext,
-    <Self as GeneratableKey<Ctx>>::Options: Default,
-{
-    /// Generate a key with the default options and a given entropy
-    fn generate_with_entropy_default(
-        entropy: Self::Entropy,
-    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        Self::generate_with_entropy(Default::default(), entropy)
-    }
-
-    /// Generate a key with the default options and a random entropy
-    fn generate_default() -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        Self::generate(Default::default())
-    }
-}
-
-/// Automatic implementation of [`GeneratableDefaultOptions`] for [`GeneratableKey`]s where
-/// `Options` implements `Default`
-impl<Ctx, K> GeneratableDefaultOptions<Ctx> for K
-where
-    Ctx: ScriptContext,
-    K: GeneratableKey<Ctx>,
-    <K as GeneratableKey<Ctx>>::Options: Default,
-{
-}
-
-impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::Xpriv {
-    type Entropy = [u8; 32];
-
-    type Options = ();
-    type Error = bip32::Error;
-
-    fn generate_with_entropy(
-        _: Self::Options,
-        entropy: Self::Entropy,
-    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        // pick a arbitrary network here, but say that we support all of them
-        let xprv = bip32::Xpriv::new_master(Network::Bitcoin, entropy.as_ref())?;
-        Ok(GeneratedKey::new(xprv, any_network()))
-    }
-}
-
-/// Options for generating a [`PrivateKey`]
-///
-/// Defaults to creating compressed keys, which save on-chain bytes and fees
-#[derive(Debug, Copy, Clone)]
-pub struct PrivateKeyGenerateOptions {
-    /// Whether the generated key should be "compressed" or not
-    pub compressed: bool,
-}
-
-impl Default for PrivateKeyGenerateOptions {
-    fn default() -> Self {
-        PrivateKeyGenerateOptions { compressed: true }
-    }
-}
-
-impl<Ctx: ScriptContext> GeneratableKey<Ctx> for PrivateKey {
-    type Entropy = [u8; secp256k1::constants::SECRET_KEY_SIZE];
-
-    type Options = PrivateKeyGenerateOptions;
-    type Error = bip32::Error;
-
-    fn generate_with_entropy(
-        options: Self::Options,
-        entropy: Self::Entropy,
-    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
-        // pick a arbitrary network here, but say that we support all of them
-        let inner = secp256k1::SecretKey::from_slice(&entropy)?;
-        let private_key = PrivateKey {
-            compressed: options.compressed,
-            network: Network::Bitcoin,
-            inner,
-        };
-
-        Ok(GeneratedKey::new(private_key, any_network()))
-    }
-}
-
-impl<Ctx: ScriptContext, T: DerivableKey<Ctx>> IntoDescriptorKey<Ctx>
-    for (T, bip32::DerivationPath)
-{
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        self.0.into_descriptor_key(None, self.1)
-    }
-}
-
-impl<Ctx: ScriptContext, T: DerivableKey<Ctx>> IntoDescriptorKey<Ctx>
-    for (T, bip32::KeySource, bip32::DerivationPath)
-{
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        self.0.into_descriptor_key(Some(self.1), self.2)
-    }
-}
-
-fn expand_multi_keys<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
-    pks: Vec<Pk>,
-    secp: &SecpCtx,
-) -> Result<(Vec<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError> {
-    let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks
-        .into_iter()
-        .map(|key| key.into_descriptor_key()?.extract(secp))
-        .collect::<Result<Vec<_>, _>>()?
-        .into_iter()
-        .map(|(a, b, c)| (a, (b, c)))
-        .unzip();
-
-    let (key_map, valid_networks) = key_maps_networks.into_iter().fold(
-        (KeyMap::default(), any_network()),
-        |(mut keys_acc, net_acc), (key, net)| {
-            keys_acc.extend(key);
-            let net_acc = merge_networks(&net_acc, &net);
-
-            (keys_acc, net_acc)
-        },
-    );
-
-    Ok((pks, key_map, valid_networks))
-}
-
-// Used internally by `bdk::fragment!` to build `pk_k()` fragments
-#[doc(hidden)]
-pub fn make_pk<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
-    descriptor_key: Pk,
-    secp: &SecpCtx,
-) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
-    let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?;
-    let minisc = Miniscript::from_ast(Terminal::PkK(key))?;
-
-    minisc.check_miniscript()?;
-
-    Ok((minisc, key_map, valid_networks))
-}
-
-// Used internally by `bdk::fragment!` to build `pk_h()` fragments
-#[doc(hidden)]
-pub fn make_pkh<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
-    descriptor_key: Pk,
-    secp: &SecpCtx,
-) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
-    let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?;
-    let minisc = Miniscript::from_ast(Terminal::PkH(key))?;
-
-    minisc.check_miniscript()?;
-
-    Ok((minisc, key_map, valid_networks))
-}
-
-// Used internally by `bdk::fragment!` to build `multi()` fragments
-#[doc(hidden)]
-pub fn make_multi<
-    Pk: IntoDescriptorKey<Ctx>,
-    Ctx: ScriptContext,
-    V: Fn(usize, Vec<DescriptorPublicKey>) -> Terminal<DescriptorPublicKey, Ctx>,
->(
-    thresh: usize,
-    variant: V,
-    pks: Vec<Pk>,
-    secp: &SecpCtx,
-) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
-    let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
-    let minisc = Miniscript::from_ast(variant(thresh, pks))?;
-
-    minisc.check_miniscript()?;
-
-    Ok((minisc, key_map, valid_networks))
-}
-
-// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
-#[doc(hidden)]
-pub fn make_sortedmulti<Pk, Ctx, F>(
-    thresh: usize,
-    pks: Vec<Pk>,
-    build_desc: F,
-    secp: &SecpCtx,
-) -> Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>
-where
-    Pk: IntoDescriptorKey<Ctx>,
-    Ctx: ScriptContext,
-    F: Fn(
-        usize,
-        Vec<DescriptorPublicKey>,
-    ) -> Result<(Descriptor<DescriptorPublicKey>, PhantomData<Ctx>), DescriptorError>,
-{
-    let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
-    let descriptor = build_desc(thresh, pks)?.0;
-
-    Ok((descriptor, key_map, valid_networks))
-}
-
-/// The "identity" conversion is used internally by some `bdk::fragment`s
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorKey<Ctx> {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        Ok(self)
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let networks = match self {
-            DescriptorPublicKey::Single(_) => any_network(),
-            DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
-                if xkey.network == Network::Bitcoin =>
-            {
-                mainnet_network()
-            }
-            _ => test_networks(),
-        };
-
-        Ok(DescriptorKey::from_public(self, networks))
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        DescriptorPublicKey::Single(SinglePub {
-            key: SinglePubKey::FullKey(self),
-            origin: None,
-        })
-        .into_descriptor_key()
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        DescriptorPublicKey::Single(SinglePub {
-            key: SinglePubKey::XOnly(self),
-            origin: None,
-        })
-        .into_descriptor_key()
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorSecretKey {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        let networks = match &self {
-            DescriptorSecretKey::Single(sk) if sk.key.network == Network::Bitcoin => {
-                mainnet_network()
-            }
-            DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
-                if xkey.network == Network::Bitcoin =>
-            {
-                mainnet_network()
-            }
-            _ => test_networks(),
-        };
-
-        Ok(DescriptorKey::from_secret(self, networks))
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for &'_ str {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        DescriptorSecretKey::from_str(self)
-            .map_err(|e| KeyError::Message(e.to_string()))?
-            .into_descriptor_key()
-    }
-}
-
-impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PrivateKey {
-    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
-        DescriptorSecretKey::Single(SinglePriv {
-            key: self,
-            origin: None,
-        })
-        .into_descriptor_key()
-    }
-}
-
-/// Errors thrown while working with [`keys`](crate::keys)
-#[derive(Debug)]
-pub enum KeyError {
-    /// The key cannot exist in the given script context
-    InvalidScriptContext,
-    /// The key is not valid for the given network
-    InvalidNetwork,
-    /// The key has an invalid checksum
-    InvalidChecksum,
-
-    /// Custom error message
-    Message(String),
-
-    /// BIP32 error
-    Bip32(bitcoin::bip32::Error),
-    /// Miniscript error
-    Miniscript(miniscript::Error),
-}
-
-impl From<miniscript::Error> for KeyError {
-    fn from(err: miniscript::Error) -> Self {
-        KeyError::Miniscript(err)
-    }
-}
-
-impl From<bip32::Error> for KeyError {
-    fn from(err: bip32::Error) -> Self {
-        KeyError::Bip32(err)
-    }
-}
-
-impl fmt::Display for KeyError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::InvalidScriptContext => write!(f, "Invalid script context"),
-            Self::InvalidNetwork => write!(f, "Invalid network"),
-            Self::InvalidChecksum => write!(f, "Invalid checksum"),
-            Self::Message(err) => write!(f, "{}", err),
-            Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
-            Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for KeyError {}
-
-#[cfg(test)]
-pub mod test {
-    use bitcoin::bip32;
-
-    use super::*;
-
-    pub const TEST_ENTROPY: [u8; 32] = [0xAA; 32];
-
-    #[test]
-    fn test_keys_generate_xprv() {
-        let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
-            bip32::Xpriv::generate_with_entropy_default(TEST_ENTROPY).unwrap();
-
-        assert_eq!(generated_xprv.valid_networks, any_network());
-        assert_eq!(generated_xprv.to_string(), "xprv9s21ZrQH143K4Xr1cJyqTvuL2FWR8eicgY9boWqMBv8MDVUZ65AXHnzBrK1nyomu6wdcabRgmGTaAKawvhAno1V5FowGpTLVx3jxzE5uk3Q");
-    }
-
-    #[test]
-    fn test_keys_generate_wif() {
-        let generated_wif: GeneratedKey<_, miniscript::Segwitv0> =
-            bitcoin::PrivateKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
-
-        assert_eq!(generated_wif.valid_networks, any_network());
-        assert_eq!(
-            generated_wif.to_string(),
-            "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"
-        );
-    }
-
-    #[cfg(feature = "keys-bip39")]
-    #[test]
-    fn test_keys_wif_network_bip39() {
-        let xkey: ExtendedKey = bip39::Mnemonic::parse_in(
-            bip39::Language::English,
-            "jelly crash boy whisper mouse ecology tuna soccer memory million news short",
-        )
-        .unwrap()
-        .into_extended_key()
-        .unwrap();
-        let xprv = xkey.into_xprv(Network::Testnet).unwrap();
-
-        assert_eq!(xprv.network, Network::Testnet);
-    }
-}
diff --git a/crates/bdk/src/lib.rs b/crates/bdk/src/lib.rs
deleted file mode 100644 (file)
index f7c6f35..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-#![doc = include_str!("../README.md")]
-// only enables the `doc_cfg` feature when the `docsrs` configuration attribute is defined
-#![cfg_attr(docsrs, feature(doc_cfg))]
-#![cfg_attr(
-    docsrs,
-    doc(html_logo_url = "https://github.com/bitcoindevkit/bdk/raw/master/static/bdk.png")
-)]
-#![no_std]
-#![warn(missing_docs)]
-
-#[cfg(feature = "std")]
-#[macro_use]
-extern crate std;
-
-#[doc(hidden)]
-#[macro_use]
-pub extern crate alloc;
-
-pub extern crate bitcoin;
-pub extern crate miniscript;
-extern crate serde;
-extern crate serde_json;
-
-#[cfg(feature = "keys-bip39")]
-extern crate bip39;
-
-pub mod descriptor;
-pub mod keys;
-pub mod psbt;
-pub(crate) mod types;
-pub mod wallet;
-
-pub use descriptor::template;
-pub use descriptor::HdKeyPaths;
-pub use types::*;
-pub use wallet::signer;
-pub use wallet::signer::SignOptions;
-pub use wallet::tx_builder::TxBuilder;
-pub use wallet::Wallet;
-
-/// Get the version of BDK at runtime
-pub fn version() -> &'static str {
-    env!("CARGO_PKG_VERSION", "unknown")
-}
-
-pub use bdk_chain as chain;
-pub(crate) use bdk_chain::collections;
diff --git a/crates/bdk/src/psbt/mod.rs b/crates/bdk/src/psbt/mod.rs
deleted file mode 100644 (file)
index 7a66989..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Additional functions on the `rust-bitcoin` `Psbt` structure.
-
-use alloc::vec::Vec;
-use bitcoin::Amount;
-use bitcoin::FeeRate;
-use bitcoin::Psbt;
-use bitcoin::TxOut;
-
-// TODO upstream the functions here to `rust-bitcoin`?
-
-/// Trait to add functions to extract utxos and calculate fees.
-pub trait PsbtUtils {
-    /// Get the `TxOut` for the specified input index, if it doesn't exist in the PSBT `None` is returned.
-    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
-
-    /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats.
-    /// If the PSBT is missing a TxOut for an input returns None.
-    fn fee_amount(&self) -> Option<u64>;
-
-    /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
-    /// `Psbt` is finalized and all witness/signature data is added to the
-    /// transaction.
-    /// If the PSBT is missing a TxOut for an input returns None.
-    fn fee_rate(&self) -> Option<FeeRate>;
-}
-
-impl PsbtUtils for Psbt {
-    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
-        let tx = &self.unsigned_tx;
-        let input = self.inputs.get(input_index)?;
-
-        match (&input.witness_utxo, &input.non_witness_utxo) {
-            (Some(_), _) => input.witness_utxo.clone(),
-            (_, Some(_)) => input.non_witness_utxo.as_ref().map(|in_tx| {
-                in_tx.output[tx.input[input_index].previous_output.vout as usize].clone()
-            }),
-            _ => None,
-        }
-    }
-
-    fn fee_amount(&self) -> Option<u64> {
-        let tx = &self.unsigned_tx;
-        let utxos: Option<Vec<TxOut>> = (0..tx.input.len()).map(|i| self.get_utxo_for(i)).collect();
-
-        utxos.map(|inputs| {
-            let input_amount: u64 = inputs.iter().map(|i| i.value.to_sat()).sum();
-            let output_amount: u64 = self
-                .unsigned_tx
-                .output
-                .iter()
-                .map(|o| o.value.to_sat())
-                .sum();
-            input_amount
-                .checked_sub(output_amount)
-                .expect("input amount must be greater than output amount")
-        })
-    }
-
-    fn fee_rate(&self) -> Option<FeeRate> {
-        let fee_amount = self.fee_amount();
-        let weight = self.clone().extract_tx().ok()?.weight();
-        fee_amount.map(|fee| Amount::from_sat(fee) / weight)
-    }
-}
diff --git a/crates/bdk/src/types.rs b/crates/bdk/src/types.rs
deleted file mode 100644 (file)
index 4ce961b..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use alloc::boxed::Box;
-use core::convert::AsRef;
-
-use bdk_chain::ConfirmationTime;
-use bitcoin::blockdata::transaction::{OutPoint, Sequence, TxOut};
-use bitcoin::psbt;
-
-use serde::{Deserialize, Serialize};
-
-/// Types of keychains
-#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
-pub enum KeychainKind {
-    /// External keychain, used for deriving recipient addresses.
-    External = 0,
-    /// Internal keychain, used for deriving change addresses.
-    Internal = 1,
-}
-
-impl KeychainKind {
-    /// Return [`KeychainKind`] as a byte
-    pub fn as_byte(&self) -> u8 {
-        match self {
-            KeychainKind::External => b'e',
-            KeychainKind::Internal => b'i',
-        }
-    }
-}
-
-impl AsRef<[u8]> for KeychainKind {
-    fn as_ref(&self) -> &[u8] {
-        match self {
-            KeychainKind::External => b"e",
-            KeychainKind::Internal => b"i",
-        }
-    }
-}
-
-/// An unspent output owned by a [`Wallet`].
-///
-/// [`Wallet`]: crate::Wallet
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
-pub struct LocalOutput {
-    /// Reference to a transaction output
-    pub outpoint: OutPoint,
-    /// Transaction output
-    pub txout: TxOut,
-    /// Type of keychain
-    pub keychain: KeychainKind,
-    /// Whether this UTXO is spent or not
-    pub is_spent: bool,
-    /// The derivation index for the script pubkey in the wallet
-    pub derivation_index: u32,
-    /// The confirmation time for transaction containing this utxo
-    pub confirmation_time: ConfirmationTime,
-}
-
-/// A [`Utxo`] with its `satisfaction_weight`.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct WeightedUtxo {
-    /// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
-    /// properly maintain the feerate when adding this input to a transaction during coin selection.
-    ///
-    /// [weight units]: https://en.bitcoin.it/wiki/Weight_units
-    pub satisfaction_weight: usize,
-    /// The UTXO
-    pub utxo: Utxo,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-/// An unspent transaction output (UTXO).
-pub enum Utxo {
-    /// A UTXO owned by the local wallet.
-    Local(LocalOutput),
-    /// A UTXO owned by another wallet.
-    Foreign {
-        /// The location of the output.
-        outpoint: OutPoint,
-        /// The nSequence value to set for this input.
-        sequence: Option<Sequence>,
-        /// The information about the input we require to add it to a PSBT.
-        // Box it to stop the type being too big.
-        psbt_input: Box<psbt::Input>,
-    },
-}
-
-impl Utxo {
-    /// Get the location of the UTXO
-    pub fn outpoint(&self) -> OutPoint {
-        match &self {
-            Utxo::Local(local) => local.outpoint,
-            Utxo::Foreign { outpoint, .. } => *outpoint,
-        }
-    }
-
-    /// Get the `TxOut` of the UTXO
-    pub fn txout(&self) -> &TxOut {
-        match &self {
-            Utxo::Local(local) => &local.txout,
-            Utxo::Foreign {
-                outpoint,
-                psbt_input,
-                ..
-            } => {
-                if let Some(prev_tx) = &psbt_input.non_witness_utxo {
-                    return &prev_tx.output[outpoint.vout as usize];
-                }
-
-                if let Some(txout) = &psbt_input.witness_utxo {
-                    return txout;
-                }
-
-                unreachable!("Foreign UTXOs will always have one of these set")
-            }
-        }
-    }
-
-    /// Get the sequence number if an explicit sequence number has to be set for this input.
-    pub fn sequence(&self) -> Option<Sequence> {
-        match self {
-            Utxo::Local(_) => None,
-            Utxo::Foreign { sequence, .. } => *sequence,
-        }
-    }
-}
diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs
deleted file mode 100644 (file)
index 6be3cb9..0000000
+++ /dev/null
@@ -1,1602 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Coin selection
-//!
-//! This module provides the trait [`CoinSelectionAlgorithm`] that can be implemented to
-//! define custom coin selection algorithms.
-//!
-//! You can specify a custom coin selection algorithm through the [`coin_selection`] method on
-//! [`TxBuilder`]. [`DefaultCoinSelectionAlgorithm`] aliases the coin selection algorithm that will
-//! be used if it is not explicitly set.
-//!
-//! [`TxBuilder`]: super::tx_builder::TxBuilder
-//! [`coin_selection`]: super::tx_builder::TxBuilder::coin_selection
-//!
-//! ## Example
-//!
-//! ```
-//! # use std::str::FromStr;
-//! # use bitcoin::*;
-//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
-//! # use bdk::wallet::error::CreateTxError;
-//! # use bdk_persist::PersistBackend;
-//! # use bdk::*;
-//! # use bdk::wallet::coin_selection::decide_change;
-//! # use anyhow::Error;
-//! #[derive(Debug)]
-//! struct AlwaysSpendEverything;
-//!
-//! impl CoinSelectionAlgorithm for AlwaysSpendEverything {
-//!     fn coin_select(
-//!         &self,
-//!         required_utxos: Vec<WeightedUtxo>,
-//!         optional_utxos: Vec<WeightedUtxo>,
-//!         fee_rate: FeeRate,
-//!         target_amount: u64,
-//!         drain_script: &Script,
-//!     ) -> Result<CoinSelectionResult, coin_selection::Error> {
-//!         let mut selected_amount = 0;
-//!         let mut additional_weight = Weight::ZERO;
-//!         let all_utxos_selected = required_utxos
-//!             .into_iter()
-//!             .chain(optional_utxos)
-//!             .scan(
-//!                 (&mut selected_amount, &mut additional_weight),
-//!                 |(selected_amount, additional_weight), weighted_utxo| {
-//!                     **selected_amount += weighted_utxo.utxo.txout().value.to_sat();
-//!                     **additional_weight += Weight::from_wu(
-//!                         (TxIn::default().segwit_weight().to_wu()
-//!                             + weighted_utxo.satisfaction_weight as u64)
-//!                             as u64,
-//!                     );
-//!                     Some(weighted_utxo.utxo)
-//!                 },
-//!             )
-//!             .collect::<Vec<_>>();
-//!         let additional_fees = (fee_rate * additional_weight).to_sat();
-//!         let amount_needed_with_fees = additional_fees + target_amount;
-//!         if selected_amount < amount_needed_with_fees {
-//!             return Err(coin_selection::Error::InsufficientFunds {
-//!                 needed: amount_needed_with_fees,
-//!                 available: selected_amount,
-//!             });
-//!         }
-//!
-//!         let remaining_amount = selected_amount - amount_needed_with_fees;
-//!
-//!         let excess = decide_change(remaining_amount, fee_rate, drain_script);
-//!
-//!         Ok(CoinSelectionResult {
-//!             selected: all_utxos_selected,
-//!             fee_amount: additional_fees,
-//!             excess,
-//!         })
-//!     }
-//! }
-//!
-//! # let mut wallet = doctest_wallet!();
-//! // create wallet, sync, ...
-//!
-//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
-//!     .unwrap()
-//!     .require_network(Network::Testnet)
-//!     .unwrap();
-//! let psbt = {
-//!     let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
-//!     builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
-//!     builder.finish()?
-//! };
-//!
-//! // inspect, sign, broadcast, ...
-//!
-//! # Ok::<(), anyhow::Error>(())
-//! ```
-
-use crate::chain::collections::HashSet;
-use crate::wallet::utils::IsDust;
-use crate::Utxo;
-use crate::WeightedUtxo;
-use bitcoin::FeeRate;
-
-use alloc::vec::Vec;
-use bitcoin::consensus::encode::serialize;
-use bitcoin::OutPoint;
-use bitcoin::TxIn;
-use bitcoin::{Script, Weight};
-
-use core::convert::TryInto;
-use core::fmt::{self, Formatter};
-use rand::seq::SliceRandom;
-
-/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
-/// overridden
-pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
-
-/// Errors that can be thrown by the [`coin_selection`](crate::wallet::coin_selection) module
-#[derive(Debug)]
-pub enum Error {
-    /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
-    InsufficientFunds {
-        /// Sats needed for some transaction
-        needed: u64,
-        /// Sats available for spending
-        available: u64,
-    },
-    /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for
-    /// the desired outputs plus fee, if there is not such combination this error is thrown
-    BnBNoExactMatch,
-    /// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow
-    /// exponentially, thus a limit is set, and when hit, this error is thrown
-    BnBTotalTriesExceeded,
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::InsufficientFunds { needed, available } => write!(
-                f,
-                "Insufficient funds: {} sat available of {} sat needed",
-                available, needed
-            ),
-            Self::BnBTotalTriesExceeded => {
-                write!(f, "Branch and bound coin selection: total tries exceeded")
-            }
-            Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for Error {}
-
-#[derive(Debug)]
-/// Remaining amount after performing coin selection
-pub enum Excess {
-    /// It's not possible to create spendable output from excess using the current drain output
-    NoChange {
-        /// Threshold to consider amount as dust for this particular change script_pubkey
-        dust_threshold: u64,
-        /// Exceeding amount of current selection over outgoing value and fee costs
-        remaining_amount: u64,
-        /// The calculated fee for the drain TxOut with the selected script_pubkey
-        change_fee: u64,
-    },
-    /// It's possible to create spendable output from excess using the current drain output
-    Change {
-        /// Effective amount available to create change after deducting the change output fee
-        amount: u64,
-        /// The deducted change output fee
-        fee: u64,
-    },
-}
-
-/// Result of a successful coin selection
-#[derive(Debug)]
-pub struct CoinSelectionResult {
-    /// List of outputs selected for use as inputs
-    pub selected: Vec<Utxo>,
-    /// Total fee amount for the selected utxos in satoshis
-    pub fee_amount: u64,
-    /// Remaining amount after deducing fees and outgoing outputs
-    pub excess: Excess,
-}
-
-impl CoinSelectionResult {
-    /// The total value of the inputs selected.
-    pub fn selected_amount(&self) -> u64 {
-        self.selected.iter().map(|u| u.txout().value.to_sat()).sum()
-    }
-
-    /// The total value of the inputs selected from the local wallet.
-    pub fn local_selected_amount(&self) -> u64 {
-        self.selected
-            .iter()
-            .filter_map(|u| match u {
-                Utxo::Local(_) => Some(u.txout().value.to_sat()),
-                _ => None,
-            })
-            .sum()
-    }
-}
-
-/// Trait for generalized coin selection algorithms
-///
-/// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
-/// selection algorithm when it creates transactions.
-///
-/// For an example see [this module](crate::wallet::coin_selection)'s documentation.
-pub trait CoinSelectionAlgorithm: core::fmt::Debug {
-    /// Perform the coin selection
-    ///
-    /// - `database`: a reference to the wallet's database that can be used to lookup additional
-    ///               details for a specific UTXO
-    /// - `required_utxos`: the utxos that must be spent regardless of `target_amount` with their
-    ///                     weight cost
-    /// - `optional_utxos`: the remaining available utxos to satisfy `target_amount` with their
-    ///                     weight cost
-    /// - `fee_rate`: fee rate to use
-    /// - `target_amount`: the outgoing amount in satoshis and the fees already
-    ///                    accumulated from added outputs and transaction’s header.
-    /// - `drain_script`: the script to use in case of change
-    #[allow(clippy::too_many_arguments)]
-    fn coin_select(
-        &self,
-        required_utxos: Vec<WeightedUtxo>,
-        optional_utxos: Vec<WeightedUtxo>,
-        fee_rate: FeeRate,
-        target_amount: u64,
-        drain_script: &Script,
-    ) -> Result<CoinSelectionResult, Error>;
-}
-
-/// Simple and dumb coin selection
-///
-/// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
-/// from the largest ones until the required amount is reached.
-#[derive(Debug, Default, Clone, Copy)]
-pub struct LargestFirstCoinSelection;
-
-impl CoinSelectionAlgorithm for LargestFirstCoinSelection {
-    fn coin_select(
-        &self,
-        required_utxos: Vec<WeightedUtxo>,
-        mut optional_utxos: Vec<WeightedUtxo>,
-        fee_rate: FeeRate,
-        target_amount: u64,
-        drain_script: &Script,
-    ) -> Result<CoinSelectionResult, Error> {
-        // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
-        // initially smallest to largest, before being reversed with `.rev()`.
-        let utxos = {
-            optional_utxos.sort_unstable_by_key(|wu| wu.utxo.txout().value);
-            required_utxos
-                .into_iter()
-                .map(|utxo| (true, utxo))
-                .chain(optional_utxos.into_iter().rev().map(|utxo| (false, utxo)))
-        };
-
-        select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
-    }
-}
-
-/// OldestFirstCoinSelection always picks the utxo with the smallest blockheight to add to the selected coins next
-///
-/// This coin selection algorithm sorts the available UTXOs by blockheight and then picks them starting
-/// from the oldest ones until the required amount is reached.
-#[derive(Debug, Default, Clone, Copy)]
-pub struct OldestFirstCoinSelection;
-
-impl CoinSelectionAlgorithm for OldestFirstCoinSelection {
-    fn coin_select(
-        &self,
-        required_utxos: Vec<WeightedUtxo>,
-        mut optional_utxos: Vec<WeightedUtxo>,
-        fee_rate: FeeRate,
-        target_amount: u64,
-        drain_script: &Script,
-    ) -> Result<CoinSelectionResult, Error> {
-        // We put the "required UTXOs" first and make sure the optional UTXOs are sorted from
-        // oldest to newest according to blocktime
-        // For utxo that doesn't exist in DB, they will have lowest priority to be selected
-        let utxos = {
-            optional_utxos.sort_unstable_by_key(|wu| match &wu.utxo {
-                Utxo::Local(local) => Some(local.confirmation_time),
-                Utxo::Foreign { .. } => None,
-            });
-
-            required_utxos
-                .into_iter()
-                .map(|utxo| (true, utxo))
-                .chain(optional_utxos.into_iter().map(|utxo| (false, utxo)))
-        };
-
-        select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
-    }
-}
-
-/// Decide if change can be created
-///
-/// - `remaining_amount`: the amount in which the selected coins exceed the target amount
-/// - `fee_rate`: required fee rate for the current selection
-/// - `drain_script`: script to consider change creation
-pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Script) -> Excess {
-    // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value)
-    let drain_output_len = serialize(drain_script).len() + 8usize;
-    let change_fee =
-        (fee_rate * Weight::from_vb(drain_output_len as u64).expect("overflow occurred")).to_sat();
-    let drain_val = remaining_amount.saturating_sub(change_fee);
-
-    if drain_val.is_dust(drain_script) {
-        let dust_threshold = drain_script.dust_value().to_sat();
-        Excess::NoChange {
-            dust_threshold,
-            change_fee,
-            remaining_amount,
-        }
-    } else {
-        Excess::Change {
-            amount: drain_val,
-            fee: change_fee,
-        }
-    }
-}
-
-fn select_sorted_utxos(
-    utxos: impl Iterator<Item = (bool, WeightedUtxo)>,
-    fee_rate: FeeRate,
-    target_amount: u64,
-    drain_script: &Script,
-) -> Result<CoinSelectionResult, Error> {
-    let mut selected_amount = 0;
-    let mut fee_amount = 0;
-    let selected = utxos
-        .scan(
-            (&mut selected_amount, &mut fee_amount),
-            |(selected_amount, fee_amount), (must_use, weighted_utxo)| {
-                if must_use || **selected_amount < target_amount + **fee_amount {
-                    **fee_amount += (fee_rate
-                        * Weight::from_wu(
-                            TxIn::default().segwit_weight().to_wu()
-                                + weighted_utxo.satisfaction_weight as u64,
-                        ))
-                    .to_sat();
-                    **selected_amount += weighted_utxo.utxo.txout().value.to_sat();
-                    Some(weighted_utxo.utxo)
-                } else {
-                    None
-                }
-            },
-        )
-        .collect::<Vec<_>>();
-
-    let amount_needed_with_fees = target_amount + fee_amount;
-    if selected_amount < amount_needed_with_fees {
-        return Err(Error::InsufficientFunds {
-            needed: amount_needed_with_fees,
-            available: selected_amount,
-        });
-    }
-
-    let remaining_amount = selected_amount - amount_needed_with_fees;
-
-    let excess = decide_change(remaining_amount, fee_rate, drain_script);
-
-    Ok(CoinSelectionResult {
-        selected,
-        fee_amount,
-        excess,
-    })
-}
-
-#[derive(Debug, Clone)]
-// Adds fee information to an UTXO.
-struct OutputGroup {
-    weighted_utxo: WeightedUtxo,
-    // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
-    fee: u64,
-    // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
-    effective_value: i64,
-}
-
-impl OutputGroup {
-    fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
-        let fee = (fee_rate
-            * Weight::from_wu(
-                TxIn::default().segwit_weight().to_wu() + weighted_utxo.satisfaction_weight as u64,
-            ))
-        .to_sat();
-        let effective_value = weighted_utxo.utxo.txout().value.to_sat() as i64 - fee as i64;
-        OutputGroup {
-            weighted_utxo,
-            fee,
-            effective_value,
-        }
-    }
-}
-
-/// Branch and bound coin selection
-///
-/// Code adapted from Bitcoin Core's implementation and from Mark Erhardt Master's Thesis: <http://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf>
-#[derive(Debug, Clone)]
-pub struct BranchAndBoundCoinSelection {
-    size_of_change: u64,
-}
-
-impl Default for BranchAndBoundCoinSelection {
-    fn default() -> Self {
-        Self {
-            // P2WPKH cost of change -> value (8 bytes) + script len (1 bytes) + script (22 bytes)
-            size_of_change: 8 + 1 + 22,
-        }
-    }
-}
-
-impl BranchAndBoundCoinSelection {
-    /// Create new instance with target size for change output
-    pub fn new(size_of_change: u64) -> Self {
-        Self { size_of_change }
-    }
-}
-
-const BNB_TOTAL_TRIES: usize = 100_000;
-
-impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection {
-    fn coin_select(
-        &self,
-        required_utxos: Vec<WeightedUtxo>,
-        optional_utxos: Vec<WeightedUtxo>,
-        fee_rate: FeeRate,
-        target_amount: u64,
-        drain_script: &Script,
-    ) -> Result<CoinSelectionResult, Error> {
-        // Mapping every (UTXO, usize) to an output group
-        let required_utxos: Vec<OutputGroup> = required_utxos
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .collect();
-
-        // Mapping every (UTXO, usize) to an output group, filtering UTXOs with a negative
-        // effective value
-        let optional_utxos: Vec<OutputGroup> = optional_utxos
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .filter(|u| u.effective_value.is_positive())
-            .collect();
-
-        let curr_value = required_utxos
-            .iter()
-            .fold(0, |acc, x| acc + x.effective_value);
-
-        let curr_available_value = optional_utxos
-            .iter()
-            .fold(0, |acc, x| acc + x.effective_value);
-
-        let cost_of_change =
-            (Weight::from_vb(self.size_of_change).expect("overflow occurred") * fee_rate).to_sat();
-
-        // `curr_value` and `curr_available_value` are both the sum of *effective_values* of
-        // the UTXOs. For the optional UTXOs (curr_available_value) we filter out UTXOs with
-        // negative effective value, so it will always be positive.
-        //
-        // Since we are required to spend the required UTXOs (curr_value) we have to consider
-        // all their effective values, even when negative, which means that curr_value could
-        // be negative as well.
-        //
-        // If the sum of curr_value and curr_available_value is negative or lower than our target,
-        // we can immediately exit with an error, as it's guaranteed we will never find a solution
-        // if we actually run the BnB.
-        let total_value: Result<u64, _> = (curr_available_value + curr_value).try_into();
-        match total_value {
-            Ok(v) if v >= target_amount => {}
-            _ => {
-                // Assume we spend all the UTXOs we can (all the required + all the optional with
-                // positive effective value), sum their value and their fee cost.
-                let (utxo_fees, utxo_value) = required_utxos
-                    .iter()
-                    .chain(optional_utxos.iter())
-                    .fold((0, 0), |(mut fees, mut value), utxo| {
-                        fees += utxo.fee;
-                        value += utxo.weighted_utxo.utxo.txout().value.to_sat();
-
-                        (fees, value)
-                    });
-
-                // Add to the target the fee cost of the UTXOs
-                return Err(Error::InsufficientFunds {
-                    needed: target_amount + utxo_fees,
-                    available: utxo_value,
-                });
-            }
-        }
-
-        let target_amount = target_amount
-            .try_into()
-            .expect("Bitcoin amount to fit into i64");
-
-        if curr_value > target_amount {
-            // remaining_amount can't be negative as that would mean the
-            // selection wasn't successful
-            // target_amount = amount_needed + (fee_amount - vin_fees)
-            let remaining_amount = (curr_value - target_amount) as u64;
-
-            let excess = decide_change(remaining_amount, fee_rate, drain_script);
-
-            return Ok(BranchAndBoundCoinSelection::calculate_cs_result(
-                vec![],
-                required_utxos,
-                excess,
-            ));
-        }
-
-        Ok(self
-            .bnb(
-                required_utxos.clone(),
-                optional_utxos.clone(),
-                curr_value,
-                curr_available_value,
-                target_amount,
-                cost_of_change,
-                drain_script,
-                fee_rate,
-            )
-            .unwrap_or_else(|_| {
-                self.single_random_draw(
-                    required_utxos,
-                    optional_utxos,
-                    curr_value,
-                    target_amount,
-                    drain_script,
-                    fee_rate,
-                )
-            }))
-    }
-}
-
-impl BranchAndBoundCoinSelection {
-    // TODO: make this more Rust-onic :)
-    // (And perhaps refactor with less arguments?)
-    #[allow(clippy::too_many_arguments)]
-    fn bnb(
-        &self,
-        required_utxos: Vec<OutputGroup>,
-        mut optional_utxos: Vec<OutputGroup>,
-        mut curr_value: i64,
-        mut curr_available_value: i64,
-        target_amount: i64,
-        cost_of_change: u64,
-        drain_script: &Script,
-        fee_rate: FeeRate,
-    ) -> Result<CoinSelectionResult, Error> {
-        // current_selection[i] will contain true if we are using optional_utxos[i],
-        // false otherwise. Note that current_selection.len() could be less than
-        // optional_utxos.len(), it just means that we still haven't decided if we should keep
-        // certain optional_utxos or not.
-        let mut current_selection: Vec<bool> = Vec::with_capacity(optional_utxos.len());
-
-        // Sort the utxo_pool
-        optional_utxos.sort_unstable_by_key(|a| a.effective_value);
-        optional_utxos.reverse();
-
-        // Contains the best selection we found
-        let mut best_selection = Vec::new();
-        let mut best_selection_value = None;
-
-        // Depth First search loop for choosing the UTXOs
-        for _ in 0..BNB_TOTAL_TRIES {
-            // Conditions for starting a backtrack
-            let mut backtrack = false;
-            // Cannot possibly reach target with the amount remaining in the curr_available_value,
-            // or the selected value is out of range.
-            // Go back and try other branch
-            if curr_value + curr_available_value < target_amount
-                || curr_value > target_amount + cost_of_change as i64
-            {
-                backtrack = true;
-            } else if curr_value >= target_amount {
-                // Selected value is within range, there's no point in going forward. Start
-                // backtracking
-                backtrack = true;
-
-                // If we found a solution better than the previous one, or if there wasn't previous
-                // solution, update the best solution
-                if best_selection_value.is_none() || curr_value < best_selection_value.unwrap() {
-                    best_selection.clone_from(&current_selection);
-                    best_selection_value = Some(curr_value);
-                }
-
-                // If we found a perfect match, break here
-                if curr_value == target_amount {
-                    break;
-                }
-            }
-
-            // Backtracking, moving backwards
-            if backtrack {
-                // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
-                while let Some(false) = current_selection.last() {
-                    current_selection.pop();
-                    curr_available_value += optional_utxos[current_selection.len()].effective_value;
-                }
-
-                if current_selection.last_mut().is_none() {
-                    // We have walked back to the first utxo and no branch is untraversed. All solutions searched
-                    // If best selection is empty, then there's no exact match
-                    if best_selection.is_empty() {
-                        return Err(Error::BnBNoExactMatch);
-                    }
-                    break;
-                }
-
-                if let Some(c) = current_selection.last_mut() {
-                    // Output was included on previous iterations, try excluding now.
-                    *c = false;
-                }
-
-                let utxo = &optional_utxos[current_selection.len() - 1];
-                curr_value -= utxo.effective_value;
-            } else {
-                // Moving forwards, continuing down this branch
-                let utxo = &optional_utxos[current_selection.len()];
-
-                // Remove this utxo from the curr_available_value utxo amount
-                curr_available_value -= utxo.effective_value;
-
-                // Inclusion branch first (Largest First Exploration)
-                current_selection.push(true);
-                curr_value += utxo.effective_value;
-            }
-        }
-
-        // Check for solution
-        if best_selection.is_empty() {
-            return Err(Error::BnBTotalTriesExceeded);
-        }
-
-        // Set output set
-        let selected_utxos = optional_utxos
-            .into_iter()
-            .zip(best_selection)
-            .filter_map(|(optional, is_in_best)| if is_in_best { Some(optional) } else { None })
-            .collect::<Vec<OutputGroup>>();
-
-        let selected_amount = best_selection_value.unwrap();
-
-        // remaining_amount can't be negative as that would mean the
-        // selection wasn't successful
-        // target_amount = amount_needed + (fee_amount - vin_fees)
-        let remaining_amount = (selected_amount - target_amount) as u64;
-
-        let excess = decide_change(remaining_amount, fee_rate, drain_script);
-
-        Ok(BranchAndBoundCoinSelection::calculate_cs_result(
-            selected_utxos,
-            required_utxos,
-            excess,
-        ))
-    }
-
-    #[allow(clippy::too_many_arguments)]
-    fn single_random_draw(
-        &self,
-        required_utxos: Vec<OutputGroup>,
-        mut optional_utxos: Vec<OutputGroup>,
-        curr_value: i64,
-        target_amount: i64,
-        drain_script: &Script,
-        fee_rate: FeeRate,
-    ) -> CoinSelectionResult {
-        optional_utxos.shuffle(&mut rand::thread_rng());
-        let selected_utxos = optional_utxos.into_iter().fold(
-            (curr_value, vec![]),
-            |(mut amount, mut utxos), utxo| {
-                if amount >= target_amount {
-                    (amount, utxos)
-                } else {
-                    amount += utxo.effective_value;
-                    utxos.push(utxo);
-                    (amount, utxos)
-                }
-            },
-        );
-
-        // remaining_amount can't be negative as that would mean the
-        // selection wasn't successful
-        // target_amount = amount_needed + (fee_amount - vin_fees)
-        let remaining_amount = (selected_utxos.0 - target_amount) as u64;
-
-        let excess = decide_change(remaining_amount, fee_rate, drain_script);
-
-        BranchAndBoundCoinSelection::calculate_cs_result(selected_utxos.1, required_utxos, excess)
-    }
-
-    fn calculate_cs_result(
-        mut selected_utxos: Vec<OutputGroup>,
-        mut required_utxos: Vec<OutputGroup>,
-        excess: Excess,
-    ) -> CoinSelectionResult {
-        selected_utxos.append(&mut required_utxos);
-        let fee_amount = selected_utxos.iter().map(|u| u.fee).sum::<u64>();
-        let selected = selected_utxos
-            .into_iter()
-            .map(|u| u.weighted_utxo.utxo)
-            .collect::<Vec<_>>();
-
-        CoinSelectionResult {
-            selected,
-            fee_amount,
-            excess,
-        }
-    }
-}
-
-/// Remove duplicate UTXOs.
-///
-/// If a UTXO appears in both `required` and `optional`, the appearance in `required` is kept.
-pub(crate) fn filter_duplicates<I>(required: I, optional: I) -> (I, I)
-where
-    I: IntoIterator<Item = WeightedUtxo> + FromIterator<WeightedUtxo>,
-{
-    let mut visited = HashSet::<OutPoint>::new();
-    let required = required
-        .into_iter()
-        .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
-        .collect::<I>();
-    let optional = optional
-        .into_iter()
-        .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
-        .collect::<I>();
-    (required, optional)
-}
-
-#[cfg(test)]
-mod test {
-    use assert_matches::assert_matches;
-    use core::str::FromStr;
-
-    use bdk_chain::ConfirmationTime;
-    use bitcoin::{Amount, ScriptBuf, TxIn, TxOut};
-
-    use super::*;
-    use crate::types::*;
-    use crate::wallet::coin_selection::filter_duplicates;
-
-    use rand::rngs::StdRng;
-    use rand::seq::SliceRandom;
-    use rand::{Rng, RngCore, SeedableRng};
-
-    // signature len (1WU) + signature and sighash (72WU)
-    // + pubkey len (1WU) + pubkey (33WU)
-    const P2WPKH_SATISFACTION_SIZE: usize = 1 + 72 + 1 + 33;
-
-    const FEE_AMOUNT: u64 = 50;
-
-    fn utxo(value: u64, index: u32, confirmation_time: ConfirmationTime) -> WeightedUtxo {
-        assert!(index < 10);
-        let outpoint = OutPoint::from_str(&format!(
-            "000000000000000000000000000000000000000000000000000000000000000{}:0",
-            index
-        ))
-        .unwrap();
-        WeightedUtxo {
-            satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
-            utxo: Utxo::Local(LocalOutput {
-                outpoint,
-                txout: TxOut {
-                    value: Amount::from_sat(value),
-                    script_pubkey: ScriptBuf::new(),
-                },
-                keychain: KeychainKind::External,
-                is_spent: false,
-                derivation_index: 42,
-                confirmation_time,
-            }),
-        }
-    }
-
-    fn get_test_utxos() -> Vec<WeightedUtxo> {
-        vec![
-            utxo(100_000, 0, ConfirmationTime::Unconfirmed { last_seen: 0 }),
-            utxo(
-                FEE_AMOUNT - 40,
-                1,
-                ConfirmationTime::Unconfirmed { last_seen: 0 },
-            ),
-            utxo(200_000, 2, ConfirmationTime::Unconfirmed { last_seen: 0 }),
-        ]
-    }
-
-    fn get_oldest_first_test_utxos() -> Vec<WeightedUtxo> {
-        // ensure utxos are from different tx
-        let utxo1 = utxo(
-            120_000,
-            1,
-            ConfirmationTime::Confirmed {
-                height: 1,
-                time: 1231006505,
-            },
-        );
-        let utxo2 = utxo(
-            80_000,
-            2,
-            ConfirmationTime::Confirmed {
-                height: 2,
-                time: 1231006505,
-            },
-        );
-        let utxo3 = utxo(
-            300_000,
-            3,
-            ConfirmationTime::Confirmed {
-                height: 3,
-                time: 1231006505,
-            },
-        );
-        vec![utxo1, utxo2, utxo3]
-    }
-
-    fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<WeightedUtxo> {
-        let mut res = Vec::new();
-        for i in 0..utxos_number {
-            res.push(WeightedUtxo {
-                satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
-                utxo: Utxo::Local(LocalOutput {
-                    outpoint: OutPoint::from_str(&format!(
-                        "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:{}",
-                        i
-                    ))
-                    .unwrap(),
-                    txout: TxOut {
-                        value: Amount::from_sat(rng.gen_range(0..200000000)),
-                        script_pubkey: ScriptBuf::new(),
-                    },
-                    keychain: KeychainKind::External,
-                    is_spent: false,
-                    derivation_index: rng.next_u32(),
-                    confirmation_time: if rng.gen_bool(0.5) {
-                        ConfirmationTime::Confirmed {
-                            height: rng.next_u32(),
-                            time: rng.next_u64(),
-                        }
-                    } else {
-                        ConfirmationTime::Unconfirmed { last_seen: 0 }
-                    },
-                }),
-            });
-        }
-        res
-    }
-
-    fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> {
-        (0..utxos_number)
-            .map(|i| WeightedUtxo {
-                satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
-                utxo: Utxo::Local(LocalOutput {
-                    outpoint: OutPoint::from_str(&format!(
-                        "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:{}",
-                        i
-                    ))
-                    .unwrap(),
-                    txout: TxOut {
-                        value: Amount::from_sat(utxos_value),
-                        script_pubkey: ScriptBuf::new(),
-                    },
-                    keychain: KeychainKind::External,
-                    is_spent: false,
-                    derivation_index: 42,
-                    confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
-                }),
-            })
-            .collect()
-    }
-
-    fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 {
-        let utxos_picked_len = rng.gen_range(2..utxos.len() / 2);
-        utxos.shuffle(&mut rng);
-        utxos[..utxos_picked_len]
-            .iter()
-            .map(|u| u.utxo.txout().value.to_sat())
-            .sum()
-    }
-
-    #[test]
-    fn test_largest_first_coin_selection_success() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 250_000 + FEE_AMOUNT;
-
-        let result = LargestFirstCoinSelection
-            .coin_select(
-                utxos,
-                vec![],
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 3);
-        assert_eq!(result.selected_amount(), 300_010);
-        assert_eq!(result.fee_amount, 204)
-    }
-
-    #[test]
-    fn test_largest_first_coin_selection_use_all() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let result = LargestFirstCoinSelection
-            .coin_select(
-                utxos,
-                vec![],
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 3);
-        assert_eq!(result.selected_amount(), 300_010);
-        assert_eq!(result.fee_amount, 204);
-    }
-
-    #[test]
-    fn test_largest_first_coin_selection_use_only_necessary() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let result = LargestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 1);
-        assert_eq!(result.selected_amount(), 200_000);
-        assert_eq!(result.fee_amount, 68);
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_largest_first_coin_selection_insufficient_funds() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 500_000 + FEE_AMOUNT;
-
-        LargestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_largest_first_coin_selection_insufficient_funds_high_fees() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 250_000 + FEE_AMOUNT;
-
-        LargestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1000),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    fn test_oldest_first_coin_selection_success() {
-        let utxos = get_oldest_first_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 180_000 + FEE_AMOUNT;
-
-        let result = OldestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 2);
-        assert_eq!(result.selected_amount(), 200_000);
-        assert_eq!(result.fee_amount, 136)
-    }
-
-    #[test]
-    fn test_oldest_first_coin_selection_use_all() {
-        let utxos = get_oldest_first_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let result = OldestFirstCoinSelection
-            .coin_select(
-                utxos,
-                vec![],
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 3);
-        assert_eq!(result.selected_amount(), 500_000);
-        assert_eq!(result.fee_amount, 204);
-    }
-
-    #[test]
-    fn test_oldest_first_coin_selection_use_only_necessary() {
-        let utxos = get_oldest_first_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let result = OldestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 1);
-        assert_eq!(result.selected_amount(), 120_000);
-        assert_eq!(result.fee_amount, 68);
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_oldest_first_coin_selection_insufficient_funds() {
-        let utxos = get_oldest_first_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 600_000 + FEE_AMOUNT;
-
-        OldestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_oldest_first_coin_selection_insufficient_funds_high_fees() {
-        let utxos = get_oldest_first_test_utxos();
-
-        let target_amount: u64 = utxos
-            .iter()
-            .map(|wu| wu.utxo.txout().value.to_sat())
-            .sum::<u64>()
-            - 50;
-        let drain_script = ScriptBuf::default();
-
-        OldestFirstCoinSelection
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1000),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    fn test_bnb_coin_selection_success() {
-        // In this case bnb won't find a suitable match and single random draw will
-        // select three outputs
-        let utxos = generate_same_value_utxos(100_000, 20);
-
-        let drain_script = ScriptBuf::default();
-
-        let target_amount = 250_000 + FEE_AMOUNT;
-
-        let result = BranchAndBoundCoinSelection::default()
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 3);
-        assert_eq!(result.selected_amount(), 300_000);
-        assert_eq!(result.fee_amount, 204);
-    }
-
-    #[test]
-    fn test_bnb_coin_selection_required_are_enough() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let result = BranchAndBoundCoinSelection::default()
-            .coin_select(
-                utxos.clone(),
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 3);
-        assert_eq!(result.selected_amount(), 300_010);
-        assert_eq!(result.fee_amount, 204);
-    }
-
-    #[test]
-    fn test_bnb_coin_selection_optional_are_enough() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 299756 + FEE_AMOUNT;
-
-        let result = BranchAndBoundCoinSelection::default()
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 2);
-        assert_eq!(result.selected_amount(), 300000);
-        assert_eq!(result.fee_amount, 136);
-    }
-
-    #[test]
-    #[ignore]
-    fn test_bnb_coin_selection_required_not_enough() {
-        let utxos = get_test_utxos();
-
-        let required = vec![utxos[0].clone()];
-        let mut optional = utxos[1..].to_vec();
-        optional.push(utxo(
-            500_000,
-            3,
-            ConfirmationTime::Unconfirmed { last_seen: 0 },
-        ));
-
-        // Defensive assertions, for sanity and in case someone changes the test utxos vector.
-        let amount: u64 = required.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
-        assert_eq!(amount, 100_000);
-        let amount: u64 = optional.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
-        assert!(amount > 150_000);
-        let drain_script = ScriptBuf::default();
-
-        let target_amount = 150_000 + FEE_AMOUNT;
-
-        let result = BranchAndBoundCoinSelection::default()
-            .coin_select(
-                required,
-                optional,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 2);
-        assert_eq!(result.selected_amount(), 300_000);
-        assert_eq!(result.fee_amount, 136);
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_bnb_coin_selection_insufficient_funds() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 500_000 + FEE_AMOUNT;
-
-        BranchAndBoundCoinSelection::default()
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "InsufficientFunds")]
-    fn test_bnb_coin_selection_insufficient_funds_high_fees() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 250_000 + FEE_AMOUNT;
-
-        BranchAndBoundCoinSelection::default()
-            .coin_select(
-                vec![],
-                utxos,
-                FeeRate::from_sat_per_vb_unchecked(1000),
-                target_amount,
-                &drain_script,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    fn test_bnb_coin_selection_check_fee_rate() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-        let target_amount = 99932; // first utxo's effective value
-        let feerate = FeeRate::BROADCAST_MIN;
-
-        let result = BranchAndBoundCoinSelection::new(0)
-            .coin_select(vec![], utxos, feerate, target_amount, &drain_script)
-            .unwrap();
-
-        assert_eq!(result.selected.len(), 1);
-        assert_eq!(result.selected_amount(), 100_000);
-        let input_weight =
-            TxIn::default().segwit_weight().to_wu() + P2WPKH_SATISFACTION_SIZE as u64;
-        // the final fee rate should be exactly the same as the fee rate given
-        let result_feerate = Amount::from_sat(result.fee_amount) / Weight::from_wu(input_weight);
-        assert_eq!(result_feerate, feerate);
-    }
-
-    #[test]
-    fn test_bnb_coin_selection_exact_match() {
-        let seed = [0; 32];
-        let mut rng: StdRng = SeedableRng::from_seed(seed);
-
-        for _i in 0..200 {
-            let mut optional_utxos = generate_random_utxos(&mut rng, 16);
-            let target_amount = sum_random_utxos(&mut rng, &mut optional_utxos);
-            let drain_script = ScriptBuf::default();
-            let result = BranchAndBoundCoinSelection::new(0)
-                .coin_select(
-                    vec![],
-                    optional_utxos,
-                    FeeRate::ZERO,
-                    target_amount,
-                    &drain_script,
-                )
-                .unwrap();
-            assert_eq!(result.selected_amount(), target_amount);
-        }
-    }
-
-    #[test]
-    #[should_panic(expected = "BnBNoExactMatch")]
-    fn test_bnb_function_no_exact_match() {
-        let fee_rate = FeeRate::from_sat_per_vb_unchecked(10);
-        let utxos: Vec<OutputGroup> = get_test_utxos()
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .collect();
-
-        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
-
-        let size_of_change = 31;
-        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
-
-        let drain_script = ScriptBuf::default();
-        let target_amount = 20_000 + FEE_AMOUNT;
-        BranchAndBoundCoinSelection::new(size_of_change)
-            .bnb(
-                vec![],
-                utxos,
-                0,
-                curr_available_value,
-                target_amount as i64,
-                cost_of_change,
-                &drain_script,
-                fee_rate,
-            )
-            .unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "BnBTotalTriesExceeded")]
-    fn test_bnb_function_tries_exceeded() {
-        let fee_rate = FeeRate::from_sat_per_vb_unchecked(10);
-        let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000)
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .collect();
-
-        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
-
-        let size_of_change = 31;
-        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
-        let target_amount = 20_000 + FEE_AMOUNT;
-
-        let drain_script = ScriptBuf::default();
-
-        BranchAndBoundCoinSelection::new(size_of_change)
-            .bnb(
-                vec![],
-                utxos,
-                0,
-                curr_available_value,
-                target_amount as i64,
-                cost_of_change,
-                &drain_script,
-                fee_rate,
-            )
-            .unwrap();
-    }
-
-    // The match won't be exact but still in the range
-    #[test]
-    fn test_bnb_function_almost_exact_match_with_fees() {
-        let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
-        let size_of_change = 31;
-        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
-
-        let utxos: Vec<_> = generate_same_value_utxos(50_000, 10)
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .collect();
-
-        let curr_value = 0;
-
-        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
-
-        // 2*(value of 1 utxo)  - 2*(1 utxo fees with 1.0sat/vbyte fee rate) -
-        // cost_of_change + 5.
-        let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change as i64 + 5;
-
-        let drain_script = ScriptBuf::default();
-
-        let result = BranchAndBoundCoinSelection::new(size_of_change)
-            .bnb(
-                vec![],
-                utxos,
-                curr_value,
-                curr_available_value,
-                target_amount,
-                cost_of_change,
-                &drain_script,
-                fee_rate,
-            )
-            .unwrap();
-        assert_eq!(result.selected_amount(), 100_000);
-        assert_eq!(result.fee_amount, 136);
-    }
-
-    // TODO: bnb() function should be optimized, and this test should be done with more utxos
-    #[test]
-    fn test_bnb_function_exact_match_more_utxos() {
-        let seed = [0; 32];
-        let mut rng: StdRng = SeedableRng::from_seed(seed);
-        let fee_rate = FeeRate::ZERO;
-
-        for _ in 0..200 {
-            let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40)
-                .into_iter()
-                .map(|u| OutputGroup::new(u, fee_rate))
-                .collect();
-
-            let curr_value = 0;
-
-            let curr_available_value = optional_utxos
-                .iter()
-                .fold(0, |acc, x| acc + x.effective_value);
-
-            let target_amount =
-                optional_utxos[3].effective_value + optional_utxos[23].effective_value;
-
-            let drain_script = ScriptBuf::default();
-
-            let result = BranchAndBoundCoinSelection::new(0)
-                .bnb(
-                    vec![],
-                    optional_utxos,
-                    curr_value,
-                    curr_available_value,
-                    target_amount,
-                    0,
-                    &drain_script,
-                    fee_rate,
-                )
-                .unwrap();
-            assert_eq!(result.selected_amount(), target_amount as u64);
-        }
-    }
-
-    #[test]
-    fn test_single_random_draw_function_success() {
-        let seed = [0; 32];
-        let mut rng: StdRng = SeedableRng::from_seed(seed);
-        let mut utxos = generate_random_utxos(&mut rng, 300);
-        let target_amount = sum_random_utxos(&mut rng, &mut utxos) + FEE_AMOUNT;
-
-        let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
-        let utxos: Vec<OutputGroup> = utxos
-            .into_iter()
-            .map(|u| OutputGroup::new(u, fee_rate))
-            .collect();
-
-        let drain_script = ScriptBuf::default();
-
-        let result = BranchAndBoundCoinSelection::default().single_random_draw(
-            vec![],
-            utxos,
-            0,
-            target_amount as i64,
-            &drain_script,
-            fee_rate,
-        );
-
-        assert!(result.selected_amount() > target_amount);
-        assert_eq!(result.fee_amount, (result.selected.len() * 68) as u64);
-    }
-
-    #[test]
-    fn test_bnb_exclude_negative_effective_value() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-
-        let selection = BranchAndBoundCoinSelection::default().coin_select(
-            vec![],
-            utxos,
-            FeeRate::from_sat_per_vb_unchecked(10),
-            500_000,
-            &drain_script,
-        );
-
-        assert_matches!(
-            selection,
-            Err(Error::InsufficientFunds {
-                available: 300_000,
-                ..
-            })
-        );
-    }
-
-    #[test]
-    fn test_bnb_include_negative_effective_value_when_required() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-
-        let (required, optional) = utxos.into_iter().partition(
-            |u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value.to_sat() < 1000),
-        );
-
-        let selection = BranchAndBoundCoinSelection::default().coin_select(
-            required,
-            optional,
-            FeeRate::from_sat_per_vb_unchecked(10),
-            500_000,
-            &drain_script,
-        );
-
-        assert_matches!(
-            selection,
-            Err(Error::InsufficientFunds {
-                available: 300_010,
-                ..
-            })
-        );
-    }
-
-    #[test]
-    fn test_bnb_sum_of_effective_value_negative() {
-        let utxos = get_test_utxos();
-        let drain_script = ScriptBuf::default();
-
-        let selection = BranchAndBoundCoinSelection::default().coin_select(
-            utxos,
-            vec![],
-            FeeRate::from_sat_per_vb_unchecked(10_000),
-            500_000,
-            &drain_script,
-        );
-
-        assert_matches!(
-            selection,
-            Err(Error::InsufficientFunds {
-                available: 300_010,
-                ..
-            })
-        );
-    }
-
-    #[test]
-    fn test_filter_duplicates() {
-        fn utxo(txid: &str, value: u64) -> WeightedUtxo {
-            WeightedUtxo {
-                satisfaction_weight: 0,
-                utxo: Utxo::Local(LocalOutput {
-                    outpoint: OutPoint::new(bitcoin::hashes::Hash::hash(txid.as_bytes()), 0),
-                    txout: TxOut {
-                        value: Amount::from_sat(value),
-                        script_pubkey: ScriptBuf::new(),
-                    },
-                    keychain: KeychainKind::External,
-                    is_spent: false,
-                    derivation_index: 0,
-                    confirmation_time: ConfirmationTime::Confirmed {
-                        height: 12345,
-                        time: 12345,
-                    },
-                }),
-            }
-        }
-
-        fn to_utxo_vec(utxos: &[(&str, u64)]) -> Vec<WeightedUtxo> {
-            let mut v = utxos
-                .iter()
-                .map(|&(txid, value)| utxo(txid, value))
-                .collect::<Vec<_>>();
-            v.sort_by_key(|u| u.utxo.outpoint());
-            v
-        }
-
-        struct TestCase<'a> {
-            name: &'a str,
-            required: &'a [(&'a str, u64)],
-            optional: &'a [(&'a str, u64)],
-            exp_required: &'a [(&'a str, u64)],
-            exp_optional: &'a [(&'a str, u64)],
-        }
-
-        let test_cases = [
-            TestCase {
-                name: "no_duplicates",
-                required: &[("A", 1000), ("B", 2100)],
-                optional: &[("C", 1000)],
-                exp_required: &[("A", 1000), ("B", 2100)],
-                exp_optional: &[("C", 1000)],
-            },
-            TestCase {
-                name: "duplicate_required_utxos",
-                required: &[("A", 3000), ("B", 1200), ("C", 1234), ("A", 3000)],
-                optional: &[("D", 2100)],
-                exp_required: &[("A", 3000), ("B", 1200), ("C", 1234)],
-                exp_optional: &[("D", 2100)],
-            },
-            TestCase {
-                name: "duplicate_optional_utxos",
-                required: &[("A", 3000), ("B", 1200)],
-                optional: &[("C", 5000), ("D", 1300), ("C", 5000)],
-                exp_required: &[("A", 3000), ("B", 1200)],
-                exp_optional: &[("C", 5000), ("D", 1300)],
-            },
-            TestCase {
-                name: "duplicate_across_required_and_optional_utxos",
-                required: &[("A", 3000), ("B", 1200), ("C", 2100)],
-                optional: &[("A", 3000), ("D", 1200), ("E", 5000)],
-                exp_required: &[("A", 3000), ("B", 1200), ("C", 2100)],
-                exp_optional: &[("D", 1200), ("E", 5000)],
-            },
-        ];
-
-        for (i, t) in test_cases.into_iter().enumerate() {
-            println!("Case {}: {}", i, t.name);
-            let (required, optional) =
-                filter_duplicates(to_utxo_vec(t.required), to_utxo_vec(t.optional));
-            assert_eq!(
-                required,
-                to_utxo_vec(t.exp_required),
-                "[{}:{}] unexpected `required` result",
-                i,
-                t.name
-            );
-            assert_eq!(
-                optional,
-                to_utxo_vec(t.exp_optional),
-                "[{}:{}] unexpected `optional` result",
-                i,
-                t.name
-            );
-        }
-    }
-}
diff --git a/crates/bdk/src/wallet/error.rs b/crates/bdk/src/wallet/error.rs
deleted file mode 100644 (file)
index eaf811d..0000000
+++ /dev/null
@@ -1,291 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)
-
-use crate::descriptor::policy::PolicyError;
-use crate::descriptor::DescriptorError;
-use crate::wallet::coin_selection;
-use crate::{descriptor, KeychainKind};
-use alloc::string::String;
-use bitcoin::{absolute, psbt, OutPoint, Sequence, Txid};
-use core::fmt;
-
-/// Errors returned by miniscript when updating inconsistent PSBTs
-#[derive(Debug, Clone)]
-pub enum MiniscriptPsbtError {
-    /// Descriptor key conversion error
-    Conversion(miniscript::descriptor::ConversionError),
-    /// Return error type for PsbtExt::update_input_with_descriptor
-    UtxoUpdate(miniscript::psbt::UtxoUpdateError),
-    /// Return error type for PsbtExt::update_output_with_descriptor
-    OutputUpdate(miniscript::psbt::OutputUpdateError),
-}
-
-impl fmt::Display for MiniscriptPsbtError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Conversion(err) => write!(f, "Conversion error: {}", err),
-            Self::UtxoUpdate(err) => write!(f, "UTXO update error: {}", err),
-            Self::OutputUpdate(err) => write!(f, "Output update error: {}", err),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for MiniscriptPsbtError {}
-
-#[derive(Debug)]
-/// Error returned from [`TxBuilder::finish`]
-///
-/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish
-pub enum CreateTxError {
-    /// There was a problem with the descriptors passed in
-    Descriptor(DescriptorError),
-    /// We were unable to load wallet data from or write wallet data to the persistence backend
-    Persist(anyhow::Error),
-    /// There was a problem while extracting and manipulating policies
-    Policy(PolicyError),
-    /// Spending policy is not compatible with this [`KeychainKind`]
-    SpendingPolicyRequired(KeychainKind),
-    /// Requested invalid transaction version '0'
-    Version0,
-    /// Requested transaction version `1`, but at least `2` is needed to use OP_CSV
-    Version1Csv,
-    /// Requested `LockTime` is less than is required to spend from this script
-    LockTime {
-        /// Requested `LockTime`
-        requested: absolute::LockTime,
-        /// Required `LockTime`
-        required: absolute::LockTime,
-    },
-    /// Cannot enable RBF with a `Sequence` >= 0xFFFFFFFE
-    RbfSequence,
-    /// Cannot enable RBF with `Sequence` given a required OP_CSV
-    RbfSequenceCsv {
-        /// Given RBF `Sequence`
-        rbf: Sequence,
-        /// Required OP_CSV `Sequence`
-        csv: Sequence,
-    },
-    /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee
-    FeeTooLow {
-        /// Required fee absolute value (satoshi)
-        required: u64,
-    },
-    /// When bumping a tx the fee rate requested is lower than required
-    FeeRateTooLow {
-        /// Required fee rate
-        required: bitcoin::FeeRate,
-    },
-    /// `manually_selected_only` option is selected but no utxo has been passed
-    NoUtxosSelected,
-    /// Output created is under the dust limit, 546 satoshis
-    OutputBelowDustLimit(usize),
-    /// The `change_policy` was set but the wallet does not have a change_descriptor
-    ChangePolicyDescriptor,
-    /// There was an error with coin selection
-    CoinSelection(coin_selection::Error),
-    /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
-    InsufficientFunds {
-        /// Sats needed for some transaction
-        needed: u64,
-        /// Sats available for spending
-        available: u64,
-    },
-    /// Cannot build a tx without recipients
-    NoRecipients,
-    /// Partially signed bitcoin transaction error
-    Psbt(psbt::Error),
-    /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended
-    /// key in the descriptor must either be a master key itself (having depth = 0) or have an
-    /// explicit origin provided
-    ///
-    /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs
-    MissingKeyOrigin(String),
-    /// Happens when trying to spend an UTXO that is not in the internal database
-    UnknownUtxo,
-    /// Missing non_witness_utxo on foreign utxo for given `OutPoint`
-    MissingNonWitnessUtxo(OutPoint),
-    /// Miniscript PSBT error
-    MiniscriptPsbt(MiniscriptPsbtError),
-}
-
-impl fmt::Display for CreateTxError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::Descriptor(e) => e.fmt(f),
-            Self::Persist(e) => {
-                write!(
-                    f,
-                    "failed to load wallet data from or write wallet data to persistence backend: {}",
-                    e
-                )
-            }
-            Self::Policy(e) => e.fmt(f),
-            CreateTxError::SpendingPolicyRequired(keychain_kind) => {
-                write!(f, "Spending policy required: {:?}", keychain_kind)
-            }
-            CreateTxError::Version0 => {
-                write!(f, "Invalid version `0`")
-            }
-            CreateTxError::Version1Csv => {
-                write!(
-                    f,
-                    "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
-                )
-            }
-            CreateTxError::LockTime {
-                requested,
-                required,
-            } => {
-                write!(f, "TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", required, requested)
-            }
-            CreateTxError::RbfSequence => {
-                write!(f, "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")
-            }
-            CreateTxError::RbfSequenceCsv { rbf, csv } => {
-                write!(
-                    f,
-                    "Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
-                    rbf, csv
-                )
-            }
-            CreateTxError::FeeTooLow { required } => {
-                write!(f, "Fee to low: required {} sat", required)
-            }
-            CreateTxError::FeeRateTooLow { required } => {
-                write!(
-                    f,
-                    // Note: alternate fmt as sat/vb (ceil) available in bitcoin-0.31
-                    //"Fee rate too low: required {required:#}"
-                    "Fee rate too low: required {} sat/vb",
-                    crate::floating_rate!(required)
-                )
-            }
-            CreateTxError::NoUtxosSelected => {
-                write!(f, "No UTXO selected")
-            }
-            CreateTxError::OutputBelowDustLimit(limit) => {
-                write!(f, "Output below the dust limit: {}", limit)
-            }
-            CreateTxError::ChangePolicyDescriptor => {
-                write!(
-                    f,
-                    "The `change_policy` can be set only if the wallet has a change_descriptor"
-                )
-            }
-            CreateTxError::CoinSelection(e) => e.fmt(f),
-            CreateTxError::InsufficientFunds { needed, available } => {
-                write!(
-                    f,
-                    "Insufficient funds: {} sat available of {} sat needed",
-                    available, needed
-                )
-            }
-            CreateTxError::NoRecipients => {
-                write!(f, "Cannot build tx without recipients")
-            }
-            CreateTxError::Psbt(e) => e.fmt(f),
-            CreateTxError::MissingKeyOrigin(err) => {
-                write!(f, "Missing key origin: {}", err)
-            }
-            CreateTxError::UnknownUtxo => {
-                write!(f, "UTXO not found in the internal database")
-            }
-            CreateTxError::MissingNonWitnessUtxo(outpoint) => {
-                write!(f, "Missing non_witness_utxo on foreign utxo {}", outpoint)
-            }
-            CreateTxError::MiniscriptPsbt(err) => {
-                write!(f, "Miniscript PSBT error: {}", err)
-            }
-        }
-    }
-}
-
-impl From<descriptor::error::Error> for CreateTxError {
-    fn from(err: descriptor::error::Error) -> Self {
-        CreateTxError::Descriptor(err)
-    }
-}
-
-impl From<PolicyError> for CreateTxError {
-    fn from(err: PolicyError) -> Self {
-        CreateTxError::Policy(err)
-    }
-}
-
-impl From<MiniscriptPsbtError> for CreateTxError {
-    fn from(err: MiniscriptPsbtError) -> Self {
-        CreateTxError::MiniscriptPsbt(err)
-    }
-}
-
-impl From<psbt::Error> for CreateTxError {
-    fn from(err: psbt::Error) -> Self {
-        CreateTxError::Psbt(err)
-    }
-}
-
-impl From<coin_selection::Error> for CreateTxError {
-    fn from(err: coin_selection::Error) -> Self {
-        CreateTxError::CoinSelection(err)
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for CreateTxError {}
-
-#[derive(Debug)]
-/// Error returned from [`Wallet::build_fee_bump`]
-///
-/// [`Wallet::build_fee_bump`]: super::Wallet::build_fee_bump
-pub enum BuildFeeBumpError {
-    /// Happens when trying to spend an UTXO that is not in the internal database
-    UnknownUtxo(OutPoint),
-    /// Thrown when a tx is not found in the internal database
-    TransactionNotFound(Txid),
-    /// Happens when trying to bump a transaction that is already confirmed
-    TransactionConfirmed(Txid),
-    /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
-    IrreplaceableTransaction(Txid),
-    /// Node doesn't have data to estimate a fee rate
-    FeeRateUnavailable,
-}
-
-impl fmt::Display for BuildFeeBumpError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::UnknownUtxo(outpoint) => write!(
-                f,
-                "UTXO not found in the internal database with txid: {}, vout: {}",
-                outpoint.txid, outpoint.vout
-            ),
-            Self::TransactionNotFound(txid) => {
-                write!(
-                    f,
-                    "Transaction not found in the internal database with txid: {}",
-                    txid
-                )
-            }
-            Self::TransactionConfirmed(txid) => {
-                write!(f, "Transaction already confirmed with txid: {}", txid)
-            }
-            Self::IrreplaceableTransaction(txid) => {
-                write!(f, "Transaction can't be replaced with txid: {}", txid)
-            }
-            Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for BuildFeeBumpError {}
diff --git a/crates/bdk/src/wallet/export.rs b/crates/bdk/src/wallet/export.rs
deleted file mode 100644 (file)
index b634930..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Wallet export
-//!
-//! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md).
-//!
-//! ## Examples
-//!
-//! ### Import from JSON
-//!
-//! ```
-//! # use std::str::FromStr;
-//! # use bitcoin::*;
-//! # use bdk::wallet::export::*;
-//! # use bdk::*;
-//! let import = r#"{
-//!     "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
-//!     "blockheight":1782088,
-//!     "label":"testnet"
-//! }"#;
-//!
-//! let import = FullyNodedExport::from_str(import)?;
-//! let wallet = Wallet::new_no_persist(
-//!     &import.descriptor(),
-//!     import.change_descriptor().as_ref(),
-//!     Network::Testnet,
-//! )?;
-//! # Ok::<_, Box<dyn std::error::Error>>(())
-//! ```
-//!
-//! ### Export a `Wallet`
-//! ```
-//! # use bitcoin::*;
-//! # use bdk::wallet::export::*;
-//! # use bdk::*;
-//! let wallet = Wallet::new_no_persist(
-//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
-//!     Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
-//!     Network::Testnet,
-//! )?;
-//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
-//!
-//! println!("Exported: {}", export.to_string());
-//! # Ok::<_, Box<dyn std::error::Error>>(())
-//! ```
-
-use alloc::string::String;
-use core::fmt;
-use core::str::FromStr;
-use serde::{Deserialize, Serialize};
-
-use miniscript::descriptor::{ShInner, WshInner};
-use miniscript::{Descriptor, ScriptContext, Terminal};
-
-use crate::types::KeychainKind;
-use crate::wallet::Wallet;
-
-/// Alias for [`FullyNodedExport`]
-#[deprecated(since = "0.18.0", note = "Please use [`FullyNodedExport`] instead")]
-pub type WalletExport = FullyNodedExport;
-
-/// Structure that contains the export of a wallet
-///
-/// For a usage example see [this module](crate::wallet::export)'s documentation.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct FullyNodedExport {
-    descriptor: String,
-    /// Earliest block to rescan when looking for the wallet's transactions
-    pub blockheight: u32,
-    /// Arbitrary label for the wallet
-    pub label: String,
-}
-
-impl fmt::Display for FullyNodedExport {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}", serde_json::to_string(self).unwrap())
-    }
-}
-
-impl FromStr for FullyNodedExport {
-    type Err = serde_json::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        serde_json::from_str(s)
-    }
-}
-
-fn remove_checksum(s: String) -> String {
-    s.split_once('#').map(|(a, _)| String::from(a)).unwrap()
-}
-
-impl FullyNodedExport {
-    /// Export a wallet
-    ///
-    /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
-    /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44
-    /// and others.
-    ///
-    /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database
-    /// for the oldest transaction it knows and use that as the earliest block to rescan.
-    ///
-    /// If the database is empty or `include_blockheight` is false, the `blockheight` field
-    /// returned will be `0`.
-    pub fn export_wallet(
-        wallet: &Wallet,
-        label: &str,
-        include_blockheight: bool,
-    ) -> Result<Self, &'static str> {
-        let descriptor = wallet
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .to_string_with_secret(
-                &wallet
-                    .get_signers(KeychainKind::External)
-                    .as_key_map(wallet.secp_ctx()),
-            );
-        let descriptor = remove_checksum(descriptor);
-        Self::is_compatible_with_core(&descriptor)?;
-
-        let blockheight = if include_blockheight {
-            wallet.transactions().next().map_or(0, |canonical_tx| {
-                match canonical_tx.chain_position {
-                    bdk_chain::ChainPosition::Confirmed(a) => a.confirmation_height,
-                    bdk_chain::ChainPosition::Unconfirmed(_) => 0,
-                }
-            })
-        } else {
-            0
-        };
-
-        let export = FullyNodedExport {
-            descriptor,
-            label: label.into(),
-            blockheight,
-        };
-
-        let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() {
-            false => None,
-            true => {
-                let descriptor = wallet
-                    .get_descriptor_for_keychain(KeychainKind::Internal)
-                    .to_string_with_secret(
-                        &wallet
-                            .get_signers(KeychainKind::Internal)
-                            .as_key_map(wallet.secp_ctx()),
-                    );
-                Some(remove_checksum(descriptor))
-            }
-        };
-        if export.change_descriptor() != change_descriptor {
-            return Err("Incompatible change descriptor");
-        }
-
-        Ok(export)
-    }
-
-    fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> {
-        fn check_ms<Ctx: ScriptContext>(
-            terminal: &Terminal<String, Ctx>,
-        ) -> Result<(), &'static str> {
-            if let Terminal::Multi(_, _) = terminal {
-                Ok(())
-            } else {
-                Err("The descriptor contains operators not supported by Bitcoin Core")
-            }
-        }
-
-        // pkh(), wpkh(), sh(wpkh()) are always fine, as well as multi() and sortedmulti()
-        match Descriptor::<String>::from_str(descriptor).map_err(|_| "Invalid descriptor")? {
-            Descriptor::Pkh(_) | Descriptor::Wpkh(_) => Ok(()),
-            Descriptor::Sh(sh) => match sh.as_inner() {
-                ShInner::Wpkh(_) => Ok(()),
-                ShInner::SortedMulti(_) => Ok(()),
-                ShInner::Wsh(wsh) => match wsh.as_inner() {
-                    WshInner::SortedMulti(_) => Ok(()),
-                    WshInner::Ms(ms) => check_ms(&ms.node),
-                },
-                ShInner::Ms(ms) => check_ms(&ms.node),
-            },
-            Descriptor::Wsh(wsh) => match wsh.as_inner() {
-                WshInner::SortedMulti(_) => Ok(()),
-                WshInner::Ms(ms) => check_ms(&ms.node),
-            },
-            _ => Err("The descriptor is not compatible with Bitcoin Core"),
-        }
-    }
-
-    /// Return the external descriptor
-    pub fn descriptor(&self) -> String {
-        self.descriptor.clone()
-    }
-
-    /// Return the internal descriptor, if present
-    pub fn change_descriptor(&self) -> Option<String> {
-        let replaced = self.descriptor.replace("/0/*", "/1/*");
-
-        if replaced != self.descriptor {
-            Some(replaced)
-        } else {
-            None
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use core::str::FromStr;
-
-    use crate::std::string::ToString;
-    use bdk_chain::{BlockId, ConfirmationTime};
-    use bitcoin::hashes::Hash;
-    use bitcoin::{transaction, BlockHash, Network, Transaction};
-
-    use super::*;
-    use crate::wallet::Wallet;
-
-    fn get_test_wallet(
-        descriptor: &str,
-        change_descriptor: Option<&str>,
-        network: Network,
-    ) -> Wallet {
-        let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
-        let transaction = Transaction {
-            input: vec![],
-            output: vec![],
-            version: transaction::Version::non_standard(0),
-            lock_time: bitcoin::absolute::LockTime::ZERO,
-        };
-        wallet
-            .insert_checkpoint(BlockId {
-                height: 5001,
-                hash: BlockHash::all_zeros(),
-            })
-            .unwrap();
-        wallet
-            .insert_tx(
-                transaction,
-                ConfirmationTime::Confirmed {
-                    height: 5000,
-                    time: 0,
-                },
-            )
-            .unwrap();
-        wallet
-    }
-
-    #[test]
-    fn test_export_bip44() {
-        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
-        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
-
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
-        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
-
-        assert_eq!(export.descriptor(), descriptor);
-        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
-        assert_eq!(export.blockheight, 5000);
-        assert_eq!(export.label, "Test Label");
-    }
-
-    #[test]
-    #[should_panic(expected = "Incompatible change descriptor")]
-    fn test_export_no_change() {
-        // This wallet explicitly doesn't have a change descriptor. It should be impossible to
-        // export, because exporting this kind of external descriptor normally implies the
-        // existence of an internal descriptor
-
-        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
-
-        let wallet = get_test_wallet(descriptor, None, Network::Bitcoin);
-        FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "Incompatible change descriptor")]
-    fn test_export_incompatible_change() {
-        // This wallet has a change descriptor, but the derivation path is not in the "standard"
-        // bip44/49/etc format
-
-        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
-        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
-
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
-        FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
-    }
-
-    #[test]
-    fn test_export_multi() {
-        let descriptor = "wsh(multi(2,\
-                                [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
-                                [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
-                                [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
-                          ))";
-        let change_descriptor = "wsh(multi(2,\
-                                       [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
-                                       [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
-                                       [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
-                                 ))";
-
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
-        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
-
-        assert_eq!(export.descriptor(), descriptor);
-        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
-        assert_eq!(export.blockheight, 5000);
-        assert_eq!(export.label, "Test Label");
-    }
-
-    #[test]
-    fn test_export_to_json() {
-        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
-        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
-
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
-        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
-
-        assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
-    }
-
-    #[test]
-    fn test_export_from_json() {
-        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
-        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
-
-        let import_str = "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}";
-        let export = FullyNodedExport::from_str(import_str).unwrap();
-
-        assert_eq!(export.descriptor(), descriptor);
-        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
-        assert_eq!(export.blockheight, 5000);
-        assert_eq!(export.label, "Test Label");
-    }
-}
diff --git a/crates/bdk/src/wallet/hardwaresigner.rs b/crates/bdk/src/wallet/hardwaresigner.rs
deleted file mode 100644 (file)
index 5a210f6..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! HWI Signer
-//!
-//! This module contains HWISigner, an implementation of a [TransactionSigner] to be
-//! used with hardware wallets.
-//! ```no_run
-//! # use bdk::bitcoin::Network;
-//! # use bdk::signer::SignerOrdering;
-//! # use bdk::wallet::hardwaresigner::HWISigner;
-//! # use bdk::wallet::AddressIndex::New;
-//! # use bdk::{KeychainKind, SignOptions, Wallet};
-//! # use hwi::HWIClient;
-//! # use std::sync::Arc;
-//! #
-//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
-//! let mut devices = HWIClient::enumerate()?;
-//! if devices.is_empty() {
-//!     panic!("No devices found!");
-//! }
-//! let first_device = devices.remove(0)?;
-//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
-//!
-//! # let mut wallet = Wallet::new_no_persist(
-//! #     "",
-//! #     None,
-//! #     Network::Testnet,
-//! # )?;
-//! #
-//! // Adding the hardware signer to the BDK wallet
-//! wallet.add_signer(
-//!     KeychainKind::External,
-//!     SignerOrdering(200),
-//!     Arc::new(custom_signer),
-//! );
-//!
-//! # Ok(())
-//! # }
-//! ```
-
-use bitcoin::bip32::Fingerprint;
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::Psbt;
-
-use hwi::error::Error;
-use hwi::types::{HWIChain, HWIDevice};
-use hwi::HWIClient;
-
-use crate::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
-
-#[derive(Debug)]
-/// Custom signer for Hardware Wallets
-///
-/// This ignores `sign_options` and leaves the decisions up to the hardware wallet.
-pub struct HWISigner {
-    fingerprint: Fingerprint,
-    client: HWIClient,
-}
-
-impl HWISigner {
-    /// Create a instance from the specified device and chain
-    pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
-        let client = HWIClient::get_client(device, false, chain)?;
-        Ok(HWISigner {
-            fingerprint: device.fingerprint,
-            client,
-        })
-    }
-}
-
-impl SignerCommon for HWISigner {
-    fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
-        SignerId::Fingerprint(self.fingerprint)
-    }
-}
-
-/// This implementation ignores `sign_options`
-impl TransactionSigner for HWISigner {
-    fn sign_transaction(
-        &self,
-        psbt: &mut Psbt,
-        _sign_options: &crate::SignOptions,
-        _secp: &crate::wallet::utils::SecpCtx,
-    ) -> Result<(), SignerError> {
-        psbt.combine(self.client.sign_tx(psbt)?.psbt)
-            .expect("Failed to combine HW signed psbt with passed PSBT");
-        Ok(())
-    }
-}
diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs
deleted file mode 100644 (file)
index 146d467..0000000
+++ /dev/null
@@ -1,2708 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Wallet
-//!
-//! This module defines the [`Wallet`].
-use crate::collections::{BTreeMap, HashMap};
-use alloc::{
-    boxed::Box,
-    string::{String, ToString},
-    sync::Arc,
-    vec::Vec,
-};
-pub use bdk_chain::keychain::Balance;
-use bdk_chain::{
-    indexed_tx_graph,
-    keychain::{self, KeychainTxOutIndex},
-    local_chain::{
-        self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
-    },
-    spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
-    tx_graph::{CanonicalTx, TxGraph},
-    Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
-    IndexedTxGraph,
-};
-use bdk_persist::{Persist, PersistBackend};
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
-use bitcoin::{
-    absolute, psbt, Address, Block, FeeRate, Network, OutPoint, Script, ScriptBuf, Sequence,
-    Transaction, TxOut, Txid, Witness,
-};
-use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
-use bitcoin::{constants::genesis_block, Amount};
-use core::fmt;
-use core::ops::Deref;
-use descriptor::error::Error as DescriptorError;
-use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
-
-use bdk_chain::tx_graph::CalculateFeeError;
-
-pub mod coin_selection;
-pub mod export;
-pub mod signer;
-pub mod tx_builder;
-pub(crate) mod utils;
-
-pub mod error;
-
-pub use utils::IsDust;
-
-use coin_selection::DefaultCoinSelectionAlgorithm;
-use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
-use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
-use utils::{check_nsequence_rbf, After, Older, SecpCtx};
-
-use crate::descriptor::policy::BuildSatisfaction;
-use crate::descriptor::{
-    self, calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
-    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
-};
-use crate::psbt::PsbtUtils;
-use crate::signer::SignerError;
-use crate::types::*;
-use crate::wallet::coin_selection::Excess::{Change, NoChange};
-use crate::wallet::error::{BuildFeeBumpError, CreateTxError, MiniscriptPsbtError};
-
-const COINBASE_MATURITY: u32 = 100;
-
-/// A Bitcoin wallet
-///
-/// The `Wallet` acts as a way of coherently interfacing with output descriptors and related transactions.
-/// Its main components are:
-///
-/// 1. output *descriptors* from which it can derive addresses.
-/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
-///
-/// [`signer`]: crate::signer
-#[derive(Debug)]
-pub struct Wallet {
-    signers: Arc<SignersContainer>,
-    change_signers: Arc<SignersContainer>,
-    chain: LocalChain,
-    indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
-    persist: Persist<ChangeSet>,
-    network: Network,
-    secp: SecpCtx,
-}
-
-/// An update to [`Wallet`].
-///
-/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
-#[derive(Debug, Clone, Default)]
-pub struct Update {
-    /// Contains the last active derivation indices per keychain (`K`), which is used to update the
-    /// [`KeychainTxOutIndex`].
-    pub last_active_indices: BTreeMap<KeychainKind, u32>,
-
-    /// Update for the wallet's internal [`TxGraph`].
-    pub graph: TxGraph<ConfirmationTimeHeightAnchor>,
-
-    /// Update for the wallet's internal [`LocalChain`].
-    ///
-    /// [`LocalChain`]: local_chain::LocalChain
-    pub chain: Option<CheckPoint>,
-}
-
-impl From<FullScanResult<KeychainKind>> for Update {
-    fn from(value: FullScanResult<KeychainKind>) -> Self {
-        Self {
-            last_active_indices: value.last_active_indices,
-            graph: value.graph_update,
-            chain: Some(value.chain_update),
-        }
-    }
-}
-
-impl From<SyncResult> for Update {
-    fn from(value: SyncResult) -> Self {
-        Self {
-            last_active_indices: BTreeMap::new(),
-            graph: value.graph_update,
-            chain: Some(value.chain_update),
-        }
-    }
-}
-
-/// The changes made to a wallet by applying an [`Update`].
-#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
-pub struct ChangeSet {
-    /// Changes to the [`LocalChain`].
-    ///
-    /// [`LocalChain`]: local_chain::LocalChain
-    pub chain: local_chain::ChangeSet,
-
-    /// Changes to [`IndexedTxGraph`].
-    ///
-    /// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph
-    pub indexed_tx_graph: indexed_tx_graph::ChangeSet<
-        ConfirmationTimeHeightAnchor,
-        keychain::ChangeSet<KeychainKind>,
-    >,
-
-    /// Stores the network type of the wallet.
-    pub network: Option<Network>,
-}
-
-impl Append for ChangeSet {
-    fn append(&mut self, other: Self) {
-        Append::append(&mut self.chain, other.chain);
-        Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
-        if other.network.is_some() {
-            debug_assert!(
-                self.network.is_none() || self.network == other.network,
-                "network type must be consistent"
-            );
-            self.network = other.network;
-        }
-    }
-
-    fn is_empty(&self) -> bool {
-        self.chain.is_empty() && self.indexed_tx_graph.is_empty()
-    }
-}
-
-impl From<local_chain::ChangeSet> for ChangeSet {
-    fn from(chain: local_chain::ChangeSet) -> Self {
-        Self {
-            chain,
-            ..Default::default()
-        }
-    }
-}
-
-impl
-    From<
-        indexed_tx_graph::ChangeSet<
-            ConfirmationTimeHeightAnchor,
-            keychain::ChangeSet<KeychainKind>,
-        >,
-    > for ChangeSet
-{
-    fn from(
-        indexed_tx_graph: indexed_tx_graph::ChangeSet<
-            ConfirmationTimeHeightAnchor,
-            keychain::ChangeSet<KeychainKind>,
-        >,
-    ) -> Self {
-        Self {
-            indexed_tx_graph,
-            ..Default::default()
-        }
-    }
-}
-
-/// A derived address and the index it was found at.
-/// For convenience this automatically derefs to `Address`
-#[derive(Debug, PartialEq, Eq)]
-pub struct AddressInfo {
-    /// Child index of this address
-    pub index: u32,
-    /// Address
-    pub address: Address,
-    /// Type of keychain
-    pub keychain: KeychainKind,
-}
-
-impl Deref for AddressInfo {
-    type Target = Address;
-
-    fn deref(&self) -> &Self::Target {
-        &self.address
-    }
-}
-
-impl fmt::Display for AddressInfo {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}", self.address)
-    }
-}
-
-impl Wallet {
-    /// Creates a wallet that does not persist data.
-    pub fn new_no_persist<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        network: Network,
-    ) -> Result<Self, DescriptorError> {
-        Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
-            NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
-            NewError::Descriptor(e) => e,
-            NewError::Persist(_) => unreachable!("mock-write must always succeed"),
-        })
-    }
-
-    /// Creates a wallet that does not persist data, with a custom genesis hash.
-    pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        network: Network,
-        genesis_hash: BlockHash,
-    ) -> Result<Self, crate::descriptor::DescriptorError> {
-        Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash)
-            .map_err(|e| match e {
-                NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
-                NewError::Descriptor(e) => e,
-                NewError::Persist(_) => unreachable!("mock-write must always succeed"),
-            })
-    }
-}
-
-/// The error type when constructing a fresh [`Wallet`].
-///
-/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
-///
-/// [`new`]: Wallet::new
-/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
-#[derive(Debug)]
-pub enum NewError {
-    /// Database already has data.
-    NonEmptyDatabase,
-    /// There was problem with the passed-in descriptor(s).
-    Descriptor(crate::descriptor::DescriptorError),
-    /// We were unable to write the wallet's data to the persistence backend.
-    Persist(anyhow::Error),
-}
-
-impl fmt::Display for NewError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            NewError::NonEmptyDatabase => write!(
-                f,
-                "database already has data - use `load` or `new_or_load` methods instead"
-            ),
-            NewError::Descriptor(e) => e.fmt(f),
-            NewError::Persist(e) => e.fmt(f),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for NewError {}
-
-/// The error type when loading a [`Wallet`] from persistence.
-///
-/// Method [`load`] may return this error.
-///
-/// [`load`]: Wallet::load
-#[derive(Debug)]
-pub enum LoadError {
-    /// There was a problem with the passed-in descriptor(s).
-    Descriptor(crate::descriptor::DescriptorError),
-    /// Loading data from the persistence backend failed.
-    Persist(anyhow::Error),
-    /// Wallet not initialized, persistence backend is empty.
-    NotInitialized,
-    /// Data loaded from persistence is missing network type.
-    MissingNetwork,
-    /// Data loaded from persistence is missing genesis hash.
-    MissingGenesis,
-    /// Data loaded from persistence is missing descriptor.
-    MissingDescriptor,
-}
-
-impl fmt::Display for LoadError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            LoadError::Descriptor(e) => e.fmt(f),
-            LoadError::Persist(e) => e.fmt(f),
-            LoadError::NotInitialized => {
-                write!(f, "wallet is not initialized, persistence backend is empty")
-            }
-            LoadError::MissingNetwork => write!(f, "loaded data is missing network type"),
-            LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"),
-            LoadError::MissingDescriptor => write!(f, "loaded data is missing descriptor"),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for LoadError {}
-
-/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
-///
-/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error.
-///
-/// [`new_or_load`]: Wallet::new_or_load
-/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
-#[derive(Debug)]
-pub enum NewOrLoadError {
-    /// There is a problem with the passed-in descriptor.
-    Descriptor(crate::descriptor::DescriptorError),
-    /// Either writing to or loading from the persistence backend failed.
-    Persist(anyhow::Error),
-    /// Wallet is not initialized, persistence backend is empty.
-    NotInitialized,
-    /// The loaded genesis hash does not match what was provided.
-    LoadedGenesisDoesNotMatch {
-        /// The expected genesis block hash.
-        expected: BlockHash,
-        /// The block hash loaded from persistence.
-        got: Option<BlockHash>,
-    },
-    /// The loaded network type does not match what was provided.
-    LoadedNetworkDoesNotMatch {
-        /// The expected network type.
-        expected: Network,
-        /// The network type loaded from persistence.
-        got: Option<Network>,
-    },
-    /// The loaded desccriptor does not match what was provided.
-    LoadedDescriptorDoesNotMatch {
-        /// The descriptor loaded from persistence.
-        got: Option<ExtendedDescriptor>,
-        /// The keychain of the descriptor not matching
-        keychain: KeychainKind,
-    },
-}
-
-impl fmt::Display for NewOrLoadError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            NewOrLoadError::Descriptor(e) => e.fmt(f),
-            NewOrLoadError::Persist(e) => write!(
-                f,
-                "failed to either write to or load from persistence, {}",
-                e
-            ),
-            NewOrLoadError::NotInitialized => {
-                write!(f, "wallet is not initialized, persistence backend is empty")
-            }
-            NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
-                write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
-            }
-            NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
-                write!(f, "loaded network type is not {}, got {:?}", expected, got)
-            }
-            NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
-                write!(
-                    f,
-                    "loaded descriptor is different from what was provided, got {:?} for keychain {:?}",
-                    got, keychain
-                )
-            }
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for NewOrLoadError {}
-
-/// An error that may occur when inserting a transaction into [`Wallet`].
-#[derive(Debug)]
-pub enum InsertTxError {
-    /// The error variant that occurs when the caller attempts to insert a transaction with a
-    /// confirmation height that is greater than the internal chain tip.
-    ConfirmationHeightCannotBeGreaterThanTip {
-        /// The internal chain's tip height.
-        tip_height: u32,
-        /// The introduced transaction's confirmation height.
-        tx_height: u32,
-    },
-}
-
-impl fmt::Display for InsertTxError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
-                tip_height,
-                tx_height,
-            } => {
-                write!(f, "cannot insert tx with confirmation height ({}) higher than internal tip height ({})", tx_height, tip_height)
-            }
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for InsertTxError {}
-
-/// An error that may occur when applying a block to [`Wallet`].
-#[derive(Debug)]
-pub enum ApplyBlockError {
-    /// Occurs when the update chain cannot connect with original chain.
-    CannotConnect(CannotConnectError),
-    /// Occurs when the `connected_to` hash does not match the hash derived from `block`.
-    UnexpectedConnectedToHash {
-        /// Block hash of `connected_to`.
-        connected_to_hash: BlockHash,
-        /// Expected block hash of `connected_to`, as derived from `block`.
-        expected_hash: BlockHash,
-    },
-}
-
-impl fmt::Display for ApplyBlockError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            ApplyBlockError::CannotConnect(err) => err.fmt(f),
-            ApplyBlockError::UnexpectedConnectedToHash {
-                expected_hash: block_hash,
-                connected_to_hash: checkpoint_hash,
-            } => write!(
-                f,
-                "`connected_to` hash {} differs from the expected hash {} (which is derived from `block`)",
-                checkpoint_hash, block_hash
-            ),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for ApplyBlockError {}
-
-impl Wallet {
-    /// Initialize an empty [`Wallet`].
-    pub fn new<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-        network: Network,
-    ) -> Result<Self, NewError> {
-        let genesis_hash = genesis_block(network).block_hash();
-        Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash)
-    }
-
-    /// Initialize an empty [`Wallet`] with a custom genesis hash.
-    ///
-    /// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful
-    /// for syncing from alternative networks.
-    pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-        network: Network,
-        genesis_hash: BlockHash,
-    ) -> Result<Self, NewError> {
-        if let Ok(changeset) = db.load_from_persistence() {
-            if changeset.is_some() {
-                return Err(NewError::NonEmptyDatabase);
-            }
-        }
-        let secp = Secp256k1::new();
-        let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
-        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
-
-        let (signers, change_signers) =
-            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
-                .map_err(NewError::Descriptor)?;
-
-        let indexed_graph = IndexedTxGraph::new(index);
-
-        let mut persist = Persist::new(db);
-        persist.stage(ChangeSet {
-            chain: chain_changeset,
-            indexed_tx_graph: indexed_graph.initial_changeset(),
-            network: Some(network),
-        });
-        persist.commit().map_err(NewError::Persist)?;
-
-        Ok(Wallet {
-            signers,
-            change_signers,
-            network,
-            chain,
-            indexed_graph,
-            persist,
-            secp,
-        })
-    }
-
-    /// Load [`Wallet`] from the given persistence backend.
-    ///
-    /// Note that the descriptor secret keys are not persisted to the db; this means that after
-    /// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be
-    /// able to sign transactions.
-    ///
-    /// If you wish to use the wallet to sign transactions, you need to add the secret keys
-    /// manually to the [`Wallet`]:
-    ///
-    /// ```rust,no_run
-    /// # use bdk::Wallet;
-    /// # use bdk::signer::{SignersContainer, SignerOrdering};
-    /// # use bdk::descriptor::Descriptor;
-    /// # use bitcoin::key::Secp256k1;
-    /// # use bdk::KeychainKind;
-    /// # use bdk_file_store::Store;
-    /// #
-    /// # fn main() -> Result<(), anyhow::Error> {
-    /// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
-    /// # let file_path = temp_dir.path().join("store.db");
-    /// # let db: Store<bdk::wallet::ChangeSet> = Store::create_new(&[], &file_path).expect("must create db");
-    /// let secp = Secp256k1::new();
-    ///
-    /// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
-    /// let (internal_descriptor, internal_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)").unwrap();
-    ///
-    /// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
-    /// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
-    ///
-    /// let mut wallet = Wallet::load(db)?;
-    ///
-    /// external_signer_container.signers().into_iter()
-    ///     .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
-    /// internal_signer_container.signers().into_iter()
-    ///     .for_each(|s| wallet.add_signer(KeychainKind::Internal, SignerOrdering::default(), s.clone()));
-    /// # Ok(())
-    /// # }
-    /// ```
-    ///
-    /// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
-    /// passed-in descriptors to the [`Wallet`].
-    pub fn load(
-        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-    ) -> Result<Self, LoadError> {
-        let changeset = db
-            .load_from_persistence()
-            .map_err(LoadError::Persist)?
-            .ok_or(LoadError::NotInitialized)?;
-        Self::load_from_changeset(db, changeset)
-    }
-
-    fn load_from_changeset(
-        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-        changeset: ChangeSet,
-    ) -> Result<Self, LoadError> {
-        let secp = Secp256k1::new();
-        let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
-        let chain =
-            LocalChain::from_changeset(changeset.chain).map_err(|_| LoadError::MissingGenesis)?;
-        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
-        let descriptor = changeset
-            .indexed_tx_graph
-            .indexer
-            .keychains_added
-            .get(&KeychainKind::External)
-            .ok_or(LoadError::MissingDescriptor)?
-            .clone();
-        let change_descriptor = changeset
-            .indexed_tx_graph
-            .indexer
-            .keychains_added
-            .get(&KeychainKind::Internal)
-            .cloned();
-
-        let (signers, change_signers) =
-            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
-                .expect("Can't fail: we passed in valid descriptors, recovered from the changeset");
-
-        let mut indexed_graph = IndexedTxGraph::new(index);
-        indexed_graph.apply_changeset(changeset.indexed_tx_graph);
-
-        let persist = Persist::new(db);
-
-        Ok(Wallet {
-            signers,
-            change_signers,
-            chain,
-            indexed_graph,
-            persist,
-            network,
-            secp,
-        })
-    }
-
-    /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist.
-    ///
-    /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
-    pub fn new_or_load<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-        network: Network,
-    ) -> Result<Self, NewOrLoadError> {
-        let genesis_hash = genesis_block(network).block_hash();
-        Self::new_or_load_with_genesis_hash(
-            descriptor,
-            change_descriptor,
-            db,
-            network,
-            genesis_hash,
-        )
-    }
-
-    /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist, using the
-    /// provided descriptor, change descriptor, network, and custom genesis hash.
-    ///
-    /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
-    /// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
-    /// useful for syncing from alternative networks.
-    pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
-        descriptor: E,
-        change_descriptor: Option<E>,
-        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
-        network: Network,
-        genesis_hash: BlockHash,
-    ) -> Result<Self, NewOrLoadError> {
-        let changeset = db
-            .load_from_persistence()
-            .map_err(NewOrLoadError::Persist)?;
-        match changeset {
-            Some(changeset) => {
-                let mut wallet = Self::load_from_changeset(db, changeset).map_err(|e| match e {
-                    LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
-                    LoadError::Persist(e) => NewOrLoadError::Persist(e),
-                    LoadError::NotInitialized => NewOrLoadError::NotInitialized,
-                    LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
-                        expected: network,
-                        got: None,
-                    },
-                    LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
-                        expected: genesis_hash,
-                        got: None,
-                    },
-                    LoadError::MissingDescriptor => NewOrLoadError::LoadedDescriptorDoesNotMatch {
-                        got: None,
-                        keychain: KeychainKind::External,
-                    },
-                })?;
-                if wallet.network != network {
-                    return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
-                        expected: network,
-                        got: Some(wallet.network),
-                    });
-                }
-                if wallet.chain.genesis_hash() != genesis_hash {
-                    return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
-                        expected: genesis_hash,
-                        got: Some(wallet.chain.genesis_hash()),
-                    });
-                }
-
-                let (expected_descriptor, expected_descriptor_keymap) = descriptor
-                    .into_wallet_descriptor(&wallet.secp, network)
-                    .map_err(NewOrLoadError::Descriptor)?;
-                let wallet_descriptor = wallet.public_descriptor(KeychainKind::External).cloned();
-                if wallet_descriptor != Some(expected_descriptor.clone()) {
-                    return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
-                        got: wallet_descriptor,
-                        keychain: KeychainKind::External,
-                    });
-                }
-                // if expected descriptor has private keys add them as new signers
-                if !expected_descriptor_keymap.is_empty() {
-                    let signer_container = SignersContainer::build(
-                        expected_descriptor_keymap,
-                        &expected_descriptor,
-                        &wallet.secp,
-                    );
-                    signer_container.signers().into_iter().for_each(|signer| {
-                        wallet.add_signer(
-                            KeychainKind::External,
-                            SignerOrdering::default(),
-                            signer.clone(),
-                        )
-                    });
-                }
-
-                let expected_change_descriptor = if let Some(c) = change_descriptor {
-                    Some(
-                        c.into_wallet_descriptor(&wallet.secp, network)
-                            .map_err(NewOrLoadError::Descriptor)?,
-                    )
-                } else {
-                    None
-                };
-                let wallet_change_descriptor =
-                    wallet.public_descriptor(KeychainKind::Internal).cloned();
-
-                match (expected_change_descriptor, wallet_change_descriptor) {
-                    (Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor))
-                        if wallet_descriptor == expected_descriptor =>
-                    {
-                        // if expected change descriptor has private keys add them as new signers
-                        if !expected_keymap.is_empty() {
-                            let signer_container = SignersContainer::build(
-                                expected_keymap,
-                                &expected_descriptor,
-                                &wallet.secp,
-                            );
-                            signer_container.signers().into_iter().for_each(|signer| {
-                                wallet.add_signer(
-                                    KeychainKind::Internal,
-                                    SignerOrdering::default(),
-                                    signer.clone(),
-                                )
-                            });
-                        }
-                    }
-                    (None, None) => (),
-                    (_, wallet_descriptor) => {
-                        return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
-                            got: wallet_descriptor,
-                            keychain: KeychainKind::Internal,
-                        });
-                    }
-                }
-
-                Ok(wallet)
-            }
-            None => Self::new_with_genesis_hash(
-                descriptor,
-                change_descriptor,
-                db,
-                network,
-                genesis_hash,
-            )
-            .map_err(|e| match e {
-                NewError::NonEmptyDatabase => {
-                    unreachable!("database is already checked to have no data")
-                }
-                NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
-                NewError::Persist(e) => NewOrLoadError::Persist(e),
-            }),
-        }
-    }
-
-    /// Get the Bitcoin network the wallet is using.
-    pub fn network(&self) -> Network {
-        self.network
-    }
-
-    /// Iterator over all keychains in this wallet
-    pub fn keychains(&self) -> impl Iterator<Item = (&KeychainKind, &ExtendedDescriptor)> {
-        self.indexed_graph.index.keychains()
-    }
-
-    /// Peek an address of the given `keychain` at `index` without revealing it.
-    ///
-    /// For non-wildcard descriptors this returns the same address at every provided index.
-    ///
-    /// # Panics
-    ///
-    /// This panics when the caller requests for an address of derivation index greater than the
-    /// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
-    pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
-        let keychain = self.map_keychain(keychain);
-        let mut spk_iter = self
-            .indexed_graph
-            .index
-            .unbounded_spk_iter(&keychain)
-            .expect("Must exist (we called map_keychain)");
-        if !spk_iter.descriptor().has_wildcard() {
-            index = 0;
-        }
-        let (index, spk) = spk_iter
-            .nth(index as usize)
-            .expect("derivation index is out of bounds");
-
-        AddressInfo {
-            index,
-            address: Address::from_script(&spk, self.network).expect("must have address form"),
-            keychain,
-        }
-    }
-
-    /// Attempt to reveal the next address of the given `keychain`.
-    ///
-    /// This will increment the internal derivation index. If the keychain's descriptor doesn't
-    /// contain a wildcard or every address is already revealed up to the maximum derivation
-    /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
-    /// then returns the last revealed address.
-    ///
-    /// # Errors
-    ///
-    /// If writing to persistent storage fails.
-    pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
-        let keychain = self.map_keychain(keychain);
-        let ((index, spk), index_changeset) = self
-            .indexed_graph
-            .index
-            .reveal_next_spk(&keychain)
-            .expect("Must exist (we called map_keychain)");
-
-        self.persist
-            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
-
-        Ok(AddressInfo {
-            index,
-            address: Address::from_script(spk, self.network).expect("must have address form"),
-            keychain,
-        })
-    }
-
-    /// Reveal addresses up to and including the target `index` and return an iterator
-    /// of newly revealed addresses.
-    ///
-    /// If the target `index` is unreachable, we make a best effort to reveal up to the last
-    /// possible index. If all addresses up to the given `index` are already revealed, then
-    /// no new addresses are returned.
-    ///
-    /// # Errors
-    ///
-    /// If writing to persistent storage fails.
-    pub fn reveal_addresses_to(
-        &mut self,
-        keychain: KeychainKind,
-        index: u32,
-    ) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
-        let keychain = self.map_keychain(keychain);
-        let (spk_iter, index_changeset) = self
-            .indexed_graph
-            .index
-            .reveal_to_target(&keychain, index)
-            .expect("must exist (we called map_keychain)");
-
-        self.persist
-            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
-
-        Ok(spk_iter.map(move |(index, spk)| AddressInfo {
-            index,
-            address: Address::from_script(&spk, self.network).expect("must have address form"),
-            keychain,
-        }))
-    }
-
-    /// Get the next unused address for the given `keychain`, i.e. the address with the lowest
-    /// derivation index that hasn't been used.
-    ///
-    /// This will attempt to derive and reveal a new address if no newly revealed addresses
-    /// are available. See also [`reveal_next_address`](Self::reveal_next_address).
-    ///
-    /// # Errors
-    ///
-    /// If writing to persistent storage fails.
-    pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
-        let keychain = self.map_keychain(keychain);
-        let ((index, spk), index_changeset) = self
-            .indexed_graph
-            .index
-            .next_unused_spk(&keychain)
-            .expect("must exist (we called map_keychain)");
-
-        self.persist
-            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
-
-        Ok(AddressInfo {
-            index,
-            address: Address::from_script(spk, self.network).expect("must have address form"),
-            keychain,
-        })
-    }
-
-    /// Marks an address used of the given `keychain` at `index`.
-    ///
-    /// Returns whether the given index was present and then removed from the unused set.
-    pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
-        self.indexed_graph.index.mark_used(keychain, index)
-    }
-
-    /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted
-    /// back into the unused set.
-    ///
-    /// Since this is only a superficial marker, it will have no effect if the address at the given
-    /// `index` was actually used, i.e. the wallet has previously indexed a tx output for the
-    /// derived spk.
-    ///
-    /// [`mark_used`]: Self::mark_used
-    pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
-        self.indexed_graph.index.unmark_used(keychain, index)
-    }
-
-    /// List addresses that are revealed but unused.
-    ///
-    /// Note if the returned iterator is empty you can reveal more addresses
-    /// by using [`reveal_next_address`](Self::reveal_next_address) or
-    /// [`reveal_addresses_to`](Self::reveal_addresses_to).
-    pub fn list_unused_addresses(
-        &self,
-        keychain: KeychainKind,
-    ) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
-        let keychain = self.map_keychain(keychain);
-        self.indexed_graph
-            .index
-            .unused_keychain_spks(&keychain)
-            .map(move |(index, spk)| AddressInfo {
-                index,
-                address: Address::from_script(spk, self.network).expect("must have address form"),
-                keychain,
-            })
-    }
-
-    /// Return whether or not a `script` is part of this wallet (either internal or external)
-    pub fn is_mine(&self, script: &Script) -> bool {
-        self.indexed_graph.index.index_of_spk(script).is_some()
-    }
-
-    /// Finds how the wallet derived the script pubkey `spk`.
-    ///
-    /// Will only return `Some(_)` if the wallet has given out the spk.
-    pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
-        self.indexed_graph.index.index_of_spk(spk)
-    }
-
-    /// Return the list of unspent outputs of this wallet
-    pub fn list_unspent(&self) -> impl Iterator<Item = LocalOutput> + '_ {
-        self.indexed_graph
-            .graph()
-            .filter_chain_unspents(
-                &self.chain,
-                self.chain.tip().block_id(),
-                self.indexed_graph.index.outpoints(),
-            )
-            .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
-    }
-
-    /// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
-    ///
-    /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.
-    pub fn list_output(&self) -> impl Iterator<Item = LocalOutput> + '_ {
-        self.indexed_graph
-            .graph()
-            .filter_chain_txouts(
-                &self.chain,
-                self.chain.tip().block_id(),
-                self.indexed_graph.index.outpoints(),
-            )
-            .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
-    }
-
-    /// Get all the checkpoints the wallet is currently storing indexed by height.
-    pub fn checkpoints(&self) -> CheckPointIter {
-        self.chain.iter_checkpoints()
-    }
-
-    /// Returns the latest checkpoint.
-    pub fn latest_checkpoint(&self) -> CheckPoint {
-        self.chain.tip()
-    }
-
-    /// Get unbounded script pubkey iterators for both `Internal` and `External` keychains.
-    ///
-    /// This is intended to be used when doing a full scan of your addresses (e.g. after restoring
-    /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
-    /// electrum server) which will go through each address until it reaches a *stop gap*.
-    ///
-    /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
-    /// script pubkeys the wallet is storing internally).
-    pub fn all_unbounded_spk_iters(
-        &self,
-    ) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
-        self.indexed_graph.index.all_unbounded_spk_iters()
-    }
-
-    /// Get an unbounded script pubkey iterator for the given `keychain`.
-    ///
-    /// See [`all_unbounded_spk_iters`] for more documentation
-    ///
-    /// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
-    pub fn unbounded_spk_iter(
-        &self,
-        keychain: KeychainKind,
-    ) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
-        let keychain = self.map_keychain(keychain);
-        self.indexed_graph
-            .index
-            .unbounded_spk_iter(&keychain)
-            .expect("Must exist (we called map_keychain)")
-    }
-
-    /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
-    /// wallet's database.
-    pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
-        let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
-        self.indexed_graph
-            .graph()
-            .filter_chain_unspents(
-                &self.chain,
-                self.chain.tip().block_id(),
-                core::iter::once(((), op)),
-            )
-            .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo))
-            .next()
-    }
-
-    /// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph.
-    ///
-    /// This is used for providing a previous output's value so that we can use [`calculate_fee`]
-    /// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
-    /// not be returned in [`list_unspent`] or [`list_output`].
-    ///
-    /// Any inserted `TxOut`s are not persisted until [`commit`] is called.
-    ///
-    /// **WARNING:** This should only be used to add `TxOut`s that the wallet does not own. Only
-    /// insert `TxOut`s that you trust the values for!
-    ///
-    /// [`calculate_fee`]: Self::calculate_fee
-    /// [`calculate_fee_rate`]: Self::calculate_fee_rate
-    /// [`list_unspent`]: Self::list_unspent
-    /// [`list_output`]: Self::list_output
-    /// [`commit`]: Self::commit
-    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
-        let additions = self.indexed_graph.insert_txout(outpoint, txout);
-        self.persist.stage(ChangeSet::from(additions));
-    }
-
-    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
-    ///
-    /// To calculate the fee for a [`Transaction`] with inputs not owned by this wallet you must
-    /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function.
-    ///
-    /// Note `tx` does not have to be in the graph for this to work.
-    ///
-    /// # Examples
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Txid;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let txid:Txid = todo!();
-    /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
-    /// let fee = wallet.calculate_fee(&tx).expect("fee");
-    /// ```
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Psbt;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let mut psbt: Psbt = todo!();
-    /// let tx = &psbt.clone().extract_tx().expect("tx");
-    /// let fee = wallet.calculate_fee(tx).expect("fee");
-    /// ```
-    /// [`insert_txout`]: Self::insert_txout
-    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
-        self.indexed_graph.graph().calculate_fee(tx)
-    }
-
-    /// Calculate the [`FeeRate`] for a given transaction.
-    ///
-    /// To calculate the fee rate for a [`Transaction`] with inputs not owned by this wallet you must
-    /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function.
-    ///
-    /// Note `tx` does not have to be in the graph for this to work.
-    ///
-    /// # Examples
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Txid;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let txid:Txid = todo!();
-    /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
-    /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate");
-    /// ```
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Psbt;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let mut psbt: Psbt = todo!();
-    /// let tx = &psbt.clone().extract_tx().expect("tx");
-    /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate");
-    /// ```
-    /// [`insert_txout`]: Self::insert_txout
-    pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<FeeRate, CalculateFeeError> {
-        self.calculate_fee(tx)
-            .map(|fee| Amount::from_sat(fee) / tx.weight())
-    }
-
-    /// Compute the `tx`'s sent and received [`Amount`]s.
-    ///
-    /// This method returns a tuple `(sent, received)`. Sent is the sum of the txin amounts
-    /// that spend from previous txouts tracked by this wallet. Received is the summation
-    /// of this tx's outputs that send to script pubkeys tracked by this wallet.
-    ///
-    /// # Examples
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Txid;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let txid:Txid = todo!();
-    /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx;
-    /// let (sent, received) = wallet.sent_and_received(&tx);
-    /// ```
-    ///
-    /// ```rust, no_run
-    /// # use bitcoin::Psbt;
-    /// # use bdk::Wallet;
-    /// # let mut wallet: Wallet = todo!();
-    /// # let mut psbt: Psbt = todo!();
-    /// let tx = &psbt.clone().extract_tx().expect("tx");
-    /// let (sent, received) = wallet.sent_and_received(tx);
-    /// ```
-    pub fn sent_and_received(&self, tx: &Transaction) -> (Amount, Amount) {
-        self.indexed_graph.index.sent_and_received(tx, ..)
-    }
-
-    /// Get a single transaction from the wallet as a [`CanonicalTx`] (if the transaction exists).
-    ///
-    /// `CanonicalTx` contains the full transaction alongside meta-data such as:
-    /// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that exist
-    ///   in the best chain.
-    /// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is
-    ///   confirmed or unconfirmed. If the transaction is confirmed, the anchor which proves the
-    ///   confirmation is provided. If the transaction is unconfirmed, the unix timestamp of when
-    ///   the transaction was last seen in the mempool is provided.
-    ///
-    /// ```rust, no_run
-    /// use bdk::{chain::ChainPosition, Wallet};
-    /// use bdk_chain::Anchor;
-    /// # let wallet: Wallet = todo!();
-    /// # let my_txid: bitcoin::Txid = todo!();
-    ///
-    /// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist");
-    ///
-    /// // get reference to full transaction
-    /// println!("my tx: {:#?}", canonical_tx.tx_node.tx);
-    ///
-    /// // list all transaction anchors
-    /// for anchor in canonical_tx.tx_node.anchors {
-    ///     println!(
-    ///         "tx is anchored by block of hash {}",
-    ///         anchor.anchor_block().hash
-    ///     );
-    /// }
-    ///
-    /// // get confirmation status of transaction
-    /// match canonical_tx.chain_position {
-    ///     ChainPosition::Confirmed(anchor) => println!(
-    ///         "tx is confirmed at height {}, we know this since {}:{} is in the best chain",
-    ///         anchor.confirmation_height, anchor.anchor_block.height, anchor.anchor_block.hash,
-    ///     ),
-    ///     ChainPosition::Unconfirmed(last_seen) => println!(
-    ///         "tx is last seen at {}, it is unconfirmed as it is not anchored in the best chain",
-    ///         last_seen,
-    ///     ),
-    /// }
-    /// ```
-    ///
-    /// [`Anchor`]: bdk_chain::Anchor
-    pub fn get_tx(
-        &self,
-        txid: Txid,
-    ) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
-        let graph = self.indexed_graph.graph();
-
-        Some(CanonicalTx {
-            chain_position: graph.get_chain_position(
-                &self.chain,
-                self.chain.tip().block_id(),
-                txid,
-            )?,
-            tx_node: graph.get_tx_node(txid)?,
-        })
-    }
-
-    /// Add a new checkpoint to the wallet's internal view of the chain.
-    /// This stages but does not [`commit`] the change.
-    ///
-    /// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already
-    /// there).
-    ///
-    /// [`commit`]: Self::commit
-    pub fn insert_checkpoint(
-        &mut self,
-        block_id: BlockId,
-    ) -> Result<bool, local_chain::AlterCheckPointError> {
-        let changeset = self.chain.insert_block(block_id)?;
-        let changed = !changeset.is_empty();
-        self.persist.stage(changeset.into());
-        Ok(changed)
-    }
-
-    /// Add a transaction to the wallet's internal view of the chain. This stages but does not
-    /// [`commit`] the change.
-    ///
-    /// Returns whether anything changed with the transaction insertion (e.g. `false` if the
-    /// transaction was already inserted at the same position).
-    ///
-    /// A `tx` can be rejected if `position` has a height greater than the [`latest_checkpoint`].
-    /// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
-    /// inserting new transactions.
-    ///
-    /// **WARNING:** If `position` is confirmed, we anchor the `tx` to a the lowest checkpoint that
-    /// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our
-    /// local view of the best chain's history.
-    ///
-    /// [`commit`]: Self::commit
-    /// [`latest_checkpoint`]: Self::latest_checkpoint
-    /// [`insert_checkpoint`]: Self::insert_checkpoint
-    pub fn insert_tx(
-        &mut self,
-        tx: Transaction,
-        position: ConfirmationTime,
-    ) -> Result<bool, InsertTxError> {
-        let (anchor, last_seen) = match position {
-            ConfirmationTime::Confirmed { height, time } => {
-                // anchor tx to checkpoint with lowest height that is >= position's height
-                let anchor = self
-                    .chain
-                    .range(height..)
-                    .last()
-                    .ok_or(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
-                        tip_height: self.chain.tip().height(),
-                        tx_height: height,
-                    })
-                    .map(|anchor_cp| ConfirmationTimeHeightAnchor {
-                        anchor_block: anchor_cp.block_id(),
-                        confirmation_height: height,
-                        confirmation_time: time,
-                    })?;
-
-                (Some(anchor), None)
-            }
-            ConfirmationTime::Unconfirmed { last_seen } => (None, Some(last_seen)),
-        };
-
-        let mut changeset = ChangeSet::default();
-        let txid = tx.txid();
-        changeset.append(self.indexed_graph.insert_tx(tx).into());
-        if let Some(anchor) = anchor {
-            changeset.append(self.indexed_graph.insert_anchor(txid, anchor).into());
-        }
-        if let Some(last_seen) = last_seen {
-            changeset.append(self.indexed_graph.insert_seen_at(txid, last_seen).into());
-        }
-
-        let changed = !changeset.is_empty();
-        self.persist.stage(changeset);
-        Ok(changed)
-    }
-
-    /// Iterate over the transactions in the wallet.
-    pub fn transactions(
-        &self,
-    ) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> + '_
-    {
-        self.indexed_graph
-            .graph()
-            .list_chain_txs(&self.chain, self.chain.tip().block_id())
-    }
-
-    /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
-    /// values.
-    pub fn get_balance(&self) -> Balance {
-        self.indexed_graph.graph().balance(
-            &self.chain,
-            self.chain.tip().block_id(),
-            self.indexed_graph.index.outpoints(),
-            |&(k, _), _| k == KeychainKind::Internal,
-        )
-    }
-
-    /// Add an external signer
-    ///
-    /// See [the `signer` module](signer) for an example.
-    pub fn add_signer(
-        &mut self,
-        keychain: KeychainKind,
-        ordering: SignerOrdering,
-        signer: Arc<dyn TransactionSigner>,
-    ) {
-        let signers = match keychain {
-            KeychainKind::External => Arc::make_mut(&mut self.signers),
-            KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
-        };
-
-        signers.add_external(signer.id(&self.secp), ordering, signer);
-    }
-
-    /// Get the signers
-    ///
-    /// ## Example
-    ///
-    /// ```
-    /// # use bdk::{Wallet, KeychainKind};
-    /// # use bdk::bitcoin::Network;
-    /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?;
-    /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
-    ///     // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
-    ///     println!("secret_key: {}", secret_key);
-    /// }
-    ///
-    /// Ok::<(), Box<dyn std::error::Error>>(())
-    /// ```
-    pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
-        match keychain {
-            KeychainKind::External => Arc::clone(&self.signers),
-            KeychainKind::Internal => Arc::clone(&self.change_signers),
-        }
-    }
-
-    /// Start building a transaction.
-    ///
-    /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
-    ///
-    /// ## Example
-    ///
-    /// ```
-    /// # use std::str::FromStr;
-    /// # use bitcoin::*;
-    /// # use bdk::*;
-    /// # use bdk::wallet::ChangeSet;
-    /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_persist::PersistBackend;
-    /// # use anyhow::Error;
-    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-    /// # let mut wallet = doctest_wallet!();
-    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
-    /// let psbt = {
-    ///    let mut builder =  wallet.build_tx();
-    ///    builder
-    ///        .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
-    ///    builder.finish()?
-    /// };
-    ///
-    /// // sign and broadcast ...
-    /// # Ok::<(), anyhow::Error>(())
-    /// ```
-    ///
-    /// [`TxBuilder`]: crate::TxBuilder
-    pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm, CreateTx> {
-        TxBuilder {
-            wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
-            params: TxParams::default(),
-            coin_selection: DefaultCoinSelectionAlgorithm::default(),
-            phantom: core::marker::PhantomData,
-        }
-    }
-
-    pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
-        &mut self,
-        coin_selection: Cs,
-        params: TxParams,
-    ) -> Result<Psbt, CreateTxError> {
-        let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect();
-        let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist");
-        let internal_descriptor = keychains.get(&KeychainKind::Internal);
-
-        let external_policy = external_descriptor
-            .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
-            .unwrap();
-        let internal_policy = internal_descriptor
-            .as_ref()
-            .map(|desc| {
-                Ok::<_, CreateTxError>(
-                    desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
-                        .unwrap(),
-                )
-            })
-            .transpose()?;
-
-        // The policy allows spending external outputs, but it requires a policy path that hasn't been
-        // provided
-        if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange
-            && external_policy.requires_path()
-            && params.external_policy_path.is_none()
-        {
-            return Err(CreateTxError::SpendingPolicyRequired(
-                KeychainKind::External,
-            ));
-        };
-        // Same for the internal_policy path, if present
-        if let Some(internal_policy) = &internal_policy {
-            if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
-                && internal_policy.requires_path()
-                && params.internal_policy_path.is_none()
-            {
-                return Err(CreateTxError::SpendingPolicyRequired(
-                    KeychainKind::Internal,
-                ));
-            };
-        }
-
-        let external_requirements = external_policy.get_condition(
-            params
-                .external_policy_path
-                .as_ref()
-                .unwrap_or(&BTreeMap::new()),
-        )?;
-        let internal_requirements = internal_policy
-            .map(|policy| {
-                Ok::<_, CreateTxError>(
-                    policy.get_condition(
-                        params
-                            .internal_policy_path
-                            .as_ref()
-                            .unwrap_or(&BTreeMap::new()),
-                    )?,
-                )
-            })
-            .transpose()?;
-
-        let requirements =
-            external_requirements.merge(&internal_requirements.unwrap_or_default())?;
-
-        let version = match params.version {
-            Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0),
-            Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
-                return Err(CreateTxError::Version1Csv)
-            }
-            Some(tx_builder::Version(x)) => x,
-            None if requirements.csv.is_some() => 2,
-            None => 1,
-        };
-
-        // We use a match here instead of a unwrap_or_else as it's way more readable :)
-        let current_height = match params.current_height {
-            // If they didn't tell us the current height, we assume it's the latest sync height.
-            None => {
-                let tip_height = self.chain.tip().height();
-                absolute::LockTime::from_height(tip_height).expect("invalid height")
-            }
-            Some(h) => h,
-        };
-
-        let lock_time = match params.locktime {
-            // When no nLockTime is specified, we try to prevent fee sniping, if possible
-            None => {
-                // Fee sniping can be partially prevented by setting the timelock
-                // to current_height. If we don't know the current_height,
-                // we default to 0.
-                let fee_sniping_height = current_height;
-
-                // We choose the biggest between the required nlocktime and the fee sniping
-                // height
-                match requirements.timelock {
-                    // No requirement, just use the fee_sniping_height
-                    None => fee_sniping_height,
-                    // There's a block-based requirement, but the value is lower than the fee_sniping_height
-                    Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => {
-                        fee_sniping_height
-                    }
-                    // There's a time-based requirement or a block-based requirement greater
-                    // than the fee_sniping_height use that value
-                    Some(value) => value,
-                }
-            }
-            // Specific nLockTime required and we have no constraints, so just set to that value
-            Some(x) if requirements.timelock.is_none() => x,
-            // Specific nLockTime required and it's compatible with the constraints
-            Some(x)
-                if requirements.timelock.unwrap().is_same_unit(x)
-                    && x >= requirements.timelock.unwrap() =>
-            {
-                x
-            }
-            // Invalid nLockTime required
-            Some(x) => {
-                return Err(CreateTxError::LockTime {
-                    requested: x,
-                    required: requirements.timelock.unwrap(),
-                })
-            }
-        };
-
-        // The nSequence to be by default for inputs unless an explicit sequence is specified.
-        let n_sequence = match (params.rbf, requirements.csv) {
-            // No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
-            (None, None) if lock_time != absolute::LockTime::ZERO => {
-                Sequence::ENABLE_LOCKTIME_NO_RBF
-            }
-            // No RBF, CSV or nLockTime, make the transaction final
-            (None, None) => Sequence::MAX,
-
-            // No RBF requested, use the value from CSV. Note that this value is by definition
-            // non-final, so even if a timelock is enabled this nSequence is fine, hence why we
-            // don't bother checking for it here. The same is true for all the other branches below
-            (None, Some(csv)) => csv,
-
-            // RBF with a specific value but that value is too high
-            (Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
-                return Err(CreateTxError::RbfSequence)
-            }
-            // RBF with a specific value requested, but the value is incompatible with CSV
-            (Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
-                if !check_nsequence_rbf(rbf, csv) =>
-            {
-                return Err(CreateTxError::RbfSequenceCsv { rbf, csv })
-            }
-
-            // RBF enabled with the default value with CSV also enabled. CSV takes precedence
-            (Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
-            // Valid RBF, either default or with a specific value. We ignore the `CSV` value
-            // because we've already checked it before
-            (Some(rbf), _) => rbf.get_value(),
-        };
-
-        let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() {
-            //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256
-            FeePolicy::FeeAmount(fee) => {
-                if let Some(previous_fee) = params.bumping_fee {
-                    if fee < previous_fee.absolute {
-                        return Err(CreateTxError::FeeTooLow {
-                            required: previous_fee.absolute,
-                        });
-                    }
-                }
-                (FeeRate::ZERO, fee)
-            }
-            FeePolicy::FeeRate(rate) => {
-                if let Some(previous_fee) = params.bumping_fee {
-                    let required_feerate = FeeRate::from_sat_per_kwu(
-                        previous_fee.rate.to_sat_per_kwu()
-                            + FeeRate::BROADCAST_MIN.to_sat_per_kwu(), // +1 sat/vb
-                    );
-                    if rate < required_feerate {
-                        return Err(CreateTxError::FeeRateTooLow {
-                            required: required_feerate,
-                        });
-                    }
-                }
-                (rate, 0)
-            }
-        };
-
-        let mut tx = Transaction {
-            version: transaction::Version::non_standard(version),
-            lock_time,
-            input: vec![],
-            output: vec![],
-        };
-
-        if params.manually_selected_only && params.utxos.is_empty() {
-            return Err(CreateTxError::NoUtxosSelected);
-        }
-
-        // we keep it as a float while we accumulate it, and only round it at the end
-        let mut outgoing: u64 = 0;
-        let mut received: u64 = 0;
-
-        let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
-
-        for (index, (script_pubkey, value)) in recipients.enumerate() {
-            if !params.allow_dust
-                && value.is_dust(script_pubkey)
-                && !script_pubkey.is_provably_unspendable()
-            {
-                return Err(CreateTxError::OutputBelowDustLimit(index));
-            }
-
-            if self.is_mine(script_pubkey) {
-                received += value;
-            }
-
-            let new_out = TxOut {
-                script_pubkey: script_pubkey.clone(),
-                value: Amount::from_sat(value),
-            };
-
-            tx.output.push(new_out);
-
-            outgoing += value;
-        }
-
-        fee_amount += (fee_rate * tx.weight()).to_sat();
-
-        if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
-            && internal_descriptor.is_none()
-        {
-            return Err(CreateTxError::ChangePolicyDescriptor);
-        }
-
-        let (required_utxos, optional_utxos) =
-            self.preselect_utxos(&params, Some(current_height.to_consensus_u32()));
-
-        // get drain script
-        let drain_script = match params.drain_to {
-            Some(ref drain_recipient) => drain_recipient.clone(),
-            None => {
-                let change_keychain = self.map_keychain(KeychainKind::Internal);
-                let ((index, spk), index_changeset) = self
-                    .indexed_graph
-                    .index
-                    .next_unused_spk(&change_keychain)
-                    .expect("Keychain exists (we called map_keychain)");
-                let spk = spk.into();
-                self.indexed_graph.index.mark_used(change_keychain, index);
-                self.persist
-                    .stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
-                        index_changeset,
-                    )));
-                self.persist.commit().map_err(CreateTxError::Persist)?;
-                spk
-            }
-        };
-
-        let (required_utxos, optional_utxos) =
-            coin_selection::filter_duplicates(required_utxos, optional_utxos);
-
-        let coin_selection = coin_selection.coin_select(
-            required_utxos,
-            optional_utxos,
-            fee_rate,
-            outgoing + fee_amount,
-            &drain_script,
-        )?;
-        fee_amount += coin_selection.fee_amount;
-        let excess = &coin_selection.excess;
-
-        tx.input = coin_selection
-            .selected
-            .iter()
-            .map(|u| bitcoin::TxIn {
-                previous_output: u.outpoint(),
-                script_sig: ScriptBuf::default(),
-                sequence: u.sequence().unwrap_or(n_sequence),
-                witness: Witness::new(),
-            })
-            .collect();
-
-        if tx.output.is_empty() {
-            // Uh oh, our transaction has no outputs.
-            // We allow this when:
-            // - We have a drain_to address and the utxos we must spend (this happens,
-            // for example, when we RBF)
-            // - We have a drain_to address and drain_wallet set
-            // Otherwise, we don't know who we should send the funds to, and how much
-            // we should send!
-            if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) {
-                if let NoChange {
-                    dust_threshold,
-                    remaining_amount,
-                    change_fee,
-                } = excess
-                {
-                    return Err(CreateTxError::InsufficientFunds {
-                        needed: *dust_threshold,
-                        available: remaining_amount.saturating_sub(*change_fee),
-                    });
-                }
-            } else {
-                return Err(CreateTxError::NoRecipients);
-            }
-        }
-
-        match excess {
-            NoChange {
-                remaining_amount, ..
-            } => fee_amount += remaining_amount,
-            Change { amount, fee } => {
-                if self.is_mine(&drain_script) {
-                    received += amount;
-                }
-                fee_amount += fee;
-
-                // create drain output
-                let drain_output = TxOut {
-                    value: Amount::from_sat(*amount),
-                    script_pubkey: drain_script,
-                };
-
-                // TODO: We should pay attention when adding a new output: this might increase
-                // the length of the "number of vouts" parameter by 2 bytes, potentially making
-                // our feerate too low
-                tx.output.push(drain_output);
-            }
-        };
-
-        // sort input/outputs according to the chosen algorithm
-        params.ordering.sort_tx(&mut tx);
-
-        let psbt = self.complete_transaction(tx, coin_selection.selected, params)?;
-        Ok(psbt)
-    }
-
-    /// Bump the fee of a transaction previously created with this wallet.
-    ///
-    /// Returns an error if the transaction is already confirmed or doesn't explicitly signal
-    /// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
-    /// pre-populated with the inputs and outputs of the original transaction.
-    ///
-    /// ## Example
-    ///
-    /// ```no_run
-    /// # // TODO: remove norun -- bumping fee seems to need the tx in the wallet database first.
-    /// # use std::str::FromStr;
-    /// # use bitcoin::*;
-    /// # use bdk::*;
-    /// # use bdk::wallet::ChangeSet;
-    /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_persist::PersistBackend;
-    /// # use anyhow::Error;
-    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-    /// # let mut wallet = doctest_wallet!();
-    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
-    /// let mut psbt = {
-    ///     let mut builder = wallet.build_tx();
-    ///     builder
-    ///         .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
-    ///         .enable_rbf();
-    ///     builder.finish()?
-    /// };
-    /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
-    /// let tx = psbt.clone().extract_tx().expect("tx");
-    /// // broadcast tx but it's taking too long to confirm so we want to bump the fee
-    /// let mut psbt =  {
-    ///     let mut builder = wallet.build_fee_bump(tx.txid())?;
-    ///     builder
-    ///         .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"));
-    ///     builder.finish()?
-    /// };
-    ///
-    /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
-    /// let fee_bumped_tx = psbt.extract_tx();
-    /// // broadcast fee_bumped_tx to replace original
-    /// # Ok::<(), anyhow::Error>(())
-    /// ```
-    // TODO: support for merging multiple transactions while bumping the fees
-    pub fn build_fee_bump(
-        &mut self,
-        txid: Txid,
-    ) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm, BumpFee>, BuildFeeBumpError> {
-        let graph = self.indexed_graph.graph();
-        let txout_index = &self.indexed_graph.index;
-        let chain_tip = self.chain.tip().block_id();
-
-        let mut tx = graph
-            .get_tx(txid)
-            .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
-            .as_ref()
-            .clone();
-
-        let pos = graph
-            .get_chain_position(&self.chain, chain_tip, txid)
-            .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?;
-        if let ChainPosition::Confirmed(_) = pos {
-            return Err(BuildFeeBumpError::TransactionConfirmed(txid));
-        }
-
-        if !tx
-            .input
-            .iter()
-            .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
-        {
-            return Err(BuildFeeBumpError::IrreplaceableTransaction(tx.txid()));
-        }
-
-        let fee = self
-            .calculate_fee(&tx)
-            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
-        let fee_rate = self
-            .calculate_fee_rate(&tx)
-            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
-
-        // remove the inputs from the tx and process them
-        let original_txin = tx.input.drain(..).collect::<Vec<_>>();
-        let original_utxos = original_txin
-            .iter()
-            .map(|txin| -> Result<_, BuildFeeBumpError> {
-                let prev_tx = graph
-                    .get_tx(txin.previous_output.txid)
-                    .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?;
-                let txout = &prev_tx.output[txin.previous_output.vout as usize];
-
-                let confirmation_time: ConfirmationTime = graph
-                    .get_chain_position(&self.chain, chain_tip, txin.previous_output.txid)
-                    .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?
-                    .cloned()
-                    .into();
-
-                let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
-                    Some((keychain, derivation_index)) => {
-                        let satisfaction_weight = self
-                            .get_descriptor_for_keychain(keychain)
-                            .max_weight_to_satisfy()
-                            .unwrap();
-                        WeightedUtxo {
-                            utxo: Utxo::Local(LocalOutput {
-                                outpoint: txin.previous_output,
-                                txout: txout.clone(),
-                                keychain,
-                                is_spent: true,
-                                derivation_index,
-                                confirmation_time,
-                            }),
-                            satisfaction_weight,
-                        }
-                    }
-                    None => {
-                        let satisfaction_weight =
-                            serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len();
-                        WeightedUtxo {
-                            utxo: Utxo::Foreign {
-                                outpoint: txin.previous_output,
-                                sequence: Some(txin.sequence),
-                                psbt_input: Box::new(psbt::Input {
-                                    witness_utxo: Some(txout.clone()),
-                                    non_witness_utxo: Some(prev_tx.as_ref().clone()),
-                                    ..Default::default()
-                                }),
-                            },
-                            satisfaction_weight,
-                        }
-                    }
-                };
-
-                Ok(weighted_utxo)
-            })
-            .collect::<Result<Vec<_>, _>>()?;
-
-        if tx.output.len() > 1 {
-            let mut change_index = None;
-            for (index, txout) in tx.output.iter().enumerate() {
-                let change_type = self.map_keychain(KeychainKind::Internal);
-                match txout_index.index_of_spk(&txout.script_pubkey) {
-                    Some((keychain, _)) if keychain == change_type => change_index = Some(index),
-                    _ => {}
-                }
-            }
-
-            if let Some(change_index) = change_index {
-                tx.output.remove(change_index);
-            }
-        }
-
-        let params = TxParams {
-            // TODO: figure out what rbf option should be?
-            version: Some(tx_builder::Version(tx.version.0)),
-            recipients: tx
-                .output
-                .into_iter()
-                .map(|txout| (txout.script_pubkey, txout.value.to_sat()))
-                .collect(),
-            utxos: original_utxos,
-            bumping_fee: Some(tx_builder::PreviousFee {
-                absolute: fee,
-                rate: fee_rate,
-            }),
-            ..Default::default()
-        };
-
-        Ok(TxBuilder {
-            wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
-            params,
-            coin_selection: DefaultCoinSelectionAlgorithm::default(),
-            phantom: core::marker::PhantomData,
-        })
-    }
-
-    /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
-    /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that has the value true if the PSBT was finalized, or false otherwise.
-    ///
-    /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
-    /// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
-    /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
-    /// in this library will.
-    ///
-    /// ## Example
-    ///
-    /// ```
-    /// # use std::str::FromStr;
-    /// # use bitcoin::*;
-    /// # use bdk::*;
-    /// # use bdk::wallet::ChangeSet;
-    /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk_persist::PersistBackend;
-    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-    /// # let mut wallet = doctest_wallet!();
-    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
-    /// let mut psbt = {
-    ///     let mut builder = wallet.build_tx();
-    ///     builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
-    ///     builder.finish()?
-    /// };
-    /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
-    /// assert!(finalized, "we should have signed all the inputs");
-    /// # Ok::<(),anyhow::Error>(())
-    pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result<bool, SignerError> {
-        // This adds all the PSBT metadata for the inputs, which will help us later figure out how
-        // to derive our keys
-        self.update_psbt_with_descriptor(psbt)
-            .map_err(SignerError::MiniscriptPsbt)?;
-
-        // If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and finalized ones)
-        // has the `non_witness_utxo`
-        if !sign_options.trust_witness_utxo
-            && psbt
-                .inputs
-                .iter()
-                .filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none())
-                .filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none())
-                .any(|i| i.non_witness_utxo.is_none())
-        {
-            return Err(SignerError::MissingNonWitnessUtxo);
-        }
-
-        // If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
-        // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot
-        if !sign_options.allow_all_sighashes
-            && !psbt.inputs.iter().all(|i| {
-                i.sighash_type.is_none()
-                    || i.sighash_type == Some(EcdsaSighashType::All.into())
-                    || i.sighash_type == Some(TapSighashType::All.into())
-                    || i.sighash_type == Some(TapSighashType::Default.into())
-            })
-        {
-            return Err(SignerError::NonStandardSighash);
-        }
-
-        for signer in self
-            .signers
-            .signers()
-            .iter()
-            .chain(self.change_signers.signers().iter())
-        {
-            signer.sign_transaction(psbt, &sign_options, &self.secp)?;
-        }
-
-        // attempt to finalize
-        if sign_options.try_finalize {
-            self.finalize_psbt(psbt, sign_options)
-        } else {
-            Ok(false)
-        }
-    }
-
-    /// Return the spending policies for the wallet's descriptor
-    pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, DescriptorError> {
-        let signers = match keychain {
-            KeychainKind::External => &self.signers,
-            KeychainKind::Internal => &self.change_signers,
-        };
-
-        match self.public_descriptor(keychain) {
-            Some(desc) => Ok(desc.extract_policy(signers, BuildSatisfaction::None, &self.secp)?),
-            None => Ok(None),
-        }
-    }
-
-    /// Return the "public" version of the wallet's descriptor, meaning a new descriptor that has
-    /// the same structure but with every secret key removed
-    ///
-    /// This can be used to build a watch-only version of a wallet
-    pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
-        self.indexed_graph
-            .index
-            .keychains()
-            .find(|(k, _)| *k == &keychain)
-            .map(|(_, d)| d)
-    }
-
-    /// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
-    /// validation and construct the respective `scriptSig` or `scriptWitness`. Please refer to
-    /// [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Input_Finalizer)
-    /// for further information.
-    ///
-    /// Returns `true` if the PSBT could be finalized, and `false` otherwise.
-    ///
-    /// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
-    pub fn finalize_psbt(
-        &self,
-        psbt: &mut Psbt,
-        sign_options: SignOptions,
-    ) -> Result<bool, SignerError> {
-        let chain_tip = self.chain.tip().block_id();
-
-        let tx = &psbt.unsigned_tx;
-        let mut finished = true;
-
-        for (n, input) in tx.input.iter().enumerate() {
-            let psbt_input = &psbt
-                .inputs
-                .get(n)
-                .ok_or(SignerError::InputIndexOutOfRange)?;
-            if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
-                continue;
-            }
-            let confirmation_height = self
-                .indexed_graph
-                .graph()
-                .get_chain_position(&self.chain, chain_tip, input.previous_output.txid)
-                .map(|chain_position| match chain_position {
-                    ChainPosition::Confirmed(a) => a.confirmation_height,
-                    ChainPosition::Unconfirmed(_) => u32::MAX,
-                });
-            let current_height = sign_options
-                .assume_height
-                .unwrap_or_else(|| self.chain.tip().height());
-
-            // - Try to derive the descriptor by looking at the txout. If it's in our database, we
-            //   know exactly which `keychain` to use, and which derivation index it is
-            // - If that fails, try to derive it by looking at the psbt input: the complete logic
-            //   is in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`,
-            //   `redeem_script` and `witness_script` to determine the right derivation
-            // - If that also fails, it will try it on the internal descriptor, if present
-            let desc = psbt
-                .get_utxo_for(n)
-                .and_then(|txout| self.get_descriptor_for_txout(&txout))
-                .or_else(|| {
-                    self.indexed_graph.index.keychains().find_map(|(_, desc)| {
-                        desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
-                    })
-                });
-
-            match desc {
-                Some(desc) => {
-                    let mut tmp_input = bitcoin::TxIn::default();
-                    match desc.satisfy(
-                        &mut tmp_input,
-                        (
-                            PsbtInputSatisfier::new(psbt, n),
-                            After::new(Some(current_height), false),
-                            Older::new(Some(current_height), confirmation_height, false),
-                        ),
-                    ) {
-                        Ok(_) => {
-                            let psbt_input = &mut psbt.inputs[n];
-                            psbt_input.final_script_sig = Some(tmp_input.script_sig);
-                            psbt_input.final_script_witness = Some(tmp_input.witness);
-                            if sign_options.remove_partial_sigs {
-                                psbt_input.partial_sigs.clear();
-                            }
-                            if sign_options.remove_taproot_extras {
-                                // We just constructed the final witness, clear these fields.
-                                psbt_input.tap_key_sig = None;
-                                psbt_input.tap_script_sigs.clear();
-                                psbt_input.tap_scripts.clear();
-                                psbt_input.tap_key_origins.clear();
-                                psbt_input.tap_internal_key = None;
-                                psbt_input.tap_merkle_root = None;
-                            }
-                        }
-                        Err(_) => finished = false,
-                    }
-                }
-                None => finished = false,
-            }
-        }
-
-        if finished && sign_options.remove_taproot_extras {
-            for output in &mut psbt.outputs {
-                output.tap_key_origins.clear();
-            }
-        }
-
-        Ok(finished)
-    }
-
-    /// Return the secp256k1 context used for all signing operations
-    pub fn secp_ctx(&self) -> &SecpCtx {
-        &self.secp
-    }
-
-    /// Returns the descriptor used to create addresses for a particular `keychain`.
-    pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
-        self.public_descriptor(self.map_keychain(keychain))
-            .expect("we mapped it to external if it doesn't exist")
-    }
-
-    /// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
-    /// Otherwise, it will return the index of the highest address it has derived.
-    pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
-        self.indexed_graph.index.last_revealed_index(&keychain)
-    }
-
-    /// The index of the next address that you would get if you were to ask the wallet for a new address
-    pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
-        let keychain = self.map_keychain(keychain);
-        self.indexed_graph
-            .index
-            .next_index(&keychain)
-            .expect("Keychain must exist (we called map_keychain)")
-            .0
-    }
-
-    /// Informs the wallet that you no longer intend to broadcast a tx that was built from it.
-    ///
-    /// This frees up the change address used when creating the tx for use in future transactions.
-    // TODO: Make this free up reserved utxos when that's implemented
-    pub fn cancel_tx(&mut self, tx: &Transaction) {
-        let txout_index = &mut self.indexed_graph.index;
-        for txout in &tx.output {
-            if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
-                // NOTE: unmark_used will **not** make something unused if it has actually been used
-                // by a tx in the tracker. It only removes the superficial marking.
-                txout_index.unmark_used(keychain, index);
-            }
-        }
-    }
-
-    fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
-        if keychain == KeychainKind::Internal
-            && self.public_descriptor(KeychainKind::Internal).is_none()
-        {
-            KeychainKind::External
-        } else {
-            keychain
-        }
-    }
-
-    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
-        let (keychain, child) = self
-            .indexed_graph
-            .index
-            .index_of_spk(&txout.script_pubkey)?;
-        let descriptor = self.get_descriptor_for_keychain(keychain);
-        descriptor.at_derivation_index(child).ok()
-    }
-
-    fn get_available_utxos(&self) -> Vec<(LocalOutput, usize)> {
-        self.list_unspent()
-            .map(|utxo| {
-                let keychain = utxo.keychain;
-                (utxo, {
-                    self.get_descriptor_for_keychain(keychain)
-                        .max_weight_to_satisfy()
-                        .unwrap()
-                })
-            })
-            .collect()
-    }
-
-    /// Given the options returns the list of utxos that must be used to form the
-    /// transaction and any further that may be used if needed.
-    fn preselect_utxos(
-        &self,
-        params: &TxParams,
-        current_height: Option<u32>,
-    ) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
-        let TxParams {
-            change_policy,
-            unspendable,
-            utxos,
-            drain_wallet,
-            manually_selected_only,
-            bumping_fee,
-            ..
-        } = params;
-
-        let manually_selected = utxos.clone();
-        // we mandate confirmed transactions if we're bumping the fee
-        let must_only_use_confirmed_tx = bumping_fee.is_some();
-        let must_use_all_available = *drain_wallet;
-
-        let chain_tip = self.chain.tip().block_id();
-        //    must_spend <- manually selected utxos
-        //    may_spend  <- all other available utxos
-        let mut may_spend = self.get_available_utxos();
-
-        may_spend.retain(|may_spend| {
-            !manually_selected
-                .iter()
-                .any(|manually_selected| manually_selected.utxo.outpoint() == may_spend.0.outpoint)
-        });
-        let mut must_spend = manually_selected;
-
-        // NOTE: we are intentionally ignoring `unspendable` here. i.e manual
-        // selection overrides unspendable.
-        if *manually_selected_only {
-            return (must_spend, vec![]);
-        }
-
-        let satisfies_confirmed = may_spend
-            .iter()
-            .map(|u| -> bool {
-                let txid = u.0.outpoint.txid;
-                let tx = match self.indexed_graph.graph().get_tx(txid) {
-                    Some(tx) => tx,
-                    None => return false,
-                };
-                let confirmation_time: ConfirmationTime = match self
-                    .indexed_graph
-                    .graph()
-                    .get_chain_position(&self.chain, chain_tip, txid)
-                {
-                    Some(chain_position) => chain_position.cloned().into(),
-                    None => return false,
-                };
-
-                // Whether the UTXO is mature and, if needed, confirmed
-                let mut spendable = true;
-                if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
-                    return false;
-                }
-                if tx.is_coinbase() {
-                    debug_assert!(
-                        confirmation_time.is_confirmed(),
-                        "coinbase must always be confirmed"
-                    );
-                    if let Some(current_height) = current_height {
-                        match confirmation_time {
-                            ConfirmationTime::Confirmed { height, .. } => {
-                                // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
-                                spendable &=
-                                    (current_height.saturating_sub(height)) >= COINBASE_MATURITY;
-                            }
-                            ConfirmationTime::Unconfirmed { .. } => spendable = false,
-                        }
-                    }
-                }
-                spendable
-            })
-            .collect::<Vec<_>>();
-
-        let mut i = 0;
-        may_spend.retain(|u| {
-            let retain = change_policy.is_satisfied_by(&u.0)
-                && !unspendable.contains(&u.0.outpoint)
-                && satisfies_confirmed[i];
-            i += 1;
-            retain
-        });
-
-        let mut may_spend = may_spend
-            .into_iter()
-            .map(|(local_utxo, satisfaction_weight)| WeightedUtxo {
-                satisfaction_weight,
-                utxo: Utxo::Local(local_utxo),
-            })
-            .collect();
-
-        if must_use_all_available {
-            must_spend.append(&mut may_spend);
-        }
-
-        (must_spend, may_spend)
-    }
-
-    fn complete_transaction(
-        &self,
-        tx: Transaction,
-        selected: Vec<Utxo>,
-        params: TxParams,
-    ) -> Result<Psbt, CreateTxError> {
-        let mut psbt = Psbt::from_unsigned_tx(tx)?;
-
-        if params.add_global_xpubs {
-            let all_xpubs = self
-                .keychains()
-                .flat_map(|(_, desc)| desc.get_extended_keys())
-                .collect::<Vec<_>>();
-
-            for xpub in all_xpubs {
-                let origin = match xpub.origin {
-                    Some(origin) => origin,
-                    None if xpub.xkey.depth == 0 => {
-                        (xpub.root_fingerprint(&self.secp), vec![].into())
-                    }
-                    _ => return Err(CreateTxError::MissingKeyOrigin(xpub.xkey.to_string())),
-                };
-
-                psbt.xpub.insert(xpub.xkey, origin);
-            }
-        }
-
-        let mut lookup_output = selected
-            .into_iter()
-            .map(|utxo| (utxo.outpoint(), utxo))
-            .collect::<HashMap<_, _>>();
-
-        // add metadata for the inputs
-        for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) {
-            let utxo = match lookup_output.remove(&input.previous_output) {
-                Some(utxo) => utxo,
-                None => continue,
-            };
-
-            match utxo {
-                Utxo::Local(utxo) => {
-                    *psbt_input =
-                        match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
-                            Ok(psbt_input) => psbt_input,
-                            Err(e) => match e {
-                                CreateTxError::UnknownUtxo => psbt::Input {
-                                    sighash_type: params.sighash,
-                                    ..psbt::Input::default()
-                                },
-                                _ => return Err(e),
-                            },
-                        }
-                }
-                Utxo::Foreign {
-                    outpoint,
-                    psbt_input: foreign_psbt_input,
-                    ..
-                } => {
-                    let is_taproot = foreign_psbt_input
-                        .witness_utxo
-                        .as_ref()
-                        .map(|txout| txout.script_pubkey.is_p2tr())
-                        .unwrap_or(false);
-                    if !is_taproot
-                        && !params.only_witness_utxo
-                        && foreign_psbt_input.non_witness_utxo.is_none()
-                    {
-                        return Err(CreateTxError::MissingNonWitnessUtxo(outpoint));
-                    }
-                    *psbt_input = *foreign_psbt_input;
-                }
-            }
-        }
-
-        self.update_psbt_with_descriptor(&mut psbt)?;
-
-        Ok(psbt)
-    }
-
-    /// get the corresponding PSBT Input for a LocalUtxo
-    pub fn get_psbt_input(
-        &self,
-        utxo: LocalOutput,
-        sighash_type: Option<psbt::PsbtSighashType>,
-        only_witness_utxo: bool,
-    ) -> Result<psbt::Input, CreateTxError> {
-        // Try to find the prev_script in our db to figure out if this is internal or external,
-        // and the derivation index
-        let (keychain, child) = self
-            .indexed_graph
-            .index
-            .index_of_spk(&utxo.txout.script_pubkey)
-            .ok_or(CreateTxError::UnknownUtxo)?;
-
-        let mut psbt_input = psbt::Input {
-            sighash_type,
-            ..psbt::Input::default()
-        };
-
-        let desc = self.get_descriptor_for_keychain(keychain);
-        let derived_descriptor = desc
-            .at_derivation_index(child)
-            .expect("child can't be hardened");
-
-        psbt_input
-            .update_with_descriptor_unchecked(&derived_descriptor)
-            .map_err(MiniscriptPsbtError::Conversion)?;
-
-        let prev_output = utxo.outpoint;
-        if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) {
-            if desc.is_witness() || desc.is_taproot() {
-                psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
-            }
-            if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
-                psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone());
-            }
-        }
-        Ok(psbt_input)
-    }
-
-    fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> Result<(), MiniscriptPsbtError> {
-        // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
-        // the input utxos and outputs
-        let utxos = (0..psbt.inputs.len())
-            .filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
-            .chain(
-                psbt.unsigned_tx
-                    .output
-                    .iter()
-                    .enumerate()
-                    .map(|(i, out)| (false, i, out.clone())),
-            )
-            .collect::<Vec<_>>();
-
-        // Try to figure out the keychain and derivation for every input and output
-        for (is_input, index, out) in utxos.into_iter() {
-            if let Some((keychain, child)) =
-                self.indexed_graph.index.index_of_spk(&out.script_pubkey)
-            {
-                let desc = self.get_descriptor_for_keychain(keychain);
-                let desc = desc
-                    .at_derivation_index(child)
-                    .expect("child can't be hardened");
-
-                if is_input {
-                    psbt.update_input_with_descriptor(index, &desc)
-                        .map_err(MiniscriptPsbtError::UtxoUpdate)?;
-                } else {
-                    psbt.update_output_with_descriptor(index, &desc)
-                        .map_err(MiniscriptPsbtError::OutputUpdate)?;
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    /// Return the checksum of the public descriptor associated to `keychain`
-    ///
-    /// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
-    pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
-        self.get_descriptor_for_keychain(keychain)
-            .to_string()
-            .split_once('#')
-            .unwrap()
-            .1
-            .to_string()
-    }
-
-    /// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
-    ///
-    /// Usually you create an `update` by interacting with some blockchain data source and inserting
-    /// transactions related to your wallet into it.
-    ///
-    /// [`commit`]: Self::commit
-    pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
-        let update = update.into();
-        let mut changeset = match update.chain {
-            Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
-            None => ChangeSet::default(),
-        };
-
-        let (_, index_changeset) = self
-            .indexed_graph
-            .index
-            .reveal_to_target_multi(&update.last_active_indices);
-        changeset.append(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
-            index_changeset,
-        )));
-        changeset.append(ChangeSet::from(
-            self.indexed_graph.apply_update(update.graph),
-        ));
-        self.persist.stage(changeset);
-        Ok(())
-    }
-
-    /// Commits all currently [`staged`] changed to the persistence backend returning and error when
-    /// this fails.
-    ///
-    /// This returns whether the `update` resulted in any changes.
-    ///
-    /// [`staged`]: Self::staged
-    pub fn commit(&mut self) -> anyhow::Result<bool> {
-        self.persist.commit().map(|c| c.is_some())
-    }
-
-    /// Returns the changes that will be committed with the next call to [`commit`].
-    ///
-    /// [`commit`]: Self::commit
-    pub fn staged(&self) -> &ChangeSet {
-        self.persist.staged()
-    }
-
-    /// Get a reference to the inner [`TxGraph`].
-    pub fn tx_graph(&self) -> &TxGraph<ConfirmationTimeHeightAnchor> {
-        self.indexed_graph.graph()
-    }
-
-    /// Get a reference to the inner [`KeychainTxOutIndex`].
-    pub fn spk_index(&self) -> &KeychainTxOutIndex<KeychainKind> {
-        &self.indexed_graph.index
-    }
-
-    /// Get a reference to the inner [`LocalChain`].
-    pub fn local_chain(&self) -> &LocalChain {
-        &self.chain
-    }
-
-    /// Introduces a `block` of `height` to the wallet, and tries to connect it to the
-    /// `prev_blockhash` of the block's header.
-    ///
-    /// This is a convenience method that is equivalent to calling [`apply_block_connected_to`]
-    /// with `prev_blockhash` and `height-1` as the `connected_to` parameter.
-    ///
-    /// [`apply_block_connected_to`]: Self::apply_block_connected_to
-    pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> {
-        let connected_to = match height.checked_sub(1) {
-            Some(prev_height) => BlockId {
-                height: prev_height,
-                hash: block.header.prev_blockhash,
-            },
-            None => BlockId {
-                height,
-                hash: block.block_hash(),
-            },
-        };
-        self.apply_block_connected_to(block, height, connected_to)
-            .map_err(|err| match err {
-                ApplyHeaderError::InconsistentBlocks => {
-                    unreachable!("connected_to is derived from the block so must be consistent")
-                }
-                ApplyHeaderError::CannotConnect(err) => err,
-            })
-    }
-
-    /// Applies relevant transactions from `block` of `height` to the wallet, and connects the
-    /// block to the internal chain.
-    ///
-    /// The `connected_to` parameter informs the wallet how this block connects to the internal
-    /// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the
-    /// internal [`TxGraph`].
-    pub fn apply_block_connected_to(
-        &mut self,
-        block: &Block,
-        height: u32,
-        connected_to: BlockId,
-    ) -> Result<(), ApplyHeaderError> {
-        let mut changeset = ChangeSet::default();
-        changeset.append(
-            self.chain
-                .apply_header_connected_to(&block.header, height, connected_to)?
-                .into(),
-        );
-        changeset.append(
-            self.indexed_graph
-                .apply_block_relevant(block, height)
-                .into(),
-        );
-        self.persist.stage(changeset);
-        Ok(())
-    }
-
-    /// Apply relevant unconfirmed transactions to the wallet.
-    ///
-    /// Transactions that are not relevant are filtered out.
-    ///
-    /// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp of
-    /// when the transaction was last seen in the mempool. This is used for conflict resolution
-    /// when there is conflicting unconfirmed transactions. The transaction with the later
-    /// `last_seen` is prioritized.
-    pub fn apply_unconfirmed_txs<'t>(
-        &mut self,
-        unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
-    ) {
-        let indexed_graph_changeset = self
-            .indexed_graph
-            .batch_insert_relevant_unconfirmed(unconfirmed_txs);
-        self.persist.stage(ChangeSet::from(indexed_graph_changeset));
-    }
-}
-
-/// Methods to construct sync/full-scan requests for spk-based chain sources.
-impl Wallet {
-    /// Create a partial [`SyncRequest`] for this wallet for all revealed spks.
-    ///
-    /// This is the first step when performing a spk-based wallet partial sync, the returned
-    /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to
-    /// start a blockchain sync with a spk based blockchain client.
-    pub fn start_sync_with_revealed_spks(&self) -> SyncRequest {
-        SyncRequest::from_chain_tip(self.chain.tip())
-            .cache_graph_txs(self.tx_graph())
-            .populate_with_revealed_spks(&self.indexed_graph.index, ..)
-    }
-
-    /// Create a [`FullScanRequest] for this wallet.
-    ///
-    /// This is the first step when performing a spk-based wallet full scan, the returned
-    /// [`FullScanRequest] collects iterators for the wallet's keychain script pub keys needed to
-    /// start a blockchain full scan with a spk based blockchain client.
-    ///
-    /// This operation is generally only used when importing or restoring a previously used wallet
-    /// in which the list of used scripts is not known.
-    pub fn start_full_scan(&self) -> FullScanRequest<KeychainKind> {
-        FullScanRequest::from_keychain_txout_index(self.chain.tip(), &self.indexed_graph.index)
-            .cache_graph_txs(self.tx_graph())
-    }
-}
-
-impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor>> for Wallet {
-    fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor> {
-        self.indexed_graph.graph()
-    }
-}
-
-/// Deterministically generate a unique name given the descriptors defining the wallet
-///
-/// Compatible with [`wallet_name_from_descriptor`]
-pub fn wallet_name_from_descriptor<T>(
-    descriptor: T,
-    change_descriptor: Option<T>,
-    network: Network,
-    secp: &SecpCtx,
-) -> Result<String, DescriptorError>
-where
-    T: IntoWalletDescriptor,
-{
-    //TODO check descriptors contains only public keys
-    let descriptor = descriptor
-        .into_wallet_descriptor(secp, network)?
-        .0
-        .to_string();
-    let mut wallet_name = calc_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
-    if let Some(change_descriptor) = change_descriptor {
-        let change_descriptor = change_descriptor
-            .into_wallet_descriptor(secp, network)?
-            .0
-            .to_string();
-        wallet_name.push_str(
-            calc_checksum(&change_descriptor[..change_descriptor.find('#').unwrap()])?.as_str(),
-        );
-    }
-
-    Ok(wallet_name)
-}
-
-fn new_local_utxo(
-    keychain: KeychainKind,
-    derivation_index: u32,
-    full_txo: FullTxOut<ConfirmationTimeHeightAnchor>,
-) -> LocalOutput {
-    LocalOutput {
-        outpoint: full_txo.outpoint,
-        txout: full_txo.txout,
-        is_spent: full_txo.spent_by.is_some(),
-        confirmation_time: full_txo.chain_position.into(),
-        keychain,
-        derivation_index,
-    }
-}
-
-fn create_signers<E: IntoWalletDescriptor>(
-    index: &mut KeychainTxOutIndex<KeychainKind>,
-    secp: &Secp256k1<All>,
-    descriptor: E,
-    change_descriptor: Option<E>,
-    network: Network,
-) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), crate::descriptor::error::Error> {
-    let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
-    let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
-    let _ = index.insert_descriptor(KeychainKind::External, descriptor);
-
-    let change_signers = match change_descriptor {
-        Some(descriptor) => {
-            let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
-            let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
-            let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
-            signers
-        }
-        None => Arc::new(SignersContainer::new()),
-    };
-
-    Ok((signers, change_signers))
-}
-
-/// Transforms a [`FeeRate`] to `f64` with unit as sat/vb.
-#[macro_export]
-#[doc(hidden)]
-macro_rules! floating_rate {
-    ($rate:expr) => {{
-        use $crate::bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
-        // sat_kwu / 250.0 -> sat_vb
-        $rate.to_sat_per_kwu() as f64 / ((1000 / WITNESS_SCALE_FACTOR) as f64)
-    }};
-}
-
-#[macro_export]
-#[doc(hidden)]
-/// Macro for getting a wallet for use in a doctest
-macro_rules! doctest_wallet {
-    () => {{
-        use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
-        use $crate::chain::{ConfirmationTime, BlockId};
-        use $crate::{KeychainKind, wallet::Wallet};
-        let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
-        let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
-
-        let mut wallet = Wallet::new_no_persist(
-            descriptor,
-            Some(change_descriptor),
-            Network::Regtest,
-        )
-        .unwrap();
-        let address = wallet.peek_address(KeychainKind::External, 0).address;
-        let tx = Transaction {
-            version: transaction::Version::ONE,
-            lock_time: absolute::LockTime::ZERO,
-            input: vec![],
-            output: vec![TxOut {
-                value: Amount::from_sat(500_000),
-                script_pubkey: address.script_pubkey(),
-            }],
-        };
-        let _ = wallet.insert_checkpoint(BlockId { height: 1_000, hash: BlockHash::all_zeros() });
-        let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed {
-            height: 500,
-            time: 50_000
-        });
-
-        wallet
-    }}
-}
diff --git a/crates/bdk/src/wallet/signer.rs b/crates/bdk/src/wallet/signer.rs
deleted file mode 100644 (file)
index 4610657..0000000
+++ /dev/null
@@ -1,1193 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Generalized signers
-//!
-//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet)
-//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function.
-//!
-//! ```
-//! # use alloc::sync::Arc;
-//! # use core::str::FromStr;
-//! # use bitcoin::secp256k1::{Secp256k1, All};
-//! # use bitcoin::*;
-//! # use bdk::signer::*;
-//! # use bdk::*;
-//! # #[derive(Debug)]
-//! # struct CustomHSM;
-//! # impl CustomHSM {
-//! #     fn hsm_sign_input(&self, _psbt: &mut Psbt, _input: usize) -> Result<(), SignerError> {
-//! #         Ok(())
-//! #     }
-//! #     fn connect() -> Self {
-//! #         CustomHSM
-//! #     }
-//! #     fn get_id(&self) -> SignerId {
-//! #         SignerId::Dummy(0)
-//! #     }
-//! # }
-//! #[derive(Debug)]
-//! struct CustomSigner {
-//!     device: CustomHSM,
-//! }
-//!
-//! impl CustomSigner {
-//!     fn connect() -> Self {
-//!         CustomSigner { device: CustomHSM::connect() }
-//!     }
-//! }
-//!
-//! impl SignerCommon for CustomSigner {
-//!     fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
-//!         self.device.get_id()
-//!     }
-//! }
-//!
-//! impl InputSigner for CustomSigner {
-//!     fn sign_input(
-//!         &self,
-//!         psbt: &mut Psbt,
-//!         input_index: usize,
-//!         _sign_options: &SignOptions,
-//!         _secp: &Secp256k1<All>,
-//!     ) -> Result<(), SignerError> {
-//!         self.device.hsm_sign_input(psbt, input_index)?;
-//!
-//!         Ok(())
-//!     }
-//! }
-//!
-//! let custom_signer = CustomSigner::connect();
-//!
-//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?;
-//! wallet.add_signer(
-//!     KeychainKind::External,
-//!     SignerOrdering(200),
-//!     Arc::new(custom_signer)
-//! );
-//!
-//! # Ok::<_, anyhow::Error>(())
-//! ```
-
-use crate::collections::BTreeMap;
-use alloc::string::String;
-use alloc::sync::Arc;
-use alloc::vec::Vec;
-use core::cmp::Ordering;
-use core::fmt;
-use core::ops::{Bound::Included, Deref};
-
-use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv};
-use bitcoin::hashes::hash160;
-use bitcoin::secp256k1::Message;
-use bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType};
-use bitcoin::{ecdsa, psbt, sighash, taproot};
-use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1};
-use bitcoin::{PrivateKey, Psbt, PublicKey};
-
-use miniscript::descriptor::{
-    Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey,
-    InnerXKey, KeyMap, SinglePriv, SinglePubKey,
-};
-use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
-
-use super::utils::SecpCtx;
-use crate::descriptor::{DescriptorMeta, XKeyUtils};
-use crate::psbt::PsbtUtils;
-use crate::wallet::error::MiniscriptPsbtError;
-
-/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
-/// multiple of them
-#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
-pub enum SignerId {
-    /// Bitcoin HASH160 (RIPEMD160 after SHA256) hash of an ECDSA public key
-    PkHash(hash160::Hash),
-    /// The fingerprint of a BIP32 extended key
-    Fingerprint(Fingerprint),
-    /// Dummy identifier
-    Dummy(u64),
-}
-
-impl From<hash160::Hash> for SignerId {
-    fn from(hash: hash160::Hash) -> SignerId {
-        SignerId::PkHash(hash)
-    }
-}
-
-impl From<Fingerprint> for SignerId {
-    fn from(fing: Fingerprint) -> SignerId {
-        SignerId::Fingerprint(fing)
-    }
-}
-
-/// Signing error
-#[derive(Debug)]
-pub enum SignerError {
-    /// The private key is missing for the required public key
-    MissingKey,
-    /// The private key in use has the right fingerprint but derives differently than expected
-    InvalidKey,
-    /// The user canceled the operation
-    UserCanceled,
-    /// Input index is out of range
-    InputIndexOutOfRange,
-    /// The `non_witness_utxo` field of the transaction is required to sign this input
-    MissingNonWitnessUtxo,
-    /// The `non_witness_utxo` specified is invalid
-    InvalidNonWitnessUtxo,
-    /// The `witness_utxo` field of the transaction is required to sign this input
-    MissingWitnessUtxo,
-    /// The `witness_script` field of the transaction is required to sign this input
-    MissingWitnessScript,
-    /// The fingerprint and derivation path are missing from the psbt input
-    MissingHdKeypath,
-    /// The psbt contains a non-`SIGHASH_ALL` sighash in one of its input and the user hasn't
-    /// explicitly allowed them
-    ///
-    /// To enable signing transactions with non-standard sighashes set
-    /// [`SignOptions::allow_all_sighashes`] to `true`.
-    NonStandardSighash,
-    /// Invalid SIGHASH for the signing context in use
-    InvalidSighash,
-    /// Error while computing the hash to sign
-    SighashError(sighash::Error),
-    /// Miniscript PSBT error
-    MiniscriptPsbt(MiniscriptPsbtError),
-    /// To be used only by external libraries implementing [`InputSigner`] or
-    /// [`TransactionSigner`], so that they can return their own custom errors, without having to
-    /// modify [`SignerError`] in BDK.
-    External(String),
-}
-
-impl From<sighash::Error> for SignerError {
-    fn from(e: sighash::Error) -> Self {
-        SignerError::SighashError(e)
-    }
-}
-
-impl fmt::Display for SignerError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::MissingKey => write!(f, "Missing private key"),
-            Self::InvalidKey => write!(f, "The private key in use has the right fingerprint but derives differently than expected"),
-            Self::UserCanceled => write!(f, "The user canceled the operation"),
-            Self::InputIndexOutOfRange => write!(f, "Input index out of range"),
-            Self::MissingNonWitnessUtxo => write!(f, "Missing non-witness UTXO"),
-            Self::InvalidNonWitnessUtxo => write!(f, "Invalid non-witness UTXO"),
-            Self::MissingWitnessUtxo => write!(f, "Missing witness UTXO"),
-            Self::MissingWitnessScript => write!(f, "Missing witness script"),
-            Self::MissingHdKeypath => write!(f, "Missing fingerprint and derivation path"),
-            Self::NonStandardSighash => write!(f, "The psbt contains a non standard sighash"),
-            Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
-            Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
-            Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
-            Self::External(err) => write!(f, "{}", err),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for SignerError {}
-
-/// Signing context
-///
-/// Used by our software signers to determine the type of signatures to make
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum SignerContext {
-    /// Legacy context
-    Legacy,
-    /// Segwit v0 context (BIP 143)
-    Segwitv0,
-    /// Taproot context (BIP 340)
-    Tap {
-        /// Whether the signer can sign for the internal key or not
-        is_internal_key: bool,
-    },
-}
-
-/// Wrapper to pair a signer with its context
-#[derive(Debug, Clone)]
-pub struct SignerWrapper<S: Sized + fmt::Debug + Clone> {
-    signer: S,
-    ctx: SignerContext,
-}
-
-impl<S: Sized + fmt::Debug + Clone> SignerWrapper<S> {
-    /// Create a wrapped signer from a signer and a context
-    pub fn new(signer: S, ctx: SignerContext) -> Self {
-        SignerWrapper { signer, ctx }
-    }
-}
-
-impl<S: Sized + fmt::Debug + Clone> Deref for SignerWrapper<S> {
-    type Target = S;
-
-    fn deref(&self) -> &Self::Target {
-        &self.signer
-    }
-}
-
-/// Common signer methods
-pub trait SignerCommon: fmt::Debug + Send + Sync {
-    /// Return the [`SignerId`] for this signer
-    ///
-    /// The [`SignerId`] can be used to lookup a signer in the [`Wallet`](crate::Wallet)'s signers map or to
-    /// compare two signers.
-    fn id(&self, secp: &SecpCtx) -> SignerId;
-
-    /// Return the secret key for the signer
-    ///
-    /// This is used internally to reconstruct the original descriptor that may contain secrets.
-    /// External signers that are meant to keep key isolated should just return `None` here (which
-    /// is the default for this method, if not overridden).
-    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
-        None
-    }
-}
-
-/// PSBT Input signer
-///
-/// This trait can be implemented to provide custom signers to the wallet. If the signer supports signing
-/// individual inputs, this trait should be implemented and BDK will provide automatically an implementation
-/// for [`TransactionSigner`].
-pub trait InputSigner: SignerCommon {
-    /// Sign a single psbt input
-    fn sign_input(
-        &self,
-        psbt: &mut Psbt,
-        input_index: usize,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError>;
-}
-
-/// PSBT signer
-///
-/// This trait can be implemented when the signer can't sign inputs individually, but signs the whole transaction
-/// at once.
-pub trait TransactionSigner: SignerCommon {
-    /// Sign all the inputs of the psbt
-    fn sign_transaction(
-        &self,
-        psbt: &mut Psbt,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError>;
-}
-
-impl<T: InputSigner> TransactionSigner for T {
-    fn sign_transaction(
-        &self,
-        psbt: &mut Psbt,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError> {
-        for input_index in 0..psbt.inputs.len() {
-            self.sign_input(psbt, input_index, sign_options, secp)?;
-        }
-
-        Ok(())
-    }
-}
-
-impl SignerCommon for SignerWrapper<DescriptorXKey<Xpriv>> {
-    fn id(&self, secp: &SecpCtx) -> SignerId {
-        SignerId::from(self.root_fingerprint(secp))
-    }
-
-    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
-        Some(DescriptorSecretKey::XPrv(self.signer.clone()))
-    }
-}
-
-impl InputSigner for SignerWrapper<DescriptorXKey<Xpriv>> {
-    fn sign_input(
-        &self,
-        psbt: &mut Psbt,
-        input_index: usize,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError> {
-        if input_index >= psbt.inputs.len() {
-            return Err(SignerError::InputIndexOutOfRange);
-        }
-
-        if psbt.inputs[input_index].final_script_sig.is_some()
-            || psbt.inputs[input_index].final_script_witness.is_some()
-        {
-            return Ok(());
-        }
-
-        let tap_key_origins = psbt.inputs[input_index]
-            .tap_key_origins
-            .iter()
-            .map(|(pk, (_, keysource))| (SinglePubKey::XOnly(*pk), keysource));
-        let (public_key, full_path) = match psbt.inputs[input_index]
-            .bip32_derivation
-            .iter()
-            .map(|(pk, keysource)| (SinglePubKey::FullKey(PublicKey::new(*pk)), keysource))
-            .chain(tap_key_origins)
-            .find_map(|(pk, keysource)| {
-                if self.matches(keysource, secp).is_some() {
-                    Some((pk, keysource.1.clone()))
-                } else {
-                    None
-                }
-            }) {
-            Some((pk, full_path)) => (pk, full_path),
-            None => return Ok(()),
-        };
-
-        let derived_key = match self.origin.clone() {
-            Some((_fingerprint, origin_path)) => {
-                let deriv_path = DerivationPath::from(
-                    &full_path.into_iter().cloned().collect::<Vec<ChildNumber>>()
-                        [origin_path.len()..],
-                );
-                self.xkey.derive_priv(secp, &deriv_path).unwrap()
-            }
-            None => self.xkey.derive_priv(secp, &full_path).unwrap(),
-        };
-
-        let computed_pk = secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key);
-        let valid_key = match public_key {
-            SinglePubKey::FullKey(pk) if pk.inner == computed_pk => true,
-            SinglePubKey::XOnly(x_only) if XOnlyPublicKey::from(computed_pk) == x_only => true,
-            _ => false,
-        };
-        if !valid_key {
-            Err(SignerError::InvalidKey)
-        } else {
-            // HD wallets imply compressed keys
-            let priv_key = PrivateKey {
-                compressed: true,
-                network: self.xkey.network,
-                inner: derived_key.private_key,
-            };
-
-            SignerWrapper::new(priv_key, self.ctx).sign_input(psbt, input_index, sign_options, secp)
-        }
-    }
-}
-
-fn multikey_to_xkeys<K: InnerXKey + Clone>(
-    multikey: DescriptorMultiXKey<K>,
-) -> Vec<DescriptorXKey<K>> {
-    multikey
-        .derivation_paths
-        .into_paths()
-        .into_iter()
-        .map(|derivation_path| DescriptorXKey {
-            origin: multikey.origin.clone(),
-            xkey: multikey.xkey.clone(),
-            derivation_path,
-            wildcard: multikey.wildcard,
-        })
-        .collect()
-}
-
-impl SignerCommon for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
-    fn id(&self, secp: &SecpCtx) -> SignerId {
-        SignerId::from(self.root_fingerprint(secp))
-    }
-
-    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
-        Some(DescriptorSecretKey::MultiXPrv(self.signer.clone()))
-    }
-}
-
-impl InputSigner for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
-    fn sign_input(
-        &self,
-        psbt: &mut Psbt,
-        input_index: usize,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError> {
-        let xkeys = multikey_to_xkeys(self.signer.clone());
-        for xkey in xkeys {
-            SignerWrapper::new(xkey, self.ctx).sign_input(psbt, input_index, sign_options, secp)?
-        }
-        Ok(())
-    }
-}
-
-impl SignerCommon for SignerWrapper<PrivateKey> {
-    fn id(&self, secp: &SecpCtx) -> SignerId {
-        SignerId::from(self.public_key(secp).to_pubkeyhash(SigType::Ecdsa))
-    }
-
-    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
-        Some(DescriptorSecretKey::Single(SinglePriv {
-            key: self.signer,
-            origin: None,
-        }))
-    }
-}
-
-impl InputSigner for SignerWrapper<PrivateKey> {
-    fn sign_input(
-        &self,
-        psbt: &mut Psbt,
-        input_index: usize,
-        sign_options: &SignOptions,
-        secp: &SecpCtx,
-    ) -> Result<(), SignerError> {
-        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
-            return Err(SignerError::InputIndexOutOfRange);
-        }
-
-        if psbt.inputs[input_index].final_script_sig.is_some()
-            || psbt.inputs[input_index].final_script_witness.is_some()
-        {
-            return Ok(());
-        }
-
-        let pubkey = PublicKey::from_private_key(secp, self);
-        let x_only_pubkey = XOnlyPublicKey::from(pubkey.inner);
-
-        if let SignerContext::Tap { is_internal_key } = self.ctx {
-            if let Some(psbt_internal_key) = psbt.inputs[input_index].tap_internal_key {
-                if is_internal_key
-                    && psbt.inputs[input_index].tap_key_sig.is_none()
-                    && sign_options.sign_with_tap_internal_key
-                    && x_only_pubkey == psbt_internal_key
-                {
-                    let (hash, hash_ty) = Tap::sighash(psbt, input_index, None)?;
-                    sign_psbt_schnorr(
-                        &self.inner,
-                        x_only_pubkey,
-                        None,
-                        &mut psbt.inputs[input_index],
-                        hash,
-                        hash_ty,
-                        secp,
-                    );
-                }
-            }
-
-            if let Some((leaf_hashes, _)) =
-                psbt.inputs[input_index].tap_key_origins.get(&x_only_pubkey)
-            {
-                let leaf_hashes = leaf_hashes
-                    .iter()
-                    .filter(|lh| {
-                        // Removing the leaves we shouldn't sign for
-                        let should_sign = match &sign_options.tap_leaves_options {
-                            TapLeavesOptions::All => true,
-                            TapLeavesOptions::Include(v) => v.contains(lh),
-                            TapLeavesOptions::Exclude(v) => !v.contains(lh),
-                            TapLeavesOptions::None => false,
-                        };
-                        // Filtering out the leaves without our key
-                        should_sign
-                            && !psbt.inputs[input_index]
-                                .tap_script_sigs
-                                .contains_key(&(x_only_pubkey, **lh))
-                    })
-                    .cloned()
-                    .collect::<Vec<_>>();
-                for lh in leaf_hashes {
-                    let (hash, hash_ty) = Tap::sighash(psbt, input_index, Some(lh))?;
-                    sign_psbt_schnorr(
-                        &self.inner,
-                        x_only_pubkey,
-                        Some(lh),
-                        &mut psbt.inputs[input_index],
-                        hash,
-                        hash_ty,
-                        secp,
-                    );
-                }
-            }
-
-            return Ok(());
-        }
-
-        if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
-            return Ok(());
-        }
-
-        let (hash, hash_ty) = match self.ctx {
-            SignerContext::Segwitv0 => {
-                let (h, t) = Segwitv0::sighash(psbt, input_index, ())?;
-                let h = h.to_raw_hash();
-                (h, t)
-            }
-            SignerContext::Legacy => {
-                let (h, t) = Legacy::sighash(psbt, input_index, ())?;
-                let h = h.to_raw_hash();
-                (h, t)
-            }
-            _ => return Ok(()), // handled above
-        };
-        sign_psbt_ecdsa(
-            &self.inner,
-            pubkey,
-            &mut psbt.inputs[input_index],
-            hash,
-            hash_ty,
-            secp,
-            sign_options.allow_grinding,
-        );
-
-        Ok(())
-    }
-}
-
-fn sign_psbt_ecdsa(
-    secret_key: &secp256k1::SecretKey,
-    pubkey: PublicKey,
-    psbt_input: &mut psbt::Input,
-    hash: impl bitcoin::hashes::Hash + bitcoin::secp256k1::ThirtyTwoByteHash,
-    hash_ty: EcdsaSighashType,
-    secp: &SecpCtx,
-    allow_grinding: bool,
-) {
-    let msg = &Message::from(hash);
-    let sig = if allow_grinding {
-        secp.sign_ecdsa_low_r(msg, secret_key)
-    } else {
-        secp.sign_ecdsa(msg, secret_key)
-    };
-    secp.verify_ecdsa(msg, &sig, &pubkey.inner)
-        .expect("invalid or corrupted ecdsa signature");
-
-    let final_signature = ecdsa::Signature { sig, hash_ty };
-    psbt_input.partial_sigs.insert(pubkey, final_signature);
-}
-
-// Calling this with `leaf_hash` = `None` will sign for key-spend
-fn sign_psbt_schnorr(
-    secret_key: &secp256k1::SecretKey,
-    pubkey: XOnlyPublicKey,
-    leaf_hash: Option<taproot::TapLeafHash>,
-    psbt_input: &mut psbt::Input,
-    hash: TapSighash,
-    hash_ty: TapSighashType,
-    secp: &SecpCtx,
-) {
-    let keypair = secp256k1::Keypair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
-    let keypair = match leaf_hash {
-        None => keypair
-            .tap_tweak(secp, psbt_input.tap_merkle_root)
-            .to_inner(),
-        Some(_) => keypair, // no tweak for script spend
-    };
-
-    let msg = &Message::from(hash);
-    let sig = secp.sign_schnorr(msg, &keypair);
-    secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair).0)
-        .expect("invalid or corrupted schnorr signature");
-
-    let final_signature = taproot::Signature { sig, hash_ty };
-
-    if let Some(lh) = leaf_hash {
-        psbt_input
-            .tap_script_sigs
-            .insert((pubkey, lh), final_signature);
-    } else {
-        psbt_input.tap_key_sig = Some(final_signature);
-    }
-}
-
-/// Defines the order in which signers are called
-///
-/// The default value is `100`. Signers with an ordering above that will be called later,
-/// and they will thus see the partial signatures added to the transaction once they get to sign
-/// themselves.
-#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
-pub struct SignerOrdering(pub usize);
-
-impl Default for SignerOrdering {
-    fn default() -> Self {
-        SignerOrdering(100)
-    }
-}
-
-#[derive(Debug, Clone)]
-struct SignersContainerKey {
-    id: SignerId,
-    ordering: SignerOrdering,
-}
-
-impl From<(SignerId, SignerOrdering)> for SignersContainerKey {
-    fn from(tuple: (SignerId, SignerOrdering)) -> Self {
-        SignersContainerKey {
-            id: tuple.0,
-            ordering: tuple.1,
-        }
-    }
-}
-
-/// Container for multiple signers
-#[derive(Debug, Default, Clone)]
-pub struct SignersContainer(BTreeMap<SignersContainerKey, Arc<dyn TransactionSigner>>);
-
-impl SignersContainer {
-    /// Create a map of public keys to secret keys
-    pub fn as_key_map(&self, secp: &SecpCtx) -> KeyMap {
-        self.0
-            .values()
-            .filter_map(|signer| signer.descriptor_secret_key())
-            .filter_map(|secret| secret.to_public(secp).ok().map(|public| (public, secret)))
-            .collect()
-    }
-
-    /// Build a new signer container from a [`KeyMap`]
-    ///
-    /// Also looks at the corresponding descriptor to determine the [`SignerContext`] to attach to
-    /// the signers
-    pub fn build(
-        keymap: KeyMap,
-        descriptor: &Descriptor<DescriptorPublicKey>,
-        secp: &SecpCtx,
-    ) -> SignersContainer {
-        let mut container = SignersContainer::new();
-
-        for (pubkey, secret) in keymap {
-            let ctx = match descriptor {
-                Descriptor::Tr(tr) => SignerContext::Tap {
-                    is_internal_key: tr.internal_key() == &pubkey,
-                },
-                _ if descriptor.is_witness() => SignerContext::Segwitv0,
-                _ => SignerContext::Legacy,
-            };
-
-            match secret {
-                DescriptorSecretKey::Single(private_key) => container.add_external(
-                    SignerId::from(
-                        private_key
-                            .key
-                            .public_key(secp)
-                            .to_pubkeyhash(SigType::Ecdsa),
-                    ),
-                    SignerOrdering::default(),
-                    Arc::new(SignerWrapper::new(private_key.key, ctx)),
-                ),
-                DescriptorSecretKey::XPrv(xprv) => container.add_external(
-                    SignerId::from(xprv.root_fingerprint(secp)),
-                    SignerOrdering::default(),
-                    Arc::new(SignerWrapper::new(xprv, ctx)),
-                ),
-                DescriptorSecretKey::MultiXPrv(xprv) => container.add_external(
-                    SignerId::from(xprv.root_fingerprint(secp)),
-                    SignerOrdering::default(),
-                    Arc::new(SignerWrapper::new(xprv, ctx)),
-                ),
-            };
-        }
-
-        container
-    }
-}
-
-impl SignersContainer {
-    /// Default constructor
-    pub fn new() -> Self {
-        SignersContainer(Default::default())
-    }
-
-    /// Adds an external signer to the container for the specified id. Optionally returns the
-    /// signer that was previously in the container, if any
-    pub fn add_external(
-        &mut self,
-        id: SignerId,
-        ordering: SignerOrdering,
-        signer: Arc<dyn TransactionSigner>,
-    ) -> Option<Arc<dyn TransactionSigner>> {
-        self.0.insert((id, ordering).into(), signer)
-    }
-
-    /// Removes a signer from the container and returns it
-    pub fn remove(
-        &mut self,
-        id: SignerId,
-        ordering: SignerOrdering,
-    ) -> Option<Arc<dyn TransactionSigner>> {
-        self.0.remove(&(id, ordering).into())
-    }
-
-    /// Returns the list of identifiers of all the signers in the container
-    pub fn ids(&self) -> Vec<&SignerId> {
-        self.0
-            .keys()
-            .map(|SignersContainerKey { id, .. }| id)
-            .collect()
-    }
-
-    /// Returns the list of signers in the container, sorted by lowest to highest `ordering`
-    pub fn signers(&self) -> Vec<&Arc<dyn TransactionSigner>> {
-        self.0.values().collect()
-    }
-
-    /// Finds the signer with lowest ordering for a given id in the container.
-    pub fn find(&self, id: SignerId) -> Option<&Arc<dyn TransactionSigner>> {
-        self.0
-            .range((
-                Included(&(id.clone(), SignerOrdering(0)).into()),
-                Included(&(id.clone(), SignerOrdering(usize::MAX)).into()),
-            ))
-            .filter(|(k, _)| k.id == id)
-            .map(|(_, v)| v)
-            .next()
-    }
-}
-
-/// Options for a software signer
-///
-/// Adjust the behavior of our software signers and the way a transaction is finalized
-#[derive(Debug, Clone)]
-pub struct SignOptions {
-    /// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
-    /// provided
-    ///
-    /// Defaults to `false` to mitigate the "SegWit bug" which should trick the wallet into
-    /// paying a fee larger than expected.
-    ///
-    /// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
-    /// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
-    /// should correctly produce a signature, at the expense of an increased trust in the creator
-    /// of the PSBT.
-    ///
-    /// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
-    pub trust_witness_utxo: bool,
-
-    /// Whether the wallet should assume a specific height has been reached when trying to finalize
-    /// a transaction
-    ///
-    /// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
-    /// timelock height has already been reached. This option allows overriding the "current height" to let the
-    /// wallet use timelocks in the future to spend a coin.
-    pub assume_height: Option<u32>,
-
-    /// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter
-    /// what its value is
-    ///
-    /// Defaults to `false` which will only allow signing using `SIGHASH_ALL`.
-    pub allow_all_sighashes: bool,
-
-    /// Whether to remove partial signatures from the PSBT inputs while finalizing PSBT.
-    ///
-    /// Defaults to `true` which will remove partial signatures during finalization.
-    pub remove_partial_sigs: bool,
-
-    /// Whether to remove taproot specific fields from the PSBT on finalization.
-    ///
-    /// For inputs this includes the taproot internal key, merkle root, and individual
-    /// scripts and signatures. For both inputs and outputs it includes key origin info.
-    ///
-    /// Defaults to `true` which will remove all of the above mentioned fields when finalizing.
-    ///
-    /// See [`BIP371`](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki) for details.
-    pub remove_taproot_extras: bool,
-
-    /// Whether to try finalizing the PSBT after the inputs are signed.
-    ///
-    /// Defaults to `true` which will try finalizing PSBT after inputs are signed.
-    pub try_finalize: bool,
-
-    /// Specifies which Taproot script-spend leaves we should sign for. This option is
-    /// ignored if we're signing a non-taproot PSBT.
-    ///
-    /// Defaults to All, i.e., the wallet will sign all the leaves it has a key for.
-    pub tap_leaves_options: TapLeavesOptions,
-
-    /// Whether we should try to sign a taproot transaction with the taproot internal key
-    /// or not. This option is ignored if we're signing a non-taproot PSBT.
-    ///
-    /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
-    pub sign_with_tap_internal_key: bool,
-
-    /// Whether we should grind ECDSA signature to ensure signing with low r
-    /// or not.
-    /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
-    pub allow_grinding: bool,
-}
-
-/// Customize which taproot script-path leaves the signer should sign.
-#[derive(Default, Debug, Clone, PartialEq, Eq)]
-pub enum TapLeavesOptions {
-    /// The signer will sign all the leaves it has a key for.
-    #[default]
-    All,
-    /// The signer won't sign leaves other than the ones specified. Note that it could still ignore
-    /// some of the specified leaves, if it doesn't have the right key to sign them.
-    Include(Vec<taproot::TapLeafHash>),
-    /// The signer won't sign the specified leaves.
-    Exclude(Vec<taproot::TapLeafHash>),
-    /// The signer won't sign any leaf.
-    None,
-}
-
-impl Default for SignOptions {
-    fn default() -> Self {
-        SignOptions {
-            trust_witness_utxo: false,
-            assume_height: None,
-            allow_all_sighashes: false,
-            remove_partial_sigs: true,
-            remove_taproot_extras: true,
-            try_finalize: true,
-            tap_leaves_options: TapLeavesOptions::default(),
-            sign_with_tap_internal_key: true,
-            allow_grinding: true,
-        }
-    }
-}
-
-pub(crate) trait ComputeSighash {
-    type Extra;
-    type Sighash;
-    type SighashType;
-
-    fn sighash(
-        psbt: &Psbt,
-        input_index: usize,
-        extra: Self::Extra,
-    ) -> Result<(Self::Sighash, Self::SighashType), SignerError>;
-}
-
-impl ComputeSighash for Legacy {
-    type Extra = ();
-    type Sighash = sighash::LegacySighash;
-    type SighashType = EcdsaSighashType;
-
-    fn sighash(
-        psbt: &Psbt,
-        input_index: usize,
-        _extra: (),
-    ) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
-        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
-            return Err(SignerError::InputIndexOutOfRange);
-        }
-
-        let psbt_input = &psbt.inputs[input_index];
-        let tx_input = &psbt.unsigned_tx.input[input_index];
-
-        let sighash = psbt_input
-            .sighash_type
-            .unwrap_or_else(|| EcdsaSighashType::All.into())
-            .ecdsa_hash_ty()
-            .map_err(|_| SignerError::InvalidSighash)?;
-        let script = match psbt_input.redeem_script {
-            Some(ref redeem_script) => redeem_script.clone(),
-            None => {
-                let non_witness_utxo = psbt_input
-                    .non_witness_utxo
-                    .as_ref()
-                    .ok_or(SignerError::MissingNonWitnessUtxo)?;
-                let prev_out = non_witness_utxo
-                    .output
-                    .get(tx_input.previous_output.vout as usize)
-                    .ok_or(SignerError::InvalidNonWitnessUtxo)?;
-
-                prev_out.script_pubkey.clone()
-            }
-        };
-
-        Ok((
-            sighash::SighashCache::new(&psbt.unsigned_tx).legacy_signature_hash(
-                input_index,
-                &script,
-                sighash.to_u32(),
-            )?,
-            sighash,
-        ))
-    }
-}
-
-impl ComputeSighash for Segwitv0 {
-    type Extra = ();
-    type Sighash = sighash::SegwitV0Sighash;
-    type SighashType = EcdsaSighashType;
-
-    fn sighash(
-        psbt: &Psbt,
-        input_index: usize,
-        _extra: (),
-    ) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
-        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
-            return Err(SignerError::InputIndexOutOfRange);
-        }
-
-        let psbt_input = &psbt.inputs[input_index];
-        let tx_input = &psbt.unsigned_tx.input[input_index];
-
-        let sighash_type = psbt_input
-            .sighash_type
-            .unwrap_or_else(|| EcdsaSighashType::All.into())
-            .ecdsa_hash_ty()
-            .map_err(|_| SignerError::InvalidSighash)?;
-
-        // Always try first with the non-witness utxo
-        let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
-            // Check the provided prev-tx
-            if prev_tx.txid() != tx_input.previous_output.txid {
-                return Err(SignerError::InvalidNonWitnessUtxo);
-            }
-
-            // The output should be present, if it's missing the `non_witness_utxo` is invalid
-            prev_tx
-                .output
-                .get(tx_input.previous_output.vout as usize)
-                .ok_or(SignerError::InvalidNonWitnessUtxo)?
-        } else if let Some(witness_utxo) = &psbt_input.witness_utxo {
-            // Fallback to the witness_utxo. If we aren't allowed to use it, signing should fail
-            // before we get to this point
-            witness_utxo
-        } else {
-            // Nothing has been provided
-            return Err(SignerError::MissingNonWitnessUtxo);
-        };
-        let value = utxo.value;
-
-        let mut sighasher = sighash::SighashCache::new(&psbt.unsigned_tx);
-
-        let sighash = match psbt_input.witness_script {
-            Some(ref witness_script) => {
-                sighasher.p2wsh_signature_hash(input_index, witness_script, value, sighash_type)?
-            }
-            None => {
-                if utxo.script_pubkey.is_p2wpkh() {
-                    sighasher.p2wpkh_signature_hash(
-                        input_index,
-                        &utxo.script_pubkey,
-                        value,
-                        sighash_type,
-                    )?
-                } else if psbt_input
-                    .redeem_script
-                    .as_ref()
-                    .map(|s| s.is_p2wpkh())
-                    .unwrap_or(false)
-                {
-                    let script_pubkey = psbt_input.redeem_script.as_ref().unwrap();
-                    sighasher.p2wpkh_signature_hash(
-                        input_index,
-                        script_pubkey,
-                        value,
-                        sighash_type,
-                    )?
-                } else {
-                    return Err(SignerError::MissingWitnessScript);
-                }
-            }
-        };
-        Ok((sighash, sighash_type))
-    }
-}
-
-impl ComputeSighash for Tap {
-    type Extra = Option<taproot::TapLeafHash>;
-    type Sighash = TapSighash;
-    type SighashType = TapSighashType;
-
-    fn sighash(
-        psbt: &Psbt,
-        input_index: usize,
-        extra: Self::Extra,
-    ) -> Result<(Self::Sighash, TapSighashType), SignerError> {
-        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
-            return Err(SignerError::InputIndexOutOfRange);
-        }
-
-        let psbt_input = &psbt.inputs[input_index];
-
-        let sighash_type = psbt_input
-            .sighash_type
-            .unwrap_or_else(|| TapSighashType::Default.into())
-            .taproot_hash_ty()
-            .map_err(|_| SignerError::InvalidSighash)?;
-        let witness_utxos = (0..psbt.inputs.len())
-            .map(|i| psbt.get_utxo_for(i))
-            .collect::<Vec<_>>();
-        let mut all_witness_utxos = vec![];
-
-        let mut cache = sighash::SighashCache::new(&psbt.unsigned_tx);
-        let is_anyone_can_pay = psbt::PsbtSighashType::from(sighash_type).to_u32() & 0x80 != 0;
-        let prevouts = if is_anyone_can_pay {
-            sighash::Prevouts::One(
-                input_index,
-                witness_utxos[input_index]
-                    .as_ref()
-                    .ok_or(SignerError::MissingWitnessUtxo)?,
-            )
-        } else if witness_utxos.iter().all(Option::is_some) {
-            all_witness_utxos.extend(witness_utxos.iter().filter_map(|x| x.as_ref()));
-            sighash::Prevouts::All(&all_witness_utxos)
-        } else {
-            return Err(SignerError::MissingWitnessUtxo);
-        };
-
-        // Assume no OP_CODESEPARATOR
-        let extra = extra.map(|leaf_hash| (leaf_hash, 0xFFFFFFFF));
-
-        Ok((
-            cache.taproot_signature_hash(input_index, &prevouts, None, extra, sighash_type)?,
-            sighash_type,
-        ))
-    }
-}
-
-impl PartialOrd for SignersContainerKey {
-    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
-        Some(self.cmp(other))
-    }
-}
-
-impl Ord for SignersContainerKey {
-    fn cmp(&self, other: &Self) -> Ordering {
-        self.ordering
-            .cmp(&other.ordering)
-            .then(self.id.cmp(&other.id))
-    }
-}
-
-impl PartialEq for SignersContainerKey {
-    fn eq(&self, other: &Self) -> bool {
-        self.id == other.id && self.ordering == other.ordering
-    }
-}
-
-impl Eq for SignersContainerKey {}
-
-#[cfg(test)]
-mod signers_container_tests {
-    use super::*;
-    use crate::descriptor;
-    use crate::descriptor::IntoWalletDescriptor;
-    use crate::keys::{DescriptorKey, IntoDescriptorKey};
-    use assert_matches::assert_matches;
-    use bitcoin::bip32;
-    use bitcoin::secp256k1::{All, Secp256k1};
-    use bitcoin::Network;
-    use core::str::FromStr;
-    use miniscript::ScriptContext;
-
-    fn is_equal(this: &Arc<dyn TransactionSigner>, that: &Arc<DummySigner>) -> bool {
-        let secp = Secp256k1::new();
-        this.id(&secp) == that.id(&secp)
-    }
-
-    // Signers added with the same ordering (like `Ordering::default`) created from `KeyMap`
-    // should be preserved and not overwritten.
-    // This happens usually when a set of signers is created from a descriptor with private keys.
-    #[test]
-    fn signers_with_same_ordering() {
-        let secp = Secp256k1::new();
-
-        let (prvkey1, _, _) = setup_keys(TPRV0_STR);
-        let (prvkey2, _, _) = setup_keys(TPRV1_STR);
-        let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap();
-        let (wallet_desc, keymap) = desc
-            .into_wallet_descriptor(&secp, Network::Testnet)
-            .unwrap();
-
-        let signers = SignersContainer::build(keymap, &wallet_desc, &secp);
-        assert_eq!(signers.ids().len(), 2);
-
-        let signers = signers.signers();
-        assert_eq!(signers.len(), 2);
-    }
-
-    #[test]
-    fn signers_sorted_by_ordering() {
-        let mut signers = SignersContainer::new();
-        let signer1 = Arc::new(DummySigner { number: 1 });
-        let signer2 = Arc::new(DummySigner { number: 2 });
-        let signer3 = Arc::new(DummySigner { number: 3 });
-
-        // Mixed order insertions verifies we are not inserting at head or tail.
-        signers.add_external(SignerId::Dummy(2), SignerOrdering(2), signer2.clone());
-        signers.add_external(SignerId::Dummy(1), SignerOrdering(1), signer1.clone());
-        signers.add_external(SignerId::Dummy(3), SignerOrdering(3), signer3.clone());
-
-        // Check that signers are sorted from lowest to highest ordering
-        let signers = signers.signers();
-
-        assert!(is_equal(signers[0], &signer1));
-        assert!(is_equal(signers[1], &signer2));
-        assert!(is_equal(signers[2], &signer3));
-    }
-
-    #[test]
-    fn find_signer_by_id() {
-        let mut signers = SignersContainer::new();
-        let signer1 = Arc::new(DummySigner { number: 1 });
-        let signer2 = Arc::new(DummySigner { number: 2 });
-        let signer3 = Arc::new(DummySigner { number: 3 });
-        let signer4 = Arc::new(DummySigner { number: 3 }); // Same ID as `signer3` but will use lower ordering.
-
-        let id1 = SignerId::Dummy(1);
-        let id2 = SignerId::Dummy(2);
-        let id3 = SignerId::Dummy(3);
-        let id_nonexistent = SignerId::Dummy(999);
-
-        signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone());
-        signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
-        signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
-
-        assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
-        assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
-        assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
-
-        // The `signer4` has the same ID as `signer3` but lower ordering.
-        // It should be found by `id3` instead of `signer3`.
-        signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
-        assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
-
-        // Can't find anything with ID that doesn't exist
-        assert_matches!(signers.find(id_nonexistent), None);
-    }
-
-    #[derive(Debug, Clone, Copy)]
-    struct DummySigner {
-        number: u64,
-    }
-
-    impl SignerCommon for DummySigner {
-        fn id(&self, _secp: &SecpCtx) -> SignerId {
-            SignerId::Dummy(self.number)
-        }
-    }
-
-    impl TransactionSigner for DummySigner {
-        fn sign_transaction(
-            &self,
-            _psbt: &mut Psbt,
-            _sign_options: &SignOptions,
-            _secp: &SecpCtx,
-        ) -> Result<(), SignerError> {
-            Ok(())
-        }
-    }
-
-    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
-    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
-
-    const PATH: &str = "m/44'/1'/0'/0";
-
-    fn setup_keys<Ctx: ScriptContext>(
-        tprv: &str,
-    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
-        let secp: Secp256k1<All> = Secp256k1::new();
-        let path = bip32::DerivationPath::from_str(PATH).unwrap();
-        let tprv = bip32::Xpriv::from_str(tprv).unwrap();
-        let tpub = bip32::Xpub::from_priv(&secp, &tprv);
-        let fingerprint = tprv.fingerprint(&secp);
-        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
-        let pubkey = (tpub, path).into_descriptor_key().unwrap();
-
-        (prvkey, pubkey, fingerprint)
-    }
-}
diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs
deleted file mode 100644 (file)
index 5c3e70d..0000000
+++ /dev/null
@@ -1,1083 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-//! Transaction builder
-//!
-//! ## Example
-//!
-//! ```
-//! # use std::str::FromStr;
-//! # use bitcoin::*;
-//! # use bdk::*;
-//! # use bdk::wallet::ChangeSet;
-//! # use bdk::wallet::error::CreateTxError;
-//! # use bdk::wallet::tx_builder::CreateTx;
-//! # use bdk_persist::PersistBackend;
-//! # use anyhow::Error;
-//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
-//! # let mut wallet = doctest_wallet!();
-//! // create a TxBuilder from a wallet
-//! let mut tx_builder = wallet.build_tx();
-//!
-//! tx_builder
-//!     // Create a transaction with one output to `to_address` of 50_000 satoshi
-//!     .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
-//!     // With a custom fee rate of 5.0 satoshi/vbyte
-//!     .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
-//!     // Only spend non-change outputs
-//!     .do_not_spend_change()
-//!     // Turn on RBF signaling
-//!     .enable_rbf();
-//! let psbt = tx_builder.finish()?;
-//! # Ok::<(), anyhow::Error>(())
-//! ```
-
-use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
-use core::cell::RefCell;
-use core::fmt;
-use core::marker::PhantomData;
-
-use bitcoin::psbt::{self, Psbt};
-use bitcoin::script::PushBytes;
-use bitcoin::{absolute, Amount, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, Txid};
-
-use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
-use super::{CreateTxError, Wallet};
-use crate::collections::{BTreeMap, HashSet};
-use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo};
-
-/// Context in which the [`TxBuilder`] is valid
-pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {}
-
-/// Marker type to indicate the [`TxBuilder`] is being used to create a new transaction (as opposed
-/// to bumping the fee of an existing one).
-#[derive(Debug, Default, Clone)]
-pub struct CreateTx;
-impl TxBuilderContext for CreateTx {}
-
-/// Marker type to indicate the [`TxBuilder`] is being used to bump the fee of an existing transaction.
-#[derive(Debug, Default, Clone)]
-pub struct BumpFee;
-impl TxBuilderContext for BumpFee {}
-
-/// A transaction builder
-///
-/// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After
-/// assigning it, you set options on it until finally calling [`finish`] to consume the builder and
-/// generate the transaction.
-///
-/// Each option setting method on `TxBuilder` takes and returns `&mut self` so you can chain calls
-/// as in the following example:
-///
-/// ```
-/// # use bdk::*;
-/// # use bdk::wallet::tx_builder::*;
-/// # use bitcoin::*;
-/// # use core::str::FromStr;
-/// # use bdk::wallet::ChangeSet;
-/// # use bdk::wallet::error::CreateTxError;
-/// # use bdk_persist::PersistBackend;
-/// # use anyhow::Error;
-/// # let mut wallet = doctest_wallet!();
-/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
-/// # let addr2 = addr1.clone();
-/// // chaining
-/// let psbt1 = {
-///     let mut builder = wallet.build_tx();
-///     builder
-///         .ordering(TxOrdering::Untouched)
-///         .add_recipient(addr1.script_pubkey(), Amount::from_sat(50_000))
-///         .add_recipient(addr2.script_pubkey(), Amount::from_sat(50_000));
-///     builder.finish()?
-/// };
-///
-/// // non-chaining
-/// let psbt2 = {
-///     let mut builder = wallet.build_tx();
-///     builder.ordering(TxOrdering::Untouched);
-///     for addr in &[addr1, addr2] {
-///         builder.add_recipient(addr.script_pubkey(), Amount::from_sat(50_000));
-///     }
-///     builder.finish()?
-/// };
-///
-/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]);
-/// # Ok::<(), anyhow::Error>(())
-/// ```
-///
-/// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`.
-/// This means it is usually best to call [`coin_selection`] on the return value of `build_tx` before assigning it.
-///
-/// For further examples see [this module](super::tx_builder)'s documentation;
-///
-/// [`build_tx`]: Wallet::build_tx
-/// [`build_fee_bump`]: Wallet::build_fee_bump
-/// [`finish`]: Self::finish
-/// [`coin_selection`]: Self::coin_selection
-#[derive(Debug)]
-pub struct TxBuilder<'a, Cs, Ctx> {
-    pub(crate) wallet: Rc<RefCell<&'a mut Wallet>>,
-    pub(crate) params: TxParams,
-    pub(crate) coin_selection: Cs,
-    pub(crate) phantom: PhantomData<Ctx>,
-}
-
-/// The parameters for transaction creation sans coin selection algorithm.
-//TODO: TxParams should eventually be exposed publicly.
-#[derive(Default, Debug, Clone)]
-pub(crate) struct TxParams {
-    pub(crate) recipients: Vec<(ScriptBuf, u64)>,
-    pub(crate) drain_wallet: bool,
-    pub(crate) drain_to: Option<ScriptBuf>,
-    pub(crate) fee_policy: Option<FeePolicy>,
-    pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
-    pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
-    pub(crate) utxos: Vec<WeightedUtxo>,
-    pub(crate) unspendable: HashSet<OutPoint>,
-    pub(crate) manually_selected_only: bool,
-    pub(crate) sighash: Option<psbt::PsbtSighashType>,
-    pub(crate) ordering: TxOrdering,
-    pub(crate) locktime: Option<absolute::LockTime>,
-    pub(crate) rbf: Option<RbfValue>,
-    pub(crate) version: Option<Version>,
-    pub(crate) change_policy: ChangeSpendPolicy,
-    pub(crate) only_witness_utxo: bool,
-    pub(crate) add_global_xpubs: bool,
-    pub(crate) include_output_redeem_witness_script: bool,
-    pub(crate) bumping_fee: Option<PreviousFee>,
-    pub(crate) current_height: Option<absolute::LockTime>,
-    pub(crate) allow_dust: bool,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub(crate) struct PreviousFee {
-    pub absolute: u64,
-    pub rate: FeeRate,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub(crate) enum FeePolicy {
-    FeeRate(FeeRate),
-    FeeAmount(u64),
-}
-
-impl Default for FeePolicy {
-    fn default() -> Self {
-        FeePolicy::FeeRate(FeeRate::BROADCAST_MIN)
-    }
-}
-
-impl<'a, Cs: Clone, Ctx> Clone for TxBuilder<'a, Cs, Ctx> {
-    fn clone(&self) -> Self {
-        TxBuilder {
-            wallet: self.wallet.clone(),
-            params: self.params.clone(),
-            coin_selection: self.coin_selection.clone(),
-            phantom: PhantomData,
-        }
-    }
-}
-
-// methods supported by both contexts, for any CoinSelectionAlgorithm
-impl<'a, Cs, Ctx> TxBuilder<'a, Cs, Ctx> {
-    /// Set a custom fee rate.
-    ///
-    /// This method sets the mining fee paid by the transaction as a rate on its size.
-    /// This means that the total fee paid is equal to `fee_rate` times the size
-    /// of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core's default
-    /// relay policy.
-    ///
-    /// Note that this is really a minimum feerate -- it's possible to
-    /// overshoot it slightly since adding a change output to drain the remaining
-    /// excess might not be viable.
-    pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
-        self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
-        self
-    }
-
-    /// Set an absolute fee
-    /// The fee_absolute method refers to the absolute transaction fee in satoshis (sats).
-    /// If anyone sets both the fee_absolute method and the fee_rate method,
-    /// the FeePolicy enum will be set by whichever method was called last,
-    /// as the FeeRate and FeeAmount are mutually exclusive.
-    ///
-    /// Note that this is really a minimum absolute fee -- it's possible to
-    /// overshoot it slightly since adding a change output to drain the remaining
-    /// excess might not be viable.
-    pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self {
-        self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount));
-        self
-    }
-
-    /// Set the policy path to use while creating the transaction for a given keychain.
-    ///
-    /// This method accepts a map where the key is the policy node id (see
-    /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of
-    /// the items that are intended to be satisfied from the policy node (see
-    /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)).
-    ///
-    /// ## Example
-    ///
-    /// An example of when the policy path is needed is the following descriptor:
-    /// `wsh(thresh(2,pk(A),sj:and_v(v:pk(B),n:older(6)),snj:and_v(v:pk(C),after(630000))))`,
-    /// derived from the miniscript policy `thresh(2,pk(A),and(pk(B),older(6)),and(pk(C),after(630000)))`.
-    /// It declares three descriptor fragments, and at the top level it uses `thresh()` to
-    /// ensure that at least two of them are satisfied. The individual fragments are:
-    ///
-    /// 1. `pk(A)`
-    /// 2. `and(pk(B),older(6))`
-    /// 3. `and(pk(C),after(630000))`
-    ///
-    /// When those conditions are combined in pairs, it's clear that the transaction needs to be created
-    /// differently depending on how the user intends to satisfy the policy afterwards:
-    ///
-    /// * If fragments `1` and `2` are used, the transaction will need to use a specific
-    ///   `n_sequence` in order to spend an `OP_CSV` branch.
-    /// * If fragments `1` and `3` are used, the transaction will need to use a specific `locktime`
-    ///   in order to spend an `OP_CLTV` branch.
-    /// * If fragments `2` and `3` are used, the transaction will need both.
-    ///
-    /// When the spending policy is represented as a tree (see
-    /// [`Wallet::policies`](super::Wallet::policies)), every node
-    /// is assigned a unique identifier that can be used in the policy path to specify which of
-    /// the node's children the user intends to satisfy: for instance, assuming the `thresh()`
-    /// root node of this example has an id of `aabbccdd`, the policy path map would look like:
-    ///
-    /// `{ "aabbccdd" => [0, 1] }`
-    ///
-    /// where the key is the node's id, and the value is a list of the children that should be
-    /// used, in no particular order.
-    ///
-    /// If a particularly complex descriptor has multiple ambiguous thresholds in its structure,
-    /// multiple entries can be added to the map, one for each node that requires an explicit path.
-    ///
-    /// ```
-    /// # use std::str::FromStr;
-    /// # use std::collections::BTreeMap;
-    /// # use bitcoin::*;
-    /// # use bdk::*;
-    /// # let to_address =
-    /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
-    ///     .unwrap()
-    ///     .assume_checked();
-    /// # let mut wallet = doctest_wallet!();
-    /// let mut path = BTreeMap::new();
-    /// path.insert("aabbccdd".to_string(), vec![0, 1]);
-    ///
-    /// let builder = wallet
-    ///     .build_tx()
-    ///     .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
-    ///     .policy_path(path, KeychainKind::External);
-    ///
-    /// # Ok::<(), anyhow::Error>(())
-    /// ```
-    pub fn policy_path(
-        &mut self,
-        policy_path: BTreeMap<String, Vec<usize>>,
-        keychain: KeychainKind,
-    ) -> &mut Self {
-        let to_update = match keychain {
-            KeychainKind::Internal => &mut self.params.internal_policy_path,
-            KeychainKind::External => &mut self.params.external_policy_path,
-        };
-
-        *to_update = Some(policy_path);
-        self
-    }
-
-    /// Add the list of outpoints to the internal list of UTXOs that **must** be spent.
-    ///
-    /// If an error occurs while adding any of the UTXOs then none of them are added and the error is returned.
-    ///
-    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
-    /// the "utxos" and the "unspendable" list, it will be spent.
-    pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> {
-        {
-            let wallet = self.wallet.borrow();
-            let utxos = outpoints
-                .iter()
-                .map(|outpoint| {
-                    wallet
-                        .get_utxo(*outpoint)
-                        .ok_or(AddUtxoError::UnknownUtxo(*outpoint))
-                })
-                .collect::<Result<Vec<_>, _>>()?;
-
-            for utxo in utxos {
-                let descriptor = wallet.get_descriptor_for_keychain(utxo.keychain);
-                let satisfaction_weight = descriptor.max_weight_to_satisfy().unwrap();
-                self.params.utxos.push(WeightedUtxo {
-                    satisfaction_weight,
-                    utxo: Utxo::Local(utxo),
-                });
-            }
-        }
-
-        Ok(self)
-    }
-
-    /// Add a utxo to the internal list of utxos that **must** be spent
-    ///
-    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
-    /// the "utxos" and the "unspendable" list, it will be spent.
-    pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, AddUtxoError> {
-        self.add_utxos(&[outpoint])
-    }
-
-    /// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
-    ///
-    /// At a minimum to add a foreign UTXO we need:
-    ///
-    /// 1. `outpoint`: To add it to the raw transaction.
-    /// 2. `psbt_input`: To know the value.
-    /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the transaction for fee calculation.
-    ///
-    /// There are several security concerns about adding foreign UTXOs that application
-    /// developers should consider. First, how do you know the value of the input is correct? If a
-    /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the
-    /// value by checking it against the transaction. If only a `witness_utxo` is provided then this
-    /// method doesn't verify the value but just takes it as a given -- it is up to you to check
-    /// that whoever sent you the `input_psbt` was not lying!
-    ///
-    /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your
-    /// application it may be important that this be known precisely. If not, a malicious
-    /// counterparty may fool you into putting in a value that is too low, giving the transaction a
-    /// lower than expected feerate. They could also fool you into putting a value that is too high
-    /// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
-    /// of course check the real input weight matches the expected weight prior to broadcasting.
-    ///
-    /// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing the
-    /// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
-    /// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`].
-    ///
-    /// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
-    ///
-    /// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a transaction
-    /// created with foreign UTXO(s) you must manually insert the corresponding TxOut(s) into the tx
-    /// graph using the [`Wallet::insert_txout`] function.
-    ///
-    /// # Errors
-    ///
-    /// This method returns errors in the following circumstances:
-    ///
-    /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
-    /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
-    ///
-    /// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this
-    /// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`]
-    /// is called.
-    ///
-    /// [`only_witness_utxo`]: Self::only_witness_utxo
-    /// [`finish`]: Self::finish
-    /// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy
-    pub fn add_foreign_utxo(
-        &mut self,
-        outpoint: OutPoint,
-        psbt_input: psbt::Input,
-        satisfaction_weight: usize,
-    ) -> Result<&mut Self, AddForeignUtxoError> {
-        self.add_foreign_utxo_with_sequence(
-            outpoint,
-            psbt_input,
-            satisfaction_weight,
-            Sequence::MAX,
-        )
-    }
-
-    /// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence value.
-    pub fn add_foreign_utxo_with_sequence(
-        &mut self,
-        outpoint: OutPoint,
-        psbt_input: psbt::Input,
-        satisfaction_weight: usize,
-        sequence: Sequence,
-    ) -> Result<&mut Self, AddForeignUtxoError> {
-        if psbt_input.witness_utxo.is_none() {
-            match psbt_input.non_witness_utxo.as_ref() {
-                Some(tx) => {
-                    if tx.txid() != outpoint.txid {
-                        return Err(AddForeignUtxoError::InvalidTxid {
-                            input_txid: tx.txid(),
-                            foreign_utxo: outpoint,
-                        });
-                    }
-                    if tx.output.len() <= outpoint.vout as usize {
-                        return Err(AddForeignUtxoError::InvalidOutpoint(outpoint));
-                    }
-                }
-                None => {
-                    return Err(AddForeignUtxoError::MissingUtxo);
-                }
-            }
-        }
-
-        self.params.utxos.push(WeightedUtxo {
-            satisfaction_weight,
-            utxo: Utxo::Foreign {
-                outpoint,
-                sequence: Some(sequence),
-                psbt_input: Box::new(psbt_input),
-            },
-        });
-
-        Ok(self)
-    }
-
-    /// Only spend utxos added by [`add_utxo`].
-    ///
-    /// The wallet will **not** add additional utxos to the transaction even if they are needed to
-    /// make the transaction valid.
-    ///
-    /// [`add_utxo`]: Self::add_utxo
-    pub fn manually_selected_only(&mut self) -> &mut Self {
-        self.params.manually_selected_only = true;
-        self
-    }
-
-    /// Replace the internal list of unspendable utxos with a new list
-    ///
-    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
-    /// have priority over these. See the docs of the two linked methods for more details.
-    pub fn unspendable(&mut self, unspendable: Vec<OutPoint>) -> &mut Self {
-        self.params.unspendable = unspendable.into_iter().collect();
-        self
-    }
-
-    /// Add a utxo to the internal list of unspendable utxos
-    ///
-    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
-    /// have priority over this. See the docs of the two linked methods for more details.
-    pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self {
-        self.params.unspendable.insert(unspendable);
-        self
-    }
-
-    /// Sign with a specific sig hash
-    ///
-    /// **Use this option very carefully**
-    pub fn sighash(&mut self, sighash: psbt::PsbtSighashType) -> &mut Self {
-        self.params.sighash = Some(sighash);
-        self
-    }
-
-    /// Choose the ordering for inputs and outputs of the transaction
-    pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self {
-        self.params.ordering = ordering;
-        self
-    }
-
-    /// Use a specific nLockTime while creating the transaction
-    ///
-    /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
-    pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self {
-        self.params.locktime = Some(locktime);
-        self
-    }
-
-    /// Build a transaction with a specific version
-    ///
-    /// The `version` should always be greater than `0` and greater than `1` if the wallet's
-    /// descriptors contain an "older" (OP_CSV) operator.
-    pub fn version(&mut self, version: i32) -> &mut Self {
-        self.params.version = Some(Version(version));
-        self
-    }
-
-    /// Do not spend change outputs
-    ///
-    /// This effectively adds all the change outputs to the "unspendable" list. See
-    /// [`TxBuilder::unspendable`].
-    pub fn do_not_spend_change(&mut self) -> &mut Self {
-        self.params.change_policy = ChangeSpendPolicy::ChangeForbidden;
-        self
-    }
-
-    /// Only spend change outputs
-    ///
-    /// This effectively adds all the non-change outputs to the "unspendable" list. See
-    /// [`TxBuilder::unspendable`].
-    pub fn only_spend_change(&mut self) -> &mut Self {
-        self.params.change_policy = ChangeSpendPolicy::OnlyChange;
-        self
-    }
-
-    /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
-    /// [`TxBuilder::only_spend_change`] for some shortcuts.
-    pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self {
-        self.params.change_policy = change_policy;
-        self
-    }
-
-    /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field when spending from
-    /// SegWit descriptors.
-    ///
-    /// This reduces the size of the PSBT, but some signers might reject them due to the lack of
-    /// the `non_witness_utxo`.
-    pub fn only_witness_utxo(&mut self) -> &mut Self {
-        self.params.only_witness_utxo = true;
-        self
-    }
-
-    /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and
-    /// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields.
-    ///
-    /// This is useful for signers which always require it, like ColdCard hardware wallets.
-    pub fn include_output_redeem_witness_script(&mut self) -> &mut Self {
-        self.params.include_output_redeem_witness_script = true;
-        self
-    }
-
-    /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external
-    /// and internal descriptors
-    ///
-    /// This is useful for offline signers that take part to a multisig. Some hardware wallets like
-    /// BitBox and ColdCard are known to require this.
-    pub fn add_global_xpubs(&mut self) -> &mut Self {
-        self.params.add_global_xpubs = true;
-        self
-    }
-
-    /// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and the change policy.
-    pub fn drain_wallet(&mut self) -> &mut Self {
-        self.params.drain_wallet = true;
-        self
-    }
-
-    /// Choose the coin selection algorithm
-    ///
-    /// Overrides the [`DefaultCoinSelectionAlgorithm`].
-    ///
-    /// Note that this function consumes the builder and returns it so it is usually best to put this as the first call on the builder.
-    pub fn coin_selection<P: CoinSelectionAlgorithm>(
-        self,
-        coin_selection: P,
-    ) -> TxBuilder<'a, P, Ctx> {
-        TxBuilder {
-            wallet: self.wallet,
-            params: self.params,
-            coin_selection,
-            phantom: PhantomData,
-        }
-    }
-
-    /// Enable signaling RBF
-    ///
-    /// This will use the default nSequence value of `0xFFFFFFFD`.
-    pub fn enable_rbf(&mut self) -> &mut Self {
-        self.params.rbf = Some(RbfValue::Default);
-        self
-    }
-
-    /// Enable signaling RBF with a specific nSequence value
-    ///
-    /// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
-    /// and the given `nsequence` is lower than the CSV value.
-    ///
-    /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
-    /// be a valid nSequence to signal RBF.
-    pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
-        self.params.rbf = Some(RbfValue::Value(nsequence));
-        self
-    }
-
-    /// Set the current blockchain height.
-    ///
-    /// This will be used to:
-    /// 1. Set the nLockTime for preventing fee sniping.
-    /// **Note**: This will be ignored if you manually specify a nlocktime using [`TxBuilder::nlocktime`].
-    /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not
-    ///    mature at `current_height`, we ignore them in the coin selection.
-    ///    If you want to create a transaction that spends immature coinbase inputs, manually
-    ///    add them using [`TxBuilder::add_utxos`].
-    ///
-    /// In both cases, if you don't provide a current height, we use the last sync height.
-    pub fn current_height(&mut self, height: u32) -> &mut Self {
-        self.params.current_height =
-            Some(absolute::LockTime::from_height(height).expect("Invalid height"));
-        self
-    }
-
-    /// 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.
-    pub fn allow_dust(&mut self, allow_dust: bool) -> &mut Self {
-        self.params.allow_dust = allow_dust;
-        self
-    }
-}
-
-impl<'a, Cs: CoinSelectionAlgorithm, Ctx> TxBuilder<'a, Cs, Ctx> {
-    /// Finish building the transaction.
-    ///
-    /// Returns a new [`Psbt`] per [`BIP174`].
-    ///
-    /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
-    pub fn finish(self) -> Result<Psbt, CreateTxError> {
-        self.wallet
-            .borrow_mut()
-            .create_tx(self.coin_selection, self.params)
-    }
-}
-
-#[derive(Debug)]
-/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`]
-pub enum AddUtxoError {
-    /// Happens when trying to spend an UTXO that is not in the internal database
-    UnknownUtxo(OutPoint),
-}
-
-impl fmt::Display for AddUtxoError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::UnknownUtxo(outpoint) => write!(
-                f,
-                "UTXO not found in the internal database for txid: {} with vout: {}",
-                outpoint.txid, outpoint.vout
-            ),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for AddUtxoError {}
-
-#[derive(Debug)]
-/// Error returned from [`TxBuilder::add_foreign_utxo`].
-pub enum AddForeignUtxoError {
-    /// Foreign utxo outpoint txid does not match PSBT input txid
-    InvalidTxid {
-        /// PSBT input txid
-        input_txid: Txid,
-        /// Foreign UTXO outpoint
-        foreign_utxo: OutPoint,
-    },
-    /// Requested outpoint doesn't exist in the tx (vout greater than available outputs)
-    InvalidOutpoint(OutPoint),
-    /// Foreign utxo missing witness_utxo or non_witness_utxo
-    MissingUtxo,
-}
-
-impl fmt::Display for AddForeignUtxoError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::InvalidTxid {
-                input_txid,
-                foreign_utxo,
-            } => write!(
-                f,
-                "Foreign UTXO outpoint txid: {} does not match PSBT input txid: {}",
-                foreign_utxo.txid, input_txid,
-            ),
-            Self::InvalidOutpoint(outpoint) => write!(
-                f,
-                "Requested outpoint doesn't exist for txid: {} with vout: {}",
-                outpoint.txid, outpoint.vout,
-            ),
-            Self::MissingUtxo => write!(f, "Foreign utxo missing witness_utxo or non_witness_utxo"),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for AddForeignUtxoError {}
-
-#[derive(Debug)]
-/// Error returned from [`TxBuilder::allow_shrinking`]
-pub enum AllowShrinkingError {
-    /// Script/PubKey was not in the original transaction
-    MissingScriptPubKey(ScriptBuf),
-}
-
-impl fmt::Display for AllowShrinkingError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Self::MissingScriptPubKey(script_buf) => write!(
-                f,
-                "Script/PubKey was not in the original transaction: {}",
-                script_buf,
-            ),
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for AllowShrinkingError {}
-
-impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
-    /// Replace the recipients already added with a new list
-    pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, Amount)>) -> &mut Self {
-        self.params.recipients = recipients
-            .into_iter()
-            .map(|(script, amount)| (script, amount.to_sat()))
-            .collect();
-        self
-    }
-
-    /// Add a recipient to the internal list
-    pub fn add_recipient(&mut self, script_pubkey: ScriptBuf, amount: Amount) -> &mut Self {
-        self.params
-            .recipients
-            .push((script_pubkey, amount.to_sat()));
-        self
-    }
-
-    /// Add data as an output, using OP_RETURN
-    pub fn add_data<T: AsRef<PushBytes>>(&mut self, data: &T) -> &mut Self {
-        let script = ScriptBuf::new_op_return(data);
-        self.add_recipient(script, Amount::ZERO);
-        self
-    }
-
-    /// Sets the address to *drain* excess coins to.
-    ///
-    /// Usually, when there are excess coins they are sent to a change address generated by the
-    /// wallet. This option replaces the usual change address with an arbitrary `script_pubkey` of
-    /// your choosing. Just as with a change output, if the drain output is not needed (the excess
-    /// coins are too small) it will not be included in the resulting transaction. The only
-    /// difference is that it is valid to use `drain_to` without setting any ordinary recipients
-    /// with [`add_recipient`] (but it is perfectly fine to add recipients as well).
-    ///
-    /// If you choose not to set any recipients, you should either provide the utxos that the
-    /// transaction should spend via [`add_utxos`], or set [`drain_wallet`] to spend all of them.
-    ///
-    /// When bumping the fees of a transaction made with this option, you probably want to
-    /// use [`allow_shrinking`] to allow this output to be reduced to pay for the extra fees.
-    ///
-    /// # Example
-    ///
-    /// `drain_to` is very useful for draining all the coins in a wallet with [`drain_wallet`] to a
-    /// single address.
-    ///
-    /// ```
-    /// # use std::str::FromStr;
-    /// # use bitcoin::*;
-    /// # use bdk::*;
-    /// # use bdk::wallet::ChangeSet;
-    /// # use bdk::wallet::error::CreateTxError;
-    /// # use bdk::wallet::tx_builder::CreateTx;
-    /// # use bdk_persist::PersistBackend;
-    /// # use anyhow::Error;
-    /// # let to_address =
-    /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
-    ///     .unwrap()
-    ///     .assume_checked();
-    /// # let mut wallet = doctest_wallet!();
-    /// let mut tx_builder = wallet.build_tx();
-    ///
-    /// tx_builder
-    ///     // Spend all outputs in this wallet.
-    ///     .drain_wallet()
-    ///     // Send the excess (which is all the coins minus the fee) to this address.
-    ///     .drain_to(to_address.script_pubkey())
-    ///     .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
-    ///     .enable_rbf();
-    /// let psbt = tx_builder.finish()?;
-    /// # Ok::<(), anyhow::Error>(())
-    /// ```
-    ///
-    /// [`allow_shrinking`]: Self::allow_shrinking
-    /// [`add_recipient`]: Self::add_recipient
-    /// [`add_utxos`]: Self::add_utxos
-    /// [`drain_wallet`]: Self::drain_wallet
-    pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self {
-        self.params.drain_to = Some(script_pubkey);
-        self
-    }
-}
-
-// methods supported only by bump_fee
-impl<'a> TxBuilder<'a, DefaultCoinSelectionAlgorithm, BumpFee> {
-    /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this
-    /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
-    /// will attempt to find a change output to shrink instead.
-    ///
-    /// **Note** that the output may shrink to below the dust limit and therefore be removed. If it is
-    /// preserved then it is currently not guaranteed to be in the same position as it was
-    /// originally.
-    ///
-    /// Returns an `Err` if `script_pubkey` can't be found among the recipients of the
-    /// transaction we are bumping.
-    pub fn allow_shrinking(
-        &mut self,
-        script_pubkey: ScriptBuf,
-    ) -> Result<&mut Self, AllowShrinkingError> {
-        match self
-            .params
-            .recipients
-            .iter()
-            .position(|(recipient_script, _)| *recipient_script == script_pubkey)
-        {
-            Some(position) => {
-                self.params.recipients.remove(position);
-                self.params.drain_to = Some(script_pubkey);
-                Ok(self)
-            }
-            None => Err(AllowShrinkingError::MissingScriptPubKey(script_pubkey)),
-        }
-    }
-}
-
-/// Ordering of the transaction's inputs and outputs
-#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
-pub enum TxOrdering {
-    /// Randomized (default)
-    #[default]
-    Shuffle,
-    /// Unchanged
-    Untouched,
-    /// BIP69 / Lexicographic
-    Bip69Lexicographic,
-}
-
-impl TxOrdering {
-    /// Sort transaction inputs and outputs by [`TxOrdering`] variant
-    pub fn sort_tx(&self, tx: &mut Transaction) {
-        match self {
-            TxOrdering::Untouched => {}
-            TxOrdering::Shuffle => {
-                use rand::seq::SliceRandom;
-                let mut rng = rand::thread_rng();
-                tx.input.shuffle(&mut rng);
-                tx.output.shuffle(&mut rng);
-            }
-            TxOrdering::Bip69Lexicographic => {
-                tx.input.sort_unstable_by_key(|txin| {
-                    (txin.previous_output.txid, txin.previous_output.vout)
-                });
-                tx.output
-                    .sort_unstable_by_key(|txout| (txout.value, txout.script_pubkey.clone()));
-            }
-        }
-    }
-}
-
-/// Transaction version
-///
-/// Has a default value of `1`
-#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
-pub(crate) struct Version(pub(crate) i32);
-
-impl Default for Version {
-    fn default() -> Self {
-        Version(1)
-    }
-}
-
-/// RBF nSequence value
-///
-/// Has a default value of `0xFFFFFFFD`
-#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
-pub(crate) enum RbfValue {
-    Default,
-    Value(Sequence),
-}
-
-impl RbfValue {
-    pub(crate) fn get_value(&self) -> Sequence {
-        match self {
-            RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
-            RbfValue::Value(v) => *v,
-        }
-    }
-}
-
-/// Policy regarding the use of change outputs when creating a transaction
-#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
-pub enum ChangeSpendPolicy {
-    /// Use both change and non-change outputs (default)
-    #[default]
-    ChangeAllowed,
-    /// Only use change outputs (see [`TxBuilder::only_spend_change`])
-    OnlyChange,
-    /// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
-    ChangeForbidden,
-}
-
-impl ChangeSpendPolicy {
-    pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool {
-        match self {
-            ChangeSpendPolicy::ChangeAllowed => true,
-            ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal,
-            ChangeSpendPolicy::ChangeForbidden => utxo.keychain == KeychainKind::External,
-        }
-    }
-}
-
-#[cfg(test)]
-mod test {
-    const ORDERING_TEST_TX: &str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\
-                                    85d1fd600f0100000000ffffffffc26f3eb7932f7acddc5ddd26602b77e75160\
-                                    79b03090a16e2c2f5485d1fd600f0000000000ffffffff571fb3e02278217852\
-                                    dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e0500000000ffffffff\
-                                    03e80300000000000002aaeee80300000000000001aa200300000000000001ff\
-                                    00000000";
-    macro_rules! ordering_test_tx {
-        () => {
-            deserialize::<bitcoin::Transaction>(&Vec::<u8>::from_hex(ORDERING_TEST_TX).unwrap())
-                .unwrap()
-        };
-    }
-
-    use bdk_chain::ConfirmationTime;
-    use bitcoin::consensus::deserialize;
-    use bitcoin::hex::FromHex;
-    use bitcoin::TxOut;
-
-    use super::*;
-
-    #[test]
-    fn test_output_ordering_default_shuffle() {
-        assert_eq!(TxOrdering::default(), TxOrdering::Shuffle);
-    }
-
-    #[test]
-    fn test_output_ordering_untouched() {
-        let original_tx = ordering_test_tx!();
-        let mut tx = original_tx.clone();
-
-        TxOrdering::Untouched.sort_tx(&mut tx);
-
-        assert_eq!(original_tx, tx);
-    }
-
-    #[test]
-    fn test_output_ordering_shuffle() {
-        let original_tx = ordering_test_tx!();
-        let mut tx = original_tx.clone();
-
-        (0..40)
-            .find(|_| {
-                TxOrdering::Shuffle.sort_tx(&mut tx);
-                original_tx.input != tx.input
-            })
-            .expect("it should have moved the inputs at least once");
-
-        let mut tx = original_tx.clone();
-        (0..40)
-            .find(|_| {
-                TxOrdering::Shuffle.sort_tx(&mut tx);
-                original_tx.output != tx.output
-            })
-            .expect("it should have moved the outputs at least once");
-    }
-
-    #[test]
-    fn test_output_ordering_bip69() {
-        use core::str::FromStr;
-
-        let original_tx = ordering_test_tx!();
-        let mut tx = original_tx;
-
-        TxOrdering::Bip69Lexicographic.sort_tx(&mut tx);
-
-        assert_eq!(
-            tx.input[0].previous_output,
-            bitcoin::OutPoint::from_str(
-                "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57:5"
-            )
-            .unwrap()
-        );
-        assert_eq!(
-            tx.input[1].previous_output,
-            bitcoin::OutPoint::from_str(
-                "0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:0"
-            )
-            .unwrap()
-        );
-        assert_eq!(
-            tx.input[2].previous_output,
-            bitcoin::OutPoint::from_str(
-                "0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:1"
-            )
-            .unwrap()
-        );
-
-        assert_eq!(tx.output[0].value.to_sat(), 800);
-        assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
-        assert_eq!(
-            tx.output[2].script_pubkey,
-            ScriptBuf::from(vec![0xAA, 0xEE])
-        );
-    }
-
-    fn get_test_utxos() -> Vec<LocalOutput> {
-        use bitcoin::hashes::Hash;
-
-        vec![
-            LocalOutput {
-                outpoint: OutPoint {
-                    txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
-                    vout: 0,
-                },
-                txout: TxOut::NULL,
-                keychain: KeychainKind::External,
-                is_spent: false,
-                confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
-                derivation_index: 0,
-            },
-            LocalOutput {
-                outpoint: OutPoint {
-                    txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
-                    vout: 1,
-                },
-                txout: TxOut::NULL,
-                keychain: KeychainKind::Internal,
-                is_spent: false,
-                confirmation_time: ConfirmationTime::Confirmed {
-                    height: 32,
-                    time: 42,
-                },
-                derivation_index: 1,
-            },
-        ]
-    }
-
-    #[test]
-    fn test_change_spend_policy_default() {
-        let change_spend_policy = ChangeSpendPolicy::default();
-        let filtered = get_test_utxos()
-            .into_iter()
-            .filter(|u| change_spend_policy.is_satisfied_by(u))
-            .count();
-
-        assert_eq!(filtered, 2);
-    }
-
-    #[test]
-    fn test_change_spend_policy_no_internal() {
-        let change_spend_policy = ChangeSpendPolicy::ChangeForbidden;
-        let filtered = get_test_utxos()
-            .into_iter()
-            .filter(|u| change_spend_policy.is_satisfied_by(u))
-            .collect::<Vec<_>>();
-
-        assert_eq!(filtered.len(), 1);
-        assert_eq!(filtered[0].keychain, KeychainKind::External);
-    }
-
-    #[test]
-    fn test_change_spend_policy_only_internal() {
-        let change_spend_policy = ChangeSpendPolicy::OnlyChange;
-        let filtered = get_test_utxos()
-            .into_iter()
-            .filter(|u| change_spend_policy.is_satisfied_by(u))
-            .collect::<Vec<_>>();
-
-        assert_eq!(filtered.len(), 1);
-        assert_eq!(filtered[0].keychain, KeychainKind::Internal);
-    }
-
-    #[test]
-    fn test_default_tx_version_1() {
-        let version = Version::default();
-        assert_eq!(version.0, 1);
-    }
-}
diff --git a/crates/bdk/src/wallet/utils.rs b/crates/bdk/src/wallet/utils.rs
deleted file mode 100644 (file)
index 208a88d..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-// Bitcoin Dev Kit
-// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
-//
-// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
-//
-// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
-// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
-// You may not use this file except in accordance with one or both of these
-// licenses.
-
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::{absolute, Script, Sequence};
-
-use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
-
-/// Trait to check if a value is below the dust limit.
-/// We are performing dust value calculation for a given script public key using rust-bitcoin to
-/// keep it compatible with network dust rate
-// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
-// instead of a <= etc.
-pub trait IsDust {
-    /// Check whether or not a value is below dust limit
-    fn is_dust(&self, script: &Script) -> bool;
-}
-
-impl IsDust for u64 {
-    fn is_dust(&self, script: &Script) -> bool {
-        *self < script.dust_value().to_sat()
-    }
-}
-
-pub struct After {
-    pub current_height: Option<u32>,
-    pub assume_height_reached: bool,
-}
-
-impl After {
-    pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After {
-        After {
-            current_height,
-            assume_height_reached,
-        }
-    }
-}
-
-pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
-    // The RBF value must enable relative timelocks
-    if !rbf.is_relative_lock_time() {
-        return false;
-    }
-
-    // Both values should be represented in the same unit (either time-based or
-    // block-height based)
-    if rbf.is_time_locked() != csv.is_time_locked() {
-        return false;
-    }
-
-    // The value should be at least `csv`
-    if rbf < csv {
-        return false;
-    }
-
-    true
-}
-
-impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
-    fn check_after(&self, n: absolute::LockTime) -> bool {
-        if let Some(current_height) = self.current_height {
-            current_height >= n.to_consensus_u32()
-        } else {
-            self.assume_height_reached
-        }
-    }
-}
-
-pub struct Older {
-    pub current_height: Option<u32>,
-    pub create_height: Option<u32>,
-    pub assume_height_reached: bool,
-}
-
-impl Older {
-    pub(crate) fn new(
-        current_height: Option<u32>,
-        create_height: Option<u32>,
-        assume_height_reached: bool,
-    ) -> Older {
-        Older {
-            current_height,
-            create_height,
-            assume_height_reached,
-        }
-    }
-}
-
-impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
-    fn check_older(&self, n: Sequence) -> bool {
-        if let Some(current_height) = self.current_height {
-            // TODO: test >= / >
-            current_height
-                >= self
-                    .create_height
-                    .unwrap_or(0)
-                    .checked_add(n.to_consensus_u32())
-                    .expect("Overflowing addition")
-        } else {
-            self.assume_height_reached
-        }
-    }
-}
-
-pub(crate) type SecpCtx = Secp256k1<All>;
-
-#[cfg(test)]
-mod test {
-    // When nSequence is lower than this flag the timelock is interpreted as block-height-based,
-    // otherwise it's time-based
-    pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
-
-    use super::{check_nsequence_rbf, IsDust};
-    use crate::bitcoin::{Address, Network, Sequence};
-    use core::str::FromStr;
-
-    #[test]
-    fn test_is_dust() {
-        let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
-            .unwrap()
-            .require_network(Network::Bitcoin)
-            .unwrap()
-            .script_pubkey();
-        assert!(script_p2pkh.is_p2pkh());
-        assert!(545.is_dust(&script_p2pkh));
-        assert!(!546.is_dust(&script_p2pkh));
-
-        let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
-            .unwrap()
-            .require_network(Network::Bitcoin)
-            .unwrap()
-            .script_pubkey();
-        assert!(script_p2wpkh.is_p2wpkh());
-        assert!(293.is_dust(&script_p2wpkh));
-        assert!(!294.is_dust(&script_p2wpkh));
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_msb_set() {
-        let result = check_nsequence_rbf(Sequence(0x80000000), Sequence(5000));
-        assert!(!result);
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_lt_csv() {
-        let result = check_nsequence_rbf(Sequence(4000), Sequence(5000));
-        assert!(!result);
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_different_unit() {
-        let result =
-            check_nsequence_rbf(Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000), Sequence(5000));
-        assert!(!result);
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_mask() {
-        let result = check_nsequence_rbf(Sequence(0x3f + 10_000), Sequence(5000));
-        assert!(result);
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_same_unit_blocks() {
-        let result = check_nsequence_rbf(Sequence(10_000), Sequence(5000));
-        assert!(result);
-    }
-
-    #[test]
-    fn test_check_nsequence_rbf_same_unit_time() {
-        let result = check_nsequence_rbf(
-            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000),
-            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000),
-        );
-        assert!(result);
-    }
-}
diff --git a/crates/bdk/tests/common.rs b/crates/bdk/tests/common.rs
deleted file mode 100644 (file)
index ec42155..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-#![allow(unused)]
-
-use bdk::{KeychainKind, LocalOutput, Wallet};
-use bdk_chain::indexed_tx_graph::Indexer;
-use bdk_chain::{BlockId, ConfirmationTime};
-use bitcoin::hashes::Hash;
-use bitcoin::{
-    transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
-    Txid,
-};
-use std::str::FromStr;
-
-// Return a fake wallet that appears to be funded for testing.
-//
-// The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
-// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-// sats are the transaction fee.
-pub fn get_funded_wallet_with_change(
-    descriptor: &str,
-    change: Option<&str>,
-) -> (Wallet, bitcoin::Txid) {
-    let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
-    let change_address = wallet.peek_address(KeychainKind::External, 0).address;
-    let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
-        .expect("address")
-        .require_network(Network::Regtest)
-        .unwrap();
-
-    let tx0 = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: bitcoin::absolute::LockTime::ZERO,
-        input: vec![TxIn {
-            previous_output: OutPoint {
-                txid: Txid::all_zeros(),
-                vout: 0,
-            },
-            script_sig: Default::default(),
-            sequence: Default::default(),
-            witness: Default::default(),
-        }],
-        output: vec![TxOut {
-            value: Amount::from_sat(76_000),
-            script_pubkey: change_address.script_pubkey(),
-        }],
-    };
-
-    let tx1 = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: bitcoin::absolute::LockTime::ZERO,
-        input: vec![TxIn {
-            previous_output: OutPoint {
-                txid: tx0.txid(),
-                vout: 0,
-            },
-            script_sig: Default::default(),
-            sequence: Default::default(),
-            witness: Default::default(),
-        }],
-        output: vec![
-            TxOut {
-                value: Amount::from_sat(50_000),
-                script_pubkey: change_address.script_pubkey(),
-            },
-            TxOut {
-                value: Amount::from_sat(25_000),
-                script_pubkey: sendto_address.script_pubkey(),
-            },
-        ],
-    };
-
-    wallet
-        .insert_checkpoint(BlockId {
-            height: 1_000,
-            hash: BlockHash::all_zeros(),
-        })
-        .unwrap();
-    wallet
-        .insert_checkpoint(BlockId {
-            height: 2_000,
-            hash: BlockHash::all_zeros(),
-        })
-        .unwrap();
-    wallet
-        .insert_tx(
-            tx0,
-            ConfirmationTime::Confirmed {
-                height: 1_000,
-                time: 100,
-            },
-        )
-        .unwrap();
-    wallet
-        .insert_tx(
-            tx1.clone(),
-            ConfirmationTime::Confirmed {
-                height: 2_000,
-                time: 200,
-            },
-        )
-        .unwrap();
-
-    (wallet, tx1.txid())
-}
-
-// Return a fake wallet that appears to be funded for testing.
-//
-// The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
-// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-// sats are the transaction fee.
-pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
-    get_funded_wallet_with_change(descriptor, None)
-}
-
-pub fn get_test_wpkh() -> &'static str {
-    "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
-}
-
-pub fn get_test_single_sig_csv() -> &'static str {
-    // and(pk(Alice),older(6))
-    "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
-}
-
-pub fn get_test_a_or_b_plus_csv() -> &'static str {
-    // or(pk(Alice),and(pk(Bob),older(144)))
-    "wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),and_v(v:pk(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(144))))"
-}
-
-pub fn get_test_single_sig_cltv() -> &'static str {
-    // and(pk(Alice),after(100000))
-    "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
-}
-
-pub fn get_test_tr_single_sig() -> &'static str {
-    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
-}
-
-pub fn get_test_tr_with_taptree() -> &'static str {
-    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-}
-
-pub fn get_test_tr_with_taptree_both_priv() -> &'static str {
-    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV)})"
-}
-
-pub fn get_test_tr_repeated_key() -> &'static str {
-    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100)),and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(200))})"
-}
-
-pub fn get_test_tr_single_sig_xprv() -> &'static str {
-    "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
-}
-
-pub fn get_test_tr_with_taptree_xprv() -> &'static str {
-    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-}
-
-pub fn get_test_tr_dup_keys() -> &'static str {
-    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-}
-
-/// Construct a new [`FeeRate`] from the given raw `sat_vb` feerate. This is
-/// useful in cases where we want to create a feerate from a `f64`, as the
-/// traditional [`FeeRate::from_sat_per_vb`] method will only accept an integer.
-///
-/// **Note** this 'quick and dirty' conversion should only be used when the input
-/// parameter has units of `satoshis/vbyte` **AND** is not expected to overflow,
-/// or else the resulting value will be inaccurate.
-pub fn feerate_unchecked(sat_vb: f64) -> FeeRate {
-    // 1 sat_vb / 4wu_vb * 1000kwu_wu = 250 sat_kwu
-    let sat_kwu = (sat_vb * 250.0).ceil() as u64;
-    FeeRate::from_sat_per_kwu(sat_kwu)
-}
diff --git a/crates/bdk/tests/psbt.rs b/crates/bdk/tests/psbt.rs
deleted file mode 100644 (file)
index 820d2d1..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-use bdk::bitcoin::{Amount, FeeRate, Psbt, TxIn};
-use bdk::{psbt, KeychainKind, SignOptions};
-use core::str::FromStr;
-mod common;
-use common::*;
-
-// from bip 174
-const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
-
-#[test]
-#[should_panic(expected = "InputIndexOutOfRange")]
-fn test_psbt_malformed_psbt_input_legacy() {
-    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let send_to = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
-    let mut psbt = builder.finish().unwrap();
-    psbt.inputs.push(psbt_bip.inputs[0].clone());
-    let options = SignOptions {
-        trust_witness_utxo: true,
-        ..Default::default()
-    };
-    let _ = wallet.sign(&mut psbt, options).unwrap();
-}
-
-#[test]
-#[should_panic(expected = "InputIndexOutOfRange")]
-fn test_psbt_malformed_psbt_input_segwit() {
-    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let send_to = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
-    let mut psbt = builder.finish().unwrap();
-    psbt.inputs.push(psbt_bip.inputs[1].clone());
-    let options = SignOptions {
-        trust_witness_utxo: true,
-        ..Default::default()
-    };
-    let _ = wallet.sign(&mut psbt, options).unwrap();
-}
-
-#[test]
-#[should_panic(expected = "InputIndexOutOfRange")]
-fn test_psbt_malformed_tx_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let send_to = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
-    let mut psbt = builder.finish().unwrap();
-    psbt.unsigned_tx.input.push(TxIn::default());
-    let options = SignOptions {
-        trust_witness_utxo: true,
-        ..Default::default()
-    };
-    let _ = wallet.sign(&mut psbt, options).unwrap();
-}
-
-#[test]
-fn test_psbt_sign_with_finalized() {
-    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let send_to = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
-    let mut psbt = builder.finish().unwrap();
-
-    // add a finalized input
-    psbt.inputs.push(psbt_bip.inputs[0].clone());
-    psbt.unsigned_tx
-        .input
-        .push(psbt_bip.unsigned_tx.input[0].clone());
-
-    let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
-}
-
-#[test]
-fn test_psbt_fee_rate_with_witness_utxo() {
-    use psbt::PsbtUtils;
-
-    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
-
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    builder.fee_rate(expected_fee_rate);
-    let mut psbt = builder.finish().unwrap();
-    let fee_amount = psbt.fee_amount();
-    assert!(fee_amount.is_some());
-
-    let unfinalized_fee_rate = psbt.fee_rate().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let finalized_fee_rate = psbt.fee_rate().unwrap();
-    assert!(finalized_fee_rate >= expected_fee_rate);
-    assert!(finalized_fee_rate < unfinalized_fee_rate);
-}
-
-#[test]
-fn test_psbt_fee_rate_with_nonwitness_utxo() {
-    use psbt::PsbtUtils;
-
-    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
-
-    let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    builder.fee_rate(expected_fee_rate);
-    let mut psbt = builder.finish().unwrap();
-    let fee_amount = psbt.fee_amount();
-    assert!(fee_amount.is_some());
-    let unfinalized_fee_rate = psbt.fee_rate().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let finalized_fee_rate = psbt.fee_rate().unwrap();
-    assert!(finalized_fee_rate >= expected_fee_rate);
-    assert!(finalized_fee_rate < unfinalized_fee_rate);
-}
-
-#[test]
-fn test_psbt_fee_rate_with_missing_txout() {
-    use psbt::PsbtUtils;
-
-    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
-
-    let (mut wpkh_wallet,  _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wpkh_wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wpkh_wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    builder.fee_rate(expected_fee_rate);
-    let mut wpkh_psbt = builder.finish().unwrap();
-
-    wpkh_psbt.inputs[0].witness_utxo = None;
-    wpkh_psbt.inputs[0].non_witness_utxo = None;
-    assert!(wpkh_psbt.fee_amount().is_none());
-    assert!(wpkh_psbt.fee_rate().is_none());
-
-    let (mut pkh_wallet,  _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = pkh_wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    builder.fee_rate(expected_fee_rate);
-    let mut pkh_psbt = builder.finish().unwrap();
-
-    pkh_psbt.inputs[0].non_witness_utxo = None;
-    assert!(pkh_psbt.fee_amount().is_none());
-    assert!(pkh_psbt.fee_rate().is_none());
-}
-
-#[test]
-fn test_psbt_multiple_internalkey_signers() {
-    use bdk::signer::{SignerContext, SignerOrdering, SignerWrapper};
-    use bdk::KeychainKind;
-    use bitcoin::key::TapTweak;
-    use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey};
-    use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
-    use bitcoin::{PrivateKey, TxOut};
-    use std::sync::Arc;
-
-    let secp = Secp256k1::new();
-    let wif = "cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG";
-    let desc = format!("tr({})", wif);
-    let prv = PrivateKey::from_wif(wif).unwrap();
-    let keypair = Keypair::from_secret_key(&secp, &prv.inner);
-
-    let (mut wallet, _) = get_funded_wallet(&desc);
-    let to_spend = wallet.get_balance().total();
-    let send_to = wallet.peek_address(KeychainKind::External, 0);
-    let mut builder = wallet.build_tx();
-    builder.drain_to(send_to.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-    let unsigned_tx = psbt.unsigned_tx.clone();
-
-    // Adds a signer for the wrong internal key, bdk should not use this key to sign
-    wallet.add_signer(
-        KeychainKind::External,
-        // A signerordering lower than 100, bdk will use this signer first
-        SignerOrdering(0),
-        Arc::new(SignerWrapper::new(
-            PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(),
-            SignerContext::Tap {
-                is_internal_key: true,
-            },
-        )),
-    );
-    let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
-    assert!(finalized);
-
-    // To verify, we need the signature, message, and pubkey
-    let witness = psbt.inputs[0].final_script_witness.as_ref().unwrap();
-    assert!(!witness.is_empty());
-    let signature = schnorr::Signature::from_slice(witness.iter().next().unwrap()).unwrap();
-
-    // the prevout we're spending
-    let prevouts = &[TxOut {
-        script_pubkey: send_to.script_pubkey(),
-        value: to_spend,
-    }];
-    let prevouts = Prevouts::All(prevouts);
-    let input_index = 0;
-    let mut sighash_cache = SighashCache::new(unsigned_tx);
-    let sighash = sighash_cache
-        .taproot_key_spend_signature_hash(input_index, &prevouts, TapSighashType::Default)
-        .unwrap();
-    let message = Message::from(sighash);
-
-    // add tweak. this was taken from `signer::sign_psbt_schnorr`
-    let keypair = keypair.tap_tweak(&secp, None).to_inner();
-    let (xonlykey, _parity) = XOnlyPublicKey::from_keypair(&keypair);
-
-    // Must verify if we used the correct key to sign
-    let verify_res = secp.verify_schnorr(&signature, &message, &xonlykey);
-    assert!(verify_res.is_ok(), "The wrong internal key was used");
-}
diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs
deleted file mode 100644 (file)
index 9da65d5..0000000
+++ /dev/null
@@ -1,3947 +0,0 @@
-use std::str::FromStr;
-
-use assert_matches::assert_matches;
-use bdk::descriptor::{calc_checksum, IntoWalletDescriptor};
-use bdk::psbt::PsbtUtils;
-use bdk::signer::{SignOptions, SignerError};
-use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection};
-use bdk::wallet::error::CreateTxError;
-use bdk::wallet::tx_builder::AddForeignUtxoError;
-use bdk::wallet::NewError;
-use bdk::wallet::{AddressInfo, Balance, Wallet};
-use bdk::KeychainKind;
-use bdk_chain::collections::BTreeMap;
-use bdk_chain::COINBASE_MATURITY;
-use bdk_chain::{BlockId, ConfirmationTime};
-use bitcoin::hashes::Hash;
-use bitcoin::key::Secp256k1;
-use bitcoin::psbt;
-use bitcoin::script::PushBytesBuf;
-use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
-use bitcoin::taproot::TapNodeHash;
-use bitcoin::{
-    absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf,
-    Sequence, Transaction, TxIn, TxOut, Txid, Weight,
-};
-
-mod common;
-use common::*;
-
-fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint {
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let tx = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: addr.script_pubkey(),
-            value: Amount::from_sat(value),
-        }],
-    };
-
-    wallet.insert_tx(tx.clone(), height).unwrap();
-
-    OutPoint {
-        txid: tx.txid(),
-        vout: 0,
-    }
-}
-
-fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
-    let latest_cp = wallet.latest_checkpoint();
-    let height = latest_cp.height();
-    let anchor = if height == 0 {
-        ConfirmationTime::Unconfirmed { last_seen: 0 }
-    } else {
-        ConfirmationTime::Confirmed { height, time: 0 }
-    };
-    receive_output(wallet, value, anchor)
-}
-
-// The satisfaction size of a P2WPKH is 112 WU =
-// 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len)
-// On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for
-// a total of 105 WU.
-// Here, we push just once for simplicity, so we have to add an extra byte for the missing
-// OP_PUSH.
-const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
-
-const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
-
-#[test]
-fn load_recovers_wallet() {
-    let temp_dir = tempfile::tempdir().expect("must create tempdir");
-    let file_path = temp_dir.path().join("store.db");
-
-    // create new wallet
-    let wallet_spk_index = {
-        let db = bdk_file_store::Store::create_new(DB_MAGIC, &file_path).expect("must create db");
-        let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet)
-            .expect("must init wallet");
-
-        wallet.reveal_next_address(KeychainKind::External).unwrap();
-        wallet.spk_index().clone()
-    };
-
-    // recover wallet
-    {
-        let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
-        let wallet = Wallet::load(db).expect("must recover wallet");
-        assert_eq!(wallet.network(), Network::Testnet);
-        assert_eq!(
-            wallet.spk_index().keychains().collect::<Vec<_>>(),
-            wallet_spk_index.keychains().collect::<Vec<_>>()
-        );
-        assert_eq!(
-            wallet.spk_index().last_revealed_indices(),
-            wallet_spk_index.last_revealed_indices()
-        );
-        let secp = Secp256k1::new();
-        assert_eq!(
-            *wallet.get_descriptor_for_keychain(KeychainKind::External),
-            get_test_tr_single_sig_xprv()
-                .into_wallet_descriptor(&secp, wallet.network())
-                .unwrap()
-                .0
-        );
-    }
-
-    // `new` can only be called on empty db
-    {
-        let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
-        let result = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet);
-        assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
-    }
-}
-
-#[test]
-fn new_or_load() {
-    let temp_dir = tempfile::tempdir().expect("must create tempdir");
-    let file_path = temp_dir.path().join("store.db");
-
-    // init wallet when non-existent
-    let wallet_keychains: BTreeMap<_, _> = {
-        let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path)
-            .expect("must create db");
-        let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
-            .expect("must init wallet");
-        wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
-    };
-
-    // wrong network
-    {
-        let db =
-            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
-        let err = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Bitcoin)
-            .expect_err("wrong network");
-        assert!(
-            matches!(
-                err,
-                bdk::wallet::NewOrLoadError::LoadedNetworkDoesNotMatch {
-                    got: Some(Network::Testnet),
-                    expected: Network::Bitcoin
-                }
-            ),
-            "err: {}",
-            err,
-        );
-    }
-
-    // wrong genesis hash
-    {
-        let exp_blockhash = BlockHash::all_zeros();
-        let got_blockhash =
-            bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash();
-
-        let db =
-            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
-        let err = Wallet::new_or_load_with_genesis_hash(
-            get_test_wpkh(),
-            None,
-            db,
-            Network::Testnet,
-            exp_blockhash,
-        )
-        .expect_err("wrong genesis hash");
-        assert!(
-            matches!(
-                err,
-                bdk::wallet::NewOrLoadError::LoadedGenesisDoesNotMatch { got, expected }
-                if got == Some(got_blockhash) && expected == exp_blockhash
-            ),
-            "err: {}",
-            err,
-        );
-    }
-
-    // wrong external descriptor
-    {
-        let exp_descriptor = get_test_tr_single_sig();
-        let got_descriptor = get_test_wpkh()
-            .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
-            .unwrap()
-            .0;
-
-        let db =
-            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
-        let err = Wallet::new_or_load(exp_descriptor, None, db, Network::Testnet)
-            .expect_err("wrong external descriptor");
-        assert!(
-            matches!(
-                err,
-                bdk::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
-                if got == &Some(got_descriptor) && keychain == KeychainKind::External
-            ),
-            "err: {}",
-            err,
-        );
-    }
-
-    // wrong internal descriptor
-    {
-        let exp_descriptor = Some(get_test_tr_single_sig());
-        let got_descriptor = None;
-
-        let db =
-            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
-        let err = Wallet::new_or_load(get_test_wpkh(), exp_descriptor, db, Network::Testnet)
-            .expect_err("wrong internal descriptor");
-        assert!(
-            matches!(
-                err,
-                bdk::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
-                if got == &got_descriptor && keychain == KeychainKind::Internal
-            ),
-            "err: {}",
-            err,
-        );
-    }
-
-    // all parameters match
-    {
-        let db =
-            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
-        let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
-            .expect("must recover wallet");
-        assert_eq!(wallet.network(), Network::Testnet);
-        assert!(wallet
-            .keychains()
-            .map(|(k, v)| (*k, v.clone()))
-            .eq(wallet_keychains));
-    }
-}
-
-#[test]
-fn test_descriptor_checksum() {
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
-    let checksum = wallet.descriptor_checksum(KeychainKind::External);
-    assert_eq!(checksum.len(), 8);
-
-    let raw_descriptor = wallet
-        .keychains()
-        .next()
-        .unwrap()
-        .1
-        .to_string()
-        .split_once('#')
-        .unwrap()
-        .0
-        .to_string();
-    assert_eq!(calc_checksum(&raw_descriptor).unwrap(), checksum);
-}
-
-#[test]
-fn test_get_funded_wallet_balance() {
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
-
-    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
-    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-    // sats are the transaction fee.
-    assert_eq!(wallet.get_balance().confirmed, Amount::from_sat(50_000));
-}
-
-#[test]
-fn test_get_funded_wallet_sent_and_received() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
-
-    let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet
-        .transactions()
-        .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node)))
-        .collect();
-    tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0));
-
-    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
-    let (sent, received) = wallet.sent_and_received(&tx);
-
-    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
-    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-    // sats are the transaction fee.
-    assert_eq!(sent.to_sat(), 76_000);
-    assert_eq!(received.to_sat(), 50_000);
-}
-
-#[test]
-fn test_get_funded_wallet_tx_fees() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
-
-    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
-    let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee");
-
-    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
-    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-    // sats are the transaction fee.
-    assert_eq!(tx_fee, 1000)
-}
-
-#[test]
-fn test_get_funded_wallet_tx_fee_rate() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
-
-    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
-    let tx_fee_rate = wallet
-        .calculate_fee_rate(&tx)
-        .expect("transaction fee rate");
-
-    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
-    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
-    // sats are the transaction fee.
-
-    // tx weight = 452 wu, as vbytes = (452 + 3) / 4 = 113
-    // fee_rate (sats per kwu) = fee / weight = 1000sat / 0.452kwu = 2212
-    // fee_rate (sats per vbyte ceil) = fee / vsize = 1000sat / 113vb = 9
-    assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2212);
-    assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9);
-}
-
-#[test]
-fn test_list_output() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
-    let txos = wallet
-        .list_output()
-        .map(|op| (op.outpoint, op))
-        .collect::<std::collections::BTreeMap<_, _>>();
-    assert_eq!(txos.len(), 2);
-    for (op, txo) in txos {
-        if op.txid == txid {
-            assert_eq!(txo.txout.value.to_sat(), 50_000);
-            assert!(!txo.is_spent);
-        } else {
-            assert_eq!(txo.txout.value.to_sat(), 76_000);
-            assert!(txo.is_spent);
-        }
-    }
-}
-
-macro_rules! assert_fee_rate {
-    ($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
-        let psbt = $psbt.clone();
-        #[allow(unused_mut)]
-        let mut tx = $psbt.clone().extract_tx().expect("failed to extract tx");
-        $(
-            $( $add_signature )*
-                for txin in &mut tx.input {
-                    txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-                }
-        )*
-
-            #[allow(unused_mut)]
-        #[allow(unused_assignments)]
-        let mut dust_change = false;
-        $(
-            $( $dust_change )*
-                dust_change = true;
-        )*
-
-            let fee_amount = psbt
-            .inputs
-            .iter()
-            .fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value.to_sat())
-            - psbt
-            .unsigned_tx
-            .output
-            .iter()
-            .fold(0, |acc, o| acc + o.value.to_sat());
-
-        assert_eq!(fee_amount, $fees);
-
-        let tx_fee_rate = (Amount::from_sat(fee_amount) / tx.weight())
-            .to_sat_per_kwu();
-        let fee_rate = $fee_rate.to_sat_per_kwu();
-        let half_default = FeeRate::BROADCAST_MIN.checked_div(2)
-            .unwrap()
-            .to_sat_per_kwu();
-
-        if !dust_change {
-            assert!(tx_fee_rate >= fee_rate && tx_fee_rate - fee_rate < half_default, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
-        } else {
-            assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
-        }
-    });
-}
-
-macro_rules! from_str {
-    ($e:expr, $t:ty) => {{
-        use core::str::FromStr;
-        <$t>::from_str($e).unwrap()
-    }};
-
-    ($e:expr) => {
-        from_str!($e, _)
-    };
-}
-
-#[test]
-#[should_panic(expected = "NoRecipients")]
-fn test_create_tx_empty_recipients() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    wallet.build_tx().finish().unwrap();
-}
-
-#[test]
-#[should_panic(expected = "NoUtxosSelected")]
-fn test_create_tx_manually_selected_empty_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .manually_selected_only();
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_version_0() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .version(0);
-    assert!(matches!(builder.finish(), Err(CreateTxError::Version0)));
-}
-
-#[test]
-fn test_create_tx_version_1_csv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .version(1);
-    assert!(matches!(builder.finish(), Err(CreateTxError::Version1Csv)));
-}
-
-#[test]
-fn test_create_tx_custom_version() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .version(42);
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.version.0, 42);
-}
-
-#[test]
-fn test_create_tx_default_locktime_is_last_sync_height() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    // Since we never synced the wallet we don't have a last_sync_height
-    // we could use to try to prevent fee sniping. We default to 0.
-    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 2_000);
-}
-
-#[test]
-fn test_create_tx_fee_sniping_locktime_last_sync() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-
-    let psbt = builder.finish().unwrap();
-
-    // If there's no current_height we're left with using the last sync height
-    assert_eq!(
-        psbt.unsigned_tx.lock_time.to_consensus_u32(),
-        wallet.latest_checkpoint().height()
-    );
-}
-
-#[test]
-fn test_create_tx_default_locktime_cltv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 100_000);
-}
-
-#[test]
-fn test_create_tx_custom_locktime() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .current_height(630_001)
-        .nlocktime(absolute::LockTime::from_height(630_000).unwrap());
-    let psbt = builder.finish().unwrap();
-
-    // When we explicitly specify a nlocktime
-    // we don't try any fee sniping prevention trick
-    // (we ignore the current_height)
-    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000);
-}
-
-#[test]
-fn test_create_tx_custom_locktime_compatible_with_cltv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .nlocktime(absolute::LockTime::from_height(630_000).unwrap());
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000);
-}
-
-#[test]
-fn test_create_tx_custom_locktime_incompatible_with_cltv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .nlocktime(absolute::LockTime::from_height(50000).unwrap());
-    assert!(matches!(builder.finish(),
-        Err(CreateTxError::LockTime { requested, required })
-        if requested.to_consensus_u32() == 50_000 && required.to_consensus_u32() == 100_000));
-}
-
-#[test]
-fn test_create_tx_no_rbf_csv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
-}
-
-#[test]
-fn test_create_tx_with_default_rbf_csv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    // When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
-    // It will be set to the OP_CSV value, in this case 6
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
-}
-
-#[test]
-fn test_create_tx_with_custom_rbf_csv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf_with_sequence(Sequence(3));
-    assert!(matches!(builder.finish(),
-        Err(CreateTxError::RbfSequenceCsv { rbf, csv })
-        if rbf.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6));
-}
-
-#[test]
-fn test_create_tx_no_rbf_cltv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-}
-
-#[test]
-fn test_create_tx_invalid_rbf_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
-    assert!(matches!(builder.finish(), Err(CreateTxError::RbfSequence)));
-}
-
-#[test]
-fn test_create_tx_custom_rbf_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf_with_sequence(Sequence(0xDEADBEEF));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF));
-}
-
-#[test]
-fn test_create_tx_default_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-}
-
-#[test]
-fn test_create_tx_change_policy_no_internal() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .do_not_spend_change();
-    assert!(matches!(
-        builder.finish(),
-        Err(CreateTxError::ChangePolicyDescriptor)
-    ));
-}
-
-macro_rules! check_fee {
-    ($wallet:expr, $psbt: expr) => {{
-        let tx = $psbt.clone().extract_tx().expect("failed to extract tx");
-        let tx_fee = $wallet.calculate_fee(&tx).ok();
-        assert_eq!(tx_fee, $psbt.fee_amount());
-        tx_fee
-    }};
-}
-
-#[test]
-fn test_create_tx_drain_wallet_and_drain_to() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.unsigned_tx.output.len(), 1);
-    assert_eq!(
-        psbt.unsigned_tx.output[0].value.to_sat(),
-        50_000 - fee.unwrap_or(0)
-    );
-}
-
-#[test]
-fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
-        .unwrap()
-        .assume_checked();
-    let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000))
-        .drain_to(drain_addr.script_pubkey())
-        .drain_wallet();
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-    let outputs = psbt.unsigned_tx.output;
-
-    assert_eq!(outputs.len(), 2);
-    let main_output = outputs
-        .iter()
-        .find(|x| x.script_pubkey == addr.script_pubkey())
-        .unwrap();
-    let drain_output = outputs
-        .iter()
-        .find(|x| x.script_pubkey == drain_addr.script_pubkey())
-        .unwrap();
-    assert_eq!(main_output.value.to_sat(), 20_000,);
-    assert_eq!(drain_output.value.to_sat(), 30_000 - fee.unwrap_or(0));
-}
-
-#[test]
-fn test_create_tx_drain_to_and_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .add_utxos(&utxos)
-        .unwrap();
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.unsigned_tx.output.len(), 1);
-    assert_eq!(
-        psbt.unsigned_tx.output[0].value.to_sat(),
-        50_000 - fee.unwrap_or(0)
-    );
-}
-
-#[test]
-#[should_panic(expected = "NoRecipients")]
-fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(drain_addr.script_pubkey());
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_default_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::BROADCAST_MIN, @add_signature);
-}
-
-#[test]
-fn test_create_tx_custom_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature);
-}
-
-#[test]
-fn test_create_tx_absolute_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_absolute(100);
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(fee.unwrap_or(0), 100);
-    assert_eq!(psbt.unsigned_tx.output.len(), 1);
-    assert_eq!(
-        psbt.unsigned_tx.output[0].value.to_sat(),
-        50_000 - fee.unwrap_or(0)
-    );
-}
-
-#[test]
-fn test_create_tx_absolute_zero_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_absolute(0);
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(fee.unwrap_or(0), 0);
-    assert_eq!(psbt.unsigned_tx.output.len(), 1);
-    assert_eq!(
-        psbt.unsigned_tx.output[0].value.to_sat(),
-        50_000 - fee.unwrap_or(0)
-    );
-}
-
-#[test]
-#[should_panic(expected = "InsufficientFunds")]
-fn test_create_tx_absolute_high_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_absolute(60_000);
-    let _ = builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_add_change() {
-    use bdk::wallet::tx_builder::TxOrdering;
-
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .ordering(TxOrdering::Untouched);
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.unsigned_tx.output.len(), 2);
-    assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 25_000);
-    assert_eq!(
-        psbt.unsigned_tx.output[1].value.to_sat(),
-        25_000 - fee.unwrap_or(0)
-    );
-}
-
-#[test]
-fn test_create_tx_skip_change_dust() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800));
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.unsigned_tx.output.len(), 1);
-    assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800);
-    assert_eq!(fee.unwrap_or(0), 200);
-}
-
-#[test]
-#[should_panic(expected = "InsufficientFunds")]
-fn test_create_tx_drain_to_dust_amount() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    // very high fee rate, so that the only output would be below dust
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(454));
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_ordering_respected() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(10_000))
-        .ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic);
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.unsigned_tx.output.len(), 3);
-    assert_eq!(
-        psbt.unsigned_tx.output[0].value.to_sat(),
-        10_000 - fee.unwrap_or(0)
-    );
-    assert_eq!(psbt.unsigned_tx.output[1].value.to_sat(), 10_000);
-    assert_eq!(psbt.unsigned_tx.output[2].value.to_sat(), 30_000);
-}
-
-#[test]
-fn test_create_tx_default_sighash() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.inputs[0].sighash_type, None);
-}
-
-#[test]
-fn test_create_tx_custom_sighash() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .sighash(EcdsaSighashType::Single.into());
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(
-        psbt.inputs[0].sighash_type,
-        Some(EcdsaSighashType::Single.into())
-    );
-}
-
-#[test]
-fn test_create_tx_input_hd_keypaths() {
-    use bitcoin::bip32::{DerivationPath, Fingerprint};
-    use core::str::FromStr;
-
-    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1);
-    assert_eq!(
-        psbt.inputs[0].bip32_derivation.values().next().unwrap(),
-        &(
-            Fingerprint::from_str("d34db33f").unwrap(),
-            DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap()
-        )
-    );
-}
-
-#[test]
-fn test_create_tx_output_hd_keypaths() {
-    use bitcoin::bip32::{DerivationPath, Fingerprint};
-    use core::str::FromStr;
-
-    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
-    let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index);
-    assert_eq!(
-        psbt.outputs[0].bip32_derivation.values().next().unwrap(),
-        &(
-            Fingerprint::from_str("d34db33f").unwrap(),
-            DerivationPath::from_str(&expected_derivation_path).unwrap()
-        )
-    );
-}
-
-#[test]
-fn test_create_tx_set_redeem_script_p2sh() {
-    use bitcoin::hex::FromHex;
-
-    let (mut wallet, _) =
-        get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(
-        psbt.inputs[0].redeem_script,
-        Some(ScriptBuf::from(
-            Vec::<u8>::from_hex(
-                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
-            )
-            .unwrap()
-        ))
-    );
-    assert_eq!(psbt.inputs[0].witness_script, None);
-}
-
-#[test]
-fn test_create_tx_set_witness_script_p2wsh() {
-    use bitcoin::hex::FromHex;
-
-    let (mut wallet, _) =
-        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.inputs[0].redeem_script, None);
-    assert_eq!(
-        psbt.inputs[0].witness_script,
-        Some(ScriptBuf::from(
-            Vec::<u8>::from_hex(
-                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
-            )
-            .unwrap()
-        ))
-    );
-}
-
-#[test]
-fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
-    let (mut wallet, _) =
-        get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    let script = ScriptBuf::from_hex(
-        "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac",
-    )
-    .unwrap();
-
-    assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_p2wsh()));
-    assert_eq!(psbt.inputs[0].witness_script, Some(script));
-}
-
-#[test]
-fn test_create_tx_non_witness_utxo() {
-    let (mut wallet, _) =
-        get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert!(psbt.inputs[0].non_witness_utxo.is_some());
-    assert!(psbt.inputs[0].witness_utxo.is_none());
-}
-
-#[test]
-fn test_create_tx_only_witness_utxo() {
-    let (mut wallet, _) =
-        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .only_witness_utxo()
-        .drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert!(psbt.inputs[0].non_witness_utxo.is_none());
-    assert!(psbt.inputs[0].witness_utxo.is_some());
-}
-
-#[test]
-fn test_create_tx_shwpkh_has_witness_utxo() {
-    let (mut wallet, _) =
-        get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert!(psbt.inputs[0].witness_utxo.is_some());
-}
-
-#[test]
-fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
-    let (mut wallet, _) =
-        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert!(psbt.inputs[0].non_witness_utxo.is_some());
-    assert!(psbt.inputs[0].witness_utxo.is_some());
-}
-
-#[test]
-fn test_create_tx_add_utxo() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let small_output_tx = Transaction {
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-        version: transaction::Version::non_standard(0),
-        lock_time: absolute::LockTime::ZERO,
-    };
-    wallet
-        .insert_tx(
-            small_output_tx.clone(),
-            ConfirmationTime::Unconfirmed { last_seen: 0 },
-        )
-        .unwrap();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .add_utxo(OutPoint {
-            txid: small_output_tx.txid(),
-            vout: 0,
-        })
-        .unwrap();
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-
-    assert_eq!(
-        psbt.unsigned_tx.input.len(),
-        2,
-        "should add an additional input since 25_000 < 30_000"
-    );
-    assert_eq!(
-        sent_received.0,
-        Amount::from_sat(75_000),
-        "total should be sum of both inputs"
-    );
-}
-
-#[test]
-#[should_panic(expected = "InsufficientFunds")]
-fn test_create_tx_manually_selected_insufficient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let small_output_tx = Transaction {
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-        version: transaction::Version::non_standard(0),
-        lock_time: absolute::LockTime::ZERO,
-    };
-
-    wallet
-        .insert_tx(
-            small_output_tx.clone(),
-            ConfirmationTime::Unconfirmed { last_seen: 0 },
-        )
-        .unwrap();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .add_utxo(OutPoint {
-            txid: small_output_tx.txid(),
-            vout: 0,
-        })
-        .unwrap()
-        .manually_selected_only();
-    builder.finish().unwrap();
-}
-
-#[test]
-#[should_panic(expected = "SpendingPolicyRequired(External)")]
-fn test_create_tx_policy_path_required() {
-    let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000));
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_policy_path_no_csv() {
-    let descriptors = get_test_wpkh();
-    let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap();
-
-    let tx = Transaction {
-        version: transaction::Version::non_standard(0),
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(50_000),
-        }],
-    };
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
-    let root_id = external_policy.id;
-    // child #0 is just the key "A"
-    let path = vec![(root_id, vec![0])].into_iter().collect();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .policy_path(path, KeychainKind::External);
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
-}
-
-#[test]
-fn test_create_tx_policy_path_use_csv() {
-    let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
-
-    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
-    let root_id = external_policy.id;
-    // child #1 is or(pk(B),older(144))
-    let path = vec![(root_id, vec![1])].into_iter().collect();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .policy_path(path, KeychainKind::External);
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
-}
-
-#[test]
-fn test_create_tx_policy_path_ignored_subtree_with_csv() {
-    let (mut wallet, _) = get_funded_wallet("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v:pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))");
-
-    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
-    let root_id = external_policy.id;
-    // child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)
-    let path = vec![(root_id, vec![0])].into_iter().collect();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
-        .policy_path(path, KeychainKind::External);
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-}
-
-#[test]
-fn test_create_tx_global_xpubs_with_origin() {
-    use bitcoin::bip32;
-    use bitcoin::hex::FromHex;
-
-    let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .add_global_xpubs();
-    let psbt = builder.finish().unwrap();
-
-    let key = bip32::Xpub::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
-    let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
-    let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
-
-    assert_eq!(psbt.xpub.len(), 1);
-    assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
-}
-
-#[test]
-fn test_add_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
-    let (wallet2, _) =
-        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let utxo = wallet2.list_unspent().next().expect("must take!");
-    let foreign_utxo_satisfaction = wallet2
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    let psbt_input = psbt::Input {
-        witness_utxo: Some(utxo.txout.clone()),
-        ..Default::default()
-    };
-
-    let mut builder = wallet1.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
-        .only_witness_utxo()
-        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
-        .unwrap();
-    let mut psbt = builder.finish().unwrap();
-    wallet1.insert_txout(utxo.outpoint, utxo.txout);
-    let fee = check_fee!(wallet1, psbt);
-    let sent_received =
-        wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-
-    assert_eq!(
-        (sent_received.0 - sent_received.1).to_sat(),
-        10_000 + fee.unwrap_or(0),
-        "we should have only net spent ~10_000"
-    );
-
-    assert!(
-        psbt.unsigned_tx
-            .input
-            .iter()
-            .any(|input| input.previous_output == utxo.outpoint),
-        "foreign_utxo should be in there"
-    );
-
-    let finished = wallet1
-        .sign(
-            &mut psbt,
-            SignOptions {
-                trust_witness_utxo: true,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-
-    assert!(
-        !finished,
-        "only one of the inputs should have been signed so far"
-    );
-
-    let finished = wallet2
-        .sign(
-            &mut psbt,
-            SignOptions {
-                trust_witness_utxo: true,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-    assert!(finished, "all the inputs should have been signed now");
-}
-
-#[test]
-#[should_panic(
-    expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])"
-)]
-fn test_calculate_fee_with_missing_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
-    let (wallet2, _) =
-        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let utxo = wallet2.list_unspent().next().expect("must take!");
-    let foreign_utxo_satisfaction = wallet2
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    let psbt_input = psbt::Input {
-        witness_utxo: Some(utxo.txout.clone()),
-        ..Default::default()
-    };
-
-    let mut builder = wallet1.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
-        .only_witness_utxo()
-        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
-        .unwrap();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    wallet1.calculate_fee(&tx).unwrap();
-}
-
-#[test]
-fn test_add_foreign_utxo_invalid_psbt_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
-    let foreign_utxo_satisfaction = wallet
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    let mut builder = wallet.build_tx();
-    let result =
-        builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction);
-    assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo)));
-}
-
-#[test]
-fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
-    let (mut wallet1, txid1) = get_funded_wallet(get_test_wpkh());
-    let (wallet2, txid2) =
-        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
-
-    let utxo2 = wallet2.list_unspent().next().unwrap();
-    let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
-    let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone();
-
-    let satisfaction_weight = wallet2
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    let mut builder = wallet1.build_tx();
-    assert!(
-        builder
-            .add_foreign_utxo(
-                utxo2.outpoint,
-                psbt::Input {
-                    non_witness_utxo: Some(tx1.as_ref().clone()),
-                    ..Default::default()
-                },
-                satisfaction_weight
-            )
-            .is_err(),
-        "should fail when outpoint doesn't match psbt_input"
-    );
-    assert!(
-        builder
-            .add_foreign_utxo(
-                utxo2.outpoint,
-                psbt::Input {
-                    non_witness_utxo: Some(tx2.as_ref().clone()),
-                    ..Default::default()
-                },
-                satisfaction_weight
-            )
-            .is_ok(),
-        "should be ok when outpoint does match psbt_input"
-    );
-}
-
-#[test]
-fn test_add_foreign_utxo_only_witness_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
-    let (wallet2, txid2) =
-        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let utxo2 = wallet2.list_unspent().next().unwrap();
-
-    let satisfaction_weight = wallet2
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    let mut builder = wallet1.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000));
-
-    {
-        let mut builder = builder.clone();
-        let psbt_input = psbt::Input {
-            witness_utxo: Some(utxo2.txout.clone()),
-            ..Default::default()
-        };
-        builder
-            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
-            .unwrap();
-        assert!(
-            builder.finish().is_err(),
-            "psbt_input with witness_utxo should fail with only witness_utxo"
-        );
-    }
-
-    {
-        let mut builder = builder.clone();
-        let psbt_input = psbt::Input {
-            witness_utxo: Some(utxo2.txout.clone()),
-            ..Default::default()
-        };
-        builder
-            .only_witness_utxo()
-            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
-            .unwrap();
-        assert!(
-            builder.finish().is_ok(),
-            "psbt_input with just witness_utxo should succeed when `only_witness_utxo` is enabled"
-        );
-    }
-
-    {
-        let mut builder = builder.clone();
-        let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx;
-        let psbt_input = psbt::Input {
-            non_witness_utxo: Some(tx2.as_ref().clone()),
-            ..Default::default()
-        };
-        builder
-            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
-            .unwrap();
-        assert!(
-            builder.finish().is_ok(),
-            "psbt_input with non_witness_utxo should succeed by default"
-        );
-    }
-}
-
-#[test]
-fn test_get_psbt_input() {
-    // this should grab a known good utxo and set the input
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
-    for utxo in wallet.list_unspent() {
-        let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
-        assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
-    }
-}
-
-#[test]
-#[should_panic(
-    expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")"
-)]
-fn test_create_tx_global_xpubs_origin_missing() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .add_global_xpubs();
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_create_tx_global_xpubs_master_without_origin() {
-    use bitcoin::bip32;
-    use bitcoin::hex::FromHex;
-
-    let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .add_global_xpubs();
-    let psbt = builder.finish().unwrap();
-
-    let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
-    let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
-
-    assert_eq!(psbt.xpub.len(), 1);
-    assert_eq!(
-        psbt.xpub.get(&key),
-        Some(&(fingerprint, bip32::DerivationPath::default()))
-    );
-}
-
-#[test]
-#[should_panic(expected = "IrreplaceableTransaction")]
-fn test_bump_fee_irreplaceable_tx() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-    wallet.build_fee_bump(txid).unwrap().finish().unwrap();
-}
-
-#[test]
-#[should_panic(expected = "TransactionConfirmed")]
-fn test_bump_fee_confirmed_tx() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-
-    wallet
-        .insert_tx(
-            tx,
-            ConfirmationTime::Confirmed {
-                height: 42,
-                time: 42_000,
-            },
-        )
-        .unwrap();
-
-    wallet.build_fee_bump(txid).unwrap().finish().unwrap();
-}
-
-#[test]
-fn test_bump_fee_low_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let feerate = psbt.fee_rate().unwrap();
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_rate(FeeRate::BROADCAST_MIN);
-    let res = builder.finish();
-    assert_matches!(
-        res,
-        Err(CreateTxError::FeeRateTooLow { .. }),
-        "expected FeeRateTooLow error"
-    );
-
-    let required = feerate.to_sat_per_kwu() + 250; // +1 sat/vb
-    let sat_vb = required as f64 / 250.0;
-    let expect = format!("Fee rate too low: required {} sat/vb", sat_vb);
-    assert_eq!(res.unwrap_err().to_string(), expect);
-}
-
-#[test]
-#[should_panic(expected = "FeeTooLow")]
-fn test_bump_fee_low_abs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_absolute(10);
-    builder.finish().unwrap();
-}
-
-#[test]
-#[should_panic(expected = "FeeTooLow")]
-fn test_bump_fee_zero_abs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_absolute(0);
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_bump_fee_reduce_change() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let original_sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let original_fee = check_fee!(wallet, psbt);
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_rate(feerate).enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(sent_received.0, original_sent_received.0);
-    assert_eq!(
-        sent_received.1 + Amount::from_sat(fee.unwrap_or(0)),
-        original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0))
-    );
-    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature);
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_absolute(200);
-    builder.enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(sent_received.0, original_sent_received.0);
-    assert_eq!(
-        sent_received.1 + Amount::from_sat(fee.unwrap_or(0)),
-        original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0))
-    );
-    assert!(
-        fee.unwrap_or(0) > original_fee.unwrap_or(0),
-        "{} > {}",
-        fee.unwrap_or(0),
-        original_fee.unwrap_or(0)
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_eq!(fee.unwrap_or(0), 200);
-}
-
-#[test]
-fn test_bump_fee_reduce_single_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.clone().extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let original_fee = check_fee!(wallet, psbt);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .fee_rate(feerate)
-        .allow_shrinking(addr.script_pubkey())
-        .unwrap();
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(sent_received.0, original_sent_received.0);
-    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.output.len(), 1);
-    assert_eq!(
-        tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)),
-        sent_received.0
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature);
-}
-
-#[test]
-fn test_bump_fee_absolute_reduce_single_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let original_fee = check_fee!(wallet, psbt);
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .allow_shrinking(addr.script_pubkey())
-        .unwrap()
-        .fee_absolute(300);
-    let psbt = builder.finish().unwrap();
-    let tx = &psbt.unsigned_tx;
-    let sent_received = wallet.sent_and_received(tx);
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(sent_received.0, original_sent_received.0);
-    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
-
-    assert_eq!(tx.output.len(), 1);
-    assert_eq!(
-        tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)),
-        sent_received.0
-    );
-
-    assert_eq!(fee.unwrap_or(0), 300);
-}
-
-#[test]
-fn test_bump_fee_drain_wallet() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    // receive an extra tx so that our wallet has two utxos.
-    let tx = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-    };
-    wallet
-        .insert_tx(
-            tx.clone(),
-            ConfirmationTime::Confirmed {
-                height: wallet.latest_checkpoint().height(),
-                time: 42_000,
-            },
-        )
-        .unwrap();
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .add_utxo(OutPoint {
-            txid: tx.txid(),
-            vout: 0,
-        })
-        .unwrap()
-        .manually_selected_only()
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-    assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
-
-    // for the new feerate, it should be enough to reduce the output, but since we specify
-    // `drain_wallet` we expect to spend everything
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .drain_wallet()
-        .allow_shrinking(addr.script_pubkey())
-        .unwrap()
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
-    let psbt = builder.finish().unwrap();
-    let sent_received = wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx"));
-
-    assert_eq!(sent_received.0, Amount::from_sat(75_000));
-}
-
-#[test]
-#[should_panic(expected = "InsufficientFunds")]
-fn test_bump_fee_remove_output_manually_selected_only() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    // receive an extra tx so that our wallet has two utxos. then we manually pick only one of
-    // them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
-    // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
-    // existing output. In other words, bump_fee + manually_selected_only is always an error
-    // unless you've also set "allow_shrinking" OR there is a change output.
-    let init_tx = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-    };
-    wallet
-        .insert_tx(
-            init_tx.clone(),
-            wallet
-                .transactions()
-                .last()
-                .unwrap()
-                .chain_position
-                .cloned()
-                .into(),
-        )
-        .unwrap();
-    let outpoint = OutPoint {
-        txid: init_tx.txid(),
-        vout: 0,
-    };
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .add_utxo(outpoint)
-        .unwrap()
-        .manually_selected_only()
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-    assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .manually_selected_only()
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(255));
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_bump_fee_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let init_tx = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-    };
-    let pos = wallet
-        .transactions()
-        .last()
-        .unwrap()
-        .chain_position
-        .cloned()
-        .into();
-    wallet.insert_tx(init_tx, pos).unwrap();
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_details = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-    assert_eq!(
-        sent_received.0,
-        original_details.0 + Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
-        Amount::from_sat(30_000)
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(45_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature);
-}
-
-#[test]
-fn test_bump_fee_absolute_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    receive_output_in_latest_block(&mut wallet, 25_000);
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_absolute(6_000);
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(
-        sent_received.0,
-        original_sent_received.0 + Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
-        Amount::from_sat(30_000)
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(45_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_eq!(fee.unwrap_or(0), 6_000);
-}
-
-#[test]
-fn test_bump_fee_no_change_add_input_and_change() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let op = receive_output_in_latest_block(&mut wallet, 25_000);
-
-    // initially make a tx without change by using `drain_to`
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .add_utxo(op)
-        .unwrap()
-        .manually_selected_only()
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let original_sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let original_fee = check_fee!(wallet, psbt);
-
-    let tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    // now bump the fees without using `allow_shrinking`. the wallet should add an
-    // extra input and a change output, and leave the original output untouched
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    let original_send_all_amount =
-        original_sent_received.0 - Amount::from_sat(original_fee.unwrap_or(0));
-    assert_eq!(
-        sent_received.0,
-        original_sent_received.0 + Amount::from_sat(50_000)
-    );
-    assert_eq!(
-        sent_received.1,
-        Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0))
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        original_send_all_amount
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0))
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature);
-}
-
-#[test]
-fn test_bump_fee_add_input_change_dust() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    receive_output_in_latest_block(&mut wallet, 25_000);
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let original_sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let original_fee = check_fee!(wallet, psbt);
-
-    let mut tx = psbt.extract_tx().expect("failed to extract tx");
-    for txin in &mut tx.input {
-        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realistic weight
-    }
-    let original_tx_weight = tx.weight();
-    assert_eq!(tx.input.len(), 1);
-    assert_eq!(tx.output.len(), 2);
-    let txid = tx.txid();
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    // We set a fee high enough that during rbf we are forced to add
-    // a new input and also that we have to remove the change
-    // that we had previously
-
-    // We calculate the new weight as:
-    //   original weight
-    // + extra input weight: 160 WU = (32 (prevout) + 4 (vout) + 4 (nsequence)) * 4
-    // + input satisfaction weight: 112 WU = 106 (witness) + 2 (witness len) + (1 (script len)) * 4
-    // - change output weight: 124 WU = (8 (value) + 1 (script len) + 22 (script)) * 4
-    let new_tx_weight =
-        original_tx_weight + Weight::from_wu(160) + Weight::from_wu(112) - Weight::from_wu(124);
-    // two inputs (50k, 25k) and one output (45k) - epsilon
-    // We use epsilon here to avoid asking for a slightly too high feerate
-    let fee_abs = 50_000 + 25_000 - 45_000 - 10;
-    builder.fee_rate(Amount::from_sat(fee_abs) / new_tx_weight);
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(
-        original_sent_received.1,
-        Amount::from_sat(5_000 - original_fee.unwrap_or(0))
-    );
-
-    assert_eq!(
-        sent_received.0,
-        original_sent_received.0 + Amount::from_sat(25_000)
-    );
-    assert_eq!(fee.unwrap_or(0), 30_000);
-    assert_eq!(sent_received.1, Amount::ZERO);
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 1);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(45_000)
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature);
-}
-
-#[test]
-fn test_bump_fee_force_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let mut tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    for txin in &mut tx.input {
-        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-    }
-    wallet
-        .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-    // the new fee_rate is low enough that just reducing the change would be fine, but we force
-    // the addition of an extra input with `add_utxo()`
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .add_utxo(incoming_op)
-        .unwrap()
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(
-        sent_received.0,
-        original_sent_received.0 + Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
-        Amount::from_sat(30_000)
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(45_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature);
-}
-
-#[test]
-fn test_bump_fee_absolute_force_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let mut tx = psbt.extract_tx().expect("failed to extract tx");
-    let original_sent_received = wallet.sent_and_received(&tx);
-    let txid = tx.txid();
-    // skip saving the new utxos, we know they can't be used anyways
-    for txin in &mut tx.input {
-        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-    }
-    wallet
-        .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    // the new fee_rate is low enough that just reducing the change would be fine, but we force
-    // the addition of an extra input with `add_utxo()`
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.add_utxo(incoming_op).unwrap().fee_absolute(250);
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(
-        sent_received.0,
-        original_sent_received.0 + Amount::from_sat(25_000)
-    );
-    assert_eq!(
-        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
-        Amount::from_sat(30_000)
-    );
-
-    let tx = &psbt.unsigned_tx;
-    assert_eq!(tx.input.len(), 2);
-    assert_eq!(tx.output.len(), 2);
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey == addr.script_pubkey())
-            .unwrap()
-            .value,
-        Amount::from_sat(45_000)
-    );
-    assert_eq!(
-        tx.output
-            .iter()
-            .find(|txout| txout.script_pubkey != addr.script_pubkey())
-            .unwrap()
-            .value,
-        sent_received.1
-    );
-
-    assert_eq!(fee.unwrap_or(0), 250);
-}
-
-#[test]
-#[should_panic(expected = "InsufficientFunds")]
-fn test_bump_fee_unconfirmed_inputs_only() {
-    // We try to bump the fee, but:
-    // - We can't reduce the change, as we have no change
-    // - All our UTXOs are unconfirmed
-    // So, we fail with "InsufficientFunds", as per RBF rule 2:
-    // The replacement transaction may only include an unconfirmed input
-    // if that input was included in one of the original transactions.
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_wallet()
-        .drain_to(addr.script_pubkey())
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    // Now we receive one transaction with 0 confirmations. We won't be able to use that for
-    // fee bumping, as it's still unconfirmed!
-    receive_output(
-        &mut wallet,
-        25_000,
-        ConfirmationTime::Unconfirmed { last_seen: 0 },
-    );
-    let mut tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    for txin in &mut tx.input {
-        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-    }
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25));
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_bump_fee_unconfirmed_input() {
-    // We create a tx draining the wallet and spending one confirmed
-    // and one unconfirmed UTXO. We check that we can fee bump normally
-    // (BIP125 rule 2 only apply to newly added unconfirmed input, you can
-    // always fee bump with an unconfirmed input if it was included in the
-    // original transaction)
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    // We receive a tx with 0 confirmations, which will be used as an input
-    // in the drain tx.
-    receive_output(&mut wallet, 25_000, ConfirmationTime::unconfirmed(0));
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_wallet()
-        .drain_to(addr.script_pubkey())
-        .enable_rbf();
-    let psbt = builder.finish().unwrap();
-    let mut tx = psbt.extract_tx().expect("failed to extract tx");
-    let txid = tx.txid();
-    for txin in &mut tx.input {
-        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
-    }
-    wallet
-        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
-        .unwrap();
-
-    let mut builder = wallet.build_fee_bump(txid).unwrap();
-    builder
-        .fee_rate(FeeRate::from_sat_per_vb_unchecked(15))
-        .allow_shrinking(addr.script_pubkey())
-        .unwrap();
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_fee_amount_negative_drain_val() {
-    // While building the transaction, bdk would calculate the drain_value
-    // as
-    // current_delta - fee_amount - drain_fee
-    // using saturating_sub, meaning that if the result would end up negative,
-    // it'll remain to zero instead.
-    // This caused a bug in master where we would calculate the wrong fee
-    // for a transaction.
-    // See https://github.com/bitcoindevkit/bdk/issues/660
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
-        .unwrap()
-        .assume_checked();
-    let fee_rate = FeeRate::from_sat_per_kwu(500);
-    let incoming_op = receive_output_in_latest_block(&mut wallet, 8859);
-
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(send_to.script_pubkey(), Amount::from_sat(8630))
-        .add_utxo(incoming_op)
-        .unwrap()
-        .enable_rbf()
-        .fee_rate(fee_rate);
-    let psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    assert_eq!(psbt.inputs.len(), 1);
-    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate, @add_signature);
-}
-
-#[test]
-fn test_sign_single_xprv() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_sign_single_xprv_with_master_fingerprint_and_path() {
-    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_sign_single_xprv_bip44_path() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_sign_single_xprv_sh_wpkh() {
-    let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_sign_single_wif() {
-    let (mut wallet, _) =
-        get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_sign_single_xprv_no_hd_keypaths() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    psbt.inputs[0].bip32_derivation.clear();
-    assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
-
-    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
-    assert!(finalized);
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(extracted.input[0].witness.len(), 2);
-}
-
-#[test]
-fn test_include_output_redeem_witness_script() {
-    let (mut wallet, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .include_output_redeem_witness_script();
-    let psbt = builder.finish().unwrap();
-
-    // p2sh-p2wsh transaction should contain both witness and redeem scripts
-    assert!(psbt
-        .outputs
-        .iter()
-        .any(|output| output.redeem_script.is_some() && output.witness_script.is_some()));
-}
-
-#[test]
-fn test_signing_only_one_of_multiple_inputs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
-        .include_output_redeem_witness_script();
-    let mut psbt = builder.finish().unwrap();
-
-    // add another input to the psbt that is at least passable.
-    let dud_input = bitcoin::psbt::Input {
-        witness_utxo: Some(TxOut {
-            value: Amount::from_sat(100_000),
-            script_pubkey: miniscript::Descriptor::<bitcoin::PublicKey>::from_str(
-                "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
-            )
-            .unwrap()
-            .script_pubkey(),
-        }),
-        ..Default::default()
-    };
-
-    psbt.inputs.push(dud_input);
-    psbt.unsigned_tx.input.push(bitcoin::TxIn::default());
-    let is_final = wallet
-        .sign(
-            &mut psbt,
-            SignOptions {
-                trust_witness_utxo: true,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-    assert!(
-        !is_final,
-        "shouldn't be final since we can't sign one of the inputs"
-    );
-    assert!(
-        psbt.inputs[0].final_script_witness.is_some(),
-        "should finalized input it signed"
-    )
-}
-
-#[test]
-fn test_remove_partial_sigs_after_finalize_sign_option() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-
-    for remove_partial_sigs in &[true, false] {
-        let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        let mut psbt = builder.finish().unwrap();
-
-        assert!(wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    remove_partial_sigs: *remove_partial_sigs,
-                    ..Default::default()
-                },
-            )
-            .unwrap());
-
-        psbt.inputs.iter().for_each(|input| {
-            if *remove_partial_sigs {
-                assert!(input.partial_sigs.is_empty())
-            } else {
-                assert!(!input.partial_sigs.is_empty())
-            }
-        });
-    }
-}
-
-#[test]
-fn test_try_finalize_sign_option() {
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-
-    for try_finalize in &[true, false] {
-        let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        let mut psbt = builder.finish().unwrap();
-
-        let finalized = wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    try_finalize: *try_finalize,
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-
-        psbt.inputs.iter().for_each(|input| {
-            if *try_finalize {
-                assert!(finalized);
-                assert!(input.final_script_sig.is_some());
-                assert!(input.final_script_witness.is_some());
-            } else {
-                assert!(!finalized);
-                assert!(input.final_script_sig.is_none());
-                assert!(input.final_script_witness.is_none());
-            }
-        });
-    }
-}
-
-#[test]
-fn test_sign_nonstandard_sighash() {
-    let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
-
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .sighash(sighash.into())
-        .drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let result = wallet.sign(&mut psbt, Default::default());
-    assert!(
-        result.is_err(),
-        "Signing should have failed because the TX uses non-standard sighashes"
-    );
-    assert_matches!(
-        result,
-        Err(SignerError::NonStandardSighash),
-        "Signing failed with the wrong error type"
-    );
-
-    // try again after opting-in
-    let result = wallet.sign(
-        &mut psbt,
-        SignOptions {
-            allow_all_sighashes: true,
-            ..Default::default()
-        },
-    );
-    assert!(result.is_ok(), "Signing should have worked");
-    assert!(
-        result.unwrap(),
-        "Should finalize the input since we can produce signatures"
-    );
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(
-        *extracted.input[0].witness.to_vec()[0].last().unwrap(),
-        sighash.to_u32() as u8,
-        "The signature should have been made with the right sighash"
-    );
-}
-
-#[test]
-fn test_unused_address() {
-    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
-
-    // `list_unused_addresses` should be empty if we haven't revealed any
-    assert!(wallet
-        .list_unused_addresses(KeychainKind::External)
-        .next()
-        .is_none());
-
-    assert_eq!(
-        wallet
-            .next_unused_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-    assert_eq!(
-        wallet
-            .list_unused_addresses(KeychainKind::External)
-            .next()
-            .unwrap()
-            .to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-}
-
-#[test]
-fn test_next_unused_address() {
-    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
-    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap();
-    assert_eq!(wallet.derivation_index(KeychainKind::External), None);
-
-    assert_eq!(
-        wallet
-            .next_unused_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
-    // calling next_unused again gives same address
-    assert_eq!(
-        wallet
-            .next_unused_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
-
-    // test mark used / unused
-    assert!(wallet.mark_used(KeychainKind::External, 0));
-    let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    assert_eq!(next_unused_addr.index, 1);
-
-    assert!(wallet.unmark_used(KeychainKind::External, 0));
-    let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    assert_eq!(next_unused_addr.index, 0);
-
-    // use the above address
-    receive_output_in_latest_block(&mut wallet, 25_000);
-
-    assert_eq!(
-        wallet
-            .next_unused_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
-
-    // trying to mark index 0 unused should return false
-    assert!(!wallet.unmark_used(KeychainKind::External, 0));
-}
-
-#[test]
-fn test_peek_address_at_index() {
-    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 1).to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 0).to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 2).to_string(),
-        "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
-    );
-
-    // current new address is not affected
-    assert_eq!(
-        wallet
-            .reveal_next_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-    );
-
-    assert_eq!(
-        wallet
-            .reveal_next_address(KeychainKind::External)
-            .unwrap()
-            .to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-}
-
-#[test]
-fn test_peek_address_at_index_not_derivable() {
-    let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
-                                 None, Network::Testnet).unwrap();
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 1).to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 0).to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 2).to_string(),
-        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-    );
-}
-
-#[test]
-fn test_returns_index_and_address() {
-    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
-
-    // new index 0
-    assert_eq!(
-        wallet.reveal_next_address(KeychainKind::External).unwrap(),
-        AddressInfo {
-            index: 0,
-            address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        }
-    );
-
-    // new index 1
-    assert_eq!(
-        wallet.reveal_next_address(KeychainKind::External).unwrap(),
-        AddressInfo {
-            index: 1,
-            address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        }
-    );
-
-    // peek index 25
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 25),
-        AddressInfo {
-            index: 25,
-            address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        }
-    );
-
-    // new index 2
-    assert_eq!(
-        wallet.reveal_next_address(KeychainKind::External).unwrap(),
-        AddressInfo {
-            index: 2,
-            address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        }
-    );
-}
-
-#[test]
-fn test_sending_to_bip350_bech32m_address() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_get_address() {
-    use bdk::descriptor::template::Bip84;
-    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-    let wallet = Wallet::new_no_persist(
-        Bip84(key, KeychainKind::External),
-        Some(Bip84(key, KeychainKind::Internal)),
-        Network::Regtest,
-    )
-    .unwrap();
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::External, 0),
-        AddressInfo {
-            index: 0,
-            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        }
-    );
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::Internal, 0),
-        AddressInfo {
-            index: 0,
-            address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::Internal,
-        }
-    );
-
-    let wallet =
-        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::Internal, 0),
-        AddressInfo {
-            index: 0,
-            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        },
-        "when there's no internal descriptor it should just use external"
-    );
-}
-
-#[test]
-fn test_reveal_addresses() {
-    let desc = get_test_tr_single_sig_xprv();
-    let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap();
-    let keychain = KeychainKind::External;
-
-    let last_revealed_addr = wallet
-        .reveal_addresses_to(keychain, 9)
-        .unwrap()
-        .last()
-        .unwrap();
-    assert_eq!(wallet.derivation_index(keychain), Some(9));
-
-    let unused_addrs = wallet.list_unused_addresses(keychain).collect::<Vec<_>>();
-    assert_eq!(unused_addrs.len(), 10);
-    assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr);
-
-    // revealing to an already revealed index returns nothing
-    let mut already_revealed = wallet.reveal_addresses_to(keychain, 9).unwrap();
-    assert!(already_revealed.next().is_none());
-}
-
-#[test]
-fn test_get_address_no_reuse_single_descriptor() {
-    use bdk::descriptor::template::Bip84;
-    use std::collections::HashSet;
-
-    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-    let mut wallet =
-        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
-
-    let mut used_set = HashSet::new();
-
-    (0..3).for_each(|_| {
-        let external_addr = wallet
-            .reveal_next_address(KeychainKind::External)
-            .unwrap()
-            .address;
-        assert!(used_set.insert(external_addr));
-
-        let internal_addr = wallet
-            .reveal_next_address(KeychainKind::Internal)
-            .unwrap()
-            .address;
-        assert!(used_set.insert(internal_addr));
-    });
-}
-
-#[test]
-fn test_taproot_remove_tapfields_after_finalize_sign_option() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
-
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-    let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
-    assert!(finalized);
-
-    // removes tap_* from inputs
-    for input in &psbt.inputs {
-        assert!(input.tap_key_sig.is_none());
-        assert!(input.tap_script_sigs.is_empty());
-        assert!(input.tap_scripts.is_empty());
-        assert!(input.tap_key_origins.is_empty());
-        assert!(input.tap_internal_key.is_none());
-        assert!(input.tap_merkle_root.is_none());
-    }
-    // removes key origins from outputs
-    for output in &psbt.outputs {
-        assert!(output.tap_key_origins.is_empty());
-    }
-}
-
-#[test]
-fn test_taproot_psbt_populate_tap_key_origins() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-    let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(
-        psbt.inputs[0]
-            .tap_key_origins
-            .clone()
-            .into_iter()
-            .collect::<Vec<_>>(),
-        vec![(
-            from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"),
-            (vec![], (from_str!("f6a5cb8b"), from_str!("m/0")))
-        )],
-        "Wrong input tap_key_origins"
-    );
-    assert_eq!(
-        psbt.outputs[0]
-            .tap_key_origins
-            .clone()
-            .into_iter()
-            .collect::<Vec<_>>(),
-        vec![(
-            from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"),
-            (vec![], (from_str!("f6a5cb8b"), from_str!("m/1")))
-        )],
-        "Wrong output tap_key_origins"
-    );
-}
-
-#[test]
-fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key());
-    let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
-
-    let path = vec![("rn4nre9c".to_string(), vec![0])]
-        .into_iter()
-        .collect();
-
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .policy_path(path, KeychainKind::External);
-    let psbt = builder.finish().unwrap();
-
-    let mut input_key_origins = psbt.inputs[0]
-        .tap_key_origins
-        .clone()
-        .into_iter()
-        .collect::<Vec<_>>();
-    input_key_origins.sort();
-
-    assert_eq!(
-        input_key_origins,
-        vec![
-            (
-                from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
-                (
-                    vec![
-                        from_str!(
-                            "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
-                        ),
-                        from_str!(
-                            "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
-                        ),
-                    ],
-                    (FromStr::from_str("ece52657").unwrap(), vec![].into())
-                )
-            ),
-            (
-                from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
-                (
-                    vec![],
-                    (FromStr::from_str("871fd295").unwrap(), vec![].into())
-                )
-            )
-        ],
-        "Wrong input tap_key_origins"
-    );
-
-    let mut output_key_origins = psbt.outputs[0]
-        .tap_key_origins
-        .clone()
-        .into_iter()
-        .collect::<Vec<_>>();
-    output_key_origins.sort();
-
-    assert_eq!(
-        input_key_origins, output_key_origins,
-        "Wrong output tap_key_origins"
-    );
-}
-
-#[test]
-fn test_taproot_psbt_input_tap_tree() {
-    use bitcoin::hex::FromHex;
-    use bitcoin::taproot;
-
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(
-        psbt.inputs[0].tap_merkle_root,
-        Some(
-            TapNodeHash::from_str(
-                "61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986"
-            )
-            .unwrap()
-        ),
-    );
-    assert_eq!(
-        psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
-        vec![
-            (taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (ScriptBuf::from_hex("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac").unwrap(), taproot::LeafVersion::TapScript)),
-            (taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (ScriptBuf::from_hex("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac").unwrap(), taproot::LeafVersion::TapScript)),
-        ],
-    );
-    assert_eq!(
-        psbt.inputs[0].tap_internal_key,
-        Some(from_str!(
-            "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"
-        ))
-    );
-
-    // Since we are creating an output to the same address as the input, assert that the
-    // internal_key is the same
-    assert_eq!(
-        psbt.inputs[0].tap_internal_key,
-        psbt.outputs[0].tap_internal_key
-    );
-
-    let tap_tree: bitcoin::taproot::TapTree = serde_json::from_str(r#"[1,{"Script":["2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",192]},1,{"Script":["208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac",192]}]"#).unwrap();
-    assert_eq!(psbt.outputs[0].tap_tree, Some(tap_tree));
-}
-
-#[test]
-fn test_taproot_sign_missing_witness_utxo() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-    let witness_utxo = psbt.inputs[0].witness_utxo.take();
-
-    let result = wallet.sign(
-        &mut psbt,
-        SignOptions {
-            allow_all_sighashes: true,
-            ..Default::default()
-        },
-    );
-    assert_matches!(
-        result,
-        Err(SignerError::MissingWitnessUtxo),
-        "Signing should have failed with the correct error because the witness_utxo is missing"
-    );
-
-    // restore the witness_utxo
-    psbt.inputs[0].witness_utxo = witness_utxo;
-
-    let result = wallet.sign(
-        &mut psbt,
-        SignOptions {
-            allow_all_sighashes: true,
-            ..Default::default()
-        },
-    );
-
-    assert_matches!(
-        result,
-        Ok(true),
-        "Should finalize the input since we can produce signatures"
-    );
-}
-
-#[test]
-fn test_taproot_sign_using_non_witness_utxo() {
-    let (mut wallet, prev_txid) = get_funded_wallet(get_test_tr_single_sig());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder.drain_to(addr.script_pubkey()).drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    psbt.inputs[0].witness_utxo = None;
-    psbt.inputs[0].non_witness_utxo =
-        Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone());
-    assert!(
-        psbt.inputs[0].non_witness_utxo.is_some(),
-        "Previous tx should be present in the database"
-    );
-
-    let result = wallet.sign(&mut psbt, Default::default());
-    assert!(result.is_ok(), "Signing should have worked");
-    assert!(
-        result.unwrap(),
-        "Should finalize the input since we can produce signatures"
-    );
-}
-
-#[test]
-fn test_taproot_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
-    let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
-
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let utxo = wallet2.list_unspent().next().unwrap();
-    let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
-    let foreign_utxo_satisfaction = wallet2
-        .get_descriptor_for_keychain(KeychainKind::External)
-        .max_weight_to_satisfy()
-        .unwrap();
-
-    assert!(
-        psbt_input.non_witness_utxo.is_none(),
-        "`non_witness_utxo` should never be populated for taproot"
-    );
-
-    let mut builder = wallet1.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
-        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
-        .unwrap();
-    let psbt = builder.finish().unwrap();
-    let sent_received =
-        wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
-    wallet1.insert_txout(utxo.outpoint, utxo.txout);
-    let fee = check_fee!(wallet1, psbt);
-
-    assert_eq!(
-        sent_received.0 - sent_received.1,
-        Amount::from_sat(10_000 + fee.unwrap_or(0)),
-        "we should have only net spent ~10_000"
-    );
-
-    assert!(
-        psbt.unsigned_tx
-            .input
-            .iter()
-            .any(|input| input.previous_output == utxo.outpoint),
-        "foreign_utxo should be in there"
-    );
-}
-
-fn test_spend_from_wallet(mut wallet: Wallet) {
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-
-    assert!(
-        wallet.sign(&mut psbt, Default::default()).unwrap(),
-        "Unable to finalize tx"
-    );
-}
-
-//     #[test]
-//     fn test_taproot_key_spend() {
-//         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-//         test_spend_from_wallet(wallet);
-
-//         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-//         test_spend_from_wallet(wallet);
-//     }
-
-#[test]
-fn test_taproot_no_key_spend() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-
-    assert!(
-        wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    sign_with_tap_internal_key: false,
-                    ..Default::default()
-                },
-            )
-            .unwrap(),
-        "Unable to finalize tx"
-    );
-
-    assert!(psbt.inputs.iter().all(|i| i.tap_key_sig.is_none()));
-}
-
-#[test]
-fn test_taproot_script_spend() {
-    let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
-    test_spend_from_wallet(wallet);
-
-    let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
-    test_spend_from_wallet(wallet);
-}
-
-#[test]
-fn test_taproot_script_spend_sign_all_leaves() {
-    use bdk::signer::TapLeavesOptions;
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-
-    assert!(
-        wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    tap_leaves_options: TapLeavesOptions::All,
-                    ..Default::default()
-                },
-            )
-            .unwrap(),
-        "Unable to finalize tx"
-    );
-
-    assert!(psbt
-        .inputs
-        .iter()
-        .all(|i| i.tap_script_sigs.len() == i.tap_scripts.len()));
-}
-
-#[test]
-fn test_taproot_script_spend_sign_include_some_leaves() {
-    use bdk::signer::TapLeavesOptions;
-    use bitcoin::taproot::TapLeafHash;
-
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-    let mut script_leaves: Vec<_> = psbt.inputs[0]
-        .tap_scripts
-        .clone()
-        .values()
-        .map(|(script, version)| TapLeafHash::from_script(script, *version))
-        .collect();
-    let included_script_leaves = vec![script_leaves.pop().unwrap()];
-    let excluded_script_leaves = script_leaves;
-
-    assert!(
-        wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    tap_leaves_options: TapLeavesOptions::Include(included_script_leaves.clone()),
-                    ..Default::default()
-                },
-            )
-            .unwrap(),
-        "Unable to finalize tx"
-    );
-
-    assert!(psbt.inputs[0]
-        .tap_script_sigs
-        .iter()
-        .all(|s| included_script_leaves.contains(&s.0 .1)
-            && !excluded_script_leaves.contains(&s.0 .1)));
-}
-
-#[test]
-fn test_taproot_script_spend_sign_exclude_some_leaves() {
-    use bdk::signer::TapLeavesOptions;
-    use bitcoin::taproot::TapLeafHash;
-
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-    let mut script_leaves: Vec<_> = psbt.inputs[0]
-        .tap_scripts
-        .clone()
-        .values()
-        .map(|(script, version)| TapLeafHash::from_script(script, *version))
-        .collect();
-    let included_script_leaves = [script_leaves.pop().unwrap()];
-    let excluded_script_leaves = script_leaves;
-
-    assert!(
-        wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    tap_leaves_options: TapLeavesOptions::Exclude(excluded_script_leaves.clone()),
-                    ..Default::default()
-                },
-            )
-            .unwrap(),
-        "Unable to finalize tx"
-    );
-
-    assert!(psbt.inputs[0]
-        .tap_script_sigs
-        .iter()
-        .all(|s| included_script_leaves.contains(&s.0 .1)
-            && !excluded_script_leaves.contains(&s.0 .1)));
-}
-
-#[test]
-fn test_taproot_script_spend_sign_no_leaves() {
-    use bdk::signer::TapLeavesOptions;
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-
-    wallet
-        .sign(
-            &mut psbt,
-            SignOptions {
-                tap_leaves_options: TapLeavesOptions::None,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-
-    assert!(psbt.inputs.iter().all(|i| i.tap_script_sigs.is_empty()));
-}
-
-#[test]
-fn test_taproot_sign_derive_index_from_psbt() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let mut psbt = builder.finish().unwrap();
-
-    // re-create the wallet with an empty db
-    let wallet_empty =
-        Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
-
-    // signing with an empty db means that we will only look at the psbt to infer the
-    // derivation index
-    assert!(
-        wallet_empty.sign(&mut psbt, Default::default()).unwrap(),
-        "Unable to finalize tx"
-    );
-}
-
-#[test]
-fn test_taproot_sign_explicit_sighash_all() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .sighash(TapSighashType::All.into())
-        .drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let result = wallet.sign(&mut psbt, Default::default());
-    assert!(
-        result.is_ok(),
-        "Signing should work because SIGHASH_ALL is safe"
-    )
-}
-
-#[test]
-fn test_taproot_sign_non_default_sighash() {
-    let sighash = TapSighashType::NonePlusAnyoneCanPay;
-
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .sighash(sighash.into())
-        .drain_wallet();
-    let mut psbt = builder.finish().unwrap();
-
-    let witness_utxo = psbt.inputs[0].witness_utxo.take();
-
-    let result = wallet.sign(&mut psbt, Default::default());
-    assert!(
-        result.is_err(),
-        "Signing should have failed because the TX uses non-standard sighashes"
-    );
-    assert_matches!(
-        result,
-        Err(SignerError::NonStandardSighash),
-        "Signing failed with the wrong error type"
-    );
-
-    // try again after opting-in
-    let result = wallet.sign(
-        &mut psbt,
-        SignOptions {
-            allow_all_sighashes: true,
-            ..Default::default()
-        },
-    );
-    assert!(
-        result.is_err(),
-        "Signing should have failed because the witness_utxo is missing"
-    );
-    assert_matches!(
-        result,
-        Err(SignerError::MissingWitnessUtxo),
-        "Signing failed with the wrong error type"
-    );
-
-    // restore the witness_utxo
-    psbt.inputs[0].witness_utxo = witness_utxo;
-
-    let result = wallet.sign(
-        &mut psbt,
-        SignOptions {
-            allow_all_sighashes: true,
-            ..Default::default()
-        },
-    );
-
-    assert!(result.is_ok(), "Signing should have worked");
-    assert!(
-        result.unwrap(),
-        "Should finalize the input since we can produce signatures"
-    );
-
-    let extracted = psbt.extract_tx().expect("failed to extract tx");
-    assert_eq!(
-        *extracted.input[0].witness.to_vec()[0].last().unwrap(),
-        sighash as u8,
-        "The signature should have been made with the right sighash"
-    );
-}
-
-#[test]
-fn test_spend_coinbase() {
-    let descriptor = get_test_wpkh();
-    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap();
-
-    let confirmation_height = 5;
-    wallet
-        .insert_checkpoint(BlockId {
-            height: confirmation_height,
-            hash: BlockHash::all_zeros(),
-        })
-        .unwrap();
-    let coinbase_tx = Transaction {
-        version: transaction::Version::ONE,
-        lock_time: absolute::LockTime::ZERO,
-        input: vec![TxIn {
-            previous_output: OutPoint::null(),
-            ..Default::default()
-        }],
-        output: vec![TxOut {
-            script_pubkey: wallet
-                .next_unused_address(KeychainKind::External)
-                .unwrap()
-                .script_pubkey(),
-            value: Amount::from_sat(25_000),
-        }],
-    };
-    wallet
-        .insert_tx(
-            coinbase_tx,
-            ConfirmationTime::Confirmed {
-                height: confirmation_height,
-                time: 30_000,
-            },
-        )
-        .unwrap();
-
-    let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 1;
-    let maturity_time = confirmation_height + COINBASE_MATURITY;
-
-    let balance = wallet.get_balance();
-    assert_eq!(
-        balance,
-        Balance {
-            immature: Amount::from_sat(25_000),
-            trusted_pending: Amount::ZERO,
-            untrusted_pending: Amount::ZERO,
-            confirmed: Amount::ZERO
-        }
-    );
-
-    // We try to create a transaction, only to notice that all
-    // our funds are unspendable
-    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
-        .unwrap()
-        .assume_checked();
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), balance.immature / 2)
-        .current_height(confirmation_height);
-    assert!(matches!(
-        builder.finish(),
-        Err(CreateTxError::CoinSelection(
-            coin_selection::Error::InsufficientFunds {
-                needed: _,
-                available: 0
-            }
-        ))
-    ));
-
-    // Still unspendable...
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), balance.immature / 2)
-        .current_height(not_yet_mature_time);
-    assert_matches!(
-        builder.finish(),
-        Err(CreateTxError::CoinSelection(
-            coin_selection::Error::InsufficientFunds {
-                needed: _,
-                available: 0
-            }
-        ))
-    );
-
-    wallet
-        .insert_checkpoint(BlockId {
-            height: maturity_time,
-            hash: BlockHash::all_zeros(),
-        })
-        .unwrap();
-    let balance = wallet.get_balance();
-    assert_eq!(
-        balance,
-        Balance {
-            immature: Amount::ZERO,
-            trusted_pending: Amount::ZERO,
-            untrusted_pending: Amount::ZERO,
-            confirmed: Amount::from_sat(25_000)
-        }
-    );
-    let mut builder = wallet.build_tx();
-    builder
-        .add_recipient(addr.script_pubkey(), balance.confirmed / 2)
-        .current_height(maturity_time);
-    builder.finish().unwrap();
-}
-
-#[test]
-fn test_allow_dust_limit() {
-    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-
-    let mut builder = wallet.build_tx();
-
-    builder.add_recipient(addr.script_pubkey(), Amount::ZERO);
-
-    assert_matches!(
-        builder.finish(),
-        Err(CreateTxError::OutputBelowDustLimit(0))
-    );
-
-    let mut builder = wallet.build_tx();
-
-    builder
-        .allow_dust(true)
-        .add_recipient(addr.script_pubkey(), Amount::ZERO);
-
-    assert!(builder.finish().is_ok());
-}
-
-#[test]
-fn test_fee_rate_sign_no_grinding_high_r() {
-    // Our goal is to obtain a transaction with a signature with high-R (71 bytes
-    // instead of 70). We then check that our fee rate and fee calculation is
-    // alright.
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
-    let mut builder = wallet.build_tx();
-    let mut data = PushBytesBuf::try_from(vec![0]).unwrap();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_rate(fee_rate)
-        .add_data(&data);
-    let mut psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-    let (op_return_vout, _) = psbt
-        .unsigned_tx
-        .output
-        .iter()
-        .enumerate()
-        .find(|(_n, i)| i.script_pubkey.is_op_return())
-        .unwrap();
-
-    let mut sig_len: usize = 0;
-    // We try to sign many different times until we find a longer signature (71 bytes)
-    while sig_len < 71 {
-        // Changing the OP_RETURN data will make the signature change (but not the fee, until
-        // data[0] is small enough)
-        data.as_mut_bytes()[0] += 1;
-        psbt.unsigned_tx.output[op_return_vout].script_pubkey = ScriptBuf::new_op_return(&data);
-        // Clearing the previous signature
-        psbt.inputs[0].partial_sigs.clear();
-        // Signing
-        wallet
-            .sign(
-                &mut psbt,
-                SignOptions {
-                    remove_partial_sigs: false,
-                    try_finalize: false,
-                    allow_grinding: false,
-                    ..Default::default()
-                },
-            )
-            .unwrap();
-        // We only have one key in the partial_sigs map, this is a trick to retrieve it
-        let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
-        sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
-    }
-    // Actually finalizing the transaction...
-    wallet
-        .sign(
-            &mut psbt,
-            SignOptions {
-                remove_partial_sigs: false,
-                allow_grinding: false,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-    // ...and checking that everything is fine
-    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
-}
-
-#[test]
-fn test_fee_rate_sign_grinding_low_r() {
-    // Our goal is to obtain a transaction with a signature with low-R (70 bytes)
-    // by setting the `allow_grinding` signing option as true.
-    // We then check that our fee rate and fee calculation is alright and that our
-    // signature is 70 bytes.
-    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
-    let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
-    let mut builder = wallet.build_tx();
-    builder
-        .drain_to(addr.script_pubkey())
-        .drain_wallet()
-        .fee_rate(fee_rate);
-    let mut psbt = builder.finish().unwrap();
-    let fee = check_fee!(wallet, psbt);
-
-    wallet
-        .sign(
-            &mut psbt,
-            SignOptions {
-                remove_partial_sigs: false,
-                allow_grinding: true,
-                ..Default::default()
-            },
-        )
-        .unwrap();
-
-    let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
-    let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
-    assert_eq!(sig_len, 70);
-    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
-}
-
-#[test]
-fn test_taproot_load_descriptor_duplicated_keys() {
-    // Added after issue https://github.com/bitcoindevkit/bdk/issues/760
-    //
-    // Having the same key in multiple taproot leaves is safe and should be accepted by BDK
-
-    let (wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
-    let addr = wallet.peek_address(KeychainKind::External, 0);
-
-    assert_eq!(
-        addr.to_string(),
-        "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5"
-    );
-}
-
-#[test]
-/// The wallet should re-use previously allocated change addresses when the tx using them is cancelled
-fn test_tx_cancellation() {
-    macro_rules! new_tx {
-        ($wallet:expr) => {{
-            let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
-                .unwrap()
-                .assume_checked();
-            let mut builder = $wallet.build_tx();
-            builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000));
-
-            let psbt = builder.finish().unwrap();
-
-            psbt
-        }};
-    }
-
-    let (mut wallet, _) =
-        get_funded_wallet_with_change(get_test_wpkh(), Some(get_test_tr_single_sig_xprv()));
-
-    let psbt1 = new_tx!(wallet);
-    let change_derivation_1 = psbt1
-        .unsigned_tx
-        .output
-        .iter()
-        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
-        .unwrap();
-    assert_eq!(change_derivation_1, (KeychainKind::Internal, 0));
-
-    let psbt2 = new_tx!(wallet);
-
-    let change_derivation_2 = psbt2
-        .unsigned_tx
-        .output
-        .iter()
-        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
-        .unwrap();
-    assert_eq!(change_derivation_2, (KeychainKind::Internal, 1));
-
-    wallet.cancel_tx(&psbt1.extract_tx().expect("failed to extract tx"));
-
-    let psbt3 = new_tx!(wallet);
-    let change_derivation_3 = psbt3
-        .unsigned_tx
-        .output
-        .iter()
-        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
-        .unwrap();
-    assert_eq!(change_derivation_3, (KeychainKind::Internal, 0));
-
-    let psbt3 = new_tx!(wallet);
-    let change_derivation_3 = psbt3
-        .unsigned_tx
-        .output
-        .iter()
-        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
-        .unwrap();
-    assert_eq!(change_derivation_3, (KeychainKind::Internal, 2));
-
-    wallet.cancel_tx(&psbt3.extract_tx().expect("failed to extract tx"));
-
-    let psbt3 = new_tx!(wallet);
-    let change_derivation_4 = psbt3
-        .unsigned_tx
-        .output
-        .iter()
-        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
-        .unwrap();
-    assert_eq!(change_derivation_4, (KeychainKind::Internal, 2));
-}
-
-#[test]
-fn test_thread_safety() {
-    fn thread_safe<T: Send + Sync>() {}
-    thread_safe::<Wallet>(); // compiles only if true
-}
index 711a2f9485e05118ac19e1d39aeccd53fdb29880..972569c7ede04106ea33c0b35940a2af50464ffa 100644 (file)
@@ -9,5 +9,5 @@ license = "MIT OR Apache-2.0"
 readme = "README.md"
 
 [dependencies]
-bdk = { path = "../bdk" }
+bdk_wallet = { path = "../wallet" }
 hwi = { version = "0.8.0", features = [ "miniscript"] }
index ab87e8a8719f97ff5080f086875e1d2aa606cc4d..129ceac24831e30bca6cc6ab256576cc312b952d 100644 (file)
@@ -3,10 +3,10 @@
 //! This crate contains HWISigner, an implementation of a [`TransactionSigner`] to be
 //! used with hardware wallets.
 //! ```no_run
-//! # use bdk::bitcoin::Network;
-//! # use bdk::signer::SignerOrdering;
+//! # use bdk_wallet::bitcoin::Network;
+//! # use bdk_wallet::signer::SignerOrdering;
 //! # use bdk_hwi::HWISigner;
-//! # use bdk::{KeychainKind, SignOptions, Wallet};
+//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
 //! # use hwi::HWIClient;
 //! # use std::sync::Arc;
 //! #
@@ -35,7 +35,7 @@
 //! # }
 //! ```
 //!
-//! [`TransactionSigner`]: bdk::wallet::signer::TransactionSigner
+//! [`TransactionSigner`]: bdk_wallet::wallet::signer::TransactionSigner
 
 mod signer;
 pub use signer::*;
index a297291ce2a54d53acbddf1c74df19b2e3be8501..bbb626619661b33b72d8ee3f936f9ded951def49 100644 (file)
@@ -1,12 +1,12 @@
-use bdk::bitcoin::bip32::Fingerprint;
-use bdk::bitcoin::secp256k1::{All, Secp256k1};
-use bdk::bitcoin::Psbt;
+use bdk_wallet::bitcoin::bip32::Fingerprint;
+use bdk_wallet::bitcoin::secp256k1::{All, Secp256k1};
+use bdk_wallet::bitcoin::Psbt;
 
 use hwi::error::Error;
 use hwi::types::{HWIChain, HWIDevice};
 use hwi::HWIClient;
 
-use bdk::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
+use bdk_wallet::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
 
 #[derive(Debug)]
 /// Custom signer for Hardware Wallets
@@ -38,7 +38,7 @@ impl TransactionSigner for HWISigner {
     fn sign_transaction(
         &self,
         psbt: &mut Psbt,
-        _sign_options: &bdk::SignOptions,
+        _sign_options: &bdk_wallet::SignOptions,
         _secp: &Secp256k1<All>,
     ) -> Result<(), SignerError> {
         psbt.combine(
@@ -61,9 +61,9 @@ impl TransactionSigner for HWISigner {
 //     fn test_hardware_signer() {
 //         use std::sync::Arc;
 //
-//         use bdk::tests::get_funded_wallet;
-//         use bdk::signer::SignerOrdering;
-//         use bdk::bitcoin::Network;
+//         use bdk_wallet::tests::get_funded_wallet;
+//         use bdk_wallet::signer::SignerOrdering;
+//         use bdk_wallet::bitcoin::Network;
 //         use crate::HWISigner;
 //         use hwi::HWIClient;
 //
@@ -78,12 +78,12 @@ impl TransactionSigner for HWISigner {
 //
 //         let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
 //         wallet.add_signer(
-//             bdk::KeychainKind::External,
+//             bdk_wallet::KeychainKind::External,
 //             SignerOrdering(200),
 //             Arc::new(custom_signer),
 //         );
 //
-//         let addr = wallet.get_address(bdk::wallet::AddressIndex::LastUnused);
+//         let addr = wallet.get_address(bdk_wallet::wallet::AddressIndex::LastUnused);
 //         let mut builder = wallet.build_tx();
 //         builder.drain_to(addr.script_pubkey()).drain_wallet();
 //         let (mut psbt, _) = builder.finish().unwrap();
diff --git a/crates/wallet/Cargo.toml b/crates/wallet/Cargo.toml
new file mode 100644 (file)
index 0000000..10e428c
--- /dev/null
@@ -0,0 +1,63 @@
+[package]
+name = "bdk_wallet"
+homepage = "https://bitcoindevkit.org"
+version = "1.0.0-alpha.11"
+repository = "https://github.com/bitcoindevkit/bdk"
+documentation = "https://docs.rs/bdk"
+description = "A modern, lightweight, descriptor-based wallet library"
+keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
+readme = "README.md"
+license = "MIT OR Apache-2.0"
+authors = ["Bitcoin Dev Kit Developers"]
+edition = "2021"
+rust-version = "1.63"
+
+[dependencies]
+anyhow = { version = "1", default-features = false }
+rand = "^0.8"
+miniscript = { version = "11.0.0", features = ["serde"], default-features = false }
+bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false }
+serde = { version = "^1.0", features = ["derive"] }
+serde_json = { version = "^1.0" }
+bdk_chain = { path = "../chain", version = "0.14.0", features = ["miniscript", "serde"], default-features = false }
+bdk_persist = { path = "../persist", version = "0.2.0" }
+
+# Optional dependencies
+bip39 = { version = "2.0", optional = true }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+getrandom = "0.2"
+js-sys = "0.3"
+
+[features]
+default = ["std"]
+std = ["bitcoin/std", "miniscript/std", "bdk_chain/std"]
+compiler = ["miniscript/compiler"]
+all-keys = ["keys-bip39"]
+keys-bip39 = ["bip39"]
+
+# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
+# for libraries to explicitly include the "getrandom/js" feature, so we only do it when
+# necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
+dev-getrandom-wasm = ["getrandom/js"]
+
+[dev-dependencies]
+lazy_static = "1.4"
+assert_matches = "1.5.0"
+tempfile = "3"
+bdk_file_store = { path = "../file_store" }
+anyhow = "1"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[[example]]
+name = "mnemonic_to_descriptors"
+path = "examples/mnemonic_to_descriptors.rs"
+required-features = ["all-keys"]
+
+[[example]]
+name = "miniscriptc"
+path = "examples/compiler.rs"
+required-features = ["compiler"]
diff --git a/crates/wallet/README.md b/crates/wallet/README.md
new file mode 100644 (file)
index 0000000..37e1618
--- /dev/null
@@ -0,0 +1,228 @@
+<div align="center">
+  <h1>BDK</h1>
+
+  <img src="https://raw.githubusercontent.com/bitcoindevkit/bdk/master/static/bdk.png" width="220" />
+
+  <p>
+    <strong>A modern, lightweight, descriptor-based wallet library written in Rust!</strong>
+  </p>
+
+  <p>
+    <a href="https://crates.io/crates/bdk_wallet"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk_wallet.svg"/></a>
+    <a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
+    <a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
+    <a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
+    <a href="https://docs.rs/bdk_wallet"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk_wallet-green"/></a>
+    <a href="https://blog.rust-lang.org/2022/08/11/Rust-1.63.0.html"><img alt="Rustc Version 1.63.0+" src="https://img.shields.io/badge/rustc-1.63.0%2B-lightgrey.svg"/></a>
+    <a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
+  </p>
+
+  <h4>
+    <a href="https://bitcoindevkit.org">Project Homepage</a>
+    <span> | </span>
+    <a href="https://docs.rs/bdk_wallet">Documentation</a>
+  </h4>
+</div>
+
+# BDK Wallet
+
+The `bdk_wallet` crate provides the [`Wallet`] type which is a simple, high-level
+interface built from the low-level components of [`bdk_chain`]. `Wallet` is a good starting point
+for many simple applications as well as a good demonstration of how to use the other mechanisms to
+construct a wallet. It has two keychains (external and internal) which are defined by
+[miniscript descriptors][`rust-miniscript`] and uses them to generate addresses. When you give it
+chain data it also uses the descriptors to find transaction outputs owned by them. From there, you
+can create and sign transactions.
+
+For details about the API of `Wallet` see the [module-level documentation][`Wallet`].
+
+## Blockchain data
+
+In order to get blockchain data for `Wallet` to consume, you should configure a client from
+an available chain source. Typically you make a request to the chain source and get a response
+that the `Wallet` can use to update its view of the chain.
+
+**Blockchain Data Sources**
+
+* [`bdk_esplora`]: Grabs blockchain data from Esplora for updating BDK structures.
+* [`bdk_electrum`]: Grabs blockchain data from Electrum for updating BDK structures.
+* [`bdk_bitcoind_rpc`]: Grabs blockchain data from Bitcoin Core for updating BDK structures.
+
+**Examples**
+
+* [`example-crates/wallet_esplora_async`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_esplora_async)
+* [`example-crates/wallet_esplora_blocking`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_esplora_blocking)
+* [`example-crates/wallet_electrum`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_electrum)
+* [`example-crates/wallet_rpc`](https://github.com/bitcoindevkit/bdk/tree/master/example-crates/wallet_rpc)
+
+## Persistence
+
+To persist the `Wallet` on disk, it must be constructed with a [`PersistBackend`] implementation.
+
+**Implementations**
+
+* [`bdk_file_store`]: A simple flat-file implementation of [`PersistBackend`].
+
+**Example**
+
+<!-- compile_fail because outpoint and txout are fake variables -->
+```rust,compile_fail
+use bdk_wallet::{bitcoin::Network, wallet::{ChangeSet, Wallet}};
+
+fn main() {
+    // Create a new file `Store`.
+    let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
+
+    let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
+    let mut wallet = Wallet::new_or_load(descriptor, None, db, Network::Testnet).expect("create or load wallet");
+
+    // Insert a single `TxOut` at `OutPoint` into the wallet.
+    let _ = wallet.insert_txout(outpoint, txout);
+    wallet.commit().expect("must write to database");
+}
+```
+
+<!-- ### Sync the balance of a descriptor -->
+
+<!-- ```rust,no_run -->
+<!-- use bdk_wallet::Wallet; -->
+<!-- use bdk_wallet::blockchain::ElectrumBlockchain; -->
+<!-- use bdk_wallet::SyncOptions; -->
+<!-- use bdk_wallet::electrum_client::Client; -->
+<!-- use bdk_wallet::bitcoin::Network; -->
+
+<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
+<!--     let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
+<!--     let wallet = Wallet::new( -->
+<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
+<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
+<!--         Network::Testnet, -->
+<!--     )?; -->
+
+<!--     wallet.sync(&blockchain, SyncOptions::default())?; -->
+
+<!--     println!("Descriptor balance: {} SAT", wallet.get_balance()?); -->
+
+<!--     Ok(()) -->
+<!-- } -->
+<!-- ``` -->
+<!-- ### Generate a few addresses -->
+
+<!-- ```rust -->
+<!-- use bdk_wallet::Wallet; -->
+<!-- use bdk_wallet::wallet::AddressIndex::New; -->
+<!-- use bdk_wallet::bitcoin::Network; -->
+
+<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
+<!--     let wallet = Wallet::new_no_persist( -->
+<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
+<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
+<!--         Network::Testnet, -->
+<!--     )?; -->
+
+<!--     println!("Address #0: {}", wallet.get_address(New)); -->
+<!--     println!("Address #1: {}", wallet.get_address(New)); -->
+<!--     println!("Address #2: {}", wallet.get_address(New)); -->
+
+<!--     Ok(()) -->
+<!-- } -->
+<!-- ``` -->
+
+<!-- ### Create a transaction -->
+
+<!-- ```rust,no_run -->
+<!-- use bdk_wallet::{FeeRate, Wallet, SyncOptions}; -->
+<!-- use bdk_wallet::blockchain::ElectrumBlockchain; -->
+
+<!-- use bdk_wallet::electrum_client::Client; -->
+<!-- use bdk_wallet::wallet::AddressIndex::New; -->
+
+<!-- use bitcoin::base64; -->
+<!-- use bdk_wallet::bitcoin::consensus::serialize; -->
+<!-- use bdk_wallet::bitcoin::Network; -->
+
+<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
+<!--     let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); -->
+<!--     let wallet = Wallet::new_no_persist( -->
+<!--         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -->
+<!--         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -->
+<!--         Network::Testnet, -->
+<!--     )?; -->
+
+<!--     wallet.sync(&blockchain, SyncOptions::default())?; -->
+
+<!--     let send_to = wallet.get_address(New); -->
+<!--     let (psbt, details) = { -->
+<!--         let mut builder = wallet.build_tx(); -->
+<!--         builder -->
+<!--             .add_recipient(send_to.script_pubkey(), 50_000) -->
+<!--             .enable_rbf() -->
+<!--             .do_not_spend_change() -->
+<!--             .fee_rate(FeeRate::from_sat_per_vb(5.0)); -->
+<!--         builder.finish()? -->
+<!--     }; -->
+
+<!--     println!("Transaction details: {:#?}", details); -->
+<!--     println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); -->
+
+<!--     Ok(()) -->
+<!-- } -->
+<!-- ``` -->
+
+<!-- ### Sign a transaction -->
+
+<!-- ```rust,no_run -->
+<!-- use bdk_wallet::{Wallet, SignOptions}; -->
+
+<!-- use bitcoin::base64; -->
+<!-- use bdk_wallet::bitcoin::consensus::deserialize; -->
+<!-- use bdk_wallet::bitcoin::Network; -->
+
+<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
+<!--     let wallet = Wallet::new_no_persist( -->
+<!--         "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", -->
+<!--         Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), -->
+<!--         Network::Testnet, -->
+<!--     )?; -->
+
+<!--     let psbt = "..."; -->
+<!--     let mut psbt = deserialize(&base64::decode(psbt).unwrap())?; -->
+
+<!--     let _finalized = wallet.sign(&mut psbt, SignOptions::default())?; -->
+
+<!--     Ok(()) -->
+<!-- } -->
+<!-- ``` -->
+
+## Testing
+
+### Unit testing
+
+```bash
+cargo test
+```
+
+# License
+
+Licensed under either of
+
+* Apache License, Version 2.0, ([LICENSE-APACHE](../../LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
+* MIT license ([LICENSE-MIT](../../LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
+
+at your option.
+
+# Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
+
+[`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html
+[`PersistBackend`]: https://docs.rs/bdk_chain/latest/bdk_chain/trait.PersistBackend.html
+[`bdk_chain`]: https://docs.rs/bdk_chain/latest
+[`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
+[`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
+[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
+[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
+[`rust-miniscript`]: https://docs.rs/miniscript/latest/miniscript/index.html
diff --git a/crates/wallet/examples/compiler.rs b/crates/wallet/examples/compiler.rs
new file mode 100644 (file)
index 0000000..116df47
--- /dev/null
@@ -0,0 +1,65 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+extern crate bdk_wallet;
+extern crate bitcoin;
+extern crate miniscript;
+extern crate serde_json;
+
+use std::error::Error;
+use std::str::FromStr;
+
+use bitcoin::Network;
+use miniscript::policy::Concrete;
+use miniscript::Descriptor;
+
+use bdk_wallet::{KeychainKind, Wallet};
+
+/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
+/// rust-miniscript library here  https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
+/// rust-miniscript provides a `compile()` function that can be used to compile any miniscript policy
+/// into a descriptor. This descriptor then in turn can be used in bdk a fully functioning wallet
+/// can be derived from the policy.
+///
+/// This example demonstrates the interaction between a bdk wallet and miniscript policy.
+
+fn main() -> Result<(), Box<dyn Error>> {
+    // We start with a generic miniscript policy string
+    let policy_str = "or(10@thresh(4,pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)),1@and(older(4209713),thresh(2,pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068))))";
+    println!("Compiling policy: \n{}", policy_str);
+
+    // Parse the string as a [`Concrete`] type miniscript policy.
+    let policy = Concrete::<String>::from_str(policy_str)?;
+
+    // Create a `wsh` type descriptor from the policy.
+    // `policy.compile()` returns the resulting miniscript from the policy.
+    let descriptor = Descriptor::new_wsh(policy.compile()?)?;
+
+    println!("Compiled into following Descriptor: \n{}", descriptor);
+
+    // Create a new wallet from this descriptor
+    let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?;
+
+    println!(
+        "First derived address from the descriptor: \n{}",
+        wallet.next_unused_address(KeychainKind::External)?,
+    );
+
+    // BDK also has it's own `Policy` structure to represent the spending condition in a more
+    // human readable json format.
+    let spending_policy = wallet.policies(KeychainKind::External)?;
+    println!(
+        "The BDK spending policy: \n{}",
+        serde_json::to_string_pretty(&spending_policy)?
+    );
+
+    Ok(())
+}
diff --git a/crates/wallet/examples/mnemonic_to_descriptors.rs b/crates/wallet/examples/mnemonic_to_descriptors.rs
new file mode 100644 (file)
index 0000000..76c53cf
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use anyhow::anyhow;
+use bdk_wallet::bitcoin::bip32::DerivationPath;
+use bdk_wallet::bitcoin::secp256k1::Secp256k1;
+use bdk_wallet::bitcoin::Network;
+use bdk_wallet::descriptor;
+use bdk_wallet::descriptor::IntoWalletDescriptor;
+use bdk_wallet::keys::bip39::{Language, Mnemonic, WordCount};
+use bdk_wallet::keys::{GeneratableKey, GeneratedKey};
+use bdk_wallet::miniscript::Tap;
+use std::str::FromStr;
+
+/// This example demonstrates how to generate a mnemonic phrase
+/// using BDK and use that to generate a descriptor string.
+fn main() -> Result<(), anyhow::Error> {
+    let secp = Secp256k1::new();
+
+    // In this example we are generating a 12 words mnemonic phrase
+    // but it is also possible generate 15, 18, 21 and 24 words
+    // using their respective `WordCount` variant.
+    let mnemonic: GeneratedKey<_, Tap> =
+        Mnemonic::generate((WordCount::Words12, Language::English))
+            .map_err(|_| anyhow!("Mnemonic generation error"))?;
+
+    println!("Mnemonic phrase: {}", *mnemonic);
+    let mnemonic_with_passphrase = (mnemonic, None);
+
+    // define external and internal derivation key path
+    let external_path = DerivationPath::from_str("m/86h/1h/0h/0").unwrap();
+    let internal_path = DerivationPath::from_str("m/86h/1h/0h/1").unwrap();
+
+    // generate external and internal descriptor from mnemonic
+    let (external_descriptor, ext_keymap) =
+        descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
+            .into_wallet_descriptor(&secp, Network::Testnet)?;
+    let (internal_descriptor, int_keymap) =
+        descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
+            .into_wallet_descriptor(&secp, Network::Testnet)?;
+
+    println!("tpub external descriptor: {}", external_descriptor);
+    println!("tpub internal descriptor: {}", internal_descriptor);
+    println!(
+        "tprv external descriptor: {}",
+        external_descriptor.to_string_with_secret(&ext_keymap)
+    );
+    println!(
+        "tprv internal descriptor: {}",
+        internal_descriptor.to_string_with_secret(&int_keymap)
+    );
+
+    Ok(())
+}
diff --git a/crates/wallet/examples/policy.rs b/crates/wallet/examples/policy.rs
new file mode 100644 (file)
index 0000000..e748c3f
--- /dev/null
@@ -0,0 +1,60 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+extern crate bdk_wallet;
+use std::error::Error;
+
+use bdk_wallet::bitcoin::Network;
+use bdk_wallet::descriptor::{policy::BuildSatisfaction, ExtractPolicy, IntoWalletDescriptor};
+use bdk_wallet::wallet::signer::SignersContainer;
+
+/// This example describes the use of the BDK's [`bdk_wallet::descriptor::policy`] module.
+///
+/// Policy is higher abstraction representation of the wallet descriptor spending condition.
+/// This is useful to express complex miniscript spending conditions into more human readable form.
+/// The resulting `Policy` structure  can be used to derive spending conditions the wallet is capable
+/// to spend from.
+///
+/// This example demos a Policy output for a 2of2 multisig between between 2 parties, where the wallet holds
+/// one of the Extend Private key.
+
+fn main() -> Result<(), Box<dyn Error>> {
+    let secp = bitcoin::secp256k1::Secp256k1::new();
+
+    // The descriptor used in the example
+    // The form is "wsh(multi(2, <privkey>, <pubkey>))"
+    let desc = "wsh(multi(2,tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/*))";
+
+    // Use the descriptor string to derive the full descriptor and a keymap.
+    // The wallet descriptor can be used to create a new bdk_wallet::wallet.
+    // While the `keymap` can be used to create a `SignerContainer`.
+    //
+    // The `SignerContainer` can sign for `PSBT`s.
+    // a bdk_wallet::wallet internally uses these to handle transaction signing.
+    // But they can be used as independent tools also.
+    let (wallet_desc, keymap) = desc.into_wallet_descriptor(&secp, Network::Testnet)?;
+
+    println!("Example Descriptor for policy analysis : {}", wallet_desc);
+
+    // Create the signer with the keymap and descriptor.
+    let signers_container = SignersContainer::build(keymap, &wallet_desc, &secp);
+
+    // Extract the Policy from the given descriptor and signer.
+    // Note that Policy is a wallet specific structure. It depends on the the descriptor, and
+    // what the concerned wallet with a given signer can sign for.
+    let policy = wallet_desc
+        .extract_policy(&signers_container, BuildSatisfaction::None, &secp)?
+        .expect("We expect a policy");
+
+    println!("Derived Policy for the descriptor {:#?}", policy);
+
+    Ok(())
+}
diff --git a/crates/wallet/src/descriptor/checksum.rs b/crates/wallet/src/descriptor/checksum.rs
new file mode 100644 (file)
index 0000000..243376b
--- /dev/null
@@ -0,0 +1,147 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptor checksum
+//!
+//! This module contains a re-implementation of the function used by Bitcoin Core to calculate the
+//! checksum of a descriptor
+
+use crate::descriptor::DescriptorError;
+use alloc::string::String;
+
+const INPUT_CHARSET: &[u8] = b"0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
+const CHECKSUM_CHARSET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+
+fn poly_mod(mut c: u64, val: u64) -> u64 {
+    let c0 = c >> 35;
+    c = ((c & 0x7ffffffff) << 5) ^ val;
+    if c0 & 1 > 0 {
+        c ^= 0xf5dee51989
+    };
+    if c0 & 2 > 0 {
+        c ^= 0xa9fdca3312
+    };
+    if c0 & 4 > 0 {
+        c ^= 0x1bab10e32d
+    };
+    if c0 & 8 > 0 {
+        c ^= 0x3706b1677a
+    };
+    if c0 & 16 > 0 {
+        c ^= 0x644d626ffd
+    };
+
+    c
+}
+
+/// Compute the checksum bytes of a descriptor, excludes any existing checksum in the descriptor string from the calculation
+pub fn calc_checksum_bytes(mut desc: &str) -> Result<[u8; 8], DescriptorError> {
+    let mut c = 1;
+    let mut cls = 0;
+    let mut clscount = 0;
+
+    let mut original_checksum = None;
+    if let Some(split) = desc.split_once('#') {
+        desc = split.0;
+        original_checksum = Some(split.1);
+    }
+
+    for ch in desc.as_bytes() {
+        let pos = INPUT_CHARSET
+            .iter()
+            .position(|b| b == ch)
+            .ok_or(DescriptorError::InvalidDescriptorCharacter(*ch))? as u64;
+        c = poly_mod(c, pos & 31);
+        cls = cls * 3 + (pos >> 5);
+        clscount += 1;
+        if clscount == 3 {
+            c = poly_mod(c, cls);
+            cls = 0;
+            clscount = 0;
+        }
+    }
+    if clscount > 0 {
+        c = poly_mod(c, cls);
+    }
+    (0..8).for_each(|_| c = poly_mod(c, 0));
+    c ^= 1;
+
+    let mut checksum = [0_u8; 8];
+    for j in 0..8 {
+        checksum[j] = CHECKSUM_CHARSET[((c >> (5 * (7 - j))) & 31) as usize];
+    }
+
+    // if input data already had a checksum, check calculated checksum against original checksum
+    if let Some(original_checksum) = original_checksum {
+        if original_checksum.as_bytes() != checksum {
+            return Err(DescriptorError::InvalidDescriptorChecksum);
+        }
+    }
+
+    Ok(checksum)
+}
+
+/// Compute the checksum of a descriptor, excludes any existing checksum in the descriptor string from the calculation
+pub fn calc_checksum(desc: &str) -> Result<String, DescriptorError> {
+    // unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
+    calc_checksum_bytes(desc).map(|b| unsafe { String::from_utf8_unchecked(b.to_vec()) })
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::descriptor::calc_checksum;
+    use assert_matches::assert_matches;
+
+    // test calc_checksum() function; it should return the same value as Bitcoin Core
+    #[test]
+    fn test_calc_checksum() {
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)";
+        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
+
+        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)";
+        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
+    }
+
+    // test calc_checksum() function; it should return the same value as Bitcoin Core even if the
+    // descriptor string includes a checksum hash
+    #[test]
+    fn test_calc_checksum_with_checksum_hash() {
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62";
+        assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
+
+        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmfs";
+        assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
+        assert_matches!(
+            calc_checksum(desc),
+            Err(DescriptorError::InvalidDescriptorChecksum)
+        );
+
+        let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
+        assert_matches!(
+            calc_checksum(desc),
+            Err(DescriptorError::InvalidDescriptorChecksum)
+        );
+    }
+
+    #[test]
+    fn test_calc_checksum_invalid_character() {
+        let sparkle_heart = unsafe { core::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
+        let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
+
+        assert_matches!(
+            calc_checksum(&invalid_desc),
+            Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
+        );
+    }
+}
diff --git a/crates/wallet/src/descriptor/dsl.rs b/crates/wallet/src/descriptor/dsl.rs
new file mode 100644 (file)
index 0000000..0d7e7c8
--- /dev/null
@@ -0,0 +1,1217 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptors DSL
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_top_level_sh {
+    // disallow `sortedmulti` in `bare()`
+    ( Bare, new, new, Legacy, sortedmulti $( $inner:tt )* ) => {
+        compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands");
+    };
+    ( Bare, new, new, Legacy, sortedmulti_vec $( $inner:tt )* ) => {
+        compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec()` operands");
+    };
+
+    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti $( $inner:tt )* ) => {{
+        use core::marker::PhantomData;
+
+        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
+        use $crate::miniscript::$ctx;
+
+        let build_desc = |k, pks| {
+            Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
+        };
+
+        $crate::impl_sortedmulti!(build_desc, sortedmulti $( $inner )*)
+    }};
+    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti_vec $( $inner:tt )* ) => {{
+        use core::marker::PhantomData;
+
+        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
+        use $crate::miniscript::$ctx;
+
+        let build_desc = |k, pks| {
+            Ok((Descriptor::<DescriptorPublicKey>::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>))
+        };
+
+        $crate::impl_sortedmulti!(build_desc, sortedmulti_vec $( $inner )*)
+    }};
+
+    ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, $( $minisc:tt )* ) => {{
+        use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey};
+
+        $crate::fragment!($( $minisc )*)
+            .and_then(|(minisc, keymap, networks)| Ok(($inner_struct::$constructor(minisc)?, keymap, networks)))
+            .and_then(|(inner, key_map, valid_networks)| Ok((Descriptor::<DescriptorPublicKey>::$inner_struct(inner), key_map, valid_networks)))
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_top_level_pk {
+    ( $inner_type:ident, $ctx:ty, $key:expr ) => {{
+        use $crate::miniscript::descriptor::$inner_type;
+
+        #[allow(unused_imports)]
+        use $crate::keys::{DescriptorKey, IntoDescriptorKey};
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+
+        $key.into_descriptor_key()
+            .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
+            .map_err($crate::descriptor::DescriptorError::Key)
+            .map(|(pk, key_map, valid_networks)| ($inner_type::new(pk), key_map, valid_networks))
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_top_level_tr {
+    ( $internal_key:expr, $tap_tree:expr ) => {{
+        use $crate::miniscript::descriptor::{
+            Descriptor, DescriptorPublicKey, KeyMap, TapTree, Tr,
+        };
+        use $crate::miniscript::Tap;
+
+        #[allow(unused_imports)]
+        use $crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
+
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+
+        $internal_key
+            .into_descriptor_key()
+            .and_then(|key: DescriptorKey<Tap>| key.extract(&secp))
+            .map_err($crate::descriptor::DescriptorError::Key)
+            .and_then(|(pk, mut key_map, mut valid_networks)| {
+                let tap_tree = $tap_tree.map(
+                    |(tap_tree, tree_keymap, tree_networks): (
+                        TapTree<DescriptorPublicKey>,
+                        KeyMap,
+                        ValidNetworks,
+                    )| {
+                        key_map.extend(tree_keymap.into_iter());
+                        valid_networks =
+                            $crate::keys::merge_networks(&valid_networks, &tree_networks);
+
+                        tap_tree
+                    },
+                );
+
+                Ok((
+                    Descriptor::<DescriptorPublicKey>::Tr(Tr::new(pk, tap_tree)?),
+                    key_map,
+                    valid_networks,
+                ))
+            })
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_leaf_opcode {
+    ( $terminal_variant:ident ) => {{
+        use $crate::descriptor::CheckMiniscript;
+
+        $crate::miniscript::Miniscript::from_ast(
+            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
+        )
+        .map_err($crate::descriptor::DescriptorError::Miniscript)
+        .and_then(|minisc| {
+            minisc.check_miniscript()?;
+            Ok(minisc)
+        })
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_leaf_opcode_value {
+    ( $terminal_variant:ident, $value:expr ) => {{
+        use $crate::descriptor::CheckMiniscript;
+
+        $crate::miniscript::Miniscript::from_ast(
+            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
+        )
+        .map_err($crate::descriptor::DescriptorError::Miniscript)
+        .and_then(|minisc| {
+            minisc.check_miniscript()?;
+            Ok(minisc)
+        })
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_leaf_opcode_value_two {
+    ( $terminal_variant:ident, $one:expr, $two:expr ) => {{
+        use $crate::descriptor::CheckMiniscript;
+
+        $crate::miniscript::Miniscript::from_ast(
+            $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
+        )
+        .map_err($crate::descriptor::DescriptorError::Miniscript)
+        .and_then(|minisc| {
+            minisc.check_miniscript()?;
+            Ok(minisc)
+        })
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_node_opcode_two {
+    ( $terminal_variant:ident, $( $inner:tt )* ) => ({
+        use $crate::descriptor::CheckMiniscript;
+
+        let inner = $crate::fragment_internal!( @t $( $inner )* );
+        let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
+
+        a
+            .and_then(|a| Ok((a, b?)))
+            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks))| {
+                // join key_maps
+                a_keymap.extend(b_keymap.into_iter());
+
+                let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
+                    $crate::alloc::sync::Arc::new(a_minisc),
+                    $crate::alloc::sync::Arc::new(b_minisc),
+                ))?;
+
+                minisc.check_miniscript()?;
+
+                Ok((minisc, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
+            })
+    });
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_node_opcode_three {
+    ( $terminal_variant:ident, $( $inner:tt )* ) => ({
+        use $crate::descriptor::CheckMiniscript;
+
+        let inner = $crate::fragment_internal!( @t $( $inner )* );
+        let (a, b, c) = $crate::descriptor::dsl::TupleThree::from(inner).flattened();
+
+        a
+            .and_then(|a| Ok((a, b?, c?)))
+            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks), (c_minisc, c_keymap, c_networks))| {
+                // join key_maps
+                a_keymap.extend(b_keymap.into_iter());
+                a_keymap.extend(c_keymap.into_iter());
+
+                let networks = $crate::keys::merge_networks(&a_networks, &b_networks);
+                let networks = $crate::keys::merge_networks(&networks, &c_networks);
+
+                let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
+                    $crate::alloc::sync::Arc::new(a_minisc),
+                    $crate::alloc::sync::Arc::new(b_minisc),
+                    $crate::alloc::sync::Arc::new(c_minisc),
+                ))?;
+
+                minisc.check_miniscript()?;
+
+                Ok((minisc, a_keymap, networks))
+            })
+    });
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! impl_sortedmulti {
+    ( $build_desc:expr, sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+        $crate::keys::make_sortedmulti($thresh, $keys, $build_desc, &secp)
+    });
+    ( $build_desc:expr, sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({
+        use $crate::keys::IntoDescriptorKey;
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+
+        let keys = vec![
+            $(
+                $key.into_descriptor_key(),
+            )*
+        ];
+
+        keys.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
+            .map_err($crate::descriptor::DescriptorError::Key)
+            .and_then(|keys| $crate::keys::make_sortedmulti($thresh, keys, $build_desc, &secp))
+    });
+
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! parse_tap_tree {
+    ( @merge $tree_a:expr, $tree_b:expr) => {{
+        use $crate::miniscript::descriptor::TapTree;
+
+        $tree_a
+            .and_then(|tree_a| Ok((tree_a, $tree_b?)))
+            .and_then(|((a_tree, mut a_keymap, a_networks), (b_tree, b_keymap, b_networks))| {
+                a_keymap.extend(b_keymap.into_iter());
+                Ok((TapTree::combine(a_tree, b_tree), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
+            })
+
+    }};
+
+    // Two sub-trees
+    ( { { $( $tree_a:tt )* }, { $( $tree_b:tt )* } } ) => {{
+        let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
+        let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
+
+        $crate::parse_tap_tree!(@merge tree_a, tree_b)
+    }};
+
+    // One leaf and a sub-tree
+    ( { $op_a:ident ( $( $minisc_a:tt )* ), { $( $tree_b:tt )* } } ) => {{
+        let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
+        let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
+
+        $crate::parse_tap_tree!(@merge tree_a, tree_b)
+    }};
+    ( { { $( $tree_a:tt )* }, $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
+        let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
+        let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
+
+        $crate::parse_tap_tree!(@merge tree_a, tree_b)
+    }};
+
+    // Two leaves
+    ( { $op_a:ident ( $( $minisc_a:tt )* ), $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
+        let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
+        let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
+
+        $crate::parse_tap_tree!(@merge tree_a, tree_b)
+    }};
+
+    // Single leaf
+    ( $op:ident ( $( $minisc:tt )* ) ) => {{
+        use $crate::alloc::sync::Arc;
+        use $crate::miniscript::descriptor::TapTree;
+
+        $crate::fragment!( $op ( $( $minisc )* ) )
+            .map(|(a_minisc, a_keymap, a_networks)| (TapTree::Leaf(Arc::new(a_minisc)), a_keymap, a_networks))
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! apply_modifier {
+    ( $terminal_variant:ident, $inner:expr ) => {{
+        use $crate::descriptor::CheckMiniscript;
+
+        $inner
+            .map_err(|e| -> $crate::descriptor::DescriptorError { e.into() })
+            .and_then(|(minisc, keymap, networks)| {
+                let minisc = $crate::miniscript::Miniscript::from_ast(
+                    $crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
+                        $crate::alloc::sync::Arc::new(minisc),
+                    ),
+                )?;
+
+                minisc.check_miniscript()?;
+
+                Ok((minisc, keymap, networks))
+            })
+    }};
+
+    ( a: $inner:expr ) => {{
+        $crate::apply_modifier!(Alt, $inner)
+    }};
+    ( s: $inner:expr ) => {{
+        $crate::apply_modifier!(Swap, $inner)
+    }};
+    ( c: $inner:expr ) => {{
+        $crate::apply_modifier!(Check, $inner)
+    }};
+    ( d: $inner:expr ) => {{
+        $crate::apply_modifier!(DupIf, $inner)
+    }};
+    ( v: $inner:expr ) => {{
+        $crate::apply_modifier!(Verify, $inner)
+    }};
+    ( j: $inner:expr ) => {{
+        $crate::apply_modifier!(NonZero, $inner)
+    }};
+    ( n: $inner:expr ) => {{
+        $crate::apply_modifier!(ZeroNotEqual, $inner)
+    }};
+
+    // Modifiers expanded to other operators
+    ( t: $inner:expr ) => {{
+        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
+            $crate::impl_leaf_opcode_value_two!(
+                AndV,
+                $crate::alloc::sync::Arc::new(a_minisc),
+                $crate::alloc::sync::Arc::new($crate::fragment!(true).unwrap().0)
+            )
+            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
+        })
+    }};
+    ( l: $inner:expr ) => {{
+        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
+            $crate::impl_leaf_opcode_value_two!(
+                OrI,
+                $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0),
+                $crate::alloc::sync::Arc::new(a_minisc)
+            )
+            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
+        })
+    }};
+    ( u: $inner:expr ) => {{
+        $inner.and_then(|(a_minisc, a_keymap, a_networks)| {
+            $crate::impl_leaf_opcode_value_two!(
+                OrI,
+                $crate::alloc::sync::Arc::new(a_minisc),
+                $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0)
+            )
+            .map(|(minisc, _, _)| (minisc, a_keymap, a_networks))
+        })
+    }};
+}
+
+/// Macro to write full descriptors with code
+///
+/// This macro expands to a `Result` of
+/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`DescriptorError`](crate::descriptor::DescriptorError)
+///
+/// The syntax is very similar to the normal descriptor syntax, with the exception that modifiers
+/// cannot be grouped together. For instance, a descriptor fragment like `sdv:older(144)` has to be
+/// broken up to `s:d:v:older(144)`.
+///
+/// The `pk()`, `pk_k()` and `pk_h()` operands can take as argument any type that implements
+/// [`IntoDescriptorKey`]. This means that keys can also be written inline as strings, but in that
+/// case they must be wrapped in quotes, which is another difference compared to the standard
+/// descriptor syntax.
+///
+/// [`IntoDescriptorKey`]: crate::keys::IntoDescriptorKey
+///
+/// ## Example
+///
+/// Signature plus timelock descriptor:
+///
+/// ```
+/// # use std::str::FromStr;
+/// let (my_descriptor, my_keys_map, networks) = bdk_wallet::descriptor!(sh(wsh(and_v(v:pk("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy"),older(50)))))?;
+/// # Ok::<(), Box<dyn std::error::Error>>(())
+/// ```
+///
+/// -------
+///
+/// 2-of-3 that becomes a 1-of-3 after a timelock has expired. Both `descriptor_a` and `descriptor_b` are equivalent: the first
+/// syntax is more suitable for a fixed number of items known at compile time, while the other accepts a
+/// [`Vec`] of items, which makes it more suitable for writing dynamic descriptors.
+///
+/// They both produce the descriptor: `wsh(thresh(2,pk(...),s:pk(...),sndv:older(...)))`
+///
+/// ```
+/// # use std::str::FromStr;
+/// let my_key_1 = bitcoin::PublicKey::from_str(
+///     "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
+/// )?;
+/// let my_key_2 =
+///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
+/// let my_timelock = 50;
+///
+/// let (descriptor_a, key_map_a, networks) = bdk_wallet::descriptor! {
+///     wsh (
+///         thresh(2, pk(my_key_1), s:pk(my_key_2), s:n:d:v:older(my_timelock))
+///     )
+/// }?;
+///
+/// #[rustfmt::skip]
+/// let b_items = vec![
+///     bdk_wallet::fragment!(pk(my_key_1))?,
+///     bdk_wallet::fragment!(s:pk(my_key_2))?,
+///     bdk_wallet::fragment!(s:n:d:v:older(my_timelock))?,
+/// ];
+/// let (descriptor_b, mut key_map_b, networks) =
+///     bdk_wallet::descriptor!(wsh(thresh_vec(2, b_items)))?;
+///
+/// assert_eq!(descriptor_a, descriptor_b);
+/// assert_eq!(key_map_a.len(), key_map_b.len());
+/// # Ok::<(), Box<dyn std::error::Error>>(())
+/// ```
+///
+/// ------
+///
+/// Simple 2-of-2 multi-signature, equivalent to: `wsh(multi(2, ...))`
+///
+/// ```
+/// # use std::str::FromStr;
+/// let my_key_1 = bitcoin::PublicKey::from_str(
+///     "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
+/// )?;
+/// let my_key_2 =
+///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
+///
+/// let (descriptor, key_map, networks) = bdk_wallet::descriptor! {
+///     wsh (
+///         multi(2, my_key_1, my_key_2)
+///     )
+/// }?;
+/// # Ok::<(), Box<dyn std::error::Error>>(())
+/// ```
+///
+/// ------
+///
+/// Native-Segwit single-sig, equivalent to: `wpkh(...)`
+///
+/// ```
+/// let my_key =
+///     bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
+///
+/// let (descriptor, key_map, networks) = bdk_wallet::descriptor!(wpkh(my_key))?;
+/// # Ok::<(), Box<dyn std::error::Error>>(())
+/// ```
+///
+/// [`Vec`]: alloc::vec::Vec
+#[macro_export]
+macro_rules! descriptor {
+    ( bare ( $( $minisc:tt )* ) ) => ({
+        $crate::impl_top_level_sh!(Bare, new, new, Legacy, $( $minisc )*)
+    });
+    ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({
+        $crate::descriptor!(shwsh ($( $minisc )*))
+    });
+    ( shwsh ( $( $minisc:tt )* ) ) => ({
+        $crate::impl_top_level_sh!(Sh, new_wsh, new_wsh_sortedmulti, Segwitv0, $( $minisc )*)
+    });
+    ( pk ( $key:expr ) ) => ({
+        // `pk()` is actually implemented as `bare(pk())`
+        $crate::descriptor!( bare ( pk ( $key ) ) )
+    });
+    ( pkh ( $key:expr ) ) => ({
+        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+        $crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key)
+            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
+            .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Pkh(a), b, c))
+    });
+    ( wpkh ( $key:expr ) ) => ({
+        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+        $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
+            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
+            .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
+    });
+    ( sh ( wpkh ( $key:expr ) ) ) => ({
+        $crate::descriptor!(shwpkh ( $key ))
+    });
+    ( shwpkh ( $key:expr ) ) => ({
+        use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh};
+
+        $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
+            .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
+            .and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
+    });
+    ( sh ( $( $minisc:tt )* ) ) => ({
+        $crate::impl_top_level_sh!(Sh, new, new_sortedmulti, Legacy, $( $minisc )*)
+    });
+    ( wsh ( $( $minisc:tt )* ) ) => ({
+        $crate::impl_top_level_sh!(Wsh, new, new_sortedmulti, Segwitv0, $( $minisc )*)
+    });
+
+    ( tr ( $internal_key:expr ) ) => ({
+        $crate::impl_top_level_tr!($internal_key, None)
+    });
+    ( tr ( $internal_key:expr, $( $taptree:tt )* ) ) => ({
+        let tap_tree = $crate::parse_tap_tree!( $( $taptree )* );
+        tap_tree
+            .and_then(|tap_tree| $crate::impl_top_level_tr!($internal_key, Some(tap_tree)))
+    });
+}
+
+#[doc(hidden)]
+pub struct TupleTwo<A, B> {
+    pub a: A,
+    pub b: B,
+}
+
+impl<A, B> TupleTwo<A, B> {
+    pub fn flattened(self) -> (A, B) {
+        (self.a, self.b)
+    }
+}
+
+impl<A, B> From<(A, (B, ()))> for TupleTwo<A, B> {
+    fn from((a, (b, _)): (A, (B, ()))) -> Self {
+        TupleTwo { a, b }
+    }
+}
+
+#[doc(hidden)]
+pub struct TupleThree<A, B, C> {
+    pub a: A,
+    pub b: B,
+    pub c: C,
+}
+
+impl<A, B, C> TupleThree<A, B, C> {
+    pub fn flattened(self) -> (A, B, C) {
+        (self.a, self.b, self.c)
+    }
+}
+
+impl<A, B, C> From<(A, (B, (C, ())))> for TupleThree<A, B, C> {
+    fn from((a, (b, (c, _))): (A, (B, (C, ())))) -> Self {
+        TupleThree { a, b, c }
+    }
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! group_multi_keys {
+    ( $( $key:expr ),+ ) => {{
+        use $crate::keys::IntoDescriptorKey;
+
+        let keys = vec![
+            $(
+                $key.into_descriptor_key(),
+            )*
+        ];
+
+        keys.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
+            .map_err($crate::descriptor::DescriptorError::Key)
+    }};
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! fragment_internal {
+    // The @v prefix is used to parse a sequence of operands and return them in a vector. This is
+    // used by operands that take a variable number of arguments, like `thresh()` and `multi()`.
+    ( @v $op:ident ( $( $args:tt )* ) $( $tail:tt )* ) => ({
+        let mut v = vec![$crate::fragment!( $op ( $( $args )* ) )];
+        v.append(&mut $crate::fragment_internal!( @v $( $tail )* ));
+
+        v
+    });
+    // Match modifiers
+    ( @v $modif:tt : $( $tail:tt )* ) => ({
+        let mut v = $crate::fragment_internal!( @v $( $tail )* );
+        let first = v.drain(..1).next().unwrap();
+
+        let first = $crate::apply_modifier!($modif:first);
+
+        let mut v_final = vec![first];
+        v_final.append(&mut v);
+
+        v_final
+    });
+    // Remove commas between operands
+    ( @v , $( $tail:tt )* ) => ({
+        $crate::fragment_internal!( @v $( $tail )* )
+    });
+    ( @v ) => ({
+        vec![]
+    });
+
+    // The @t prefix is used to parse a sequence of operands and return them in a tuple. This
+    // allows checking at compile-time the number of arguments passed to an operand. For this
+    // reason it's used by `and_*()`, `or_*()`, etc.
+    //
+    // Unfortunately, due to the fact that concatenating tuples is pretty hard, the final result
+    // adds in the first spot the parsed operand and in the second spot the result of parsing
+    // all the following ones. For two operands the type then corresponds to: (X, (X, ())). For
+    // three operands it's (X, (X, (X, ()))), etc.
+    //
+    // To check that the right number of arguments has been passed we can "cast" those tuples to
+    // more convenient structures like `TupleTwo`. If the conversion succeeds, the right number of
+    // args was passed. Otherwise the compilation fails entirely.
+    ( @t $op:ident ( $( $args:tt )* ) $( $tail:tt )* ) => ({
+        ($crate::fragment!( $op ( $( $args )* ) ), $crate::fragment_internal!( @t $( $tail )* ))
+    });
+    // Match modifiers
+    ( @t $modif:tt : $( $tail:tt )* ) => ({
+        let (first, tail) = $crate::fragment_internal!( @t $( $tail )* );
+        ($crate::apply_modifier!($modif:first), tail)
+    });
+    // Remove commas between operands
+    ( @t , $( $tail:tt )* ) => ({
+        $crate::fragment_internal!( @t $( $tail )* )
+    });
+    ( @t ) => ({});
+
+    // Fallback to calling `fragment!()`
+    ( $( $tokens:tt )* ) => ({
+        $crate::fragment!($( $tokens )*)
+    });
+}
+
+/// Macro to write descriptor fragments with code
+///
+/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), DescriptorError>`. It allows writing
+/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec(m, ...))`.
+///
+/// The syntax to write macro fragment is the same as documented for the [`descriptor`] macro.
+#[macro_export]
+macro_rules! fragment {
+    // Modifiers
+    ( $modif:tt : $( $tail:tt )* ) => ({
+        let op = $crate::fragment!( $( $tail )* );
+        $crate::apply_modifier!($modif:op)
+    });
+
+    // Miniscript
+    ( true ) => ({
+        $crate::impl_leaf_opcode!(True)
+    });
+    ( false ) => ({
+        $crate::impl_leaf_opcode!(False)
+    });
+    ( pk_k ( $key:expr ) ) => ({
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+        $crate::keys::make_pk($key, &secp)
+    });
+    ( pk ( $key:expr ) ) => ({
+        $crate::fragment!(c:pk_k ( $key ))
+    });
+    ( pk_h ( $key:expr ) ) => ({
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+        $crate::keys::make_pkh($key, &secp)
+    });
+    ( after ( $value:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(After, $crate::miniscript::AbsLockTime::from_consensus($value))
+    });
+    ( older ( $value:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
+    });
+    ( sha256 ( $hash:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(Sha256, $hash)
+    });
+    ( hash256 ( $hash:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(Hash256, $hash)
+    });
+    ( ripemd160 ( $hash:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(Ripemd160, $hash)
+    });
+    ( hash160 ( $hash:expr ) ) => ({
+        $crate::impl_leaf_opcode_value!(Hash160, $hash)
+    });
+    ( and_v ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(AndV, $( $inner )*)
+    });
+    ( and_b ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(AndB, $( $inner )*)
+    });
+    ( and_or ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_three!(AndOr, $( $inner )*)
+    });
+    ( andor ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_three!(AndOr, $( $inner )*)
+    });
+    ( or_b ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(OrB, $( $inner )*)
+    });
+    ( or_d ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(OrD, $( $inner )*)
+    });
+    ( or_c ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(OrC, $( $inner )*)
+    });
+    ( or_i ( $( $inner:tt )* ) ) => ({
+        $crate::impl_node_opcode_two!(OrI, $( $inner )*)
+    });
+    ( thresh_vec ( $thresh:expr, $items:expr ) ) => ({
+        use $crate::miniscript::descriptor::KeyMap;
+
+        let (items, key_maps_networks): ($crate::alloc::vec::Vec<_>, $crate::alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip();
+        let items = items.into_iter().map($crate::alloc::sync::Arc::new).collect();
+
+        let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| {
+            keys_acc.extend(key.into_iter());
+            let net_acc = $crate::keys::merge_networks(&net_acc, &net);
+
+            (keys_acc, net_acc)
+        });
+
+        $crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
+            .map(|(minisc, _, _)| (minisc, key_maps, valid_networks))
+    });
+    ( thresh ( $thresh:expr, $( $inner:tt )* ) ) => ({
+        let items = $crate::fragment_internal!( @v $( $inner )* );
+
+        items.into_iter().collect::<Result<$crate::alloc::vec::Vec<_>, _>>()
+            .and_then(|items| $crate::fragment!(thresh_vec($thresh, items)))
+    });
+    ( multi_vec ( $thresh:expr, $keys:expr ) ) => ({
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+
+        $crate::keys::make_multi($thresh, $crate::miniscript::Terminal::Multi, $keys, &secp)
+    });
+    ( multi ( $thresh:expr $(, $key:expr )+ ) ) => ({
+        $crate::group_multi_keys!( $( $key ),* )
+            .and_then(|keys| $crate::fragment!( multi_vec ( $thresh, keys ) ))
+    });
+    ( multi_a_vec ( $thresh:expr, $keys:expr ) ) => ({
+        let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
+
+        $crate::keys::make_multi($thresh, $crate::miniscript::Terminal::MultiA, $keys, &secp)
+    });
+    ( multi_a ( $thresh:expr $(, $key:expr )+ ) ) => ({
+        $crate::group_multi_keys!( $( $key ),* )
+            .and_then(|keys| $crate::fragment!( multi_a_vec ( $thresh, keys ) ))
+    });
+
+    // `sortedmulti()` is handled separately
+    ( sortedmulti ( $( $inner:tt )* ) ) => ({
+        compile_error!("`sortedmulti` can only be used as the root operand of a descriptor");
+    });
+    ( sortedmulti_vec ( $( $inner:tt )* ) ) => ({
+        compile_error!("`sortedmulti_vec` can only be used as the root operand of a descriptor");
+    });
+}
+
+#[cfg(test)]
+mod test {
+    use alloc::string::ToString;
+    use bitcoin::secp256k1::Secp256k1;
+    use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
+    use miniscript::{Descriptor, Legacy, Segwitv0};
+
+    use core::str::FromStr;
+
+    use crate::descriptor::{DescriptorError, DescriptorMeta};
+    use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
+    use bitcoin::bip32;
+    use bitcoin::Network::{Bitcoin, Regtest, Signet, Testnet};
+    use bitcoin::PrivateKey;
+
+    // test the descriptor!() macro
+
+    // verify descriptor generates expected script(s) (if bare or pk) or address(es)
+    fn check(
+        desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
+        is_witness: bool,
+        is_fixed: bool,
+        expected: &[&str],
+    ) {
+        let (desc, _key_map, _networks) = desc.unwrap();
+        assert_eq!(desc.is_witness(), is_witness);
+        assert_eq!(!desc.has_wildcard(), is_fixed);
+        for i in 0..expected.len() {
+            let child_desc = desc
+                .at_derivation_index(i as u32)
+                .expect("i is not hardened");
+            let address = child_desc.address(Regtest);
+            if let Ok(address) = address {
+                assert_eq!(address.to_string(), *expected.get(i).unwrap());
+            } else {
+                let script = child_desc.script_pubkey();
+                assert_eq!(script.to_hex_string(), *expected.get(i).unwrap());
+            }
+        }
+    }
+
+    // - at least one of each "type" of operator; i.e. one modifier, one leaf_opcode, one leaf_opcode_value, etc.
+    // - mixing up key types that implement IntoDescriptorKey in multi() or thresh()
+
+    // expected script for pk and bare manually created
+    // expected addresses created with `bitcoin-cli getdescriptorinfo` (for hash) and `bitcoin-cli deriveaddresses`
+
+    #[test]
+    fn test_fixed_legacy_descriptors() {
+        let pubkey1 = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        let pubkey2 = bitcoin::PublicKey::from_str(
+            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
+        )
+        .unwrap();
+
+        check(
+            descriptor!(bare(multi(1,pubkey1,pubkey2))),
+            false,
+            true,
+            &["512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af52ae"],
+        );
+        check(
+            descriptor!(pk(pubkey1)),
+            false,
+            true,
+            &["2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"],
+        );
+        check(
+            descriptor!(pkh(pubkey1)),
+            false,
+            true,
+            &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
+        );
+        check(
+            descriptor!(sh(multi(1, pubkey1, pubkey2))),
+            false,
+            true,
+            &["2MymURoV1bzuMnWMGiXzyomDkeuxXY7Suey"],
+        );
+    }
+
+    #[test]
+    fn test_fixed_segwitv0_descriptors() {
+        let pubkey1 = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        let pubkey2 = bitcoin::PublicKey::from_str(
+            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
+        )
+        .unwrap();
+
+        check(
+            descriptor!(wpkh(pubkey1)),
+            true,
+            true,
+            &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
+        );
+        check(
+            descriptor!(sh(wpkh(pubkey1))),
+            true,
+            true,
+            &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
+        );
+        check(
+            descriptor!(wsh(multi(1, pubkey1, pubkey2))),
+            true,
+            true,
+            &["bcrt1qgw8jvv2hsrvjfa6q66rk6har7d32lrqm5unnf5cl63q9phxfvgps5fyfqe"],
+        );
+        check(
+            descriptor!(sh(wsh(multi(1, pubkey1, pubkey2)))),
+            true,
+            true,
+            &["2NCidRJysy7apkmE6JF5mLLaJFkrN3Ub9iy"],
+        );
+    }
+
+    #[test]
+    fn test_fixed_threeop_descriptors() {
+        let redeem_key = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        let move_key = bitcoin::PublicKey::from_str(
+            "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
+        )
+        .unwrap();
+
+        check(
+            descriptor!(sh(wsh(and_or(pk(redeem_key), older(1000), pk(move_key))))),
+            true,
+            true,
+            &["2MypGwr5eQWAWWJtiJgUEToVxc4zuokjQRe"],
+        );
+    }
+
+    #[test]
+    fn test_bip32_legacy_descriptors() {
+        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
+        check(
+            descriptor!(pk(desc_key)),
+            false,
+            false,
+            &[
+                "2102363ad03c10024e1b597a5b01b9982807fb638e00b06f3b2d4a89707de3b93c37ac",
+                "2102063a21fd780df370ed2fc8c4b86aa5ea642630609c203009df631feb7b480dd2ac",
+                "2102ba2685ad1fa5891cb100f1656b2ce3801822ccb9bac0336734a6f8c1b93ebbc0ac",
+            ],
+        );
+
+        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
+        check(
+            descriptor!(pkh(desc_key)),
+            false,
+            false,
+            &[
+                "muvBdsVpJxpFuTHMKA47htJPdCvdt4F9DP",
+                "mxQSHK7DL2t1DN3xFxov1janCoXSSkrSPj",
+                "mfz43r15GiWo4nizmyzMNubsnkDpByFFAn",
+            ],
+        );
+
+        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
+        let desc_key1 = (xprv, path).into_descriptor_key().unwrap();
+        let desc_key2 = (xprv, path2).into_descriptor_key().unwrap();
+
+        check(
+            descriptor!(sh(multi(1, desc_key1, desc_key2))),
+            false,
+            false,
+            &[
+                "2MtMDXsfwefZkEEhVViEPidvcKRUtJamJJ8",
+                "2MwAUZ1NYyWjhVvGTethFL6n7nZhS8WE6At",
+                "2MuT6Bj66HLwZd7s4SoD8XbK4GwriKEA6Gr",
+            ],
+        );
+    }
+
+    #[test]
+    fn test_bip32_segwitv0_descriptors() {
+        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
+        check(
+            descriptor!(wpkh(desc_key)),
+            true,
+            false,
+            &[
+                "bcrt1qnhm8w9fhc8cxzgqsmqdf9fyjccyvc0gltnymu0",
+                "bcrt1qhylfd55rn75w9fj06zspctad5w4hz33rf0ttad",
+                "bcrt1qq5sq3a6k9av9d8cne0k9wcldy4nqey5yt6889r",
+            ],
+        );
+
+        let desc_key = (xprv, path.clone()).into_descriptor_key().unwrap();
+        check(
+            descriptor!(sh(wpkh(desc_key))),
+            true,
+            false,
+            &[
+                "2MxvjQCaLqZ5QxZ7XotZDQ63hZw3NPss763",
+                "2NDUoevN4QMzhvHDMGhKuiT2fN9HXbFRMwn",
+                "2NF4BEAY2jF1Fu8vqfN3NVKoFtom77pUxrx",
+            ],
+        );
+
+        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
+        let desc_key1 = (xprv, path.clone()).into_descriptor_key().unwrap();
+        let desc_key2 = (xprv, path2.clone()).into_descriptor_key().unwrap();
+        check(
+            descriptor!(wsh(multi(1, desc_key1, desc_key2))),
+            true,
+            false,
+            &[
+                "bcrt1qfxv8mxmlv5sz8q2mnuyaqdfe9jr4vvmx0csjhn092p6f4qfygfkq2hng49",
+                "bcrt1qerj85g243e6jlcdxpmn9spk0gefcwvu7nw7ee059d5ydzpdhkm2qwfkf5k",
+                "bcrt1qxkl2qss3k58q9ktc8e89pwr4gnptfpw4hju4xstxcjc0hkcae3jstluty7",
+            ],
+        );
+
+        let desc_key1 = (xprv, path).into_descriptor_key().unwrap();
+        let desc_key2 = (xprv, path2).into_descriptor_key().unwrap();
+        check(
+            descriptor!(sh(wsh(multi(1, desc_key1, desc_key2)))),
+            true,
+            false,
+            &[
+                "2NFCtXvx9q4ci2kvKub17iSTgvRXGctCGhz",
+                "2NB2PrFPv5NxWCpygas8tPrGJG2ZFgeuwJw",
+                "2N79ZAGo5cMi5Jt7Wo9L5YmF5GkEw7sjWdC",
+            ],
+        );
+    }
+
+    #[test]
+    fn test_dsl_sortedmulti() {
+        let key_1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        let path_1 = bip32::DerivationPath::from_str("m/0").unwrap();
+
+        let key_2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
+        let path_2 = bip32::DerivationPath::from_str("m/1").unwrap();
+
+        let desc_key1 = (key_1, path_1);
+        let desc_key2 = (key_2, path_2);
+
+        check(
+            descriptor!(sh(sortedmulti(1, desc_key1.clone(), desc_key2.clone()))),
+            false,
+            false,
+            &[
+                "2MsxzPEJDBzpGffJXPaDpfXZAUNnZhaMh2N",
+                "2My3x3DLPK3UbGWGpxrXr1RnbD8MNC4FpgS",
+                "2NByEuiQT7YLqHCTNxL5KwYjvtuCYcXNBSC",
+                "2N1TGbP81kj2VUKTSWgrwxoMfuWjvfUdyu7",
+                "2N3Bomq2fpAcLRNfZnD3bCWK9quan28CxCR",
+                "2N9nrZaEzEFDqEAU9RPvDnXGT6AVwBDKAQb",
+            ],
+        );
+
+        check(
+            descriptor!(sh(wsh(sortedmulti(
+                1,
+                desc_key1.clone(),
+                desc_key2.clone()
+            )))),
+            true,
+            false,
+            &[
+                "2NCogc5YyM4N6ruv1hUa7WLMW1BPeCK7N9B",
+                "2N6mkSAKi1V2oaBXby7XHdvBMKEDRQcFpNe",
+                "2NFmTSttm9v6bXeoWaBvpMcgfPQcZhNn3Eh",
+                "2Mvib87RBPUHXNEpX5S5Kv1qqrhBfgBGsJM",
+                "2MtMv5mcK2EjcLsH8Txpx2JxLLzHr4ttczL",
+                "2MsWCB56rb4T6yPv8QudZGHERTwNgesE4f6",
+            ],
+        );
+
+        check(
+            descriptor!(wsh(sortedmulti_vec(1, vec![desc_key1, desc_key2]))),
+            true,
+            false,
+            &[
+                "bcrt1qcvq0lg8q7a47ytrd7zk5y7uls7mulrenjgvflwylpppgwf8029es4vhpnj",
+                "bcrt1q80yn8sdt6l7pjvkz25lglyaqctlmsq9ugk80rmxt8yu0npdsj97sc7l4de",
+                "bcrt1qrvf6024v9s50qhffe3t2fr2q9ckdhx2g6jz32chm2pp24ymgtr5qfrdmct",
+                "bcrt1q6srfmra0ynypym35c7jvsxt2u4yrugeajq95kg2ps7lk6h2gaunsq9lzxn",
+                "bcrt1qhl8rrzzcdpu7tcup3lcg7tge52sqvwy5fcv4k78v6kxtwmqf3v6qpvyjza",
+                "bcrt1ql2elz9mhm9ll27ddpewhxs732xyl2fk2kpkqz9gdyh33wgcun4vstrd49k",
+            ],
+        );
+    }
+
+    // - verify the valid_networks returned is correctly computed based on the keys present in the descriptor
+    #[test]
+    fn test_valid_networks() {
+        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+        let desc_key = (xprv, path).into_descriptor_key().unwrap();
+
+        let (_desc, _key_map, valid_networks) = descriptor!(pkh(desc_key)).unwrap();
+        assert_eq!(
+            valid_networks,
+            [Testnet, Regtest, Signet].iter().cloned().collect()
+        );
+
+        let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi").unwrap();
+        let path = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
+        let desc_key = (xprv, path).into_descriptor_key().unwrap();
+
+        let (_desc, _key_map, valid_networks) = descriptor!(wpkh(desc_key)).unwrap();
+        assert_eq!(valid_networks, [Bitcoin].iter().cloned().collect());
+    }
+
+    // - verify the key_maps are correctly merged together
+    #[test]
+    fn test_key_maps_merged() {
+        let secp = Secp256k1::new();
+
+        let xprv1 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        let path1 = bip32::DerivationPath::from_str("m/0").unwrap();
+        let desc_key1 = (xprv1, path1.clone()).into_descriptor_key().unwrap();
+
+        let xprv2 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap();
+        let path2 = bip32::DerivationPath::from_str("m/2147483647'/0").unwrap();
+        let desc_key2 = (xprv2, path2.clone()).into_descriptor_key().unwrap();
+
+        let xprv3 = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf").unwrap();
+        let path3 = bip32::DerivationPath::from_str("m/10/20/30/40").unwrap();
+        let desc_key3 = (xprv3, path3.clone()).into_descriptor_key().unwrap();
+
+        let (_desc, key_map, _valid_networks) =
+            descriptor!(sh(wsh(multi(2, desc_key1, desc_key2, desc_key3)))).unwrap();
+        assert_eq!(key_map.len(), 3);
+
+        let desc_key1: DescriptorKey<Segwitv0> = (xprv1, path1).into_descriptor_key().unwrap();
+        let desc_key2: DescriptorKey<Segwitv0> = (xprv2, path2).into_descriptor_key().unwrap();
+        let desc_key3: DescriptorKey<Segwitv0> = (xprv3, path3).into_descriptor_key().unwrap();
+
+        let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap();
+        let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap();
+        let (key3, _key_map, _valid_networks) = desc_key3.extract(&secp).unwrap();
+        assert_eq!(key_map.get(&key1).unwrap().to_string(), "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*");
+        assert_eq!(key_map.get(&key2).unwrap().to_string(), "tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF/2147483647'/0/*");
+        assert_eq!(key_map.get(&key3).unwrap().to_string(), "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf/10/20/30/40/*");
+    }
+
+    // - verify the ScriptContext is correctly validated (i.e. passing a type that only impl IntoDescriptorKey<Segwitv0> to a pkh() descriptor should throw a compilation error
+    #[test]
+    fn test_script_context_validation() {
+        // this compiles
+        let xprv = bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+        let desc_key: DescriptorKey<Legacy> = (xprv, path).into_descriptor_key().unwrap();
+
+        let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
+        assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2");
+
+        // as expected this does not compile due to invalid context
+        //let desc_key:DescriptorKey<Segwitv0> = (xprv, path.clone()).into_descriptor_key().unwrap();
+        //let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap();
+    }
+
+    #[test]
+    fn test_dsl_modifiers() {
+        let private_key =
+            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
+        let (descriptor, _, _) =
+            descriptor!(wsh(thresh(2,n:d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap();
+
+        assert_eq!(descriptor.to_string(), "wsh(thresh(2,ndv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#zzk3ux8g")
+    }
+
+    #[test]
+    #[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")]
+    fn test_dsl_miniscript_checks() {
+        let mut uncompressed_pk =
+            PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
+        uncompressed_pk.compressed = false;
+
+        descriptor!(wsh(v: pk(uncompressed_pk))).unwrap();
+    }
+
+    #[test]
+    fn test_dsl_tr_only_key() {
+        let private_key =
+            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
+        let (descriptor, _, _) = descriptor!(tr(private_key)).unwrap();
+
+        assert_eq!(
+            descriptor.to_string(),
+            "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#heq9m95v"
+        )
+    }
+
+    #[test]
+    fn test_dsl_tr_simple_tree() {
+        let private_key =
+            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
+        let (descriptor, _, _) =
+            descriptor!(tr(private_key, { pk(private_key), pk(private_key) })).unwrap();
+
+        assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,{pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)})#xy5fjw6d")
+    }
+
+    #[test]
+    fn test_dsl_tr_single_leaf() {
+        let private_key =
+            PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
+        let (descriptor, _, _) = descriptor!(tr(private_key, pk(private_key))).unwrap();
+
+        assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c))#lzl2vmc7")
+    }
+}
diff --git a/crates/wallet/src/descriptor/error.rs b/crates/wallet/src/descriptor/error.rs
new file mode 100644 (file)
index 0000000..b2809f2
--- /dev/null
@@ -0,0 +1,123 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptor errors
+use core::fmt;
+
+/// Errors related to the parsing and usage of descriptors
+#[derive(Debug)]
+pub enum Error {
+    /// Invalid HD Key path, such as having a wildcard but a length != 1
+    InvalidHdKeyPath,
+    /// The provided descriptor doesn't match its checksum
+    InvalidDescriptorChecksum,
+    /// The descriptor contains hardened derivation steps on public extended keys
+    HardenedDerivationXpub,
+    /// The descriptor contains multipath keys
+    MultiPath,
+
+    /// Error thrown while working with [`keys`](crate::keys)
+    Key(crate::keys::KeyError),
+    /// Error while extracting and manipulating policies
+    Policy(crate::descriptor::policy::PolicyError),
+
+    /// Invalid byte found in the descriptor checksum
+    InvalidDescriptorCharacter(u8),
+
+    /// BIP32 error
+    Bip32(bitcoin::bip32::Error),
+    /// Error during base58 decoding
+    Base58(bitcoin::base58::Error),
+    /// Key-related error
+    Pk(bitcoin::key::Error),
+    /// Miniscript error
+    Miniscript(miniscript::Error),
+    /// Hex decoding error
+    Hex(bitcoin::hex::HexToBytesError),
+}
+
+impl From<crate::keys::KeyError> for Error {
+    fn from(key_error: crate::keys::KeyError) -> Error {
+        match key_error {
+            crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
+            crate::keys::KeyError::Bip32(inner) => Error::Bip32(inner),
+            e => Error::Key(e),
+        }
+    }
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::InvalidHdKeyPath => write!(f, "Invalid HD key path"),
+            Self::InvalidDescriptorChecksum => {
+                write!(f, "The provided descriptor doesn't match its checksum")
+            }
+            Self::HardenedDerivationXpub => write!(
+                f,
+                "The descriptor contains hardened derivation steps on public extended keys"
+            ),
+            Self::MultiPath => write!(
+                f,
+                "The descriptor contains multipath keys, which are not supported yet"
+            ),
+            Self::Key(err) => write!(f, "Key error: {}", err),
+            Self::Policy(err) => write!(f, "Policy error: {}", err),
+            Self::InvalidDescriptorCharacter(char) => {
+                write!(f, "Invalid descriptor character: {}", char)
+            }
+            Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
+            Self::Base58(err) => write!(f, "Base58 error: {}", err),
+            Self::Pk(err) => write!(f, "Key-related error: {}", err),
+            Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
+            Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+impl From<bitcoin::bip32::Error> for Error {
+    fn from(err: bitcoin::bip32::Error) -> Self {
+        Error::Bip32(err)
+    }
+}
+
+impl From<bitcoin::base58::Error> for Error {
+    fn from(err: bitcoin::base58::Error) -> Self {
+        Error::Base58(err)
+    }
+}
+
+impl From<bitcoin::key::Error> for Error {
+    fn from(err: bitcoin::key::Error) -> Self {
+        Error::Pk(err)
+    }
+}
+
+impl From<miniscript::Error> for Error {
+    fn from(err: miniscript::Error) -> Self {
+        Error::Miniscript(err)
+    }
+}
+
+impl From<bitcoin::hex::HexToBytesError> for Error {
+    fn from(err: bitcoin::hex::HexToBytesError) -> Self {
+        Error::Hex(err)
+    }
+}
+
+impl From<crate::descriptor::policy::PolicyError> for Error {
+    fn from(err: crate::descriptor::policy::PolicyError) -> Self {
+        Error::Policy(err)
+    }
+}
diff --git a/crates/wallet/src/descriptor/mod.rs b/crates/wallet/src/descriptor/mod.rs
new file mode 100644 (file)
index 0000000..4b1135f
--- /dev/null
@@ -0,0 +1,900 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptors
+//!
+//! This module contains generic utilities to work with descriptors, plus some re-exported types
+//! from [`miniscript`].
+
+use crate::collections::BTreeMap;
+use alloc::string::String;
+use alloc::vec::Vec;
+
+use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, KeySource, Xpub};
+use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
+use bitcoin::{psbt, taproot};
+use bitcoin::{Network, TxOut};
+
+use miniscript::descriptor::{
+    DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
+    DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
+};
+pub use miniscript::{
+    Descriptor, DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
+};
+use miniscript::{ForEachKey, MiniscriptKey, TranslatePk};
+
+use crate::descriptor::policy::BuildSatisfaction;
+
+pub mod checksum;
+#[doc(hidden)]
+pub mod dsl;
+pub mod error;
+pub mod policy;
+pub mod template;
+
+pub use self::checksum::calc_checksum;
+use self::checksum::calc_checksum_bytes;
+pub use self::error::Error as DescriptorError;
+pub use self::policy::Policy;
+use self::template::DescriptorTemplateOut;
+use crate::keys::{IntoDescriptorKey, KeyError};
+use crate::wallet::signer::SignersContainer;
+use crate::wallet::utils::SecpCtx;
+
+/// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
+pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
+
+/// Alias for a [`Descriptor`] that contains extended **derived** keys
+pub type DerivedDescriptor = Descriptor<DefiniteDescriptorKey>;
+
+/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
+/// [`psbt::Output`]
+///
+/// [`psbt::Input`]: bitcoin::psbt::Input
+/// [`psbt::Output`]: bitcoin::psbt::Output
+pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
+
+/// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or
+/// [`psbt::Output`]
+///
+/// [`psbt::Input`]: bitcoin::psbt::Input
+/// [`psbt::Output`]: bitcoin::psbt::Output
+pub type TapKeyOrigins = BTreeMap<XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
+
+/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
+pub trait IntoWalletDescriptor {
+    /// Convert to wallet descriptor
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
+}
+
+impl IntoWalletDescriptor for &str {
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        let descriptor = match self.split_once('#') {
+            Some((desc, original_checksum)) => {
+                let checksum = calc_checksum_bytes(desc)?;
+                if original_checksum.as_bytes() != checksum {
+                    return Err(DescriptorError::InvalidDescriptorChecksum);
+                }
+                desc
+            }
+            None => self,
+        };
+
+        ExtendedDescriptor::parse_descriptor(secp, descriptor)?
+            .into_wallet_descriptor(secp, network)
+    }
+}
+
+impl IntoWalletDescriptor for &String {
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        self.as_str().into_wallet_descriptor(secp, network)
+    }
+}
+
+impl IntoWalletDescriptor for ExtendedDescriptor {
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        (self, KeyMap::default()).into_wallet_descriptor(secp, network)
+    }
+}
+
+impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        use crate::keys::DescriptorKey;
+
+        struct Translator<'s, 'd> {
+            secp: &'s SecpCtx,
+            descriptor: &'d ExtendedDescriptor,
+            network: Network,
+        }
+
+        impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
+            for Translator<'s, 'd>
+        {
+            fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
+                let secp = &self.secp;
+
+                let (_, _, networks) = if self.descriptor.is_taproot() {
+                    let descriptor_key: DescriptorKey<miniscript::Tap> =
+                        pk.clone().into_descriptor_key()?;
+                    descriptor_key.extract(secp)?
+                } else if self.descriptor.is_witness() {
+                    let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
+                        pk.clone().into_descriptor_key()?;
+                    descriptor_key.extract(secp)?
+                } else {
+                    let descriptor_key: DescriptorKey<miniscript::Legacy> =
+                        pk.clone().into_descriptor_key()?;
+                    descriptor_key.extract(secp)?
+                };
+
+                if networks.contains(&self.network) {
+                    Ok(Default::default())
+                } else {
+                    Err(DescriptorError::Key(KeyError::InvalidNetwork))
+                }
+            }
+            fn sha256(
+                &mut self,
+                _sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
+            ) -> Result<String, DescriptorError> {
+                Ok(Default::default())
+            }
+            fn hash256(
+                &mut self,
+                _hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
+            ) -> Result<String, DescriptorError> {
+                Ok(Default::default())
+            }
+            fn ripemd160(
+                &mut self,
+                _ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
+            ) -> Result<String, DescriptorError> {
+                Ok(Default::default())
+            }
+            fn hash160(
+                &mut self,
+                _hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
+            ) -> Result<String, DescriptorError> {
+                Ok(Default::default())
+            }
+        }
+
+        // check the network for the keys
+        use miniscript::TranslateErr;
+        match self.0.translate_pk(&mut Translator {
+            secp,
+            network,
+            descriptor: &self.0,
+        }) {
+            Ok(_) => {}
+            Err(TranslateErr::TranslatorErr(e)) => return Err(e),
+            Err(TranslateErr::OuterError(e)) => return Err(e.into()),
+        }
+
+        Ok(self)
+    }
+}
+
+impl IntoWalletDescriptor for DescriptorTemplateOut {
+    fn into_wallet_descriptor(
+        self,
+        _secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        struct Translator {
+            network: Network,
+        }
+
+        impl miniscript::Translator<DescriptorPublicKey, DescriptorPublicKey, DescriptorError>
+            for Translator
+        {
+            fn pk(
+                &mut self,
+                pk: &DescriptorPublicKey,
+            ) -> Result<DescriptorPublicKey, DescriptorError> {
+                // workaround for xpubs generated by other key types, like bip39: since when the
+                // conversion is made one network has to be chosen, what we generally choose
+                // "mainnet", but then override the set of valid networks to specify that all of
+                // them are valid. here we reset the network to make sure the wallet struct gets a
+                // descriptor with the right network everywhere.
+                let pk = match pk {
+                    DescriptorPublicKey::XPub(ref xpub) => {
+                        let mut xpub = xpub.clone();
+                        xpub.xkey.network = self.network;
+
+                        DescriptorPublicKey::XPub(xpub)
+                    }
+                    other => other.clone(),
+                };
+
+                Ok(pk)
+            }
+            miniscript::translate_hash_clone!(
+                DescriptorPublicKey,
+                DescriptorPublicKey,
+                DescriptorError
+            );
+        }
+
+        let (desc, keymap, networks) = self;
+
+        if !networks.contains(&network) {
+            return Err(DescriptorError::Key(KeyError::InvalidNetwork));
+        }
+
+        // fixup the network for keys that need it in the descriptor
+        use miniscript::TranslateErr;
+        let translated = match desc.translate_pk(&mut Translator { network }) {
+            Ok(descriptor) => descriptor,
+            Err(TranslateErr::TranslatorErr(e)) => return Err(e),
+            Err(TranslateErr::OuterError(e)) => return Err(e.into()),
+        };
+        // ...and in the key map
+        let fixed_keymap = keymap
+            .into_iter()
+            .map(|(mut k, mut v)| {
+                match (&mut k, &mut v) {
+                    (DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
+                        xpub.xkey.network = network;
+                        xprv.xkey.network = network;
+                    }
+                    (_, DescriptorSecretKey::Single(key)) => {
+                        key.key.network = network;
+                    }
+                    _ => {}
+                }
+
+                (k, v)
+            })
+            .collect();
+
+        Ok((translated, fixed_keymap))
+    }
+}
+
+/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
+/// descriptor
+pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
+    inner: T,
+    secp: &SecpCtx,
+    network: Network,
+) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+    let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
+
+    // Ensure the keys don't contain any hardened derivation steps or hardened wildcards
+    let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
+        if let DescriptorPublicKey::XPub(DescriptorXKey {
+            derivation_path,
+            wildcard,
+            ..
+        }) = k
+        {
+            return *wildcard == Wildcard::Hardened
+                || derivation_path.into_iter().any(ChildNumber::is_hardened);
+        }
+
+        false
+    });
+    if descriptor_contains_hardened_steps {
+        return Err(DescriptorError::HardenedDerivationXpub);
+    }
+
+    if descriptor.is_multipath() {
+        return Err(DescriptorError::MultiPath);
+    }
+
+    // Run miniscript's sanity check, which will look for duplicated keys and other potential
+    // issues
+    descriptor.sanity_check()?;
+
+    Ok((descriptor, keymap))
+}
+
+#[doc(hidden)]
+/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
+pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
+    fn check_miniscript(&self) -> Result<(), miniscript::Error>;
+}
+
+impl<Ctx: miniscript::ScriptContext, Pk: miniscript::MiniscriptKey> CheckMiniscript<Ctx>
+    for miniscript::Miniscript<Pk, Ctx>
+{
+    fn check_miniscript(&self) -> Result<(), miniscript::Error> {
+        Ctx::check_global_validity(self)?;
+
+        Ok(())
+    }
+}
+
+/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]
+pub trait ExtractPolicy {
+    /// Extract the spending [`policy`]
+    fn extract_policy(
+        &self,
+        signers: &SignersContainer,
+        psbt: BuildSatisfaction,
+        secp: &SecpCtx,
+    ) -> Result<Option<Policy>, DescriptorError>;
+}
+
+pub(crate) trait XKeyUtils {
+    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
+}
+
+impl<T> XKeyUtils for DescriptorMultiXKey<T>
+where
+    T: InnerXKey,
+{
+    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
+        match self.origin {
+            Some((fingerprint, _)) => fingerprint,
+            None => self.xkey.xkey_fingerprint(secp),
+        }
+    }
+}
+
+impl<T> XKeyUtils for DescriptorXKey<T>
+where
+    T: InnerXKey,
+{
+    fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
+        match self.origin {
+            Some((fingerprint, _)) => fingerprint,
+            None => self.xkey.xkey_fingerprint(secp),
+        }
+    }
+}
+
+pub(crate) trait DescriptorMeta {
+    fn is_witness(&self) -> bool;
+    fn is_taproot(&self) -> bool;
+    fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>>;
+    fn derive_from_hd_keypaths(
+        &self,
+        hd_keypaths: &HdKeyPaths,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor>;
+    fn derive_from_tap_key_origins(
+        &self,
+        tap_key_origins: &TapKeyOrigins,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor>;
+    fn derive_from_psbt_key_origins(
+        &self,
+        key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor>;
+    fn derive_from_psbt_input(
+        &self,
+        psbt_input: &psbt::Input,
+        utxo: Option<TxOut>,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor>;
+}
+
+impl DescriptorMeta for ExtendedDescriptor {
+    fn is_witness(&self) -> bool {
+        matches!(
+            self.desc_type(),
+            DescriptorType::Wpkh
+                | DescriptorType::ShWpkh
+                | DescriptorType::Wsh
+                | DescriptorType::ShWsh
+                | DescriptorType::ShWshSortedMulti
+                | DescriptorType::WshSortedMulti
+        )
+    }
+
+    fn is_taproot(&self) -> bool {
+        self.desc_type() == DescriptorType::Tr
+    }
+
+    fn get_extended_keys(&self) -> Vec<DescriptorXKey<Xpub>> {
+        let mut answer = Vec::new();
+
+        self.for_each_key(|pk| {
+            if let DescriptorPublicKey::XPub(xpub) = pk {
+                answer.push(xpub.clone());
+            }
+
+            true
+        });
+
+        answer
+    }
+
+    fn derive_from_psbt_key_origins(
+        &self,
+        key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor> {
+        // Ensure that deriving `xpub` with `path` yields `expected`
+        let verify_key =
+            |xpub: &DescriptorXKey<Xpub>, path: &DerivationPath, expected: &SinglePubKey| {
+                let derived = xpub
+                    .xkey
+                    .derive_pub(secp, path)
+                    .expect("The path should never contain hardened derivation steps")
+                    .public_key;
+
+                match expected {
+                    SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
+                    SinglePubKey::XOnly(pk) if &XOnlyPublicKey::from(derived) == pk => true,
+                    _ => false,
+                }
+            };
+
+        let mut path_found = None;
+
+        // using `for_any_key` should make this stop as soon as we return `true`
+        self.for_any_key(|key| {
+            if let DescriptorPublicKey::XPub(xpub) = key {
+                // Check if the key matches one entry in our `key_origins`. If it does, `matches()` will
+                // return the "prefix" that matched, so we remove that prefix from the full path
+                // found in `key_origins` and save it in `derive_path`. We expect this to be a derivation
+                // path of length 1 if the key is `wildcard` and an empty path otherwise.
+                let root_fingerprint = xpub.root_fingerprint(secp);
+                let derive_path = key_origins
+                    .get_key_value(&root_fingerprint)
+                    .and_then(|(fingerprint, (path, expected))| {
+                        xpub.matches(&(*fingerprint, (*path).clone()), secp)
+                            .zip(Some((path, expected)))
+                    })
+                    .and_then(|(prefix, (full_path, expected))| {
+                        let derive_path = full_path
+                            .into_iter()
+                            .skip(prefix.into_iter().count())
+                            .cloned()
+                            .collect::<DerivationPath>();
+
+                        // `derive_path` only contains the replacement index for the wildcard, if present, or
+                        // an empty path for fixed descriptors. To verify the key we also need the normal steps
+                        // that come before the wildcard, so we take them directly from `xpub` and then append
+                        // the final index
+                        if verify_key(
+                            xpub,
+                            &xpub.derivation_path.extend(derive_path.clone()),
+                            expected,
+                        ) {
+                            Some(derive_path)
+                        } else {
+                            None
+                        }
+                    });
+
+                match derive_path {
+                    Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => {
+                        // Ignore hardened wildcards
+                        if let ChildNumber::Normal { index } = path[0] {
+                            path_found = Some(index);
+                            return true;
+                        }
+                    }
+                    Some(path) if xpub.wildcard == Wildcard::None && path.is_empty() => {
+                        path_found = Some(0);
+                        return true;
+                    }
+                    _ => {}
+                }
+            }
+
+            false
+        });
+
+        path_found.map(|path| {
+            self.at_derivation_index(path)
+                .expect("We ignore hardened wildcards")
+        })
+    }
+
+    fn derive_from_hd_keypaths(
+        &self,
+        hd_keypaths: &HdKeyPaths,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor> {
+        // "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins`
+        let key_origins = hd_keypaths
+            .iter()
+            .map(|(pk, (fingerprint, path))| {
+                (
+                    *fingerprint,
+                    (path, SinglePubKey::FullKey(PublicKey::new(*pk))),
+                )
+            })
+            .collect();
+        self.derive_from_psbt_key_origins(key_origins, secp)
+    }
+
+    fn derive_from_tap_key_origins(
+        &self,
+        tap_key_origins: &TapKeyOrigins,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor> {
+        // "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins`
+        let key_origins = tap_key_origins
+            .iter()
+            .map(|(pk, (_, (fingerprint, path)))| (*fingerprint, (path, SinglePubKey::XOnly(*pk))))
+            .collect();
+        self.derive_from_psbt_key_origins(key_origins, secp)
+    }
+
+    fn derive_from_psbt_input(
+        &self,
+        psbt_input: &psbt::Input,
+        utxo: Option<TxOut>,
+        secp: &SecpCtx,
+    ) -> Option<DerivedDescriptor> {
+        if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
+            return Some(derived);
+        }
+        if let Some(derived) = self.derive_from_tap_key_origins(&psbt_input.tap_key_origins, secp) {
+            return Some(derived);
+        }
+        if self.has_wildcard() {
+            // We can't try to bruteforce the derivation index, exit here
+            return None;
+        }
+
+        let descriptor = self.at_derivation_index(0).expect("0 is not hardened");
+        match descriptor.desc_type() {
+            // TODO: add pk() here
+            DescriptorType::Pkh
+            | DescriptorType::Wpkh
+            | DescriptorType::ShWpkh
+            | DescriptorType::Tr
+                if utxo.is_some()
+                    && descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
+            {
+                Some(descriptor)
+            }
+            DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
+                if psbt_input.redeem_script.is_some()
+                    && &descriptor.explicit_script().unwrap()
+                        == psbt_input.redeem_script.as_ref().unwrap() =>
+            {
+                Some(descriptor)
+            }
+            DescriptorType::Wsh
+            | DescriptorType::ShWsh
+            | DescriptorType::ShWshSortedMulti
+            | DescriptorType::WshSortedMulti
+                if psbt_input.witness_script.is_some()
+                    && &descriptor.explicit_script().unwrap()
+                        == psbt_input.witness_script.as_ref().unwrap() =>
+            {
+                Some(descriptor)
+            }
+            _ => None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use alloc::string::ToString;
+    use core::str::FromStr;
+
+    use assert_matches::assert_matches;
+    use bitcoin::hex::FromHex;
+    use bitcoin::secp256k1::Secp256k1;
+    use bitcoin::ScriptBuf;
+    use bitcoin::{bip32, Psbt};
+
+    use super::*;
+    use crate::psbt::PsbtUtils;
+
+    #[test]
+    fn test_derive_from_psbt_input_wpkh_wif() {
+        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+            "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
+        )
+        .unwrap();
+        let psbt = Psbt::deserialize(
+            &Vec::<u8>::from_hex(
+                "70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
+                 11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
+                 001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa086\
+                 01000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304\
+                 010000000000",
+            )
+            .unwrap(),
+        )
+        .unwrap();
+
+        assert!(descriptor
+            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
+            .is_some());
+    }
+
+    #[test]
+    fn test_derive_from_psbt_input_pkh_tpub() {
+        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+            "pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
+        )
+        .unwrap();
+        let psbt = Psbt::deserialize(
+            &Vec::<u8>::from_hex(
+                "70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\
+                 e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\
+                 a91432bb94283282f72b2e034709e348c44d5a4db0ef8700000000000100f902\
+                 0000000001010167e99c0eb67640f3a1b6805f2d8be8238c947f8aaf49eb0a9c\
+                 bee6a42c984200000000171600142b29a22019cca05b9c2b2d283a4c4489e1cf\
+                 9f8ffeffffff02a01dced06100000017a914e2abf033cadbd74f0f4c74946201\
+                 decd20d5c43c8780969800000000001976a9148b0fce5fb1264e599a65387313\
+                 3c95478b902eb288ac02473044022015d9211576163fa5b001e84dfa3d44efd9\
+                 86b8f3a0d3d2174369288b2b750906022048dacc0e5d73ae42512fd2b97e2071\
+                 a8d0bce443b390b1fe0b8128fe70ec919e01210232dad1c5a67dcb0116d407e2\
+                 52584228ab7ec00e8b9779d0c3ffe8114fc1a7d2c80600000103040100000022\
+                 0603433b83583f8c4879b329dd08bbc7da935e4cc02f637ff746e05f0466ffb2\
+                 a6a2180f0569432c00008000000080000000800a000000000000000000",
+            )
+            .unwrap(),
+        )
+        .unwrap();
+
+        assert!(descriptor
+            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
+            .is_some());
+    }
+
+    #[test]
+    fn test_derive_from_psbt_input_wsh() {
+        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+            "wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))",
+        )
+        .unwrap();
+        let psbt = Psbt::deserialize(
+            &Vec::<u8>::from_hex(
+                "70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\
+                 67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\
+                 a914ad105f61102e0d01d7af40d06d6a5c3ae2f7fde387000000000001012b80\
+                 969800000000002200203ca72f106a72234754890ca7640c43f65d2174e44d33\
+                 336030f9059345091044010304010000000105252103b6633fef2397a0a9de9d\
+                 7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14ad56b20000",
+            )
+            .unwrap(),
+        )
+        .unwrap();
+
+        assert!(descriptor
+            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
+            .is_some());
+    }
+
+    #[test]
+    fn test_derive_from_psbt_input_sh() {
+        let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+            "sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))",
+        )
+        .unwrap();
+        let psbt = Psbt::deserialize(
+            &Vec::<u8>::from_hex(
+                "70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\
+                 a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\
+                 a91457b148ba4d3e5fa8608a8657875124e3d1c9390887f09c0900000100e002\
+                 0000000001016ba1bbe05cc93574a0d611ec7d93ad0ab6685b28d0cd80e8a82d\
+                 debb326643c90100000000feffffff02809698000000000017a914d9a6e8c455\
+                 8e16c8253afe53ce37ad61cf4c38c487403504cf6100000017a9144044fb6e0b\
+                 757dfc1b34886b6a95aef4d3db137e870247304402202a9b72d939bcde8ba2a1\
+                 e0980597e47af4f5c152a78499143c3d0a78ac2286a602207a45b1df9e93b8c9\
+                 6f09f5c025fe3e413ca4b905fe65ee55d32a3276439a9b8f012102dc1fcc2636\
+                 4da1aa718f03d8d9bd6f2ff410ed2cf1245a168aa3bcc995ac18e0a806000001\
+                 03040100000001042821021403881a5587297818fcaf17d239cefca22fce84a4\
+                 5b3b1d23e836c4af671dbbad03f09c09b10000",
+            )
+            .unwrap(),
+        )
+        .unwrap();
+
+        assert!(descriptor
+            .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0), &Secp256k1::new())
+            .is_some());
+    }
+
+    #[test]
+    fn test_to_wallet_descriptor_fixup_networks() {
+        use crate::keys::{any_network, IntoDescriptorKey};
+
+        let secp = Secp256k1::new();
+
+        let xprv = bip32::Xpriv::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+
+        // here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
+        // we are using an "xpub"
+        let key = (xprv, path.clone()).into_descriptor_key().unwrap();
+        // override it with any. this happens in some key conversions, like bip39
+        let key = key.override_valid_networks(any_network());
+
+        // make a descriptor out of it
+        let desc = crate::descriptor!(wpkh(key)).unwrap();
+        // this should convert the key that supports "any_network" to the right network (testnet)
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+
+        let mut xprv_testnet = xprv;
+        xprv_testnet.network = Network::Testnet;
+
+        let xpub_testnet = bip32::Xpub::from_priv(&secp, &xprv_testnet);
+        let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
+            xkey: xpub_testnet,
+            origin: None,
+            derivation_path: path,
+            wildcard: Wildcard::Unhardened,
+        });
+
+        assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
+        assert_eq!(
+            keymap
+                .get(&desc_pubkey)
+                .map(|key| key.to_public(&secp).unwrap()),
+            Some(desc_pubkey)
+        );
+    }
+
+    // test IntoWalletDescriptor trait from &str with and without checksum appended
+    #[test]
+    fn test_descriptor_from_str_with_checksum() {
+        let secp = Secp256k1::new();
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
+    }
+
+    // test IntoWalletDescriptor trait from &str with keys from right and wrong network
+    #[test]
+    fn test_descriptor_from_str_with_keys_network() {
+        let secp = Secp256k1::new();
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Regtest);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Regtest);
+        assert!(desc.is_ok());
+
+        let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
+            .into_wallet_descriptor(&secp, Network::Testnet);
+        assert!(desc.is_ok());
+
+        let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
+            .into_wallet_descriptor(&secp, Network::Bitcoin);
+        assert!(desc.is_ok());
+
+        let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Bitcoin);
+        assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
+
+        let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
+            .into_wallet_descriptor(&secp, Network::Bitcoin);
+        assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
+    }
+
+    // test IntoWalletDescriptor trait from the output of the descriptor!() macro
+    #[test]
+    fn test_descriptor_from_str_from_output_of_macro() {
+        let secp = Secp256k1::new();
+
+        let tpub = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
+        let path = bip32::DerivationPath::from_str("m/1/2").unwrap();
+        let key = (tpub, path).into_descriptor_key().unwrap();
+
+        // make a descriptor out of it
+        let desc = crate::descriptor!(wpkh(key)).unwrap();
+
+        let (wallet_desc, _) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let wallet_desc_str = wallet_desc.to_string();
+        assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw");
+
+        let (wallet_desc2, _) = wallet_desc_str
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        assert_eq!(wallet_desc, wallet_desc2)
+    }
+
+    #[test]
+    fn test_into_wallet_descriptor_checked() {
+        let secp = Secp256k1::new();
+
+        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
+        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
+
+        assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
+
+        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
+        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
+
+        assert_matches!(result, Err(DescriptorError::MultiPath));
+
+        // repeated pubkeys
+        let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
+        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
+
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn test_sh_wsh_sortedmulti_redeemscript() {
+        use miniscript::psbt::PsbtInputExt;
+
+        let secp = Secp256k1::new();
+
+        let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))";
+        let (descriptor, _) =
+            into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
+
+        let descriptor = descriptor.at_derivation_index(0).unwrap();
+
+        let script = ScriptBuf::from_hex("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
+
+        let mut psbt_input = psbt::Input::default();
+        psbt_input
+            .update_with_descriptor_unchecked(&descriptor)
+            .unwrap();
+
+        assert_eq!(psbt_input.redeem_script, Some(script.to_p2wsh()));
+        assert_eq!(psbt_input.witness_script, Some(script));
+    }
+}
diff --git a/crates/wallet/src/descriptor/policy.rs b/crates/wallet/src/descriptor/policy.rs
new file mode 100644 (file)
index 0000000..bf8a661
--- /dev/null
@@ -0,0 +1,1905 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptor policy
+//!
+//! This module implements the logic to extract and represent the spending policies of a descriptor
+//! in a more human-readable format.
+//!
+//! This is an **EXPERIMENTAL** feature, API and other major changes are expected.
+//!
+//! ## Example
+//!
+//! ```
+//! # use std::sync::Arc;
+//! # use bdk_wallet::descriptor::*;
+//! # use bdk_wallet::wallet::signer::*;
+//! # use bdk_wallet::bitcoin::secp256k1::Secp256k1;
+//! use bdk_wallet::descriptor::policy::BuildSatisfaction;
+//! let secp = Secp256k1::new();
+//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
+//!
+//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
+//! println!("{:?}", extended_desc);
+//!
+//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp));
+//! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
+//! println!("policy: {}", serde_json::to_string(&policy).unwrap());
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use crate::collections::{BTreeMap, HashSet, VecDeque};
+use alloc::string::String;
+use alloc::vec::Vec;
+use core::cmp::max;
+
+use core::fmt;
+
+use serde::ser::SerializeMap;
+use serde::{Serialize, Serializer};
+
+use bitcoin::bip32::Fingerprint;
+use bitcoin::hashes::{hash160, ripemd160, sha256};
+use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
+
+use miniscript::descriptor::{
+    DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
+};
+use miniscript::hash256;
+use miniscript::{
+    Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
+};
+
+use crate::descriptor::ExtractPolicy;
+use crate::keys::ExtScriptContext;
+use crate::wallet::signer::{SignerId, SignersContainer};
+use crate::wallet::utils::{After, Older, SecpCtx};
+
+use super::checksum::calc_checksum;
+use super::error::Error;
+use super::XKeyUtils;
+use bitcoin::psbt::{self, Psbt};
+use miniscript::psbt::PsbtInputSatisfier;
+
+/// A unique identifier for a key
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum PkOrF {
+    /// A legacy public key
+    Pubkey(PublicKey),
+    /// A x-only public key
+    XOnlyPubkey(XOnlyPublicKey),
+    /// An extended key fingerprint
+    Fingerprint(Fingerprint),
+}
+
+impl PkOrF {
+    fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
+        match k {
+            DescriptorPublicKey::Single(SinglePub {
+                key: SinglePubKey::FullKey(pk),
+                ..
+            }) => PkOrF::Pubkey(*pk),
+            DescriptorPublicKey::Single(SinglePub {
+                key: SinglePubKey::XOnly(pk),
+                ..
+            }) => PkOrF::XOnlyPubkey(*pk),
+            DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
+            DescriptorPublicKey::MultiXPub(multi) => {
+                PkOrF::Fingerprint(multi.root_fingerprint(secp))
+            }
+        }
+    }
+}
+
+/// An item that needs to be satisfied
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(tag = "type", rename_all = "UPPERCASE")]
+pub enum SatisfiableItem {
+    // Leaves
+    /// ECDSA Signature for a raw public key
+    EcdsaSignature(PkOrF),
+    /// Schnorr Signature for a raw public key
+    SchnorrSignature(PkOrF),
+    /// SHA256 preimage hash
+    Sha256Preimage {
+        /// The digest value
+        hash: sha256::Hash,
+    },
+    /// Double SHA256 preimage hash
+    Hash256Preimage {
+        /// The digest value
+        hash: hash256::Hash,
+    },
+    /// RIPEMD160 preimage hash
+    Ripemd160Preimage {
+        /// The digest value
+        hash: ripemd160::Hash,
+    },
+    /// SHA256 then RIPEMD160 preimage hash
+    Hash160Preimage {
+        /// The digest value
+        hash: hash160::Hash,
+    },
+    /// Absolute timeclock timestamp
+    AbsoluteTimelock {
+        /// The timelock value
+        value: absolute::LockTime,
+    },
+    /// Relative timelock locktime
+    RelativeTimelock {
+        /// The timelock value
+        value: Sequence,
+    },
+    /// Multi-signature public keys with threshold count
+    Multisig {
+        /// The raw public key or extended key fingerprint
+        keys: Vec<PkOrF>,
+        /// The required threshold count
+        threshold: usize,
+    },
+
+    // Complex item
+    /// Threshold items with threshold count
+    Thresh {
+        /// The policy items
+        items: Vec<Policy>,
+        /// The required threshold count
+        threshold: usize,
+    },
+}
+
+impl SatisfiableItem {
+    /// Returns whether the [`SatisfiableItem`] is a leaf item
+    pub fn is_leaf(&self) -> bool {
+        !matches!(
+            self,
+            SatisfiableItem::Thresh {
+                items: _,
+                threshold: _,
+            }
+        )
+    }
+
+    /// Returns a unique id for the [`SatisfiableItem`]
+    pub fn id(&self) -> String {
+        calc_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
+            .expect("Failed to compute a SatisfiableItem id")
+    }
+}
+
+fn combinations(vec: &[usize], size: usize) -> Vec<Vec<usize>> {
+    assert!(vec.len() >= size);
+
+    let mut answer = Vec::new();
+
+    let mut queue = VecDeque::new();
+    for (index, val) in vec.iter().enumerate() {
+        let mut new_vec = Vec::with_capacity(size);
+        new_vec.push(*val);
+        queue.push_back((index, new_vec));
+    }
+
+    while let Some((index, vals)) = queue.pop_front() {
+        if vals.len() >= size {
+            answer.push(vals);
+        } else {
+            for (new_index, val) in vec.iter().skip(index + 1).enumerate() {
+                let mut cloned = vals.clone();
+                cloned.push(*val);
+                queue.push_front((new_index, cloned));
+            }
+        }
+    }
+
+    answer
+}
+
+fn mix<T: Clone>(vec: Vec<Vec<T>>) -> Vec<Vec<T>> {
+    if vec.is_empty() || vec.iter().any(Vec::is_empty) {
+        return vec![];
+    }
+
+    let mut answer = Vec::new();
+    let size = vec.len();
+
+    let mut queue = VecDeque::new();
+    for i in &vec[0] {
+        let mut new_vec = Vec::with_capacity(size);
+        new_vec.push(i.clone());
+        queue.push_back(new_vec);
+    }
+
+    while let Some(vals) = queue.pop_front() {
+        if vals.len() >= size {
+            answer.push(vals);
+        } else {
+            let level = vals.len();
+            for i in &vec[level] {
+                let mut cloned = vals.clone();
+                cloned.push(i.clone());
+                queue.push_front(cloned);
+            }
+        }
+    }
+
+    answer
+}
+
+/// Type for a map of sets of [`Condition`] items keyed by each set's index
+pub type ConditionMap = BTreeMap<usize, HashSet<Condition>>;
+/// Type for a map of folded sets of [`Condition`] items keyed by a vector of the combined set's indexes
+pub type FoldedConditionMap = BTreeMap<Vec<usize>, HashSet<Condition>>;
+
+fn serialize_folded_cond_map<S>(
+    input_map: &FoldedConditionMap,
+    serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+    S: Serializer,
+{
+    let mut map = serializer.serialize_map(Some(input_map.len()))?;
+    for (k, v) in input_map {
+        let k_string = format!("{:?}", k);
+        map.serialize_entry(&k_string, v)?;
+    }
+    map.end()
+}
+
+/// Represent if and how much a policy item is satisfied by the wallet's descriptor
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(tag = "type", rename_all = "UPPERCASE")]
+pub enum Satisfaction {
+    /// Only a partial satisfaction of some kind of threshold policy
+    Partial {
+        /// Total number of items
+        n: usize,
+        /// Threshold
+        m: usize,
+        /// The items that can be satisfied by the descriptor or are satisfied in the PSBT
+        items: Vec<usize>,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
+        sorted: Option<bool>,
+        #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+        /// Extra conditions that also need to be satisfied
+        conditions: ConditionMap,
+    },
+    /// Can reach the threshold of some kind of threshold policy
+    PartialComplete {
+        /// Total number of items
+        n: usize,
+        /// Threshold
+        m: usize,
+        /// The items that can be satisfied by the descriptor
+        items: Vec<usize>,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        /// Whether the items are sorted in lexicographic order (used by `sortedmulti`)
+        sorted: Option<bool>,
+        #[serde(
+            serialize_with = "serialize_folded_cond_map",
+            skip_serializing_if = "BTreeMap::is_empty"
+        )]
+        /// Extra conditions that also need to be satisfied
+        conditions: FoldedConditionMap,
+    },
+
+    /// Can satisfy the policy item
+    Complete {
+        /// Extra conditions that also need to be satisfied
+        condition: Condition,
+    },
+    /// Cannot satisfy or contribute to the policy item
+    None,
+}
+
+impl Satisfaction {
+    /// Returns whether the [`Satisfaction`] is a leaf item
+    pub fn is_leaf(&self) -> bool {
+        match self {
+            Satisfaction::None | Satisfaction::Complete { .. } => true,
+            Satisfaction::PartialComplete { .. } | Satisfaction::Partial { .. } => false,
+        }
+    }
+
+    // add `inner` as one of self's partial items. this only makes sense on partials
+    fn add(&mut self, inner: &Satisfaction, inner_index: usize) -> Result<(), PolicyError> {
+        match self {
+            Satisfaction::None | Satisfaction::Complete { .. } => Err(PolicyError::AddOnLeaf),
+            Satisfaction::PartialComplete { .. } => Err(PolicyError::AddOnPartialComplete),
+            Satisfaction::Partial {
+                n,
+                ref mut conditions,
+                ref mut items,
+                ..
+            } => {
+                if inner_index >= *n || items.contains(&inner_index) {
+                    return Err(PolicyError::IndexOutOfRange(inner_index));
+                }
+
+                match inner {
+                    // not relevant if not completed yet
+                    Satisfaction::None | Satisfaction::Partial { .. } => return Ok(()),
+                    Satisfaction::Complete { condition } => {
+                        items.push(inner_index);
+                        conditions.insert(inner_index, vec![*condition].into_iter().collect());
+                    }
+                    Satisfaction::PartialComplete {
+                        conditions: other_conditions,
+                        ..
+                    } => {
+                        items.push(inner_index);
+                        let conditions_set = other_conditions
+                            .values()
+                            .fold(HashSet::new(), |set, i| set.union(i).cloned().collect());
+                        conditions.insert(inner_index, conditions_set);
+                    }
+                }
+
+                Ok(())
+            }
+        }
+    }
+
+    fn finalize(&mut self) {
+        // if partial try to bump it to a partialcomplete
+        if let Satisfaction::Partial {
+            n,
+            m,
+            items,
+            conditions,
+            sorted,
+        } = self
+        {
+            if items.len() >= *m {
+                let mut map = BTreeMap::new();
+                let indexes = combinations(items, *m);
+                // `indexes` at this point is a Vec<Vec<usize>>, with the "n choose k" of items (m of n)
+                indexes
+                    .into_iter()
+                    // .inspect(|x| println!("--- orig --- {:?}", x))
+                    // we map each of the combinations of elements into a tuple of ([chosen items], [conditions]). unfortunately, those items have potentially more than one
+                    // condition (think about ORs), so we also use `mix` to expand those, i.e. [[0], [1, 2]] becomes [[0, 1], [0, 2]]. This is necessary to make sure that we
+                    // consider every possible options and check whether or not they are compatible.
+                    // since this step can turn one item of the iterator into multiple ones, we use `flat_map()` to expand them out
+                    .flat_map(|i_vec| {
+                        mix(i_vec
+                            .iter()
+                            .map(|i| {
+                                conditions
+                                    .get(i)
+                                    .map(|set| set.clone().into_iter().collect())
+                                    .unwrap_or_default()
+                            })
+                            .collect())
+                        .into_iter()
+                        .map(|x| (i_vec.clone(), x))
+                        .collect::<Vec<(Vec<usize>, Vec<Condition>)>>()
+                    })
+                    // .inspect(|x| println!("flat {:?}", x))
+                    // try to fold all the conditions for this specific combination of indexes/options. if they are not compatible, try_fold will be Err
+                    .map(|(key, val)| {
+                        (
+                            key,
+                            val.into_iter()
+                                .try_fold(Condition::default(), |acc, v| acc.merge(&v)),
+                        )
+                    })
+                    // .inspect(|x| println!("try_fold {:?}", x))
+                    // filter out all the incompatible combinations
+                    .filter(|(_, val)| val.is_ok())
+                    // .inspect(|x| println!("filter {:?}", x))
+                    // push them into the map
+                    .for_each(|(key, val)| {
+                        map.entry(key)
+                            .or_insert_with(HashSet::new)
+                            .insert(val.unwrap());
+                    });
+                // TODO: if the map is empty, the conditions are not compatible, return an error?
+                *self = Satisfaction::PartialComplete {
+                    n: *n,
+                    m: *m,
+                    items: items.clone(),
+                    conditions: map,
+                    sorted: *sorted,
+                };
+            }
+        }
+    }
+}
+
+impl From<bool> for Satisfaction {
+    fn from(other: bool) -> Self {
+        if other {
+            Satisfaction::Complete {
+                condition: Default::default(),
+            }
+        } else {
+            Satisfaction::None
+        }
+    }
+}
+
+/// Descriptor spending policy
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+pub struct Policy {
+    /// Identifier for this policy node
+    pub id: String,
+
+    /// Type of this policy node
+    #[serde(flatten)]
+    pub item: SatisfiableItem,
+    /// How much a given PSBT already satisfies this policy node in terms of signatures
+    pub satisfaction: Satisfaction,
+    /// How the wallet's descriptor can satisfy this policy node
+    pub contribution: Satisfaction,
+}
+
+/// An extra condition that must be satisfied but that is out of control of the user
+/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
+#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
+pub struct Condition {
+    /// Optional CheckSequenceVerify condition
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub csv: Option<Sequence>,
+    /// Optional timelock condition
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub timelock: Option<absolute::LockTime>,
+}
+
+impl Condition {
+    fn merge_nlocktime(
+        a: absolute::LockTime,
+        b: absolute::LockTime,
+    ) -> Result<absolute::LockTime, PolicyError> {
+        if !a.is_same_unit(b) {
+            Err(PolicyError::MixedTimelockUnits)
+        } else if a > b {
+            Ok(a)
+        } else {
+            Ok(b)
+        }
+    }
+
+    fn merge_nsequence(a: Sequence, b: Sequence) -> Result<Sequence, PolicyError> {
+        if a.is_time_locked() != b.is_time_locked() {
+            Err(PolicyError::MixedTimelockUnits)
+        } else {
+            Ok(max(a, b))
+        }
+    }
+
+    pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
+        match (self.csv, other.csv) {
+            (Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
+            (None, any) => self.csv = any,
+            _ => {}
+        }
+
+        match (self.timelock, other.timelock) {
+            (Some(a), Some(b)) => self.timelock = Some(Self::merge_nlocktime(a, b)?),
+            (None, any) => self.timelock = any,
+            _ => {}
+        }
+
+        Ok(self)
+    }
+
+    /// Returns `true` if there are no extra conditions to verify
+    pub fn is_null(&self) -> bool {
+        self.csv.is_none() && self.timelock.is_none()
+    }
+}
+
+/// Errors that can happen while extracting and manipulating policies
+#[derive(Debug, PartialEq, Eq)]
+pub enum PolicyError {
+    /// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
+    NotEnoughItemsSelected(String),
+    /// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`]
+    IndexOutOfRange(usize),
+    /// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`]
+    AddOnLeaf,
+    /// Can not add to an item that is [`Satisfaction::PartialComplete`]
+    AddOnPartialComplete,
+    /// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000
+    MixedTimelockUnits,
+    /// Incompatible conditions (not currently used)
+    IncompatibleConditions,
+}
+
+impl fmt::Display for PolicyError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::NotEnoughItemsSelected(err) => write!(f, "Not enough items selected: {}", err),
+            Self::IndexOutOfRange(index) => write!(f, "Index out of range: {}", index),
+            Self::AddOnLeaf => write!(f, "Add on leaf"),
+            Self::AddOnPartialComplete => write!(f, "Add on partial complete"),
+            Self::MixedTimelockUnits => write!(f, "Mixed timelock units"),
+            Self::IncompatibleConditions => write!(f, "Incompatible conditions"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for PolicyError {}
+
+impl Policy {
+    fn new(item: SatisfiableItem) -> Self {
+        Policy {
+            id: item.id(),
+            item,
+            satisfaction: Satisfaction::None,
+            contribution: Satisfaction::None,
+        }
+    }
+
+    fn make_and(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
+        match (a, b) {
+            (None, None) => Ok(None),
+            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
+            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
+        }
+    }
+
+    fn make_or(a: Option<Policy>, b: Option<Policy>) -> Result<Option<Policy>, PolicyError> {
+        match (a, b) {
+            (None, None) => Ok(None),
+            (Some(x), None) | (None, Some(x)) => Ok(Some(x)),
+            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
+        }
+    }
+
+    fn make_thresh(items: Vec<Policy>, threshold: usize) -> Result<Option<Policy>, PolicyError> {
+        if threshold == 0 {
+            return Ok(None);
+        }
+
+        let mut contribution = Satisfaction::Partial {
+            n: items.len(),
+            m: threshold,
+            items: vec![],
+            conditions: Default::default(),
+            sorted: None,
+        };
+        let mut satisfaction = contribution.clone();
+        for (index, item) in items.iter().enumerate() {
+            contribution.add(&item.contribution, index)?;
+            satisfaction.add(&item.satisfaction, index)?;
+        }
+
+        contribution.finalize();
+        satisfaction.finalize();
+
+        let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
+        policy.contribution = contribution;
+        policy.satisfaction = satisfaction;
+
+        Ok(Some(policy))
+    }
+
+    fn make_multisig<Ctx: ScriptContext + 'static>(
+        keys: &[DescriptorPublicKey],
+        signers: &SignersContainer,
+        build_sat: BuildSatisfaction,
+        threshold: usize,
+        sorted: bool,
+        secp: &SecpCtx,
+    ) -> Result<Option<Policy>, PolicyError> {
+        if threshold == 0 {
+            return Ok(None);
+        }
+
+        let parsed_keys = keys.iter().map(|k| PkOrF::from_key(k, secp)).collect();
+
+        let mut contribution = Satisfaction::Partial {
+            n: keys.len(),
+            m: threshold,
+            items: vec![],
+            conditions: Default::default(),
+            sorted: Some(sorted),
+        };
+        let mut satisfaction = contribution.clone();
+
+        for (index, key) in keys.iter().enumerate() {
+            if signers.find(signer_id(key, secp)).is_some() {
+                contribution.add(
+                    &Satisfaction::Complete {
+                        condition: Default::default(),
+                    },
+                    index,
+                )?;
+            }
+
+            if let Some(psbt) = build_sat.psbt() {
+                if Ctx::find_signature(psbt, key, secp) {
+                    satisfaction.add(
+                        &Satisfaction::Complete {
+                            condition: Default::default(),
+                        },
+                        index,
+                    )?;
+                }
+            }
+        }
+        satisfaction.finalize();
+        contribution.finalize();
+
+        let mut policy: Policy = SatisfiableItem::Multisig {
+            keys: parsed_keys,
+            threshold,
+        }
+        .into();
+        policy.contribution = contribution;
+        policy.satisfaction = satisfaction;
+
+        Ok(Some(policy))
+    }
+
+    /// Return whether or not a specific path in the policy tree is required to unambiguously
+    /// create a transaction
+    ///
+    /// What this means is that for some spending policies the user should select which paths in
+    /// the tree it intends to satisfy while signing, because the transaction must be created differently based
+    /// on that.
+    pub fn requires_path(&self) -> bool {
+        self.get_condition(&BTreeMap::new()).is_err()
+    }
+
+    /// Return the conditions that are set by the spending policy for a given path in the
+    /// policy tree
+    pub fn get_condition(
+        &self,
+        path: &BTreeMap<String, Vec<usize>>,
+    ) -> Result<Condition, PolicyError> {
+        // if items.len() == threshold, selected can be omitted and we take all of them by default
+        let default = match &self.item {
+            SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
+                (0..*threshold).collect()
+            }
+            SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
+            _ => HashSet::new(),
+        };
+        let selected: HashSet<_> = match path.get(&self.id) {
+            Some(arr) => arr.iter().copied().collect(),
+            _ => default,
+        };
+
+        match &self.item {
+            SatisfiableItem::Thresh { items, threshold } => {
+                let mapped_req = items
+                    .iter()
+                    .map(|i| i.get_condition(path))
+                    .collect::<Vec<_>>();
+
+                // if all the requirements are null we don't care about `selected` because there
+                // are no requirements
+                if mapped_req
+                    .iter()
+                    .all(|cond| matches!(cond, Ok(c) if c.is_null()))
+                {
+                    return Ok(Condition::default());
+                }
+
+                // make sure all the indexes in the `selected` list are within range
+                for index in &selected {
+                    if *index >= items.len() {
+                        return Err(PolicyError::IndexOutOfRange(*index));
+                    }
+                }
+
+                // if we have something, make sure we have enough items. note that the user can set
+                // an empty value for this step in case of n-of-n, because `selected` is set to all
+                // the elements above
+                if selected.len() < *threshold {
+                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
+                }
+
+                // check the selected items, see if there are conflicting requirements
+                mapped_req
+                    .into_iter()
+                    .enumerate()
+                    .filter(|(index, _)| selected.contains(index))
+                    .try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
+            }
+            SatisfiableItem::Multisig { keys, threshold } => {
+                if selected.len() < *threshold {
+                    return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
+                }
+                if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
+                    return Err(PolicyError::IndexOutOfRange(item));
+                }
+
+                Ok(Condition::default())
+            }
+            SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition {
+                csv: None,
+                timelock: Some(*value),
+            }),
+            SatisfiableItem::RelativeTimelock { value } => Ok(Condition {
+                csv: Some(*value),
+                timelock: None,
+            }),
+            _ => Ok(Condition::default()),
+        }
+    }
+}
+
+impl From<SatisfiableItem> for Policy {
+    fn from(other: SatisfiableItem) -> Self {
+        Self::new(other)
+    }
+}
+
+fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
+    // For consistency we always compute the key hash in "ecdsa" form (with the leading sign
+    // prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
+    // for a key, so it doesn't really matter how the identifier is computed.
+    match key {
+        DescriptorPublicKey::Single(SinglePub {
+            key: SinglePubKey::FullKey(pk),
+            ..
+        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
+        DescriptorPublicKey::Single(SinglePub {
+            key: SinglePubKey::XOnly(pk),
+            ..
+        }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
+        DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
+        DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
+    }
+}
+
+fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
+    key: &DescriptorPublicKey,
+    signers: &SignersContainer,
+    build_sat: BuildSatisfaction,
+    secp: &SecpCtx,
+    make_policy: M,
+    find_sig: F,
+) -> Policy {
+    let mut policy: Policy = make_policy().into();
+
+    policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
+        Satisfaction::Complete {
+            condition: Default::default(),
+        }
+    } else {
+        Satisfaction::None
+    };
+
+    if let Some(psbt) = build_sat.psbt() {
+        policy.satisfaction = if find_sig(psbt) {
+            Satisfaction::Complete {
+                condition: Default::default(),
+            }
+        } else {
+            Satisfaction::None
+        };
+    }
+
+    policy
+}
+
+fn generic_sig_in_psbt<
+    // C is for "check", it's a closure we use to *check* if a psbt input contains the signature
+    // for a specific key
+    C: Fn(&psbt::Input, &SinglePubKey) -> bool,
+    // E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
+    E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
+>(
+    psbt: &Psbt,
+    key: &DescriptorPublicKey,
+    secp: &SecpCtx,
+    check: C,
+    extract: E,
+) -> bool {
+    //TODO check signature validity
+    psbt.inputs.iter().all(|input| match key {
+        DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
+        DescriptorPublicKey::XPub(xpub) => {
+            //TODO check actual derivation matches
+            match extract(input, xpub.root_fingerprint(secp)) {
+                Some(pubkey) => check(input, &pubkey),
+                None => false,
+            }
+        }
+        DescriptorPublicKey::MultiXPub(xpub) => {
+            //TODO check actual derivation matches
+            match extract(input, xpub.root_fingerprint(secp)) {
+                Some(pubkey) => check(input, &pubkey),
+                None => false,
+            }
+        }
+    })
+}
+
+trait SigExt: ScriptContext {
+    fn make_signature(
+        key: &DescriptorPublicKey,
+        signers: &SignersContainer,
+        build_sat: BuildSatisfaction,
+        secp: &SecpCtx,
+    ) -> Policy;
+
+    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
+}
+
+impl<T: ScriptContext + 'static> SigExt for T {
+    fn make_signature(
+        key: &DescriptorPublicKey,
+        signers: &SignersContainer,
+        build_sat: BuildSatisfaction,
+        secp: &SecpCtx,
+    ) -> Policy {
+        if T::as_enum().is_taproot() {
+            make_generic_signature(
+                key,
+                signers,
+                build_sat,
+                secp,
+                || SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
+                |psbt| Self::find_signature(psbt, key, secp),
+            )
+        } else {
+            make_generic_signature(
+                key,
+                signers,
+                build_sat,
+                secp,
+                || SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
+                |psbt| Self::find_signature(psbt, key, secp),
+            )
+        }
+    }
+
+    fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
+        if T::as_enum().is_taproot() {
+            generic_sig_in_psbt(
+                psbt,
+                key,
+                secp,
+                |input, pk| {
+                    let pk = match pk {
+                        SinglePubKey::XOnly(pk) => pk,
+                        _ => return false,
+                    };
+
+                    if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
+                        true
+                    } else {
+                        input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
+                    }
+                },
+                |input, fing| {
+                    input
+                        .tap_key_origins
+                        .iter()
+                        .find(|(_, (_, (f, _)))| f == &fing)
+                        .map(|(pk, _)| SinglePubKey::XOnly(*pk))
+                },
+            )
+        } else {
+            generic_sig_in_psbt(
+                psbt,
+                key,
+                secp,
+                |input, pk| match pk {
+                    SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
+                    _ => false,
+                },
+                |input, fing| {
+                    input
+                        .bip32_derivation
+                        .iter()
+                        .find(|(_, (f, _))| f == &fing)
+                        .map(|(pk, _)| SinglePubKey::FullKey(PublicKey::new(*pk)))
+                },
+            )
+        }
+    }
+}
+
+impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
+    fn extract_policy(
+        &self,
+        signers: &SignersContainer,
+        build_sat: BuildSatisfaction,
+        secp: &SecpCtx,
+    ) -> Result<Option<Policy>, Error> {
+        Ok(match &self.node {
+            // Leaves
+            Terminal::True | Terminal::False => None,
+            Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
+            Terminal::PkH(pubkey_hash) => {
+                Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
+            }
+            Terminal::After(value) => {
+                let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
+                    value: (*value).into(),
+                }
+                .into();
+                policy.contribution = Satisfaction::Complete {
+                    condition: Condition {
+                        timelock: Some((*value).into()),
+                        csv: None,
+                    },
+                };
+                if let BuildSatisfaction::PsbtTimelocks {
+                    current_height,
+                    psbt,
+                    ..
+                } = build_sat
+                {
+                    let after = After::new(Some(current_height), false);
+                    let after_sat =
+                        Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
+                    let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
+                        Satisfier::<bitcoin::PublicKey>::check_after(&sat, (*value).into())
+                    });
+                    if after_sat && inputs_sat {
+                        policy.satisfaction = policy.contribution.clone();
+                    }
+                }
+
+                Some(policy)
+            }
+            Terminal::Older(value) => {
+                let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
+                policy.contribution = Satisfaction::Complete {
+                    condition: Condition {
+                        timelock: None,
+                        csv: Some(*value),
+                    },
+                };
+                if let BuildSatisfaction::PsbtTimelocks {
+                    current_height,
+                    input_max_height,
+                    psbt,
+                } = build_sat
+                {
+                    let older = Older::new(Some(current_height), Some(input_max_height), false);
+                    let older_sat = Satisfier::<bitcoin::PublicKey>::check_older(&older, *value);
+                    let inputs_sat = psbt_inputs_sat(psbt)
+                        .all(|sat| Satisfier::<bitcoin::PublicKey>::check_older(&sat, *value));
+                    if older_sat && inputs_sat {
+                        policy.satisfaction = policy.contribution.clone();
+                    }
+                }
+
+                Some(policy)
+            }
+            Terminal::Sha256(hash) => Some(SatisfiableItem::Sha256Preimage { hash: *hash }.into()),
+            Terminal::Hash256(hash) => {
+                Some(SatisfiableItem::Hash256Preimage { hash: *hash }.into())
+            }
+            Terminal::Ripemd160(hash) => {
+                Some(SatisfiableItem::Ripemd160Preimage { hash: *hash }.into())
+            }
+            Terminal::Hash160(hash) => {
+                Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
+            }
+            Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => {
+                Policy::make_multisig::<Ctx>(pks, signers, build_sat, *k, false, secp)?
+            }
+            // Identities
+            Terminal::Alt(inner)
+            | Terminal::Swap(inner)
+            | Terminal::Check(inner)
+            | Terminal::DupIf(inner)
+            | Terminal::Verify(inner)
+            | Terminal::NonZero(inner)
+            | Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, build_sat, secp)?,
+            // Complex policies
+            Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
+                a.extract_policy(signers, build_sat, secp)?,
+                b.extract_policy(signers, build_sat, secp)?,
+            )?,
+            Terminal::AndOr(x, y, z) => Policy::make_or(
+                Policy::make_and(
+                    x.extract_policy(signers, build_sat, secp)?,
+                    y.extract_policy(signers, build_sat, secp)?,
+                )?,
+                z.extract_policy(signers, build_sat, secp)?,
+            )?,
+            Terminal::OrB(a, b)
+            | Terminal::OrD(a, b)
+            | Terminal::OrC(a, b)
+            | Terminal::OrI(a, b) => Policy::make_or(
+                a.extract_policy(signers, build_sat, secp)?,
+                b.extract_policy(signers, build_sat, secp)?,
+            )?,
+            Terminal::Thresh(k, nodes) => {
+                let mut threshold = *k;
+                let mapped: Vec<_> = nodes
+                    .iter()
+                    .map(|n| n.extract_policy(signers, build_sat, secp))
+                    .collect::<Result<Vec<_>, _>>()?
+                    .into_iter()
+                    .flatten()
+                    .collect();
+
+                if mapped.len() < nodes.len() {
+                    threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
+                        None => return Ok(None),
+                        Some(x) => x,
+                    };
+                }
+
+                Policy::make_thresh(mapped, threshold)?
+            }
+
+            // Unsupported
+            Terminal::RawPkH(_) => None,
+        })
+    }
+}
+
+fn psbt_inputs_sat(psbt: &Psbt) -> impl Iterator<Item = PsbtInputSatisfier> {
+    (0..psbt.inputs.len()).map(move |i| PsbtInputSatisfier::new(psbt, i))
+}
+
+/// Options to build the satisfaction field in the policy
+#[derive(Debug, Clone, Copy)]
+pub enum BuildSatisfaction<'a> {
+    /// Don't generate `satisfaction` field
+    None,
+    /// Analyze the given PSBT to check for existing signatures
+    Psbt(&'a Psbt),
+    /// Like `Psbt` variant and also check for expired timelocks
+    PsbtTimelocks {
+        /// Given PSBT
+        psbt: &'a Psbt,
+        /// Current blockchain height
+        current_height: u32,
+        /// The highest confirmation height between the inputs
+        /// CSV should consider different inputs, but we consider the worst condition for the tx as whole
+        input_max_height: u32,
+    },
+}
+impl<'a> BuildSatisfaction<'a> {
+    fn psbt(&self) -> Option<&'a Psbt> {
+        match self {
+            BuildSatisfaction::None => None,
+            BuildSatisfaction::Psbt(psbt) => Some(psbt),
+            BuildSatisfaction::PsbtTimelocks { psbt, .. } => Some(psbt),
+        }
+    }
+}
+
+impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
+    fn extract_policy(
+        &self,
+        signers: &SignersContainer,
+        build_sat: BuildSatisfaction,
+        secp: &SecpCtx,
+    ) -> Result<Option<Policy>, Error> {
+        fn make_sortedmulti<Ctx: ScriptContext + 'static>(
+            keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
+            signers: &SignersContainer,
+            build_sat: BuildSatisfaction,
+            secp: &SecpCtx,
+        ) -> Result<Option<Policy>, Error> {
+            Ok(Policy::make_multisig::<Ctx>(
+                keys.pks.as_ref(),
+                signers,
+                build_sat,
+                keys.k,
+                true,
+                secp,
+            )?)
+        }
+
+        match self {
+            Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
+                pk.as_inner(),
+                signers,
+                build_sat,
+                secp,
+            ))),
+            Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
+                pk.as_inner(),
+                signers,
+                build_sat,
+                secp,
+            ))),
+            Descriptor::Sh(sh) => match sh.as_inner() {
+                ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
+                    pk.as_inner(),
+                    signers,
+                    build_sat,
+                    secp,
+                ))),
+                ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
+                ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
+                ShInner::Wsh(wsh) => match wsh.as_inner() {
+                    WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
+                    WshInner::SortedMulti(ref keys) => {
+                        make_sortedmulti(keys, signers, build_sat, secp)
+                    }
+                },
+            },
+            Descriptor::Wsh(wsh) => match wsh.as_inner() {
+                WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
+                WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
+            },
+            Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
+            Descriptor::Tr(tr) => {
+                // If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
+                // node with threshold = 1 and the key spend signature plus all the tree leaves
+                let key_spend_sig =
+                    miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
+
+                if tr.tap_tree().is_none() {
+                    Ok(Some(key_spend_sig))
+                } else {
+                    let mut items = vec![key_spend_sig];
+                    items.append(
+                        &mut tr
+                            .iter_scripts()
+                            .filter_map(|(_, ms)| {
+                                ms.extract_policy(signers, build_sat, secp).transpose()
+                            })
+                            .collect::<Result<Vec<_>, _>>()?,
+                    );
+
+                    Ok(Policy::make_thresh(items, 1)?)
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::descriptor;
+    use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
+
+    use super::*;
+    use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
+    use crate::keys::{DescriptorKey, IntoDescriptorKey};
+    use crate::wallet::signer::SignersContainer;
+    use alloc::{string::ToString, sync::Arc};
+    use assert_matches::assert_matches;
+    use bitcoin::bip32;
+    use bitcoin::secp256k1::Secp256k1;
+    use bitcoin::Network;
+    use core::str::FromStr;
+
+    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
+    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
+
+    const PATH: &str = "m/44'/1'/0'/0";
+
+    fn setup_keys<Ctx: ScriptContext>(
+        tprv: &str,
+        path: &str,
+        secp: &SecpCtx,
+    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
+        let path = bip32::DerivationPath::from_str(path).unwrap();
+        let tprv = bip32::Xpriv::from_str(tprv).unwrap();
+        let tpub = bip32::Xpub::from_priv(secp, &tprv);
+        let fingerprint = tprv.fingerprint(secp);
+        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
+        let pubkey = (tpub, path).into_descriptor_key().unwrap();
+
+        (prvkey, pubkey, fingerprint)
+    }
+
+    // test ExtractPolicy trait for simple descriptors; wpkh(), sh(multi())
+
+    #[test]
+    fn test_extract_policy_for_wpkh() {
+        let secp = Secp256k1::new();
+
+        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
+        let desc = descriptor!(wpkh(pubkey)).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
+        assert_matches!(&policy.contribution, Satisfaction::None);
+
+        let desc = descriptor!(wpkh(prvkey)).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
+        assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
+    }
+
+    // 2 pub keys descriptor, required 2 prv keys
+    #[test]
+    fn test_extract_policy_for_sh_multi_partial_0of2() {
+        let secp = Secp256k1::new();
+        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let desc = descriptor!(sh(multi(2, pubkey0, pubkey1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
+            && keys[0] == PkOrF::Fingerprint(fingerprint0)
+            && keys[1] == PkOrF::Fingerprint(fingerprint1)
+        );
+        // TODO should this be "Satisfaction::None" since we have no prv keys?
+        // TODO should items and conditions not be empty?
+        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
+            && m == &2usize
+            && items.is_empty()
+            && conditions.is_empty()
+        );
+    }
+
+    // 1 prv and 1 pub key descriptor, required 2 prv keys
+    #[test]
+    fn test_extract_policy_for_sh_multi_partial_1of2() {
+        let secp = Secp256k1::new();
+        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (_prvkey1, pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let desc = descriptor!(sh(multi(2, prvkey0, pubkey1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
+            && keys[0] == PkOrF::Fingerprint(fingerprint0)
+            && keys[1] == PkOrF::Fingerprint(fingerprint1)
+        );
+
+        assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
+             && m == &2usize
+             && items.len() == 1
+             && conditions.contains_key(&0)
+        );
+    }
+
+    // 1 prv and 1 pub key descriptor, required 1 prv keys
+    #[test]
+    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
+    fn test_extract_policy_for_sh_multi_complete_1of2() {
+        let secp = Secp256k1::new();
+
+        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
+            && keys[0] == PkOrF::Fingerprint(fingerprint0)
+            && keys[1] == PkOrF::Fingerprint(fingerprint1)
+        );
+        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
+             && m == &1
+             && items.len() == 2
+             && conditions.contains_key(&vec![0])
+             && conditions.contains_key(&vec![1])
+        );
+    }
+
+    // 2 prv keys descriptor, required 2 prv keys
+    #[test]
+    fn test_extract_policy_for_sh_multi_complete_2of2() {
+        let secp = Secp256k1::new();
+
+        let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
+            && keys[0] == PkOrF::Fingerprint(fingerprint0)
+            && keys[1] == PkOrF::Fingerprint(fingerprint1)
+        );
+
+        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
+             && m == &2
+             && items.len() == 2
+             && conditions.contains_key(&vec![0,1])
+        );
+    }
+
+    // test ExtractPolicy trait with extended and single keys
+
+    #[test]
+    fn test_extract_policy_for_single_wpkh() {
+        let secp = Secp256k1::new();
+
+        let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR, PATH, &secp);
+        let desc = descriptor!(wpkh(pubkey)).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
+        assert_matches!(&policy.contribution, Satisfaction::None);
+
+        let desc = descriptor!(wpkh(prvkey)).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
+        assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
+    }
+
+    // single key, 1 prv and 1 pub key descriptor, required 1 prv keys
+    #[test]
+    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
+    fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
+        let secp = Secp256k1::new();
+
+        let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
+            && keys[0] == PkOrF::Fingerprint(fingerprint0)
+            && keys[1] == PkOrF::Fingerprint(fingerprint1)
+        );
+        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
+             && m == 1
+             && items.len() == 2
+             && conditions.contains_key(&vec![0])
+             && conditions.contains_key(&vec![1])
+        );
+    }
+
+    // test ExtractPolicy trait with descriptors containing timelocks in a thresh()
+
+    #[test]
+    #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
+    fn test_extract_policy_for_wsh_multi_timelock() {
+        let secp = Secp256k1::new();
+
+        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR, PATH, &secp);
+        let sequence = 50;
+        #[rustfmt::skip]
+        let desc = descriptor!(wsh(thresh(
+            2,
+            pk(prvkey0),
+            s:pk(pubkey1),
+            s:d:v:older(sequence)
+        )))
+        .unwrap();
+
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
+
+        assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
+             && m == &2
+             && items.len() == 3
+             && conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
+             && conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
+             && conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
+        );
+    }
+
+    // - mixed timelocks should fail
+
+    #[test]
+    #[ignore]
+    fn test_extract_policy_for_wsh_mixed_timelocks() {
+        let secp = Secp256k1::new();
+        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let locktime_threshold = 500000000; // if less than this means block number, else block time in seconds
+        let locktime_blocks = 100;
+        let locktime_seconds = locktime_blocks + locktime_threshold;
+        let desc = descriptor!(sh(and_v(
+            v: pk(prvkey0),
+            and_v(v: after(locktime_seconds), after(locktime_blocks))
+        )))
+        .unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let _policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+        // println!("desc policy = {:?}", policy); // TODO remove
+        // TODO how should this fail with mixed timelocks?
+    }
+
+    // - multiple timelocks of the same type should be correctly merged together
+    #[test]
+    #[ignore]
+    fn test_extract_policy_for_multiple_same_timelocks() {
+        let secp = Secp256k1::new();
+        let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR, PATH, &secp);
+        let locktime_blocks0 = 100;
+        let locktime_blocks1 = 200;
+        let desc = descriptor!(sh(and_v(
+            v: pk(prvkey0),
+            and_v(v: after(locktime_blocks0), after(locktime_blocks1))
+        )))
+        .unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let _policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+        // println!("desc policy = {:?}", policy); // TODO remove
+        // TODO how should this merge timelocks?
+        let (prvkey1, _pubkey1, _fingerprint1) = setup_keys(TPRV0_STR, PATH, &secp);
+        let locktime_seconds0 = 500000100;
+        let locktime_seconds1 = 500000200;
+        let desc = descriptor!(sh(and_v(
+            v: pk(prvkey1),
+            and_v(v: after(locktime_seconds0), after(locktime_seconds1))
+        )))
+        .unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+        let _policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        // println!("desc policy = {:?}", policy); // TODO remove
+
+        // TODO how should this merge timelocks?
+    }
+
+    #[test]
+    fn test_get_condition_multisig() {
+        let secp = Secp256k1::new();
+
+        let (_, pk0, _) = setup_keys(TPRV0_STR, PATH, &secp);
+        let (_, pk1, _) = setup_keys(TPRV1_STR, PATH, &secp);
+
+        let desc = descriptor!(wsh(multi(1, pk0, pk1))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        // no args, choose the default
+        let no_args = policy.get_condition(&vec![].into_iter().collect());
+        assert_eq!(no_args, Ok(Condition::default()));
+
+        // enough args
+        let eq_thresh =
+            policy.get_condition(&vec![(policy.id.clone(), vec![0])].into_iter().collect());
+        assert_eq!(eq_thresh, Ok(Condition::default()));
+
+        // more args, it doesn't really change anything
+        let gt_thresh =
+            policy.get_condition(&vec![(policy.id.clone(), vec![0, 1])].into_iter().collect());
+        assert_eq!(gt_thresh, Ok(Condition::default()));
+
+        // not enough args, error
+        let lt_thresh =
+            policy.get_condition(&vec![(policy.id.clone(), vec![])].into_iter().collect());
+        assert_eq!(
+            lt_thresh,
+            Err(PolicyError::NotEnoughItemsSelected(policy.id.clone()))
+        );
+
+        // index out of range
+        let out_of_range =
+            policy.get_condition(&vec![(policy.id.clone(), vec![5])].into_iter().collect());
+        assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
+    }
+
+    const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
+    const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
+    const CAROL_TPRV_STR:&str = "tprv8ZgxMBicQKsPdC3CicFifuLCEyVVdXVUNYorxUWj3iGZ6nimnLAYAY9SYB7ib8rKzRxrCKFcEytCt6szwd2GHnGPRCBLAEAoSVDefSNk4Bt";
+    const ALICE_BOB_PATH: &str = "m/0'";
+
+    #[test]
+    fn test_extract_satisfaction() {
+        const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
+        const BOB_SIGNED_PSBT: &str =   "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA==";
+        const ALICE_BOB_SIGNED_PSBT: &str =   "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA";
+
+        let secp = Secp256k1::new();
+
+        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(wsh(multi(2, prvkey_alice, prvkey_bob))).unwrap();
+
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+
+        let addr = wallet_desc
+            .at_derivation_index(0)
+            .unwrap()
+            .address(Network::Testnet)
+            .unwrap();
+        assert_eq!(
+            "tb1qg3cwv3xt50gdg875qvjjpfgaps86gtk4rz0ejvp6ttc5ldnlxuvqlcn0xk",
+            addr.to_string()
+        );
+
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
+
+        let policy_alice_psbt = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
+            .unwrap()
+            .unwrap();
+        //println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
+
+        assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
+             && m == &2
+             && items == &vec![0]
+        );
+
+        let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
+        let policy_bob_psbt = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
+            .unwrap()
+            .unwrap();
+        //println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
+
+        assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
+             && m == &2
+             && items == &vec![1]
+        );
+
+        let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
+        let policy_alice_bob_psbt = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
+            .unwrap()
+            .unwrap();
+        assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
+             && m == &2
+             && items == &vec![0, 1]
+        );
+    }
+
+    #[test]
+    fn test_extract_satisfaction_timelock() {
+        //const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
+        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED:     &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
+        const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED: &str ="cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstIMEUCIQCtZxNm6H3Ux3pnc64DSpgohMdBj+57xhFHcURYt2BpPAIgG3OnI7bcj/3GtWX1HHyYGSI7QGa/zq5YnsmK1Cw29NABAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEIoAQASDBFAiEArWcTZuh91Md6Z3OuA0qYKITHQY/ue8YRR3FEWLdgaTwCIBtzpyO23I/9xrVl9Rx8mBkiO0Bmv86uWJ7JitQsNvTQAQEBUnZjUrJpaHwhA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLrJN8IQL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiKyTUocAAA==";
+
+        let secp = Secp256k1::new();
+
+        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc =
+            descriptor!(wsh(thresh(2,n:d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
+
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let addr = wallet_desc
+            .at_derivation_index(0)
+            .unwrap()
+            .address(Network::Testnet)
+            .unwrap();
+        assert_eq!(
+            "tb1qsydsey4hexagwkvercqsmes6yet0ndkyt6uzcphtqnygjd8hmzmsfxrv58",
+            addr.to_string()
+        );
+
+        let psbt = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap();
+
+        let build_sat = BuildSatisfaction::PsbtTimelocks {
+            psbt: &psbt,
+            current_height: 10,
+            input_max_height: 9,
+        };
+
+        let policy = wallet_desc
+            .extract_policy(&signers_container, build_sat, &secp)
+            .unwrap()
+            .unwrap();
+        assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
+             && m == &2
+             && items.is_empty()
+        );
+        //println!("{}", serde_json::to_string(&policy).unwrap());
+
+        let build_sat_expired = BuildSatisfaction::PsbtTimelocks {
+            psbt: &psbt,
+            current_height: 12,
+            input_max_height: 9,
+        };
+
+        let policy_expired = wallet_desc
+            .extract_policy(&signers_container, build_sat_expired, &secp)
+            .unwrap()
+            .unwrap();
+        assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
+             && m == &2
+             && items == &vec![0]
+        );
+        //println!("{}", serde_json::to_string(&policy_expired).unwrap());
+
+        let psbt_signed = Psbt::from_str(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap();
+
+        let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
+            psbt: &psbt_signed,
+            current_height: 12,
+            input_max_height: 9,
+        };
+
+        let policy_expired_signed = wallet_desc
+            .extract_policy(&signers_container, build_sat_expired_signed, &secp)
+            .unwrap()
+            .unwrap();
+        assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
+             && m == &2
+             && items == &vec![0, 1]
+        );
+        //println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
+    }
+
+    #[test]
+    fn test_extract_pkh() {
+        let secp = Secp256k1::new();
+
+        let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (prvkey_carol, _, _) = setup_keys(CAROL_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(wsh(c: andor(
+            pk(prvkey_alice),
+            pk_k(prvkey_bob),
+            pk_h(prvkey_carol),
+        )))
+        .unwrap();
+
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
+        assert!(policy.is_ok());
+    }
+
+    #[test]
+    fn test_extract_tr_key_spend() {
+        let secp = Secp256k1::new();
+
+        let (prvkey, _, fingerprint) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(tr(prvkey)).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap();
+        assert_eq!(
+            policy,
+            Some(Policy {
+                id: "48u0tz0n".to_string(),
+                item: SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(fingerprint)),
+                satisfaction: Satisfaction::None,
+                contribution: Satisfaction::Complete {
+                    condition: Condition::default()
+                }
+            })
+        );
+    }
+
+    #[test]
+    fn test_extract_tr_script_spend() {
+        let secp = Secp256k1::new();
+
+        let (alice_prv, _, alice_fing) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (_, bob_pub, bob_fing) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+        let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
+
+        let policy = wallet_desc
+            .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
+        assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
+
+        let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
+        let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
+
+        let thresh_items = match policy.item {
+            SatisfiableItem::Thresh { items, .. } => items,
+            _ => unreachable!(),
+        };
+
+        assert_eq!(thresh_items[0].item, bob_sig);
+        assert_eq!(thresh_items[1].item, alice_sig);
+    }
+
+    #[test]
+    fn test_extract_tr_satisfaction_key_spend() {
+        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA==";
+        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA=";
+
+        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
+        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
+
+        let secp = Secp256k1::new();
+
+        let (_, pubkey, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(tr(pubkey)).unwrap();
+        let (wallet_desc, _) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+
+        let policy_unsigned = wallet_desc
+            .extract_policy(
+                &SignersContainer::default(),
+                BuildSatisfaction::Psbt(&unsigned_psbt),
+                &secp,
+            )
+            .unwrap()
+            .unwrap();
+        let policy_signed = wallet_desc
+            .extract_policy(
+                &SignersContainer::default(),
+                BuildSatisfaction::Psbt(&signed_psbt),
+                &secp,
+            )
+            .unwrap()
+            .unwrap();
+
+        assert_eq!(policy_unsigned.satisfaction, Satisfaction::None);
+        assert_eq!(
+            policy_signed.satisfaction,
+            Satisfaction::Complete {
+                condition: Default::default()
+            }
+        );
+    }
+
+    #[test]
+    fn test_extract_tr_satisfaction_script_spend() {
+        const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
+        const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2AQcAAQhCAUALcP9w/+Ddly9DWdhHTnQ9uCDWLPZjR6vKbKePswW2Ee6W5KNfrklus/8z98n7BQ1U4vADHk0FbadeeL8rrbHlARNAC3D/cP/g3ZcvQ1nYR050Pbgg1iz2Y0erymynj7MFthHuluSjX65JbrP/M/fJ+wUNVOLwAx5NBW2nXni/K62x5UEUeEbK57HG1FUp69HHhjBZH9bSvss8e3qhLoMuXPK5hBr2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHUAXNmWieJ80Fs+PMa2C186YOBPZbYG/ieEUkagMwzJ788SoCucNdp5wnxfpuJVygFhglDrXGzujFtC82PrMohwuIhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
+
+        let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
+        let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
+
+        let secp = Secp256k1::new();
+
+        let (_, alice_pub, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
+        let (_, bob_pub, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
+
+        let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap();
+        let (wallet_desc, _) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+
+        let policy_unsigned = wallet_desc
+            .extract_policy(
+                &SignersContainer::default(),
+                BuildSatisfaction::Psbt(&unsigned_psbt),
+                &secp,
+            )
+            .unwrap()
+            .unwrap();
+        let policy_signed = wallet_desc
+            .extract_policy(
+                &SignersContainer::default(),
+                BuildSatisfaction::Psbt(&signed_psbt),
+                &secp,
+            )
+            .unwrap()
+            .unwrap();
+
+        assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
+        assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
+
+        assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
+        assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
+
+        let satisfied_items = match policy_signed.item {
+            SatisfiableItem::Thresh { items, .. } => items,
+            _ => unreachable!(),
+        };
+
+        assert_eq!(
+            satisfied_items[0].satisfaction,
+            Satisfaction::Complete {
+                condition: Default::default()
+            }
+        );
+        assert_eq!(
+            satisfied_items[1].satisfaction,
+            Satisfaction::Complete {
+                condition: Default::default()
+            }
+        );
+    }
+}
diff --git a/crates/wallet/src/descriptor/template.rs b/crates/wallet/src/descriptor/template.rs
new file mode 100644 (file)
index 0000000..528be1f
--- /dev/null
@@ -0,0 +1,985 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Descriptor templates
+//!
+//! This module contains the definition of various common script templates that are ready to be
+//! used. See the documentation of each template for an example.
+
+use bitcoin::bip32;
+use bitcoin::Network;
+
+use miniscript::{Legacy, Segwitv0, Tap};
+
+use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
+use crate::descriptor::DescriptorError;
+use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworks};
+use crate::wallet::utils::SecpCtx;
+use crate::{descriptor, KeychainKind};
+
+/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
+pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
+
+/// Trait for descriptor templates that can be built into a full descriptor
+///
+/// Since [`IntoWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be
+/// passed directly to the [`Wallet`](crate::Wallet) constructor.
+///
+/// ## Example
+///
+/// ```
+/// use bdk_wallet::descriptor::error::Error as DescriptorError;
+/// use bdk_wallet::keys::{IntoDescriptorKey, KeyError};
+/// use bdk_wallet::miniscript::Legacy;
+/// use bdk_wallet::template::{DescriptorTemplate, DescriptorTemplateOut};
+/// use bitcoin::Network;
+///
+/// struct MyP2PKH<K: IntoDescriptorKey<Legacy>>(K);
+///
+/// impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for MyP2PKH<K> {
+///     fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+///         Ok(bdk_wallet::descriptor!(pkh(self.0))?)
+///     }
+/// }
+/// ```
+pub trait DescriptorTemplate {
+    /// Build the complete descriptor
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError>;
+}
+
+/// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its
+/// [`build`](DescriptorTemplate::build) method
+impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
+    fn into_wallet_descriptor(
+        self,
+        secp: &SecpCtx,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
+        self.build(network)?.into_wallet_descriptor(secp, network)
+    }
+}
+
+/// P2PKH template. Expands to a descriptor `pkh(key)`
+///
+/// ## Example
+///
+/// ```
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::Wallet;
+/// # use bdk_wallet::KeychainKind;
+/// use bdk_wallet::template::P2Pkh;
+///
+/// let key =
+///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
+/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?;
+///
+/// assert_eq!(
+///     wallet
+///         .next_unused_address(KeychainKind::External)?
+///         .to_string(),
+///     "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
+/// );
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct P2Pkh<K: IntoDescriptorKey<Legacy>>(pub K);
+
+impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
+    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        descriptor!(pkh(self.0))
+    }
+}
+
+/// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))`
+///
+/// ## Example
+///
+/// ```
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::Wallet;
+/// # use bdk_wallet::KeychainKind;
+/// use bdk_wallet::template::P2Wpkh_P2Sh;
+///
+/// let key =
+///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
+/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?;
+///
+/// assert_eq!(
+///     wallet
+///         .next_unused_address(KeychainKind::External)?
+///         .to_string(),
+///     "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
+/// );
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+#[allow(non_camel_case_types)]
+pub struct P2Wpkh_P2Sh<K: IntoDescriptorKey<Segwitv0>>(pub K);
+
+impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
+    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        descriptor!(sh(wpkh(self.0)))
+    }
+}
+
+/// P2WPKH template. Expands to a descriptor `wpkh(key)`
+///
+/// ## Example
+///
+/// ```
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet};
+/// # use bdk_wallet::KeychainKind;
+/// use bdk_wallet::template::P2Wpkh;
+///
+/// let key =
+///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
+/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?;
+///
+/// assert_eq!(
+///     wallet
+///         .next_unused_address(KeychainKind::External)?
+///         .to_string(),
+///     "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
+/// );
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct P2Wpkh<K: IntoDescriptorKey<Segwitv0>>(pub K);
+
+impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
+    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        descriptor!(wpkh(self.0))
+    }
+}
+
+/// P2TR template. Expands to a descriptor `tr(key)`
+///
+/// ## Example
+///
+/// ```
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::Wallet;
+/// # use bdk_wallet::KeychainKind;
+/// use bdk_wallet::template::P2TR;
+///
+/// let key =
+///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
+/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?;
+///
+/// assert_eq!(
+///     wallet
+///         .next_unused_address(KeychainKind::External)?
+///         .to_string(),
+///     "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
+/// );
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct P2TR<K: IntoDescriptorKey<Tap>>(pub K);
+
+impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
+    fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        descriptor!(tr(self.0))
+    }
+}
+
+/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
+///
+/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
+///
+/// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip44;
+///
+/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip44(key.clone(), KeychainKind::External),
+///     Some(Bip44(key, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
+
+impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network)
+    }
+}
+
+/// BIP44 public template. Expands to `pkh(key/{0,1}/*)`
+///
+/// This assumes that the key used has already been derived with `m/44'/0'/0'` for Mainnet or `m/44'/1'/0'` for Testnet.
+///
+/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
+///
+/// See [`Bip44`] for a template that does the full derivation, but requires private data
+/// for the key.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip44Public;
+///
+/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
+/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip44Public(key.clone(), fingerprint, KeychainKind::External),
+///     Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
+
+impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Pkh(legacy::make_bipxx_public(
+            44, self.0, self.1, self.2, network,
+        )?)
+        .build(network)
+    }
+}
+
+/// BIP49 template. Expands to `sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))`
+///
+/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
+///
+/// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip49;
+///
+/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip49(key.clone(), KeychainKind::External),
+///     Some(Bip49(key, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
+
+impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network)
+    }
+}
+
+/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))`
+///
+/// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or `m/49'/1'/0'` for Testnet.
+///
+/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
+///
+/// See [`Bip49`] for a template that does the full derivation, but requires private data
+/// for the key.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip49Public;
+///
+/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
+/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip49Public(key.clone(), fingerprint, KeychainKind::External),
+///     Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
+
+impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
+            49, self.0, self.1, self.2, network,
+        )?)
+        .build(network)
+    }
+}
+
+/// BIP84 template. Expands to `wpkh(key/84'/{0,1}'/0'/{0,1}/*)`
+///
+/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
+///
+/// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip84;
+///
+/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip84(key.clone(), KeychainKind::External),
+///     Some(Bip84(key, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
+
+impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network)
+    }
+}
+
+/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
+///
+/// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or `m/84'/1'/0'` for Testnet.
+///
+/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
+///
+/// See [`Bip84`] for a template that does the full derivation, but requires private data
+/// for the key.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip84Public;
+///
+/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
+/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip84Public(key.clone(), fingerprint, KeychainKind::External),
+///     Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
+
+impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2Wpkh(segwit_v0::make_bipxx_public(
+            84, self.0, self.1, self.2, network,
+        )?)
+        .build(network)
+    }
+}
+
+/// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)`
+///
+/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
+///
+/// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip86;
+///
+/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip86(key.clone(), KeychainKind::External),
+///     Some(Bip86(key, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip86<K: DerivableKey<Tap>>(pub K, pub KeychainKind);
+
+impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network)
+    }
+}
+
+/// BIP86 public template. Expands to `tr(key/{0,1}/*)`
+///
+/// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or `m/86'/1'/0'` for Testnet.
+///
+/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
+///
+/// See [`Bip86`] for a template that does the full derivation, but requires private data
+/// for the key.
+///
+/// ## Example
+///
+/// ```
+/// # use std::str::FromStr;
+/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
+/// # use bdk_wallet::{Wallet,  KeychainKind};
+/// use bdk_wallet::template::Bip86Public;
+///
+/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
+/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     Bip86Public(key.clone(), fingerprint, KeychainKind::External),
+///     Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
+///     Network::Testnet,
+/// )?;
+///
+/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub struct Bip86Public<K: DerivableKey<Tap>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
+
+impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86Public<K> {
+    fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
+        P2TR(segwit_v1::make_bipxx_public(
+            86, self.0, self.1, self.2, network,
+        )?)
+        .build(network)
+    }
+}
+
+macro_rules! expand_make_bipxx {
+    ( $mod_name:ident, $ctx:ty ) => {
+        mod $mod_name {
+            use super::*;
+
+            pub(super) fn make_bipxx_private<K: DerivableKey<$ctx>>(
+                bip: u32,
+                key: K,
+                keychain: KeychainKind,
+                network: Network,
+            ) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
+                let mut derivation_path = alloc::vec::Vec::with_capacity(4);
+                derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?);
+
+                match network {
+                    Network::Bitcoin => {
+                        derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
+                    }
+                    _ => {
+                        derivation_path.push(bip32::ChildNumber::from_hardened_idx(1)?);
+                    }
+                }
+                derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
+
+                match keychain {
+                    KeychainKind::External => {
+                        derivation_path.push(bip32::ChildNumber::from_normal_idx(0)?)
+                    }
+                    KeychainKind::Internal => {
+                        derivation_path.push(bip32::ChildNumber::from_normal_idx(1)?)
+                    }
+                };
+
+                let derivation_path: bip32::DerivationPath = derivation_path.into();
+
+                Ok((key, derivation_path))
+            }
+            pub(super) fn make_bipxx_public<K: DerivableKey<$ctx>>(
+                bip: u32,
+                key: K,
+                parent_fingerprint: bip32::Fingerprint,
+                keychain: KeychainKind,
+                network: Network,
+            ) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
+                let derivation_path: bip32::DerivationPath = match keychain {
+                    KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
+                    KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(),
+                };
+
+                let source_path = bip32::DerivationPath::from(vec![
+                    bip32::ChildNumber::from_hardened_idx(bip)?,
+                    match network {
+                        Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
+                        _ => bip32::ChildNumber::from_hardened_idx(1)?,
+                    },
+                    bip32::ChildNumber::from_hardened_idx(0)?,
+                ]);
+
+                Ok((key, (parent_fingerprint, source_path), derivation_path))
+            }
+        }
+    };
+}
+
+expand_make_bipxx!(legacy, Legacy);
+expand_make_bipxx!(segwit_v0, Segwitv0);
+expand_make_bipxx!(segwit_v1, Tap);
+
+#[cfg(test)]
+mod test {
+    // test existing descriptor templates, make sure they are expanded to the right descriptors
+
+    use alloc::{string::ToString, vec::Vec};
+    use core::str::FromStr;
+
+    use super::*;
+    use crate::descriptor::{DescriptorError, DescriptorMeta};
+    use crate::keys::ValidNetworks;
+    use assert_matches::assert_matches;
+    use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
+    use miniscript::Descriptor;
+
+    // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
+    #[test]
+    fn test_bip44_template_cointype() {
+        use bitcoin::bip32::ChildNumber::{self, Hardened};
+
+        let xprvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
+        assert_eq!(Network::Bitcoin, xprvkey.network);
+        let xdesc = Bip44(xprvkey, KeychainKind::Internal)
+            .build(Network::Bitcoin)
+            .unwrap();
+
+        if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
+            let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
+            let purpose = path.first().unwrap();
+            assert_matches!(purpose, Hardened { index: 44 });
+            let coin_type = path.get(1).unwrap();
+            assert_matches!(coin_type, Hardened { index: 0 });
+        }
+
+        let tprvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        assert_eq!(Network::Testnet, tprvkey.network);
+        let tdesc = Bip44(tprvkey, KeychainKind::Internal)
+            .build(Network::Testnet)
+            .unwrap();
+
+        if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
+            let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
+            let purpose = path.first().unwrap();
+            assert_matches!(purpose, Hardened { index: 44 });
+            let coin_type = path.get(1).unwrap();
+            assert_matches!(coin_type, Hardened { index: 1 });
+        }
+    }
+
+    // verify template descriptor generates expected address(es)
+    fn check(
+        desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
+        is_witness: bool,
+        is_taproot: bool,
+        is_fixed: bool,
+        network: Network,
+        expected: &[&str],
+    ) {
+        let (desc, _key_map, _networks) = desc.unwrap();
+        assert_eq!(desc.is_witness(), is_witness);
+        assert_eq!(desc.is_taproot(), is_taproot);
+        assert_eq!(!desc.has_wildcard(), is_fixed);
+        for i in 0..expected.len() {
+            let index = i as u32;
+            let child_desc = if !desc.has_wildcard() {
+                desc.at_derivation_index(0).unwrap()
+            } else {
+                desc.at_derivation_index(index).unwrap()
+            };
+            let address = child_desc.address(network).unwrap();
+            assert_eq!(address.to_string(), *expected.get(i).unwrap());
+        }
+    }
+
+    // P2PKH
+    #[test]
+    fn test_p2ph_template() {
+        let prvkey =
+            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
+                .unwrap();
+        check(
+            P2Pkh(prvkey).build(Network::Bitcoin),
+            false,
+            false,
+            true,
+            Network::Regtest,
+            &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
+        );
+
+        let pubkey = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        check(
+            P2Pkh(pubkey).build(Network::Bitcoin),
+            false,
+            false,
+            true,
+            Network::Regtest,
+            &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
+        );
+    }
+
+    // P2WPKH-P2SH `sh(wpkh(key))`
+    #[test]
+    fn test_p2wphp2sh_template() {
+        let prvkey =
+            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
+                .unwrap();
+        check(
+            P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
+            true,
+            false,
+            true,
+            Network::Regtest,
+            &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
+        );
+
+        let pubkey = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        check(
+            P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
+            true,
+            false,
+            true,
+            Network::Regtest,
+            &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
+        );
+    }
+
+    // P2WPKH `wpkh(key)`
+    #[test]
+    fn test_p2wph_template() {
+        let prvkey =
+            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
+                .unwrap();
+        check(
+            P2Wpkh(prvkey).build(Network::Bitcoin),
+            true,
+            false,
+            true,
+            Network::Regtest,
+            &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
+        );
+
+        let pubkey = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        check(
+            P2Wpkh(pubkey).build(Network::Bitcoin),
+            true,
+            false,
+            true,
+            Network::Regtest,
+            &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
+        );
+    }
+
+    // P2TR `tr(key)`
+    #[test]
+    fn test_p2tr_template() {
+        let prvkey =
+            bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
+                .unwrap();
+        check(
+            P2TR(prvkey).build(Network::Bitcoin),
+            false,
+            true,
+            true,
+            Network::Regtest,
+            &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"],
+        );
+
+        let pubkey = bitcoin::PublicKey::from_str(
+            "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
+        )
+        .unwrap();
+        check(
+            P2TR(pubkey).build(Network::Bitcoin),
+            false,
+            true,
+            true,
+            Network::Regtest,
+            &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"],
+        );
+    }
+
+    // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
+    #[test]
+    fn test_bip44_template() {
+        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        check(
+            Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
+            false,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
+                "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
+                "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ",
+            ],
+        );
+        check(
+            Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
+            false,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
+                "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
+                "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA",
+            ],
+        );
+    }
+
+    // BIP44 public `pkh(key/{0,1}/*)`
+    #[test]
+    fn test_bip44_public_template() {
+        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
+        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
+        check(
+            Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
+            false,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
+                "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
+                "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g",
+            ],
+        );
+        check(
+            Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
+            false,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
+                "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
+                "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo",
+            ],
+        );
+    }
+
+    // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
+    #[test]
+    fn test_bip49_template() {
+        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        check(
+            Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
+                "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
+                "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4",
+            ],
+        );
+        check(
+            Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
+                "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
+                "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn",
+            ],
+        );
+    }
+
+    // BIP49 public `sh(wpkh(key/{0,1}/*))`
+    #[test]
+    fn test_bip49_public_template() {
+        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
+        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
+        check(
+            Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
+                "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
+                "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7",
+            ],
+        );
+        check(
+            Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
+                "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
+                "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp",
+            ],
+        );
+    }
+
+    // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
+    #[test]
+    fn test_bip84_template() {
+        let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+        check(
+            Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
+                "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
+                "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp",
+            ],
+        );
+        check(
+            Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
+                "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
+                "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce",
+            ],
+        );
+    }
+
+    // BIP84 public `wpkh(key/{0,1}/*)`
+    #[test]
+    fn test_bip84_public_template() {
+        let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
+        let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
+        check(
+            Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
+                "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
+                "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9",
+            ],
+        );
+        check(
+            Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
+            true,
+            false,
+            false,
+            Network::Regtest,
+            &[
+                "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
+                "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
+                "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp",
+            ],
+        );
+    }
+
+    // BIP86 `tr(key/86'/0'/0'/{0,1}/*)`
+    // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
+    #[test]
+    fn test_bip86_template() {
+        let prvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
+        check(
+            Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin),
+            false,
+            true,
+            false,
+            Network::Bitcoin,
+            &[
+                "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
+                "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
+                "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
+            ],
+        );
+        check(
+            Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
+            false,
+            true,
+            false,
+            Network::Bitcoin,
+            &[
+                "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
+                "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
+                "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
+            ],
+        );
+    }
+
+    // BIP86 public `tr(key/{0,1}/*)`
+    // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
+    #[test]
+    fn test_bip86_public_template() {
+        let pubkey = bitcoin::bip32::Xpub::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
+        let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap();
+        check(
+            Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
+            false,
+            true,
+            false,
+            Network::Bitcoin,
+            &[
+                "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
+                "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
+                "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
+            ],
+        );
+        check(
+            Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
+            false,
+            true,
+            false,
+            Network::Bitcoin,
+            &[
+                "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
+                "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
+                "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
+            ],
+        );
+    }
+}
diff --git a/crates/wallet/src/keys/bip39.rs b/crates/wallet/src/keys/bip39.rs
new file mode 100644 (file)
index 0000000..7158505
--- /dev/null
@@ -0,0 +1,227 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! BIP-0039
+
+// TODO: maybe write our own implementation of bip39? Seems stupid to have an extra dependency for
+// something that should be fairly simple to re-implement.
+
+use alloc::string::String;
+use bitcoin::bip32;
+use bitcoin::Network;
+
+use miniscript::ScriptContext;
+
+pub use bip39::{Error, Language, Mnemonic};
+
+type Seed = [u8; 64];
+
+/// Type describing entropy length (aka word count) in the mnemonic
+pub enum WordCount {
+    /// 12 words mnemonic (128 bits entropy)
+    Words12 = 128,
+    /// 15 words mnemonic (160 bits entropy)
+    Words15 = 160,
+    /// 18 words mnemonic (192 bits entropy)
+    Words18 = 192,
+    /// 21 words mnemonic (224 bits entropy)
+    Words21 = 224,
+    /// 24 words mnemonic (256 bits entropy)
+    Words24 = 256,
+}
+
+use super::{
+    any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
+};
+
+fn set_valid_on_any_network<Ctx: ScriptContext>(
+    descriptor_key: DescriptorKey<Ctx>,
+) -> DescriptorKey<Ctx> {
+    // We have to pick one network to build the xprv, but since the bip39 standard doesn't
+    // encode the network, the xprv we create is actually valid everywhere. So we override the
+    // valid networks with `any_network()`.
+    descriptor_key.override_valid_networks(any_network())
+}
+
+/// Type for a BIP39 mnemonic with an optional passphrase
+pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
+
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        Ok(bip32::Xpriv::new_master(Network::Bitcoin, &self[..])?.into())
+    }
+
+    fn into_descriptor_key(
+        self,
+        source: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let descriptor_key = self
+            .into_extended_key()?
+            .into_descriptor_key(source, derivation_path)?;
+
+        Ok(set_valid_on_any_network(descriptor_key))
+    }
+}
+
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        let (mnemonic, passphrase) = self;
+        let seed: Seed = mnemonic.to_seed(passphrase.as_deref().unwrap_or(""));
+
+        seed.into_extended_key()
+    }
+
+    fn into_descriptor_key(
+        self,
+        source: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let descriptor_key = self
+            .into_extended_key()?
+            .into_descriptor_key(source, derivation_path)?;
+
+        Ok(set_valid_on_any_network(descriptor_key))
+    }
+}
+
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for (GeneratedKey<Mnemonic, Ctx>, Option<String>) {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        let (mnemonic, passphrase) = self;
+        (mnemonic.into_key(), passphrase).into_extended_key()
+    }
+
+    fn into_descriptor_key(
+        self,
+        source: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let (mnemonic, passphrase) = self;
+        (mnemonic.into_key(), passphrase).into_descriptor_key(source, derivation_path)
+    }
+}
+
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        (self, None).into_extended_key()
+    }
+
+    fn into_descriptor_key(
+        self,
+        source: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let descriptor_key = self
+            .into_extended_key()?
+            .into_descriptor_key(source, derivation_path)?;
+
+        Ok(set_valid_on_any_network(descriptor_key))
+    }
+}
+
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
+    type Entropy = [u8; 32];
+
+    type Options = (WordCount, Language);
+    type Error = Option<bip39::Error>;
+
+    fn generate_with_entropy(
+        (word_count, language): Self::Options,
+        entropy: Self::Entropy,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        let entropy = &entropy[..(word_count as usize / 8)];
+        let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
+
+        Ok(GeneratedKey::new(mnemonic, any_network()))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use alloc::string::ToString;
+    use core::str::FromStr;
+
+    use bitcoin::bip32;
+
+    use bip39::{Language, Mnemonic};
+
+    use crate::keys::{any_network, GeneratableKey, GeneratedKey};
+
+    use super::WordCount;
+
+    #[test]
+    fn test_keys_bip39_mnemonic() {
+        let mnemonic =
+            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
+        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
+        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
+
+        let key = (mnemonic, path);
+        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
+        assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#0r8v4nkv");
+        assert_eq!(keys.len(), 1);
+        assert_eq!(networks.len(), 4);
+    }
+
+    #[test]
+    fn test_keys_bip39_mnemonic_passphrase() {
+        let mnemonic =
+            "aim bunker wash balance finish force paper analyst cabin spoon stable organ";
+        let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
+        let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
+
+        let key = ((mnemonic, Some("passphrase".into())), path);
+        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
+        assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)#h0j0tg5m");
+        assert_eq!(keys.len(), 1);
+        assert_eq!(networks.len(), 4);
+    }
+
+    #[test]
+    fn test_keys_generate_bip39() {
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate_with_entropy(
+                (WordCount::Words12, Language::English),
+                crate::keys::test::TEST_ENTROPY,
+            )
+            .unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+        assert_eq!(
+            generated_mnemonic.to_string(),
+            "primary fetch primary fetch primary fetch primary fetch primary fetch primary fever"
+        );
+
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate_with_entropy(
+                (WordCount::Words24, Language::English),
+                crate::keys::test::TEST_ENTROPY,
+            )
+            .unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+        assert_eq!(generated_mnemonic.to_string(), "primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary fetch primary foster");
+    }
+
+    #[test]
+    fn test_keys_generate_bip39_random() {
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+
+        let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
+            Mnemonic::generate((WordCount::Words24, Language::English)).unwrap();
+        assert_eq!(generated_mnemonic.valid_networks, any_network());
+    }
+}
diff --git a/crates/wallet/src/keys/mod.rs b/crates/wallet/src/keys/mod.rs
new file mode 100644 (file)
index 0000000..5f6b54c
--- /dev/null
@@ -0,0 +1,1008 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Key formats
+
+use crate::collections::HashSet;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+use core::any::TypeId;
+use core::fmt;
+use core::marker::PhantomData;
+use core::ops::Deref;
+use core::str::FromStr;
+
+use bitcoin::secp256k1::{self, Secp256k1, Signing};
+
+use bitcoin::bip32;
+use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey};
+
+use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
+pub use miniscript::descriptor::{
+    DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey,
+    SortedMultiVec,
+};
+pub use miniscript::ScriptContext;
+use miniscript::{Miniscript, Terminal};
+
+use crate::descriptor::{CheckMiniscript, DescriptorError};
+use crate::wallet::utils::SecpCtx;
+
+#[cfg(feature = "keys-bip39")]
+#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
+pub mod bip39;
+
+/// Set of valid networks for a key
+pub type ValidNetworks = HashSet<Network>;
+
+/// Create a set containing mainnet, testnet, signet, and regtest
+pub fn any_network() -> ValidNetworks {
+    vec![
+        Network::Bitcoin,
+        Network::Testnet,
+        Network::Regtest,
+        Network::Signet,
+    ]
+    .into_iter()
+    .collect()
+}
+/// Create a set only containing mainnet
+pub fn mainnet_network() -> ValidNetworks {
+    vec![Network::Bitcoin].into_iter().collect()
+}
+/// Create a set containing testnet and regtest
+pub fn test_networks() -> ValidNetworks {
+    vec![Network::Testnet, Network::Regtest, Network::Signet]
+        .into_iter()
+        .collect()
+}
+/// Compute the intersection of two sets
+pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks {
+    a.intersection(b).cloned().collect()
+}
+
+/// Container for public or secret keys
+#[derive(Debug)]
+pub enum DescriptorKey<Ctx: ScriptContext> {
+    #[doc(hidden)]
+    Public(DescriptorPublicKey, ValidNetworks, PhantomData<Ctx>),
+    #[doc(hidden)]
+    Secret(DescriptorSecretKey, ValidNetworks, PhantomData<Ctx>),
+}
+
+impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
+    /// Create an instance given a public key and a set of valid networks
+    pub fn from_public(public: DescriptorPublicKey, networks: ValidNetworks) -> Self {
+        DescriptorKey::Public(public, networks, PhantomData)
+    }
+
+    /// Create an instance given a secret key and a set of valid networks
+    pub fn from_secret(secret: DescriptorSecretKey, networks: ValidNetworks) -> Self {
+        DescriptorKey::Secret(secret, networks, PhantomData)
+    }
+
+    /// Override the computed set of valid networks
+    pub fn override_valid_networks(self, networks: ValidNetworks) -> Self {
+        match self {
+            DescriptorKey::Public(key, _, _) => DescriptorKey::Public(key, networks, PhantomData),
+            DescriptorKey::Secret(key, _, _) => DescriptorKey::Secret(key, networks, PhantomData),
+        }
+    }
+
+    // This method is used internally by `bdk_wallet::fragment!` and `bdk_wallet::descriptor!`. It has to be
+    // public because it is effectively called by external crates once the macros are expanded,
+    // but since it is not meant to be part of the public api we hide it from the docs.
+    #[doc(hidden)]
+    pub fn extract(
+        self,
+        secp: &SecpCtx,
+    ) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> {
+        match self {
+            DescriptorKey::Public(public, valid_networks, _) => {
+                Ok((public, KeyMap::default(), valid_networks))
+            }
+            DescriptorKey::Secret(secret, valid_networks, _) => {
+                let mut key_map = KeyMap::new();
+
+                let public = secret
+                    .to_public(secp)
+                    .map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
+                key_map.insert(public.clone(), secret);
+
+                Ok((public, key_map, valid_networks))
+            }
+        }
+    }
+}
+
+/// Enum representation of the known valid [`ScriptContext`]s
+#[derive(Debug, Eq, PartialEq, Copy, Clone)]
+pub enum ScriptContextEnum {
+    /// Legacy scripts
+    Legacy,
+    /// Segwitv0 scripts
+    Segwitv0,
+    /// Taproot scripts
+    Tap,
+}
+
+impl ScriptContextEnum {
+    /// Returns whether the script context is [`ScriptContextEnum::Legacy`]
+    pub fn is_legacy(&self) -> bool {
+        self == &ScriptContextEnum::Legacy
+    }
+
+    /// Returns whether the script context is [`ScriptContextEnum::Segwitv0`]
+    pub fn is_segwit_v0(&self) -> bool {
+        self == &ScriptContextEnum::Segwitv0
+    }
+
+    /// Returns whether the script context is [`ScriptContextEnum::Tap`]
+    pub fn is_taproot(&self) -> bool {
+        self == &ScriptContextEnum::Tap
+    }
+}
+
+/// Trait that adds extra useful methods to [`ScriptContext`]s
+pub trait ExtScriptContext: ScriptContext {
+    /// Returns the [`ScriptContext`] as a [`ScriptContextEnum`]
+    fn as_enum() -> ScriptContextEnum;
+
+    /// Returns whether the script context is [`Legacy`](miniscript::Legacy)
+    fn is_legacy() -> bool {
+        Self::as_enum().is_legacy()
+    }
+
+    /// Returns whether the script context is [`Segwitv0`](miniscript::Segwitv0)
+    fn is_segwit_v0() -> bool {
+        Self::as_enum().is_segwit_v0()
+    }
+
+    /// Returns whether the script context is [`Tap`](miniscript::Tap), aka Taproot or Segwit V1
+    fn is_taproot() -> bool {
+        Self::as_enum().is_taproot()
+    }
+}
+
+impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
+    fn as_enum() -> ScriptContextEnum {
+        match TypeId::of::<Ctx>() {
+            t if t == TypeId::of::<miniscript::Legacy>() => ScriptContextEnum::Legacy,
+            t if t == TypeId::of::<miniscript::Segwitv0>() => ScriptContextEnum::Segwitv0,
+            t if t == TypeId::of::<miniscript::Tap>() => ScriptContextEnum::Tap,
+            _ => unimplemented!("Unknown ScriptContext type"),
+        }
+    }
+}
+
+/// Trait for objects that can be turned into a public or secret [`DescriptorKey`]
+///
+/// The generic type `Ctx` is used to define the context in which the key is valid: some key
+/// formats, like the mnemonics used by Electrum wallets, encode internally whether the wallet is
+/// legacy or segwit. Thus, trying to turn a valid legacy mnemonic into a `DescriptorKey`
+/// that would become part of a segwit descriptor should fail.
+///
+/// For key types that do care about this, the [`ExtScriptContext`] trait provides some useful
+/// methods that can be used to check at runtime which `Ctx` is being used.
+///
+/// For key types that can do this check statically (because they can only work within a
+/// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type
+/// checking.
+///
+/// Keys also have control over the networks they support: constructing the return object with
+/// [`DescriptorKey::from_public`] or [`DescriptorKey::from_secret`] allows to specify a set of
+/// [`ValidNetworks`].
+///
+/// ## Examples
+///
+/// Key type valid in any context:
+///
+/// ```
+/// use bdk_wallet::bitcoin::PublicKey;
+///
+/// use bdk_wallet::keys::{DescriptorKey, IntoDescriptorKey, KeyError, ScriptContext};
+///
+/// pub struct MyKeyType {
+///     pubkey: PublicKey,
+/// }
+///
+/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
+///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+///         self.pubkey.into_descriptor_key()
+///     }
+/// }
+/// ```
+///
+/// Key type that is only valid on mainnet:
+///
+/// ```
+/// use bdk_wallet::bitcoin::PublicKey;
+///
+/// use bdk_wallet::keys::{
+///     mainnet_network, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError,
+///     ScriptContext, SinglePub, SinglePubKey,
+/// };
+///
+/// pub struct MyKeyType {
+///     pubkey: PublicKey,
+/// }
+///
+/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
+///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+///         Ok(DescriptorKey::from_public(
+///             DescriptorPublicKey::Single(SinglePub {
+///                 origin: None,
+///                 key: SinglePubKey::FullKey(self.pubkey),
+///             }),
+///             mainnet_network(),
+///         ))
+///     }
+/// }
+/// ```
+///
+/// Key type that internally encodes in which context it's valid. The context is checked at runtime:
+///
+/// ```
+/// use bdk_wallet::bitcoin::PublicKey;
+///
+/// use bdk_wallet::keys::{
+///     DescriptorKey, ExtScriptContext, IntoDescriptorKey, KeyError, ScriptContext,
+/// };
+///
+/// pub struct MyKeyType {
+///     is_legacy: bool,
+///     pubkey: PublicKey,
+/// }
+///
+/// impl<Ctx: ScriptContext + 'static> IntoDescriptorKey<Ctx> for MyKeyType {
+///     fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+///         if Ctx::is_legacy() == self.is_legacy {
+///             self.pubkey.into_descriptor_key()
+///         } else {
+///             Err(KeyError::InvalidScriptContext)
+///         }
+///     }
+/// }
+/// ```
+///
+/// Key type that can only work within [`miniscript::Segwitv0`] context. Only the specialized version
+/// of the trait is implemented.
+///
+/// This example deliberately fails to compile, to demonstrate how the compiler can catch when keys
+/// are misused. In this case, the "segwit-only" key is used to build a `pkh()` descriptor, which
+/// makes the compiler (correctly) fail.
+///
+/// ```compile_fail
+/// use bdk_wallet::bitcoin::PublicKey;
+/// use core::str::FromStr;
+///
+/// use bdk_wallet::keys::{DescriptorKey, IntoDescriptorKey, KeyError};
+///
+/// pub struct MySegwitOnlyKeyType {
+///     pubkey: PublicKey,
+/// }
+///
+/// impl IntoDescriptorKey<bdk_wallet::miniscript::Segwitv0> for MySegwitOnlyKeyType {
+///     fn into_descriptor_key(self) -> Result<DescriptorKey<bdk_wallet::miniscript::Segwitv0>, KeyError> {
+///         self.pubkey.into_descriptor_key()
+///     }
+/// }
+///
+/// let key = MySegwitOnlyKeyType {
+///     pubkey: PublicKey::from_str("...")?,
+/// };
+/// let (descriptor, _, _) = bdk_wallet::descriptor!(pkh(key))?;
+/// //                                               ^^^^^ changing this to `wpkh` would make it compile
+///
+/// # Ok::<_, Box<dyn std::error::Error>>(())
+/// ```
+pub trait IntoDescriptorKey<Ctx: ScriptContext>: Sized {
+    /// Turn the key into a [`DescriptorKey`] within the requested [`ScriptContext`]
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError>;
+}
+
+/// Enum for extended keys that can be either `xprv` or `xpub`
+///
+/// An instance of [`ExtendedKey`] can be constructed from an [`Xpriv`](bip32::Xpriv)
+/// or an [`Xpub`](bip32::Xpub) by using the `From` trait.
+///
+/// Defaults to the [`Legacy`](miniscript::Legacy) context.
+pub enum ExtendedKey<Ctx: ScriptContext = miniscript::Legacy> {
+    /// A private extended key, aka an `xprv`
+    Private((bip32::Xpriv, PhantomData<Ctx>)),
+    /// A public extended key, aka an `xpub`
+    Public((bip32::Xpub, PhantomData<Ctx>)),
+}
+
+impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
+    /// Return whether or not the key contains the private data
+    pub fn has_secret(&self) -> bool {
+        match self {
+            ExtendedKey::Private(_) => true,
+            ExtendedKey::Public(_) => false,
+        }
+    }
+
+    /// Transform the [`ExtendedKey`] into an [`Xpriv`](bip32::Xpriv) for the
+    /// given [`Network`], if the key contains the private data
+    pub fn into_xprv(self, network: Network) -> Option<bip32::Xpriv> {
+        match self {
+            ExtendedKey::Private((mut xprv, _)) => {
+                xprv.network = network;
+                Some(xprv)
+            }
+            ExtendedKey::Public(_) => None,
+        }
+    }
+
+    /// Transform the [`ExtendedKey`] into an [`Xpub`](bip32::Xpub) for the
+    /// given [`Network`]
+    pub fn into_xpub<C: Signing>(
+        self,
+        network: bitcoin::Network,
+        secp: &Secp256k1<C>,
+    ) -> bip32::Xpub {
+        let mut xpub = match self {
+            ExtendedKey::Private((xprv, _)) => bip32::Xpub::from_priv(secp, &xprv),
+            ExtendedKey::Public((xpub, _)) => xpub,
+        };
+
+        xpub.network = network;
+        xpub
+    }
+}
+
+impl<Ctx: ScriptContext> From<bip32::Xpub> for ExtendedKey<Ctx> {
+    fn from(xpub: bip32::Xpub) -> Self {
+        ExtendedKey::Public((xpub, PhantomData))
+    }
+}
+
+impl<Ctx: ScriptContext> From<bip32::Xpriv> for ExtendedKey<Ctx> {
+    fn from(xprv: bip32::Xpriv) -> Self {
+        ExtendedKey::Private((xprv, PhantomData))
+    }
+}
+
+/// Trait for keys that can be derived.
+///
+/// When extra metadata are provided, a [`DerivableKey`] can be transformed into a
+/// [`DescriptorKey`]: the trait [`IntoDescriptorKey`] is automatically implemented
+/// for `(DerivableKey, DerivationPath)` and
+/// `(DerivableKey, KeySource, DerivationPath)` tuples.
+///
+/// For key types that don't encode any indication about the path to use (like bip39), it's
+/// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same
+/// rules regarding script context and valid networks apply.
+///
+/// ## Examples
+///
+/// Key types that can be directly converted into an [`Xpriv`] or
+/// an [`Xpub`] can implement only the required `into_extended_key()` method.
+///
+/// ```
+/// use bdk_wallet::bitcoin;
+/// use bdk_wallet::bitcoin::bip32;
+/// use bdk_wallet::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext};
+///
+/// struct MyCustomKeyType {
+///     key_data: bitcoin::PrivateKey,
+///     chain_code: [u8; 32],
+///     network: bitcoin::Network,
+/// }
+///
+/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
+///     fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+///         let xprv = bip32::Xpriv {
+///             network: self.network,
+///             depth: 0,
+///             parent_fingerprint: bip32::Fingerprint::default(),
+///             private_key: self.key_data.inner,
+///             chain_code: bip32::ChainCode::from(&self.chain_code),
+///             child_number: bip32::ChildNumber::Normal { index: 0 },
+///         };
+///
+///         xprv.into_extended_key()
+///     }
+/// }
+/// ```
+///
+/// Types that don't internally encode the [`Network`] in which they are valid need some extra
+/// steps to override the set of valid networks, otherwise only the network specified in the
+/// [`Xpriv`] or [`Xpub`] will be considered valid.
+///
+/// ```
+/// use bdk_wallet::bitcoin;
+/// use bdk_wallet::bitcoin::bip32;
+/// use bdk_wallet::keys::{
+///     any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext,
+/// };
+///
+/// struct MyCustomKeyType {
+///     key_data: bitcoin::PrivateKey,
+///     chain_code: [u8; 32],
+/// }
+///
+/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
+///     fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+///         let xprv = bip32::Xpriv {
+///             network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
+///             depth: 0,
+///             parent_fingerprint: bip32::Fingerprint::default(),
+///             private_key: self.key_data.inner,
+///             chain_code: bip32::ChainCode::from(&self.chain_code),
+///             child_number: bip32::ChildNumber::Normal { index: 0 },
+///         };
+///
+///         xprv.into_extended_key()
+///     }
+///
+///     fn into_descriptor_key(
+///         self,
+///         source: Option<bip32::KeySource>,
+///         derivation_path: bip32::DerivationPath,
+///     ) -> Result<DescriptorKey<Ctx>, KeyError> {
+///         let descriptor_key = self
+///             .into_extended_key()?
+///             .into_descriptor_key(source, derivation_path)?;
+///
+///         // Override the set of valid networks here
+///         Ok(descriptor_key.override_valid_networks(any_network()))
+///     }
+/// }
+/// ```
+///
+/// [`DerivationPath`]: (bip32::DerivationPath)
+/// [`Xpriv`]: (bip32::Xpriv)
+/// [`Xpub`]: (bip32::Xpub)
+pub trait DerivableKey<Ctx: ScriptContext = miniscript::Legacy>: Sized {
+    /// Consume `self` and turn it into an [`ExtendedKey`]
+    #[cfg_attr(
+        feature = "keys-bip39",
+        doc = r##"
+This can be used to get direct access to `xprv`s and `xpub`s for types that implement this trait,
+like [`Mnemonic`](bip39::Mnemonic) when the `keys-bip39` feature is enabled.
+```rust
+use bdk_wallet::bitcoin::Network;
+use bdk_wallet::keys::{DerivableKey, ExtendedKey};
+use bdk_wallet::keys::bip39::{Mnemonic, Language};
+
+# fn main() -> Result<(), Box<dyn std::error::Error>> {
+let xkey: ExtendedKey =
+    Mnemonic::parse_in(
+        Language::English,
+        "jelly crash boy whisper mouse ecology tuna soccer memory million news short",
+    )?
+    .into_extended_key()?;
+let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
+# Ok(()) }
+```
+"##
+    )]
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError>;
+
+    /// Consume `self` and turn it into a [`DescriptorKey`] by adding the extra metadata, such as
+    /// key origin and derivation path
+    fn into_descriptor_key(
+        self,
+        origin: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        match self.into_extended_key()? {
+            ExtendedKey::Private((xprv, _)) => DescriptorSecretKey::XPrv(DescriptorXKey {
+                origin,
+                xkey: xprv,
+                derivation_path,
+                wildcard: Wildcard::Unhardened,
+            })
+            .into_descriptor_key(),
+            ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey {
+                origin,
+                xkey: xpub,
+                derivation_path,
+                wildcard: Wildcard::Unhardened,
+            })
+            .into_descriptor_key(),
+        }
+    }
+}
+
+/// Identity conversion
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for ExtendedKey<Ctx> {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        Ok(self)
+    }
+}
+
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpub {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        Ok(self.into())
+    }
+}
+
+impl<Ctx: ScriptContext> DerivableKey<Ctx> for bip32::Xpriv {
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        Ok(self.into())
+    }
+}
+
+/// Output of a [`GeneratableKey`] key generation
+pub struct GeneratedKey<K, Ctx: ScriptContext> {
+    key: K,
+    valid_networks: ValidNetworks,
+    phantom: PhantomData<Ctx>,
+}
+
+impl<K, Ctx: ScriptContext> GeneratedKey<K, Ctx> {
+    fn new(key: K, valid_networks: ValidNetworks) -> Self {
+        GeneratedKey {
+            key,
+            valid_networks,
+            phantom: PhantomData,
+        }
+    }
+
+    /// Consumes `self` and returns the key
+    pub fn into_key(self) -> K {
+        self.key
+    }
+}
+
+impl<K, Ctx: ScriptContext> Deref for GeneratedKey<K, Ctx> {
+    type Target = K;
+
+    fn deref(&self) -> &Self::Target {
+        &self.key
+    }
+}
+
+impl<K: Clone, Ctx: ScriptContext> Clone for GeneratedKey<K, Ctx> {
+    fn clone(&self) -> GeneratedKey<K, Ctx> {
+        GeneratedKey {
+            key: self.key.clone(),
+            valid_networks: self.valid_networks.clone(),
+            phantom: self.phantom,
+        }
+    }
+}
+
+// Make generated "derivable" keys themselves "derivable". Also make sure they are assigned the
+// right `valid_networks`.
+impl<Ctx, K> DerivableKey<Ctx> for GeneratedKey<K, Ctx>
+where
+    Ctx: ScriptContext,
+    K: DerivableKey<Ctx>,
+{
+    fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
+        self.key.into_extended_key()
+    }
+
+    fn into_descriptor_key(
+        self,
+        origin: Option<bip32::KeySource>,
+        derivation_path: bip32::DerivationPath,
+    ) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let descriptor_key = self.key.into_descriptor_key(origin, derivation_path)?;
+        Ok(descriptor_key.override_valid_networks(self.valid_networks))
+    }
+}
+
+// Make generated keys directly usable in descriptors, and make sure they get assigned the right
+// `valid_networks`.
+impl<Ctx, K> IntoDescriptorKey<Ctx> for GeneratedKey<K, Ctx>
+where
+    Ctx: ScriptContext,
+    K: IntoDescriptorKey<Ctx>,
+{
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let desc_key = self.key.into_descriptor_key()?;
+        Ok(desc_key.override_valid_networks(self.valid_networks))
+    }
+}
+
+/// Trait for keys that can be generated
+///
+/// The same rules about [`ScriptContext`] and [`ValidNetworks`] from [`IntoDescriptorKey`] apply.
+///
+/// This trait is particularly useful when combined with [`DerivableKey`]: if `Self`
+/// implements it, the returned [`GeneratedKey`] will also implement it. The same is true for
+/// [`IntoDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
+/// [`IntoDescriptorKey`].
+pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
+    /// Type specifying the amount of entropy required e.g. `[u8;32]`
+    type Entropy: AsMut<[u8]> + Default;
+
+    /// Extra options required by the `generate_with_entropy`
+    type Options;
+    /// Returned error in case of failure
+    type Error: core::fmt::Debug;
+
+    /// Generate a key given the extra options and the entropy
+    fn generate_with_entropy(
+        options: Self::Options,
+        entropy: Self::Entropy,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error>;
+
+    /// Generate a key given the options with a random entropy
+    fn generate(options: Self::Options) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        use rand::{thread_rng, Rng};
+
+        let mut entropy = Self::Entropy::default();
+        thread_rng().fill(entropy.as_mut());
+        Self::generate_with_entropy(options, entropy)
+    }
+}
+
+/// Trait that allows generating a key with the default options
+///
+/// This trait is automatically implemented if the [`GeneratableKey::Options`] implements [`Default`].
+pub trait GeneratableDefaultOptions<Ctx>: GeneratableKey<Ctx>
+where
+    Ctx: ScriptContext,
+    <Self as GeneratableKey<Ctx>>::Options: Default,
+{
+    /// Generate a key with the default options and a given entropy
+    fn generate_with_entropy_default(
+        entropy: Self::Entropy,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        Self::generate_with_entropy(Default::default(), entropy)
+    }
+
+    /// Generate a key with the default options and a random entropy
+    fn generate_default() -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        Self::generate(Default::default())
+    }
+}
+
+/// Automatic implementation of [`GeneratableDefaultOptions`] for [`GeneratableKey`]s where
+/// `Options` implements `Default`
+impl<Ctx, K> GeneratableDefaultOptions<Ctx> for K
+where
+    Ctx: ScriptContext,
+    K: GeneratableKey<Ctx>,
+    <K as GeneratableKey<Ctx>>::Options: Default,
+{
+}
+
+impl<Ctx: ScriptContext> GeneratableKey<Ctx> for bip32::Xpriv {
+    type Entropy = [u8; 32];
+
+    type Options = ();
+    type Error = bip32::Error;
+
+    fn generate_with_entropy(
+        _: Self::Options,
+        entropy: Self::Entropy,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        // pick a arbitrary network here, but say that we support all of them
+        let xprv = bip32::Xpriv::new_master(Network::Bitcoin, entropy.as_ref())?;
+        Ok(GeneratedKey::new(xprv, any_network()))
+    }
+}
+
+/// Options for generating a [`PrivateKey`]
+///
+/// Defaults to creating compressed keys, which save on-chain bytes and fees
+#[derive(Debug, Copy, Clone)]
+pub struct PrivateKeyGenerateOptions {
+    /// Whether the generated key should be "compressed" or not
+    pub compressed: bool,
+}
+
+impl Default for PrivateKeyGenerateOptions {
+    fn default() -> Self {
+        PrivateKeyGenerateOptions { compressed: true }
+    }
+}
+
+impl<Ctx: ScriptContext> GeneratableKey<Ctx> for PrivateKey {
+    type Entropy = [u8; secp256k1::constants::SECRET_KEY_SIZE];
+
+    type Options = PrivateKeyGenerateOptions;
+    type Error = bip32::Error;
+
+    fn generate_with_entropy(
+        options: Self::Options,
+        entropy: Self::Entropy,
+    ) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
+        // pick a arbitrary network here, but say that we support all of them
+        let inner = secp256k1::SecretKey::from_slice(&entropy)?;
+        let private_key = PrivateKey {
+            compressed: options.compressed,
+            network: Network::Bitcoin,
+            inner,
+        };
+
+        Ok(GeneratedKey::new(private_key, any_network()))
+    }
+}
+
+impl<Ctx: ScriptContext, T: DerivableKey<Ctx>> IntoDescriptorKey<Ctx>
+    for (T, bip32::DerivationPath)
+{
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        self.0.into_descriptor_key(None, self.1)
+    }
+}
+
+impl<Ctx: ScriptContext, T: DerivableKey<Ctx>> IntoDescriptorKey<Ctx>
+    for (T, bip32::KeySource, bip32::DerivationPath)
+{
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        self.0.into_descriptor_key(Some(self.1), self.2)
+    }
+}
+
+fn expand_multi_keys<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
+    pks: Vec<Pk>,
+    secp: &SecpCtx,
+) -> Result<(Vec<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError> {
+    let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks
+        .into_iter()
+        .map(|key| key.into_descriptor_key()?.extract(secp))
+        .collect::<Result<Vec<_>, _>>()?
+        .into_iter()
+        .map(|(a, b, c)| (a, (b, c)))
+        .unzip();
+
+    let (key_map, valid_networks) = key_maps_networks.into_iter().fold(
+        (KeyMap::default(), any_network()),
+        |(mut keys_acc, net_acc), (key, net)| {
+            keys_acc.extend(key);
+            let net_acc = merge_networks(&net_acc, &net);
+
+            (keys_acc, net_acc)
+        },
+    );
+
+    Ok((pks, key_map, valid_networks))
+}
+
+// Used internally by `bdk_wallet::fragment!` to build `pk_k()` fragments
+#[doc(hidden)]
+pub fn make_pk<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
+    descriptor_key: Pk,
+    secp: &SecpCtx,
+) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
+    let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?;
+    let minisc = Miniscript::from_ast(Terminal::PkK(key))?;
+
+    minisc.check_miniscript()?;
+
+    Ok((minisc, key_map, valid_networks))
+}
+
+// Used internally by `bdk_wallet::fragment!` to build `pk_h()` fragments
+#[doc(hidden)]
+pub fn make_pkh<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
+    descriptor_key: Pk,
+    secp: &SecpCtx,
+) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
+    let (key, key_map, valid_networks) = descriptor_key.into_descriptor_key()?.extract(secp)?;
+    let minisc = Miniscript::from_ast(Terminal::PkH(key))?;
+
+    minisc.check_miniscript()?;
+
+    Ok((minisc, key_map, valid_networks))
+}
+
+// Used internally by `bdk_wallet::fragment!` to build `multi()` fragments
+#[doc(hidden)]
+pub fn make_multi<
+    Pk: IntoDescriptorKey<Ctx>,
+    Ctx: ScriptContext,
+    V: Fn(usize, Vec<DescriptorPublicKey>) -> Terminal<DescriptorPublicKey, Ctx>,
+>(
+    thresh: usize,
+    variant: V,
+    pks: Vec<Pk>,
+    secp: &SecpCtx,
+) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
+    let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
+    let minisc = Miniscript::from_ast(variant(thresh, pks))?;
+
+    minisc.check_miniscript()?;
+
+    Ok((minisc, key_map, valid_networks))
+}
+
+// Used internally by `bdk_wallet::descriptor!` to build `sortedmulti()` fragments
+#[doc(hidden)]
+pub fn make_sortedmulti<Pk, Ctx, F>(
+    thresh: usize,
+    pks: Vec<Pk>,
+    build_desc: F,
+    secp: &SecpCtx,
+) -> Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>
+where
+    Pk: IntoDescriptorKey<Ctx>,
+    Ctx: ScriptContext,
+    F: Fn(
+        usize,
+        Vec<DescriptorPublicKey>,
+    ) -> Result<(Descriptor<DescriptorPublicKey>, PhantomData<Ctx>), DescriptorError>,
+{
+    let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
+    let descriptor = build_desc(thresh, pks)?.0;
+
+    Ok((descriptor, key_map, valid_networks))
+}
+
+/// The "identity" conversion is used internally by some `bdk_wallet::fragment`s
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorKey<Ctx> {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        Ok(self)
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let networks = match self {
+            DescriptorPublicKey::Single(_) => any_network(),
+            DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
+                if xkey.network == Network::Bitcoin =>
+            {
+                mainnet_network()
+            }
+            _ => test_networks(),
+        };
+
+        Ok(DescriptorKey::from_public(self, networks))
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorPublicKey::Single(SinglePub {
+            key: SinglePubKey::FullKey(self),
+            origin: None,
+        })
+        .into_descriptor_key()
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorPublicKey::Single(SinglePub {
+            key: SinglePubKey::XOnly(self),
+            origin: None,
+        })
+        .into_descriptor_key()
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorSecretKey {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let networks = match &self {
+            DescriptorSecretKey::Single(sk) if sk.key.network == Network::Bitcoin => {
+                mainnet_network()
+            }
+            DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
+                if xkey.network == Network::Bitcoin =>
+            {
+                mainnet_network()
+            }
+            _ => test_networks(),
+        };
+
+        Ok(DescriptorKey::from_secret(self, networks))
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for &'_ str {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorSecretKey::from_str(self)
+            .map_err(|e| KeyError::Message(e.to_string()))?
+            .into_descriptor_key()
+    }
+}
+
+impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PrivateKey {
+    fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorSecretKey::Single(SinglePriv {
+            key: self,
+            origin: None,
+        })
+        .into_descriptor_key()
+    }
+}
+
+/// Errors thrown while working with [`keys`](crate::keys)
+#[derive(Debug)]
+pub enum KeyError {
+    /// The key cannot exist in the given script context
+    InvalidScriptContext,
+    /// The key is not valid for the given network
+    InvalidNetwork,
+    /// The key has an invalid checksum
+    InvalidChecksum,
+
+    /// Custom error message
+    Message(String),
+
+    /// BIP32 error
+    Bip32(bitcoin::bip32::Error),
+    /// Miniscript error
+    Miniscript(miniscript::Error),
+}
+
+impl From<miniscript::Error> for KeyError {
+    fn from(err: miniscript::Error) -> Self {
+        KeyError::Miniscript(err)
+    }
+}
+
+impl From<bip32::Error> for KeyError {
+    fn from(err: bip32::Error) -> Self {
+        KeyError::Bip32(err)
+    }
+}
+
+impl fmt::Display for KeyError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::InvalidScriptContext => write!(f, "Invalid script context"),
+            Self::InvalidNetwork => write!(f, "Invalid network"),
+            Self::InvalidChecksum => write!(f, "Invalid checksum"),
+            Self::Message(err) => write!(f, "{}", err),
+            Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
+            Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for KeyError {}
+
+#[cfg(test)]
+pub mod test {
+    use bitcoin::bip32;
+
+    use super::*;
+
+    pub const TEST_ENTROPY: [u8; 32] = [0xAA; 32];
+
+    #[test]
+    fn test_keys_generate_xprv() {
+        let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
+            bip32::Xpriv::generate_with_entropy_default(TEST_ENTROPY).unwrap();
+
+        assert_eq!(generated_xprv.valid_networks, any_network());
+        assert_eq!(generated_xprv.to_string(), "xprv9s21ZrQH143K4Xr1cJyqTvuL2FWR8eicgY9boWqMBv8MDVUZ65AXHnzBrK1nyomu6wdcabRgmGTaAKawvhAno1V5FowGpTLVx3jxzE5uk3Q");
+    }
+
+    #[test]
+    fn test_keys_generate_wif() {
+        let generated_wif: GeneratedKey<_, miniscript::Segwitv0> =
+            bitcoin::PrivateKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
+
+        assert_eq!(generated_wif.valid_networks, any_network());
+        assert_eq!(
+            generated_wif.to_string(),
+            "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"
+        );
+    }
+
+    #[cfg(feature = "keys-bip39")]
+    #[test]
+    fn test_keys_wif_network_bip39() {
+        let xkey: ExtendedKey = bip39::Mnemonic::parse_in(
+            bip39::Language::English,
+            "jelly crash boy whisper mouse ecology tuna soccer memory million news short",
+        )
+        .unwrap()
+        .into_extended_key()
+        .unwrap();
+        let xprv = xkey.into_xprv(Network::Testnet).unwrap();
+
+        assert_eq!(xprv.network, Network::Testnet);
+    }
+}
diff --git a/crates/wallet/src/lib.rs b/crates/wallet/src/lib.rs
new file mode 100644 (file)
index 0000000..f7c6f35
--- /dev/null
@@ -0,0 +1,47 @@
+#![doc = include_str!("../README.md")]
+// only enables the `doc_cfg` feature when the `docsrs` configuration attribute is defined
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(
+    docsrs,
+    doc(html_logo_url = "https://github.com/bitcoindevkit/bdk/raw/master/static/bdk.png")
+)]
+#![no_std]
+#![warn(missing_docs)]
+
+#[cfg(feature = "std")]
+#[macro_use]
+extern crate std;
+
+#[doc(hidden)]
+#[macro_use]
+pub extern crate alloc;
+
+pub extern crate bitcoin;
+pub extern crate miniscript;
+extern crate serde;
+extern crate serde_json;
+
+#[cfg(feature = "keys-bip39")]
+extern crate bip39;
+
+pub mod descriptor;
+pub mod keys;
+pub mod psbt;
+pub(crate) mod types;
+pub mod wallet;
+
+pub use descriptor::template;
+pub use descriptor::HdKeyPaths;
+pub use types::*;
+pub use wallet::signer;
+pub use wallet::signer::SignOptions;
+pub use wallet::tx_builder::TxBuilder;
+pub use wallet::Wallet;
+
+/// Get the version of BDK at runtime
+pub fn version() -> &'static str {
+    env!("CARGO_PKG_VERSION", "unknown")
+}
+
+pub use bdk_chain as chain;
+pub(crate) use bdk_chain::collections;
diff --git a/crates/wallet/src/psbt/mod.rs b/crates/wallet/src/psbt/mod.rs
new file mode 100644 (file)
index 0000000..7a66989
--- /dev/null
@@ -0,0 +1,75 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Additional functions on the `rust-bitcoin` `Psbt` structure.
+
+use alloc::vec::Vec;
+use bitcoin::Amount;
+use bitcoin::FeeRate;
+use bitcoin::Psbt;
+use bitcoin::TxOut;
+
+// TODO upstream the functions here to `rust-bitcoin`?
+
+/// Trait to add functions to extract utxos and calculate fees.
+pub trait PsbtUtils {
+    /// Get the `TxOut` for the specified input index, if it doesn't exist in the PSBT `None` is returned.
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
+
+    /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats.
+    /// If the PSBT is missing a TxOut for an input returns None.
+    fn fee_amount(&self) -> Option<u64>;
+
+    /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
+    /// `Psbt` is finalized and all witness/signature data is added to the
+    /// transaction.
+    /// If the PSBT is missing a TxOut for an input returns None.
+    fn fee_rate(&self) -> Option<FeeRate>;
+}
+
+impl PsbtUtils for Psbt {
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
+        let tx = &self.unsigned_tx;
+        let input = self.inputs.get(input_index)?;
+
+        match (&input.witness_utxo, &input.non_witness_utxo) {
+            (Some(_), _) => input.witness_utxo.clone(),
+            (_, Some(_)) => input.non_witness_utxo.as_ref().map(|in_tx| {
+                in_tx.output[tx.input[input_index].previous_output.vout as usize].clone()
+            }),
+            _ => None,
+        }
+    }
+
+    fn fee_amount(&self) -> Option<u64> {
+        let tx = &self.unsigned_tx;
+        let utxos: Option<Vec<TxOut>> = (0..tx.input.len()).map(|i| self.get_utxo_for(i)).collect();
+
+        utxos.map(|inputs| {
+            let input_amount: u64 = inputs.iter().map(|i| i.value.to_sat()).sum();
+            let output_amount: u64 = self
+                .unsigned_tx
+                .output
+                .iter()
+                .map(|o| o.value.to_sat())
+                .sum();
+            input_amount
+                .checked_sub(output_amount)
+                .expect("input amount must be greater than output amount")
+        })
+    }
+
+    fn fee_rate(&self) -> Option<FeeRate> {
+        let fee_amount = self.fee_amount();
+        let weight = self.clone().extract_tx().ok()?.weight();
+        fee_amount.map(|fee| Amount::from_sat(fee) / weight)
+    }
+}
diff --git a/crates/wallet/src/types.rs b/crates/wallet/src/types.rs
new file mode 100644 (file)
index 0000000..4ce961b
--- /dev/null
@@ -0,0 +1,135 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use alloc::boxed::Box;
+use core::convert::AsRef;
+
+use bdk_chain::ConfirmationTime;
+use bitcoin::blockdata::transaction::{OutPoint, Sequence, TxOut};
+use bitcoin::psbt;
+
+use serde::{Deserialize, Serialize};
+
+/// Types of keychains
+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
+pub enum KeychainKind {
+    /// External keychain, used for deriving recipient addresses.
+    External = 0,
+    /// Internal keychain, used for deriving change addresses.
+    Internal = 1,
+}
+
+impl KeychainKind {
+    /// Return [`KeychainKind`] as a byte
+    pub fn as_byte(&self) -> u8 {
+        match self {
+            KeychainKind::External => b'e',
+            KeychainKind::Internal => b'i',
+        }
+    }
+}
+
+impl AsRef<[u8]> for KeychainKind {
+    fn as_ref(&self) -> &[u8] {
+        match self {
+            KeychainKind::External => b"e",
+            KeychainKind::Internal => b"i",
+        }
+    }
+}
+
+/// An unspent output owned by a [`Wallet`].
+///
+/// [`Wallet`]: crate::Wallet
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
+pub struct LocalOutput {
+    /// Reference to a transaction output
+    pub outpoint: OutPoint,
+    /// Transaction output
+    pub txout: TxOut,
+    /// Type of keychain
+    pub keychain: KeychainKind,
+    /// Whether this UTXO is spent or not
+    pub is_spent: bool,
+    /// The derivation index for the script pubkey in the wallet
+    pub derivation_index: u32,
+    /// The confirmation time for transaction containing this utxo
+    pub confirmation_time: ConfirmationTime,
+}
+
+/// A [`Utxo`] with its `satisfaction_weight`.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WeightedUtxo {
+    /// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
+    /// properly maintain the feerate when adding this input to a transaction during coin selection.
+    ///
+    /// [weight units]: https://en.bitcoin.it/wiki/Weight_units
+    pub satisfaction_weight: usize,
+    /// The UTXO
+    pub utxo: Utxo,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+/// An unspent transaction output (UTXO).
+pub enum Utxo {
+    /// A UTXO owned by the local wallet.
+    Local(LocalOutput),
+    /// A UTXO owned by another wallet.
+    Foreign {
+        /// The location of the output.
+        outpoint: OutPoint,
+        /// The nSequence value to set for this input.
+        sequence: Option<Sequence>,
+        /// The information about the input we require to add it to a PSBT.
+        // Box it to stop the type being too big.
+        psbt_input: Box<psbt::Input>,
+    },
+}
+
+impl Utxo {
+    /// Get the location of the UTXO
+    pub fn outpoint(&self) -> OutPoint {
+        match &self {
+            Utxo::Local(local) => local.outpoint,
+            Utxo::Foreign { outpoint, .. } => *outpoint,
+        }
+    }
+
+    /// Get the `TxOut` of the UTXO
+    pub fn txout(&self) -> &TxOut {
+        match &self {
+            Utxo::Local(local) => &local.txout,
+            Utxo::Foreign {
+                outpoint,
+                psbt_input,
+                ..
+            } => {
+                if let Some(prev_tx) = &psbt_input.non_witness_utxo {
+                    return &prev_tx.output[outpoint.vout as usize];
+                }
+
+                if let Some(txout) = &psbt_input.witness_utxo {
+                    return txout;
+                }
+
+                unreachable!("Foreign UTXOs will always have one of these set")
+            }
+        }
+    }
+
+    /// Get the sequence number if an explicit sequence number has to be set for this input.
+    pub fn sequence(&self) -> Option<Sequence> {
+        match self {
+            Utxo::Local(_) => None,
+            Utxo::Foreign { sequence, .. } => *sequence,
+        }
+    }
+}
diff --git a/crates/wallet/src/wallet/coin_selection.rs b/crates/wallet/src/wallet/coin_selection.rs
new file mode 100644 (file)
index 0000000..5df6479
--- /dev/null
@@ -0,0 +1,1602 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Coin selection
+//!
+//! This module provides the trait [`CoinSelectionAlgorithm`] that can be implemented to
+//! define custom coin selection algorithms.
+//!
+//! You can specify a custom coin selection algorithm through the [`coin_selection`] method on
+//! [`TxBuilder`]. [`DefaultCoinSelectionAlgorithm`] aliases the coin selection algorithm that will
+//! be used if it is not explicitly set.
+//!
+//! [`TxBuilder`]: super::tx_builder::TxBuilder
+//! [`coin_selection`]: super::tx_builder::TxBuilder::coin_selection
+//!
+//! ## Example
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
+//! # use bdk_wallet::wallet::error::CreateTxError;
+//! # use bdk_persist::PersistBackend;
+//! # use bdk_wallet::*;
+//! # use bdk_wallet::wallet::coin_selection::decide_change;
+//! # use anyhow::Error;
+//! #[derive(Debug)]
+//! struct AlwaysSpendEverything;
+//!
+//! impl CoinSelectionAlgorithm for AlwaysSpendEverything {
+//!     fn coin_select(
+//!         &self,
+//!         required_utxos: Vec<WeightedUtxo>,
+//!         optional_utxos: Vec<WeightedUtxo>,
+//!         fee_rate: FeeRate,
+//!         target_amount: u64,
+//!         drain_script: &Script,
+//!     ) -> Result<CoinSelectionResult, coin_selection::Error> {
+//!         let mut selected_amount = 0;
+//!         let mut additional_weight = Weight::ZERO;
+//!         let all_utxos_selected = required_utxos
+//!             .into_iter()
+//!             .chain(optional_utxos)
+//!             .scan(
+//!                 (&mut selected_amount, &mut additional_weight),
+//!                 |(selected_amount, additional_weight), weighted_utxo| {
+//!                     **selected_amount += weighted_utxo.utxo.txout().value.to_sat();
+//!                     **additional_weight += Weight::from_wu(
+//!                         (TxIn::default().segwit_weight().to_wu()
+//!                             + weighted_utxo.satisfaction_weight as u64)
+//!                             as u64,
+//!                     );
+//!                     Some(weighted_utxo.utxo)
+//!                 },
+//!             )
+//!             .collect::<Vec<_>>();
+//!         let additional_fees = (fee_rate * additional_weight).to_sat();
+//!         let amount_needed_with_fees = additional_fees + target_amount;
+//!         if selected_amount < amount_needed_with_fees {
+//!             return Err(coin_selection::Error::InsufficientFunds {
+//!                 needed: amount_needed_with_fees,
+//!                 available: selected_amount,
+//!             });
+//!         }
+//!
+//!         let remaining_amount = selected_amount - amount_needed_with_fees;
+//!
+//!         let excess = decide_change(remaining_amount, fee_rate, drain_script);
+//!
+//!         Ok(CoinSelectionResult {
+//!             selected: all_utxos_selected,
+//!             fee_amount: additional_fees,
+//!             excess,
+//!         })
+//!     }
+//! }
+//!
+//! # let mut wallet = doctest_wallet!();
+//! // create wallet, sync, ...
+//!
+//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
+//!     .unwrap()
+//!     .require_network(Network::Testnet)
+//!     .unwrap();
+//! let psbt = {
+//!     let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
+//!     builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
+//!     builder.finish()?
+//! };
+//!
+//! // inspect, sign, broadcast, ...
+//!
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use crate::chain::collections::HashSet;
+use crate::wallet::utils::IsDust;
+use crate::Utxo;
+use crate::WeightedUtxo;
+use bitcoin::FeeRate;
+
+use alloc::vec::Vec;
+use bitcoin::consensus::encode::serialize;
+use bitcoin::OutPoint;
+use bitcoin::TxIn;
+use bitcoin::{Script, Weight};
+
+use core::convert::TryInto;
+use core::fmt::{self, Formatter};
+use rand::seq::SliceRandom;
+
+/// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
+/// overridden
+pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
+
+/// Errors that can be thrown by the [`coin_selection`](crate::wallet::coin_selection) module
+#[derive(Debug)]
+pub enum Error {
+    /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
+    InsufficientFunds {
+        /// Sats needed for some transaction
+        needed: u64,
+        /// Sats available for spending
+        available: u64,
+    },
+    /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for
+    /// the desired outputs plus fee, if there is not such combination this error is thrown
+    BnBNoExactMatch,
+    /// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow
+    /// exponentially, thus a limit is set, and when hit, this error is thrown
+    BnBTotalTriesExceeded,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::InsufficientFunds { needed, available } => write!(
+                f,
+                "Insufficient funds: {} sat available of {} sat needed",
+                available, needed
+            ),
+            Self::BnBTotalTriesExceeded => {
+                write!(f, "Branch and bound coin selection: total tries exceeded")
+            }
+            Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+#[derive(Debug)]
+/// Remaining amount after performing coin selection
+pub enum Excess {
+    /// It's not possible to create spendable output from excess using the current drain output
+    NoChange {
+        /// Threshold to consider amount as dust for this particular change script_pubkey
+        dust_threshold: u64,
+        /// Exceeding amount of current selection over outgoing value and fee costs
+        remaining_amount: u64,
+        /// The calculated fee for the drain TxOut with the selected script_pubkey
+        change_fee: u64,
+    },
+    /// It's possible to create spendable output from excess using the current drain output
+    Change {
+        /// Effective amount available to create change after deducting the change output fee
+        amount: u64,
+        /// The deducted change output fee
+        fee: u64,
+    },
+}
+
+/// Result of a successful coin selection
+#[derive(Debug)]
+pub struct CoinSelectionResult {
+    /// List of outputs selected for use as inputs
+    pub selected: Vec<Utxo>,
+    /// Total fee amount for the selected utxos in satoshis
+    pub fee_amount: u64,
+    /// Remaining amount after deducing fees and outgoing outputs
+    pub excess: Excess,
+}
+
+impl CoinSelectionResult {
+    /// The total value of the inputs selected.
+    pub fn selected_amount(&self) -> u64 {
+        self.selected.iter().map(|u| u.txout().value.to_sat()).sum()
+    }
+
+    /// The total value of the inputs selected from the local wallet.
+    pub fn local_selected_amount(&self) -> u64 {
+        self.selected
+            .iter()
+            .filter_map(|u| match u {
+                Utxo::Local(_) => Some(u.txout().value.to_sat()),
+                _ => None,
+            })
+            .sum()
+    }
+}
+
+/// Trait for generalized coin selection algorithms
+///
+/// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
+/// selection algorithm when it creates transactions.
+///
+/// For an example see [this module](crate::wallet::coin_selection)'s documentation.
+pub trait CoinSelectionAlgorithm: core::fmt::Debug {
+    /// Perform the coin selection
+    ///
+    /// - `database`: a reference to the wallet's database that can be used to lookup additional
+    ///               details for a specific UTXO
+    /// - `required_utxos`: the utxos that must be spent regardless of `target_amount` with their
+    ///                     weight cost
+    /// - `optional_utxos`: the remaining available utxos to satisfy `target_amount` with their
+    ///                     weight cost
+    /// - `fee_rate`: fee rate to use
+    /// - `target_amount`: the outgoing amount in satoshis and the fees already
+    ///                    accumulated from added outputs and transaction’s header.
+    /// - `drain_script`: the script to use in case of change
+    #[allow(clippy::too_many_arguments)]
+    fn coin_select(
+        &self,
+        required_utxos: Vec<WeightedUtxo>,
+        optional_utxos: Vec<WeightedUtxo>,
+        fee_rate: FeeRate,
+        target_amount: u64,
+        drain_script: &Script,
+    ) -> Result<CoinSelectionResult, Error>;
+}
+
+/// Simple and dumb coin selection
+///
+/// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
+/// from the largest ones until the required amount is reached.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct LargestFirstCoinSelection;
+
+impl CoinSelectionAlgorithm for LargestFirstCoinSelection {
+    fn coin_select(
+        &self,
+        required_utxos: Vec<WeightedUtxo>,
+        mut optional_utxos: Vec<WeightedUtxo>,
+        fee_rate: FeeRate,
+        target_amount: u64,
+        drain_script: &Script,
+    ) -> Result<CoinSelectionResult, Error> {
+        // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
+        // initially smallest to largest, before being reversed with `.rev()`.
+        let utxos = {
+            optional_utxos.sort_unstable_by_key(|wu| wu.utxo.txout().value);
+            required_utxos
+                .into_iter()
+                .map(|utxo| (true, utxo))
+                .chain(optional_utxos.into_iter().rev().map(|utxo| (false, utxo)))
+        };
+
+        select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
+    }
+}
+
+/// OldestFirstCoinSelection always picks the utxo with the smallest blockheight to add to the selected coins next
+///
+/// This coin selection algorithm sorts the available UTXOs by blockheight and then picks them starting
+/// from the oldest ones until the required amount is reached.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct OldestFirstCoinSelection;
+
+impl CoinSelectionAlgorithm for OldestFirstCoinSelection {
+    fn coin_select(
+        &self,
+        required_utxos: Vec<WeightedUtxo>,
+        mut optional_utxos: Vec<WeightedUtxo>,
+        fee_rate: FeeRate,
+        target_amount: u64,
+        drain_script: &Script,
+    ) -> Result<CoinSelectionResult, Error> {
+        // We put the "required UTXOs" first and make sure the optional UTXOs are sorted from
+        // oldest to newest according to blocktime
+        // For utxo that doesn't exist in DB, they will have lowest priority to be selected
+        let utxos = {
+            optional_utxos.sort_unstable_by_key(|wu| match &wu.utxo {
+                Utxo::Local(local) => Some(local.confirmation_time),
+                Utxo::Foreign { .. } => None,
+            });
+
+            required_utxos
+                .into_iter()
+                .map(|utxo| (true, utxo))
+                .chain(optional_utxos.into_iter().map(|utxo| (false, utxo)))
+        };
+
+        select_sorted_utxos(utxos, fee_rate, target_amount, drain_script)
+    }
+}
+
+/// Decide if change can be created
+///
+/// - `remaining_amount`: the amount in which the selected coins exceed the target amount
+/// - `fee_rate`: required fee rate for the current selection
+/// - `drain_script`: script to consider change creation
+pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Script) -> Excess {
+    // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value)
+    let drain_output_len = serialize(drain_script).len() + 8usize;
+    let change_fee =
+        (fee_rate * Weight::from_vb(drain_output_len as u64).expect("overflow occurred")).to_sat();
+    let drain_val = remaining_amount.saturating_sub(change_fee);
+
+    if drain_val.is_dust(drain_script) {
+        let dust_threshold = drain_script.dust_value().to_sat();
+        Excess::NoChange {
+            dust_threshold,
+            change_fee,
+            remaining_amount,
+        }
+    } else {
+        Excess::Change {
+            amount: drain_val,
+            fee: change_fee,
+        }
+    }
+}
+
+fn select_sorted_utxos(
+    utxos: impl Iterator<Item = (bool, WeightedUtxo)>,
+    fee_rate: FeeRate,
+    target_amount: u64,
+    drain_script: &Script,
+) -> Result<CoinSelectionResult, Error> {
+    let mut selected_amount = 0;
+    let mut fee_amount = 0;
+    let selected = utxos
+        .scan(
+            (&mut selected_amount, &mut fee_amount),
+            |(selected_amount, fee_amount), (must_use, weighted_utxo)| {
+                if must_use || **selected_amount < target_amount + **fee_amount {
+                    **fee_amount += (fee_rate
+                        * Weight::from_wu(
+                            TxIn::default().segwit_weight().to_wu()
+                                + weighted_utxo.satisfaction_weight as u64,
+                        ))
+                    .to_sat();
+                    **selected_amount += weighted_utxo.utxo.txout().value.to_sat();
+                    Some(weighted_utxo.utxo)
+                } else {
+                    None
+                }
+            },
+        )
+        .collect::<Vec<_>>();
+
+    let amount_needed_with_fees = target_amount + fee_amount;
+    if selected_amount < amount_needed_with_fees {
+        return Err(Error::InsufficientFunds {
+            needed: amount_needed_with_fees,
+            available: selected_amount,
+        });
+    }
+
+    let remaining_amount = selected_amount - amount_needed_with_fees;
+
+    let excess = decide_change(remaining_amount, fee_rate, drain_script);
+
+    Ok(CoinSelectionResult {
+        selected,
+        fee_amount,
+        excess,
+    })
+}
+
+#[derive(Debug, Clone)]
+// Adds fee information to an UTXO.
+struct OutputGroup {
+    weighted_utxo: WeightedUtxo,
+    // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
+    fee: u64,
+    // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
+    effective_value: i64,
+}
+
+impl OutputGroup {
+    fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
+        let fee = (fee_rate
+            * Weight::from_wu(
+                TxIn::default().segwit_weight().to_wu() + weighted_utxo.satisfaction_weight as u64,
+            ))
+        .to_sat();
+        let effective_value = weighted_utxo.utxo.txout().value.to_sat() as i64 - fee as i64;
+        OutputGroup {
+            weighted_utxo,
+            fee,
+            effective_value,
+        }
+    }
+}
+
+/// Branch and bound coin selection
+///
+/// Code adapted from Bitcoin Core's implementation and from Mark Erhardt Master's Thesis: <http://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf>
+#[derive(Debug, Clone)]
+pub struct BranchAndBoundCoinSelection {
+    size_of_change: u64,
+}
+
+impl Default for BranchAndBoundCoinSelection {
+    fn default() -> Self {
+        Self {
+            // P2WPKH cost of change -> value (8 bytes) + script len (1 bytes) + script (22 bytes)
+            size_of_change: 8 + 1 + 22,
+        }
+    }
+}
+
+impl BranchAndBoundCoinSelection {
+    /// Create new instance with target size for change output
+    pub fn new(size_of_change: u64) -> Self {
+        Self { size_of_change }
+    }
+}
+
+const BNB_TOTAL_TRIES: usize = 100_000;
+
+impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection {
+    fn coin_select(
+        &self,
+        required_utxos: Vec<WeightedUtxo>,
+        optional_utxos: Vec<WeightedUtxo>,
+        fee_rate: FeeRate,
+        target_amount: u64,
+        drain_script: &Script,
+    ) -> Result<CoinSelectionResult, Error> {
+        // Mapping every (UTXO, usize) to an output group
+        let required_utxos: Vec<OutputGroup> = required_utxos
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .collect();
+
+        // Mapping every (UTXO, usize) to an output group, filtering UTXOs with a negative
+        // effective value
+        let optional_utxos: Vec<OutputGroup> = optional_utxos
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .filter(|u| u.effective_value.is_positive())
+            .collect();
+
+        let curr_value = required_utxos
+            .iter()
+            .fold(0, |acc, x| acc + x.effective_value);
+
+        let curr_available_value = optional_utxos
+            .iter()
+            .fold(0, |acc, x| acc + x.effective_value);
+
+        let cost_of_change =
+            (Weight::from_vb(self.size_of_change).expect("overflow occurred") * fee_rate).to_sat();
+
+        // `curr_value` and `curr_available_value` are both the sum of *effective_values* of
+        // the UTXOs. For the optional UTXOs (curr_available_value) we filter out UTXOs with
+        // negative effective value, so it will always be positive.
+        //
+        // Since we are required to spend the required UTXOs (curr_value) we have to consider
+        // all their effective values, even when negative, which means that curr_value could
+        // be negative as well.
+        //
+        // If the sum of curr_value and curr_available_value is negative or lower than our target,
+        // we can immediately exit with an error, as it's guaranteed we will never find a solution
+        // if we actually run the BnB.
+        let total_value: Result<u64, _> = (curr_available_value + curr_value).try_into();
+        match total_value {
+            Ok(v) if v >= target_amount => {}
+            _ => {
+                // Assume we spend all the UTXOs we can (all the required + all the optional with
+                // positive effective value), sum their value and their fee cost.
+                let (utxo_fees, utxo_value) = required_utxos
+                    .iter()
+                    .chain(optional_utxos.iter())
+                    .fold((0, 0), |(mut fees, mut value), utxo| {
+                        fees += utxo.fee;
+                        value += utxo.weighted_utxo.utxo.txout().value.to_sat();
+
+                        (fees, value)
+                    });
+
+                // Add to the target the fee cost of the UTXOs
+                return Err(Error::InsufficientFunds {
+                    needed: target_amount + utxo_fees,
+                    available: utxo_value,
+                });
+            }
+        }
+
+        let target_amount = target_amount
+            .try_into()
+            .expect("Bitcoin amount to fit into i64");
+
+        if curr_value > target_amount {
+            // remaining_amount can't be negative as that would mean the
+            // selection wasn't successful
+            // target_amount = amount_needed + (fee_amount - vin_fees)
+            let remaining_amount = (curr_value - target_amount) as u64;
+
+            let excess = decide_change(remaining_amount, fee_rate, drain_script);
+
+            return Ok(BranchAndBoundCoinSelection::calculate_cs_result(
+                vec![],
+                required_utxos,
+                excess,
+            ));
+        }
+
+        Ok(self
+            .bnb(
+                required_utxos.clone(),
+                optional_utxos.clone(),
+                curr_value,
+                curr_available_value,
+                target_amount,
+                cost_of_change,
+                drain_script,
+                fee_rate,
+            )
+            .unwrap_or_else(|_| {
+                self.single_random_draw(
+                    required_utxos,
+                    optional_utxos,
+                    curr_value,
+                    target_amount,
+                    drain_script,
+                    fee_rate,
+                )
+            }))
+    }
+}
+
+impl BranchAndBoundCoinSelection {
+    // TODO: make this more Rust-onic :)
+    // (And perhaps refactor with less arguments?)
+    #[allow(clippy::too_many_arguments)]
+    fn bnb(
+        &self,
+        required_utxos: Vec<OutputGroup>,
+        mut optional_utxos: Vec<OutputGroup>,
+        mut curr_value: i64,
+        mut curr_available_value: i64,
+        target_amount: i64,
+        cost_of_change: u64,
+        drain_script: &Script,
+        fee_rate: FeeRate,
+    ) -> Result<CoinSelectionResult, Error> {
+        // current_selection[i] will contain true if we are using optional_utxos[i],
+        // false otherwise. Note that current_selection.len() could be less than
+        // optional_utxos.len(), it just means that we still haven't decided if we should keep
+        // certain optional_utxos or not.
+        let mut current_selection: Vec<bool> = Vec::with_capacity(optional_utxos.len());
+
+        // Sort the utxo_pool
+        optional_utxos.sort_unstable_by_key(|a| a.effective_value);
+        optional_utxos.reverse();
+
+        // Contains the best selection we found
+        let mut best_selection = Vec::new();
+        let mut best_selection_value = None;
+
+        // Depth First search loop for choosing the UTXOs
+        for _ in 0..BNB_TOTAL_TRIES {
+            // Conditions for starting a backtrack
+            let mut backtrack = false;
+            // Cannot possibly reach target with the amount remaining in the curr_available_value,
+            // or the selected value is out of range.
+            // Go back and try other branch
+            if curr_value + curr_available_value < target_amount
+                || curr_value > target_amount + cost_of_change as i64
+            {
+                backtrack = true;
+            } else if curr_value >= target_amount {
+                // Selected value is within range, there's no point in going forward. Start
+                // backtracking
+                backtrack = true;
+
+                // If we found a solution better than the previous one, or if there wasn't previous
+                // solution, update the best solution
+                if best_selection_value.is_none() || curr_value < best_selection_value.unwrap() {
+                    best_selection.clone_from(&current_selection);
+                    best_selection_value = Some(curr_value);
+                }
+
+                // If we found a perfect match, break here
+                if curr_value == target_amount {
+                    break;
+                }
+            }
+
+            // Backtracking, moving backwards
+            if backtrack {
+                // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
+                while let Some(false) = current_selection.last() {
+                    current_selection.pop();
+                    curr_available_value += optional_utxos[current_selection.len()].effective_value;
+                }
+
+                if current_selection.last_mut().is_none() {
+                    // We have walked back to the first utxo and no branch is untraversed. All solutions searched
+                    // If best selection is empty, then there's no exact match
+                    if best_selection.is_empty() {
+                        return Err(Error::BnBNoExactMatch);
+                    }
+                    break;
+                }
+
+                if let Some(c) = current_selection.last_mut() {
+                    // Output was included on previous iterations, try excluding now.
+                    *c = false;
+                }
+
+                let utxo = &optional_utxos[current_selection.len() - 1];
+                curr_value -= utxo.effective_value;
+            } else {
+                // Moving forwards, continuing down this branch
+                let utxo = &optional_utxos[current_selection.len()];
+
+                // Remove this utxo from the curr_available_value utxo amount
+                curr_available_value -= utxo.effective_value;
+
+                // Inclusion branch first (Largest First Exploration)
+                current_selection.push(true);
+                curr_value += utxo.effective_value;
+            }
+        }
+
+        // Check for solution
+        if best_selection.is_empty() {
+            return Err(Error::BnBTotalTriesExceeded);
+        }
+
+        // Set output set
+        let selected_utxos = optional_utxos
+            .into_iter()
+            .zip(best_selection)
+            .filter_map(|(optional, is_in_best)| if is_in_best { Some(optional) } else { None })
+            .collect::<Vec<OutputGroup>>();
+
+        let selected_amount = best_selection_value.unwrap();
+
+        // remaining_amount can't be negative as that would mean the
+        // selection wasn't successful
+        // target_amount = amount_needed + (fee_amount - vin_fees)
+        let remaining_amount = (selected_amount - target_amount) as u64;
+
+        let excess = decide_change(remaining_amount, fee_rate, drain_script);
+
+        Ok(BranchAndBoundCoinSelection::calculate_cs_result(
+            selected_utxos,
+            required_utxos,
+            excess,
+        ))
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    fn single_random_draw(
+        &self,
+        required_utxos: Vec<OutputGroup>,
+        mut optional_utxos: Vec<OutputGroup>,
+        curr_value: i64,
+        target_amount: i64,
+        drain_script: &Script,
+        fee_rate: FeeRate,
+    ) -> CoinSelectionResult {
+        optional_utxos.shuffle(&mut rand::thread_rng());
+        let selected_utxos = optional_utxos.into_iter().fold(
+            (curr_value, vec![]),
+            |(mut amount, mut utxos), utxo| {
+                if amount >= target_amount {
+                    (amount, utxos)
+                } else {
+                    amount += utxo.effective_value;
+                    utxos.push(utxo);
+                    (amount, utxos)
+                }
+            },
+        );
+
+        // remaining_amount can't be negative as that would mean the
+        // selection wasn't successful
+        // target_amount = amount_needed + (fee_amount - vin_fees)
+        let remaining_amount = (selected_utxos.0 - target_amount) as u64;
+
+        let excess = decide_change(remaining_amount, fee_rate, drain_script);
+
+        BranchAndBoundCoinSelection::calculate_cs_result(selected_utxos.1, required_utxos, excess)
+    }
+
+    fn calculate_cs_result(
+        mut selected_utxos: Vec<OutputGroup>,
+        mut required_utxos: Vec<OutputGroup>,
+        excess: Excess,
+    ) -> CoinSelectionResult {
+        selected_utxos.append(&mut required_utxos);
+        let fee_amount = selected_utxos.iter().map(|u| u.fee).sum::<u64>();
+        let selected = selected_utxos
+            .into_iter()
+            .map(|u| u.weighted_utxo.utxo)
+            .collect::<Vec<_>>();
+
+        CoinSelectionResult {
+            selected,
+            fee_amount,
+            excess,
+        }
+    }
+}
+
+/// Remove duplicate UTXOs.
+///
+/// If a UTXO appears in both `required` and `optional`, the appearance in `required` is kept.
+pub(crate) fn filter_duplicates<I>(required: I, optional: I) -> (I, I)
+where
+    I: IntoIterator<Item = WeightedUtxo> + FromIterator<WeightedUtxo>,
+{
+    let mut visited = HashSet::<OutPoint>::new();
+    let required = required
+        .into_iter()
+        .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
+        .collect::<I>();
+    let optional = optional
+        .into_iter()
+        .filter(|utxo| visited.insert(utxo.utxo.outpoint()))
+        .collect::<I>();
+    (required, optional)
+}
+
+#[cfg(test)]
+mod test {
+    use assert_matches::assert_matches;
+    use core::str::FromStr;
+
+    use bdk_chain::ConfirmationTime;
+    use bitcoin::{Amount, ScriptBuf, TxIn, TxOut};
+
+    use super::*;
+    use crate::types::*;
+    use crate::wallet::coin_selection::filter_duplicates;
+
+    use rand::rngs::StdRng;
+    use rand::seq::SliceRandom;
+    use rand::{Rng, RngCore, SeedableRng};
+
+    // signature len (1WU) + signature and sighash (72WU)
+    // + pubkey len (1WU) + pubkey (33WU)
+    const P2WPKH_SATISFACTION_SIZE: usize = 1 + 72 + 1 + 33;
+
+    const FEE_AMOUNT: u64 = 50;
+
+    fn utxo(value: u64, index: u32, confirmation_time: ConfirmationTime) -> WeightedUtxo {
+        assert!(index < 10);
+        let outpoint = OutPoint::from_str(&format!(
+            "000000000000000000000000000000000000000000000000000000000000000{}:0",
+            index
+        ))
+        .unwrap();
+        WeightedUtxo {
+            satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
+            utxo: Utxo::Local(LocalOutput {
+                outpoint,
+                txout: TxOut {
+                    value: Amount::from_sat(value),
+                    script_pubkey: ScriptBuf::new(),
+                },
+                keychain: KeychainKind::External,
+                is_spent: false,
+                derivation_index: 42,
+                confirmation_time,
+            }),
+        }
+    }
+
+    fn get_test_utxos() -> Vec<WeightedUtxo> {
+        vec![
+            utxo(100_000, 0, ConfirmationTime::Unconfirmed { last_seen: 0 }),
+            utxo(
+                FEE_AMOUNT - 40,
+                1,
+                ConfirmationTime::Unconfirmed { last_seen: 0 },
+            ),
+            utxo(200_000, 2, ConfirmationTime::Unconfirmed { last_seen: 0 }),
+        ]
+    }
+
+    fn get_oldest_first_test_utxos() -> Vec<WeightedUtxo> {
+        // ensure utxos are from different tx
+        let utxo1 = utxo(
+            120_000,
+            1,
+            ConfirmationTime::Confirmed {
+                height: 1,
+                time: 1231006505,
+            },
+        );
+        let utxo2 = utxo(
+            80_000,
+            2,
+            ConfirmationTime::Confirmed {
+                height: 2,
+                time: 1231006505,
+            },
+        );
+        let utxo3 = utxo(
+            300_000,
+            3,
+            ConfirmationTime::Confirmed {
+                height: 3,
+                time: 1231006505,
+            },
+        );
+        vec![utxo1, utxo2, utxo3]
+    }
+
+    fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<WeightedUtxo> {
+        let mut res = Vec::new();
+        for i in 0..utxos_number {
+            res.push(WeightedUtxo {
+                satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
+                utxo: Utxo::Local(LocalOutput {
+                    outpoint: OutPoint::from_str(&format!(
+                        "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:{}",
+                        i
+                    ))
+                    .unwrap(),
+                    txout: TxOut {
+                        value: Amount::from_sat(rng.gen_range(0..200000000)),
+                        script_pubkey: ScriptBuf::new(),
+                    },
+                    keychain: KeychainKind::External,
+                    is_spent: false,
+                    derivation_index: rng.next_u32(),
+                    confirmation_time: if rng.gen_bool(0.5) {
+                        ConfirmationTime::Confirmed {
+                            height: rng.next_u32(),
+                            time: rng.next_u64(),
+                        }
+                    } else {
+                        ConfirmationTime::Unconfirmed { last_seen: 0 }
+                    },
+                }),
+            });
+        }
+        res
+    }
+
+    fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> {
+        (0..utxos_number)
+            .map(|i| WeightedUtxo {
+                satisfaction_weight: P2WPKH_SATISFACTION_SIZE,
+                utxo: Utxo::Local(LocalOutput {
+                    outpoint: OutPoint::from_str(&format!(
+                        "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:{}",
+                        i
+                    ))
+                    .unwrap(),
+                    txout: TxOut {
+                        value: Amount::from_sat(utxos_value),
+                        script_pubkey: ScriptBuf::new(),
+                    },
+                    keychain: KeychainKind::External,
+                    is_spent: false,
+                    derivation_index: 42,
+                    confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
+                }),
+            })
+            .collect()
+    }
+
+    fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 {
+        let utxos_picked_len = rng.gen_range(2..utxos.len() / 2);
+        utxos.shuffle(&mut rng);
+        utxos[..utxos_picked_len]
+            .iter()
+            .map(|u| u.utxo.txout().value.to_sat())
+            .sum()
+    }
+
+    #[test]
+    fn test_largest_first_coin_selection_success() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 250_000 + FEE_AMOUNT;
+
+        let result = LargestFirstCoinSelection
+            .coin_select(
+                utxos,
+                vec![],
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 3);
+        assert_eq!(result.selected_amount(), 300_010);
+        assert_eq!(result.fee_amount, 204)
+    }
+
+    #[test]
+    fn test_largest_first_coin_selection_use_all() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let result = LargestFirstCoinSelection
+            .coin_select(
+                utxos,
+                vec![],
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 3);
+        assert_eq!(result.selected_amount(), 300_010);
+        assert_eq!(result.fee_amount, 204);
+    }
+
+    #[test]
+    fn test_largest_first_coin_selection_use_only_necessary() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let result = LargestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 1);
+        assert_eq!(result.selected_amount(), 200_000);
+        assert_eq!(result.fee_amount, 68);
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_largest_first_coin_selection_insufficient_funds() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 500_000 + FEE_AMOUNT;
+
+        LargestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_largest_first_coin_selection_insufficient_funds_high_fees() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 250_000 + FEE_AMOUNT;
+
+        LargestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1000),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    fn test_oldest_first_coin_selection_success() {
+        let utxos = get_oldest_first_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 180_000 + FEE_AMOUNT;
+
+        let result = OldestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 2);
+        assert_eq!(result.selected_amount(), 200_000);
+        assert_eq!(result.fee_amount, 136)
+    }
+
+    #[test]
+    fn test_oldest_first_coin_selection_use_all() {
+        let utxos = get_oldest_first_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let result = OldestFirstCoinSelection
+            .coin_select(
+                utxos,
+                vec![],
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 3);
+        assert_eq!(result.selected_amount(), 500_000);
+        assert_eq!(result.fee_amount, 204);
+    }
+
+    #[test]
+    fn test_oldest_first_coin_selection_use_only_necessary() {
+        let utxos = get_oldest_first_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let result = OldestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 1);
+        assert_eq!(result.selected_amount(), 120_000);
+        assert_eq!(result.fee_amount, 68);
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_oldest_first_coin_selection_insufficient_funds() {
+        let utxos = get_oldest_first_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 600_000 + FEE_AMOUNT;
+
+        OldestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_oldest_first_coin_selection_insufficient_funds_high_fees() {
+        let utxos = get_oldest_first_test_utxos();
+
+        let target_amount: u64 = utxos
+            .iter()
+            .map(|wu| wu.utxo.txout().value.to_sat())
+            .sum::<u64>()
+            - 50;
+        let drain_script = ScriptBuf::default();
+
+        OldestFirstCoinSelection
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1000),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    fn test_bnb_coin_selection_success() {
+        // In this case bnb won't find a suitable match and single random draw will
+        // select three outputs
+        let utxos = generate_same_value_utxos(100_000, 20);
+
+        let drain_script = ScriptBuf::default();
+
+        let target_amount = 250_000 + FEE_AMOUNT;
+
+        let result = BranchAndBoundCoinSelection::default()
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 3);
+        assert_eq!(result.selected_amount(), 300_000);
+        assert_eq!(result.fee_amount, 204);
+    }
+
+    #[test]
+    fn test_bnb_coin_selection_required_are_enough() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let result = BranchAndBoundCoinSelection::default()
+            .coin_select(
+                utxos.clone(),
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 3);
+        assert_eq!(result.selected_amount(), 300_010);
+        assert_eq!(result.fee_amount, 204);
+    }
+
+    #[test]
+    fn test_bnb_coin_selection_optional_are_enough() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 299756 + FEE_AMOUNT;
+
+        let result = BranchAndBoundCoinSelection::default()
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 2);
+        assert_eq!(result.selected_amount(), 300000);
+        assert_eq!(result.fee_amount, 136);
+    }
+
+    #[test]
+    #[ignore]
+    fn test_bnb_coin_selection_required_not_enough() {
+        let utxos = get_test_utxos();
+
+        let required = vec![utxos[0].clone()];
+        let mut optional = utxos[1..].to_vec();
+        optional.push(utxo(
+            500_000,
+            3,
+            ConfirmationTime::Unconfirmed { last_seen: 0 },
+        ));
+
+        // Defensive assertions, for sanity and in case someone changes the test utxos vector.
+        let amount: u64 = required.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
+        assert_eq!(amount, 100_000);
+        let amount: u64 = optional.iter().map(|u| u.utxo.txout().value.to_sat()).sum();
+        assert!(amount > 150_000);
+        let drain_script = ScriptBuf::default();
+
+        let target_amount = 150_000 + FEE_AMOUNT;
+
+        let result = BranchAndBoundCoinSelection::default()
+            .coin_select(
+                required,
+                optional,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 2);
+        assert_eq!(result.selected_amount(), 300_000);
+        assert_eq!(result.fee_amount, 136);
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_bnb_coin_selection_insufficient_funds() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 500_000 + FEE_AMOUNT;
+
+        BranchAndBoundCoinSelection::default()
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "InsufficientFunds")]
+    fn test_bnb_coin_selection_insufficient_funds_high_fees() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 250_000 + FEE_AMOUNT;
+
+        BranchAndBoundCoinSelection::default()
+            .coin_select(
+                vec![],
+                utxos,
+                FeeRate::from_sat_per_vb_unchecked(1000),
+                target_amount,
+                &drain_script,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    fn test_bnb_coin_selection_check_fee_rate() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+        let target_amount = 99932; // first utxo's effective value
+        let feerate = FeeRate::BROADCAST_MIN;
+
+        let result = BranchAndBoundCoinSelection::new(0)
+            .coin_select(vec![], utxos, feerate, target_amount, &drain_script)
+            .unwrap();
+
+        assert_eq!(result.selected.len(), 1);
+        assert_eq!(result.selected_amount(), 100_000);
+        let input_weight =
+            TxIn::default().segwit_weight().to_wu() + P2WPKH_SATISFACTION_SIZE as u64;
+        // the final fee rate should be exactly the same as the fee rate given
+        let result_feerate = Amount::from_sat(result.fee_amount) / Weight::from_wu(input_weight);
+        assert_eq!(result_feerate, feerate);
+    }
+
+    #[test]
+    fn test_bnb_coin_selection_exact_match() {
+        let seed = [0; 32];
+        let mut rng: StdRng = SeedableRng::from_seed(seed);
+
+        for _i in 0..200 {
+            let mut optional_utxos = generate_random_utxos(&mut rng, 16);
+            let target_amount = sum_random_utxos(&mut rng, &mut optional_utxos);
+            let drain_script = ScriptBuf::default();
+            let result = BranchAndBoundCoinSelection::new(0)
+                .coin_select(
+                    vec![],
+                    optional_utxos,
+                    FeeRate::ZERO,
+                    target_amount,
+                    &drain_script,
+                )
+                .unwrap();
+            assert_eq!(result.selected_amount(), target_amount);
+        }
+    }
+
+    #[test]
+    #[should_panic(expected = "BnBNoExactMatch")]
+    fn test_bnb_function_no_exact_match() {
+        let fee_rate = FeeRate::from_sat_per_vb_unchecked(10);
+        let utxos: Vec<OutputGroup> = get_test_utxos()
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .collect();
+
+        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
+
+        let size_of_change = 31;
+        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
+
+        let drain_script = ScriptBuf::default();
+        let target_amount = 20_000 + FEE_AMOUNT;
+        BranchAndBoundCoinSelection::new(size_of_change)
+            .bnb(
+                vec![],
+                utxos,
+                0,
+                curr_available_value,
+                target_amount as i64,
+                cost_of_change,
+                &drain_script,
+                fee_rate,
+            )
+            .unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "BnBTotalTriesExceeded")]
+    fn test_bnb_function_tries_exceeded() {
+        let fee_rate = FeeRate::from_sat_per_vb_unchecked(10);
+        let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000)
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .collect();
+
+        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
+
+        let size_of_change = 31;
+        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
+        let target_amount = 20_000 + FEE_AMOUNT;
+
+        let drain_script = ScriptBuf::default();
+
+        BranchAndBoundCoinSelection::new(size_of_change)
+            .bnb(
+                vec![],
+                utxos,
+                0,
+                curr_available_value,
+                target_amount as i64,
+                cost_of_change,
+                &drain_script,
+                fee_rate,
+            )
+            .unwrap();
+    }
+
+    // The match won't be exact but still in the range
+    #[test]
+    fn test_bnb_function_almost_exact_match_with_fees() {
+        let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
+        let size_of_change = 31;
+        let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat();
+
+        let utxos: Vec<_> = generate_same_value_utxos(50_000, 10)
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .collect();
+
+        let curr_value = 0;
+
+        let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value);
+
+        // 2*(value of 1 utxo)  - 2*(1 utxo fees with 1.0sat/vbyte fee rate) -
+        // cost_of_change + 5.
+        let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change as i64 + 5;
+
+        let drain_script = ScriptBuf::default();
+
+        let result = BranchAndBoundCoinSelection::new(size_of_change)
+            .bnb(
+                vec![],
+                utxos,
+                curr_value,
+                curr_available_value,
+                target_amount,
+                cost_of_change,
+                &drain_script,
+                fee_rate,
+            )
+            .unwrap();
+        assert_eq!(result.selected_amount(), 100_000);
+        assert_eq!(result.fee_amount, 136);
+    }
+
+    // TODO: bnb() function should be optimized, and this test should be done with more utxos
+    #[test]
+    fn test_bnb_function_exact_match_more_utxos() {
+        let seed = [0; 32];
+        let mut rng: StdRng = SeedableRng::from_seed(seed);
+        let fee_rate = FeeRate::ZERO;
+
+        for _ in 0..200 {
+            let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40)
+                .into_iter()
+                .map(|u| OutputGroup::new(u, fee_rate))
+                .collect();
+
+            let curr_value = 0;
+
+            let curr_available_value = optional_utxos
+                .iter()
+                .fold(0, |acc, x| acc + x.effective_value);
+
+            let target_amount =
+                optional_utxos[3].effective_value + optional_utxos[23].effective_value;
+
+            let drain_script = ScriptBuf::default();
+
+            let result = BranchAndBoundCoinSelection::new(0)
+                .bnb(
+                    vec![],
+                    optional_utxos,
+                    curr_value,
+                    curr_available_value,
+                    target_amount,
+                    0,
+                    &drain_script,
+                    fee_rate,
+                )
+                .unwrap();
+            assert_eq!(result.selected_amount(), target_amount as u64);
+        }
+    }
+
+    #[test]
+    fn test_single_random_draw_function_success() {
+        let seed = [0; 32];
+        let mut rng: StdRng = SeedableRng::from_seed(seed);
+        let mut utxos = generate_random_utxos(&mut rng, 300);
+        let target_amount = sum_random_utxos(&mut rng, &mut utxos) + FEE_AMOUNT;
+
+        let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
+        let utxos: Vec<OutputGroup> = utxos
+            .into_iter()
+            .map(|u| OutputGroup::new(u, fee_rate))
+            .collect();
+
+        let drain_script = ScriptBuf::default();
+
+        let result = BranchAndBoundCoinSelection::default().single_random_draw(
+            vec![],
+            utxos,
+            0,
+            target_amount as i64,
+            &drain_script,
+            fee_rate,
+        );
+
+        assert!(result.selected_amount() > target_amount);
+        assert_eq!(result.fee_amount, (result.selected.len() * 68) as u64);
+    }
+
+    #[test]
+    fn test_bnb_exclude_negative_effective_value() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+
+        let selection = BranchAndBoundCoinSelection::default().coin_select(
+            vec![],
+            utxos,
+            FeeRate::from_sat_per_vb_unchecked(10),
+            500_000,
+            &drain_script,
+        );
+
+        assert_matches!(
+            selection,
+            Err(Error::InsufficientFunds {
+                available: 300_000,
+                ..
+            })
+        );
+    }
+
+    #[test]
+    fn test_bnb_include_negative_effective_value_when_required() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+
+        let (required, optional) = utxos.into_iter().partition(
+            |u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value.to_sat() < 1000),
+        );
+
+        let selection = BranchAndBoundCoinSelection::default().coin_select(
+            required,
+            optional,
+            FeeRate::from_sat_per_vb_unchecked(10),
+            500_000,
+            &drain_script,
+        );
+
+        assert_matches!(
+            selection,
+            Err(Error::InsufficientFunds {
+                available: 300_010,
+                ..
+            })
+        );
+    }
+
+    #[test]
+    fn test_bnb_sum_of_effective_value_negative() {
+        let utxos = get_test_utxos();
+        let drain_script = ScriptBuf::default();
+
+        let selection = BranchAndBoundCoinSelection::default().coin_select(
+            utxos,
+            vec![],
+            FeeRate::from_sat_per_vb_unchecked(10_000),
+            500_000,
+            &drain_script,
+        );
+
+        assert_matches!(
+            selection,
+            Err(Error::InsufficientFunds {
+                available: 300_010,
+                ..
+            })
+        );
+    }
+
+    #[test]
+    fn test_filter_duplicates() {
+        fn utxo(txid: &str, value: u64) -> WeightedUtxo {
+            WeightedUtxo {
+                satisfaction_weight: 0,
+                utxo: Utxo::Local(LocalOutput {
+                    outpoint: OutPoint::new(bitcoin::hashes::Hash::hash(txid.as_bytes()), 0),
+                    txout: TxOut {
+                        value: Amount::from_sat(value),
+                        script_pubkey: ScriptBuf::new(),
+                    },
+                    keychain: KeychainKind::External,
+                    is_spent: false,
+                    derivation_index: 0,
+                    confirmation_time: ConfirmationTime::Confirmed {
+                        height: 12345,
+                        time: 12345,
+                    },
+                }),
+            }
+        }
+
+        fn to_utxo_vec(utxos: &[(&str, u64)]) -> Vec<WeightedUtxo> {
+            let mut v = utxos
+                .iter()
+                .map(|&(txid, value)| utxo(txid, value))
+                .collect::<Vec<_>>();
+            v.sort_by_key(|u| u.utxo.outpoint());
+            v
+        }
+
+        struct TestCase<'a> {
+            name: &'a str,
+            required: &'a [(&'a str, u64)],
+            optional: &'a [(&'a str, u64)],
+            exp_required: &'a [(&'a str, u64)],
+            exp_optional: &'a [(&'a str, u64)],
+        }
+
+        let test_cases = [
+            TestCase {
+                name: "no_duplicates",
+                required: &[("A", 1000), ("B", 2100)],
+                optional: &[("C", 1000)],
+                exp_required: &[("A", 1000), ("B", 2100)],
+                exp_optional: &[("C", 1000)],
+            },
+            TestCase {
+                name: "duplicate_required_utxos",
+                required: &[("A", 3000), ("B", 1200), ("C", 1234), ("A", 3000)],
+                optional: &[("D", 2100)],
+                exp_required: &[("A", 3000), ("B", 1200), ("C", 1234)],
+                exp_optional: &[("D", 2100)],
+            },
+            TestCase {
+                name: "duplicate_optional_utxos",
+                required: &[("A", 3000), ("B", 1200)],
+                optional: &[("C", 5000), ("D", 1300), ("C", 5000)],
+                exp_required: &[("A", 3000), ("B", 1200)],
+                exp_optional: &[("C", 5000), ("D", 1300)],
+            },
+            TestCase {
+                name: "duplicate_across_required_and_optional_utxos",
+                required: &[("A", 3000), ("B", 1200), ("C", 2100)],
+                optional: &[("A", 3000), ("D", 1200), ("E", 5000)],
+                exp_required: &[("A", 3000), ("B", 1200), ("C", 2100)],
+                exp_optional: &[("D", 1200), ("E", 5000)],
+            },
+        ];
+
+        for (i, t) in test_cases.into_iter().enumerate() {
+            println!("Case {}: {}", i, t.name);
+            let (required, optional) =
+                filter_duplicates(to_utxo_vec(t.required), to_utxo_vec(t.optional));
+            assert_eq!(
+                required,
+                to_utxo_vec(t.exp_required),
+                "[{}:{}] unexpected `required` result",
+                i,
+                t.name
+            );
+            assert_eq!(
+                optional,
+                to_utxo_vec(t.exp_optional),
+                "[{}:{}] unexpected `optional` result",
+                i,
+                t.name
+            );
+        }
+    }
+}
diff --git a/crates/wallet/src/wallet/error.rs b/crates/wallet/src/wallet/error.rs
new file mode 100644 (file)
index 0000000..eaf811d
--- /dev/null
@@ -0,0 +1,291 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)
+
+use crate::descriptor::policy::PolicyError;
+use crate::descriptor::DescriptorError;
+use crate::wallet::coin_selection;
+use crate::{descriptor, KeychainKind};
+use alloc::string::String;
+use bitcoin::{absolute, psbt, OutPoint, Sequence, Txid};
+use core::fmt;
+
+/// Errors returned by miniscript when updating inconsistent PSBTs
+#[derive(Debug, Clone)]
+pub enum MiniscriptPsbtError {
+    /// Descriptor key conversion error
+    Conversion(miniscript::descriptor::ConversionError),
+    /// Return error type for PsbtExt::update_input_with_descriptor
+    UtxoUpdate(miniscript::psbt::UtxoUpdateError),
+    /// Return error type for PsbtExt::update_output_with_descriptor
+    OutputUpdate(miniscript::psbt::OutputUpdateError),
+}
+
+impl fmt::Display for MiniscriptPsbtError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Conversion(err) => write!(f, "Conversion error: {}", err),
+            Self::UtxoUpdate(err) => write!(f, "UTXO update error: {}", err),
+            Self::OutputUpdate(err) => write!(f, "Output update error: {}", err),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for MiniscriptPsbtError {}
+
+#[derive(Debug)]
+/// Error returned from [`TxBuilder::finish`]
+///
+/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish
+pub enum CreateTxError {
+    /// There was a problem with the descriptors passed in
+    Descriptor(DescriptorError),
+    /// We were unable to load wallet data from or write wallet data to the persistence backend
+    Persist(anyhow::Error),
+    /// There was a problem while extracting and manipulating policies
+    Policy(PolicyError),
+    /// Spending policy is not compatible with this [`KeychainKind`]
+    SpendingPolicyRequired(KeychainKind),
+    /// Requested invalid transaction version '0'
+    Version0,
+    /// Requested transaction version `1`, but at least `2` is needed to use OP_CSV
+    Version1Csv,
+    /// Requested `LockTime` is less than is required to spend from this script
+    LockTime {
+        /// Requested `LockTime`
+        requested: absolute::LockTime,
+        /// Required `LockTime`
+        required: absolute::LockTime,
+    },
+    /// Cannot enable RBF with a `Sequence` >= 0xFFFFFFFE
+    RbfSequence,
+    /// Cannot enable RBF with `Sequence` given a required OP_CSV
+    RbfSequenceCsv {
+        /// Given RBF `Sequence`
+        rbf: Sequence,
+        /// Required OP_CSV `Sequence`
+        csv: Sequence,
+    },
+    /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee
+    FeeTooLow {
+        /// Required fee absolute value (satoshi)
+        required: u64,
+    },
+    /// When bumping a tx the fee rate requested is lower than required
+    FeeRateTooLow {
+        /// Required fee rate
+        required: bitcoin::FeeRate,
+    },
+    /// `manually_selected_only` option is selected but no utxo has been passed
+    NoUtxosSelected,
+    /// Output created is under the dust limit, 546 satoshis
+    OutputBelowDustLimit(usize),
+    /// The `change_policy` was set but the wallet does not have a change_descriptor
+    ChangePolicyDescriptor,
+    /// There was an error with coin selection
+    CoinSelection(coin_selection::Error),
+    /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
+    InsufficientFunds {
+        /// Sats needed for some transaction
+        needed: u64,
+        /// Sats available for spending
+        available: u64,
+    },
+    /// Cannot build a tx without recipients
+    NoRecipients,
+    /// Partially signed bitcoin transaction error
+    Psbt(psbt::Error),
+    /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended
+    /// key in the descriptor must either be a master key itself (having depth = 0) or have an
+    /// explicit origin provided
+    ///
+    /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs
+    MissingKeyOrigin(String),
+    /// Happens when trying to spend an UTXO that is not in the internal database
+    UnknownUtxo,
+    /// Missing non_witness_utxo on foreign utxo for given `OutPoint`
+    MissingNonWitnessUtxo(OutPoint),
+    /// Miniscript PSBT error
+    MiniscriptPsbt(MiniscriptPsbtError),
+}
+
+impl fmt::Display for CreateTxError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Descriptor(e) => e.fmt(f),
+            Self::Persist(e) => {
+                write!(
+                    f,
+                    "failed to load wallet data from or write wallet data to persistence backend: {}",
+                    e
+                )
+            }
+            Self::Policy(e) => e.fmt(f),
+            CreateTxError::SpendingPolicyRequired(keychain_kind) => {
+                write!(f, "Spending policy required: {:?}", keychain_kind)
+            }
+            CreateTxError::Version0 => {
+                write!(f, "Invalid version `0`")
+            }
+            CreateTxError::Version1Csv => {
+                write!(
+                    f,
+                    "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
+                )
+            }
+            CreateTxError::LockTime {
+                requested,
+                required,
+            } => {
+                write!(f, "TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", required, requested)
+            }
+            CreateTxError::RbfSequence => {
+                write!(f, "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")
+            }
+            CreateTxError::RbfSequenceCsv { rbf, csv } => {
+                write!(
+                    f,
+                    "Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
+                    rbf, csv
+                )
+            }
+            CreateTxError::FeeTooLow { required } => {
+                write!(f, "Fee to low: required {} sat", required)
+            }
+            CreateTxError::FeeRateTooLow { required } => {
+                write!(
+                    f,
+                    // Note: alternate fmt as sat/vb (ceil) available in bitcoin-0.31
+                    //"Fee rate too low: required {required:#}"
+                    "Fee rate too low: required {} sat/vb",
+                    crate::floating_rate!(required)
+                )
+            }
+            CreateTxError::NoUtxosSelected => {
+                write!(f, "No UTXO selected")
+            }
+            CreateTxError::OutputBelowDustLimit(limit) => {
+                write!(f, "Output below the dust limit: {}", limit)
+            }
+            CreateTxError::ChangePolicyDescriptor => {
+                write!(
+                    f,
+                    "The `change_policy` can be set only if the wallet has a change_descriptor"
+                )
+            }
+            CreateTxError::CoinSelection(e) => e.fmt(f),
+            CreateTxError::InsufficientFunds { needed, available } => {
+                write!(
+                    f,
+                    "Insufficient funds: {} sat available of {} sat needed",
+                    available, needed
+                )
+            }
+            CreateTxError::NoRecipients => {
+                write!(f, "Cannot build tx without recipients")
+            }
+            CreateTxError::Psbt(e) => e.fmt(f),
+            CreateTxError::MissingKeyOrigin(err) => {
+                write!(f, "Missing key origin: {}", err)
+            }
+            CreateTxError::UnknownUtxo => {
+                write!(f, "UTXO not found in the internal database")
+            }
+            CreateTxError::MissingNonWitnessUtxo(outpoint) => {
+                write!(f, "Missing non_witness_utxo on foreign utxo {}", outpoint)
+            }
+            CreateTxError::MiniscriptPsbt(err) => {
+                write!(f, "Miniscript PSBT error: {}", err)
+            }
+        }
+    }
+}
+
+impl From<descriptor::error::Error> for CreateTxError {
+    fn from(err: descriptor::error::Error) -> Self {
+        CreateTxError::Descriptor(err)
+    }
+}
+
+impl From<PolicyError> for CreateTxError {
+    fn from(err: PolicyError) -> Self {
+        CreateTxError::Policy(err)
+    }
+}
+
+impl From<MiniscriptPsbtError> for CreateTxError {
+    fn from(err: MiniscriptPsbtError) -> Self {
+        CreateTxError::MiniscriptPsbt(err)
+    }
+}
+
+impl From<psbt::Error> for CreateTxError {
+    fn from(err: psbt::Error) -> Self {
+        CreateTxError::Psbt(err)
+    }
+}
+
+impl From<coin_selection::Error> for CreateTxError {
+    fn from(err: coin_selection::Error) -> Self {
+        CreateTxError::CoinSelection(err)
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for CreateTxError {}
+
+#[derive(Debug)]
+/// Error returned from [`Wallet::build_fee_bump`]
+///
+/// [`Wallet::build_fee_bump`]: super::Wallet::build_fee_bump
+pub enum BuildFeeBumpError {
+    /// Happens when trying to spend an UTXO that is not in the internal database
+    UnknownUtxo(OutPoint),
+    /// Thrown when a tx is not found in the internal database
+    TransactionNotFound(Txid),
+    /// Happens when trying to bump a transaction that is already confirmed
+    TransactionConfirmed(Txid),
+    /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE`
+    IrreplaceableTransaction(Txid),
+    /// Node doesn't have data to estimate a fee rate
+    FeeRateUnavailable,
+}
+
+impl fmt::Display for BuildFeeBumpError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::UnknownUtxo(outpoint) => write!(
+                f,
+                "UTXO not found in the internal database with txid: {}, vout: {}",
+                outpoint.txid, outpoint.vout
+            ),
+            Self::TransactionNotFound(txid) => {
+                write!(
+                    f,
+                    "Transaction not found in the internal database with txid: {}",
+                    txid
+                )
+            }
+            Self::TransactionConfirmed(txid) => {
+                write!(f, "Transaction already confirmed with txid: {}", txid)
+            }
+            Self::IrreplaceableTransaction(txid) => {
+                write!(f, "Transaction can't be replaced with txid: {}", txid)
+            }
+            Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BuildFeeBumpError {}
diff --git a/crates/wallet/src/wallet/export.rs b/crates/wallet/src/wallet/export.rs
new file mode 100644 (file)
index 0000000..95d9119
--- /dev/null
@@ -0,0 +1,341 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Wallet export
+//!
+//! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md).
+//!
+//! ## Examples
+//!
+//! ### Import from JSON
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use bdk_wallet::wallet::export::*;
+//! # use bdk_wallet::*;
+//! let import = r#"{
+//!     "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
+//!     "blockheight":1782088,
+//!     "label":"testnet"
+//! }"#;
+//!
+//! let import = FullyNodedExport::from_str(import)?;
+//! let wallet = Wallet::new_no_persist(
+//!     &import.descriptor(),
+//!     import.change_descriptor().as_ref(),
+//!     Network::Testnet,
+//! )?;
+//! # Ok::<_, Box<dyn std::error::Error>>(())
+//! ```
+//!
+//! ### Export a `Wallet`
+//! ```
+//! # use bitcoin::*;
+//! # use bdk_wallet::wallet::export::*;
+//! # use bdk_wallet::*;
+//! let wallet = Wallet::new_no_persist(
+//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
+//!     Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
+//!     Network::Testnet,
+//! )?;
+//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
+//!
+//! println!("Exported: {}", export.to_string());
+//! # Ok::<_, Box<dyn std::error::Error>>(())
+//! ```
+
+use alloc::string::String;
+use core::fmt;
+use core::str::FromStr;
+use serde::{Deserialize, Serialize};
+
+use miniscript::descriptor::{ShInner, WshInner};
+use miniscript::{Descriptor, ScriptContext, Terminal};
+
+use crate::types::KeychainKind;
+use crate::wallet::Wallet;
+
+/// Alias for [`FullyNodedExport`]
+#[deprecated(since = "0.18.0", note = "Please use [`FullyNodedExport`] instead")]
+pub type WalletExport = FullyNodedExport;
+
+/// Structure that contains the export of a wallet
+///
+/// For a usage example see [this module](crate::wallet::export)'s documentation.
+#[derive(Debug, Serialize, Deserialize)]
+pub struct FullyNodedExport {
+    descriptor: String,
+    /// Earliest block to rescan when looking for the wallet's transactions
+    pub blockheight: u32,
+    /// Arbitrary label for the wallet
+    pub label: String,
+}
+
+impl fmt::Display for FullyNodedExport {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", serde_json::to_string(self).unwrap())
+    }
+}
+
+impl FromStr for FullyNodedExport {
+    type Err = serde_json::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        serde_json::from_str(s)
+    }
+}
+
+fn remove_checksum(s: String) -> String {
+    s.split_once('#').map(|(a, _)| String::from(a)).unwrap()
+}
+
+impl FullyNodedExport {
+    /// Export a wallet
+    ///
+    /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
+    /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44
+    /// and others.
+    ///
+    /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database
+    /// for the oldest transaction it knows and use that as the earliest block to rescan.
+    ///
+    /// If the database is empty or `include_blockheight` is false, the `blockheight` field
+    /// returned will be `0`.
+    pub fn export_wallet(
+        wallet: &Wallet,
+        label: &str,
+        include_blockheight: bool,
+    ) -> Result<Self, &'static str> {
+        let descriptor = wallet
+            .get_descriptor_for_keychain(KeychainKind::External)
+            .to_string_with_secret(
+                &wallet
+                    .get_signers(KeychainKind::External)
+                    .as_key_map(wallet.secp_ctx()),
+            );
+        let descriptor = remove_checksum(descriptor);
+        Self::is_compatible_with_core(&descriptor)?;
+
+        let blockheight = if include_blockheight {
+            wallet.transactions().next().map_or(0, |canonical_tx| {
+                match canonical_tx.chain_position {
+                    bdk_chain::ChainPosition::Confirmed(a) => a.confirmation_height,
+                    bdk_chain::ChainPosition::Unconfirmed(_) => 0,
+                }
+            })
+        } else {
+            0
+        };
+
+        let export = FullyNodedExport {
+            descriptor,
+            label: label.into(),
+            blockheight,
+        };
+
+        let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() {
+            false => None,
+            true => {
+                let descriptor = wallet
+                    .get_descriptor_for_keychain(KeychainKind::Internal)
+                    .to_string_with_secret(
+                        &wallet
+                            .get_signers(KeychainKind::Internal)
+                            .as_key_map(wallet.secp_ctx()),
+                    );
+                Some(remove_checksum(descriptor))
+            }
+        };
+        if export.change_descriptor() != change_descriptor {
+            return Err("Incompatible change descriptor");
+        }
+
+        Ok(export)
+    }
+
+    fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> {
+        fn check_ms<Ctx: ScriptContext>(
+            terminal: &Terminal<String, Ctx>,
+        ) -> Result<(), &'static str> {
+            if let Terminal::Multi(_, _) = terminal {
+                Ok(())
+            } else {
+                Err("The descriptor contains operators not supported by Bitcoin Core")
+            }
+        }
+
+        // pkh(), wpkh(), sh(wpkh()) are always fine, as well as multi() and sortedmulti()
+        match Descriptor::<String>::from_str(descriptor).map_err(|_| "Invalid descriptor")? {
+            Descriptor::Pkh(_) | Descriptor::Wpkh(_) => Ok(()),
+            Descriptor::Sh(sh) => match sh.as_inner() {
+                ShInner::Wpkh(_) => Ok(()),
+                ShInner::SortedMulti(_) => Ok(()),
+                ShInner::Wsh(wsh) => match wsh.as_inner() {
+                    WshInner::SortedMulti(_) => Ok(()),
+                    WshInner::Ms(ms) => check_ms(&ms.node),
+                },
+                ShInner::Ms(ms) => check_ms(&ms.node),
+            },
+            Descriptor::Wsh(wsh) => match wsh.as_inner() {
+                WshInner::SortedMulti(_) => Ok(()),
+                WshInner::Ms(ms) => check_ms(&ms.node),
+            },
+            _ => Err("The descriptor is not compatible with Bitcoin Core"),
+        }
+    }
+
+    /// Return the external descriptor
+    pub fn descriptor(&self) -> String {
+        self.descriptor.clone()
+    }
+
+    /// Return the internal descriptor, if present
+    pub fn change_descriptor(&self) -> Option<String> {
+        let replaced = self.descriptor.replace("/0/*", "/1/*");
+
+        if replaced != self.descriptor {
+            Some(replaced)
+        } else {
+            None
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use core::str::FromStr;
+
+    use crate::std::string::ToString;
+    use bdk_chain::{BlockId, ConfirmationTime};
+    use bitcoin::hashes::Hash;
+    use bitcoin::{transaction, BlockHash, Network, Transaction};
+
+    use super::*;
+    use crate::wallet::Wallet;
+
+    fn get_test_wallet(
+        descriptor: &str,
+        change_descriptor: Option<&str>,
+        network: Network,
+    ) -> Wallet {
+        let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
+        let transaction = Transaction {
+            input: vec![],
+            output: vec![],
+            version: transaction::Version::non_standard(0),
+            lock_time: bitcoin::absolute::LockTime::ZERO,
+        };
+        wallet
+            .insert_checkpoint(BlockId {
+                height: 5001,
+                hash: BlockHash::all_zeros(),
+            })
+            .unwrap();
+        wallet
+            .insert_tx(
+                transaction,
+                ConfirmationTime::Confirmed {
+                    height: 5000,
+                    time: 0,
+                },
+            )
+            .unwrap();
+        wallet
+    }
+
+    #[test]
+    fn test_export_bip44() {
+        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
+
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
+
+        assert_eq!(export.descriptor(), descriptor);
+        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
+        assert_eq!(export.blockheight, 5000);
+        assert_eq!(export.label, "Test Label");
+    }
+
+    #[test]
+    #[should_panic(expected = "Incompatible change descriptor")]
+    fn test_export_no_change() {
+        // This wallet explicitly doesn't have a change descriptor. It should be impossible to
+        // export, because exporting this kind of external descriptor normally implies the
+        // existence of an internal descriptor
+
+        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+
+        let wallet = get_test_wallet(descriptor, None, Network::Bitcoin);
+        FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "Incompatible change descriptor")]
+    fn test_export_incompatible_change() {
+        // This wallet has a change descriptor, but the derivation path is not in the "standard"
+        // bip44/49/etc format
+
+        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
+
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
+    }
+
+    #[test]
+    fn test_export_multi() {
+        let descriptor = "wsh(multi(2,\
+                                [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
+                                [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
+                                [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
+                          ))";
+        let change_descriptor = "wsh(multi(2,\
+                                       [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
+                                       [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
+                                       [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
+                                 ))";
+
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
+        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
+
+        assert_eq!(export.descriptor(), descriptor);
+        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
+        assert_eq!(export.blockheight, 5000);
+        assert_eq!(export.label, "Test Label");
+    }
+
+    #[test]
+    fn test_export_to_json() {
+        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
+
+        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
+
+        assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
+    }
+
+    #[test]
+    fn test_export_from_json() {
+        let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
+
+        let import_str = "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}";
+        let export = FullyNodedExport::from_str(import_str).unwrap();
+
+        assert_eq!(export.descriptor(), descriptor);
+        assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
+        assert_eq!(export.blockheight, 5000);
+        assert_eq!(export.label, "Test Label");
+    }
+}
diff --git a/crates/wallet/src/wallet/hardwaresigner.rs b/crates/wallet/src/wallet/hardwaresigner.rs
new file mode 100644 (file)
index 0000000..b9bff5a
--- /dev/null
@@ -0,0 +1,98 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! HWI Signer
+//!
+//! This module contains HWISigner, an implementation of a [TransactionSigner] to be
+//! used with hardware wallets.
+//! ```no_run
+//! # use bdk_wallet::bitcoin::Network;
+//! # use bdk_wallet::signer::SignerOrdering;
+//! # use bdk_wallet::wallet::hardwaresigner::HWISigner;
+//! # use bdk_wallet::wallet::AddressIndex::New;
+//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
+//! # use hwi::HWIClient;
+//! # use std::sync::Arc;
+//! #
+//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
+//! let mut devices = HWIClient::enumerate()?;
+//! if devices.is_empty() {
+//!     panic!("No devices found!");
+//! }
+//! let first_device = devices.remove(0)?;
+//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
+//!
+//! # let mut wallet = Wallet::new_no_persist(
+//! #     "",
+//! #     None,
+//! #     Network::Testnet,
+//! # )?;
+//! #
+//! // Adding the hardware signer to the BDK wallet
+//! wallet.add_signer(
+//!     KeychainKind::External,
+//!     SignerOrdering(200),
+//!     Arc::new(custom_signer),
+//! );
+//!
+//! # Ok(())
+//! # }
+//! ```
+
+use bitcoin::bip32::Fingerprint;
+use bitcoin::secp256k1::{All, Secp256k1};
+use bitcoin::Psbt;
+
+use hwi::error::Error;
+use hwi::types::{HWIChain, HWIDevice};
+use hwi::HWIClient;
+
+use crate::signer::{SignerCommon, SignerError, SignerId, TransactionSigner};
+
+#[derive(Debug)]
+/// Custom signer for Hardware Wallets
+///
+/// This ignores `sign_options` and leaves the decisions up to the hardware wallet.
+pub struct HWISigner {
+    fingerprint: Fingerprint,
+    client: HWIClient,
+}
+
+impl HWISigner {
+    /// Create a instance from the specified device and chain
+    pub fn from_device(device: &HWIDevice, chain: HWIChain) -> Result<HWISigner, Error> {
+        let client = HWIClient::get_client(device, false, chain)?;
+        Ok(HWISigner {
+            fingerprint: device.fingerprint,
+            client,
+        })
+    }
+}
+
+impl SignerCommon for HWISigner {
+    fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
+        SignerId::Fingerprint(self.fingerprint)
+    }
+}
+
+/// This implementation ignores `sign_options`
+impl TransactionSigner for HWISigner {
+    fn sign_transaction(
+        &self,
+        psbt: &mut Psbt,
+        _sign_options: &crate::SignOptions,
+        _secp: &crate::wallet::utils::SecpCtx,
+    ) -> Result<(), SignerError> {
+        psbt.combine(self.client.sign_tx(psbt)?.psbt)
+            .expect("Failed to combine HW signed psbt with passed PSBT");
+        Ok(())
+    }
+}
diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs
new file mode 100644 (file)
index 0000000..61ec589
--- /dev/null
@@ -0,0 +1,2708 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Wallet
+//!
+//! This module defines the [`Wallet`].
+use crate::collections::{BTreeMap, HashMap};
+use alloc::{
+    boxed::Box,
+    string::{String, ToString},
+    sync::Arc,
+    vec::Vec,
+};
+pub use bdk_chain::keychain::Balance;
+use bdk_chain::{
+    indexed_tx_graph,
+    keychain::{self, KeychainTxOutIndex},
+    local_chain::{
+        self, ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain,
+    },
+    spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
+    tx_graph::{CanonicalTx, TxGraph},
+    Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
+    IndexedTxGraph,
+};
+use bdk_persist::{Persist, PersistBackend};
+use bitcoin::secp256k1::{All, Secp256k1};
+use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
+use bitcoin::{
+    absolute, psbt, Address, Block, FeeRate, Network, OutPoint, Script, ScriptBuf, Sequence,
+    Transaction, TxOut, Txid, Witness,
+};
+use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
+use bitcoin::{constants::genesis_block, Amount};
+use core::fmt;
+use core::ops::Deref;
+use descriptor::error::Error as DescriptorError;
+use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
+
+use bdk_chain::tx_graph::CalculateFeeError;
+
+pub mod coin_selection;
+pub mod export;
+pub mod signer;
+pub mod tx_builder;
+pub(crate) mod utils;
+
+pub mod error;
+
+pub use utils::IsDust;
+
+use coin_selection::DefaultCoinSelectionAlgorithm;
+use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
+use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
+use utils::{check_nsequence_rbf, After, Older, SecpCtx};
+
+use crate::descriptor::policy::BuildSatisfaction;
+use crate::descriptor::{
+    self, calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
+    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
+};
+use crate::psbt::PsbtUtils;
+use crate::signer::SignerError;
+use crate::types::*;
+use crate::wallet::coin_selection::Excess::{Change, NoChange};
+use crate::wallet::error::{BuildFeeBumpError, CreateTxError, MiniscriptPsbtError};
+
+const COINBASE_MATURITY: u32 = 100;
+
+/// A Bitcoin wallet
+///
+/// The `Wallet` acts as a way of coherently interfacing with output descriptors and related transactions.
+/// Its main components are:
+///
+/// 1. output *descriptors* from which it can derive addresses.
+/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
+///
+/// [`signer`]: crate::signer
+#[derive(Debug)]
+pub struct Wallet {
+    signers: Arc<SignersContainer>,
+    change_signers: Arc<SignersContainer>,
+    chain: LocalChain,
+    indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
+    persist: Persist<ChangeSet>,
+    network: Network,
+    secp: SecpCtx,
+}
+
+/// An update to [`Wallet`].
+///
+/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
+#[derive(Debug, Clone, Default)]
+pub struct Update {
+    /// Contains the last active derivation indices per keychain (`K`), which is used to update the
+    /// [`KeychainTxOutIndex`].
+    pub last_active_indices: BTreeMap<KeychainKind, u32>,
+
+    /// Update for the wallet's internal [`TxGraph`].
+    pub graph: TxGraph<ConfirmationTimeHeightAnchor>,
+
+    /// Update for the wallet's internal [`LocalChain`].
+    ///
+    /// [`LocalChain`]: local_chain::LocalChain
+    pub chain: Option<CheckPoint>,
+}
+
+impl From<FullScanResult<KeychainKind>> for Update {
+    fn from(value: FullScanResult<KeychainKind>) -> Self {
+        Self {
+            last_active_indices: value.last_active_indices,
+            graph: value.graph_update,
+            chain: Some(value.chain_update),
+        }
+    }
+}
+
+impl From<SyncResult> for Update {
+    fn from(value: SyncResult) -> Self {
+        Self {
+            last_active_indices: BTreeMap::new(),
+            graph: value.graph_update,
+            chain: Some(value.chain_update),
+        }
+    }
+}
+
+/// The changes made to a wallet by applying an [`Update`].
+#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
+pub struct ChangeSet {
+    /// Changes to the [`LocalChain`].
+    ///
+    /// [`LocalChain`]: local_chain::LocalChain
+    pub chain: local_chain::ChangeSet,
+
+    /// Changes to [`IndexedTxGraph`].
+    ///
+    /// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph
+    pub indexed_tx_graph: indexed_tx_graph::ChangeSet<
+        ConfirmationTimeHeightAnchor,
+        keychain::ChangeSet<KeychainKind>,
+    >,
+
+    /// Stores the network type of the wallet.
+    pub network: Option<Network>,
+}
+
+impl Append for ChangeSet {
+    fn append(&mut self, other: Self) {
+        Append::append(&mut self.chain, other.chain);
+        Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
+        if other.network.is_some() {
+            debug_assert!(
+                self.network.is_none() || self.network == other.network,
+                "network type must be consistent"
+            );
+            self.network = other.network;
+        }
+    }
+
+    fn is_empty(&self) -> bool {
+        self.chain.is_empty() && self.indexed_tx_graph.is_empty()
+    }
+}
+
+impl From<local_chain::ChangeSet> for ChangeSet {
+    fn from(chain: local_chain::ChangeSet) -> Self {
+        Self {
+            chain,
+            ..Default::default()
+        }
+    }
+}
+
+impl
+    From<
+        indexed_tx_graph::ChangeSet<
+            ConfirmationTimeHeightAnchor,
+            keychain::ChangeSet<KeychainKind>,
+        >,
+    > for ChangeSet
+{
+    fn from(
+        indexed_tx_graph: indexed_tx_graph::ChangeSet<
+            ConfirmationTimeHeightAnchor,
+            keychain::ChangeSet<KeychainKind>,
+        >,
+    ) -> Self {
+        Self {
+            indexed_tx_graph,
+            ..Default::default()
+        }
+    }
+}
+
+/// A derived address and the index it was found at.
+/// For convenience this automatically derefs to `Address`
+#[derive(Debug, PartialEq, Eq)]
+pub struct AddressInfo {
+    /// Child index of this address
+    pub index: u32,
+    /// Address
+    pub address: Address,
+    /// Type of keychain
+    pub keychain: KeychainKind,
+}
+
+impl Deref for AddressInfo {
+    type Target = Address;
+
+    fn deref(&self) -> &Self::Target {
+        &self.address
+    }
+}
+
+impl fmt::Display for AddressInfo {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.address)
+    }
+}
+
+impl Wallet {
+    /// Creates a wallet that does not persist data.
+    pub fn new_no_persist<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        network: Network,
+    ) -> Result<Self, DescriptorError> {
+        Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
+            NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
+            NewError::Descriptor(e) => e,
+            NewError::Persist(_) => unreachable!("mock-write must always succeed"),
+        })
+    }
+
+    /// Creates a wallet that does not persist data, with a custom genesis hash.
+    pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        network: Network,
+        genesis_hash: BlockHash,
+    ) -> Result<Self, crate::descriptor::DescriptorError> {
+        Self::new_with_genesis_hash(descriptor, change_descriptor, (), network, genesis_hash)
+            .map_err(|e| match e {
+                NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
+                NewError::Descriptor(e) => e,
+                NewError::Persist(_) => unreachable!("mock-write must always succeed"),
+            })
+    }
+}
+
+/// The error type when constructing a fresh [`Wallet`].
+///
+/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
+///
+/// [`new`]: Wallet::new
+/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
+#[derive(Debug)]
+pub enum NewError {
+    /// Database already has data.
+    NonEmptyDatabase,
+    /// There was problem with the passed-in descriptor(s).
+    Descriptor(crate::descriptor::DescriptorError),
+    /// We were unable to write the wallet's data to the persistence backend.
+    Persist(anyhow::Error),
+}
+
+impl fmt::Display for NewError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            NewError::NonEmptyDatabase => write!(
+                f,
+                "database already has data - use `load` or `new_or_load` methods instead"
+            ),
+            NewError::Descriptor(e) => e.fmt(f),
+            NewError::Persist(e) => e.fmt(f),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for NewError {}
+
+/// The error type when loading a [`Wallet`] from persistence.
+///
+/// Method [`load`] may return this error.
+///
+/// [`load`]: Wallet::load
+#[derive(Debug)]
+pub enum LoadError {
+    /// There was a problem with the passed-in descriptor(s).
+    Descriptor(crate::descriptor::DescriptorError),
+    /// Loading data from the persistence backend failed.
+    Persist(anyhow::Error),
+    /// Wallet not initialized, persistence backend is empty.
+    NotInitialized,
+    /// Data loaded from persistence is missing network type.
+    MissingNetwork,
+    /// Data loaded from persistence is missing genesis hash.
+    MissingGenesis,
+    /// Data loaded from persistence is missing descriptor.
+    MissingDescriptor,
+}
+
+impl fmt::Display for LoadError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            LoadError::Descriptor(e) => e.fmt(f),
+            LoadError::Persist(e) => e.fmt(f),
+            LoadError::NotInitialized => {
+                write!(f, "wallet is not initialized, persistence backend is empty")
+            }
+            LoadError::MissingNetwork => write!(f, "loaded data is missing network type"),
+            LoadError::MissingGenesis => write!(f, "loaded data is missing genesis hash"),
+            LoadError::MissingDescriptor => write!(f, "loaded data is missing descriptor"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for LoadError {}
+
+/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
+///
+/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error.
+///
+/// [`new_or_load`]: Wallet::new_or_load
+/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
+#[derive(Debug)]
+pub enum NewOrLoadError {
+    /// There is a problem with the passed-in descriptor.
+    Descriptor(crate::descriptor::DescriptorError),
+    /// Either writing to or loading from the persistence backend failed.
+    Persist(anyhow::Error),
+    /// Wallet is not initialized, persistence backend is empty.
+    NotInitialized,
+    /// The loaded genesis hash does not match what was provided.
+    LoadedGenesisDoesNotMatch {
+        /// The expected genesis block hash.
+        expected: BlockHash,
+        /// The block hash loaded from persistence.
+        got: Option<BlockHash>,
+    },
+    /// The loaded network type does not match what was provided.
+    LoadedNetworkDoesNotMatch {
+        /// The expected network type.
+        expected: Network,
+        /// The network type loaded from persistence.
+        got: Option<Network>,
+    },
+    /// The loaded desccriptor does not match what was provided.
+    LoadedDescriptorDoesNotMatch {
+        /// The descriptor loaded from persistence.
+        got: Option<ExtendedDescriptor>,
+        /// The keychain of the descriptor not matching
+        keychain: KeychainKind,
+    },
+}
+
+impl fmt::Display for NewOrLoadError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            NewOrLoadError::Descriptor(e) => e.fmt(f),
+            NewOrLoadError::Persist(e) => write!(
+                f,
+                "failed to either write to or load from persistence, {}",
+                e
+            ),
+            NewOrLoadError::NotInitialized => {
+                write!(f, "wallet is not initialized, persistence backend is empty")
+            }
+            NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
+                write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
+            }
+            NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
+                write!(f, "loaded network type is not {}, got {:?}", expected, got)
+            }
+            NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
+                write!(
+                    f,
+                    "loaded descriptor is different from what was provided, got {:?} for keychain {:?}",
+                    got, keychain
+                )
+            }
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for NewOrLoadError {}
+
+/// An error that may occur when inserting a transaction into [`Wallet`].
+#[derive(Debug)]
+pub enum InsertTxError {
+    /// The error variant that occurs when the caller attempts to insert a transaction with a
+    /// confirmation height that is greater than the internal chain tip.
+    ConfirmationHeightCannotBeGreaterThanTip {
+        /// The internal chain's tip height.
+        tip_height: u32,
+        /// The introduced transaction's confirmation height.
+        tx_height: u32,
+    },
+}
+
+impl fmt::Display for InsertTxError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
+                tip_height,
+                tx_height,
+            } => {
+                write!(f, "cannot insert tx with confirmation height ({}) higher than internal tip height ({})", tx_height, tip_height)
+            }
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InsertTxError {}
+
+/// An error that may occur when applying a block to [`Wallet`].
+#[derive(Debug)]
+pub enum ApplyBlockError {
+    /// Occurs when the update chain cannot connect with original chain.
+    CannotConnect(CannotConnectError),
+    /// Occurs when the `connected_to` hash does not match the hash derived from `block`.
+    UnexpectedConnectedToHash {
+        /// Block hash of `connected_to`.
+        connected_to_hash: BlockHash,
+        /// Expected block hash of `connected_to`, as derived from `block`.
+        expected_hash: BlockHash,
+    },
+}
+
+impl fmt::Display for ApplyBlockError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            ApplyBlockError::CannotConnect(err) => err.fmt(f),
+            ApplyBlockError::UnexpectedConnectedToHash {
+                expected_hash: block_hash,
+                connected_to_hash: checkpoint_hash,
+            } => write!(
+                f,
+                "`connected_to` hash {} differs from the expected hash {} (which is derived from `block`)",
+                checkpoint_hash, block_hash
+            ),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ApplyBlockError {}
+
+impl Wallet {
+    /// Initialize an empty [`Wallet`].
+    pub fn new<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+        network: Network,
+    ) -> Result<Self, NewError> {
+        let genesis_hash = genesis_block(network).block_hash();
+        Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash)
+    }
+
+    /// Initialize an empty [`Wallet`] with a custom genesis hash.
+    ///
+    /// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful
+    /// for syncing from alternative networks.
+    pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+        network: Network,
+        genesis_hash: BlockHash,
+    ) -> Result<Self, NewError> {
+        if let Ok(changeset) = db.load_from_persistence() {
+            if changeset.is_some() {
+                return Err(NewError::NonEmptyDatabase);
+            }
+        }
+        let secp = Secp256k1::new();
+        let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
+        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
+
+        let (signers, change_signers) =
+            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
+                .map_err(NewError::Descriptor)?;
+
+        let indexed_graph = IndexedTxGraph::new(index);
+
+        let mut persist = Persist::new(db);
+        persist.stage(ChangeSet {
+            chain: chain_changeset,
+            indexed_tx_graph: indexed_graph.initial_changeset(),
+            network: Some(network),
+        });
+        persist.commit().map_err(NewError::Persist)?;
+
+        Ok(Wallet {
+            signers,
+            change_signers,
+            network,
+            chain,
+            indexed_graph,
+            persist,
+            secp,
+        })
+    }
+
+    /// Load [`Wallet`] from the given persistence backend.
+    ///
+    /// Note that the descriptor secret keys are not persisted to the db; this means that after
+    /// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be
+    /// able to sign transactions.
+    ///
+    /// If you wish to use the wallet to sign transactions, you need to add the secret keys
+    /// manually to the [`Wallet`]:
+    ///
+    /// ```rust,no_run
+    /// # use bdk_wallet::Wallet;
+    /// # use bdk_wallet::signer::{SignersContainer, SignerOrdering};
+    /// # use bdk_wallet::descriptor::Descriptor;
+    /// # use bitcoin::key::Secp256k1;
+    /// # use bdk_wallet::KeychainKind;
+    /// # use bdk_file_store::Store;
+    /// #
+    /// # fn main() -> Result<(), anyhow::Error> {
+    /// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
+    /// # let file_path = temp_dir.path().join("store.db");
+    /// # let db: Store<bdk_wallet::wallet::ChangeSet> = Store::create_new(&[], &file_path).expect("must create db");
+    /// let secp = Secp256k1::new();
+    ///
+    /// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
+    /// let (internal_descriptor, internal_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)").unwrap();
+    ///
+    /// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
+    /// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
+    ///
+    /// let mut wallet = Wallet::load(db)?;
+    ///
+    /// external_signer_container.signers().into_iter()
+    ///     .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
+    /// internal_signer_container.signers().into_iter()
+    ///     .for_each(|s| wallet.add_signer(KeychainKind::Internal, SignerOrdering::default(), s.clone()));
+    /// # Ok(())
+    /// # }
+    /// ```
+    ///
+    /// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
+    /// passed-in descriptors to the [`Wallet`].
+    pub fn load(
+        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+    ) -> Result<Self, LoadError> {
+        let changeset = db
+            .load_from_persistence()
+            .map_err(LoadError::Persist)?
+            .ok_or(LoadError::NotInitialized)?;
+        Self::load_from_changeset(db, changeset)
+    }
+
+    fn load_from_changeset(
+        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+        changeset: ChangeSet,
+    ) -> Result<Self, LoadError> {
+        let secp = Secp256k1::new();
+        let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
+        let chain =
+            LocalChain::from_changeset(changeset.chain).map_err(|_| LoadError::MissingGenesis)?;
+        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
+        let descriptor = changeset
+            .indexed_tx_graph
+            .indexer
+            .keychains_added
+            .get(&KeychainKind::External)
+            .ok_or(LoadError::MissingDescriptor)?
+            .clone();
+        let change_descriptor = changeset
+            .indexed_tx_graph
+            .indexer
+            .keychains_added
+            .get(&KeychainKind::Internal)
+            .cloned();
+
+        let (signers, change_signers) =
+            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
+                .expect("Can't fail: we passed in valid descriptors, recovered from the changeset");
+
+        let mut indexed_graph = IndexedTxGraph::new(index);
+        indexed_graph.apply_changeset(changeset.indexed_tx_graph);
+
+        let persist = Persist::new(db);
+
+        Ok(Wallet {
+            signers,
+            change_signers,
+            chain,
+            indexed_graph,
+            persist,
+            network,
+            secp,
+        })
+    }
+
+    /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist.
+    ///
+    /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
+    pub fn new_or_load<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+        network: Network,
+    ) -> Result<Self, NewOrLoadError> {
+        let genesis_hash = genesis_block(network).block_hash();
+        Self::new_or_load_with_genesis_hash(
+            descriptor,
+            change_descriptor,
+            db,
+            network,
+            genesis_hash,
+        )
+    }
+
+    /// Either loads [`Wallet`] from persistence, or initializes it if it does not exist, using the
+    /// provided descriptor, change descriptor, network, and custom genesis hash.
+    ///
+    /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
+    /// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
+    /// useful for syncing from alternative networks.
+    pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
+        descriptor: E,
+        change_descriptor: Option<E>,
+        mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
+        network: Network,
+        genesis_hash: BlockHash,
+    ) -> Result<Self, NewOrLoadError> {
+        let changeset = db
+            .load_from_persistence()
+            .map_err(NewOrLoadError::Persist)?;
+        match changeset {
+            Some(changeset) => {
+                let mut wallet = Self::load_from_changeset(db, changeset).map_err(|e| match e {
+                    LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
+                    LoadError::Persist(e) => NewOrLoadError::Persist(e),
+                    LoadError::NotInitialized => NewOrLoadError::NotInitialized,
+                    LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
+                        expected: network,
+                        got: None,
+                    },
+                    LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
+                        expected: genesis_hash,
+                        got: None,
+                    },
+                    LoadError::MissingDescriptor => NewOrLoadError::LoadedDescriptorDoesNotMatch {
+                        got: None,
+                        keychain: KeychainKind::External,
+                    },
+                })?;
+                if wallet.network != network {
+                    return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
+                        expected: network,
+                        got: Some(wallet.network),
+                    });
+                }
+                if wallet.chain.genesis_hash() != genesis_hash {
+                    return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
+                        expected: genesis_hash,
+                        got: Some(wallet.chain.genesis_hash()),
+                    });
+                }
+
+                let (expected_descriptor, expected_descriptor_keymap) = descriptor
+                    .into_wallet_descriptor(&wallet.secp, network)
+                    .map_err(NewOrLoadError::Descriptor)?;
+                let wallet_descriptor = wallet.public_descriptor(KeychainKind::External).cloned();
+                if wallet_descriptor != Some(expected_descriptor.clone()) {
+                    return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
+                        got: wallet_descriptor,
+                        keychain: KeychainKind::External,
+                    });
+                }
+                // if expected descriptor has private keys add them as new signers
+                if !expected_descriptor_keymap.is_empty() {
+                    let signer_container = SignersContainer::build(
+                        expected_descriptor_keymap,
+                        &expected_descriptor,
+                        &wallet.secp,
+                    );
+                    signer_container.signers().into_iter().for_each(|signer| {
+                        wallet.add_signer(
+                            KeychainKind::External,
+                            SignerOrdering::default(),
+                            signer.clone(),
+                        )
+                    });
+                }
+
+                let expected_change_descriptor = if let Some(c) = change_descriptor {
+                    Some(
+                        c.into_wallet_descriptor(&wallet.secp, network)
+                            .map_err(NewOrLoadError::Descriptor)?,
+                    )
+                } else {
+                    None
+                };
+                let wallet_change_descriptor =
+                    wallet.public_descriptor(KeychainKind::Internal).cloned();
+
+                match (expected_change_descriptor, wallet_change_descriptor) {
+                    (Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor))
+                        if wallet_descriptor == expected_descriptor =>
+                    {
+                        // if expected change descriptor has private keys add them as new signers
+                        if !expected_keymap.is_empty() {
+                            let signer_container = SignersContainer::build(
+                                expected_keymap,
+                                &expected_descriptor,
+                                &wallet.secp,
+                            );
+                            signer_container.signers().into_iter().for_each(|signer| {
+                                wallet.add_signer(
+                                    KeychainKind::Internal,
+                                    SignerOrdering::default(),
+                                    signer.clone(),
+                                )
+                            });
+                        }
+                    }
+                    (None, None) => (),
+                    (_, wallet_descriptor) => {
+                        return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
+                            got: wallet_descriptor,
+                            keychain: KeychainKind::Internal,
+                        });
+                    }
+                }
+
+                Ok(wallet)
+            }
+            None => Self::new_with_genesis_hash(
+                descriptor,
+                change_descriptor,
+                db,
+                network,
+                genesis_hash,
+            )
+            .map_err(|e| match e {
+                NewError::NonEmptyDatabase => {
+                    unreachable!("database is already checked to have no data")
+                }
+                NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
+                NewError::Persist(e) => NewOrLoadError::Persist(e),
+            }),
+        }
+    }
+
+    /// Get the Bitcoin network the wallet is using.
+    pub fn network(&self) -> Network {
+        self.network
+    }
+
+    /// Iterator over all keychains in this wallet
+    pub fn keychains(&self) -> impl Iterator<Item = (&KeychainKind, &ExtendedDescriptor)> {
+        self.indexed_graph.index.keychains()
+    }
+
+    /// Peek an address of the given `keychain` at `index` without revealing it.
+    ///
+    /// For non-wildcard descriptors this returns the same address at every provided index.
+    ///
+    /// # Panics
+    ///
+    /// This panics when the caller requests for an address of derivation index greater than the
+    /// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
+    pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
+        let keychain = self.map_keychain(keychain);
+        let mut spk_iter = self
+            .indexed_graph
+            .index
+            .unbounded_spk_iter(&keychain)
+            .expect("Must exist (we called map_keychain)");
+        if !spk_iter.descriptor().has_wildcard() {
+            index = 0;
+        }
+        let (index, spk) = spk_iter
+            .nth(index as usize)
+            .expect("derivation index is out of bounds");
+
+        AddressInfo {
+            index,
+            address: Address::from_script(&spk, self.network).expect("must have address form"),
+            keychain,
+        }
+    }
+
+    /// Attempt to reveal the next address of the given `keychain`.
+    ///
+    /// This will increment the internal derivation index. If the keychain's descriptor doesn't
+    /// contain a wildcard or every address is already revealed up to the maximum derivation
+    /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
+    /// then returns the last revealed address.
+    ///
+    /// # Errors
+    ///
+    /// If writing to persistent storage fails.
+    pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
+        let keychain = self.map_keychain(keychain);
+        let ((index, spk), index_changeset) = self
+            .indexed_graph
+            .index
+            .reveal_next_spk(&keychain)
+            .expect("Must exist (we called map_keychain)");
+
+        self.persist
+            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+
+        Ok(AddressInfo {
+            index,
+            address: Address::from_script(spk, self.network).expect("must have address form"),
+            keychain,
+        })
+    }
+
+    /// Reveal addresses up to and including the target `index` and return an iterator
+    /// of newly revealed addresses.
+    ///
+    /// If the target `index` is unreachable, we make a best effort to reveal up to the last
+    /// possible index. If all addresses up to the given `index` are already revealed, then
+    /// no new addresses are returned.
+    ///
+    /// # Errors
+    ///
+    /// If writing to persistent storage fails.
+    pub fn reveal_addresses_to(
+        &mut self,
+        keychain: KeychainKind,
+        index: u32,
+    ) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
+        let keychain = self.map_keychain(keychain);
+        let (spk_iter, index_changeset) = self
+            .indexed_graph
+            .index
+            .reveal_to_target(&keychain, index)
+            .expect("must exist (we called map_keychain)");
+
+        self.persist
+            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+
+        Ok(spk_iter.map(move |(index, spk)| AddressInfo {
+            index,
+            address: Address::from_script(&spk, self.network).expect("must have address form"),
+            keychain,
+        }))
+    }
+
+    /// Get the next unused address for the given `keychain`, i.e. the address with the lowest
+    /// derivation index that hasn't been used.
+    ///
+    /// This will attempt to derive and reveal a new address if no newly revealed addresses
+    /// are available. See also [`reveal_next_address`](Self::reveal_next_address).
+    ///
+    /// # Errors
+    ///
+    /// If writing to persistent storage fails.
+    pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
+        let keychain = self.map_keychain(keychain);
+        let ((index, spk), index_changeset) = self
+            .indexed_graph
+            .index
+            .next_unused_spk(&keychain)
+            .expect("must exist (we called map_keychain)");
+
+        self.persist
+            .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
+
+        Ok(AddressInfo {
+            index,
+            address: Address::from_script(spk, self.network).expect("must have address form"),
+            keychain,
+        })
+    }
+
+    /// Marks an address used of the given `keychain` at `index`.
+    ///
+    /// Returns whether the given index was present and then removed from the unused set.
+    pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
+        self.indexed_graph.index.mark_used(keychain, index)
+    }
+
+    /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted
+    /// back into the unused set.
+    ///
+    /// Since this is only a superficial marker, it will have no effect if the address at the given
+    /// `index` was actually used, i.e. the wallet has previously indexed a tx output for the
+    /// derived spk.
+    ///
+    /// [`mark_used`]: Self::mark_used
+    pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
+        self.indexed_graph.index.unmark_used(keychain, index)
+    }
+
+    /// List addresses that are revealed but unused.
+    ///
+    /// Note if the returned iterator is empty you can reveal more addresses
+    /// by using [`reveal_next_address`](Self::reveal_next_address) or
+    /// [`reveal_addresses_to`](Self::reveal_addresses_to).
+    pub fn list_unused_addresses(
+        &self,
+        keychain: KeychainKind,
+    ) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
+        let keychain = self.map_keychain(keychain);
+        self.indexed_graph
+            .index
+            .unused_keychain_spks(&keychain)
+            .map(move |(index, spk)| AddressInfo {
+                index,
+                address: Address::from_script(spk, self.network).expect("must have address form"),
+                keychain,
+            })
+    }
+
+    /// Return whether or not a `script` is part of this wallet (either internal or external)
+    pub fn is_mine(&self, script: &Script) -> bool {
+        self.indexed_graph.index.index_of_spk(script).is_some()
+    }
+
+    /// Finds how the wallet derived the script pubkey `spk`.
+    ///
+    /// Will only return `Some(_)` if the wallet has given out the spk.
+    pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
+        self.indexed_graph.index.index_of_spk(spk)
+    }
+
+    /// Return the list of unspent outputs of this wallet
+    pub fn list_unspent(&self) -> impl Iterator<Item = LocalOutput> + '_ {
+        self.indexed_graph
+            .graph()
+            .filter_chain_unspents(
+                &self.chain,
+                self.chain.tip().block_id(),
+                self.indexed_graph.index.outpoints(),
+            )
+            .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
+    }
+
+    /// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed).
+    ///
+    /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead.
+    pub fn list_output(&self) -> impl Iterator<Item = LocalOutput> + '_ {
+        self.indexed_graph
+            .graph()
+            .filter_chain_txouts(
+                &self.chain,
+                self.chain.tip().block_id(),
+                self.indexed_graph.index.outpoints(),
+            )
+            .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
+    }
+
+    /// Get all the checkpoints the wallet is currently storing indexed by height.
+    pub fn checkpoints(&self) -> CheckPointIter {
+        self.chain.iter_checkpoints()
+    }
+
+    /// Returns the latest checkpoint.
+    pub fn latest_checkpoint(&self) -> CheckPoint {
+        self.chain.tip()
+    }
+
+    /// Get unbounded script pubkey iterators for both `Internal` and `External` keychains.
+    ///
+    /// This is intended to be used when doing a full scan of your addresses (e.g. after restoring
+    /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
+    /// electrum server) which will go through each address until it reaches a *stop gap*.
+    ///
+    /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
+    /// script pubkeys the wallet is storing internally).
+    pub fn all_unbounded_spk_iters(
+        &self,
+    ) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
+        self.indexed_graph.index.all_unbounded_spk_iters()
+    }
+
+    /// Get an unbounded script pubkey iterator for the given `keychain`.
+    ///
+    /// See [`all_unbounded_spk_iters`] for more documentation
+    ///
+    /// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
+    pub fn unbounded_spk_iter(
+        &self,
+        keychain: KeychainKind,
+    ) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
+        let keychain = self.map_keychain(keychain);
+        self.indexed_graph
+            .index
+            .unbounded_spk_iter(&keychain)
+            .expect("Must exist (we called map_keychain)")
+    }
+
+    /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
+    /// wallet's database.
+    pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
+        let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
+        self.indexed_graph
+            .graph()
+            .filter_chain_unspents(
+                &self.chain,
+                self.chain.tip().block_id(),
+                core::iter::once(((), op)),
+            )
+            .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo))
+            .next()
+    }
+
+    /// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph.
+    ///
+    /// This is used for providing a previous output's value so that we can use [`calculate_fee`]
+    /// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will
+    /// not be returned in [`list_unspent`] or [`list_output`].
+    ///
+    /// Any inserted `TxOut`s are not persisted until [`commit`] is called.
+    ///
+    /// **WARNING:** This should only be used to add `TxOut`s that the wallet does not own. Only
+    /// insert `TxOut`s that you trust the values for!
+    ///
+    /// [`calculate_fee`]: Self::calculate_fee
+    /// [`calculate_fee_rate`]: Self::calculate_fee_rate
+    /// [`list_unspent`]: Self::list_unspent
+    /// [`list_output`]: Self::list_output
+    /// [`commit`]: Self::commit
+    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
+        let additions = self.indexed_graph.insert_txout(outpoint, txout);
+        self.persist.stage(ChangeSet::from(additions));
+    }
+
+    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
+    ///
+    /// To calculate the fee for a [`Transaction`] with inputs not owned by this wallet you must
+    /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function.
+    ///
+    /// Note `tx` does not have to be in the graph for this to work.
+    ///
+    /// # Examples
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Txid;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let txid:Txid = todo!();
+    /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
+    /// let fee = wallet.calculate_fee(&tx).expect("fee");
+    /// ```
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Psbt;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let mut psbt: Psbt = todo!();
+    /// let tx = &psbt.clone().extract_tx().expect("tx");
+    /// let fee = wallet.calculate_fee(tx).expect("fee");
+    /// ```
+    /// [`insert_txout`]: Self::insert_txout
+    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
+        self.indexed_graph.graph().calculate_fee(tx)
+    }
+
+    /// Calculate the [`FeeRate`] for a given transaction.
+    ///
+    /// To calculate the fee rate for a [`Transaction`] with inputs not owned by this wallet you must
+    /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function.
+    ///
+    /// Note `tx` does not have to be in the graph for this to work.
+    ///
+    /// # Examples
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Txid;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let txid:Txid = todo!();
+    /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
+    /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate");
+    /// ```
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Psbt;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let mut psbt: Psbt = todo!();
+    /// let tx = &psbt.clone().extract_tx().expect("tx");
+    /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate");
+    /// ```
+    /// [`insert_txout`]: Self::insert_txout
+    pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<FeeRate, CalculateFeeError> {
+        self.calculate_fee(tx)
+            .map(|fee| Amount::from_sat(fee) / tx.weight())
+    }
+
+    /// Compute the `tx`'s sent and received [`Amount`]s.
+    ///
+    /// This method returns a tuple `(sent, received)`. Sent is the sum of the txin amounts
+    /// that spend from previous txouts tracked by this wallet. Received is the summation
+    /// of this tx's outputs that send to script pubkeys tracked by this wallet.
+    ///
+    /// # Examples
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Txid;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let txid:Txid = todo!();
+    /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx;
+    /// let (sent, received) = wallet.sent_and_received(&tx);
+    /// ```
+    ///
+    /// ```rust, no_run
+    /// # use bitcoin::Psbt;
+    /// # use bdk_wallet::Wallet;
+    /// # let mut wallet: Wallet = todo!();
+    /// # let mut psbt: Psbt = todo!();
+    /// let tx = &psbt.clone().extract_tx().expect("tx");
+    /// let (sent, received) = wallet.sent_and_received(tx);
+    /// ```
+    pub fn sent_and_received(&self, tx: &Transaction) -> (Amount, Amount) {
+        self.indexed_graph.index.sent_and_received(tx, ..)
+    }
+
+    /// Get a single transaction from the wallet as a [`CanonicalTx`] (if the transaction exists).
+    ///
+    /// `CanonicalTx` contains the full transaction alongside meta-data such as:
+    /// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that exist
+    ///   in the best chain.
+    /// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is
+    ///   confirmed or unconfirmed. If the transaction is confirmed, the anchor which proves the
+    ///   confirmation is provided. If the transaction is unconfirmed, the unix timestamp of when
+    ///   the transaction was last seen in the mempool is provided.
+    ///
+    /// ```rust, no_run
+    /// use bdk_chain::Anchor;
+    /// use bdk_wallet::{chain::ChainPosition, Wallet};
+    /// # let wallet: Wallet = todo!();
+    /// # let my_txid: bitcoin::Txid = todo!();
+    ///
+    /// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist");
+    ///
+    /// // get reference to full transaction
+    /// println!("my tx: {:#?}", canonical_tx.tx_node.tx);
+    ///
+    /// // list all transaction anchors
+    /// for anchor in canonical_tx.tx_node.anchors {
+    ///     println!(
+    ///         "tx is anchored by block of hash {}",
+    ///         anchor.anchor_block().hash
+    ///     );
+    /// }
+    ///
+    /// // get confirmation status of transaction
+    /// match canonical_tx.chain_position {
+    ///     ChainPosition::Confirmed(anchor) => println!(
+    ///         "tx is confirmed at height {}, we know this since {}:{} is in the best chain",
+    ///         anchor.confirmation_height, anchor.anchor_block.height, anchor.anchor_block.hash,
+    ///     ),
+    ///     ChainPosition::Unconfirmed(last_seen) => println!(
+    ///         "tx is last seen at {}, it is unconfirmed as it is not anchored in the best chain",
+    ///         last_seen,
+    ///     ),
+    /// }
+    /// ```
+    ///
+    /// [`Anchor`]: bdk_chain::Anchor
+    pub fn get_tx(
+        &self,
+        txid: Txid,
+    ) -> Option<CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> {
+        let graph = self.indexed_graph.graph();
+
+        Some(CanonicalTx {
+            chain_position: graph.get_chain_position(
+                &self.chain,
+                self.chain.tip().block_id(),
+                txid,
+            )?,
+            tx_node: graph.get_tx_node(txid)?,
+        })
+    }
+
+    /// Add a new checkpoint to the wallet's internal view of the chain.
+    /// This stages but does not [`commit`] the change.
+    ///
+    /// Returns whether anything changed with the insertion (e.g. `false` if checkpoint was already
+    /// there).
+    ///
+    /// [`commit`]: Self::commit
+    pub fn insert_checkpoint(
+        &mut self,
+        block_id: BlockId,
+    ) -> Result<bool, local_chain::AlterCheckPointError> {
+        let changeset = self.chain.insert_block(block_id)?;
+        let changed = !changeset.is_empty();
+        self.persist.stage(changeset.into());
+        Ok(changed)
+    }
+
+    /// Add a transaction to the wallet's internal view of the chain. This stages but does not
+    /// [`commit`] the change.
+    ///
+    /// Returns whether anything changed with the transaction insertion (e.g. `false` if the
+    /// transaction was already inserted at the same position).
+    ///
+    /// A `tx` can be rejected if `position` has a height greater than the [`latest_checkpoint`].
+    /// Therefore you should use [`insert_checkpoint`] to insert new checkpoints before manually
+    /// inserting new transactions.
+    ///
+    /// **WARNING:** If `position` is confirmed, we anchor the `tx` to a the lowest checkpoint that
+    /// is >= the `position`'s height. The caller is responsible for ensuring the `tx` exists in our
+    /// local view of the best chain's history.
+    ///
+    /// [`commit`]: Self::commit
+    /// [`latest_checkpoint`]: Self::latest_checkpoint
+    /// [`insert_checkpoint`]: Self::insert_checkpoint
+    pub fn insert_tx(
+        &mut self,
+        tx: Transaction,
+        position: ConfirmationTime,
+    ) -> Result<bool, InsertTxError> {
+        let (anchor, last_seen) = match position {
+            ConfirmationTime::Confirmed { height, time } => {
+                // anchor tx to checkpoint with lowest height that is >= position's height
+                let anchor = self
+                    .chain
+                    .range(height..)
+                    .last()
+                    .ok_or(InsertTxError::ConfirmationHeightCannotBeGreaterThanTip {
+                        tip_height: self.chain.tip().height(),
+                        tx_height: height,
+                    })
+                    .map(|anchor_cp| ConfirmationTimeHeightAnchor {
+                        anchor_block: anchor_cp.block_id(),
+                        confirmation_height: height,
+                        confirmation_time: time,
+                    })?;
+
+                (Some(anchor), None)
+            }
+            ConfirmationTime::Unconfirmed { last_seen } => (None, Some(last_seen)),
+        };
+
+        let mut changeset = ChangeSet::default();
+        let txid = tx.txid();
+        changeset.append(self.indexed_graph.insert_tx(tx).into());
+        if let Some(anchor) = anchor {
+            changeset.append(self.indexed_graph.insert_anchor(txid, anchor).into());
+        }
+        if let Some(last_seen) = last_seen {
+            changeset.append(self.indexed_graph.insert_seen_at(txid, last_seen).into());
+        }
+
+        let changed = !changeset.is_empty();
+        self.persist.stage(changeset);
+        Ok(changed)
+    }
+
+    /// Iterate over the transactions in the wallet.
+    pub fn transactions(
+        &self,
+    ) -> impl Iterator<Item = CanonicalTx<'_, Arc<Transaction>, ConfirmationTimeHeightAnchor>> + '_
+    {
+        self.indexed_graph
+            .graph()
+            .list_chain_txs(&self.chain, self.chain.tip().block_id())
+    }
+
+    /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature
+    /// values.
+    pub fn get_balance(&self) -> Balance {
+        self.indexed_graph.graph().balance(
+            &self.chain,
+            self.chain.tip().block_id(),
+            self.indexed_graph.index.outpoints(),
+            |&(k, _), _| k == KeychainKind::Internal,
+        )
+    }
+
+    /// Add an external signer
+    ///
+    /// See [the `signer` module](signer) for an example.
+    pub fn add_signer(
+        &mut self,
+        keychain: KeychainKind,
+        ordering: SignerOrdering,
+        signer: Arc<dyn TransactionSigner>,
+    ) {
+        let signers = match keychain {
+            KeychainKind::External => Arc::make_mut(&mut self.signers),
+            KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
+        };
+
+        signers.add_external(signer.id(&self.secp), ordering, signer);
+    }
+
+    /// Get the signers
+    ///
+    /// ## Example
+    ///
+    /// ```
+    /// # use bdk_wallet::{Wallet, KeychainKind};
+    /// # use bdk_wallet::bitcoin::Network;
+    /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?;
+    /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
+    ///     // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
+    ///     println!("secret_key: {}", secret_key);
+    /// }
+    ///
+    /// Ok::<(), Box<dyn std::error::Error>>(())
+    /// ```
+    pub fn get_signers(&self, keychain: KeychainKind) -> Arc<SignersContainer> {
+        match keychain {
+            KeychainKind::External => Arc::clone(&self.signers),
+            KeychainKind::Internal => Arc::clone(&self.change_signers),
+        }
+    }
+
+    /// Start building a transaction.
+    ///
+    /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the transaction.
+    ///
+    /// ## Example
+    ///
+    /// ```
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use bdk_wallet::*;
+    /// # use bdk_wallet::wallet::ChangeSet;
+    /// # use bdk_wallet::wallet::error::CreateTxError;
+    /// # use bdk_persist::PersistBackend;
+    /// # use anyhow::Error;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let mut wallet = doctest_wallet!();
+    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
+    /// let psbt = {
+    ///    let mut builder =  wallet.build_tx();
+    ///    builder
+    ///        .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
+    ///    builder.finish()?
+    /// };
+    ///
+    /// // sign and broadcast ...
+    /// # Ok::<(), anyhow::Error>(())
+    /// ```
+    ///
+    /// [`TxBuilder`]: crate::TxBuilder
+    pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm, CreateTx> {
+        TxBuilder {
+            wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
+            params: TxParams::default(),
+            coin_selection: DefaultCoinSelectionAlgorithm::default(),
+            phantom: core::marker::PhantomData,
+        }
+    }
+
+    pub(crate) fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
+        &mut self,
+        coin_selection: Cs,
+        params: TxParams,
+    ) -> Result<Psbt, CreateTxError> {
+        let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect();
+        let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist");
+        let internal_descriptor = keychains.get(&KeychainKind::Internal);
+
+        let external_policy = external_descriptor
+            .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
+            .unwrap();
+        let internal_policy = internal_descriptor
+            .as_ref()
+            .map(|desc| {
+                Ok::<_, CreateTxError>(
+                    desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
+                        .unwrap(),
+                )
+            })
+            .transpose()?;
+
+        // The policy allows spending external outputs, but it requires a policy path that hasn't been
+        // provided
+        if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange
+            && external_policy.requires_path()
+            && params.external_policy_path.is_none()
+        {
+            return Err(CreateTxError::SpendingPolicyRequired(
+                KeychainKind::External,
+            ));
+        };
+        // Same for the internal_policy path, if present
+        if let Some(internal_policy) = &internal_policy {
+            if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
+                && internal_policy.requires_path()
+                && params.internal_policy_path.is_none()
+            {
+                return Err(CreateTxError::SpendingPolicyRequired(
+                    KeychainKind::Internal,
+                ));
+            };
+        }
+
+        let external_requirements = external_policy.get_condition(
+            params
+                .external_policy_path
+                .as_ref()
+                .unwrap_or(&BTreeMap::new()),
+        )?;
+        let internal_requirements = internal_policy
+            .map(|policy| {
+                Ok::<_, CreateTxError>(
+                    policy.get_condition(
+                        params
+                            .internal_policy_path
+                            .as_ref()
+                            .unwrap_or(&BTreeMap::new()),
+                    )?,
+                )
+            })
+            .transpose()?;
+
+        let requirements =
+            external_requirements.merge(&internal_requirements.unwrap_or_default())?;
+
+        let version = match params.version {
+            Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0),
+            Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
+                return Err(CreateTxError::Version1Csv)
+            }
+            Some(tx_builder::Version(x)) => x,
+            None if requirements.csv.is_some() => 2,
+            None => 1,
+        };
+
+        // We use a match here instead of a unwrap_or_else as it's way more readable :)
+        let current_height = match params.current_height {
+            // If they didn't tell us the current height, we assume it's the latest sync height.
+            None => {
+                let tip_height = self.chain.tip().height();
+                absolute::LockTime::from_height(tip_height).expect("invalid height")
+            }
+            Some(h) => h,
+        };
+
+        let lock_time = match params.locktime {
+            // When no nLockTime is specified, we try to prevent fee sniping, if possible
+            None => {
+                // Fee sniping can be partially prevented by setting the timelock
+                // to current_height. If we don't know the current_height,
+                // we default to 0.
+                let fee_sniping_height = current_height;
+
+                // We choose the biggest between the required nlocktime and the fee sniping
+                // height
+                match requirements.timelock {
+                    // No requirement, just use the fee_sniping_height
+                    None => fee_sniping_height,
+                    // There's a block-based requirement, but the value is lower than the fee_sniping_height
+                    Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => {
+                        fee_sniping_height
+                    }
+                    // There's a time-based requirement or a block-based requirement greater
+                    // than the fee_sniping_height use that value
+                    Some(value) => value,
+                }
+            }
+            // Specific nLockTime required and we have no constraints, so just set to that value
+            Some(x) if requirements.timelock.is_none() => x,
+            // Specific nLockTime required and it's compatible with the constraints
+            Some(x)
+                if requirements.timelock.unwrap().is_same_unit(x)
+                    && x >= requirements.timelock.unwrap() =>
+            {
+                x
+            }
+            // Invalid nLockTime required
+            Some(x) => {
+                return Err(CreateTxError::LockTime {
+                    requested: x,
+                    required: requirements.timelock.unwrap(),
+                })
+            }
+        };
+
+        // The nSequence to be by default for inputs unless an explicit sequence is specified.
+        let n_sequence = match (params.rbf, requirements.csv) {
+            // No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
+            (None, None) if lock_time != absolute::LockTime::ZERO => {
+                Sequence::ENABLE_LOCKTIME_NO_RBF
+            }
+            // No RBF, CSV or nLockTime, make the transaction final
+            (None, None) => Sequence::MAX,
+
+            // No RBF requested, use the value from CSV. Note that this value is by definition
+            // non-final, so even if a timelock is enabled this nSequence is fine, hence why we
+            // don't bother checking for it here. The same is true for all the other branches below
+            (None, Some(csv)) => csv,
+
+            // RBF with a specific value but that value is too high
+            (Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
+                return Err(CreateTxError::RbfSequence)
+            }
+            // RBF with a specific value requested, but the value is incompatible with CSV
+            (Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
+                if !check_nsequence_rbf(rbf, csv) =>
+            {
+                return Err(CreateTxError::RbfSequenceCsv { rbf, csv })
+            }
+
+            // RBF enabled with the default value with CSV also enabled. CSV takes precedence
+            (Some(tx_builder::RbfValue::Default), Some(csv)) => csv,
+            // Valid RBF, either default or with a specific value. We ignore the `CSV` value
+            // because we've already checked it before
+            (Some(rbf), _) => rbf.get_value(),
+        };
+
+        let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() {
+            //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256
+            FeePolicy::FeeAmount(fee) => {
+                if let Some(previous_fee) = params.bumping_fee {
+                    if fee < previous_fee.absolute {
+                        return Err(CreateTxError::FeeTooLow {
+                            required: previous_fee.absolute,
+                        });
+                    }
+                }
+                (FeeRate::ZERO, fee)
+            }
+            FeePolicy::FeeRate(rate) => {
+                if let Some(previous_fee) = params.bumping_fee {
+                    let required_feerate = FeeRate::from_sat_per_kwu(
+                        previous_fee.rate.to_sat_per_kwu()
+                            + FeeRate::BROADCAST_MIN.to_sat_per_kwu(), // +1 sat/vb
+                    );
+                    if rate < required_feerate {
+                        return Err(CreateTxError::FeeRateTooLow {
+                            required: required_feerate,
+                        });
+                    }
+                }
+                (rate, 0)
+            }
+        };
+
+        let mut tx = Transaction {
+            version: transaction::Version::non_standard(version),
+            lock_time,
+            input: vec![],
+            output: vec![],
+        };
+
+        if params.manually_selected_only && params.utxos.is_empty() {
+            return Err(CreateTxError::NoUtxosSelected);
+        }
+
+        // we keep it as a float while we accumulate it, and only round it at the end
+        let mut outgoing: u64 = 0;
+        let mut received: u64 = 0;
+
+        let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
+
+        for (index, (script_pubkey, value)) in recipients.enumerate() {
+            if !params.allow_dust
+                && value.is_dust(script_pubkey)
+                && !script_pubkey.is_provably_unspendable()
+            {
+                return Err(CreateTxError::OutputBelowDustLimit(index));
+            }
+
+            if self.is_mine(script_pubkey) {
+                received += value;
+            }
+
+            let new_out = TxOut {
+                script_pubkey: script_pubkey.clone(),
+                value: Amount::from_sat(value),
+            };
+
+            tx.output.push(new_out);
+
+            outgoing += value;
+        }
+
+        fee_amount += (fee_rate * tx.weight()).to_sat();
+
+        if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
+            && internal_descriptor.is_none()
+        {
+            return Err(CreateTxError::ChangePolicyDescriptor);
+        }
+
+        let (required_utxos, optional_utxos) =
+            self.preselect_utxos(&params, Some(current_height.to_consensus_u32()));
+
+        // get drain script
+        let drain_script = match params.drain_to {
+            Some(ref drain_recipient) => drain_recipient.clone(),
+            None => {
+                let change_keychain = self.map_keychain(KeychainKind::Internal);
+                let ((index, spk), index_changeset) = self
+                    .indexed_graph
+                    .index
+                    .next_unused_spk(&change_keychain)
+                    .expect("Keychain exists (we called map_keychain)");
+                let spk = spk.into();
+                self.indexed_graph.index.mark_used(change_keychain, index);
+                self.persist
+                    .stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
+                        index_changeset,
+                    )));
+                self.persist.commit().map_err(CreateTxError::Persist)?;
+                spk
+            }
+        };
+
+        let (required_utxos, optional_utxos) =
+            coin_selection::filter_duplicates(required_utxos, optional_utxos);
+
+        let coin_selection = coin_selection.coin_select(
+            required_utxos,
+            optional_utxos,
+            fee_rate,
+            outgoing + fee_amount,
+            &drain_script,
+        )?;
+        fee_amount += coin_selection.fee_amount;
+        let excess = &coin_selection.excess;
+
+        tx.input = coin_selection
+            .selected
+            .iter()
+            .map(|u| bitcoin::TxIn {
+                previous_output: u.outpoint(),
+                script_sig: ScriptBuf::default(),
+                sequence: u.sequence().unwrap_or(n_sequence),
+                witness: Witness::new(),
+            })
+            .collect();
+
+        if tx.output.is_empty() {
+            // Uh oh, our transaction has no outputs.
+            // We allow this when:
+            // - We have a drain_to address and the utxos we must spend (this happens,
+            // for example, when we RBF)
+            // - We have a drain_to address and drain_wallet set
+            // Otherwise, we don't know who we should send the funds to, and how much
+            // we should send!
+            if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) {
+                if let NoChange {
+                    dust_threshold,
+                    remaining_amount,
+                    change_fee,
+                } = excess
+                {
+                    return Err(CreateTxError::InsufficientFunds {
+                        needed: *dust_threshold,
+                        available: remaining_amount.saturating_sub(*change_fee),
+                    });
+                }
+            } else {
+                return Err(CreateTxError::NoRecipients);
+            }
+        }
+
+        match excess {
+            NoChange {
+                remaining_amount, ..
+            } => fee_amount += remaining_amount,
+            Change { amount, fee } => {
+                if self.is_mine(&drain_script) {
+                    received += amount;
+                }
+                fee_amount += fee;
+
+                // create drain output
+                let drain_output = TxOut {
+                    value: Amount::from_sat(*amount),
+                    script_pubkey: drain_script,
+                };
+
+                // TODO: We should pay attention when adding a new output: this might increase
+                // the length of the "number of vouts" parameter by 2 bytes, potentially making
+                // our feerate too low
+                tx.output.push(drain_output);
+            }
+        };
+
+        // sort input/outputs according to the chosen algorithm
+        params.ordering.sort_tx(&mut tx);
+
+        let psbt = self.complete_transaction(tx, coin_selection.selected, params)?;
+        Ok(psbt)
+    }
+
+    /// Bump the fee of a transaction previously created with this wallet.
+    ///
+    /// Returns an error if the transaction is already confirmed or doesn't explicitly signal
+    /// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`]
+    /// pre-populated with the inputs and outputs of the original transaction.
+    ///
+    /// ## Example
+    ///
+    /// ```no_run
+    /// # // TODO: remove norun -- bumping fee seems to need the tx in the wallet database first.
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use bdk_wallet::*;
+    /// # use bdk_wallet::wallet::ChangeSet;
+    /// # use bdk_wallet::wallet::error::CreateTxError;
+    /// # use bdk_persist::PersistBackend;
+    /// # use anyhow::Error;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let mut wallet = doctest_wallet!();
+    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
+    /// let mut psbt = {
+    ///     let mut builder = wallet.build_tx();
+    ///     builder
+    ///         .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
+    ///         .enable_rbf();
+    ///     builder.finish()?
+    /// };
+    /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
+    /// let tx = psbt.clone().extract_tx().expect("tx");
+    /// // broadcast tx but it's taking too long to confirm so we want to bump the fee
+    /// let mut psbt =  {
+    ///     let mut builder = wallet.build_fee_bump(tx.txid())?;
+    ///     builder
+    ///         .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"));
+    ///     builder.finish()?
+    /// };
+    ///
+    /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
+    /// let fee_bumped_tx = psbt.extract_tx();
+    /// // broadcast fee_bumped_tx to replace original
+    /// # Ok::<(), anyhow::Error>(())
+    /// ```
+    // TODO: support for merging multiple transactions while bumping the fees
+    pub fn build_fee_bump(
+        &mut self,
+        txid: Txid,
+    ) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm, BumpFee>, BuildFeeBumpError> {
+        let graph = self.indexed_graph.graph();
+        let txout_index = &self.indexed_graph.index;
+        let chain_tip = self.chain.tip().block_id();
+
+        let mut tx = graph
+            .get_tx(txid)
+            .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?
+            .as_ref()
+            .clone();
+
+        let pos = graph
+            .get_chain_position(&self.chain, chain_tip, txid)
+            .ok_or(BuildFeeBumpError::TransactionNotFound(txid))?;
+        if let ChainPosition::Confirmed(_) = pos {
+            return Err(BuildFeeBumpError::TransactionConfirmed(txid));
+        }
+
+        if !tx
+            .input
+            .iter()
+            .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD)
+        {
+            return Err(BuildFeeBumpError::IrreplaceableTransaction(tx.txid()));
+        }
+
+        let fee = self
+            .calculate_fee(&tx)
+            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
+        let fee_rate = self
+            .calculate_fee_rate(&tx)
+            .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?;
+
+        // remove the inputs from the tx and process them
+        let original_txin = tx.input.drain(..).collect::<Vec<_>>();
+        let original_utxos = original_txin
+            .iter()
+            .map(|txin| -> Result<_, BuildFeeBumpError> {
+                let prev_tx = graph
+                    .get_tx(txin.previous_output.txid)
+                    .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?;
+                let txout = &prev_tx.output[txin.previous_output.vout as usize];
+
+                let confirmation_time: ConfirmationTime = graph
+                    .get_chain_position(&self.chain, chain_tip, txin.previous_output.txid)
+                    .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?
+                    .cloned()
+                    .into();
+
+                let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
+                    Some((keychain, derivation_index)) => {
+                        let satisfaction_weight = self
+                            .get_descriptor_for_keychain(keychain)
+                            .max_weight_to_satisfy()
+                            .unwrap();
+                        WeightedUtxo {
+                            utxo: Utxo::Local(LocalOutput {
+                                outpoint: txin.previous_output,
+                                txout: txout.clone(),
+                                keychain,
+                                is_spent: true,
+                                derivation_index,
+                                confirmation_time,
+                            }),
+                            satisfaction_weight,
+                        }
+                    }
+                    None => {
+                        let satisfaction_weight =
+                            serialize(&txin.script_sig).len() * 4 + serialize(&txin.witness).len();
+                        WeightedUtxo {
+                            utxo: Utxo::Foreign {
+                                outpoint: txin.previous_output,
+                                sequence: Some(txin.sequence),
+                                psbt_input: Box::new(psbt::Input {
+                                    witness_utxo: Some(txout.clone()),
+                                    non_witness_utxo: Some(prev_tx.as_ref().clone()),
+                                    ..Default::default()
+                                }),
+                            },
+                            satisfaction_weight,
+                        }
+                    }
+                };
+
+                Ok(weighted_utxo)
+            })
+            .collect::<Result<Vec<_>, _>>()?;
+
+        if tx.output.len() > 1 {
+            let mut change_index = None;
+            for (index, txout) in tx.output.iter().enumerate() {
+                let change_type = self.map_keychain(KeychainKind::Internal);
+                match txout_index.index_of_spk(&txout.script_pubkey) {
+                    Some((keychain, _)) if keychain == change_type => change_index = Some(index),
+                    _ => {}
+                }
+            }
+
+            if let Some(change_index) = change_index {
+                tx.output.remove(change_index);
+            }
+        }
+
+        let params = TxParams {
+            // TODO: figure out what rbf option should be?
+            version: Some(tx_builder::Version(tx.version.0)),
+            recipients: tx
+                .output
+                .into_iter()
+                .map(|txout| (txout.script_pubkey, txout.value.to_sat()))
+                .collect(),
+            utxos: original_utxos,
+            bumping_fee: Some(tx_builder::PreviousFee {
+                absolute: fee,
+                rate: fee_rate,
+            }),
+            ..Default::default()
+        };
+
+        Ok(TxBuilder {
+            wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
+            params,
+            coin_selection: DefaultCoinSelectionAlgorithm::default(),
+            phantom: core::marker::PhantomData,
+        })
+    }
+
+    /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
+    /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that has the value true if the PSBT was finalized, or false otherwise.
+    ///
+    /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
+    /// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
+    /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
+    /// in this library will.
+    ///
+    /// ## Example
+    ///
+    /// ```
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use bdk_wallet::*;
+    /// # use bdk_wallet::wallet::ChangeSet;
+    /// # use bdk_wallet::wallet::error::CreateTxError;
+    /// # use bdk_persist::PersistBackend;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let mut wallet = doctest_wallet!();
+    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
+    /// let mut psbt = {
+    ///     let mut builder = wallet.build_tx();
+    ///     builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000));
+    ///     builder.finish()?
+    /// };
+    /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
+    /// assert!(finalized, "we should have signed all the inputs");
+    /// # Ok::<(),anyhow::Error>(())
+    pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result<bool, SignerError> {
+        // This adds all the PSBT metadata for the inputs, which will help us later figure out how
+        // to derive our keys
+        self.update_psbt_with_descriptor(psbt)
+            .map_err(SignerError::MiniscriptPsbt)?;
+
+        // If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and finalized ones)
+        // has the `non_witness_utxo`
+        if !sign_options.trust_witness_utxo
+            && psbt
+                .inputs
+                .iter()
+                .filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none())
+                .filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none())
+                .any(|i| i.non_witness_utxo.is_none())
+        {
+            return Err(SignerError::MissingNonWitnessUtxo);
+        }
+
+        // If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
+        // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot
+        if !sign_options.allow_all_sighashes
+            && !psbt.inputs.iter().all(|i| {
+                i.sighash_type.is_none()
+                    || i.sighash_type == Some(EcdsaSighashType::All.into())
+                    || i.sighash_type == Some(TapSighashType::All.into())
+                    || i.sighash_type == Some(TapSighashType::Default.into())
+            })
+        {
+            return Err(SignerError::NonStandardSighash);
+        }
+
+        for signer in self
+            .signers
+            .signers()
+            .iter()
+            .chain(self.change_signers.signers().iter())
+        {
+            signer.sign_transaction(psbt, &sign_options, &self.secp)?;
+        }
+
+        // attempt to finalize
+        if sign_options.try_finalize {
+            self.finalize_psbt(psbt, sign_options)
+        } else {
+            Ok(false)
+        }
+    }
+
+    /// Return the spending policies for the wallet's descriptor
+    pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, DescriptorError> {
+        let signers = match keychain {
+            KeychainKind::External => &self.signers,
+            KeychainKind::Internal => &self.change_signers,
+        };
+
+        match self.public_descriptor(keychain) {
+            Some(desc) => Ok(desc.extract_policy(signers, BuildSatisfaction::None, &self.secp)?),
+            None => Ok(None),
+        }
+    }
+
+    /// Return the "public" version of the wallet's descriptor, meaning a new descriptor that has
+    /// the same structure but with every secret key removed
+    ///
+    /// This can be used to build a watch-only version of a wallet
+    pub fn public_descriptor(&self, keychain: KeychainKind) -> Option<&ExtendedDescriptor> {
+        self.indexed_graph
+            .index
+            .keychains()
+            .find(|(k, _)| *k == &keychain)
+            .map(|(_, d)| d)
+    }
+
+    /// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass
+    /// validation and construct the respective `scriptSig` or `scriptWitness`. Please refer to
+    /// [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Input_Finalizer)
+    /// for further information.
+    ///
+    /// Returns `true` if the PSBT could be finalized, and `false` otherwise.
+    ///
+    /// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
+    pub fn finalize_psbt(
+        &self,
+        psbt: &mut Psbt,
+        sign_options: SignOptions,
+    ) -> Result<bool, SignerError> {
+        let chain_tip = self.chain.tip().block_id();
+
+        let tx = &psbt.unsigned_tx;
+        let mut finished = true;
+
+        for (n, input) in tx.input.iter().enumerate() {
+            let psbt_input = &psbt
+                .inputs
+                .get(n)
+                .ok_or(SignerError::InputIndexOutOfRange)?;
+            if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
+                continue;
+            }
+            let confirmation_height = self
+                .indexed_graph
+                .graph()
+                .get_chain_position(&self.chain, chain_tip, input.previous_output.txid)
+                .map(|chain_position| match chain_position {
+                    ChainPosition::Confirmed(a) => a.confirmation_height,
+                    ChainPosition::Unconfirmed(_) => u32::MAX,
+                });
+            let current_height = sign_options
+                .assume_height
+                .unwrap_or_else(|| self.chain.tip().height());
+
+            // - Try to derive the descriptor by looking at the txout. If it's in our database, we
+            //   know exactly which `keychain` to use, and which derivation index it is
+            // - If that fails, try to derive it by looking at the psbt input: the complete logic
+            //   is in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`,
+            //   `redeem_script` and `witness_script` to determine the right derivation
+            // - If that also fails, it will try it on the internal descriptor, if present
+            let desc = psbt
+                .get_utxo_for(n)
+                .and_then(|txout| self.get_descriptor_for_txout(&txout))
+                .or_else(|| {
+                    self.indexed_graph.index.keychains().find_map(|(_, desc)| {
+                        desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp)
+                    })
+                });
+
+            match desc {
+                Some(desc) => {
+                    let mut tmp_input = bitcoin::TxIn::default();
+                    match desc.satisfy(
+                        &mut tmp_input,
+                        (
+                            PsbtInputSatisfier::new(psbt, n),
+                            After::new(Some(current_height), false),
+                            Older::new(Some(current_height), confirmation_height, false),
+                        ),
+                    ) {
+                        Ok(_) => {
+                            let psbt_input = &mut psbt.inputs[n];
+                            psbt_input.final_script_sig = Some(tmp_input.script_sig);
+                            psbt_input.final_script_witness = Some(tmp_input.witness);
+                            if sign_options.remove_partial_sigs {
+                                psbt_input.partial_sigs.clear();
+                            }
+                            if sign_options.remove_taproot_extras {
+                                // We just constructed the final witness, clear these fields.
+                                psbt_input.tap_key_sig = None;
+                                psbt_input.tap_script_sigs.clear();
+                                psbt_input.tap_scripts.clear();
+                                psbt_input.tap_key_origins.clear();
+                                psbt_input.tap_internal_key = None;
+                                psbt_input.tap_merkle_root = None;
+                            }
+                        }
+                        Err(_) => finished = false,
+                    }
+                }
+                None => finished = false,
+            }
+        }
+
+        if finished && sign_options.remove_taproot_extras {
+            for output in &mut psbt.outputs {
+                output.tap_key_origins.clear();
+            }
+        }
+
+        Ok(finished)
+    }
+
+    /// Return the secp256k1 context used for all signing operations
+    pub fn secp_ctx(&self) -> &SecpCtx {
+        &self.secp
+    }
+
+    /// Returns the descriptor used to create addresses for a particular `keychain`.
+    pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
+        self.public_descriptor(self.map_keychain(keychain))
+            .expect("we mapped it to external if it doesn't exist")
+    }
+
+    /// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
+    /// Otherwise, it will return the index of the highest address it has derived.
+    pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
+        self.indexed_graph.index.last_revealed_index(&keychain)
+    }
+
+    /// The index of the next address that you would get if you were to ask the wallet for a new address
+    pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
+        let keychain = self.map_keychain(keychain);
+        self.indexed_graph
+            .index
+            .next_index(&keychain)
+            .expect("Keychain must exist (we called map_keychain)")
+            .0
+    }
+
+    /// Informs the wallet that you no longer intend to broadcast a tx that was built from it.
+    ///
+    /// This frees up the change address used when creating the tx for use in future transactions.
+    // TODO: Make this free up reserved utxos when that's implemented
+    pub fn cancel_tx(&mut self, tx: &Transaction) {
+        let txout_index = &mut self.indexed_graph.index;
+        for txout in &tx.output {
+            if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
+                // NOTE: unmark_used will **not** make something unused if it has actually been used
+                // by a tx in the tracker. It only removes the superficial marking.
+                txout_index.unmark_used(keychain, index);
+            }
+        }
+    }
+
+    fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
+        if keychain == KeychainKind::Internal
+            && self.public_descriptor(KeychainKind::Internal).is_none()
+        {
+            KeychainKind::External
+        } else {
+            keychain
+        }
+    }
+
+    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
+        let (keychain, child) = self
+            .indexed_graph
+            .index
+            .index_of_spk(&txout.script_pubkey)?;
+        let descriptor = self.get_descriptor_for_keychain(keychain);
+        descriptor.at_derivation_index(child).ok()
+    }
+
+    fn get_available_utxos(&self) -> Vec<(LocalOutput, usize)> {
+        self.list_unspent()
+            .map(|utxo| {
+                let keychain = utxo.keychain;
+                (utxo, {
+                    self.get_descriptor_for_keychain(keychain)
+                        .max_weight_to_satisfy()
+                        .unwrap()
+                })
+            })
+            .collect()
+    }
+
+    /// Given the options returns the list of utxos that must be used to form the
+    /// transaction and any further that may be used if needed.
+    fn preselect_utxos(
+        &self,
+        params: &TxParams,
+        current_height: Option<u32>,
+    ) -> (Vec<WeightedUtxo>, Vec<WeightedUtxo>) {
+        let TxParams {
+            change_policy,
+            unspendable,
+            utxos,
+            drain_wallet,
+            manually_selected_only,
+            bumping_fee,
+            ..
+        } = params;
+
+        let manually_selected = utxos.clone();
+        // we mandate confirmed transactions if we're bumping the fee
+        let must_only_use_confirmed_tx = bumping_fee.is_some();
+        let must_use_all_available = *drain_wallet;
+
+        let chain_tip = self.chain.tip().block_id();
+        //    must_spend <- manually selected utxos
+        //    may_spend  <- all other available utxos
+        let mut may_spend = self.get_available_utxos();
+
+        may_spend.retain(|may_spend| {
+            !manually_selected
+                .iter()
+                .any(|manually_selected| manually_selected.utxo.outpoint() == may_spend.0.outpoint)
+        });
+        let mut must_spend = manually_selected;
+
+        // NOTE: we are intentionally ignoring `unspendable` here. i.e manual
+        // selection overrides unspendable.
+        if *manually_selected_only {
+            return (must_spend, vec![]);
+        }
+
+        let satisfies_confirmed = may_spend
+            .iter()
+            .map(|u| -> bool {
+                let txid = u.0.outpoint.txid;
+                let tx = match self.indexed_graph.graph().get_tx(txid) {
+                    Some(tx) => tx,
+                    None => return false,
+                };
+                let confirmation_time: ConfirmationTime = match self
+                    .indexed_graph
+                    .graph()
+                    .get_chain_position(&self.chain, chain_tip, txid)
+                {
+                    Some(chain_position) => chain_position.cloned().into(),
+                    None => return false,
+                };
+
+                // Whether the UTXO is mature and, if needed, confirmed
+                let mut spendable = true;
+                if must_only_use_confirmed_tx && !confirmation_time.is_confirmed() {
+                    return false;
+                }
+                if tx.is_coinbase() {
+                    debug_assert!(
+                        confirmation_time.is_confirmed(),
+                        "coinbase must always be confirmed"
+                    );
+                    if let Some(current_height) = current_height {
+                        match confirmation_time {
+                            ConfirmationTime::Confirmed { height, .. } => {
+                                // https://github.com/bitcoin/bitcoin/blob/c5e67be03bb06a5d7885c55db1f016fbf2333fe3/src/validation.cpp#L373-L375
+                                spendable &=
+                                    (current_height.saturating_sub(height)) >= COINBASE_MATURITY;
+                            }
+                            ConfirmationTime::Unconfirmed { .. } => spendable = false,
+                        }
+                    }
+                }
+                spendable
+            })
+            .collect::<Vec<_>>();
+
+        let mut i = 0;
+        may_spend.retain(|u| {
+            let retain = change_policy.is_satisfied_by(&u.0)
+                && !unspendable.contains(&u.0.outpoint)
+                && satisfies_confirmed[i];
+            i += 1;
+            retain
+        });
+
+        let mut may_spend = may_spend
+            .into_iter()
+            .map(|(local_utxo, satisfaction_weight)| WeightedUtxo {
+                satisfaction_weight,
+                utxo: Utxo::Local(local_utxo),
+            })
+            .collect();
+
+        if must_use_all_available {
+            must_spend.append(&mut may_spend);
+        }
+
+        (must_spend, may_spend)
+    }
+
+    fn complete_transaction(
+        &self,
+        tx: Transaction,
+        selected: Vec<Utxo>,
+        params: TxParams,
+    ) -> Result<Psbt, CreateTxError> {
+        let mut psbt = Psbt::from_unsigned_tx(tx)?;
+
+        if params.add_global_xpubs {
+            let all_xpubs = self
+                .keychains()
+                .flat_map(|(_, desc)| desc.get_extended_keys())
+                .collect::<Vec<_>>();
+
+            for xpub in all_xpubs {
+                let origin = match xpub.origin {
+                    Some(origin) => origin,
+                    None if xpub.xkey.depth == 0 => {
+                        (xpub.root_fingerprint(&self.secp), vec![].into())
+                    }
+                    _ => return Err(CreateTxError::MissingKeyOrigin(xpub.xkey.to_string())),
+                };
+
+                psbt.xpub.insert(xpub.xkey, origin);
+            }
+        }
+
+        let mut lookup_output = selected
+            .into_iter()
+            .map(|utxo| (utxo.outpoint(), utxo))
+            .collect::<HashMap<_, _>>();
+
+        // add metadata for the inputs
+        for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) {
+            let utxo = match lookup_output.remove(&input.previous_output) {
+                Some(utxo) => utxo,
+                None => continue,
+            };
+
+            match utxo {
+                Utxo::Local(utxo) => {
+                    *psbt_input =
+                        match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
+                            Ok(psbt_input) => psbt_input,
+                            Err(e) => match e {
+                                CreateTxError::UnknownUtxo => psbt::Input {
+                                    sighash_type: params.sighash,
+                                    ..psbt::Input::default()
+                                },
+                                _ => return Err(e),
+                            },
+                        }
+                }
+                Utxo::Foreign {
+                    outpoint,
+                    psbt_input: foreign_psbt_input,
+                    ..
+                } => {
+                    let is_taproot = foreign_psbt_input
+                        .witness_utxo
+                        .as_ref()
+                        .map(|txout| txout.script_pubkey.is_p2tr())
+                        .unwrap_or(false);
+                    if !is_taproot
+                        && !params.only_witness_utxo
+                        && foreign_psbt_input.non_witness_utxo.is_none()
+                    {
+                        return Err(CreateTxError::MissingNonWitnessUtxo(outpoint));
+                    }
+                    *psbt_input = *foreign_psbt_input;
+                }
+            }
+        }
+
+        self.update_psbt_with_descriptor(&mut psbt)?;
+
+        Ok(psbt)
+    }
+
+    /// get the corresponding PSBT Input for a LocalUtxo
+    pub fn get_psbt_input(
+        &self,
+        utxo: LocalOutput,
+        sighash_type: Option<psbt::PsbtSighashType>,
+        only_witness_utxo: bool,
+    ) -> Result<psbt::Input, CreateTxError> {
+        // Try to find the prev_script in our db to figure out if this is internal or external,
+        // and the derivation index
+        let (keychain, child) = self
+            .indexed_graph
+            .index
+            .index_of_spk(&utxo.txout.script_pubkey)
+            .ok_or(CreateTxError::UnknownUtxo)?;
+
+        let mut psbt_input = psbt::Input {
+            sighash_type,
+            ..psbt::Input::default()
+        };
+
+        let desc = self.get_descriptor_for_keychain(keychain);
+        let derived_descriptor = desc
+            .at_derivation_index(child)
+            .expect("child can't be hardened");
+
+        psbt_input
+            .update_with_descriptor_unchecked(&derived_descriptor)
+            .map_err(MiniscriptPsbtError::Conversion)?;
+
+        let prev_output = utxo.outpoint;
+        if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) {
+            if desc.is_witness() || desc.is_taproot() {
+                psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
+            }
+            if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
+                psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone());
+            }
+        }
+        Ok(psbt_input)
+    }
+
+    fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> Result<(), MiniscriptPsbtError> {
+        // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all
+        // the input utxos and outputs
+        let utxos = (0..psbt.inputs.len())
+            .filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo)))
+            .chain(
+                psbt.unsigned_tx
+                    .output
+                    .iter()
+                    .enumerate()
+                    .map(|(i, out)| (false, i, out.clone())),
+            )
+            .collect::<Vec<_>>();
+
+        // Try to figure out the keychain and derivation for every input and output
+        for (is_input, index, out) in utxos.into_iter() {
+            if let Some((keychain, child)) =
+                self.indexed_graph.index.index_of_spk(&out.script_pubkey)
+            {
+                let desc = self.get_descriptor_for_keychain(keychain);
+                let desc = desc
+                    .at_derivation_index(child)
+                    .expect("child can't be hardened");
+
+                if is_input {
+                    psbt.update_input_with_descriptor(index, &desc)
+                        .map_err(MiniscriptPsbtError::UtxoUpdate)?;
+                } else {
+                    psbt.update_output_with_descriptor(index, &desc)
+                        .map_err(MiniscriptPsbtError::OutputUpdate)?;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Return the checksum of the public descriptor associated to `keychain`
+    ///
+    /// Internally calls [`Self::get_descriptor_for_keychain`] to fetch the right descriptor
+    pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String {
+        self.get_descriptor_for_keychain(keychain)
+            .to_string()
+            .split_once('#')
+            .unwrap()
+            .1
+            .to_string()
+    }
+
+    /// Applies an update to the wallet and stages the changes (but does not [`commit`] them).
+    ///
+    /// Usually you create an `update` by interacting with some blockchain data source and inserting
+    /// transactions related to your wallet into it.
+    ///
+    /// [`commit`]: Self::commit
+    pub fn apply_update(&mut self, update: impl Into<Update>) -> Result<(), CannotConnectError> {
+        let update = update.into();
+        let mut changeset = match update.chain {
+            Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
+            None => ChangeSet::default(),
+        };
+
+        let (_, index_changeset) = self
+            .indexed_graph
+            .index
+            .reveal_to_target_multi(&update.last_active_indices);
+        changeset.append(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
+            index_changeset,
+        )));
+        changeset.append(ChangeSet::from(
+            self.indexed_graph.apply_update(update.graph),
+        ));
+        self.persist.stage(changeset);
+        Ok(())
+    }
+
+    /// Commits all currently [`staged`] changed to the persistence backend returning and error when
+    /// this fails.
+    ///
+    /// This returns whether the `update` resulted in any changes.
+    ///
+    /// [`staged`]: Self::staged
+    pub fn commit(&mut self) -> anyhow::Result<bool> {
+        self.persist.commit().map(|c| c.is_some())
+    }
+
+    /// Returns the changes that will be committed with the next call to [`commit`].
+    ///
+    /// [`commit`]: Self::commit
+    pub fn staged(&self) -> &ChangeSet {
+        self.persist.staged()
+    }
+
+    /// Get a reference to the inner [`TxGraph`].
+    pub fn tx_graph(&self) -> &TxGraph<ConfirmationTimeHeightAnchor> {
+        self.indexed_graph.graph()
+    }
+
+    /// Get a reference to the inner [`KeychainTxOutIndex`].
+    pub fn spk_index(&self) -> &KeychainTxOutIndex<KeychainKind> {
+        &self.indexed_graph.index
+    }
+
+    /// Get a reference to the inner [`LocalChain`].
+    pub fn local_chain(&self) -> &LocalChain {
+        &self.chain
+    }
+
+    /// Introduces a `block` of `height` to the wallet, and tries to connect it to the
+    /// `prev_blockhash` of the block's header.
+    ///
+    /// This is a convenience method that is equivalent to calling [`apply_block_connected_to`]
+    /// with `prev_blockhash` and `height-1` as the `connected_to` parameter.
+    ///
+    /// [`apply_block_connected_to`]: Self::apply_block_connected_to
+    pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> {
+        let connected_to = match height.checked_sub(1) {
+            Some(prev_height) => BlockId {
+                height: prev_height,
+                hash: block.header.prev_blockhash,
+            },
+            None => BlockId {
+                height,
+                hash: block.block_hash(),
+            },
+        };
+        self.apply_block_connected_to(block, height, connected_to)
+            .map_err(|err| match err {
+                ApplyHeaderError::InconsistentBlocks => {
+                    unreachable!("connected_to is derived from the block so must be consistent")
+                }
+                ApplyHeaderError::CannotConnect(err) => err,
+            })
+    }
+
+    /// Applies relevant transactions from `block` of `height` to the wallet, and connects the
+    /// block to the internal chain.
+    ///
+    /// The `connected_to` parameter informs the wallet how this block connects to the internal
+    /// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the
+    /// internal [`TxGraph`].
+    pub fn apply_block_connected_to(
+        &mut self,
+        block: &Block,
+        height: u32,
+        connected_to: BlockId,
+    ) -> Result<(), ApplyHeaderError> {
+        let mut changeset = ChangeSet::default();
+        changeset.append(
+            self.chain
+                .apply_header_connected_to(&block.header, height, connected_to)?
+                .into(),
+        );
+        changeset.append(
+            self.indexed_graph
+                .apply_block_relevant(block, height)
+                .into(),
+        );
+        self.persist.stage(changeset);
+        Ok(())
+    }
+
+    /// Apply relevant unconfirmed transactions to the wallet.
+    ///
+    /// Transactions that are not relevant are filtered out.
+    ///
+    /// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp of
+    /// when the transaction was last seen in the mempool. This is used for conflict resolution
+    /// when there is conflicting unconfirmed transactions. The transaction with the later
+    /// `last_seen` is prioritized.
+    pub fn apply_unconfirmed_txs<'t>(
+        &mut self,
+        unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
+    ) {
+        let indexed_graph_changeset = self
+            .indexed_graph
+            .batch_insert_relevant_unconfirmed(unconfirmed_txs);
+        self.persist.stage(ChangeSet::from(indexed_graph_changeset));
+    }
+}
+
+/// Methods to construct sync/full-scan requests for spk-based chain sources.
+impl Wallet {
+    /// Create a partial [`SyncRequest`] for this wallet for all revealed spks.
+    ///
+    /// This is the first step when performing a spk-based wallet partial sync, the returned
+    /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to
+    /// start a blockchain sync with a spk based blockchain client.
+    pub fn start_sync_with_revealed_spks(&self) -> SyncRequest {
+        SyncRequest::from_chain_tip(self.chain.tip())
+            .cache_graph_txs(self.tx_graph())
+            .populate_with_revealed_spks(&self.indexed_graph.index, ..)
+    }
+
+    /// Create a [`FullScanRequest] for this wallet.
+    ///
+    /// This is the first step when performing a spk-based wallet full scan, the returned
+    /// [`FullScanRequest] collects iterators for the wallet's keychain script pub keys needed to
+    /// start a blockchain full scan with a spk based blockchain client.
+    ///
+    /// This operation is generally only used when importing or restoring a previously used wallet
+    /// in which the list of used scripts is not known.
+    pub fn start_full_scan(&self) -> FullScanRequest<KeychainKind> {
+        FullScanRequest::from_keychain_txout_index(self.chain.tip(), &self.indexed_graph.index)
+            .cache_graph_txs(self.tx_graph())
+    }
+}
+
+impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor>> for Wallet {
+    fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor> {
+        self.indexed_graph.graph()
+    }
+}
+
+/// Deterministically generate a unique name given the descriptors defining the wallet
+///
+/// Compatible with [`wallet_name_from_descriptor`]
+pub fn wallet_name_from_descriptor<T>(
+    descriptor: T,
+    change_descriptor: Option<T>,
+    network: Network,
+    secp: &SecpCtx,
+) -> Result<String, DescriptorError>
+where
+    T: IntoWalletDescriptor,
+{
+    //TODO check descriptors contains only public keys
+    let descriptor = descriptor
+        .into_wallet_descriptor(secp, network)?
+        .0
+        .to_string();
+    let mut wallet_name = calc_checksum(&descriptor[..descriptor.find('#').unwrap()])?;
+    if let Some(change_descriptor) = change_descriptor {
+        let change_descriptor = change_descriptor
+            .into_wallet_descriptor(secp, network)?
+            .0
+            .to_string();
+        wallet_name.push_str(
+            calc_checksum(&change_descriptor[..change_descriptor.find('#').unwrap()])?.as_str(),
+        );
+    }
+
+    Ok(wallet_name)
+}
+
+fn new_local_utxo(
+    keychain: KeychainKind,
+    derivation_index: u32,
+    full_txo: FullTxOut<ConfirmationTimeHeightAnchor>,
+) -> LocalOutput {
+    LocalOutput {
+        outpoint: full_txo.outpoint,
+        txout: full_txo.txout,
+        is_spent: full_txo.spent_by.is_some(),
+        confirmation_time: full_txo.chain_position.into(),
+        keychain,
+        derivation_index,
+    }
+}
+
+fn create_signers<E: IntoWalletDescriptor>(
+    index: &mut KeychainTxOutIndex<KeychainKind>,
+    secp: &Secp256k1<All>,
+    descriptor: E,
+    change_descriptor: Option<E>,
+    network: Network,
+) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), crate::descriptor::error::Error> {
+    let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
+    let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
+    let _ = index.insert_descriptor(KeychainKind::External, descriptor);
+
+    let change_signers = match change_descriptor {
+        Some(descriptor) => {
+            let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
+            let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
+            let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
+            signers
+        }
+        None => Arc::new(SignersContainer::new()),
+    };
+
+    Ok((signers, change_signers))
+}
+
+/// Transforms a [`FeeRate`] to `f64` with unit as sat/vb.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! floating_rate {
+    ($rate:expr) => {{
+        use $crate::bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
+        // sat_kwu / 250.0 -> sat_vb
+        $rate.to_sat_per_kwu() as f64 / ((1000 / WITNESS_SCALE_FACTOR) as f64)
+    }};
+}
+
+#[macro_export]
+#[doc(hidden)]
+/// Macro for getting a wallet for use in a doctest
+macro_rules! doctest_wallet {
+    () => {{
+        use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
+        use $crate::chain::{ConfirmationTime, BlockId};
+        use $crate::{KeychainKind, wallet::Wallet};
+        let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
+        let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
+
+        let mut wallet = Wallet::new_no_persist(
+            descriptor,
+            Some(change_descriptor),
+            Network::Regtest,
+        )
+        .unwrap();
+        let address = wallet.peek_address(KeychainKind::External, 0).address;
+        let tx = Transaction {
+            version: transaction::Version::ONE,
+            lock_time: absolute::LockTime::ZERO,
+            input: vec![],
+            output: vec![TxOut {
+                value: Amount::from_sat(500_000),
+                script_pubkey: address.script_pubkey(),
+            }],
+        };
+        let _ = wallet.insert_checkpoint(BlockId { height: 1_000, hash: BlockHash::all_zeros() });
+        let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed {
+            height: 500,
+            time: 50_000
+        });
+
+        wallet
+    }}
+}
diff --git a/crates/wallet/src/wallet/signer.rs b/crates/wallet/src/wallet/signer.rs
new file mode 100644 (file)
index 0000000..51420ca
--- /dev/null
@@ -0,0 +1,1193 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Generalized signers
+//!
+//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet)
+//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function.
+//!
+//! ```
+//! # use alloc::sync::Arc;
+//! # use core::str::FromStr;
+//! # use bitcoin::secp256k1::{Secp256k1, All};
+//! # use bitcoin::*;
+//! # use bdk_wallet::signer::*;
+//! # use bdk_wallet::*;
+//! # #[derive(Debug)]
+//! # struct CustomHSM;
+//! # impl CustomHSM {
+//! #     fn hsm_sign_input(&self, _psbt: &mut Psbt, _input: usize) -> Result<(), SignerError> {
+//! #         Ok(())
+//! #     }
+//! #     fn connect() -> Self {
+//! #         CustomHSM
+//! #     }
+//! #     fn get_id(&self) -> SignerId {
+//! #         SignerId::Dummy(0)
+//! #     }
+//! # }
+//! #[derive(Debug)]
+//! struct CustomSigner {
+//!     device: CustomHSM,
+//! }
+//!
+//! impl CustomSigner {
+//!     fn connect() -> Self {
+//!         CustomSigner { device: CustomHSM::connect() }
+//!     }
+//! }
+//!
+//! impl SignerCommon for CustomSigner {
+//!     fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
+//!         self.device.get_id()
+//!     }
+//! }
+//!
+//! impl InputSigner for CustomSigner {
+//!     fn sign_input(
+//!         &self,
+//!         psbt: &mut Psbt,
+//!         input_index: usize,
+//!         _sign_options: &SignOptions,
+//!         _secp: &Secp256k1<All>,
+//!     ) -> Result<(), SignerError> {
+//!         self.device.hsm_sign_input(psbt, input_index)?;
+//!
+//!         Ok(())
+//!     }
+//! }
+//!
+//! let custom_signer = CustomSigner::connect();
+//!
+//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?;
+//! wallet.add_signer(
+//!     KeychainKind::External,
+//!     SignerOrdering(200),
+//!     Arc::new(custom_signer)
+//! );
+//!
+//! # Ok::<_, anyhow::Error>(())
+//! ```
+
+use crate::collections::BTreeMap;
+use alloc::string::String;
+use alloc::sync::Arc;
+use alloc::vec::Vec;
+use core::cmp::Ordering;
+use core::fmt;
+use core::ops::{Bound::Included, Deref};
+
+use bitcoin::bip32::{ChildNumber, DerivationPath, Fingerprint, Xpriv};
+use bitcoin::hashes::hash160;
+use bitcoin::secp256k1::Message;
+use bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType};
+use bitcoin::{ecdsa, psbt, sighash, taproot};
+use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1};
+use bitcoin::{PrivateKey, Psbt, PublicKey};
+
+use miniscript::descriptor::{
+    Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey,
+    InnerXKey, KeyMap, SinglePriv, SinglePubKey,
+};
+use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
+
+use super::utils::SecpCtx;
+use crate::descriptor::{DescriptorMeta, XKeyUtils};
+use crate::psbt::PsbtUtils;
+use crate::wallet::error::MiniscriptPsbtError;
+
+/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
+/// multiple of them
+#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
+pub enum SignerId {
+    /// Bitcoin HASH160 (RIPEMD160 after SHA256) hash of an ECDSA public key
+    PkHash(hash160::Hash),
+    /// The fingerprint of a BIP32 extended key
+    Fingerprint(Fingerprint),
+    /// Dummy identifier
+    Dummy(u64),
+}
+
+impl From<hash160::Hash> for SignerId {
+    fn from(hash: hash160::Hash) -> SignerId {
+        SignerId::PkHash(hash)
+    }
+}
+
+impl From<Fingerprint> for SignerId {
+    fn from(fing: Fingerprint) -> SignerId {
+        SignerId::Fingerprint(fing)
+    }
+}
+
+/// Signing error
+#[derive(Debug)]
+pub enum SignerError {
+    /// The private key is missing for the required public key
+    MissingKey,
+    /// The private key in use has the right fingerprint but derives differently than expected
+    InvalidKey,
+    /// The user canceled the operation
+    UserCanceled,
+    /// Input index is out of range
+    InputIndexOutOfRange,
+    /// The `non_witness_utxo` field of the transaction is required to sign this input
+    MissingNonWitnessUtxo,
+    /// The `non_witness_utxo` specified is invalid
+    InvalidNonWitnessUtxo,
+    /// The `witness_utxo` field of the transaction is required to sign this input
+    MissingWitnessUtxo,
+    /// The `witness_script` field of the transaction is required to sign this input
+    MissingWitnessScript,
+    /// The fingerprint and derivation path are missing from the psbt input
+    MissingHdKeypath,
+    /// The psbt contains a non-`SIGHASH_ALL` sighash in one of its input and the user hasn't
+    /// explicitly allowed them
+    ///
+    /// To enable signing transactions with non-standard sighashes set
+    /// [`SignOptions::allow_all_sighashes`] to `true`.
+    NonStandardSighash,
+    /// Invalid SIGHASH for the signing context in use
+    InvalidSighash,
+    /// Error while computing the hash to sign
+    SighashError(sighash::Error),
+    /// Miniscript PSBT error
+    MiniscriptPsbt(MiniscriptPsbtError),
+    /// To be used only by external libraries implementing [`InputSigner`] or
+    /// [`TransactionSigner`], so that they can return their own custom errors, without having to
+    /// modify [`SignerError`] in BDK.
+    External(String),
+}
+
+impl From<sighash::Error> for SignerError {
+    fn from(e: sighash::Error) -> Self {
+        SignerError::SighashError(e)
+    }
+}
+
+impl fmt::Display for SignerError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::MissingKey => write!(f, "Missing private key"),
+            Self::InvalidKey => write!(f, "The private key in use has the right fingerprint but derives differently than expected"),
+            Self::UserCanceled => write!(f, "The user canceled the operation"),
+            Self::InputIndexOutOfRange => write!(f, "Input index out of range"),
+            Self::MissingNonWitnessUtxo => write!(f, "Missing non-witness UTXO"),
+            Self::InvalidNonWitnessUtxo => write!(f, "Invalid non-witness UTXO"),
+            Self::MissingWitnessUtxo => write!(f, "Missing witness UTXO"),
+            Self::MissingWitnessScript => write!(f, "Missing witness script"),
+            Self::MissingHdKeypath => write!(f, "Missing fingerprint and derivation path"),
+            Self::NonStandardSighash => write!(f, "The psbt contains a non standard sighash"),
+            Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
+            Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
+            Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
+            Self::External(err) => write!(f, "{}", err),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for SignerError {}
+
+/// Signing context
+///
+/// Used by our software signers to determine the type of signatures to make
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SignerContext {
+    /// Legacy context
+    Legacy,
+    /// Segwit v0 context (BIP 143)
+    Segwitv0,
+    /// Taproot context (BIP 340)
+    Tap {
+        /// Whether the signer can sign for the internal key or not
+        is_internal_key: bool,
+    },
+}
+
+/// Wrapper to pair a signer with its context
+#[derive(Debug, Clone)]
+pub struct SignerWrapper<S: Sized + fmt::Debug + Clone> {
+    signer: S,
+    ctx: SignerContext,
+}
+
+impl<S: Sized + fmt::Debug + Clone> SignerWrapper<S> {
+    /// Create a wrapped signer from a signer and a context
+    pub fn new(signer: S, ctx: SignerContext) -> Self {
+        SignerWrapper { signer, ctx }
+    }
+}
+
+impl<S: Sized + fmt::Debug + Clone> Deref for SignerWrapper<S> {
+    type Target = S;
+
+    fn deref(&self) -> &Self::Target {
+        &self.signer
+    }
+}
+
+/// Common signer methods
+pub trait SignerCommon: fmt::Debug + Send + Sync {
+    /// Return the [`SignerId`] for this signer
+    ///
+    /// The [`SignerId`] can be used to lookup a signer in the [`Wallet`](crate::Wallet)'s signers map or to
+    /// compare two signers.
+    fn id(&self, secp: &SecpCtx) -> SignerId;
+
+    /// Return the secret key for the signer
+    ///
+    /// This is used internally to reconstruct the original descriptor that may contain secrets.
+    /// External signers that are meant to keep key isolated should just return `None` here (which
+    /// is the default for this method, if not overridden).
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        None
+    }
+}
+
+/// PSBT Input signer
+///
+/// This trait can be implemented to provide custom signers to the wallet. If the signer supports signing
+/// individual inputs, this trait should be implemented and BDK will provide automatically an implementation
+/// for [`TransactionSigner`].
+pub trait InputSigner: SignerCommon {
+    /// Sign a single psbt input
+    fn sign_input(
+        &self,
+        psbt: &mut Psbt,
+        input_index: usize,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError>;
+}
+
+/// PSBT signer
+///
+/// This trait can be implemented when the signer can't sign inputs individually, but signs the whole transaction
+/// at once.
+pub trait TransactionSigner: SignerCommon {
+    /// Sign all the inputs of the psbt
+    fn sign_transaction(
+        &self,
+        psbt: &mut Psbt,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError>;
+}
+
+impl<T: InputSigner> TransactionSigner for T {
+    fn sign_transaction(
+        &self,
+        psbt: &mut Psbt,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError> {
+        for input_index in 0..psbt.inputs.len() {
+            self.sign_input(psbt, input_index, sign_options, secp)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl SignerCommon for SignerWrapper<DescriptorXKey<Xpriv>> {
+    fn id(&self, secp: &SecpCtx) -> SignerId {
+        SignerId::from(self.root_fingerprint(secp))
+    }
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        Some(DescriptorSecretKey::XPrv(self.signer.clone()))
+    }
+}
+
+impl InputSigner for SignerWrapper<DescriptorXKey<Xpriv>> {
+    fn sign_input(
+        &self,
+        psbt: &mut Psbt,
+        input_index: usize,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError> {
+        if input_index >= psbt.inputs.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        if psbt.inputs[input_index].final_script_sig.is_some()
+            || psbt.inputs[input_index].final_script_witness.is_some()
+        {
+            return Ok(());
+        }
+
+        let tap_key_origins = psbt.inputs[input_index]
+            .tap_key_origins
+            .iter()
+            .map(|(pk, (_, keysource))| (SinglePubKey::XOnly(*pk), keysource));
+        let (public_key, full_path) = match psbt.inputs[input_index]
+            .bip32_derivation
+            .iter()
+            .map(|(pk, keysource)| (SinglePubKey::FullKey(PublicKey::new(*pk)), keysource))
+            .chain(tap_key_origins)
+            .find_map(|(pk, keysource)| {
+                if self.matches(keysource, secp).is_some() {
+                    Some((pk, keysource.1.clone()))
+                } else {
+                    None
+                }
+            }) {
+            Some((pk, full_path)) => (pk, full_path),
+            None => return Ok(()),
+        };
+
+        let derived_key = match self.origin.clone() {
+            Some((_fingerprint, origin_path)) => {
+                let deriv_path = DerivationPath::from(
+                    &full_path.into_iter().cloned().collect::<Vec<ChildNumber>>()
+                        [origin_path.len()..],
+                );
+                self.xkey.derive_priv(secp, &deriv_path).unwrap()
+            }
+            None => self.xkey.derive_priv(secp, &full_path).unwrap(),
+        };
+
+        let computed_pk = secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key);
+        let valid_key = match public_key {
+            SinglePubKey::FullKey(pk) if pk.inner == computed_pk => true,
+            SinglePubKey::XOnly(x_only) if XOnlyPublicKey::from(computed_pk) == x_only => true,
+            _ => false,
+        };
+        if !valid_key {
+            Err(SignerError::InvalidKey)
+        } else {
+            // HD wallets imply compressed keys
+            let priv_key = PrivateKey {
+                compressed: true,
+                network: self.xkey.network,
+                inner: derived_key.private_key,
+            };
+
+            SignerWrapper::new(priv_key, self.ctx).sign_input(psbt, input_index, sign_options, secp)
+        }
+    }
+}
+
+fn multikey_to_xkeys<K: InnerXKey + Clone>(
+    multikey: DescriptorMultiXKey<K>,
+) -> Vec<DescriptorXKey<K>> {
+    multikey
+        .derivation_paths
+        .into_paths()
+        .into_iter()
+        .map(|derivation_path| DescriptorXKey {
+            origin: multikey.origin.clone(),
+            xkey: multikey.xkey.clone(),
+            derivation_path,
+            wildcard: multikey.wildcard,
+        })
+        .collect()
+}
+
+impl SignerCommon for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
+    fn id(&self, secp: &SecpCtx) -> SignerId {
+        SignerId::from(self.root_fingerprint(secp))
+    }
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        Some(DescriptorSecretKey::MultiXPrv(self.signer.clone()))
+    }
+}
+
+impl InputSigner for SignerWrapper<DescriptorMultiXKey<Xpriv>> {
+    fn sign_input(
+        &self,
+        psbt: &mut Psbt,
+        input_index: usize,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError> {
+        let xkeys = multikey_to_xkeys(self.signer.clone());
+        for xkey in xkeys {
+            SignerWrapper::new(xkey, self.ctx).sign_input(psbt, input_index, sign_options, secp)?
+        }
+        Ok(())
+    }
+}
+
+impl SignerCommon for SignerWrapper<PrivateKey> {
+    fn id(&self, secp: &SecpCtx) -> SignerId {
+        SignerId::from(self.public_key(secp).to_pubkeyhash(SigType::Ecdsa))
+    }
+
+    fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+        Some(DescriptorSecretKey::Single(SinglePriv {
+            key: self.signer,
+            origin: None,
+        }))
+    }
+}
+
+impl InputSigner for SignerWrapper<PrivateKey> {
+    fn sign_input(
+        &self,
+        psbt: &mut Psbt,
+        input_index: usize,
+        sign_options: &SignOptions,
+        secp: &SecpCtx,
+    ) -> Result<(), SignerError> {
+        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        if psbt.inputs[input_index].final_script_sig.is_some()
+            || psbt.inputs[input_index].final_script_witness.is_some()
+        {
+            return Ok(());
+        }
+
+        let pubkey = PublicKey::from_private_key(secp, self);
+        let x_only_pubkey = XOnlyPublicKey::from(pubkey.inner);
+
+        if let SignerContext::Tap { is_internal_key } = self.ctx {
+            if let Some(psbt_internal_key) = psbt.inputs[input_index].tap_internal_key {
+                if is_internal_key
+                    && psbt.inputs[input_index].tap_key_sig.is_none()
+                    && sign_options.sign_with_tap_internal_key
+                    && x_only_pubkey == psbt_internal_key
+                {
+                    let (hash, hash_ty) = Tap::sighash(psbt, input_index, None)?;
+                    sign_psbt_schnorr(
+                        &self.inner,
+                        x_only_pubkey,
+                        None,
+                        &mut psbt.inputs[input_index],
+                        hash,
+                        hash_ty,
+                        secp,
+                    );
+                }
+            }
+
+            if let Some((leaf_hashes, _)) =
+                psbt.inputs[input_index].tap_key_origins.get(&x_only_pubkey)
+            {
+                let leaf_hashes = leaf_hashes
+                    .iter()
+                    .filter(|lh| {
+                        // Removing the leaves we shouldn't sign for
+                        let should_sign = match &sign_options.tap_leaves_options {
+                            TapLeavesOptions::All => true,
+                            TapLeavesOptions::Include(v) => v.contains(lh),
+                            TapLeavesOptions::Exclude(v) => !v.contains(lh),
+                            TapLeavesOptions::None => false,
+                        };
+                        // Filtering out the leaves without our key
+                        should_sign
+                            && !psbt.inputs[input_index]
+                                .tap_script_sigs
+                                .contains_key(&(x_only_pubkey, **lh))
+                    })
+                    .cloned()
+                    .collect::<Vec<_>>();
+                for lh in leaf_hashes {
+                    let (hash, hash_ty) = Tap::sighash(psbt, input_index, Some(lh))?;
+                    sign_psbt_schnorr(
+                        &self.inner,
+                        x_only_pubkey,
+                        Some(lh),
+                        &mut psbt.inputs[input_index],
+                        hash,
+                        hash_ty,
+                        secp,
+                    );
+                }
+            }
+
+            return Ok(());
+        }
+
+        if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
+            return Ok(());
+        }
+
+        let (hash, hash_ty) = match self.ctx {
+            SignerContext::Segwitv0 => {
+                let (h, t) = Segwitv0::sighash(psbt, input_index, ())?;
+                let h = h.to_raw_hash();
+                (h, t)
+            }
+            SignerContext::Legacy => {
+                let (h, t) = Legacy::sighash(psbt, input_index, ())?;
+                let h = h.to_raw_hash();
+                (h, t)
+            }
+            _ => return Ok(()), // handled above
+        };
+        sign_psbt_ecdsa(
+            &self.inner,
+            pubkey,
+            &mut psbt.inputs[input_index],
+            hash,
+            hash_ty,
+            secp,
+            sign_options.allow_grinding,
+        );
+
+        Ok(())
+    }
+}
+
+fn sign_psbt_ecdsa(
+    secret_key: &secp256k1::SecretKey,
+    pubkey: PublicKey,
+    psbt_input: &mut psbt::Input,
+    hash: impl bitcoin::hashes::Hash + bitcoin::secp256k1::ThirtyTwoByteHash,
+    hash_ty: EcdsaSighashType,
+    secp: &SecpCtx,
+    allow_grinding: bool,
+) {
+    let msg = &Message::from(hash);
+    let sig = if allow_grinding {
+        secp.sign_ecdsa_low_r(msg, secret_key)
+    } else {
+        secp.sign_ecdsa(msg, secret_key)
+    };
+    secp.verify_ecdsa(msg, &sig, &pubkey.inner)
+        .expect("invalid or corrupted ecdsa signature");
+
+    let final_signature = ecdsa::Signature { sig, hash_ty };
+    psbt_input.partial_sigs.insert(pubkey, final_signature);
+}
+
+// Calling this with `leaf_hash` = `None` will sign for key-spend
+fn sign_psbt_schnorr(
+    secret_key: &secp256k1::SecretKey,
+    pubkey: XOnlyPublicKey,
+    leaf_hash: Option<taproot::TapLeafHash>,
+    psbt_input: &mut psbt::Input,
+    hash: TapSighash,
+    hash_ty: TapSighashType,
+    secp: &SecpCtx,
+) {
+    let keypair = secp256k1::Keypair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
+    let keypair = match leaf_hash {
+        None => keypair
+            .tap_tweak(secp, psbt_input.tap_merkle_root)
+            .to_inner(),
+        Some(_) => keypair, // no tweak for script spend
+    };
+
+    let msg = &Message::from(hash);
+    let sig = secp.sign_schnorr(msg, &keypair);
+    secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair).0)
+        .expect("invalid or corrupted schnorr signature");
+
+    let final_signature = taproot::Signature { sig, hash_ty };
+
+    if let Some(lh) = leaf_hash {
+        psbt_input
+            .tap_script_sigs
+            .insert((pubkey, lh), final_signature);
+    } else {
+        psbt_input.tap_key_sig = Some(final_signature);
+    }
+}
+
+/// Defines the order in which signers are called
+///
+/// The default value is `100`. Signers with an ordering above that will be called later,
+/// and they will thus see the partial signatures added to the transaction once they get to sign
+/// themselves.
+#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
+pub struct SignerOrdering(pub usize);
+
+impl Default for SignerOrdering {
+    fn default() -> Self {
+        SignerOrdering(100)
+    }
+}
+
+#[derive(Debug, Clone)]
+struct SignersContainerKey {
+    id: SignerId,
+    ordering: SignerOrdering,
+}
+
+impl From<(SignerId, SignerOrdering)> for SignersContainerKey {
+    fn from(tuple: (SignerId, SignerOrdering)) -> Self {
+        SignersContainerKey {
+            id: tuple.0,
+            ordering: tuple.1,
+        }
+    }
+}
+
+/// Container for multiple signers
+#[derive(Debug, Default, Clone)]
+pub struct SignersContainer(BTreeMap<SignersContainerKey, Arc<dyn TransactionSigner>>);
+
+impl SignersContainer {
+    /// Create a map of public keys to secret keys
+    pub fn as_key_map(&self, secp: &SecpCtx) -> KeyMap {
+        self.0
+            .values()
+            .filter_map(|signer| signer.descriptor_secret_key())
+            .filter_map(|secret| secret.to_public(secp).ok().map(|public| (public, secret)))
+            .collect()
+    }
+
+    /// Build a new signer container from a [`KeyMap`]
+    ///
+    /// Also looks at the corresponding descriptor to determine the [`SignerContext`] to attach to
+    /// the signers
+    pub fn build(
+        keymap: KeyMap,
+        descriptor: &Descriptor<DescriptorPublicKey>,
+        secp: &SecpCtx,
+    ) -> SignersContainer {
+        let mut container = SignersContainer::new();
+
+        for (pubkey, secret) in keymap {
+            let ctx = match descriptor {
+                Descriptor::Tr(tr) => SignerContext::Tap {
+                    is_internal_key: tr.internal_key() == &pubkey,
+                },
+                _ if descriptor.is_witness() => SignerContext::Segwitv0,
+                _ => SignerContext::Legacy,
+            };
+
+            match secret {
+                DescriptorSecretKey::Single(private_key) => container.add_external(
+                    SignerId::from(
+                        private_key
+                            .key
+                            .public_key(secp)
+                            .to_pubkeyhash(SigType::Ecdsa),
+                    ),
+                    SignerOrdering::default(),
+                    Arc::new(SignerWrapper::new(private_key.key, ctx)),
+                ),
+                DescriptorSecretKey::XPrv(xprv) => container.add_external(
+                    SignerId::from(xprv.root_fingerprint(secp)),
+                    SignerOrdering::default(),
+                    Arc::new(SignerWrapper::new(xprv, ctx)),
+                ),
+                DescriptorSecretKey::MultiXPrv(xprv) => container.add_external(
+                    SignerId::from(xprv.root_fingerprint(secp)),
+                    SignerOrdering::default(),
+                    Arc::new(SignerWrapper::new(xprv, ctx)),
+                ),
+            };
+        }
+
+        container
+    }
+}
+
+impl SignersContainer {
+    /// Default constructor
+    pub fn new() -> Self {
+        SignersContainer(Default::default())
+    }
+
+    /// Adds an external signer to the container for the specified id. Optionally returns the
+    /// signer that was previously in the container, if any
+    pub fn add_external(
+        &mut self,
+        id: SignerId,
+        ordering: SignerOrdering,
+        signer: Arc<dyn TransactionSigner>,
+    ) -> Option<Arc<dyn TransactionSigner>> {
+        self.0.insert((id, ordering).into(), signer)
+    }
+
+    /// Removes a signer from the container and returns it
+    pub fn remove(
+        &mut self,
+        id: SignerId,
+        ordering: SignerOrdering,
+    ) -> Option<Arc<dyn TransactionSigner>> {
+        self.0.remove(&(id, ordering).into())
+    }
+
+    /// Returns the list of identifiers of all the signers in the container
+    pub fn ids(&self) -> Vec<&SignerId> {
+        self.0
+            .keys()
+            .map(|SignersContainerKey { id, .. }| id)
+            .collect()
+    }
+
+    /// Returns the list of signers in the container, sorted by lowest to highest `ordering`
+    pub fn signers(&self) -> Vec<&Arc<dyn TransactionSigner>> {
+        self.0.values().collect()
+    }
+
+    /// Finds the signer with lowest ordering for a given id in the container.
+    pub fn find(&self, id: SignerId) -> Option<&Arc<dyn TransactionSigner>> {
+        self.0
+            .range((
+                Included(&(id.clone(), SignerOrdering(0)).into()),
+                Included(&(id.clone(), SignerOrdering(usize::MAX)).into()),
+            ))
+            .filter(|(k, _)| k.id == id)
+            .map(|(_, v)| v)
+            .next()
+    }
+}
+
+/// Options for a software signer
+///
+/// Adjust the behavior of our software signers and the way a transaction is finalized
+#[derive(Debug, Clone)]
+pub struct SignOptions {
+    /// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
+    /// provided
+    ///
+    /// Defaults to `false` to mitigate the "SegWit bug" which should trick the wallet into
+    /// paying a fee larger than expected.
+    ///
+    /// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
+    /// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
+    /// should correctly produce a signature, at the expense of an increased trust in the creator
+    /// of the PSBT.
+    ///
+    /// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
+    pub trust_witness_utxo: bool,
+
+    /// Whether the wallet should assume a specific height has been reached when trying to finalize
+    /// a transaction
+    ///
+    /// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
+    /// timelock height has already been reached. This option allows overriding the "current height" to let the
+    /// wallet use timelocks in the future to spend a coin.
+    pub assume_height: Option<u32>,
+
+    /// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter
+    /// what its value is
+    ///
+    /// Defaults to `false` which will only allow signing using `SIGHASH_ALL`.
+    pub allow_all_sighashes: bool,
+
+    /// Whether to remove partial signatures from the PSBT inputs while finalizing PSBT.
+    ///
+    /// Defaults to `true` which will remove partial signatures during finalization.
+    pub remove_partial_sigs: bool,
+
+    /// Whether to remove taproot specific fields from the PSBT on finalization.
+    ///
+    /// For inputs this includes the taproot internal key, merkle root, and individual
+    /// scripts and signatures. For both inputs and outputs it includes key origin info.
+    ///
+    /// Defaults to `true` which will remove all of the above mentioned fields when finalizing.
+    ///
+    /// See [`BIP371`](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki) for details.
+    pub remove_taproot_extras: bool,
+
+    /// Whether to try finalizing the PSBT after the inputs are signed.
+    ///
+    /// Defaults to `true` which will try finalizing PSBT after inputs are signed.
+    pub try_finalize: bool,
+
+    /// Specifies which Taproot script-spend leaves we should sign for. This option is
+    /// ignored if we're signing a non-taproot PSBT.
+    ///
+    /// Defaults to All, i.e., the wallet will sign all the leaves it has a key for.
+    pub tap_leaves_options: TapLeavesOptions,
+
+    /// Whether we should try to sign a taproot transaction with the taproot internal key
+    /// or not. This option is ignored if we're signing a non-taproot PSBT.
+    ///
+    /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
+    pub sign_with_tap_internal_key: bool,
+
+    /// Whether we should grind ECDSA signature to ensure signing with low r
+    /// or not.
+    /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
+    pub allow_grinding: bool,
+}
+
+/// Customize which taproot script-path leaves the signer should sign.
+#[derive(Default, Debug, Clone, PartialEq, Eq)]
+pub enum TapLeavesOptions {
+    /// The signer will sign all the leaves it has a key for.
+    #[default]
+    All,
+    /// The signer won't sign leaves other than the ones specified. Note that it could still ignore
+    /// some of the specified leaves, if it doesn't have the right key to sign them.
+    Include(Vec<taproot::TapLeafHash>),
+    /// The signer won't sign the specified leaves.
+    Exclude(Vec<taproot::TapLeafHash>),
+    /// The signer won't sign any leaf.
+    None,
+}
+
+impl Default for SignOptions {
+    fn default() -> Self {
+        SignOptions {
+            trust_witness_utxo: false,
+            assume_height: None,
+            allow_all_sighashes: false,
+            remove_partial_sigs: true,
+            remove_taproot_extras: true,
+            try_finalize: true,
+            tap_leaves_options: TapLeavesOptions::default(),
+            sign_with_tap_internal_key: true,
+            allow_grinding: true,
+        }
+    }
+}
+
+pub(crate) trait ComputeSighash {
+    type Extra;
+    type Sighash;
+    type SighashType;
+
+    fn sighash(
+        psbt: &Psbt,
+        input_index: usize,
+        extra: Self::Extra,
+    ) -> Result<(Self::Sighash, Self::SighashType), SignerError>;
+}
+
+impl ComputeSighash for Legacy {
+    type Extra = ();
+    type Sighash = sighash::LegacySighash;
+    type SighashType = EcdsaSighashType;
+
+    fn sighash(
+        psbt: &Psbt,
+        input_index: usize,
+        _extra: (),
+    ) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
+        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let psbt_input = &psbt.inputs[input_index];
+        let tx_input = &psbt.unsigned_tx.input[input_index];
+
+        let sighash = psbt_input
+            .sighash_type
+            .unwrap_or_else(|| EcdsaSighashType::All.into())
+            .ecdsa_hash_ty()
+            .map_err(|_| SignerError::InvalidSighash)?;
+        let script = match psbt_input.redeem_script {
+            Some(ref redeem_script) => redeem_script.clone(),
+            None => {
+                let non_witness_utxo = psbt_input
+                    .non_witness_utxo
+                    .as_ref()
+                    .ok_or(SignerError::MissingNonWitnessUtxo)?;
+                let prev_out = non_witness_utxo
+                    .output
+                    .get(tx_input.previous_output.vout as usize)
+                    .ok_or(SignerError::InvalidNonWitnessUtxo)?;
+
+                prev_out.script_pubkey.clone()
+            }
+        };
+
+        Ok((
+            sighash::SighashCache::new(&psbt.unsigned_tx).legacy_signature_hash(
+                input_index,
+                &script,
+                sighash.to_u32(),
+            )?,
+            sighash,
+        ))
+    }
+}
+
+impl ComputeSighash for Segwitv0 {
+    type Extra = ();
+    type Sighash = sighash::SegwitV0Sighash;
+    type SighashType = EcdsaSighashType;
+
+    fn sighash(
+        psbt: &Psbt,
+        input_index: usize,
+        _extra: (),
+    ) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
+        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let psbt_input = &psbt.inputs[input_index];
+        let tx_input = &psbt.unsigned_tx.input[input_index];
+
+        let sighash_type = psbt_input
+            .sighash_type
+            .unwrap_or_else(|| EcdsaSighashType::All.into())
+            .ecdsa_hash_ty()
+            .map_err(|_| SignerError::InvalidSighash)?;
+
+        // Always try first with the non-witness utxo
+        let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
+            // Check the provided prev-tx
+            if prev_tx.txid() != tx_input.previous_output.txid {
+                return Err(SignerError::InvalidNonWitnessUtxo);
+            }
+
+            // The output should be present, if it's missing the `non_witness_utxo` is invalid
+            prev_tx
+                .output
+                .get(tx_input.previous_output.vout as usize)
+                .ok_or(SignerError::InvalidNonWitnessUtxo)?
+        } else if let Some(witness_utxo) = &psbt_input.witness_utxo {
+            // Fallback to the witness_utxo. If we aren't allowed to use it, signing should fail
+            // before we get to this point
+            witness_utxo
+        } else {
+            // Nothing has been provided
+            return Err(SignerError::MissingNonWitnessUtxo);
+        };
+        let value = utxo.value;
+
+        let mut sighasher = sighash::SighashCache::new(&psbt.unsigned_tx);
+
+        let sighash = match psbt_input.witness_script {
+            Some(ref witness_script) => {
+                sighasher.p2wsh_signature_hash(input_index, witness_script, value, sighash_type)?
+            }
+            None => {
+                if utxo.script_pubkey.is_p2wpkh() {
+                    sighasher.p2wpkh_signature_hash(
+                        input_index,
+                        &utxo.script_pubkey,
+                        value,
+                        sighash_type,
+                    )?
+                } else if psbt_input
+                    .redeem_script
+                    .as_ref()
+                    .map(|s| s.is_p2wpkh())
+                    .unwrap_or(false)
+                {
+                    let script_pubkey = psbt_input.redeem_script.as_ref().unwrap();
+                    sighasher.p2wpkh_signature_hash(
+                        input_index,
+                        script_pubkey,
+                        value,
+                        sighash_type,
+                    )?
+                } else {
+                    return Err(SignerError::MissingWitnessScript);
+                }
+            }
+        };
+        Ok((sighash, sighash_type))
+    }
+}
+
+impl ComputeSighash for Tap {
+    type Extra = Option<taproot::TapLeafHash>;
+    type Sighash = TapSighash;
+    type SighashType = TapSighashType;
+
+    fn sighash(
+        psbt: &Psbt,
+        input_index: usize,
+        extra: Self::Extra,
+    ) -> Result<(Self::Sighash, TapSighashType), SignerError> {
+        if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
+            return Err(SignerError::InputIndexOutOfRange);
+        }
+
+        let psbt_input = &psbt.inputs[input_index];
+
+        let sighash_type = psbt_input
+            .sighash_type
+            .unwrap_or_else(|| TapSighashType::Default.into())
+            .taproot_hash_ty()
+            .map_err(|_| SignerError::InvalidSighash)?;
+        let witness_utxos = (0..psbt.inputs.len())
+            .map(|i| psbt.get_utxo_for(i))
+            .collect::<Vec<_>>();
+        let mut all_witness_utxos = vec![];
+
+        let mut cache = sighash::SighashCache::new(&psbt.unsigned_tx);
+        let is_anyone_can_pay = psbt::PsbtSighashType::from(sighash_type).to_u32() & 0x80 != 0;
+        let prevouts = if is_anyone_can_pay {
+            sighash::Prevouts::One(
+                input_index,
+                witness_utxos[input_index]
+                    .as_ref()
+                    .ok_or(SignerError::MissingWitnessUtxo)?,
+            )
+        } else if witness_utxos.iter().all(Option::is_some) {
+            all_witness_utxos.extend(witness_utxos.iter().filter_map(|x| x.as_ref()));
+            sighash::Prevouts::All(&all_witness_utxos)
+        } else {
+            return Err(SignerError::MissingWitnessUtxo);
+        };
+
+        // Assume no OP_CODESEPARATOR
+        let extra = extra.map(|leaf_hash| (leaf_hash, 0xFFFFFFFF));
+
+        Ok((
+            cache.taproot_signature_hash(input_index, &prevouts, None, extra, sighash_type)?,
+            sighash_type,
+        ))
+    }
+}
+
+impl PartialOrd for SignersContainerKey {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for SignersContainerKey {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.ordering
+            .cmp(&other.ordering)
+            .then(self.id.cmp(&other.id))
+    }
+}
+
+impl PartialEq for SignersContainerKey {
+    fn eq(&self, other: &Self) -> bool {
+        self.id == other.id && self.ordering == other.ordering
+    }
+}
+
+impl Eq for SignersContainerKey {}
+
+#[cfg(test)]
+mod signers_container_tests {
+    use super::*;
+    use crate::descriptor;
+    use crate::descriptor::IntoWalletDescriptor;
+    use crate::keys::{DescriptorKey, IntoDescriptorKey};
+    use assert_matches::assert_matches;
+    use bitcoin::bip32;
+    use bitcoin::secp256k1::{All, Secp256k1};
+    use bitcoin::Network;
+    use core::str::FromStr;
+    use miniscript::ScriptContext;
+
+    fn is_equal(this: &Arc<dyn TransactionSigner>, that: &Arc<DummySigner>) -> bool {
+        let secp = Secp256k1::new();
+        this.id(&secp) == that.id(&secp)
+    }
+
+    // Signers added with the same ordering (like `Ordering::default`) created from `KeyMap`
+    // should be preserved and not overwritten.
+    // This happens usually when a set of signers is created from a descriptor with private keys.
+    #[test]
+    fn signers_with_same_ordering() {
+        let secp = Secp256k1::new();
+
+        let (prvkey1, _, _) = setup_keys(TPRV0_STR);
+        let (prvkey2, _, _) = setup_keys(TPRV1_STR);
+        let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap();
+        let (wallet_desc, keymap) = desc
+            .into_wallet_descriptor(&secp, Network::Testnet)
+            .unwrap();
+
+        let signers = SignersContainer::build(keymap, &wallet_desc, &secp);
+        assert_eq!(signers.ids().len(), 2);
+
+        let signers = signers.signers();
+        assert_eq!(signers.len(), 2);
+    }
+
+    #[test]
+    fn signers_sorted_by_ordering() {
+        let mut signers = SignersContainer::new();
+        let signer1 = Arc::new(DummySigner { number: 1 });
+        let signer2 = Arc::new(DummySigner { number: 2 });
+        let signer3 = Arc::new(DummySigner { number: 3 });
+
+        // Mixed order insertions verifies we are not inserting at head or tail.
+        signers.add_external(SignerId::Dummy(2), SignerOrdering(2), signer2.clone());
+        signers.add_external(SignerId::Dummy(1), SignerOrdering(1), signer1.clone());
+        signers.add_external(SignerId::Dummy(3), SignerOrdering(3), signer3.clone());
+
+        // Check that signers are sorted from lowest to highest ordering
+        let signers = signers.signers();
+
+        assert!(is_equal(signers[0], &signer1));
+        assert!(is_equal(signers[1], &signer2));
+        assert!(is_equal(signers[2], &signer3));
+    }
+
+    #[test]
+    fn find_signer_by_id() {
+        let mut signers = SignersContainer::new();
+        let signer1 = Arc::new(DummySigner { number: 1 });
+        let signer2 = Arc::new(DummySigner { number: 2 });
+        let signer3 = Arc::new(DummySigner { number: 3 });
+        let signer4 = Arc::new(DummySigner { number: 3 }); // Same ID as `signer3` but will use lower ordering.
+
+        let id1 = SignerId::Dummy(1);
+        let id2 = SignerId::Dummy(2);
+        let id3 = SignerId::Dummy(3);
+        let id_nonexistent = SignerId::Dummy(999);
+
+        signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone());
+        signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
+        signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
+
+        assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
+        assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
+        assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
+
+        // The `signer4` has the same ID as `signer3` but lower ordering.
+        // It should be found by `id3` instead of `signer3`.
+        signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
+        assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
+
+        // Can't find anything with ID that doesn't exist
+        assert_matches!(signers.find(id_nonexistent), None);
+    }
+
+    #[derive(Debug, Clone, Copy)]
+    struct DummySigner {
+        number: u64,
+    }
+
+    impl SignerCommon for DummySigner {
+        fn id(&self, _secp: &SecpCtx) -> SignerId {
+            SignerId::Dummy(self.number)
+        }
+    }
+
+    impl TransactionSigner for DummySigner {
+        fn sign_transaction(
+            &self,
+            _psbt: &mut Psbt,
+            _sign_options: &SignOptions,
+            _secp: &SecpCtx,
+        ) -> Result<(), SignerError> {
+            Ok(())
+        }
+    }
+
+    const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf";
+    const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N";
+
+    const PATH: &str = "m/44'/1'/0'/0";
+
+    fn setup_keys<Ctx: ScriptContext>(
+        tprv: &str,
+    ) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
+        let secp: Secp256k1<All> = Secp256k1::new();
+        let path = bip32::DerivationPath::from_str(PATH).unwrap();
+        let tprv = bip32::Xpriv::from_str(tprv).unwrap();
+        let tpub = bip32::Xpub::from_priv(&secp, &tprv);
+        let fingerprint = tprv.fingerprint(&secp);
+        let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
+        let pubkey = (tpub, path).into_descriptor_key().unwrap();
+
+        (prvkey, pubkey, fingerprint)
+    }
+}
diff --git a/crates/wallet/src/wallet/tx_builder.rs b/crates/wallet/src/wallet/tx_builder.rs
new file mode 100644 (file)
index 0000000..28b70a8
--- /dev/null
@@ -0,0 +1,1083 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Transaction builder
+//!
+//! ## Example
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use bdk_wallet::*;
+//! # use bdk_wallet::wallet::ChangeSet;
+//! # use bdk_wallet::wallet::error::CreateTxError;
+//! # use bdk_wallet::wallet::tx_builder::CreateTx;
+//! # use bdk_persist::PersistBackend;
+//! # use anyhow::Error;
+//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
+//! # let mut wallet = doctest_wallet!();
+//! // create a TxBuilder from a wallet
+//! let mut tx_builder = wallet.build_tx();
+//!
+//! tx_builder
+//!     // Create a transaction with one output to `to_address` of 50_000 satoshi
+//!     .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
+//!     // With a custom fee rate of 5.0 satoshi/vbyte
+//!     .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
+//!     // Only spend non-change outputs
+//!     .do_not_spend_change()
+//!     // Turn on RBF signaling
+//!     .enable_rbf();
+//! let psbt = tx_builder.finish()?;
+//! # Ok::<(), anyhow::Error>(())
+//! ```
+
+use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
+use core::cell::RefCell;
+use core::fmt;
+use core::marker::PhantomData;
+
+use bitcoin::psbt::{self, Psbt};
+use bitcoin::script::PushBytes;
+use bitcoin::{absolute, Amount, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, Txid};
+
+use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
+use super::{CreateTxError, Wallet};
+use crate::collections::{BTreeMap, HashSet};
+use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo};
+
+/// Context in which the [`TxBuilder`] is valid
+pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {}
+
+/// Marker type to indicate the [`TxBuilder`] is being used to create a new transaction (as opposed
+/// to bumping the fee of an existing one).
+#[derive(Debug, Default, Clone)]
+pub struct CreateTx;
+impl TxBuilderContext for CreateTx {}
+
+/// Marker type to indicate the [`TxBuilder`] is being used to bump the fee of an existing transaction.
+#[derive(Debug, Default, Clone)]
+pub struct BumpFee;
+impl TxBuilderContext for BumpFee {}
+
+/// A transaction builder
+///
+/// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After
+/// assigning it, you set options on it until finally calling [`finish`] to consume the builder and
+/// generate the transaction.
+///
+/// Each option setting method on `TxBuilder` takes and returns `&mut self` so you can chain calls
+/// as in the following example:
+///
+/// ```
+/// # use bdk_wallet::*;
+/// # use bdk_wallet::wallet::tx_builder::*;
+/// # use bitcoin::*;
+/// # use core::str::FromStr;
+/// # use bdk_wallet::wallet::ChangeSet;
+/// # use bdk_wallet::wallet::error::CreateTxError;
+/// # use bdk_persist::PersistBackend;
+/// # use anyhow::Error;
+/// # let mut wallet = doctest_wallet!();
+/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
+/// # let addr2 = addr1.clone();
+/// // chaining
+/// let psbt1 = {
+///     let mut builder = wallet.build_tx();
+///     builder
+///         .ordering(TxOrdering::Untouched)
+///         .add_recipient(addr1.script_pubkey(), Amount::from_sat(50_000))
+///         .add_recipient(addr2.script_pubkey(), Amount::from_sat(50_000));
+///     builder.finish()?
+/// };
+///
+/// // non-chaining
+/// let psbt2 = {
+///     let mut builder = wallet.build_tx();
+///     builder.ordering(TxOrdering::Untouched);
+///     for addr in &[addr1, addr2] {
+///         builder.add_recipient(addr.script_pubkey(), Amount::from_sat(50_000));
+///     }
+///     builder.finish()?
+/// };
+///
+/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]);
+/// # Ok::<(), anyhow::Error>(())
+/// ```
+///
+/// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`.
+/// This means it is usually best to call [`coin_selection`] on the return value of `build_tx` before assigning it.
+///
+/// For further examples see [this module](super::tx_builder)'s documentation;
+///
+/// [`build_tx`]: Wallet::build_tx
+/// [`build_fee_bump`]: Wallet::build_fee_bump
+/// [`finish`]: Self::finish
+/// [`coin_selection`]: Self::coin_selection
+#[derive(Debug)]
+pub struct TxBuilder<'a, Cs, Ctx> {
+    pub(crate) wallet: Rc<RefCell<&'a mut Wallet>>,
+    pub(crate) params: TxParams,
+    pub(crate) coin_selection: Cs,
+    pub(crate) phantom: PhantomData<Ctx>,
+}
+
+/// The parameters for transaction creation sans coin selection algorithm.
+//TODO: TxParams should eventually be exposed publicly.
+#[derive(Default, Debug, Clone)]
+pub(crate) struct TxParams {
+    pub(crate) recipients: Vec<(ScriptBuf, u64)>,
+    pub(crate) drain_wallet: bool,
+    pub(crate) drain_to: Option<ScriptBuf>,
+    pub(crate) fee_policy: Option<FeePolicy>,
+    pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
+    pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
+    pub(crate) utxos: Vec<WeightedUtxo>,
+    pub(crate) unspendable: HashSet<OutPoint>,
+    pub(crate) manually_selected_only: bool,
+    pub(crate) sighash: Option<psbt::PsbtSighashType>,
+    pub(crate) ordering: TxOrdering,
+    pub(crate) locktime: Option<absolute::LockTime>,
+    pub(crate) rbf: Option<RbfValue>,
+    pub(crate) version: Option<Version>,
+    pub(crate) change_policy: ChangeSpendPolicy,
+    pub(crate) only_witness_utxo: bool,
+    pub(crate) add_global_xpubs: bool,
+    pub(crate) include_output_redeem_witness_script: bool,
+    pub(crate) bumping_fee: Option<PreviousFee>,
+    pub(crate) current_height: Option<absolute::LockTime>,
+    pub(crate) allow_dust: bool,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub(crate) struct PreviousFee {
+    pub absolute: u64,
+    pub rate: FeeRate,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum FeePolicy {
+    FeeRate(FeeRate),
+    FeeAmount(u64),
+}
+
+impl Default for FeePolicy {
+    fn default() -> Self {
+        FeePolicy::FeeRate(FeeRate::BROADCAST_MIN)
+    }
+}
+
+impl<'a, Cs: Clone, Ctx> Clone for TxBuilder<'a, Cs, Ctx> {
+    fn clone(&self) -> Self {
+        TxBuilder {
+            wallet: self.wallet.clone(),
+            params: self.params.clone(),
+            coin_selection: self.coin_selection.clone(),
+            phantom: PhantomData,
+        }
+    }
+}
+
+// methods supported by both contexts, for any CoinSelectionAlgorithm
+impl<'a, Cs, Ctx> TxBuilder<'a, Cs, Ctx> {
+    /// Set a custom fee rate.
+    ///
+    /// This method sets the mining fee paid by the transaction as a rate on its size.
+    /// This means that the total fee paid is equal to `fee_rate` times the size
+    /// of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core's default
+    /// relay policy.
+    ///
+    /// Note that this is really a minimum feerate -- it's possible to
+    /// overshoot it slightly since adding a change output to drain the remaining
+    /// excess might not be viable.
+    pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
+        self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate));
+        self
+    }
+
+    /// Set an absolute fee
+    /// The fee_absolute method refers to the absolute transaction fee in satoshis (sats).
+    /// If anyone sets both the fee_absolute method and the fee_rate method,
+    /// the FeePolicy enum will be set by whichever method was called last,
+    /// as the FeeRate and FeeAmount are mutually exclusive.
+    ///
+    /// Note that this is really a minimum absolute fee -- it's possible to
+    /// overshoot it slightly since adding a change output to drain the remaining
+    /// excess might not be viable.
+    pub fn fee_absolute(&mut self, fee_amount: u64) -> &mut Self {
+        self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount));
+        self
+    }
+
+    /// Set the policy path to use while creating the transaction for a given keychain.
+    ///
+    /// This method accepts a map where the key is the policy node id (see
+    /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of
+    /// the items that are intended to be satisfied from the policy node (see
+    /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)).
+    ///
+    /// ## Example
+    ///
+    /// An example of when the policy path is needed is the following descriptor:
+    /// `wsh(thresh(2,pk(A),sj:and_v(v:pk(B),n:older(6)),snj:and_v(v:pk(C),after(630000))))`,
+    /// derived from the miniscript policy `thresh(2,pk(A),and(pk(B),older(6)),and(pk(C),after(630000)))`.
+    /// It declares three descriptor fragments, and at the top level it uses `thresh()` to
+    /// ensure that at least two of them are satisfied. The individual fragments are:
+    ///
+    /// 1. `pk(A)`
+    /// 2. `and(pk(B),older(6))`
+    /// 3. `and(pk(C),after(630000))`
+    ///
+    /// When those conditions are combined in pairs, it's clear that the transaction needs to be created
+    /// differently depending on how the user intends to satisfy the policy afterwards:
+    ///
+    /// * If fragments `1` and `2` are used, the transaction will need to use a specific
+    ///   `n_sequence` in order to spend an `OP_CSV` branch.
+    /// * If fragments `1` and `3` are used, the transaction will need to use a specific `locktime`
+    ///   in order to spend an `OP_CLTV` branch.
+    /// * If fragments `2` and `3` are used, the transaction will need both.
+    ///
+    /// When the spending policy is represented as a tree (see
+    /// [`Wallet::policies`](super::Wallet::policies)), every node
+    /// is assigned a unique identifier that can be used in the policy path to specify which of
+    /// the node's children the user intends to satisfy: for instance, assuming the `thresh()`
+    /// root node of this example has an id of `aabbccdd`, the policy path map would look like:
+    ///
+    /// `{ "aabbccdd" => [0, 1] }`
+    ///
+    /// where the key is the node's id, and the value is a list of the children that should be
+    /// used, in no particular order.
+    ///
+    /// If a particularly complex descriptor has multiple ambiguous thresholds in its structure,
+    /// multiple entries can be added to the map, one for each node that requires an explicit path.
+    ///
+    /// ```
+    /// # use std::str::FromStr;
+    /// # use std::collections::BTreeMap;
+    /// # use bitcoin::*;
+    /// # use bdk_wallet::*;
+    /// # let to_address =
+    /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
+    ///     .unwrap()
+    ///     .assume_checked();
+    /// # let mut wallet = doctest_wallet!();
+    /// let mut path = BTreeMap::new();
+    /// path.insert("aabbccdd".to_string(), vec![0, 1]);
+    ///
+    /// let builder = wallet
+    ///     .build_tx()
+    ///     .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000))
+    ///     .policy_path(path, KeychainKind::External);
+    ///
+    /// # Ok::<(), anyhow::Error>(())
+    /// ```
+    pub fn policy_path(
+        &mut self,
+        policy_path: BTreeMap<String, Vec<usize>>,
+        keychain: KeychainKind,
+    ) -> &mut Self {
+        let to_update = match keychain {
+            KeychainKind::Internal => &mut self.params.internal_policy_path,
+            KeychainKind::External => &mut self.params.external_policy_path,
+        };
+
+        *to_update = Some(policy_path);
+        self
+    }
+
+    /// Add the list of outpoints to the internal list of UTXOs that **must** be spent.
+    ///
+    /// If an error occurs while adding any of the UTXOs then none of them are added and the error is returned.
+    ///
+    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+    /// the "utxos" and the "unspendable" list, it will be spent.
+    pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> {
+        {
+            let wallet = self.wallet.borrow();
+            let utxos = outpoints
+                .iter()
+                .map(|outpoint| {
+                    wallet
+                        .get_utxo(*outpoint)
+                        .ok_or(AddUtxoError::UnknownUtxo(*outpoint))
+                })
+                .collect::<Result<Vec<_>, _>>()?;
+
+            for utxo in utxos {
+                let descriptor = wallet.get_descriptor_for_keychain(utxo.keychain);
+                let satisfaction_weight = descriptor.max_weight_to_satisfy().unwrap();
+                self.params.utxos.push(WeightedUtxo {
+                    satisfaction_weight,
+                    utxo: Utxo::Local(utxo),
+                });
+            }
+        }
+
+        Ok(self)
+    }
+
+    /// Add a utxo to the internal list of utxos that **must** be spent
+    ///
+    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+    /// the "utxos" and the "unspendable" list, it will be spent.
+    pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, AddUtxoError> {
+        self.add_utxos(&[outpoint])
+    }
+
+    /// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
+    ///
+    /// At a minimum to add a foreign UTXO we need:
+    ///
+    /// 1. `outpoint`: To add it to the raw transaction.
+    /// 2. `psbt_input`: To know the value.
+    /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the transaction for fee calculation.
+    ///
+    /// There are several security concerns about adding foreign UTXOs that application
+    /// developers should consider. First, how do you know the value of the input is correct? If a
+    /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the
+    /// value by checking it against the transaction. If only a `witness_utxo` is provided then this
+    /// method doesn't verify the value but just takes it as a given -- it is up to you to check
+    /// that whoever sent you the `input_psbt` was not lying!
+    ///
+    /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your
+    /// application it may be important that this be known precisely. If not, a malicious
+    /// counterparty may fool you into putting in a value that is too low, giving the transaction a
+    /// lower than expected feerate. They could also fool you into putting a value that is too high
+    /// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
+    /// of course check the real input weight matches the expected weight prior to broadcasting.
+    ///
+    /// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing the
+    /// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
+    /// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`].
+    ///
+    /// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
+    ///
+    /// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a transaction
+    /// created with foreign UTXO(s) you must manually insert the corresponding TxOut(s) into the tx
+    /// graph using the [`Wallet::insert_txout`] function.
+    ///
+    /// # Errors
+    ///
+    /// This method returns errors in the following circumstances:
+    ///
+    /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
+    /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
+    ///
+    /// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this
+    /// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`]
+    /// is called.
+    ///
+    /// [`only_witness_utxo`]: Self::only_witness_utxo
+    /// [`finish`]: Self::finish
+    /// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy
+    pub fn add_foreign_utxo(
+        &mut self,
+        outpoint: OutPoint,
+        psbt_input: psbt::Input,
+        satisfaction_weight: usize,
+    ) -> Result<&mut Self, AddForeignUtxoError> {
+        self.add_foreign_utxo_with_sequence(
+            outpoint,
+            psbt_input,
+            satisfaction_weight,
+            Sequence::MAX,
+        )
+    }
+
+    /// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence value.
+    pub fn add_foreign_utxo_with_sequence(
+        &mut self,
+        outpoint: OutPoint,
+        psbt_input: psbt::Input,
+        satisfaction_weight: usize,
+        sequence: Sequence,
+    ) -> Result<&mut Self, AddForeignUtxoError> {
+        if psbt_input.witness_utxo.is_none() {
+            match psbt_input.non_witness_utxo.as_ref() {
+                Some(tx) => {
+                    if tx.txid() != outpoint.txid {
+                        return Err(AddForeignUtxoError::InvalidTxid {
+                            input_txid: tx.txid(),
+                            foreign_utxo: outpoint,
+                        });
+                    }
+                    if tx.output.len() <= outpoint.vout as usize {
+                        return Err(AddForeignUtxoError::InvalidOutpoint(outpoint));
+                    }
+                }
+                None => {
+                    return Err(AddForeignUtxoError::MissingUtxo);
+                }
+            }
+        }
+
+        self.params.utxos.push(WeightedUtxo {
+            satisfaction_weight,
+            utxo: Utxo::Foreign {
+                outpoint,
+                sequence: Some(sequence),
+                psbt_input: Box::new(psbt_input),
+            },
+        });
+
+        Ok(self)
+    }
+
+    /// Only spend utxos added by [`add_utxo`].
+    ///
+    /// The wallet will **not** add additional utxos to the transaction even if they are needed to
+    /// make the transaction valid.
+    ///
+    /// [`add_utxo`]: Self::add_utxo
+    pub fn manually_selected_only(&mut self) -> &mut Self {
+        self.params.manually_selected_only = true;
+        self
+    }
+
+    /// Replace the internal list of unspendable utxos with a new list
+    ///
+    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
+    /// have priority over these. See the docs of the two linked methods for more details.
+    pub fn unspendable(&mut self, unspendable: Vec<OutPoint>) -> &mut Self {
+        self.params.unspendable = unspendable.into_iter().collect();
+        self
+    }
+
+    /// Add a utxo to the internal list of unspendable utxos
+    ///
+    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`]
+    /// have priority over this. See the docs of the two linked methods for more details.
+    pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self {
+        self.params.unspendable.insert(unspendable);
+        self
+    }
+
+    /// Sign with a specific sig hash
+    ///
+    /// **Use this option very carefully**
+    pub fn sighash(&mut self, sighash: psbt::PsbtSighashType) -> &mut Self {
+        self.params.sighash = Some(sighash);
+        self
+    }
+
+    /// Choose the ordering for inputs and outputs of the transaction
+    pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self {
+        self.params.ordering = ordering;
+        self
+    }
+
+    /// Use a specific nLockTime while creating the transaction
+    ///
+    /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
+    pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self {
+        self.params.locktime = Some(locktime);
+        self
+    }
+
+    /// Build a transaction with a specific version
+    ///
+    /// The `version` should always be greater than `0` and greater than `1` if the wallet's
+    /// descriptors contain an "older" (OP_CSV) operator.
+    pub fn version(&mut self, version: i32) -> &mut Self {
+        self.params.version = Some(Version(version));
+        self
+    }
+
+    /// Do not spend change outputs
+    ///
+    /// This effectively adds all the change outputs to the "unspendable" list. See
+    /// [`TxBuilder::unspendable`].
+    pub fn do_not_spend_change(&mut self) -> &mut Self {
+        self.params.change_policy = ChangeSpendPolicy::ChangeForbidden;
+        self
+    }
+
+    /// Only spend change outputs
+    ///
+    /// This effectively adds all the non-change outputs to the "unspendable" list. See
+    /// [`TxBuilder::unspendable`].
+    pub fn only_spend_change(&mut self) -> &mut Self {
+        self.params.change_policy = ChangeSpendPolicy::OnlyChange;
+        self
+    }
+
+    /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
+    /// [`TxBuilder::only_spend_change`] for some shortcuts.
+    pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self {
+        self.params.change_policy = change_policy;
+        self
+    }
+
+    /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field when spending from
+    /// SegWit descriptors.
+    ///
+    /// This reduces the size of the PSBT, but some signers might reject them due to the lack of
+    /// the `non_witness_utxo`.
+    pub fn only_witness_utxo(&mut self) -> &mut Self {
+        self.params.only_witness_utxo = true;
+        self
+    }
+
+    /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and
+    /// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields.
+    ///
+    /// This is useful for signers which always require it, like ColdCard hardware wallets.
+    pub fn include_output_redeem_witness_script(&mut self) -> &mut Self {
+        self.params.include_output_redeem_witness_script = true;
+        self
+    }
+
+    /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external
+    /// and internal descriptors
+    ///
+    /// This is useful for offline signers that take part to a multisig. Some hardware wallets like
+    /// BitBox and ColdCard are known to require this.
+    pub fn add_global_xpubs(&mut self) -> &mut Self {
+        self.params.add_global_xpubs = true;
+        self
+    }
+
+    /// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and the change policy.
+    pub fn drain_wallet(&mut self) -> &mut Self {
+        self.params.drain_wallet = true;
+        self
+    }
+
+    /// Choose the coin selection algorithm
+    ///
+    /// Overrides the [`DefaultCoinSelectionAlgorithm`].
+    ///
+    /// Note that this function consumes the builder and returns it so it is usually best to put this as the first call on the builder.
+    pub fn coin_selection<P: CoinSelectionAlgorithm>(
+        self,
+        coin_selection: P,
+    ) -> TxBuilder<'a, P, Ctx> {
+        TxBuilder {
+            wallet: self.wallet,
+            params: self.params,
+            coin_selection,
+            phantom: PhantomData,
+        }
+    }
+
+    /// Enable signaling RBF
+    ///
+    /// This will use the default nSequence value of `0xFFFFFFFD`.
+    pub fn enable_rbf(&mut self) -> &mut Self {
+        self.params.rbf = Some(RbfValue::Default);
+        self
+    }
+
+    /// Enable signaling RBF with a specific nSequence value
+    ///
+    /// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
+    /// and the given `nsequence` is lower than the CSV value.
+    ///
+    /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
+    /// be a valid nSequence to signal RBF.
+    pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
+        self.params.rbf = Some(RbfValue::Value(nsequence));
+        self
+    }
+
+    /// Set the current blockchain height.
+    ///
+    /// This will be used to:
+    /// 1. Set the nLockTime for preventing fee sniping.
+    /// **Note**: This will be ignored if you manually specify a nlocktime using [`TxBuilder::nlocktime`].
+    /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not
+    ///    mature at `current_height`, we ignore them in the coin selection.
+    ///    If you want to create a transaction that spends immature coinbase inputs, manually
+    ///    add them using [`TxBuilder::add_utxos`].
+    ///
+    /// In both cases, if you don't provide a current height, we use the last sync height.
+    pub fn current_height(&mut self, height: u32) -> &mut Self {
+        self.params.current_height =
+            Some(absolute::LockTime::from_height(height).expect("Invalid height"));
+        self
+    }
+
+    /// 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.
+    pub fn allow_dust(&mut self, allow_dust: bool) -> &mut Self {
+        self.params.allow_dust = allow_dust;
+        self
+    }
+}
+
+impl<'a, Cs: CoinSelectionAlgorithm, Ctx> TxBuilder<'a, Cs, Ctx> {
+    /// Finish building the transaction.
+    ///
+    /// Returns a new [`Psbt`] per [`BIP174`].
+    ///
+    /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
+    pub fn finish(self) -> Result<Psbt, CreateTxError> {
+        self.wallet
+            .borrow_mut()
+            .create_tx(self.coin_selection, self.params)
+    }
+}
+
+#[derive(Debug)]
+/// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`]
+pub enum AddUtxoError {
+    /// Happens when trying to spend an UTXO that is not in the internal database
+    UnknownUtxo(OutPoint),
+}
+
+impl fmt::Display for AddUtxoError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::UnknownUtxo(outpoint) => write!(
+                f,
+                "UTXO not found in the internal database for txid: {} with vout: {}",
+                outpoint.txid, outpoint.vout
+            ),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AddUtxoError {}
+
+#[derive(Debug)]
+/// Error returned from [`TxBuilder::add_foreign_utxo`].
+pub enum AddForeignUtxoError {
+    /// Foreign utxo outpoint txid does not match PSBT input txid
+    InvalidTxid {
+        /// PSBT input txid
+        input_txid: Txid,
+        /// Foreign UTXO outpoint
+        foreign_utxo: OutPoint,
+    },
+    /// Requested outpoint doesn't exist in the tx (vout greater than available outputs)
+    InvalidOutpoint(OutPoint),
+    /// Foreign utxo missing witness_utxo or non_witness_utxo
+    MissingUtxo,
+}
+
+impl fmt::Display for AddForeignUtxoError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::InvalidTxid {
+                input_txid,
+                foreign_utxo,
+            } => write!(
+                f,
+                "Foreign UTXO outpoint txid: {} does not match PSBT input txid: {}",
+                foreign_utxo.txid, input_txid,
+            ),
+            Self::InvalidOutpoint(outpoint) => write!(
+                f,
+                "Requested outpoint doesn't exist for txid: {} with vout: {}",
+                outpoint.txid, outpoint.vout,
+            ),
+            Self::MissingUtxo => write!(f, "Foreign utxo missing witness_utxo or non_witness_utxo"),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AddForeignUtxoError {}
+
+#[derive(Debug)]
+/// Error returned from [`TxBuilder::allow_shrinking`]
+pub enum AllowShrinkingError {
+    /// Script/PubKey was not in the original transaction
+    MissingScriptPubKey(ScriptBuf),
+}
+
+impl fmt::Display for AllowShrinkingError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::MissingScriptPubKey(script_buf) => write!(
+                f,
+                "Script/PubKey was not in the original transaction: {}",
+                script_buf,
+            ),
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for AllowShrinkingError {}
+
+impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
+    /// Replace the recipients already added with a new list
+    pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, Amount)>) -> &mut Self {
+        self.params.recipients = recipients
+            .into_iter()
+            .map(|(script, amount)| (script, amount.to_sat()))
+            .collect();
+        self
+    }
+
+    /// Add a recipient to the internal list
+    pub fn add_recipient(&mut self, script_pubkey: ScriptBuf, amount: Amount) -> &mut Self {
+        self.params
+            .recipients
+            .push((script_pubkey, amount.to_sat()));
+        self
+    }
+
+    /// Add data as an output, using OP_RETURN
+    pub fn add_data<T: AsRef<PushBytes>>(&mut self, data: &T) -> &mut Self {
+        let script = ScriptBuf::new_op_return(data);
+        self.add_recipient(script, Amount::ZERO);
+        self
+    }
+
+    /// Sets the address to *drain* excess coins to.
+    ///
+    /// Usually, when there are excess coins they are sent to a change address generated by the
+    /// wallet. This option replaces the usual change address with an arbitrary `script_pubkey` of
+    /// your choosing. Just as with a change output, if the drain output is not needed (the excess
+    /// coins are too small) it will not be included in the resulting transaction. The only
+    /// difference is that it is valid to use `drain_to` without setting any ordinary recipients
+    /// with [`add_recipient`] (but it is perfectly fine to add recipients as well).
+    ///
+    /// If you choose not to set any recipients, you should either provide the utxos that the
+    /// transaction should spend via [`add_utxos`], or set [`drain_wallet`] to spend all of them.
+    ///
+    /// When bumping the fees of a transaction made with this option, you probably want to
+    /// use [`allow_shrinking`] to allow this output to be reduced to pay for the extra fees.
+    ///
+    /// # Example
+    ///
+    /// `drain_to` is very useful for draining all the coins in a wallet with [`drain_wallet`] to a
+    /// single address.
+    ///
+    /// ```
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use bdk_wallet::*;
+    /// # use bdk_wallet::wallet::ChangeSet;
+    /// # use bdk_wallet::wallet::error::CreateTxError;
+    /// # use bdk_wallet::wallet::tx_builder::CreateTx;
+    /// # use bdk_persist::PersistBackend;
+    /// # use anyhow::Error;
+    /// # let to_address =
+    /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
+    ///     .unwrap()
+    ///     .assume_checked();
+    /// # let mut wallet = doctest_wallet!();
+    /// let mut tx_builder = wallet.build_tx();
+    ///
+    /// tx_builder
+    ///     // Spend all outputs in this wallet.
+    ///     .drain_wallet()
+    ///     // Send the excess (which is all the coins minus the fee) to this address.
+    ///     .drain_to(to_address.script_pubkey())
+    ///     .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate"))
+    ///     .enable_rbf();
+    /// let psbt = tx_builder.finish()?;
+    /// # Ok::<(), anyhow::Error>(())
+    /// ```
+    ///
+    /// [`allow_shrinking`]: Self::allow_shrinking
+    /// [`add_recipient`]: Self::add_recipient
+    /// [`add_utxos`]: Self::add_utxos
+    /// [`drain_wallet`]: Self::drain_wallet
+    pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self {
+        self.params.drain_to = Some(script_pubkey);
+        self
+    }
+}
+
+// methods supported only by bump_fee
+impl<'a> TxBuilder<'a, DefaultCoinSelectionAlgorithm, BumpFee> {
+    /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this
+    /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
+    /// will attempt to find a change output to shrink instead.
+    ///
+    /// **Note** that the output may shrink to below the dust limit and therefore be removed. If it is
+    /// preserved then it is currently not guaranteed to be in the same position as it was
+    /// originally.
+    ///
+    /// Returns an `Err` if `script_pubkey` can't be found among the recipients of the
+    /// transaction we are bumping.
+    pub fn allow_shrinking(
+        &mut self,
+        script_pubkey: ScriptBuf,
+    ) -> Result<&mut Self, AllowShrinkingError> {
+        match self
+            .params
+            .recipients
+            .iter()
+            .position(|(recipient_script, _)| *recipient_script == script_pubkey)
+        {
+            Some(position) => {
+                self.params.recipients.remove(position);
+                self.params.drain_to = Some(script_pubkey);
+                Ok(self)
+            }
+            None => Err(AllowShrinkingError::MissingScriptPubKey(script_pubkey)),
+        }
+    }
+}
+
+/// Ordering of the transaction's inputs and outputs
+#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
+pub enum TxOrdering {
+    /// Randomized (default)
+    #[default]
+    Shuffle,
+    /// Unchanged
+    Untouched,
+    /// BIP69 / Lexicographic
+    Bip69Lexicographic,
+}
+
+impl TxOrdering {
+    /// Sort transaction inputs and outputs by [`TxOrdering`] variant
+    pub fn sort_tx(&self, tx: &mut Transaction) {
+        match self {
+            TxOrdering::Untouched => {}
+            TxOrdering::Shuffle => {
+                use rand::seq::SliceRandom;
+                let mut rng = rand::thread_rng();
+                tx.input.shuffle(&mut rng);
+                tx.output.shuffle(&mut rng);
+            }
+            TxOrdering::Bip69Lexicographic => {
+                tx.input.sort_unstable_by_key(|txin| {
+                    (txin.previous_output.txid, txin.previous_output.vout)
+                });
+                tx.output
+                    .sort_unstable_by_key(|txout| (txout.value, txout.script_pubkey.clone()));
+            }
+        }
+    }
+}
+
+/// Transaction version
+///
+/// Has a default value of `1`
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
+pub(crate) struct Version(pub(crate) i32);
+
+impl Default for Version {
+    fn default() -> Self {
+        Version(1)
+    }
+}
+
+/// RBF nSequence value
+///
+/// Has a default value of `0xFFFFFFFD`
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
+pub(crate) enum RbfValue {
+    Default,
+    Value(Sequence),
+}
+
+impl RbfValue {
+    pub(crate) fn get_value(&self) -> Sequence {
+        match self {
+            RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
+            RbfValue::Value(v) => *v,
+        }
+    }
+}
+
+/// Policy regarding the use of change outputs when creating a transaction
+#[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
+pub enum ChangeSpendPolicy {
+    /// Use both change and non-change outputs (default)
+    #[default]
+    ChangeAllowed,
+    /// Only use change outputs (see [`TxBuilder::only_spend_change`])
+    OnlyChange,
+    /// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
+    ChangeForbidden,
+}
+
+impl ChangeSpendPolicy {
+    pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool {
+        match self {
+            ChangeSpendPolicy::ChangeAllowed => true,
+            ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal,
+            ChangeSpendPolicy::ChangeForbidden => utxo.keychain == KeychainKind::External,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    const ORDERING_TEST_TX: &str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\
+                                    85d1fd600f0100000000ffffffffc26f3eb7932f7acddc5ddd26602b77e75160\
+                                    79b03090a16e2c2f5485d1fd600f0000000000ffffffff571fb3e02278217852\
+                                    dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e0500000000ffffffff\
+                                    03e80300000000000002aaeee80300000000000001aa200300000000000001ff\
+                                    00000000";
+    macro_rules! ordering_test_tx {
+        () => {
+            deserialize::<bitcoin::Transaction>(&Vec::<u8>::from_hex(ORDERING_TEST_TX).unwrap())
+                .unwrap()
+        };
+    }
+
+    use bdk_chain::ConfirmationTime;
+    use bitcoin::consensus::deserialize;
+    use bitcoin::hex::FromHex;
+    use bitcoin::TxOut;
+
+    use super::*;
+
+    #[test]
+    fn test_output_ordering_default_shuffle() {
+        assert_eq!(TxOrdering::default(), TxOrdering::Shuffle);
+    }
+
+    #[test]
+    fn test_output_ordering_untouched() {
+        let original_tx = ordering_test_tx!();
+        let mut tx = original_tx.clone();
+
+        TxOrdering::Untouched.sort_tx(&mut tx);
+
+        assert_eq!(original_tx, tx);
+    }
+
+    #[test]
+    fn test_output_ordering_shuffle() {
+        let original_tx = ordering_test_tx!();
+        let mut tx = original_tx.clone();
+
+        (0..40)
+            .find(|_| {
+                TxOrdering::Shuffle.sort_tx(&mut tx);
+                original_tx.input != tx.input
+            })
+            .expect("it should have moved the inputs at least once");
+
+        let mut tx = original_tx.clone();
+        (0..40)
+            .find(|_| {
+                TxOrdering::Shuffle.sort_tx(&mut tx);
+                original_tx.output != tx.output
+            })
+            .expect("it should have moved the outputs at least once");
+    }
+
+    #[test]
+    fn test_output_ordering_bip69() {
+        use core::str::FromStr;
+
+        let original_tx = ordering_test_tx!();
+        let mut tx = original_tx;
+
+        TxOrdering::Bip69Lexicographic.sort_tx(&mut tx);
+
+        assert_eq!(
+            tx.input[0].previous_output,
+            bitcoin::OutPoint::from_str(
+                "0e53ec5dfb2cb8a71fec32dc9a634a35b7e24799295ddd5278217822e0b31f57:5"
+            )
+            .unwrap()
+        );
+        assert_eq!(
+            tx.input[1].previous_output,
+            bitcoin::OutPoint::from_str(
+                "0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:0"
+            )
+            .unwrap()
+        );
+        assert_eq!(
+            tx.input[2].previous_output,
+            bitcoin::OutPoint::from_str(
+                "0f60fdd185542f2c6ea19030b0796051e7772b6026dd5ddccd7a2f93b73e6fc2:1"
+            )
+            .unwrap()
+        );
+
+        assert_eq!(tx.output[0].value.to_sat(), 800);
+        assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
+        assert_eq!(
+            tx.output[2].script_pubkey,
+            ScriptBuf::from(vec![0xAA, 0xEE])
+        );
+    }
+
+    fn get_test_utxos() -> Vec<LocalOutput> {
+        use bitcoin::hashes::Hash;
+
+        vec![
+            LocalOutput {
+                outpoint: OutPoint {
+                    txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
+                    vout: 0,
+                },
+                txout: TxOut::NULL,
+                keychain: KeychainKind::External,
+                is_spent: false,
+                confirmation_time: ConfirmationTime::Unconfirmed { last_seen: 0 },
+                derivation_index: 0,
+            },
+            LocalOutput {
+                outpoint: OutPoint {
+                    txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
+                    vout: 1,
+                },
+                txout: TxOut::NULL,
+                keychain: KeychainKind::Internal,
+                is_spent: false,
+                confirmation_time: ConfirmationTime::Confirmed {
+                    height: 32,
+                    time: 42,
+                },
+                derivation_index: 1,
+            },
+        ]
+    }
+
+    #[test]
+    fn test_change_spend_policy_default() {
+        let change_spend_policy = ChangeSpendPolicy::default();
+        let filtered = get_test_utxos()
+            .into_iter()
+            .filter(|u| change_spend_policy.is_satisfied_by(u))
+            .count();
+
+        assert_eq!(filtered, 2);
+    }
+
+    #[test]
+    fn test_change_spend_policy_no_internal() {
+        let change_spend_policy = ChangeSpendPolicy::ChangeForbidden;
+        let filtered = get_test_utxos()
+            .into_iter()
+            .filter(|u| change_spend_policy.is_satisfied_by(u))
+            .collect::<Vec<_>>();
+
+        assert_eq!(filtered.len(), 1);
+        assert_eq!(filtered[0].keychain, KeychainKind::External);
+    }
+
+    #[test]
+    fn test_change_spend_policy_only_internal() {
+        let change_spend_policy = ChangeSpendPolicy::OnlyChange;
+        let filtered = get_test_utxos()
+            .into_iter()
+            .filter(|u| change_spend_policy.is_satisfied_by(u))
+            .collect::<Vec<_>>();
+
+        assert_eq!(filtered.len(), 1);
+        assert_eq!(filtered[0].keychain, KeychainKind::Internal);
+    }
+
+    #[test]
+    fn test_default_tx_version_1() {
+        let version = Version::default();
+        assert_eq!(version.0, 1);
+    }
+}
diff --git a/crates/wallet/src/wallet/utils.rs b/crates/wallet/src/wallet/utils.rs
new file mode 100644 (file)
index 0000000..208a88d
--- /dev/null
@@ -0,0 +1,185 @@
+// Bitcoin Dev Kit
+// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
+//
+// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+use bitcoin::secp256k1::{All, Secp256k1};
+use bitcoin::{absolute, Script, Sequence};
+
+use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
+
+/// Trait to check if a value is below the dust limit.
+/// We are performing dust value calculation for a given script public key using rust-bitcoin to
+/// keep it compatible with network dust rate
+// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
+// instead of a <= etc.
+pub trait IsDust {
+    /// Check whether or not a value is below dust limit
+    fn is_dust(&self, script: &Script) -> bool;
+}
+
+impl IsDust for u64 {
+    fn is_dust(&self, script: &Script) -> bool {
+        *self < script.dust_value().to_sat()
+    }
+}
+
+pub struct After {
+    pub current_height: Option<u32>,
+    pub assume_height_reached: bool,
+}
+
+impl After {
+    pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After {
+        After {
+            current_height,
+            assume_height_reached,
+        }
+    }
+}
+
+pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
+    // The RBF value must enable relative timelocks
+    if !rbf.is_relative_lock_time() {
+        return false;
+    }
+
+    // Both values should be represented in the same unit (either time-based or
+    // block-height based)
+    if rbf.is_time_locked() != csv.is_time_locked() {
+        return false;
+    }
+
+    // The value should be at least `csv`
+    if rbf < csv {
+        return false;
+    }
+
+    true
+}
+
+impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
+    fn check_after(&self, n: absolute::LockTime) -> bool {
+        if let Some(current_height) = self.current_height {
+            current_height >= n.to_consensus_u32()
+        } else {
+            self.assume_height_reached
+        }
+    }
+}
+
+pub struct Older {
+    pub current_height: Option<u32>,
+    pub create_height: Option<u32>,
+    pub assume_height_reached: bool,
+}
+
+impl Older {
+    pub(crate) fn new(
+        current_height: Option<u32>,
+        create_height: Option<u32>,
+        assume_height_reached: bool,
+    ) -> Older {
+        Older {
+            current_height,
+            create_height,
+            assume_height_reached,
+        }
+    }
+}
+
+impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
+    fn check_older(&self, n: Sequence) -> bool {
+        if let Some(current_height) = self.current_height {
+            // TODO: test >= / >
+            current_height
+                >= self
+                    .create_height
+                    .unwrap_or(0)
+                    .checked_add(n.to_consensus_u32())
+                    .expect("Overflowing addition")
+        } else {
+            self.assume_height_reached
+        }
+    }
+}
+
+pub(crate) type SecpCtx = Secp256k1<All>;
+
+#[cfg(test)]
+mod test {
+    // When nSequence is lower than this flag the timelock is interpreted as block-height-based,
+    // otherwise it's time-based
+    pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
+
+    use super::{check_nsequence_rbf, IsDust};
+    use crate::bitcoin::{Address, Network, Sequence};
+    use core::str::FromStr;
+
+    #[test]
+    fn test_is_dust() {
+        let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
+            .unwrap()
+            .require_network(Network::Bitcoin)
+            .unwrap()
+            .script_pubkey();
+        assert!(script_p2pkh.is_p2pkh());
+        assert!(545.is_dust(&script_p2pkh));
+        assert!(!546.is_dust(&script_p2pkh));
+
+        let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
+            .unwrap()
+            .require_network(Network::Bitcoin)
+            .unwrap()
+            .script_pubkey();
+        assert!(script_p2wpkh.is_p2wpkh());
+        assert!(293.is_dust(&script_p2wpkh));
+        assert!(!294.is_dust(&script_p2wpkh));
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_msb_set() {
+        let result = check_nsequence_rbf(Sequence(0x80000000), Sequence(5000));
+        assert!(!result);
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_lt_csv() {
+        let result = check_nsequence_rbf(Sequence(4000), Sequence(5000));
+        assert!(!result);
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_different_unit() {
+        let result =
+            check_nsequence_rbf(Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000), Sequence(5000));
+        assert!(!result);
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_mask() {
+        let result = check_nsequence_rbf(Sequence(0x3f + 10_000), Sequence(5000));
+        assert!(result);
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_same_unit_blocks() {
+        let result = check_nsequence_rbf(Sequence(10_000), Sequence(5000));
+        assert!(result);
+    }
+
+    #[test]
+    fn test_check_nsequence_rbf_same_unit_time() {
+        let result = check_nsequence_rbf(
+            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000),
+            Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000),
+        );
+        assert!(result);
+    }
+}
diff --git a/crates/wallet/tests/common.rs b/crates/wallet/tests/common.rs
new file mode 100644 (file)
index 0000000..a51dcaf
--- /dev/null
@@ -0,0 +1,172 @@
+#![allow(unused)]
+
+use bdk_chain::indexed_tx_graph::Indexer;
+use bdk_chain::{BlockId, ConfirmationTime};
+use bdk_wallet::{KeychainKind, LocalOutput, Wallet};
+use bitcoin::hashes::Hash;
+use bitcoin::{
+    transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction, TxIn, TxOut,
+    Txid,
+};
+use std::str::FromStr;
+
+// Return a fake wallet that appears to be funded for testing.
+//
+// The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
+// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+// sats are the transaction fee.
+pub fn get_funded_wallet_with_change(
+    descriptor: &str,
+    change: Option<&str>,
+) -> (Wallet, bitcoin::Txid) {
+    let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
+    let change_address = wallet.peek_address(KeychainKind::External, 0).address;
+    let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
+        .expect("address")
+        .require_network(Network::Regtest)
+        .unwrap();
+
+    let tx0 = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: bitcoin::absolute::LockTime::ZERO,
+        input: vec![TxIn {
+            previous_output: OutPoint {
+                txid: Txid::all_zeros(),
+                vout: 0,
+            },
+            script_sig: Default::default(),
+            sequence: Default::default(),
+            witness: Default::default(),
+        }],
+        output: vec![TxOut {
+            value: Amount::from_sat(76_000),
+            script_pubkey: change_address.script_pubkey(),
+        }],
+    };
+
+    let tx1 = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: bitcoin::absolute::LockTime::ZERO,
+        input: vec![TxIn {
+            previous_output: OutPoint {
+                txid: tx0.txid(),
+                vout: 0,
+            },
+            script_sig: Default::default(),
+            sequence: Default::default(),
+            witness: Default::default(),
+        }],
+        output: vec![
+            TxOut {
+                value: Amount::from_sat(50_000),
+                script_pubkey: change_address.script_pubkey(),
+            },
+            TxOut {
+                value: Amount::from_sat(25_000),
+                script_pubkey: sendto_address.script_pubkey(),
+            },
+        ],
+    };
+
+    wallet
+        .insert_checkpoint(BlockId {
+            height: 1_000,
+            hash: BlockHash::all_zeros(),
+        })
+        .unwrap();
+    wallet
+        .insert_checkpoint(BlockId {
+            height: 2_000,
+            hash: BlockHash::all_zeros(),
+        })
+        .unwrap();
+    wallet
+        .insert_tx(
+            tx0,
+            ConfirmationTime::Confirmed {
+                height: 1_000,
+                time: 100,
+            },
+        )
+        .unwrap();
+    wallet
+        .insert_tx(
+            tx1.clone(),
+            ConfirmationTime::Confirmed {
+                height: 2_000,
+                time: 200,
+            },
+        )
+        .unwrap();
+
+    (wallet, tx1.txid())
+}
+
+// Return a fake wallet that appears to be funded for testing.
+//
+// The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
+// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+// sats are the transaction fee.
+pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
+    get_funded_wallet_with_change(descriptor, None)
+}
+
+pub fn get_test_wpkh() -> &'static str {
+    "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
+}
+
+pub fn get_test_single_sig_csv() -> &'static str {
+    // and(pk(Alice),older(6))
+    "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
+}
+
+pub fn get_test_a_or_b_plus_csv() -> &'static str {
+    // or(pk(Alice),and(pk(Bob),older(144)))
+    "wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),and_v(v:pk(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(144))))"
+}
+
+pub fn get_test_single_sig_cltv() -> &'static str {
+    // and(pk(Alice),after(100000))
+    "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
+}
+
+pub fn get_test_tr_single_sig() -> &'static str {
+    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
+}
+
+pub fn get_test_tr_with_taptree() -> &'static str {
+    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
+}
+
+pub fn get_test_tr_with_taptree_both_priv() -> &'static str {
+    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV)})"
+}
+
+pub fn get_test_tr_repeated_key() -> &'static str {
+    "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100)),and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(200))})"
+}
+
+pub fn get_test_tr_single_sig_xprv() -> &'static str {
+    "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
+}
+
+pub fn get_test_tr_with_taptree_xprv() -> &'static str {
+    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
+}
+
+pub fn get_test_tr_dup_keys() -> &'static str {
+    "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
+}
+
+/// Construct a new [`FeeRate`] from the given raw `sat_vb` feerate. This is
+/// useful in cases where we want to create a feerate from a `f64`, as the
+/// traditional [`FeeRate::from_sat_per_vb`] method will only accept an integer.
+///
+/// **Note** this 'quick and dirty' conversion should only be used when the input
+/// parameter has units of `satoshis/vbyte` **AND** is not expected to overflow,
+/// or else the resulting value will be inaccurate.
+pub fn feerate_unchecked(sat_vb: f64) -> FeeRate {
+    // 1 sat_vb / 4wu_vb * 1000kwu_wu = 250 sat_kwu
+    let sat_kwu = (sat_vb * 250.0).ceil() as u64;
+    FeeRate::from_sat_per_kwu(sat_kwu)
+}
diff --git a/crates/wallet/tests/psbt.rs b/crates/wallet/tests/psbt.rs
new file mode 100644 (file)
index 0000000..3652f10
--- /dev/null
@@ -0,0 +1,221 @@
+use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, TxIn};
+use bdk_wallet::{psbt, KeychainKind, SignOptions};
+use core::str::FromStr;
+mod common;
+use common::*;
+
+// from bip 174
+const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
+
+#[test]
+#[should_panic(expected = "InputIndexOutOfRange")]
+fn test_psbt_malformed_psbt_input_legacy() {
+    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let send_to = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
+    let mut psbt = builder.finish().unwrap();
+    psbt.inputs.push(psbt_bip.inputs[0].clone());
+    let options = SignOptions {
+        trust_witness_utxo: true,
+        ..Default::default()
+    };
+    let _ = wallet.sign(&mut psbt, options).unwrap();
+}
+
+#[test]
+#[should_panic(expected = "InputIndexOutOfRange")]
+fn test_psbt_malformed_psbt_input_segwit() {
+    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let send_to = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
+    let mut psbt = builder.finish().unwrap();
+    psbt.inputs.push(psbt_bip.inputs[1].clone());
+    let options = SignOptions {
+        trust_witness_utxo: true,
+        ..Default::default()
+    };
+    let _ = wallet.sign(&mut psbt, options).unwrap();
+}
+
+#[test]
+#[should_panic(expected = "InputIndexOutOfRange")]
+fn test_psbt_malformed_tx_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let send_to = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
+    let mut psbt = builder.finish().unwrap();
+    psbt.unsigned_tx.input.push(TxIn::default());
+    let options = SignOptions {
+        trust_witness_utxo: true,
+        ..Default::default()
+    };
+    let _ = wallet.sign(&mut psbt, options).unwrap();
+}
+
+#[test]
+fn test_psbt_sign_with_finalized() {
+    let psbt_bip = Psbt::from_str(PSBT_STR).unwrap();
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let send_to = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000));
+    let mut psbt = builder.finish().unwrap();
+
+    // add a finalized input
+    psbt.inputs.push(psbt_bip.inputs[0].clone());
+    psbt.unsigned_tx
+        .input
+        .push(psbt_bip.unsigned_tx.input[0].clone());
+
+    let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
+}
+
+#[test]
+fn test_psbt_fee_rate_with_witness_utxo() {
+    use psbt::PsbtUtils;
+
+    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
+
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(expected_fee_rate);
+    let mut psbt = builder.finish().unwrap();
+    let fee_amount = psbt.fee_amount();
+    assert!(fee_amount.is_some());
+
+    let unfinalized_fee_rate = psbt.fee_rate().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let finalized_fee_rate = psbt.fee_rate().unwrap();
+    assert!(finalized_fee_rate >= expected_fee_rate);
+    assert!(finalized_fee_rate < unfinalized_fee_rate);
+}
+
+#[test]
+fn test_psbt_fee_rate_with_nonwitness_utxo() {
+    use psbt::PsbtUtils;
+
+    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
+
+    let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(expected_fee_rate);
+    let mut psbt = builder.finish().unwrap();
+    let fee_amount = psbt.fee_amount();
+    assert!(fee_amount.is_some());
+    let unfinalized_fee_rate = psbt.fee_rate().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let finalized_fee_rate = psbt.fee_rate().unwrap();
+    assert!(finalized_fee_rate >= expected_fee_rate);
+    assert!(finalized_fee_rate < unfinalized_fee_rate);
+}
+
+#[test]
+fn test_psbt_fee_rate_with_missing_txout() {
+    use psbt::PsbtUtils;
+
+    let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
+
+    let (mut wpkh_wallet,  _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wpkh_wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wpkh_wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(expected_fee_rate);
+    let mut wpkh_psbt = builder.finish().unwrap();
+
+    wpkh_psbt.inputs[0].witness_utxo = None;
+    wpkh_psbt.inputs[0].non_witness_utxo = None;
+    assert!(wpkh_psbt.fee_amount().is_none());
+    assert!(wpkh_psbt.fee_rate().is_none());
+
+    let (mut pkh_wallet,  _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = pkh_wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(expected_fee_rate);
+    let mut pkh_psbt = builder.finish().unwrap();
+
+    pkh_psbt.inputs[0].non_witness_utxo = None;
+    assert!(pkh_psbt.fee_amount().is_none());
+    assert!(pkh_psbt.fee_rate().is_none());
+}
+
+#[test]
+fn test_psbt_multiple_internalkey_signers() {
+    use bdk_wallet::signer::{SignerContext, SignerOrdering, SignerWrapper};
+    use bdk_wallet::KeychainKind;
+    use bitcoin::key::TapTweak;
+    use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey};
+    use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType};
+    use bitcoin::{PrivateKey, TxOut};
+    use std::sync::Arc;
+
+    let secp = Secp256k1::new();
+    let wif = "cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG";
+    let desc = format!("tr({})", wif);
+    let prv = PrivateKey::from_wif(wif).unwrap();
+    let keypair = Keypair::from_secret_key(&secp, &prv.inner);
+
+    let (mut wallet, _) = get_funded_wallet(&desc);
+    let to_spend = wallet.get_balance().total();
+    let send_to = wallet.peek_address(KeychainKind::External, 0);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(send_to.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+    let unsigned_tx = psbt.unsigned_tx.clone();
+
+    // Adds a signer for the wrong internal key, bdk should not use this key to sign
+    wallet.add_signer(
+        KeychainKind::External,
+        // A signerordering lower than 100, bdk will use this signer first
+        SignerOrdering(0),
+        Arc::new(SignerWrapper::new(
+            PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(),
+            SignerContext::Tap {
+                is_internal_key: true,
+            },
+        )),
+    );
+    let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
+    assert!(finalized);
+
+    // To verify, we need the signature, message, and pubkey
+    let witness = psbt.inputs[0].final_script_witness.as_ref().unwrap();
+    assert!(!witness.is_empty());
+    let signature = schnorr::Signature::from_slice(witness.iter().next().unwrap()).unwrap();
+
+    // the prevout we're spending
+    let prevouts = &[TxOut {
+        script_pubkey: send_to.script_pubkey(),
+        value: to_spend,
+    }];
+    let prevouts = Prevouts::All(prevouts);
+    let input_index = 0;
+    let mut sighash_cache = SighashCache::new(unsigned_tx);
+    let sighash = sighash_cache
+        .taproot_key_spend_signature_hash(input_index, &prevouts, TapSighashType::Default)
+        .unwrap();
+    let message = Message::from(sighash);
+
+    // add tweak. this was taken from `signer::sign_psbt_schnorr`
+    let keypair = keypair.tap_tweak(&secp, None).to_inner();
+    let (xonlykey, _parity) = XOnlyPublicKey::from_keypair(&keypair);
+
+    // Must verify if we used the correct key to sign
+    let verify_res = secp.verify_schnorr(&signature, &message, &xonlykey);
+    assert!(verify_res.is_ok(), "The wrong internal key was used");
+}
diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs
new file mode 100644 (file)
index 0000000..58b2506
--- /dev/null
@@ -0,0 +1,3947 @@
+use std::str::FromStr;
+
+use assert_matches::assert_matches;
+use bdk_chain::collections::BTreeMap;
+use bdk_chain::COINBASE_MATURITY;
+use bdk_chain::{BlockId, ConfirmationTime};
+use bdk_wallet::descriptor::{calc_checksum, IntoWalletDescriptor};
+use bdk_wallet::psbt::PsbtUtils;
+use bdk_wallet::signer::{SignOptions, SignerError};
+use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
+use bdk_wallet::wallet::error::CreateTxError;
+use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
+use bdk_wallet::wallet::NewError;
+use bdk_wallet::wallet::{AddressInfo, Balance, Wallet};
+use bdk_wallet::KeychainKind;
+use bitcoin::hashes::Hash;
+use bitcoin::key::Secp256k1;
+use bitcoin::psbt;
+use bitcoin::script::PushBytesBuf;
+use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
+use bitcoin::taproot::TapNodeHash;
+use bitcoin::{
+    absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf,
+    Sequence, Transaction, TxIn, TxOut, Txid, Weight,
+};
+
+mod common;
+use common::*;
+
+fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint {
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let tx = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: addr.script_pubkey(),
+            value: Amount::from_sat(value),
+        }],
+    };
+
+    wallet.insert_tx(tx.clone(), height).unwrap();
+
+    OutPoint {
+        txid: tx.txid(),
+        vout: 0,
+    }
+}
+
+fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
+    let latest_cp = wallet.latest_checkpoint();
+    let height = latest_cp.height();
+    let anchor = if height == 0 {
+        ConfirmationTime::Unconfirmed { last_seen: 0 }
+    } else {
+        ConfirmationTime::Confirmed { height, time: 0 }
+    };
+    receive_output(wallet, value, anchor)
+}
+
+// The satisfaction size of a P2WPKH is 112 WU =
+// 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len)
+// On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for
+// a total of 105 WU.
+// Here, we push just once for simplicity, so we have to add an extra byte for the missing
+// OP_PUSH.
+const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
+
+const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
+
+#[test]
+fn load_recovers_wallet() {
+    let temp_dir = tempfile::tempdir().expect("must create tempdir");
+    let file_path = temp_dir.path().join("store.db");
+
+    // create new wallet
+    let wallet_spk_index = {
+        let db = bdk_file_store::Store::create_new(DB_MAGIC, &file_path).expect("must create db");
+        let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet)
+            .expect("must init wallet");
+
+        wallet.reveal_next_address(KeychainKind::External).unwrap();
+        wallet.spk_index().clone()
+    };
+
+    // recover wallet
+    {
+        let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
+        let wallet = Wallet::load(db).expect("must recover wallet");
+        assert_eq!(wallet.network(), Network::Testnet);
+        assert_eq!(
+            wallet.spk_index().keychains().collect::<Vec<_>>(),
+            wallet_spk_index.keychains().collect::<Vec<_>>()
+        );
+        assert_eq!(
+            wallet.spk_index().last_revealed_indices(),
+            wallet_spk_index.last_revealed_indices()
+        );
+        let secp = Secp256k1::new();
+        assert_eq!(
+            *wallet.get_descriptor_for_keychain(KeychainKind::External),
+            get_test_tr_single_sig_xprv()
+                .into_wallet_descriptor(&secp, wallet.network())
+                .unwrap()
+                .0
+        );
+    }
+
+    // `new` can only be called on empty db
+    {
+        let db = bdk_file_store::Store::open(DB_MAGIC, &file_path).expect("must recover db");
+        let result = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet);
+        assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
+    }
+}
+
+#[test]
+fn new_or_load() {
+    let temp_dir = tempfile::tempdir().expect("must create tempdir");
+    let file_path = temp_dir.path().join("store.db");
+
+    // init wallet when non-existent
+    let wallet_keychains: BTreeMap<_, _> = {
+        let db = bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path)
+            .expect("must create db");
+        let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
+            .expect("must init wallet");
+        wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
+    };
+
+    // wrong network
+    {
+        let db =
+            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
+        let err = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Bitcoin)
+            .expect_err("wrong network");
+        assert!(
+            matches!(
+                err,
+                bdk_wallet::wallet::NewOrLoadError::LoadedNetworkDoesNotMatch {
+                    got: Some(Network::Testnet),
+                    expected: Network::Bitcoin
+                }
+            ),
+            "err: {}",
+            err,
+        );
+    }
+
+    // wrong genesis hash
+    {
+        let exp_blockhash = BlockHash::all_zeros();
+        let got_blockhash =
+            bitcoin::blockdata::constants::genesis_block(Network::Testnet).block_hash();
+
+        let db =
+            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
+        let err = Wallet::new_or_load_with_genesis_hash(
+            get_test_wpkh(),
+            None,
+            db,
+            Network::Testnet,
+            exp_blockhash,
+        )
+        .expect_err("wrong genesis hash");
+        assert!(
+            matches!(
+                err,
+                bdk_wallet::wallet::NewOrLoadError::LoadedGenesisDoesNotMatch { got, expected }
+                if got == Some(got_blockhash) && expected == exp_blockhash
+            ),
+            "err: {}",
+            err,
+        );
+    }
+
+    // wrong external descriptor
+    {
+        let exp_descriptor = get_test_tr_single_sig();
+        let got_descriptor = get_test_wpkh()
+            .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
+            .unwrap()
+            .0;
+
+        let db =
+            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
+        let err = Wallet::new_or_load(exp_descriptor, None, db, Network::Testnet)
+            .expect_err("wrong external descriptor");
+        assert!(
+            matches!(
+                err,
+                bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
+                if got == &Some(got_descriptor) && keychain == KeychainKind::External
+            ),
+            "err: {}",
+            err,
+        );
+    }
+
+    // wrong internal descriptor
+    {
+        let exp_descriptor = Some(get_test_tr_single_sig());
+        let got_descriptor = None;
+
+        let db =
+            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
+        let err = Wallet::new_or_load(get_test_wpkh(), exp_descriptor, db, Network::Testnet)
+            .expect_err("wrong internal descriptor");
+        assert!(
+            matches!(
+                err,
+                bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
+                if got == &got_descriptor && keychain == KeychainKind::Internal
+            ),
+            "err: {}",
+            err,
+        );
+    }
+
+    // all parameters match
+    {
+        let db =
+            bdk_file_store::Store::open_or_create_new(DB_MAGIC, &file_path).expect("must open db");
+        let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
+            .expect("must recover wallet");
+        assert_eq!(wallet.network(), Network::Testnet);
+        assert!(wallet
+            .keychains()
+            .map(|(k, v)| (*k, v.clone()))
+            .eq(wallet_keychains));
+    }
+}
+
+#[test]
+fn test_descriptor_checksum() {
+    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+    let checksum = wallet.descriptor_checksum(KeychainKind::External);
+    assert_eq!(checksum.len(), 8);
+
+    let raw_descriptor = wallet
+        .keychains()
+        .next()
+        .unwrap()
+        .1
+        .to_string()
+        .split_once('#')
+        .unwrap()
+        .0
+        .to_string();
+    assert_eq!(calc_checksum(&raw_descriptor).unwrap(), checksum);
+}
+
+#[test]
+fn test_get_funded_wallet_balance() {
+    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+
+    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
+    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+    // sats are the transaction fee.
+    assert_eq!(wallet.get_balance().confirmed, Amount::from_sat(50_000));
+}
+
+#[test]
+fn test_get_funded_wallet_sent_and_received() {
+    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+
+    let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet
+        .transactions()
+        .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node)))
+        .collect();
+    tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0));
+
+    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
+    let (sent, received) = wallet.sent_and_received(&tx);
+
+    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
+    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+    // sats are the transaction fee.
+    assert_eq!(sent.to_sat(), 76_000);
+    assert_eq!(received.to_sat(), 50_000);
+}
+
+#[test]
+fn test_get_funded_wallet_tx_fees() {
+    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+
+    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
+    let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee");
+
+    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
+    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+    // sats are the transaction fee.
+    assert_eq!(tx_fee, 1000)
+}
+
+#[test]
+fn test_get_funded_wallet_tx_fee_rate() {
+    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+
+    let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
+    let tx_fee_rate = wallet
+        .calculate_fee_rate(&tx)
+        .expect("transaction fee rate");
+
+    // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
+    // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
+    // sats are the transaction fee.
+
+    // tx weight = 452 wu, as vbytes = (452 + 3) / 4 = 113
+    // fee_rate (sats per kwu) = fee / weight = 1000sat / 0.452kwu = 2212
+    // fee_rate (sats per vbyte ceil) = fee / vsize = 1000sat / 113vb = 9
+    assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2212);
+    assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9);
+}
+
+#[test]
+fn test_list_output() {
+    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+    let txos = wallet
+        .list_output()
+        .map(|op| (op.outpoint, op))
+        .collect::<std::collections::BTreeMap<_, _>>();
+    assert_eq!(txos.len(), 2);
+    for (op, txo) in txos {
+        if op.txid == txid {
+            assert_eq!(txo.txout.value.to_sat(), 50_000);
+            assert!(!txo.is_spent);
+        } else {
+            assert_eq!(txo.txout.value.to_sat(), 76_000);
+            assert!(txo.is_spent);
+        }
+    }
+}
+
+macro_rules! assert_fee_rate {
+    ($psbt:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
+        let psbt = $psbt.clone();
+        #[allow(unused_mut)]
+        let mut tx = $psbt.clone().extract_tx().expect("failed to extract tx");
+        $(
+            $( $add_signature )*
+                for txin in &mut tx.input {
+                    txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+                }
+        )*
+
+            #[allow(unused_mut)]
+        #[allow(unused_assignments)]
+        let mut dust_change = false;
+        $(
+            $( $dust_change )*
+                dust_change = true;
+        )*
+
+            let fee_amount = psbt
+            .inputs
+            .iter()
+            .fold(0, |acc, i| acc + i.witness_utxo.as_ref().unwrap().value.to_sat())
+            - psbt
+            .unsigned_tx
+            .output
+            .iter()
+            .fold(0, |acc, o| acc + o.value.to_sat());
+
+        assert_eq!(fee_amount, $fees);
+
+        let tx_fee_rate = (Amount::from_sat(fee_amount) / tx.weight())
+            .to_sat_per_kwu();
+        let fee_rate = $fee_rate.to_sat_per_kwu();
+        let half_default = FeeRate::BROADCAST_MIN.checked_div(2)
+            .unwrap()
+            .to_sat_per_kwu();
+
+        if !dust_change {
+            assert!(tx_fee_rate >= fee_rate && tx_fee_rate - fee_rate < half_default, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
+        } else {
+            assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate);
+        }
+    });
+}
+
+macro_rules! from_str {
+    ($e:expr, $t:ty) => {{
+        use core::str::FromStr;
+        <$t>::from_str($e).unwrap()
+    }};
+
+    ($e:expr) => {
+        from_str!($e, _)
+    };
+}
+
+#[test]
+#[should_panic(expected = "NoRecipients")]
+fn test_create_tx_empty_recipients() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    wallet.build_tx().finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "NoUtxosSelected")]
+fn test_create_tx_manually_selected_empty_utxos() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .manually_selected_only();
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_version_0() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .version(0);
+    assert!(matches!(builder.finish(), Err(CreateTxError::Version0)));
+}
+
+#[test]
+fn test_create_tx_version_1_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .version(1);
+    assert!(matches!(builder.finish(), Err(CreateTxError::Version1Csv)));
+}
+
+#[test]
+fn test_create_tx_custom_version() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .version(42);
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.version.0, 42);
+}
+
+#[test]
+fn test_create_tx_default_locktime_is_last_sync_height() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    // Since we never synced the wallet we don't have a last_sync_height
+    // we could use to try to prevent fee sniping. We default to 0.
+    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 2_000);
+}
+
+#[test]
+fn test_create_tx_fee_sniping_locktime_last_sync() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+
+    let psbt = builder.finish().unwrap();
+
+    // If there's no current_height we're left with using the last sync height
+    assert_eq!(
+        psbt.unsigned_tx.lock_time.to_consensus_u32(),
+        wallet.latest_checkpoint().height()
+    );
+}
+
+#[test]
+fn test_create_tx_default_locktime_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 100_000);
+}
+
+#[test]
+fn test_create_tx_custom_locktime() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .current_height(630_001)
+        .nlocktime(absolute::LockTime::from_height(630_000).unwrap());
+    let psbt = builder.finish().unwrap();
+
+    // When we explicitly specify a nlocktime
+    // we don't try any fee sniping prevention trick
+    // (we ignore the current_height)
+    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000);
+}
+
+#[test]
+fn test_create_tx_custom_locktime_compatible_with_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .nlocktime(absolute::LockTime::from_height(630_000).unwrap());
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000);
+}
+
+#[test]
+fn test_create_tx_custom_locktime_incompatible_with_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .nlocktime(absolute::LockTime::from_height(50000).unwrap());
+    assert!(matches!(builder.finish(),
+        Err(CreateTxError::LockTime { requested, required })
+        if requested.to_consensus_u32() == 50_000 && required.to_consensus_u32() == 100_000));
+}
+
+#[test]
+fn test_create_tx_no_rbf_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
+}
+
+#[test]
+fn test_create_tx_with_default_rbf_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    // When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
+    // It will be set to the OP_CSV value, in this case 6
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6));
+}
+
+#[test]
+fn test_create_tx_with_custom_rbf_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf_with_sequence(Sequence(3));
+    assert!(matches!(builder.finish(),
+        Err(CreateTxError::RbfSequenceCsv { rbf, csv })
+        if rbf.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6));
+}
+
+#[test]
+fn test_create_tx_no_rbf_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
+#[test]
+fn test_create_tx_invalid_rbf_sequence() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
+    assert!(matches!(builder.finish(), Err(CreateTxError::RbfSequence)));
+}
+
+#[test]
+fn test_create_tx_custom_rbf_sequence() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf_with_sequence(Sequence(0xDEADBEEF));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF));
+}
+
+#[test]
+fn test_create_tx_default_sequence() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
+#[test]
+fn test_create_tx_change_policy_no_internal() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .do_not_spend_change();
+    assert!(matches!(
+        builder.finish(),
+        Err(CreateTxError::ChangePolicyDescriptor)
+    ));
+}
+
+macro_rules! check_fee {
+    ($wallet:expr, $psbt: expr) => {{
+        let tx = $psbt.clone().extract_tx().expect("failed to extract tx");
+        let tx_fee = $wallet.calculate_fee(&tx).ok();
+        assert_eq!(tx_fee, $psbt.fee_amount());
+        tx_fee
+    }};
+}
+
+#[test]
+fn test_create_tx_drain_wallet_and_drain_to() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value.to_sat(),
+        50_000 - fee.unwrap_or(0)
+    );
+}
+
+#[test]
+fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
+        .unwrap()
+        .assume_checked();
+    let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000))
+        .drain_to(drain_addr.script_pubkey())
+        .drain_wallet();
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+    let outputs = psbt.unsigned_tx.output;
+
+    assert_eq!(outputs.len(), 2);
+    let main_output = outputs
+        .iter()
+        .find(|x| x.script_pubkey == addr.script_pubkey())
+        .unwrap();
+    let drain_output = outputs
+        .iter()
+        .find(|x| x.script_pubkey == drain_addr.script_pubkey())
+        .unwrap();
+    assert_eq!(main_output.value.to_sat(), 20_000,);
+    assert_eq!(drain_output.value.to_sat(), 30_000 - fee.unwrap_or(0));
+}
+
+#[test]
+fn test_create_tx_drain_to_and_utxos() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxos(&utxos)
+        .unwrap();
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value.to_sat(),
+        50_000 - fee.unwrap_or(0)
+    );
+}
+
+#[test]
+#[should_panic(expected = "NoRecipients")]
+fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(drain_addr.script_pubkey());
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_default_fee_rate() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::BROADCAST_MIN, @add_signature);
+}
+
+#[test]
+fn test_create_tx_custom_fee_rate() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature);
+}
+
+#[test]
+fn test_create_tx_absolute_fee() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(100);
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(fee.unwrap_or(0), 100);
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value.to_sat(),
+        50_000 - fee.unwrap_or(0)
+    );
+}
+
+#[test]
+fn test_create_tx_absolute_zero_fee() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(0);
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(fee.unwrap_or(0), 0);
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value.to_sat(),
+        50_000 - fee.unwrap_or(0)
+    );
+}
+
+#[test]
+#[should_panic(expected = "InsufficientFunds")]
+fn test_create_tx_absolute_high_fee() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(60_000);
+    let _ = builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_add_change() {
+    use bdk_wallet::wallet::tx_builder::TxOrdering;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .ordering(TxOrdering::Untouched);
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 2);
+    assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 25_000);
+    assert_eq!(
+        psbt.unsigned_tx.output[1].value.to_sat(),
+        25_000 - fee.unwrap_or(0)
+    );
+}
+
+#[test]
+fn test_create_tx_skip_change_dust() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800));
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800);
+    assert_eq!(fee.unwrap_or(0), 200);
+}
+
+#[test]
+#[should_panic(expected = "InsufficientFunds")]
+fn test_create_tx_drain_to_dust_amount() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    // very high fee rate, so that the only output would be below dust
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(454));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_ordering_respected() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(10_000))
+        .ordering(bdk_wallet::wallet::tx_builder::TxOrdering::Bip69Lexicographic);
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 3);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value.to_sat(),
+        10_000 - fee.unwrap_or(0)
+    );
+    assert_eq!(psbt.unsigned_tx.output[1].value.to_sat(), 10_000);
+    assert_eq!(psbt.unsigned_tx.output[2].value.to_sat(), 30_000);
+}
+
+#[test]
+fn test_create_tx_default_sighash() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.inputs[0].sighash_type, None);
+}
+
+#[test]
+fn test_create_tx_custom_sighash() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .sighash(EcdsaSighashType::Single.into());
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.inputs[0].sighash_type,
+        Some(EcdsaSighashType::Single.into())
+    );
+}
+
+#[test]
+fn test_create_tx_input_hd_keypaths() {
+    use bitcoin::bip32::{DerivationPath, Fingerprint};
+    use core::str::FromStr;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1);
+    assert_eq!(
+        psbt.inputs[0].bip32_derivation.values().next().unwrap(),
+        &(
+            Fingerprint::from_str("d34db33f").unwrap(),
+            DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap()
+        )
+    );
+}
+
+#[test]
+fn test_create_tx_output_hd_keypaths() {
+    use bitcoin::bip32::{DerivationPath, Fingerprint};
+    use core::str::FromStr;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
+    let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index);
+    assert_eq!(
+        psbt.outputs[0].bip32_derivation.values().next().unwrap(),
+        &(
+            Fingerprint::from_str("d34db33f").unwrap(),
+            DerivationPath::from_str(&expected_derivation_path).unwrap()
+        )
+    );
+}
+
+#[test]
+fn test_create_tx_set_redeem_script_p2sh() {
+    use bitcoin::hex::FromHex;
+
+    let (mut wallet, _) =
+        get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.inputs[0].redeem_script,
+        Some(ScriptBuf::from(
+            Vec::<u8>::from_hex(
+                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
+            )
+            .unwrap()
+        ))
+    );
+    assert_eq!(psbt.inputs[0].witness_script, None);
+}
+
+#[test]
+fn test_create_tx_set_witness_script_p2wsh() {
+    use bitcoin::hex::FromHex;
+
+    let (mut wallet, _) =
+        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.inputs[0].redeem_script, None);
+    assert_eq!(
+        psbt.inputs[0].witness_script,
+        Some(ScriptBuf::from(
+            Vec::<u8>::from_hex(
+                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
+            )
+            .unwrap()
+        ))
+    );
+}
+
+#[test]
+fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
+    let (mut wallet, _) =
+        get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    let script = ScriptBuf::from_hex(
+        "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac",
+    )
+    .unwrap();
+
+    assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_p2wsh()));
+    assert_eq!(psbt.inputs[0].witness_script, Some(script));
+}
+
+#[test]
+fn test_create_tx_non_witness_utxo() {
+    let (mut wallet, _) =
+        get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert!(psbt.inputs[0].non_witness_utxo.is_some());
+    assert!(psbt.inputs[0].witness_utxo.is_none());
+}
+
+#[test]
+fn test_create_tx_only_witness_utxo() {
+    let (mut wallet, _) =
+        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .only_witness_utxo()
+        .drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert!(psbt.inputs[0].non_witness_utxo.is_none());
+    assert!(psbt.inputs[0].witness_utxo.is_some());
+}
+
+#[test]
+fn test_create_tx_shwpkh_has_witness_utxo() {
+    let (mut wallet, _) =
+        get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert!(psbt.inputs[0].witness_utxo.is_some());
+}
+
+#[test]
+fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
+    let (mut wallet, _) =
+        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert!(psbt.inputs[0].non_witness_utxo.is_some());
+    assert!(psbt.inputs[0].witness_utxo.is_some());
+}
+
+#[test]
+fn test_create_tx_add_utxo() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let small_output_tx = Transaction {
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+        version: transaction::Version::non_standard(0),
+        lock_time: absolute::LockTime::ZERO,
+    };
+    wallet
+        .insert_tx(
+            small_output_tx.clone(),
+            ConfirmationTime::Unconfirmed { last_seen: 0 },
+        )
+        .unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .add_utxo(OutPoint {
+            txid: small_output_tx.txid(),
+            vout: 0,
+        })
+        .unwrap();
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+
+    assert_eq!(
+        psbt.unsigned_tx.input.len(),
+        2,
+        "should add an additional input since 25_000 < 30_000"
+    );
+    assert_eq!(
+        sent_received.0,
+        Amount::from_sat(75_000),
+        "total should be sum of both inputs"
+    );
+}
+
+#[test]
+#[should_panic(expected = "InsufficientFunds")]
+fn test_create_tx_manually_selected_insufficient() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let small_output_tx = Transaction {
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+        version: transaction::Version::non_standard(0),
+        lock_time: absolute::LockTime::ZERO,
+    };
+
+    wallet
+        .insert_tx(
+            small_output_tx.clone(),
+            ConfirmationTime::Unconfirmed { last_seen: 0 },
+        )
+        .unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .add_utxo(OutPoint {
+            txid: small_output_tx.txid(),
+            vout: 0,
+        })
+        .unwrap()
+        .manually_selected_only();
+    builder.finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "SpendingPolicyRequired(External)")]
+fn test_create_tx_policy_path_required() {
+    let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_policy_path_no_csv() {
+    let descriptors = get_test_wpkh();
+    let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap();
+
+    let tx = Transaction {
+        version: transaction::Version::non_standard(0),
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(50_000),
+        }],
+    };
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
+    let root_id = external_policy.id;
+    // child #0 is just the key "A"
+    let path = vec![(root_id, vec![0])].into_iter().collect();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .policy_path(path, KeychainKind::External);
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFF));
+}
+
+#[test]
+fn test_create_tx_policy_path_use_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
+
+    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
+    let root_id = external_policy.id;
+    // child #1 is or(pk(B),older(144))
+    let path = vec![(root_id, vec![1])].into_iter().collect();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .policy_path(path, KeychainKind::External);
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
+}
+
+#[test]
+fn test_create_tx_policy_path_ignored_subtree_with_csv() {
+    let (mut wallet, _) = get_funded_wallet("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v:pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))");
+
+    let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
+    let root_id = external_policy.id;
+    // child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)
+    let path = vec![(root_id, vec![0])].into_iter().collect();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000))
+        .policy_path(path, KeychainKind::External);
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
+#[test]
+fn test_create_tx_global_xpubs_with_origin() {
+    use bitcoin::bip32;
+    use bitcoin::hex::FromHex;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .add_global_xpubs();
+    let psbt = builder.finish().unwrap();
+
+    let key = bip32::Xpub::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
+    let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
+    let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
+
+    assert_eq!(psbt.xpub.len(), 1);
+    assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
+}
+
+#[test]
+fn test_add_foreign_utxo() {
+    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet2, _) =
+        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let utxo = wallet2.list_unspent().next().expect("must take!");
+    let foreign_utxo_satisfaction = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    let psbt_input = psbt::Input {
+        witness_utxo: Some(utxo.txout.clone()),
+        ..Default::default()
+    };
+
+    let mut builder = wallet1.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
+        .only_witness_utxo()
+        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
+        .unwrap();
+    let mut psbt = builder.finish().unwrap();
+    wallet1.insert_txout(utxo.outpoint, utxo.txout);
+    let fee = check_fee!(wallet1, psbt);
+    let sent_received =
+        wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+
+    assert_eq!(
+        (sent_received.0 - sent_received.1).to_sat(),
+        10_000 + fee.unwrap_or(0),
+        "we should have only net spent ~10_000"
+    );
+
+    assert!(
+        psbt.unsigned_tx
+            .input
+            .iter()
+            .any(|input| input.previous_output == utxo.outpoint),
+        "foreign_utxo should be in there"
+    );
+
+    let finished = wallet1
+        .sign(
+            &mut psbt,
+            SignOptions {
+                trust_witness_utxo: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+
+    assert!(
+        !finished,
+        "only one of the inputs should have been signed so far"
+    );
+
+    let finished = wallet2
+        .sign(
+            &mut psbt,
+            SignOptions {
+                trust_witness_utxo: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+    assert!(finished, "all the inputs should have been signed now");
+}
+
+#[test]
+#[should_panic(
+    expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])"
+)]
+fn test_calculate_fee_with_missing_foreign_utxo() {
+    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet2, _) =
+        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let utxo = wallet2.list_unspent().next().expect("must take!");
+    let foreign_utxo_satisfaction = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    let psbt_input = psbt::Input {
+        witness_utxo: Some(utxo.txout.clone()),
+        ..Default::default()
+    };
+
+    let mut builder = wallet1.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
+        .only_witness_utxo()
+        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
+        .unwrap();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    wallet1.calculate_fee(&tx).unwrap();
+}
+
+#[test]
+fn test_add_foreign_utxo_invalid_psbt_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
+    let foreign_utxo_satisfaction = wallet
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    let mut builder = wallet.build_tx();
+    let result =
+        builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction);
+    assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo)));
+}
+
+#[test]
+fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
+    let (mut wallet1, txid1) = get_funded_wallet(get_test_wpkh());
+    let (wallet2, txid2) =
+        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
+
+    let utxo2 = wallet2.list_unspent().next().unwrap();
+    let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone();
+    let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone();
+
+    let satisfaction_weight = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    let mut builder = wallet1.build_tx();
+    assert!(
+        builder
+            .add_foreign_utxo(
+                utxo2.outpoint,
+                psbt::Input {
+                    non_witness_utxo: Some(tx1.as_ref().clone()),
+                    ..Default::default()
+                },
+                satisfaction_weight
+            )
+            .is_err(),
+        "should fail when outpoint doesn't match psbt_input"
+    );
+    assert!(
+        builder
+            .add_foreign_utxo(
+                utxo2.outpoint,
+                psbt::Input {
+                    non_witness_utxo: Some(tx2.as_ref().clone()),
+                    ..Default::default()
+                },
+                satisfaction_weight
+            )
+            .is_ok(),
+        "should be ok when outpoint does match psbt_input"
+    );
+}
+
+#[test]
+fn test_add_foreign_utxo_only_witness_utxo() {
+    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet2, txid2) =
+        get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let utxo2 = wallet2.list_unspent().next().unwrap();
+
+    let satisfaction_weight = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    let mut builder = wallet1.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000));
+
+    {
+        let mut builder = builder.clone();
+        let psbt_input = psbt::Input {
+            witness_utxo: Some(utxo2.txout.clone()),
+            ..Default::default()
+        };
+        builder
+            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
+            .unwrap();
+        assert!(
+            builder.finish().is_err(),
+            "psbt_input with witness_utxo should fail with only witness_utxo"
+        );
+    }
+
+    {
+        let mut builder = builder.clone();
+        let psbt_input = psbt::Input {
+            witness_utxo: Some(utxo2.txout.clone()),
+            ..Default::default()
+        };
+        builder
+            .only_witness_utxo()
+            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
+            .unwrap();
+        assert!(
+            builder.finish().is_ok(),
+            "psbt_input with just witness_utxo should succeed when `only_witness_utxo` is enabled"
+        );
+    }
+
+    {
+        let mut builder = builder.clone();
+        let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx;
+        let psbt_input = psbt::Input {
+            non_witness_utxo: Some(tx2.as_ref().clone()),
+            ..Default::default()
+        };
+        builder
+            .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
+            .unwrap();
+        assert!(
+            builder.finish().is_ok(),
+            "psbt_input with non_witness_utxo should succeed by default"
+        );
+    }
+}
+
+#[test]
+fn test_get_psbt_input() {
+    // this should grab a known good utxo and set the input
+    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+    for utxo in wallet.list_unspent() {
+        let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
+        assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
+    }
+}
+
+#[test]
+#[should_panic(
+    expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")"
+)]
+fn test_create_tx_global_xpubs_origin_missing() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .add_global_xpubs();
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_global_xpubs_master_without_origin() {
+    use bitcoin::bip32;
+    use bitcoin::hex::FromHex;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .add_global_xpubs();
+    let psbt = builder.finish().unwrap();
+
+    let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
+    let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
+
+    assert_eq!(psbt.xpub.len(), 1);
+    assert_eq!(
+        psbt.xpub.get(&key),
+        Some(&(fingerprint, bip32::DerivationPath::default()))
+    );
+}
+
+#[test]
+#[should_panic(expected = "IrreplaceableTransaction")]
+fn test_bump_fee_irreplaceable_tx() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+    wallet.build_fee_bump(txid).unwrap().finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "TransactionConfirmed")]
+fn test_bump_fee_confirmed_tx() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+
+    wallet
+        .insert_tx(
+            tx,
+            ConfirmationTime::Confirmed {
+                height: 42,
+                time: 42_000,
+            },
+        )
+        .unwrap();
+
+    wallet.build_fee_bump(txid).unwrap().finish().unwrap();
+}
+
+#[test]
+fn test_bump_fee_low_fee_rate() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let feerate = psbt.fee_rate().unwrap();
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::BROADCAST_MIN);
+    let res = builder.finish();
+    assert_matches!(
+        res,
+        Err(CreateTxError::FeeRateTooLow { .. }),
+        "expected FeeRateTooLow error"
+    );
+
+    let required = feerate.to_sat_per_kwu() + 250; // +1 sat/vb
+    let sat_vb = required as f64 / 250.0;
+    let expect = format!("Fee rate too low: required {} sat/vb", sat_vb);
+    assert_eq!(res.unwrap_err().to_string(), expect);
+}
+
+#[test]
+#[should_panic(expected = "FeeTooLow")]
+fn test_bump_fee_low_abs() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(10);
+    builder.finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "FeeTooLow")]
+fn test_bump_fee_zero_abs() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(0);
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_bump_fee_reduce_change() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let original_sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let original_fee = check_fee!(wallet, psbt);
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(feerate).enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(sent_received.0, original_sent_received.0);
+    assert_eq!(
+        sent_received.1 + Amount::from_sat(fee.unwrap_or(0)),
+        original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0))
+    );
+    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature);
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(200);
+    builder.enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(sent_received.0, original_sent_received.0);
+    assert_eq!(
+        sent_received.1 + Amount::from_sat(fee.unwrap_or(0)),
+        original_sent_received.1 + Amount::from_sat(original_fee.unwrap_or(0))
+    );
+    assert!(
+        fee.unwrap_or(0) > original_fee.unwrap_or(0),
+        "{} > {}",
+        fee.unwrap_or(0),
+        original_fee.unwrap_or(0)
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_eq!(fee.unwrap_or(0), 200);
+}
+
+#[test]
+fn test_bump_fee_reduce_single_recipient() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.clone().extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let original_fee = check_fee!(wallet, psbt);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .fee_rate(feerate)
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap();
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(sent_received.0, original_sent_received.0);
+    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.output.len(), 1);
+    assert_eq!(
+        tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)),
+        sent_received.0
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), feerate, @add_signature);
+}
+
+#[test]
+fn test_bump_fee_absolute_reduce_single_recipient() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let original_fee = check_fee!(wallet, psbt);
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap()
+        .fee_absolute(300);
+    let psbt = builder.finish().unwrap();
+    let tx = &psbt.unsigned_tx;
+    let sent_received = wallet.sent_and_received(tx);
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(sent_received.0, original_sent_received.0);
+    assert!(fee.unwrap_or(0) > original_fee.unwrap_or(0));
+
+    assert_eq!(tx.output.len(), 1);
+    assert_eq!(
+        tx.output[0].value + Amount::from_sat(fee.unwrap_or(0)),
+        sent_received.0
+    );
+
+    assert_eq!(fee.unwrap_or(0), 300);
+}
+
+#[test]
+fn test_bump_fee_drain_wallet() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    // receive an extra tx so that our wallet has two utxos.
+    let tx = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+    };
+    wallet
+        .insert_tx(
+            tx.clone(),
+            ConfirmationTime::Confirmed {
+                height: wallet.latest_checkpoint().height(),
+                time: 42_000,
+            },
+        )
+        .unwrap();
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxo(OutPoint {
+            txid: tx.txid(),
+            vout: 0,
+        })
+        .unwrap()
+        .manually_selected_only()
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+    assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
+
+    // for the new feerate, it should be enough to reduce the output, but since we specify
+    // `drain_wallet` we expect to spend everything
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .drain_wallet()
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap()
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
+    let psbt = builder.finish().unwrap();
+    let sent_received = wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx"));
+
+    assert_eq!(sent_received.0, Amount::from_sat(75_000));
+}
+
+#[test]
+#[should_panic(expected = "InsufficientFunds")]
+fn test_bump_fee_remove_output_manually_selected_only() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    // receive an extra tx so that our wallet has two utxos. then we manually pick only one of
+    // them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
+    // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
+    // existing output. In other words, bump_fee + manually_selected_only is always an error
+    // unless you've also set "allow_shrinking" OR there is a change output.
+    let init_tx = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+    };
+    wallet
+        .insert_tx(
+            init_tx.clone(),
+            wallet
+                .transactions()
+                .last()
+                .unwrap()
+                .chain_position
+                .cloned()
+                .into(),
+        )
+        .unwrap();
+    let outpoint = OutPoint {
+        txid: init_tx.txid(),
+        vout: 0,
+    };
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxo(outpoint)
+        .unwrap()
+        .manually_selected_only()
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+    assert_eq!(original_sent_received.0, Amount::from_sat(25_000));
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .manually_selected_only()
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(255));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_bump_fee_add_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let init_tx = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+    };
+    let pos = wallet
+        .transactions()
+        .last()
+        .unwrap()
+        .chain_position
+        .cloned()
+        .into();
+    wallet.insert_tx(init_tx, pos).unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_details = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+    assert_eq!(
+        sent_received.0,
+        original_details.0 + Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
+        Amount::from_sat(30_000)
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(45_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature);
+}
+
+#[test]
+fn test_bump_fee_absolute_add_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    receive_output_in_latest_block(&mut wallet, 25_000);
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(6_000);
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(
+        sent_received.0,
+        original_sent_received.0 + Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
+        Amount::from_sat(30_000)
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(45_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_eq!(fee.unwrap_or(0), 6_000);
+}
+
+#[test]
+fn test_bump_fee_no_change_add_input_and_change() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let op = receive_output_in_latest_block(&mut wallet, 25_000);
+
+    // initially make a tx without change by using `drain_to`
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxo(op)
+        .unwrap()
+        .manually_selected_only()
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let original_sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let original_fee = check_fee!(wallet, psbt);
+
+    let tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    // now bump the fees without using `allow_shrinking`. the wallet should add an
+    // extra input and a change output, and leave the original output untouched
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50));
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    let original_send_all_amount =
+        original_sent_received.0 - Amount::from_sat(original_fee.unwrap_or(0));
+    assert_eq!(
+        sent_received.0,
+        original_sent_received.0 + Amount::from_sat(50_000)
+    );
+    assert_eq!(
+        sent_received.1,
+        Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0))
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        original_send_all_amount
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(75_000) - original_send_all_amount - Amount::from_sat(fee.unwrap_or(0))
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature);
+}
+
+#[test]
+fn test_bump_fee_add_input_change_dust() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    receive_output_in_latest_block(&mut wallet, 25_000);
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let original_sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let original_fee = check_fee!(wallet, psbt);
+
+    let mut tx = psbt.extract_tx().expect("failed to extract tx");
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realistic weight
+    }
+    let original_tx_weight = tx.weight();
+    assert_eq!(tx.input.len(), 1);
+    assert_eq!(tx.output.len(), 2);
+    let txid = tx.txid();
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    // We set a fee high enough that during rbf we are forced to add
+    // a new input and also that we have to remove the change
+    // that we had previously
+
+    // We calculate the new weight as:
+    //   original weight
+    // + extra input weight: 160 WU = (32 (prevout) + 4 (vout) + 4 (nsequence)) * 4
+    // + input satisfaction weight: 112 WU = 106 (witness) + 2 (witness len) + (1 (script len)) * 4
+    // - change output weight: 124 WU = (8 (value) + 1 (script len) + 22 (script)) * 4
+    let new_tx_weight =
+        original_tx_weight + Weight::from_wu(160) + Weight::from_wu(112) - Weight::from_wu(124);
+    // two inputs (50k, 25k) and one output (45k) - epsilon
+    // We use epsilon here to avoid asking for a slightly too high feerate
+    let fee_abs = 50_000 + 25_000 - 45_000 - 10;
+    builder.fee_rate(Amount::from_sat(fee_abs) / new_tx_weight);
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(
+        original_sent_received.1,
+        Amount::from_sat(5_000 - original_fee.unwrap_or(0))
+    );
+
+    assert_eq!(
+        sent_received.0,
+        original_sent_received.0 + Amount::from_sat(25_000)
+    );
+    assert_eq!(fee.unwrap_or(0), 30_000);
+    assert_eq!(sent_received.1, Amount::ZERO);
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 1);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(45_000)
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature);
+}
+
+#[test]
+fn test_bump_fee_force_add_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let mut tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+    }
+    wallet
+        .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+    // the new fee_rate is low enough that just reducing the change would be fine, but we force
+    // the addition of an extra input with `add_utxo()`
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .add_utxo(incoming_op)
+        .unwrap()
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(5));
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(
+        sent_received.0,
+        original_sent_received.0 + Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
+        Amount::from_sat(30_000)
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(45_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature);
+}
+
+#[test]
+fn test_bump_fee_absolute_force_add_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let mut tx = psbt.extract_tx().expect("failed to extract tx");
+    let original_sent_received = wallet.sent_and_received(&tx);
+    let txid = tx.txid();
+    // skip saving the new utxos, we know they can't be used anyways
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+    }
+    wallet
+        .insert_tx(tx.clone(), ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    // the new fee_rate is low enough that just reducing the change would be fine, but we force
+    // the addition of an extra input with `add_utxo()`
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.add_utxo(incoming_op).unwrap().fee_absolute(250);
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(
+        sent_received.0,
+        original_sent_received.0 + Amount::from_sat(25_000)
+    );
+    assert_eq!(
+        Amount::from_sat(fee.unwrap_or(0)) + sent_received.1,
+        Amount::from_sat(30_000)
+    );
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.input.len(), 2);
+    assert_eq!(tx.output.len(), 2);
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey == addr.script_pubkey())
+            .unwrap()
+            .value,
+        Amount::from_sat(45_000)
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        sent_received.1
+    );
+
+    assert_eq!(fee.unwrap_or(0), 250);
+}
+
+#[test]
+#[should_panic(expected = "InsufficientFunds")]
+fn test_bump_fee_unconfirmed_inputs_only() {
+    // We try to bump the fee, but:
+    // - We can't reduce the change, as we have no change
+    // - All our UTXOs are unconfirmed
+    // So, we fail with "InsufficientFunds", as per RBF rule 2:
+    // The replacement transaction may only include an unconfirmed input
+    // if that input was included in one of the original transactions.
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_wallet()
+        .drain_to(addr.script_pubkey())
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    // Now we receive one transaction with 0 confirmations. We won't be able to use that for
+    // fee bumping, as it's still unconfirmed!
+    receive_output(
+        &mut wallet,
+        25_000,
+        ConfirmationTime::Unconfirmed { last_seen: 0 },
+    );
+    let mut tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+    }
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_bump_fee_unconfirmed_input() {
+    // We create a tx draining the wallet and spending one confirmed
+    // and one unconfirmed UTXO. We check that we can fee bump normally
+    // (BIP125 rule 2 only apply to newly added unconfirmed input, you can
+    // always fee bump with an unconfirmed input if it was included in the
+    // original transaction)
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    // We receive a tx with 0 confirmations, which will be used as an input
+    // in the drain tx.
+    receive_output(&mut wallet, 25_000, ConfirmationTime::unconfirmed(0));
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_wallet()
+        .drain_to(addr.script_pubkey())
+        .enable_rbf();
+    let psbt = builder.finish().unwrap();
+    let mut tx = psbt.extract_tx().expect("failed to extract tx");
+    let txid = tx.txid();
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // fake signature
+    }
+    wallet
+        .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 })
+        .unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .fee_rate(FeeRate::from_sat_per_vb_unchecked(15))
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap();
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_fee_amount_negative_drain_val() {
+    // While building the transaction, bdk would calculate the drain_value
+    // as
+    // current_delta - fee_amount - drain_fee
+    // using saturating_sub, meaning that if the result would end up negative,
+    // it'll remain to zero instead.
+    // This caused a bug in master where we would calculate the wrong fee
+    // for a transaction.
+    // See https://github.com/bitcoindevkit/bdk/issues/660
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
+        .unwrap()
+        .assume_checked();
+    let fee_rate = FeeRate::from_sat_per_kwu(500);
+    let incoming_op = receive_output_in_latest_block(&mut wallet, 8859);
+
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(send_to.script_pubkey(), Amount::from_sat(8630))
+        .add_utxo(incoming_op)
+        .unwrap()
+        .enable_rbf()
+        .fee_rate(fee_rate);
+    let psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    assert_eq!(psbt.inputs.len(), 1);
+    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate, @add_signature);
+}
+
+#[test]
+fn test_sign_single_xprv() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_sign_single_xprv_with_master_fingerprint_and_path() {
+    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_sign_single_xprv_bip44_path() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_sign_single_xprv_sh_wpkh() {
+    let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_sign_single_wif() {
+    let (mut wallet, _) =
+        get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_sign_single_xprv_no_hd_keypaths() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    psbt.inputs[0].bip32_derivation.clear();
+    assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
+
+    let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
+    assert!(finalized);
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(extracted.input[0].witness.len(), 2);
+}
+
+#[test]
+fn test_include_output_redeem_witness_script() {
+    let (mut wallet, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .include_output_redeem_witness_script();
+    let psbt = builder.finish().unwrap();
+
+    // p2sh-p2wsh transaction should contain both witness and redeem scripts
+    assert!(psbt
+        .outputs
+        .iter()
+        .any(|output| output.redeem_script.is_some() && output.witness_script.is_some()));
+}
+
+#[test]
+fn test_signing_only_one_of_multiple_inputs() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000))
+        .include_output_redeem_witness_script();
+    let mut psbt = builder.finish().unwrap();
+
+    // add another input to the psbt that is at least passable.
+    let dud_input = bitcoin::psbt::Input {
+        witness_utxo: Some(TxOut {
+            value: Amount::from_sat(100_000),
+            script_pubkey: miniscript::Descriptor::<bitcoin::PublicKey>::from_str(
+                "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
+            )
+            .unwrap()
+            .script_pubkey(),
+        }),
+        ..Default::default()
+    };
+
+    psbt.inputs.push(dud_input);
+    psbt.unsigned_tx.input.push(bitcoin::TxIn::default());
+    let is_final = wallet
+        .sign(
+            &mut psbt,
+            SignOptions {
+                trust_witness_utxo: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+    assert!(
+        !is_final,
+        "shouldn't be final since we can't sign one of the inputs"
+    );
+    assert!(
+        psbt.inputs[0].final_script_witness.is_some(),
+        "should finalized input it signed"
+    )
+}
+
+#[test]
+fn test_remove_partial_sigs_after_finalize_sign_option() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+
+    for remove_partial_sigs in &[true, false] {
+        let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+        let mut builder = wallet.build_tx();
+        builder.drain_to(addr.script_pubkey()).drain_wallet();
+        let mut psbt = builder.finish().unwrap();
+
+        assert!(wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    remove_partial_sigs: *remove_partial_sigs,
+                    ..Default::default()
+                },
+            )
+            .unwrap());
+
+        psbt.inputs.iter().for_each(|input| {
+            if *remove_partial_sigs {
+                assert!(input.partial_sigs.is_empty())
+            } else {
+                assert!(!input.partial_sigs.is_empty())
+            }
+        });
+    }
+}
+
+#[test]
+fn test_try_finalize_sign_option() {
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+
+    for try_finalize in &[true, false] {
+        let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+        let mut builder = wallet.build_tx();
+        builder.drain_to(addr.script_pubkey()).drain_wallet();
+        let mut psbt = builder.finish().unwrap();
+
+        let finalized = wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    try_finalize: *try_finalize,
+                    ..Default::default()
+                },
+            )
+            .unwrap();
+
+        psbt.inputs.iter().for_each(|input| {
+            if *try_finalize {
+                assert!(finalized);
+                assert!(input.final_script_sig.is_some());
+                assert!(input.final_script_witness.is_some());
+            } else {
+                assert!(!finalized);
+                assert!(input.final_script_sig.is_none());
+                assert!(input.final_script_witness.is_none());
+            }
+        });
+    }
+}
+
+#[test]
+fn test_sign_nonstandard_sighash() {
+    let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .sighash(sighash.into())
+        .drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let result = wallet.sign(&mut psbt, Default::default());
+    assert!(
+        result.is_err(),
+        "Signing should have failed because the TX uses non-standard sighashes"
+    );
+    assert_matches!(
+        result,
+        Err(SignerError::NonStandardSighash),
+        "Signing failed with the wrong error type"
+    );
+
+    // try again after opting-in
+    let result = wallet.sign(
+        &mut psbt,
+        SignOptions {
+            allow_all_sighashes: true,
+            ..Default::default()
+        },
+    );
+    assert!(result.is_ok(), "Signing should have worked");
+    assert!(
+        result.unwrap(),
+        "Should finalize the input since we can produce signatures"
+    );
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(
+        *extracted.input[0].witness.to_vec()[0].last().unwrap(),
+        sighash.to_u32() as u8,
+        "The signature should have been made with the right sighash"
+    );
+}
+
+#[test]
+fn test_unused_address() {
+    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    // `list_unused_addresses` should be empty if we haven't revealed any
+    assert!(wallet
+        .list_unused_addresses(KeychainKind::External)
+        .next()
+        .is_none());
+
+    assert_eq!(
+        wallet
+            .next_unused_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(
+        wallet
+            .list_unused_addresses(KeychainKind::External)
+            .next()
+            .unwrap()
+            .to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+}
+
+#[test]
+fn test_next_unused_address() {
+    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
+    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap();
+    assert_eq!(wallet.derivation_index(KeychainKind::External), None);
+
+    assert_eq!(
+        wallet
+            .next_unused_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
+    // calling next_unused again gives same address
+    assert_eq!(
+        wallet
+            .next_unused_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
+
+    // test mark used / unused
+    assert!(wallet.mark_used(KeychainKind::External, 0));
+    let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    assert_eq!(next_unused_addr.index, 1);
+
+    assert!(wallet.unmark_used(KeychainKind::External, 0));
+    let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    assert_eq!(next_unused_addr.index, 0);
+
+    // use the above address
+    receive_output_in_latest_block(&mut wallet, 25_000);
+
+    assert_eq!(
+        wallet
+            .next_unused_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
+
+    // trying to mark index 0 unused should return false
+    assert!(!wallet.unmark_used(KeychainKind::External, 0));
+}
+
+#[test]
+fn test_peek_address_at_index() {
+    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 1).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 0).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 2).to_string(),
+        "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
+    );
+
+    // current new address is not affected
+    assert_eq!(
+        wallet
+            .reveal_next_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+
+    assert_eq!(
+        wallet
+            .reveal_next_address(KeychainKind::External)
+            .unwrap()
+            .to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+}
+
+#[test]
+fn test_peek_address_at_index_not_derivable() {
+    let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+                                 None, Network::Testnet).unwrap();
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 1).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 0).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 2).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+}
+
+#[test]
+fn test_returns_index_and_address() {
+    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    // new index 0
+    assert_eq!(
+        wallet.reveal_next_address(KeychainKind::External).unwrap(),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // new index 1
+    assert_eq!(
+        wallet.reveal_next_address(KeychainKind::External).unwrap(),
+        AddressInfo {
+            index: 1,
+            address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // peek index 25
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 25),
+        AddressInfo {
+            index: 25,
+            address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // new index 2
+    assert_eq!(
+        wallet.reveal_next_address(KeychainKind::External).unwrap(),
+        AddressInfo {
+            index: 2,
+            address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        }
+    );
+}
+
+#[test]
+fn test_sending_to_bip350_bech32m_address() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_get_address() {
+    use bdk_wallet::descriptor::template::Bip84;
+    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+    let wallet = Wallet::new_no_persist(
+        Bip84(key, KeychainKind::External),
+        Some(Bip84(key, KeychainKind::Internal)),
+        Network::Regtest,
+    )
+    .unwrap();
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::External, 0),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::Internal, 0),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::Internal,
+        }
+    );
+
+    let wallet =
+        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+
+    assert_eq!(
+        wallet.peek_address(KeychainKind::Internal, 0),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
+                .unwrap()
+                .assume_checked(),
+            keychain: KeychainKind::External,
+        },
+        "when there's no internal descriptor it should just use external"
+    );
+}
+
+#[test]
+fn test_reveal_addresses() {
+    let desc = get_test_tr_single_sig_xprv();
+    let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap();
+    let keychain = KeychainKind::External;
+
+    let last_revealed_addr = wallet
+        .reveal_addresses_to(keychain, 9)
+        .unwrap()
+        .last()
+        .unwrap();
+    assert_eq!(wallet.derivation_index(keychain), Some(9));
+
+    let unused_addrs = wallet.list_unused_addresses(keychain).collect::<Vec<_>>();
+    assert_eq!(unused_addrs.len(), 10);
+    assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr);
+
+    // revealing to an already revealed index returns nothing
+    let mut already_revealed = wallet.reveal_addresses_to(keychain, 9).unwrap();
+    assert!(already_revealed.next().is_none());
+}
+
+#[test]
+fn test_get_address_no_reuse_single_descriptor() {
+    use bdk_wallet::descriptor::template::Bip84;
+    use std::collections::HashSet;
+
+    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+    let mut wallet =
+        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+
+    let mut used_set = HashSet::new();
+
+    (0..3).for_each(|_| {
+        let external_addr = wallet
+            .reveal_next_address(KeychainKind::External)
+            .unwrap()
+            .address;
+        assert!(used_set.insert(external_addr));
+
+        let internal_addr = wallet
+            .reveal_next_address(KeychainKind::Internal)
+            .unwrap()
+            .address;
+        assert!(used_set.insert(internal_addr));
+    });
+}
+
+#[test]
+fn test_taproot_remove_tapfields_after_finalize_sign_option() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
+
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+    let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
+    assert!(finalized);
+
+    // removes tap_* from inputs
+    for input in &psbt.inputs {
+        assert!(input.tap_key_sig.is_none());
+        assert!(input.tap_script_sigs.is_empty());
+        assert!(input.tap_scripts.is_empty());
+        assert!(input.tap_key_origins.is_empty());
+        assert!(input.tap_internal_key.is_none());
+        assert!(input.tap_merkle_root.is_none());
+    }
+    // removes key origins from outputs
+    for output in &psbt.outputs {
+        assert!(output.tap_key_origins.is_empty());
+    }
+}
+
+#[test]
+fn test_taproot_psbt_populate_tap_key_origins() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+    let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.inputs[0]
+            .tap_key_origins
+            .clone()
+            .into_iter()
+            .collect::<Vec<_>>(),
+        vec![(
+            from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"),
+            (vec![], (from_str!("f6a5cb8b"), from_str!("m/0")))
+        )],
+        "Wrong input tap_key_origins"
+    );
+    assert_eq!(
+        psbt.outputs[0]
+            .tap_key_origins
+            .clone()
+            .into_iter()
+            .collect::<Vec<_>>(),
+        vec![(
+            from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"),
+            (vec![], (from_str!("f6a5cb8b"), from_str!("m/1")))
+        )],
+        "Wrong output tap_key_origins"
+    );
+}
+
+#[test]
+fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key());
+    let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
+
+    let path = vec![("rn4nre9c".to_string(), vec![0])]
+        .into_iter()
+        .collect();
+
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .policy_path(path, KeychainKind::External);
+    let psbt = builder.finish().unwrap();
+
+    let mut input_key_origins = psbt.inputs[0]
+        .tap_key_origins
+        .clone()
+        .into_iter()
+        .collect::<Vec<_>>();
+    input_key_origins.sort();
+
+    assert_eq!(
+        input_key_origins,
+        vec![
+            (
+                from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
+                (
+                    vec![
+                        from_str!(
+                            "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
+                        ),
+                        from_str!(
+                            "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
+                        ),
+                    ],
+                    (FromStr::from_str("ece52657").unwrap(), vec![].into())
+                )
+            ),
+            (
+                from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
+                (
+                    vec![],
+                    (FromStr::from_str("871fd295").unwrap(), vec![].into())
+                )
+            )
+        ],
+        "Wrong input tap_key_origins"
+    );
+
+    let mut output_key_origins = psbt.outputs[0]
+        .tap_key_origins
+        .clone()
+        .into_iter()
+        .collect::<Vec<_>>();
+    output_key_origins.sort();
+
+    assert_eq!(
+        input_key_origins, output_key_origins,
+        "Wrong output tap_key_origins"
+    );
+}
+
+#[test]
+fn test_taproot_psbt_input_tap_tree() {
+    use bitcoin::hex::FromHex;
+    use bitcoin::taproot;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.inputs[0].tap_merkle_root,
+        Some(
+            TapNodeHash::from_str(
+                "61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986"
+            )
+            .unwrap()
+        ),
+    );
+    assert_eq!(
+        psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
+        vec![
+            (taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (ScriptBuf::from_hex("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac").unwrap(), taproot::LeafVersion::TapScript)),
+            (taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (ScriptBuf::from_hex("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac").unwrap(), taproot::LeafVersion::TapScript)),
+        ],
+    );
+    assert_eq!(
+        psbt.inputs[0].tap_internal_key,
+        Some(from_str!(
+            "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"
+        ))
+    );
+
+    // Since we are creating an output to the same address as the input, assert that the
+    // internal_key is the same
+    assert_eq!(
+        psbt.inputs[0].tap_internal_key,
+        psbt.outputs[0].tap_internal_key
+    );
+
+    let tap_tree: bitcoin::taproot::TapTree = serde_json::from_str(r#"[1,{"Script":["2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",192]},1,{"Script":["208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac",192]}]"#).unwrap();
+    assert_eq!(psbt.outputs[0].tap_tree, Some(tap_tree));
+}
+
+#[test]
+fn test_taproot_sign_missing_witness_utxo() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+    let witness_utxo = psbt.inputs[0].witness_utxo.take();
+
+    let result = wallet.sign(
+        &mut psbt,
+        SignOptions {
+            allow_all_sighashes: true,
+            ..Default::default()
+        },
+    );
+    assert_matches!(
+        result,
+        Err(SignerError::MissingWitnessUtxo),
+        "Signing should have failed with the correct error because the witness_utxo is missing"
+    );
+
+    // restore the witness_utxo
+    psbt.inputs[0].witness_utxo = witness_utxo;
+
+    let result = wallet.sign(
+        &mut psbt,
+        SignOptions {
+            allow_all_sighashes: true,
+            ..Default::default()
+        },
+    );
+
+    assert_matches!(
+        result,
+        Ok(true),
+        "Should finalize the input since we can produce signatures"
+    );
+}
+
+#[test]
+fn test_taproot_sign_using_non_witness_utxo() {
+    let (mut wallet, prev_txid) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    psbt.inputs[0].witness_utxo = None;
+    psbt.inputs[0].non_witness_utxo =
+        Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone());
+    assert!(
+        psbt.inputs[0].non_witness_utxo.is_some(),
+        "Previous tx should be present in the database"
+    );
+
+    let result = wallet.sign(&mut psbt, Default::default());
+    assert!(result.is_ok(), "Signing should have worked");
+    assert!(
+        result.unwrap(),
+        "Should finalize the input since we can produce signatures"
+    );
+}
+
+#[test]
+fn test_taproot_foreign_utxo() {
+    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let utxo = wallet2.list_unspent().next().unwrap();
+    let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
+    let foreign_utxo_satisfaction = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_weight_to_satisfy()
+        .unwrap();
+
+    assert!(
+        psbt_input.non_witness_utxo.is_none(),
+        "`non_witness_utxo` should never be populated for taproot"
+    );
+
+    let mut builder = wallet1.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000))
+        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
+        .unwrap();
+    let psbt = builder.finish().unwrap();
+    let sent_received =
+        wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx"));
+    wallet1.insert_txout(utxo.outpoint, utxo.txout);
+    let fee = check_fee!(wallet1, psbt);
+
+    assert_eq!(
+        sent_received.0 - sent_received.1,
+        Amount::from_sat(10_000 + fee.unwrap_or(0)),
+        "we should have only net spent ~10_000"
+    );
+
+    assert!(
+        psbt.unsigned_tx
+            .input
+            .iter()
+            .any(|input| input.previous_output == utxo.outpoint),
+        "foreign_utxo should be in there"
+    );
+}
+
+fn test_spend_from_wallet(mut wallet: Wallet) {
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+
+    assert!(
+        wallet.sign(&mut psbt, Default::default()).unwrap(),
+        "Unable to finalize tx"
+    );
+}
+
+//     #[test]
+//     fn test_taproot_key_spend() {
+//         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+//         test_spend_from_wallet(wallet);
+
+//         let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+//         test_spend_from_wallet(wallet);
+//     }
+
+#[test]
+fn test_taproot_no_key_spend() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+
+    assert!(
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    sign_with_tap_internal_key: false,
+                    ..Default::default()
+                },
+            )
+            .unwrap(),
+        "Unable to finalize tx"
+    );
+
+    assert!(psbt.inputs.iter().all(|i| i.tap_key_sig.is_none()));
+}
+
+#[test]
+fn test_taproot_script_spend() {
+    let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
+    test_spend_from_wallet(wallet);
+
+    let (wallet, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
+    test_spend_from_wallet(wallet);
+}
+
+#[test]
+fn test_taproot_script_spend_sign_all_leaves() {
+    use bdk_wallet::signer::TapLeavesOptions;
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+
+    assert!(
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    tap_leaves_options: TapLeavesOptions::All,
+                    ..Default::default()
+                },
+            )
+            .unwrap(),
+        "Unable to finalize tx"
+    );
+
+    assert!(psbt
+        .inputs
+        .iter()
+        .all(|i| i.tap_script_sigs.len() == i.tap_scripts.len()));
+}
+
+#[test]
+fn test_taproot_script_spend_sign_include_some_leaves() {
+    use bdk_wallet::signer::TapLeavesOptions;
+    use bitcoin::taproot::TapLeafHash;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+    let mut script_leaves: Vec<_> = psbt.inputs[0]
+        .tap_scripts
+        .clone()
+        .values()
+        .map(|(script, version)| TapLeafHash::from_script(script, *version))
+        .collect();
+    let included_script_leaves = vec![script_leaves.pop().unwrap()];
+    let excluded_script_leaves = script_leaves;
+
+    assert!(
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    tap_leaves_options: TapLeavesOptions::Include(included_script_leaves.clone()),
+                    ..Default::default()
+                },
+            )
+            .unwrap(),
+        "Unable to finalize tx"
+    );
+
+    assert!(psbt.inputs[0]
+        .tap_script_sigs
+        .iter()
+        .all(|s| included_script_leaves.contains(&s.0 .1)
+            && !excluded_script_leaves.contains(&s.0 .1)));
+}
+
+#[test]
+fn test_taproot_script_spend_sign_exclude_some_leaves() {
+    use bdk_wallet::signer::TapLeavesOptions;
+    use bitcoin::taproot::TapLeafHash;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+    let mut script_leaves: Vec<_> = psbt.inputs[0]
+        .tap_scripts
+        .clone()
+        .values()
+        .map(|(script, version)| TapLeafHash::from_script(script, *version))
+        .collect();
+    let included_script_leaves = [script_leaves.pop().unwrap()];
+    let excluded_script_leaves = script_leaves;
+
+    assert!(
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    tap_leaves_options: TapLeavesOptions::Exclude(excluded_script_leaves.clone()),
+                    ..Default::default()
+                },
+            )
+            .unwrap(),
+        "Unable to finalize tx"
+    );
+
+    assert!(psbt.inputs[0]
+        .tap_script_sigs
+        .iter()
+        .all(|s| included_script_leaves.contains(&s.0 .1)
+            && !excluded_script_leaves.contains(&s.0 .1)));
+}
+
+#[test]
+fn test_taproot_script_spend_sign_no_leaves() {
+    use bdk_wallet::signer::TapLeavesOptions;
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+
+    wallet
+        .sign(
+            &mut psbt,
+            SignOptions {
+                tap_leaves_options: TapLeavesOptions::None,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+
+    assert!(psbt.inputs.iter().all(|i| i.tap_script_sigs.is_empty()));
+}
+
+#[test]
+fn test_taproot_sign_derive_index_from_psbt() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let mut psbt = builder.finish().unwrap();
+
+    // re-create the wallet with an empty db
+    let wallet_empty =
+        Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
+
+    // signing with an empty db means that we will only look at the psbt to infer the
+    // derivation index
+    assert!(
+        wallet_empty.sign(&mut psbt, Default::default()).unwrap(),
+        "Unable to finalize tx"
+    );
+}
+
+#[test]
+fn test_taproot_sign_explicit_sighash_all() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .sighash(TapSighashType::All.into())
+        .drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let result = wallet.sign(&mut psbt, Default::default());
+    assert!(
+        result.is_ok(),
+        "Signing should work because SIGHASH_ALL is safe"
+    )
+}
+
+#[test]
+fn test_taproot_sign_non_default_sighash() {
+    let sighash = TapSighashType::NonePlusAnyoneCanPay;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .sighash(sighash.into())
+        .drain_wallet();
+    let mut psbt = builder.finish().unwrap();
+
+    let witness_utxo = psbt.inputs[0].witness_utxo.take();
+
+    let result = wallet.sign(&mut psbt, Default::default());
+    assert!(
+        result.is_err(),
+        "Signing should have failed because the TX uses non-standard sighashes"
+    );
+    assert_matches!(
+        result,
+        Err(SignerError::NonStandardSighash),
+        "Signing failed with the wrong error type"
+    );
+
+    // try again after opting-in
+    let result = wallet.sign(
+        &mut psbt,
+        SignOptions {
+            allow_all_sighashes: true,
+            ..Default::default()
+        },
+    );
+    assert!(
+        result.is_err(),
+        "Signing should have failed because the witness_utxo is missing"
+    );
+    assert_matches!(
+        result,
+        Err(SignerError::MissingWitnessUtxo),
+        "Signing failed with the wrong error type"
+    );
+
+    // restore the witness_utxo
+    psbt.inputs[0].witness_utxo = witness_utxo;
+
+    let result = wallet.sign(
+        &mut psbt,
+        SignOptions {
+            allow_all_sighashes: true,
+            ..Default::default()
+        },
+    );
+
+    assert!(result.is_ok(), "Signing should have worked");
+    assert!(
+        result.unwrap(),
+        "Should finalize the input since we can produce signatures"
+    );
+
+    let extracted = psbt.extract_tx().expect("failed to extract tx");
+    assert_eq!(
+        *extracted.input[0].witness.to_vec()[0].last().unwrap(),
+        sighash as u8,
+        "The signature should have been made with the right sighash"
+    );
+}
+
+#[test]
+fn test_spend_coinbase() {
+    let descriptor = get_test_wpkh();
+    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap();
+
+    let confirmation_height = 5;
+    wallet
+        .insert_checkpoint(BlockId {
+            height: confirmation_height,
+            hash: BlockHash::all_zeros(),
+        })
+        .unwrap();
+    let coinbase_tx = Transaction {
+        version: transaction::Version::ONE,
+        lock_time: absolute::LockTime::ZERO,
+        input: vec![TxIn {
+            previous_output: OutPoint::null(),
+            ..Default::default()
+        }],
+        output: vec![TxOut {
+            script_pubkey: wallet
+                .next_unused_address(KeychainKind::External)
+                .unwrap()
+                .script_pubkey(),
+            value: Amount::from_sat(25_000),
+        }],
+    };
+    wallet
+        .insert_tx(
+            coinbase_tx,
+            ConfirmationTime::Confirmed {
+                height: confirmation_height,
+                time: 30_000,
+            },
+        )
+        .unwrap();
+
+    let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 1;
+    let maturity_time = confirmation_height + COINBASE_MATURITY;
+
+    let balance = wallet.get_balance();
+    assert_eq!(
+        balance,
+        Balance {
+            immature: Amount::from_sat(25_000),
+            trusted_pending: Amount::ZERO,
+            untrusted_pending: Amount::ZERO,
+            confirmed: Amount::ZERO
+        }
+    );
+
+    // We try to create a transaction, only to notice that all
+    // our funds are unspendable
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
+        .unwrap()
+        .assume_checked();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), balance.immature / 2)
+        .current_height(confirmation_height);
+    assert!(matches!(
+        builder.finish(),
+        Err(CreateTxError::CoinSelection(
+            coin_selection::Error::InsufficientFunds {
+                needed: _,
+                available: 0
+            }
+        ))
+    ));
+
+    // Still unspendable...
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), balance.immature / 2)
+        .current_height(not_yet_mature_time);
+    assert_matches!(
+        builder.finish(),
+        Err(CreateTxError::CoinSelection(
+            coin_selection::Error::InsufficientFunds {
+                needed: _,
+                available: 0
+            }
+        ))
+    );
+
+    wallet
+        .insert_checkpoint(BlockId {
+            height: maturity_time,
+            hash: BlockHash::all_zeros(),
+        })
+        .unwrap();
+    let balance = wallet.get_balance();
+    assert_eq!(
+        balance,
+        Balance {
+            immature: Amount::ZERO,
+            trusted_pending: Amount::ZERO,
+            untrusted_pending: Amount::ZERO,
+            confirmed: Amount::from_sat(25_000)
+        }
+    );
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), balance.confirmed / 2)
+        .current_height(maturity_time);
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_allow_dust_limit() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+
+    let mut builder = wallet.build_tx();
+
+    builder.add_recipient(addr.script_pubkey(), Amount::ZERO);
+
+    assert_matches!(
+        builder.finish(),
+        Err(CreateTxError::OutputBelowDustLimit(0))
+    );
+
+    let mut builder = wallet.build_tx();
+
+    builder
+        .allow_dust(true)
+        .add_recipient(addr.script_pubkey(), Amount::ZERO);
+
+    assert!(builder.finish().is_ok());
+}
+
+#[test]
+fn test_fee_rate_sign_no_grinding_high_r() {
+    // Our goal is to obtain a transaction with a signature with high-R (71 bytes
+    // instead of 70). We then check that our fee rate and fee calculation is
+    // alright.
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
+    let mut builder = wallet.build_tx();
+    let mut data = PushBytesBuf::try_from(vec![0]).unwrap();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_rate(fee_rate)
+        .add_data(&data);
+    let mut psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+    let (op_return_vout, _) = psbt
+        .unsigned_tx
+        .output
+        .iter()
+        .enumerate()
+        .find(|(_n, i)| i.script_pubkey.is_op_return())
+        .unwrap();
+
+    let mut sig_len: usize = 0;
+    // We try to sign many different times until we find a longer signature (71 bytes)
+    while sig_len < 71 {
+        // Changing the OP_RETURN data will make the signature change (but not the fee, until
+        // data[0] is small enough)
+        data.as_mut_bytes()[0] += 1;
+        psbt.unsigned_tx.output[op_return_vout].script_pubkey = ScriptBuf::new_op_return(&data);
+        // Clearing the previous signature
+        psbt.inputs[0].partial_sigs.clear();
+        // Signing
+        wallet
+            .sign(
+                &mut psbt,
+                SignOptions {
+                    remove_partial_sigs: false,
+                    try_finalize: false,
+                    allow_grinding: false,
+                    ..Default::default()
+                },
+            )
+            .unwrap();
+        // We only have one key in the partial_sigs map, this is a trick to retrieve it
+        let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
+        sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
+    }
+    // Actually finalizing the transaction...
+    wallet
+        .sign(
+            &mut psbt,
+            SignOptions {
+                remove_partial_sigs: false,
+                allow_grinding: false,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+    // ...and checking that everything is fine
+    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
+}
+
+#[test]
+fn test_fee_rate_sign_grinding_low_r() {
+    // Our goal is to obtain a transaction with a signature with low-R (70 bytes)
+    // by setting the `allow_grinding` signing option as true.
+    // We then check that our fee rate and fee calculation is alright and that our
+    // signature is 70 bytes.
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_rate(fee_rate);
+    let mut psbt = builder.finish().unwrap();
+    let fee = check_fee!(wallet, psbt);
+
+    wallet
+        .sign(
+            &mut psbt,
+            SignOptions {
+                remove_partial_sigs: false,
+                allow_grinding: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+
+    let key = psbt.inputs[0].partial_sigs.keys().next().unwrap();
+    let sig_len = psbt.inputs[0].partial_sigs[key].sig.serialize_der().len();
+    assert_eq!(sig_len, 70);
+    assert_fee_rate!(psbt, fee.unwrap_or(0), fee_rate);
+}
+
+#[test]
+fn test_taproot_load_descriptor_duplicated_keys() {
+    // Added after issue https://github.com/bitcoindevkit/bdk/issues/760
+    //
+    // Having the same key in multiple taproot leaves is safe and should be accepted by BDK
+
+    let (wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
+    let addr = wallet.peek_address(KeychainKind::External, 0);
+
+    assert_eq!(
+        addr.to_string(),
+        "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5"
+    );
+}
+
+#[test]
+/// The wallet should re-use previously allocated change addresses when the tx using them is cancelled
+fn test_tx_cancellation() {
+    macro_rules! new_tx {
+        ($wallet:expr) => {{
+            let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
+                .unwrap()
+                .assume_checked();
+            let mut builder = $wallet.build_tx();
+            builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000));
+
+            let psbt = builder.finish().unwrap();
+
+            psbt
+        }};
+    }
+
+    let (mut wallet, _) =
+        get_funded_wallet_with_change(get_test_wpkh(), Some(get_test_tr_single_sig_xprv()));
+
+    let psbt1 = new_tx!(wallet);
+    let change_derivation_1 = psbt1
+        .unsigned_tx
+        .output
+        .iter()
+        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
+        .unwrap();
+    assert_eq!(change_derivation_1, (KeychainKind::Internal, 0));
+
+    let psbt2 = new_tx!(wallet);
+
+    let change_derivation_2 = psbt2
+        .unsigned_tx
+        .output
+        .iter()
+        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
+        .unwrap();
+    assert_eq!(change_derivation_2, (KeychainKind::Internal, 1));
+
+    wallet.cancel_tx(&psbt1.extract_tx().expect("failed to extract tx"));
+
+    let psbt3 = new_tx!(wallet);
+    let change_derivation_3 = psbt3
+        .unsigned_tx
+        .output
+        .iter()
+        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
+        .unwrap();
+    assert_eq!(change_derivation_3, (KeychainKind::Internal, 0));
+
+    let psbt3 = new_tx!(wallet);
+    let change_derivation_3 = psbt3
+        .unsigned_tx
+        .output
+        .iter()
+        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
+        .unwrap();
+    assert_eq!(change_derivation_3, (KeychainKind::Internal, 2));
+
+    wallet.cancel_tx(&psbt3.extract_tx().expect("failed to extract tx"));
+
+    let psbt3 = new_tx!(wallet);
+    let change_derivation_4 = psbt3
+        .unsigned_tx
+        .output
+        .iter()
+        .find_map(|txout| wallet.derivation_of_spk(&txout.script_pubkey))
+        .unwrap();
+    assert_eq!(change_derivation_4, (KeychainKind::Internal, 2));
+}
+
+#[test]
+fn test_thread_safety() {
+    fn thread_safe<T: Send + Sync>() {}
+    thread_safe::<Wallet>(); // compiles only if true
+}
index 847cd90d63b121eff78aa706cb4649fd52716613..2f562837fe0bfc3d556149389d3f3c90a7f3a9a8 100644 (file)
@@ -4,7 +4,7 @@ version = "0.2.0"
 edition = "2021"
 
 [dependencies]
-bdk = { path = "../../crates/bdk" }
+bdk_wallet = { path = "../../crates/wallet" }
 bdk_electrum = { path = "../../crates/electrum" }
 bdk_file_store = { path = "../../crates/file_store" }
 anyhow = "1"
index eca96f32a05eeb6e69a14454226e52ece10687af..c411713ffa5319813f68a7f9bb2b4f6e6e25f675 100644 (file)
@@ -6,19 +6,20 @@ const BATCH_SIZE: usize = 5;
 use std::io::Write;
 use std::str::FromStr;
 
-use bdk::bitcoin::{Address, Amount};
-use bdk::chain::collections::HashSet;
-use bdk::{bitcoin::Network, Wallet};
-use bdk::{KeychainKind, SignOptions};
 use bdk_electrum::{
     electrum_client::{self, ElectrumApi},
     ElectrumExt,
 };
 use bdk_file_store::Store;
+use bdk_wallet::bitcoin::{Address, Amount};
+use bdk_wallet::chain::collections::HashSet;
+use bdk_wallet::{bitcoin::Network, Wallet};
+use bdk_wallet::{KeychainKind, SignOptions};
 
 fn main() -> Result<(), anyhow::Error> {
     let db_path = std::env::temp_dir().join("bdk-electrum-example");
-    let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
+    let db =
+        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
     let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
     let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 
index c588a87aa630c5bc17ebdb8d77803c7fbd9db425..803c0fd3f5b403e42c8ca130b942d2b00f361843 100644 (file)
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bdk = { path = "../../crates/bdk" }
+bdk_wallet = { path = "../../crates/wallet" }
 bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] }
 bdk_file_store = { path = "../../crates/file_store" }
 tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
index 7664ec32e7588a9be12c6bd772e25034dd4073b9..22fb8b2d3b8e5a003d4fa88531a38a442b53ceb0 100644 (file)
@@ -1,11 +1,11 @@
 use std::{collections::BTreeSet, io::Write, str::FromStr};
 
-use bdk::{
+use bdk_esplora::{esplora_client, EsploraAsyncExt};
+use bdk_file_store::Store;
+use bdk_wallet::{
     bitcoin::{Address, Amount, Network, Script},
     KeychainKind, SignOptions, Wallet,
 };
-use bdk_esplora::{esplora_client, EsploraAsyncExt};
-use bdk_file_store::Store;
 
 const DB_MAGIC: &str = "bdk_wallet_esplora_async_example";
 const SEND_AMOUNT: Amount = Amount::from_sat(5000);
@@ -15,7 +15,8 @@ const PARALLEL_REQUESTS: usize = 5;
 #[tokio::main]
 async fn main() -> Result<(), anyhow::Error> {
     let db_path = std::env::temp_dir().join("bdk-esplora-async-example");
-    let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
+    let db =
+        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
     let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
     let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 
index 0679bd8f38ccf2e6bd44d32e392d77ba109033ef..857660acfbc03d147cd94b071312a527ee9bfe29 100644 (file)
@@ -7,7 +7,7 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bdk = { path = "../../crates/bdk" }
+bdk_wallet = { path = "../../crates/wallet" }
 bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] }
 bdk_file_store = { path = "../../crates/file_store" }
 anyhow = "1"
index 4d713156aa6186ead92583ee9bedbd85fe3772f4..f42cb9d82f5fc4a51e841a187d5695523ea5f1e2 100644 (file)
@@ -5,16 +5,17 @@ const PARALLEL_REQUESTS: usize = 1;
 
 use std::{collections::BTreeSet, io::Write, str::FromStr};
 
-use bdk::{
+use bdk_esplora::{esplora_client, EsploraExt};
+use bdk_file_store::Store;
+use bdk_wallet::{
     bitcoin::{Address, Amount, Network},
     KeychainKind, SignOptions, Wallet,
 };
-use bdk_esplora::{esplora_client, EsploraExt};
-use bdk_file_store::Store;
 
 fn main() -> Result<(), anyhow::Error> {
     let db_path = std::env::temp_dir().join("bdk-esplora-example");
-    let db = Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
+    let db =
+        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
     let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
     let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 
index 174144e9bb219282119d057fb6892637701136f7..b7a9a9e4720ef2013002a972d83801bad73d630b 100644 (file)
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-bdk = { path = "../../crates/bdk" }
+bdk_wallet = { path = "../../crates/wallet" }
 bdk_file_store = { path = "../../crates/file_store" }
 bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" }
 
index 03c1b0f567219d17914d201bbb70e2dccd482220..28eb07b1fed11e697ae4fb923151590a09775dbe 100644 (file)
@@ -4,7 +4,7 @@
 $ cargo run --bin wallet_rpc -- --help
 
 wallet_rpc 0.1.0
-Bitcoind RPC example using `bdk::Wallet`
+Bitcoind RPC example using `bdk_wallet::Wallet`
 
 USAGE:
     wallet_rpc [OPTIONS] <DESCRIPTOR> [CHANGE_DESCRIPTOR]
index 264204a71a66c8143b007c77199185cc0354f756..3bdd515c31b353213b00caced68d4f9996b736c8 100644 (file)
@@ -1,18 +1,18 @@
-use bdk::{
-    bitcoin::{Block, Network, Transaction},
-    wallet::Wallet,
-};
 use bdk_bitcoind_rpc::{
     bitcoincore_rpc::{Auth, Client, RpcApi},
     Emitter,
 };
 use bdk_file_store::Store;
+use bdk_wallet::{
+    bitcoin::{Block, Network, Transaction},
+    wallet::Wallet,
+};
 use clap::{self, Parser};
 use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant};
 
 const DB_MAGIC: &str = "bdk-rpc-wallet-example";
 
-/// Bitcoind RPC example using `bdk::Wallet`.
+/// Bitcoind RPC example using `bdk_wallet::Wallet`.
 ///
 /// This syncs the chain block-by-block and prints the current balance, transaction count and UTXO
 /// count.
@@ -89,7 +89,10 @@ fn main() -> anyhow::Result<()> {
     let mut wallet = Wallet::new_or_load(
         &args.descriptor,
         args.change_descriptor.as_ref(),
-        Store::<bdk::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?,
+        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
+            DB_MAGIC.as_bytes(),
+            args.db_path,
+        )?,
         args.network,
     )?;
     println!(