]> Untitled Git - bdk/commitdiff
Move tests to /tests
authorLLFourn <lloyd.fourn@gmail.com>
Tue, 14 Feb 2023 02:54:07 +0000 (13:54 +1100)
committerDaniela Brozzoni <danielabrozzoni@protonmail.com>
Thu, 2 Mar 2023 09:55:10 +0000 (10:55 +0100)
To stop my rust-anlayzer from killing me

src/psbt/mod.rs
src/wallet/coin_selection.rs
src/wallet/mod.rs
tests/common.rs [new file with mode: 0644]
tests/psbt.rs [new file with mode: 0644]
tests/wallet.rs [new file with mode: 0644]

index 62ef0a7937c43d9310663614c36aac1f38c9274e..8724323679c4797eb72cfa1e6085a730fe465127 100644 (file)
@@ -77,164 +77,3 @@ impl PsbtUtils for Psbt {
         })
     }
 }
-
-#[cfg(test)]
-mod test {
-    use crate::bitcoin::TxIn;
-    use crate::psbt::Psbt;
-    use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
-    use crate::wallet::AddressIndex;
-    use crate::wallet::AddressIndex::New;
-    use crate::{psbt, FeeRate, SignOptions};
-    use core::str::FromStr;
-
-    // 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.get_address(AddressIndex::New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(send_to.script_pubkey(), 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 = 1.2345;
-
-        let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        builder.fee_rate(FeeRate::from_sat_per_vb(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.as_sat_per_vb() >= expected_fee_rate);
-        assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb());
-    }
-
-    #[test]
-    fn test_psbt_fee_rate_with_nonwitness_utxo() {
-        use psbt::PsbtUtils;
-
-        let expected_fee_rate = 1.2345;
-
-        let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        builder.fee_rate(FeeRate::from_sat_per_vb(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.as_sat_per_vb() >= expected_fee_rate);
-        assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb());
-    }
-
-    #[test]
-    fn test_psbt_fee_rate_with_missing_txout() {
-        use psbt::PsbtUtils;
-
-        let expected_fee_rate = 1.2345;
-
-        let (mut wpkh_wallet,  _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wpkh_wallet.get_address(New);
-        let mut builder = wpkh_wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        builder.fee_rate(FeeRate::from_sat_per_vb(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.get_address(New);
-        let mut builder = pkh_wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        builder.fee_rate(FeeRate::from_sat_per_vb(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());
-    }
-}
index bfd1fe57992550a7a82651dbbae1225475ed7e3f..12c89a7b9c489f0315a5b9dbfb04361eba87b73d 100644 (file)
@@ -112,10 +112,7 @@ use rand::thread_rng;
 
 /// Default coin selection algorithm used by [`TxBuilder`](super::tx_builder::TxBuilder) if not
 /// overridden
-#[cfg(not(test))]
 pub type DefaultCoinSelectionAlgorithm = BranchAndBoundCoinSelection;
-#[cfg(test)]
-pub type DefaultCoinSelectionAlgorithm = LargestFirstCoinSelection; // make the tests more predictable
 
 // Base weight of a Txin, not counting the weight needed for satisfying it.
 // prev_txid (32 bytes) + prev_vout (4 bytes) + sequence (4 bytes)
@@ -373,7 +370,7 @@ impl OutputGroup {
 /// 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)]
+#[derive(Debug, Clone)]
 pub struct BranchAndBoundCoinSelection {
     size_of_change: u64,
 }
index 8884553efd419c25724ea687dfe7c5343e6215f8..295a8e38d043f15fb405ab14f92627840c17d7a2 100644 (file)
@@ -19,11 +19,11 @@ use alloc::{
     sync::Arc,
     vec::Vec,
 };
-use bdk_chain::{keychain::KeychainTracker, sparse_chain, BlockId, ConfirmationTime, chain_graph};
+use bdk_chain::{chain_graph, keychain::KeychainTracker, sparse_chain, BlockId, ConfirmationTime};
 use bitcoin::secp256k1::Secp256k1;
+use core::convert::TryInto;
 use core::fmt;
 use core::ops::Deref;
-use core::convert::TryInto;
 
 use bitcoin::consensus::encode::serialize;
 use bitcoin::util::psbt;
@@ -422,7 +422,7 @@ impl Wallet {
 
     /// Iterate over the transactions in the wallet in order of ascending confirmation time with
     /// unconfirmed transactions last.
-    fn transactions(
+    pub fn transactions(
         &self,
     ) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
         self.keychain_tracker
@@ -1640,3319 +1640,3 @@ where
 
     Ok(wallet_name)
 }
-
-#[cfg(test)]
-pub(crate) mod test {
-    use crate::types::KeychainKind;
-    use assert_matches::assert_matches;
-    use bdk_chain::TxHeight;
-    use bitcoin::hashes::Hash;
-    use bitcoin::{util::psbt, Network};
-    use bitcoin::{PackedLockTime, TxIn};
-    use core::str::FromStr;
-
-    use super::*;
-    use crate::signer::SignOptions;
-    use crate::wallet::AddressIndex::*;
-
-    /// Return a fake wallet that appears to be funded for testing.
-    pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
-        let mut wallet = Wallet::new(descriptor, None, Network::Regtest).unwrap();
-        let address = wallet.get_address(AddressIndex::New).address;
-
-        let tx = Transaction {
-            version: 1,
-            lock_time: bitcoin::PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                value: 50_000,
-                script_pubkey: address.script_pubkey(),
-            }],
-        };
-
-        wallet
-            .insert_checkpoint(BlockId {
-                height: 1_000,
-                hash: BlockHash::all_zeros(),
-            })
-            .unwrap();
-        wallet
-            .insert_tx(
-                tx.clone(),
-                ConfirmationTime::Confirmed {
-                    height: 1_000,
-                    time: 100,
-                },
-            )
-            .unwrap();
-
-        (wallet, tx.txid())
-    }
-
-    fn receive_output(wallet: &mut Wallet, value: u64, height: TxHeight) -> OutPoint {
-        let tx = Transaction {
-            version: 1,
-            lock_time: PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                script_pubkey: wallet.get_address(LastUnused).script_pubkey(),
-                value,
-            }],
-        };
-
-        wallet
-            .insert_tx(
-                tx.clone(),
-                match height {
-                    TxHeight::Confirmed(height) => ConfirmationTime::Confirmed {
-                        height,
-                        time: 42_000,
-                    },
-                    TxHeight::Unconfirmed => ConfirmationTime::Unconfirmed,
-                },
-            )
-            .unwrap();
-
-        OutPoint {
-            txid: tx.txid(),
-            vout: 0,
-        }
-    }
-
-    fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
-        let height = wallet.latest_checkpoint().map(|id| id.height).into();
-        receive_output(wallet, value, height)
-    }
-
-    // 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;
-
-    #[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
-            .keychanins()
-            .iter()
-            .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());
-        assert_eq!(wallet.get_balance().confirmed, 50000);
-    }
-
-    pub(crate) fn get_test_wpkh() -> &'static str {
-        "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
-    }
-
-    pub(crate) fn get_test_single_sig_csv() -> &'static str {
-        // and(pk(Alice),older(6))
-        "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
-    }
-
-    pub(crate) 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(crate) fn get_test_single_sig_cltv() -> &'static str {
-        // and(pk(Alice),after(100000))
-        "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
-    }
-
-    pub(crate) fn get_test_tr_single_sig() -> &'static str {
-        "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
-    }
-
-    pub(crate) fn get_test_tr_with_taptree() -> &'static str {
-        "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-    }
-
-    pub(crate) fn get_test_tr_with_taptree_both_priv() -> &'static str {
-        "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(cNaQCDwmmh4dS9LzCgVtyy1e1xjCJ21GUDHe9K98nzb689JvinGV)})"
-    }
-
-    pub(crate) 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(crate) fn get_test_tr_single_sig_xprv() -> &'static str {
-        "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
-    }
-
-    pub(crate) fn get_test_tr_with_taptree_xprv() -> &'static str {
-        "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-    }
-
-    pub(crate) fn get_test_tr_dup_keys() -> &'static str {
-        "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
-    }
-
-    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();
-            $(
-                $( $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)
-                - psbt
-                .unsigned_tx
-                .output
-                .iter()
-                .fold(0, |acc, o| acc + o.value);
-
-            assert_eq!(fee_amount, $fees);
-
-            let tx_fee_rate = FeeRate::from_wu($fees, tx.weight());
-            let fee_rate = $fee_rate;
-
-            if !dust_change {
-                assert!(tx_fee_rate >= fee_rate && (tx_fee_rate - fee_rate).as_sat_per_vb().abs() < 0.5, "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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .manually_selected_only();
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "Invalid version `0`")]
-    fn test_create_tx_version_0() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .version(0);
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    #[should_panic(
-        expected = "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
-    )]
-    fn test_create_tx_version_1_csv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .version(1);
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_custom_version() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .version(42);
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.version, 42);
-    }
-
-    #[test]
-    fn test_create_tx_default_locktime_is_last_sync_height() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.0, 1_000);
-    }
-
-    #[test]
-    fn test_create_tx_fee_sniping_locktime_last_sync() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.0,
-            wallet.latest_checkpoint().unwrap().height
-        );
-    }
-
-    #[test]
-    fn test_create_tx_default_locktime_cltv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.lock_time.0, 100_000);
-    }
-
-    #[test]
-    fn test_create_tx_custom_locktime() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .current_height(630_001)
-            .nlocktime(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.0, 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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .nlocktime(LockTime::from_height(630_000).unwrap());
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.lock_time.0, 630_000);
-    }
-
-    #[test]
-    #[should_panic(
-        expected = "TxBuilder requested timelock of `Blocks(Height(50000))`, but at least `Blocks(Height(100000))` is required to spend from this script"
-    )]
-    fn test_create_tx_custom_locktime_incompatible_with_cltv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .nlocktime(LockTime::from_height(50000).unwrap());
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_no_rbf_csv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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]
-    #[should_panic(
-        expected = "Cannot enable RBF with nSequence `Sequence(3)` given a required OP_CSV of `Sequence(6)`"
-    )]
-    fn test_create_tx_with_custom_rbf_csv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf_with_sequence(Sequence(3));
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_no_rbf_cltv() {
-        let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-    }
-
-    #[test]
-    #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
-    fn test_create_tx_invalid_rbf_sequence() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_custom_rbf_sequence() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-    }
-
-    #[test]
-    #[should_panic(
-        expected = "The `change_policy` can be set only if the wallet has a change_descriptor"
-    )]
-    fn test_create_tx_change_policy_no_internal() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .do_not_spend_change();
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_drain_wallet_and_drain_to() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.output.len(), 1);
-        assert_eq!(
-            psbt.unsigned_tx.output[0].value,
-            50_000 - details.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();
-        let drain_addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 20_000)
-            .drain_to(drain_addr.script_pubkey())
-            .drain_wallet();
-        let (psbt, details) = builder.finish().unwrap();
-        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, 20_000,);
-        assert_eq!(drain_output.value, 30_000 - details.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.get_address(New);
-        let utxos: Vec<_> = wallet
-            .get_available_utxos()
-            .into_iter()
-            .map(|(u, _)| u.outpoint)
-            .collect();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .add_utxos(&utxos)
-            .unwrap();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.output.len(), 1);
-        assert_eq!(
-            psbt.unsigned_tx.output[0].value,
-            50_000 - details.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.get_address(New);
-        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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::default(), @add_signature);
-    }
-
-    #[test]
-    fn test_create_tx_custom_fee_rate() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .fee_rate(FeeRate::from_sat_per_vb(5.0));
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature);
-    }
-
-    #[test]
-    fn test_create_tx_absolute_fee() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .fee_absolute(100);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.fee.unwrap_or(0), 100);
-        assert_eq!(psbt.unsigned_tx.output.len(), 1);
-        assert_eq!(
-            psbt.unsigned_tx.output[0].value,
-            50_000 - details.fee.unwrap_or(0)
-        );
-    }
-
-    #[test]
-    fn test_create_tx_absolute_zero_fee() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .fee_absolute(0);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.fee.unwrap_or(0), 0);
-        assert_eq!(psbt.unsigned_tx.output.len(), 1);
-        assert_eq!(
-            psbt.unsigned_tx.output[0].value,
-            50_000 - details.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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .fee_absolute(60_000);
-        let (_psbt, _details) = builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_add_change() {
-        use super::tx_builder::TxOrdering;
-
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .ordering(TxOrdering::Untouched);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.output.len(), 2);
-        assert_eq!(psbt.unsigned_tx.output[0].value, 25_000);
-        assert_eq!(
-            psbt.unsigned_tx.output[1].value,
-            25_000 - details.fee.unwrap_or(0)
-        );
-    }
-
-    #[test]
-    fn test_create_tx_skip_change_dust() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 49_800);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.output.len(), 1);
-        assert_eq!(psbt.unsigned_tx.output[0].value, 49_800);
-        assert_eq!(details.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.get_address(New);
-        // 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(453.0));
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_ordering_respected() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 30_000)
-            .add_recipient(addr.script_pubkey(), 10_000)
-            .ordering(super::tx_builder::TxOrdering::Bip69Lexicographic);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(psbt.unsigned_tx.output.len(), 3);
-        assert_eq!(
-            psbt.unsigned_tx.output[0].value,
-            10_000 - details.fee.unwrap_or(0)
-        );
-        assert_eq!(psbt.unsigned_tx.output[1].value, 10_000);
-        assert_eq!(psbt.unsigned_tx.output[2].value, 30_000);
-    }
-
-    #[test]
-    fn test_create_tx_default_sighash() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 30_000)
-            .sighash(bitcoin::EcdsaSighashType::Single.into());
-        let (psbt, _) = builder.finish().unwrap();
-
-        assert_eq!(
-            psbt.inputs[0].sighash_type,
-            Some(bitcoin::EcdsaSighashType::Single.into())
-        );
-    }
-
-    #[test]
-    fn test_create_tx_input_hd_keypaths() {
-        use bitcoin::util::bip32::{DerivationPath, Fingerprint};
-        use core::str::FromStr;
-
-        let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-        let addr = wallet.get_address(New);
-        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::util::bip32::{DerivationPath, Fingerprint};
-        use core::str::FromStr;
-
-        let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-
-        let addr = wallet.get_address(New);
-        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::hashes::hex::FromHex;
-
-        let (mut wallet, _) =
-            get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New);
-        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(Script::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::hashes::hex::FromHex;
-
-        let (mut wallet, _) =
-            get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_address(New);
-        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(Script::from(
-                Vec::<u8>::from_hex(
-                    "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
-                )
-                .unwrap()
-            ))
-        );
-    }
-
-    #[test]
-    fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
-        use bitcoin::hashes::hex::FromHex;
-
-        let (mut wallet, _) =
-            get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.drain_to(addr.script_pubkey()).drain_wallet();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex(
-                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac",
-            )
-            .unwrap(),
-        );
-
-        assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_v0_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.get_address(New);
-        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.get_address(New);
-        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.get_address(New);
-        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.get_address(New);
-        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 {
-                value: 25_000,
-                script_pubkey: wallet.get_address(New).address.script_pubkey(),
-            }],
-            version: 0,
-            lock_time: PackedLockTime(0),
-        };
-        wallet
-            .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
-            .unwrap();
-
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 30_000)
-            .add_utxo(OutPoint {
-                txid: small_output_tx.txid(),
-                vout: 0,
-            })
-            .unwrap();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(
-            psbt.unsigned_tx.input.len(),
-            2,
-            "should add an additional input since 25_000 < 30_000"
-        );
-        assert_eq!(details.sent, 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 {
-                value: 25_000,
-                script_pubkey: wallet.get_address(New).address.script_pubkey(),
-            }],
-            version: 0,
-            lock_time: PackedLockTime(0),
-        };
-
-        wallet
-            .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
-            .unwrap();
-
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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();
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 30_000);
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_policy_path_no_csv() {
-        let descriptors = get_test_wpkh();
-        let mut wallet = Wallet::new(descriptors, None, Network::Regtest).unwrap();
-
-        let tx = Transaction {
-            version: 0,
-            lock_time: PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                value: 50_000,
-                script_pubkey: wallet.get_address(New).script_pubkey(),
-            }],
-        };
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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_global_xpubs_with_origin() {
-        use bitcoin::hashes::hex::FromHex;
-        use bitcoin::util::bip32;
-
-        let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .add_global_xpubs();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let key = bip32::ExtendedPubKey::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();
-        let utxo = wallet2.list_unspent().remove(0);
-        let foreign_utxo_satisfaction = wallet2
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .max_satisfaction_weight()
-            .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(), 60_000)
-            .only_witness_utxo()
-            .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
-            .unwrap();
-        let (mut psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(
-            details.sent - details.received,
-            10_000 + details.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 = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")]
-    fn test_add_foreign_utxo_invalid_psbt_input() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let outpoint = wallet.list_unspent()[0].outpoint;
-        let foreign_utxo_satisfaction = wallet
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .max_satisfaction_weight()
-            .unwrap();
-
-        let mut builder = wallet.build_tx();
-        builder
-            .add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction)
-            .unwrap();
-    }
-
-    #[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().remove(0);
-        let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap();
-        let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
-
-        let satisfaction_weight = wallet2
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .max_satisfaction_weight()
-            .unwrap();
-
-        let mut builder = wallet1.build_tx();
-        assert!(
-            builder
-                .add_foreign_utxo(
-                    utxo2.outpoint,
-                    psbt::Input {
-                        non_witness_utxo: Some(tx1),
-                        ..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),
-                        ..Default::default()
-                    },
-                    satisfaction_weight
-                )
-                .is_ok(),
-            "shoulld 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();
-        let utxo2 = wallet2.list_unspent().remove(0);
-
-        let satisfaction_weight = wallet2
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .max_satisfaction_weight()
-            .unwrap();
-
-        let mut builder = wallet1.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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, true).unwrap().transaction.unwrap();
-            let psbt_input = psbt::Input {
-                non_witness_utxo: Some(tx2),
-                ..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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .add_global_xpubs();
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_create_tx_global_xpubs_master_without_origin() {
-        use bitcoin::hashes::hex::FromHex;
-        use bitcoin::util::bip32;
-
-        let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .add_global_xpubs();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let key = bip32::ExtendedPubKey::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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, _) = builder.finish().unwrap();
-
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (psbt, _) = builder.finish().unwrap();
-
-        let tx = psbt.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]
-    #[should_panic(expected = "FeeRateTooLow")]
-    fn test_bump_fee_low_fee_rate() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_rate(FeeRate::from_sat_per_vb(1.0));
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "FeeTooLow")]
-    fn test_bump_fee_low_abs() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf();
-        let (psbt, _) = builder.finish().unwrap();
-
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 25_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent);
-        assert_eq!(
-            details.received + details.fee.unwrap_or(0),
-            original_details.received + original_details.fee.unwrap_or(0)
-        );
-        assert!(details.fee.unwrap_or(0) > original_details.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,
-            25_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature);
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_absolute(200);
-        builder.enable_rbf();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent);
-        assert_eq!(
-            details.received + details.fee.unwrap_or(0),
-            original_details.received + original_details.fee.unwrap_or(0)
-        );
-        assert!(
-            details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0),
-            "{} > {}",
-            details.fee.unwrap_or(0),
-            original_details.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,
-            25_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_eq!(details.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();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder
-            .fee_rate(FeeRate::from_sat_per_vb(2.5))
-            .allow_shrinking(addr.script_pubkey())
-            .unwrap();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent);
-        assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
-
-        let tx = &psbt.unsigned_tx;
-        assert_eq!(tx.output.len(), 1);
-        assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @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();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder
-            .allow_shrinking(addr.script_pubkey())
-            .unwrap()
-            .fee_absolute(300);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent);
-        assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
-
-        let tx = &psbt.unsigned_tx;
-        assert_eq!(tx.output.len(), 1);
-        assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
-
-        assert_eq!(details.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: 1,
-            lock_time: PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                value: 25_000,
-                script_pubkey: wallet.get_address(New).script_pubkey(),
-            }],
-        };
-        wallet
-            .insert_tx(
-                tx.clone(),
-                ConfirmationTime::Confirmed {
-                    height: wallet.latest_checkpoint().unwrap().height,
-                    time: 42_000,
-                },
-            )
-            .unwrap();
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-
-        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, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-        assert_eq!(original_details.sent, 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(5.0));
-        let (_, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, 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: 1,
-            lock_time: PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                script_pubkey: wallet.get_address(New).script_pubkey(),
-                value: 25_000,
-            }],
-        };
-        wallet
-            .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
-            .unwrap();
-        let outpoint = OutPoint {
-            txid: init_tx.txid(),
-            vout: 0,
-        };
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .add_utxo(outpoint)
-            .unwrap()
-            .manually_selected_only()
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-        assert_eq!(original_details.sent, 25_000);
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder
-            .manually_selected_only()
-            .fee_rate(FeeRate::from_sat_per_vb(255.0));
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_bump_fee_add_input() {
-        let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-        let init_tx = Transaction {
-            version: 1,
-            lock_time: PackedLockTime(0),
-            input: vec![],
-            output: vec![TxOut {
-                script_pubkey: wallet.get_address(New).script_pubkey(),
-                value: 25_000,
-            }],
-        };
-        wallet
-            .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
-            .unwrap();
-
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 45_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent + 25_000);
-        assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
-            45_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 45_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_absolute(6_000);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent + 25_000);
-        assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
-            45_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_eq!(details.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();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .add_utxo(op)
-            .unwrap()
-            .manually_selected_only()
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-
-        let tx = psbt.extract_tx();
-        let txid = tx.txid();
-        wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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(50.0));
-        let (psbt, details) = builder.finish().unwrap();
-
-        let original_send_all_amount = original_details.sent - original_details.fee.unwrap_or(0);
-        assert_eq!(details.sent, original_details.sent + 50_000);
-        assert_eq!(
-            details.received,
-            75_000 - original_send_all_amount - details.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,
-            75_000 - original_send_all_amount - details.fee.unwrap_or(0)
-        );
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 45_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_tx();
-        for txin in &mut tx.input {
-            txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realisitc 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).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 + 160 + 112 - 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(FeeRate::from_wu(fee_abs, new_tx_weight));
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(
-            original_details.received,
-            5_000 - original_details.fee.unwrap_or(0)
-        );
-
-        assert_eq!(details.sent, original_details.sent + 25_000);
-        assert_eq!(details.fee.unwrap_or(0), 30_000);
-        assert_eq!(details.received, 0);
-
-        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,
-            45_000
-        );
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 45_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let mut tx = psbt.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.clone(), ConfirmationTime::Unconfirmed)
-            .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(5.0));
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent + 25_000);
-        assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
-            45_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 45_000)
-            .enable_rbf();
-        let (psbt, original_details) = builder.finish().unwrap();
-        let mut tx = psbt.extract_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)
-            .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, details) = builder.finish().unwrap();
-
-        assert_eq!(details.sent, original_details.sent + 25_000);
-        assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
-            45_000
-        );
-        assert_eq!(
-            tx.output
-                .iter()
-                .find(|txout| txout.script_pubkey != addr.script_pubkey())
-                .unwrap()
-                .value,
-            details.received
-        );
-
-        assert_eq!(details.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();
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_wallet()
-            .drain_to(addr.script_pubkey())
-            .enable_rbf();
-        let (psbt, __details) = 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, TxHeight::Unconfirmed);
-        let mut tx = psbt.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).unwrap();
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder.fee_rate(FeeRate::from_sat_per_vb(25.0));
-        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();
-        // We receive a tx with 0 confirmations, which will be used as an input
-        // in the drain tx.
-        receive_output(&mut wallet, 25_000, TxHeight::Unconfirmed);
-        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();
-        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).unwrap();
-
-        let mut builder = wallet.build_fee_bump(txid).unwrap();
-        builder
-            .fee_rate(FeeRate::from_sat_per_vb(15.0))
-            .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();
-        let fee_rate = FeeRate::from_sat_per_vb(2.01);
-        let incoming_op = receive_output_in_latest_block(&mut wallet, 8859);
-
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(send_to.script_pubkey(), 8630)
-            .add_utxo(incoming_op)
-            .unwrap()
-            .enable_rbf()
-            .fee_rate(fee_rate);
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert!(psbt.inputs.len() == 1);
-        assert_fee_rate!(psbt, details.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.get_address(New);
-        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();
-        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.get_address(New);
-        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();
-        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.get_address(New);
-        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();
-        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.get_address(New);
-        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();
-        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.get_address(New);
-        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();
-        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.get_address(New);
-        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();
-        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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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::util::psbt::Input {
-            witness_utxo: Some(TxOut {
-                value: 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.get_address(New);
-            let mut builder = wallet.build_tx();
-            builder.drain_to(addr.script_pubkey()).drain_wallet();
-            let mut psbt = builder.finish().unwrap().0;
-
-            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.get_address(New);
-            let mut builder = wallet.build_tx();
-            builder.drain_to(addr.script_pubkey()).drain_wallet();
-            let mut psbt = builder.finish().unwrap().0;
-
-            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.get_address(New);
-        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(Error::Signer(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();
-        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("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                             None, Network::Testnet).unwrap();
-
-        assert_eq!(
-            wallet.get_address(LastUnused).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-        assert_eq!(
-            wallet.get_address(LastUnused).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-    }
-
-    #[test]
-    fn test_next_unused_address() {
-        let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
-        let mut wallet = Wallet::new(descriptor, None, Network::Testnet).unwrap();
-        assert_eq!(wallet.derivation_index(KeychainKind::External), None);
-
-        assert_eq!(
-            wallet.get_address(LastUnused).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
-        assert_eq!(
-            wallet.get_address(LastUnused).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
-
-        // use the above address
-        receive_output_in_latest_block(&mut wallet, 25_000);
-
-        assert_eq!(
-            wallet.get_address(LastUnused).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-        assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
-    }
-
-    #[test]
-    fn test_peek_address_at_index() {
-        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                             None, Network::Testnet).unwrap();
-
-        assert_eq!(
-            wallet.get_address(Peek(1)).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-
-        assert_eq!(
-            wallet.get_address(Peek(0)).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-
-        assert_eq!(
-            wallet.get_address(Peek(2)).to_string(),
-            "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
-        );
-
-        // current new address is not affected
-        assert_eq!(
-            wallet.get_address(New).to_string(),
-            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
-        );
-
-        assert_eq!(
-            wallet.get_address(New).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-    }
-
-    #[test]
-    fn test_peek_address_at_index_not_derivable() {
-        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
-                                             None, Network::Testnet).unwrap();
-
-        assert_eq!(
-            wallet.get_address(Peek(1)).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-
-        assert_eq!(
-            wallet.get_address(Peek(0)).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-
-        assert_eq!(
-            wallet.get_address(Peek(2)).to_string(),
-            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
-        );
-    }
-
-    #[test]
-    fn test_returns_index_and_address() {
-        let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                             None, Network::Testnet).unwrap();
-
-        // new index 0
-        assert_eq!(
-            wallet.get_address(New),
-            AddressInfo {
-                index: 0,
-                address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        // new index 1
-        assert_eq!(
-            wallet.get_address(New),
-            AddressInfo {
-                index: 1,
-                address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        // peek index 25
-        assert_eq!(
-            wallet.get_address(Peek(25)),
-            AddressInfo {
-                index: 25,
-                address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        // new index 2
-        assert_eq!(
-            wallet.get_address(New),
-            AddressInfo {
-                index: 2,
-                address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
-                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();
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 45_000);
-        builder.finish().unwrap();
-    }
-
-    #[test]
-    fn test_get_address() {
-        use crate::descriptor::template::Bip84;
-        let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let mut wallet = Wallet::new(
-            Bip84(key, KeychainKind::External),
-            Some(Bip84(key, KeychainKind::Internal)),
-            Network::Regtest,
-        )
-        .unwrap();
-
-        assert_eq!(
-            wallet.get_address(AddressIndex::New),
-            AddressInfo {
-                index: 0,
-                address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
-                keychain: KeychainKind::External,
-            }
-        );
-
-        assert_eq!(
-            wallet.get_internal_address(AddressIndex::New),
-            AddressInfo {
-                index: 0,
-                address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79").unwrap(),
-                keychain: KeychainKind::Internal,
-            }
-        );
-
-        let mut wallet =
-            Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
-
-        assert_eq!(
-            wallet.get_internal_address(AddressIndex::New),
-            AddressInfo {
-                index: 0,
-                address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
-                keychain: KeychainKind::Internal,
-            },
-            "when there's no internal descriptor it should just use external"
-        );
-    }
-
-    #[test]
-    fn test_get_address_no_reuse_single_descriptor() {
-        use crate::collections::HashSet;
-        use crate::descriptor::template::Bip84;
-
-        let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-        let mut wallet =
-            Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
-
-        let mut used_set = HashSet::new();
-
-        (0..3).for_each(|_| {
-            let external_addr = wallet.get_address(AddressIndex::New).address;
-            assert!(used_set.insert(external_addr));
-
-            let internal_addr = wallet.get_internal_address(AddressIndex::New).address;
-            assert!(used_set.insert(internal_addr));
-        });
-    }
-
-    #[test]
-    fn test_taproot_psbt_populate_tap_key_origins() {
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
-        let addr = wallet.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
-
-        let path = vec![("e5mmg3xh".to_string(), vec![0])]
-            .into_iter()
-            .collect();
-
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), 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!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
-                    (
-                        vec![],
-                        (FromStr::from_str("871fd295").unwrap(), vec![].into())
-                    )
-                ),
-                (
-                    from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
-                    (
-                        vec![
-                            from_str!(
-                                "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
-                            ),
-                            from_str!(
-                                "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
-                            ),
-                        ],
-                        (FromStr::from_str("ece52657").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 crate::bitcoin::psbt::serialize::Deserialize;
-        use crate::bitcoin::psbt::TapTree;
-        use bitcoin::hashes::hex::FromHex;
-        use bitcoin::util::taproot;
-
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
-        let addr = wallet.get_address(AddressIndex::Peek(0));
-
-        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(
-                FromHex::from_hex(
-                    "61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986"
-                )
-                .unwrap()
-            ),
-        );
-        assert_eq!(
-            psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
-            vec![
-                (taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (from_str!("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac"), taproot::LeafVersion::TapScript)),
-                (taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (from_str!("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac"), 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
-        );
-
-        assert_eq!(
-            psbt.outputs[0].tap_tree,
-            Some(TapTree::deserialize(&Vec::<u8>::from_hex("01c022208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac01c0222051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",).unwrap()).unwrap())
-        );
-    }
-
-    #[test]
-    fn test_taproot_sign_missing_witness_utxo() {
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New);
-        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(Error::Signer(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.get_address(New);
-        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 = wallet.get_tx(prev_txid, true).unwrap().transaction;
-        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();
-        let utxo = wallet2.list_unspent().remove(0);
-        let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
-        let foreign_utxo_satisfaction = wallet2
-            .get_descriptor_for_keychain(KeychainKind::External)
-            .max_satisfaction_weight()
-            .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(), 60_000)
-            .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
-            .unwrap();
-        let (psbt, details) = builder.finish().unwrap();
-
-        assert_eq!(
-            details.sent - details.received,
-            10_000 + details.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.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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 crate::signer::TapLeavesOptions;
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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 crate::signer::TapLeavesOptions;
-        use bitcoin::util::taproot::TapLeafHash;
-
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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 crate::signer::TapLeavesOptions;
-        use bitcoin::util::taproot::TapLeafHash;
-
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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::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 crate::signer::TapLeavesOptions;
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
-        let addr = wallet.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
-
-        let mut builder = wallet.build_tx();
-        builder.add_recipient(addr.script_pubkey(), 25_000);
-        let (mut psbt, _) = builder.finish().unwrap();
-
-        // re-create the wallet with an empty db
-        let wallet_empty =
-            Wallet::new(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.get_address(New);
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .sighash(SchnorrSighashType::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 = SchnorrSighashType::NonePlusAnyoneCanPay;
-
-        let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
-        let addr = wallet.get_address(New);
-        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(Error::Signer(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(Error::Signer(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();
-        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(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: 1,
-            lock_time: bitcoin::PackedLockTime(0),
-            input: vec![TxIn {
-                previous_output: OutPoint::null(),
-                ..Default::default()
-            }],
-            output: vec![TxOut {
-                value: 25_000,
-                script_pubkey: wallet.get_address(New).address.script_pubkey(),
-            }],
-        };
-        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: 25_000,
-                trusted_pending: 0,
-                untrusted_pending: 0,
-                confirmed: 0
-            }
-        );
-
-        // We try to create a transaction, only to notice that all
-        // our funds are unspendable
-        let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
-        let mut builder = wallet.build_tx();
-        builder
-            .add_recipient(addr.script_pubkey(), balance.immature / 2)
-            .current_height(confirmation_height);
-        assert!(matches!(
-            builder.finish(),
-            Err(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(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: 0,
-                trusted_pending: 0,
-                untrusted_pending: 0,
-                confirmed: 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.get_address(New);
-
-        let mut builder = wallet.build_tx();
-
-        builder.add_recipient(addr.script_pubkey(), 0);
-
-        assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
-
-        let mut builder = wallet.build_tx();
-
-        builder
-            .allow_dust(true)
-            .add_recipient(addr.script_pubkey(), 0);
-
-        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.get_address(New);
-        let fee_rate = FeeRate::from_sat_per_vb(1.0);
-        let mut builder = wallet.build_tx();
-        let mut data = vec![0];
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .fee_rate(fee_rate)
-            .add_data(&data);
-        let (mut psbt, details) = builder.finish().unwrap();
-        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[0] += 1;
-            psbt.unsigned_tx.output[op_return_vout].script_pubkey = Script::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, details.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.get_address(New);
-        let fee_rate = FeeRate::from_sat_per_vb(1.0);
-        let mut builder = wallet.build_tx();
-        builder
-            .drain_to(addr.script_pubkey())
-            .drain_wallet()
-            .fee_rate(fee_rate);
-        let (mut psbt, details) = builder.finish().unwrap();
-
-        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, details.fee.unwrap_or(0), fee_rate);
-    }
-
-    #[cfg(feature = "test-hardware-signer")]
-    #[test]
-    fn test_create_signer() {
-        use crate::wallet::hardwaresigner::HWISigner;
-        use hwi::types::HWIChain;
-        use hwi::HWIClient;
-
-        let mut devices = HWIClient::enumerate().unwrap();
-        if devices.is_empty() {
-            panic!("No devices found!");
-        }
-        let device = devices.remove(0).unwrap();
-        let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
-        let descriptors = client.get_descriptors::<String>(None).unwrap();
-        let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
-
-        let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
-        wallet.add_signer(
-            KeychainKind::External,
-            SignerOrdering(200),
-            Arc::new(custom_signer),
-        );
-
-        let addr = wallet.get_address(LastUnused);
-        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);
-    }
-
-    #[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 (mut wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
-        let addr = wallet.get_address(New);
-
-        assert_eq!(
-            addr.to_string(),
-            "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5"
-        );
-    }
-}
diff --git a/tests/common.rs b/tests/common.rs
new file mode 100644 (file)
index 0000000..cd00db8
--- /dev/null
@@ -0,0 +1,86 @@
+#![allow(unused)]
+use bdk::{wallet::AddressIndex, Wallet};
+use bdk_chain::{BlockId, ConfirmationTime};
+use bitcoin::hashes::Hash;
+use bitcoin::{BlockHash, Network, Transaction, TxOut};
+
+/// Return a fake wallet that appears to be funded for testing.
+pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
+    let mut wallet = Wallet::new(descriptor, None, Network::Regtest).unwrap();
+    let address = wallet.get_address(AddressIndex::New).address;
+
+    let tx = Transaction {
+        version: 1,
+        lock_time: bitcoin::PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            value: 50_000,
+            script_pubkey: address.script_pubkey(),
+        }],
+    };
+
+    wallet
+        .insert_checkpoint(BlockId {
+            height: 1_000,
+            hash: BlockHash::all_zeros(),
+        })
+        .unwrap();
+    wallet
+        .insert_tx(
+            tx.clone(),
+            ConfirmationTime::Confirmed {
+                height: 1_000,
+                time: 100,
+            },
+        )
+        .unwrap();
+
+    (wallet, tx.txid())
+}
+
+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)})"
+}
diff --git a/tests/psbt.rs b/tests/psbt.rs
new file mode 100644 (file)
index 0000000..8d399f5
--- /dev/null
@@ -0,0 +1,158 @@
+use bdk::bitcoin::TxIn;
+use bdk::wallet::AddressIndex;
+use bdk::wallet::AddressIndex::New;
+use bdk::{psbt, FeeRate, SignOptions};
+use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
+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.get_address(AddressIndex::New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), 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.get_address(AddressIndex::New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(send_to.script_pubkey(), 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 = 1.2345;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(FeeRate::from_sat_per_vb(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.as_sat_per_vb() >= expected_fee_rate);
+    assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb());
+}
+
+#[test]
+fn test_psbt_fee_rate_with_nonwitness_utxo() {
+    use psbt::PsbtUtils;
+
+    let expected_fee_rate = 1.2345;
+
+    let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(FeeRate::from_sat_per_vb(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.as_sat_per_vb() >= expected_fee_rate);
+    assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb());
+}
+
+#[test]
+fn test_psbt_fee_rate_with_missing_txout() {
+    use psbt::PsbtUtils;
+
+    let expected_fee_rate = 1.2345;
+
+    let (mut wpkh_wallet,  _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let addr = wpkh_wallet.get_address(New);
+    let mut builder = wpkh_wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(FeeRate::from_sat_per_vb(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.get_address(New);
+    let mut builder = pkh_wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    builder.fee_rate(FeeRate::from_sat_per_vb(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());
+}
diff --git a/tests/wallet.rs b/tests/wallet.rs
new file mode 100644 (file)
index 0000000..88de27a
--- /dev/null
@@ -0,0 +1,3241 @@
+use assert_matches::assert_matches;
+use bdk::descriptor::calc_checksum;
+use bdk::signer::{SignOptions, SignerError};
+use bdk::wallet::coin_selection::LargestFirstCoinSelection;
+use bdk::wallet::AddressIndex::*;
+use bdk::wallet::AddressInfo;
+use bdk::wallet::{AddressIndex, Wallet};
+use bdk::Balance;
+use bdk::Error;
+use bdk::FeeRate;
+use bdk::KeychainKind;
+use bdk_chain::BlockId;
+use bdk_chain::COINBASE_MATURITY;
+use bdk_chain::{ConfirmationTime, TxHeight};
+use bitcoin::hashes::Hash;
+use bitcoin::BlockHash;
+use bitcoin::Script;
+use bitcoin::{util::psbt, Network};
+use bitcoin::{
+    Address, EcdsaSighashType, LockTime, OutPoint, PackedLockTime, SchnorrSighashType, Sequence,
+    Transaction, TxIn, TxOut,
+};
+use core::str::FromStr;
+
+mod common;
+use common::*;
+
+fn receive_output(wallet: &mut Wallet, value: u64, height: TxHeight) -> OutPoint {
+    let tx = Transaction {
+        version: 1,
+        lock_time: PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet.get_address(LastUnused).script_pubkey(),
+            value,
+        }],
+    };
+
+    wallet
+        .insert_tx(
+            tx.clone(),
+            match height {
+                TxHeight::Confirmed(height) => ConfirmationTime::Confirmed {
+                    height,
+                    time: 42_000,
+                },
+                TxHeight::Unconfirmed => ConfirmationTime::Unconfirmed,
+            },
+        )
+        .unwrap();
+
+    OutPoint {
+        txid: tx.txid(),
+        vout: 0,
+    }
+}
+
+fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
+    let height = wallet.latest_checkpoint().map(|id| id.height).into();
+    receive_output(wallet, value, height)
+}
+
+// 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;
+
+#[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
+        .keychanins()
+        .iter()
+        .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());
+    assert_eq!(wallet.get_balance().confirmed, 50000);
+}
+
+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();
+        $(
+            $( $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)
+            - psbt
+            .unsigned_tx
+            .output
+            .iter()
+            .fold(0, |acc, o| acc + o.value);
+
+        assert_eq!(fee_amount, $fees);
+
+        let tx_fee_rate = FeeRate::from_wu($fees, tx.weight());
+        let fee_rate = $fee_rate;
+
+        if !dust_change {
+            assert!(tx_fee_rate >= fee_rate && (tx_fee_rate - fee_rate).as_sat_per_vb().abs() < 0.5, "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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .manually_selected_only();
+    builder.finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "Invalid version `0`")]
+fn test_create_tx_version_0() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .version(0);
+    builder.finish().unwrap();
+}
+
+#[test]
+#[should_panic(
+    expected = "TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
+)]
+fn test_create_tx_version_1_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .version(1);
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_custom_version() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .version(42);
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.version, 42);
+}
+
+#[test]
+fn test_create_tx_default_locktime_is_last_sync_height() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.0, 1_000);
+}
+
+#[test]
+fn test_create_tx_fee_sniping_locktime_last_sync() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.0,
+        wallet.latest_checkpoint().unwrap().height
+    );
+}
+
+#[test]
+fn test_create_tx_default_locktime_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.lock_time.0, 100_000);
+}
+
+#[test]
+fn test_create_tx_custom_locktime() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .current_height(630_001)
+        .nlocktime(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.0, 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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .nlocktime(LockTime::from_height(630_000).unwrap());
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.lock_time.0, 630_000);
+}
+
+#[test]
+#[should_panic(
+    expected = "TxBuilder requested timelock of `Blocks(Height(50000))`, but at least `Blocks(Height(100000))` is required to spend from this script"
+)]
+fn test_create_tx_custom_locktime_incompatible_with_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .nlocktime(LockTime::from_height(50000).unwrap());
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_no_rbf_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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]
+#[should_panic(
+    expected = "Cannot enable RBF with nSequence `Sequence(3)` given a required OP_CSV of `Sequence(6)`"
+)]
+fn test_create_tx_with_custom_rbf_csv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_csv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf_with_sequence(Sequence(3));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_no_rbf_cltv() {
+    let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
+#[test]
+#[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
+fn test_create_tx_invalid_rbf_sequence() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf_with_sequence(Sequence(0xFFFFFFFE));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_custom_rbf_sequence() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
+#[test]
+#[should_panic(
+    expected = "The `change_policy` can be set only if the wallet has a change_descriptor"
+)]
+fn test_create_tx_change_policy_no_internal() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .do_not_spend_change();
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_drain_wallet_and_drain_to() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value,
+        50_000 - details.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();
+    let drain_addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 20_000)
+        .drain_to(drain_addr.script_pubkey())
+        .drain_wallet();
+    let (psbt, details) = builder.finish().unwrap();
+    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, 20_000,);
+    assert_eq!(drain_output.value, 30_000 - details.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.get_address(New);
+    let utxos: Vec<_> = wallet
+        .list_unspent()
+        .into_iter()
+        .map(|u| u.outpoint)
+        .collect();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxos(&utxos)
+        .unwrap();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value,
+        50_000 - details.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.get_address(New);
+    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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::default(), @add_signature);
+}
+
+#[test]
+fn test_create_tx_custom_fee_rate() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .fee_rate(FeeRate::from_sat_per_vb(5.0));
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature);
+}
+
+#[test]
+fn test_create_tx_absolute_fee() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(100);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.fee.unwrap_or(0), 100);
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value,
+        50_000 - details.fee.unwrap_or(0)
+    );
+}
+
+#[test]
+fn test_create_tx_absolute_zero_fee() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(0);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.fee.unwrap_or(0), 0);
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value,
+        50_000 - details.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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_absolute(60_000);
+    let (_psbt, _details) = 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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .ordering(TxOrdering::Untouched);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 2);
+    assert_eq!(psbt.unsigned_tx.output[0].value, 25_000);
+    assert_eq!(
+        psbt.unsigned_tx.output[1].value,
+        25_000 - details.fee.unwrap_or(0)
+    );
+}
+
+#[test]
+fn test_create_tx_skip_change_dust() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 49_800);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 1);
+    assert_eq!(psbt.unsigned_tx.output[0].value, 49_800);
+    assert_eq!(details.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.get_address(New);
+    // 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(453.0));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_ordering_respected() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 30_000)
+        .add_recipient(addr.script_pubkey(), 10_000)
+        .ordering(bdk::wallet::tx_builder::TxOrdering::Bip69Lexicographic);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.output.len(), 3);
+    assert_eq!(
+        psbt.unsigned_tx.output[0].value,
+        10_000 - details.fee.unwrap_or(0)
+    );
+    assert_eq!(psbt.unsigned_tx.output[1].value, 10_000);
+    assert_eq!(psbt.unsigned_tx.output[2].value, 30_000);
+}
+
+#[test]
+fn test_create_tx_default_sighash() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 30_000)
+        .sighash(bitcoin::EcdsaSighashType::Single.into());
+    let (psbt, _) = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.inputs[0].sighash_type,
+        Some(bitcoin::EcdsaSighashType::Single.into())
+    );
+}
+
+#[test]
+fn test_create_tx_input_hd_keypaths() {
+    use bitcoin::util::bip32::{DerivationPath, Fingerprint};
+    use core::str::FromStr;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+    let addr = wallet.get_address(New);
+    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::util::bip32::{DerivationPath, Fingerprint};
+    use core::str::FromStr;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+
+    let addr = wallet.get_address(New);
+    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::hashes::hex::FromHex;
+
+    let (mut wallet, _) =
+        get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.get_address(New);
+    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(Script::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::hashes::hex::FromHex;
+
+    let (mut wallet, _) =
+        get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
+    let addr = wallet.get_address(New);
+    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(Script::from(
+            Vec::<u8>::from_hex(
+                "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
+            )
+            .unwrap()
+        ))
+    );
+}
+
+#[test]
+fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
+    use bitcoin::hashes::hex::FromHex;
+
+    let (mut wallet, _) =
+        get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let script = Script::from(
+        Vec::<u8>::from_hex(
+            "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac",
+        )
+        .unwrap(),
+    );
+
+    assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_v0_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.get_address(New);
+    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.get_address(New);
+    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.get_address(New);
+    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.get_address(New);
+    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 {
+            value: 25_000,
+            script_pubkey: wallet.get_address(New).address.script_pubkey(),
+        }],
+        version: 0,
+        lock_time: PackedLockTime(0),
+    };
+    wallet
+        .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
+        .unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 30_000)
+        .add_utxo(OutPoint {
+            txid: small_output_tx.txid(),
+            vout: 0,
+        })
+        .unwrap();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(
+        psbt.unsigned_tx.input.len(),
+        2,
+        "should add an additional input since 25_000 < 30_000"
+    );
+    assert_eq!(details.sent, 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 {
+            value: 25_000,
+            script_pubkey: wallet.get_address(New).address.script_pubkey(),
+        }],
+        version: 0,
+        lock_time: PackedLockTime(0),
+    };
+
+    wallet
+        .insert_tx(small_output_tx.clone(), ConfirmationTime::Unconfirmed)
+        .unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 30_000);
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_policy_path_no_csv() {
+    let descriptors = get_test_wpkh();
+    let mut wallet = Wallet::new(descriptors, None, Network::Regtest).unwrap();
+
+    let tx = Transaction {
+        version: 0,
+        lock_time: PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            value: 50_000,
+            script_pubkey: wallet.get_address(New).script_pubkey(),
+        }],
+    };
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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_global_xpubs_with_origin() {
+    use bitcoin::hashes::hex::FromHex;
+    use bitcoin::util::bip32;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .add_global_xpubs();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let key = bip32::ExtendedPubKey::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();
+    let utxo = wallet2.list_unspent().remove(0);
+    let foreign_utxo_satisfaction = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_satisfaction_weight()
+        .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(), 60_000)
+        .only_witness_utxo()
+        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
+        .unwrap();
+    let (mut psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(
+        details.sent - details.received,
+        10_000 + details.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 = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")]
+fn test_add_foreign_utxo_invalid_psbt_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let outpoint = wallet.list_unspent()[0].outpoint;
+    let foreign_utxo_satisfaction = wallet
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_satisfaction_weight()
+        .unwrap();
+
+    let mut builder = wallet.build_tx();
+    builder
+        .add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction)
+        .unwrap();
+}
+
+#[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().remove(0);
+    let tx1 = wallet1.get_tx(txid1, true).unwrap().transaction.unwrap();
+    let tx2 = wallet2.get_tx(txid2, true).unwrap().transaction.unwrap();
+
+    let satisfaction_weight = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_satisfaction_weight()
+        .unwrap();
+
+    let mut builder = wallet1.build_tx();
+    assert!(
+        builder
+            .add_foreign_utxo(
+                utxo2.outpoint,
+                psbt::Input {
+                    non_witness_utxo: Some(tx1),
+                    ..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),
+                    ..Default::default()
+                },
+                satisfaction_weight
+            )
+            .is_ok(),
+        "shoulld 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();
+    let utxo2 = wallet2.list_unspent().remove(0);
+
+    let satisfaction_weight = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_satisfaction_weight()
+        .unwrap();
+
+    let mut builder = wallet1.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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, true).unwrap().transaction.unwrap();
+        let psbt_input = psbt::Input {
+            non_witness_utxo: Some(tx2),
+            ..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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .add_global_xpubs();
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_create_tx_global_xpubs_master_without_origin() {
+    use bitcoin::hashes::hex::FromHex;
+    use bitcoin::util::bip32;
+
+    let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .add_global_xpubs();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let key = bip32::ExtendedPubKey::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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, _) = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (psbt, _) = builder.finish().unwrap();
+
+    let tx = psbt.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]
+#[should_panic(expected = "FeeRateTooLow")]
+fn test_bump_fee_low_fee_rate() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb(1.0));
+    builder.finish().unwrap();
+}
+
+#[test]
+#[should_panic(expected = "FeeTooLow")]
+fn test_bump_fee_low_abs() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let addr = wallet.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf();
+    let (psbt, _) = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 25_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent);
+    assert_eq!(
+        details.received + details.fee.unwrap_or(0),
+        original_details.received + original_details.fee.unwrap_or(0)
+    );
+    assert!(details.fee.unwrap_or(0) > original_details.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,
+        25_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature);
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(200);
+    builder.enable_rbf();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent);
+    assert_eq!(
+        details.received + details.fee.unwrap_or(0),
+        original_details.received + original_details.fee.unwrap_or(0)
+    );
+    assert!(
+        details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0),
+        "{} > {}",
+        details.fee.unwrap_or(0),
+        original_details.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,
+        25_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_eq!(details.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();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .fee_rate(FeeRate::from_sat_per_vb(2.5))
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent);
+    assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.output.len(), 1);
+    assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @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();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .allow_shrinking(addr.script_pubkey())
+        .unwrap()
+        .fee_absolute(300);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent);
+    assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
+
+    let tx = &psbt.unsigned_tx;
+    assert_eq!(tx.output.len(), 1);
+    assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
+
+    assert_eq!(details.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: 1,
+        lock_time: PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            value: 25_000,
+            script_pubkey: wallet.get_address(New).script_pubkey(),
+        }],
+    };
+    wallet
+        .insert_tx(
+            tx.clone(),
+            ConfirmationTime::Confirmed {
+                height: wallet.latest_checkpoint().unwrap().height,
+                time: 42_000,
+            },
+        )
+        .unwrap();
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+
+    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, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+    assert_eq!(original_details.sent, 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(5.0));
+    let (_, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, 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: 1,
+        lock_time: PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet.get_address(New).script_pubkey(),
+            value: 25_000,
+        }],
+    };
+    wallet
+        .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
+        .unwrap();
+    let outpoint = OutPoint {
+        txid: init_tx.txid(),
+        vout: 0,
+    };
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxo(outpoint)
+        .unwrap()
+        .manually_selected_only()
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+    assert_eq!(original_details.sent, 25_000);
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .manually_selected_only()
+        .fee_rate(FeeRate::from_sat_per_vb(255.0));
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_bump_fee_add_input() {
+    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let init_tx = Transaction {
+        version: 1,
+        lock_time: PackedLockTime(0),
+        input: vec![],
+        output: vec![TxOut {
+            script_pubkey: wallet.get_address(New).script_pubkey(),
+            value: 25_000,
+        }],
+    };
+    wallet
+        .insert_tx(init_tx.clone(), wallet.transactions().last().unwrap().0)
+        .unwrap();
+
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), 45_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb(50.0));
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent + 25_000);
+    assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
+        45_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 45_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_absolute(6_000);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent + 25_000);
+    assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
+        45_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_eq!(details.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();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .add_utxo(op)
+        .unwrap()
+        .manually_selected_only()
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+
+    let tx = psbt.extract_tx();
+    let txid = tx.txid();
+    wallet.insert_tx(tx, ConfirmationTime::Unconfirmed).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(50.0));
+    let (psbt, details) = builder.finish().unwrap();
+
+    let original_send_all_amount = original_details.sent - original_details.fee.unwrap_or(0);
+    assert_eq!(details.sent, original_details.sent + 50_000);
+    assert_eq!(
+        details.received,
+        75_000 - original_send_all_amount - details.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,
+        75_000 - original_send_all_amount - details.fee.unwrap_or(0)
+    );
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @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();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), 45_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let mut tx = psbt.extract_tx();
+    for txin in &mut tx.input {
+        txin.witness.push([0x00; P2WPKH_FAKE_WITNESS_SIZE]); // to get realisitc 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).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 + 160 + 112 - 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(FeeRate::from_wu(fee_abs, new_tx_weight));
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(
+        original_details.received,
+        5_000 - original_details.fee.unwrap_or(0)
+    );
+
+    assert_eq!(details.sent, original_details.sent + 25_000);
+    assert_eq!(details.fee.unwrap_or(0), 30_000);
+    assert_eq!(details.received, 0);
+
+    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,
+        45_000
+    );
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @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();
+    let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection);
+    builder
+        .add_recipient(addr.script_pubkey(), 45_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let mut tx = psbt.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.clone(), ConfirmationTime::Unconfirmed)
+        .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(5.0));
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent + 25_000);
+    assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
+        45_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_fee_rate!(psbt, details.fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 45_000)
+        .enable_rbf();
+    let (psbt, original_details) = builder.finish().unwrap();
+    let mut tx = psbt.extract_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)
+        .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, details) = builder.finish().unwrap();
+
+    assert_eq!(details.sent, original_details.sent + 25_000);
+    assert_eq!(details.fee.unwrap_or(0) + details.received, 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,
+        45_000
+    );
+    assert_eq!(
+        tx.output
+            .iter()
+            .find(|txout| txout.script_pubkey != addr.script_pubkey())
+            .unwrap()
+            .value,
+        details.received
+    );
+
+    assert_eq!(details.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();
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_wallet()
+        .drain_to(addr.script_pubkey())
+        .enable_rbf();
+    let (psbt, __details) = 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, TxHeight::Unconfirmed);
+    let mut tx = psbt.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).unwrap();
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder.fee_rate(FeeRate::from_sat_per_vb(25.0));
+    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();
+    // We receive a tx with 0 confirmations, which will be used as an input
+    // in the drain tx.
+    receive_output(&mut wallet, 25_000, TxHeight::Unconfirmed);
+    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();
+    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).unwrap();
+
+    let mut builder = wallet.build_fee_bump(txid).unwrap();
+    builder
+        .fee_rate(FeeRate::from_sat_per_vb(15.0))
+        .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();
+    let fee_rate = FeeRate::from_sat_per_vb(2.01);
+    let incoming_op = receive_output_in_latest_block(&mut wallet, 8859);
+
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(send_to.script_pubkey(), 8630)
+        .add_utxo(incoming_op)
+        .unwrap()
+        .enable_rbf()
+        .fee_rate(fee_rate);
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert!(psbt.inputs.len() == 1);
+    assert_fee_rate!(psbt, details.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.get_address(New);
+    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();
+    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.get_address(New);
+    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();
+    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.get_address(New);
+    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();
+    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.get_address(New);
+    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();
+    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.get_address(New);
+    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();
+    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.get_address(New);
+    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();
+    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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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::util::psbt::Input {
+        witness_utxo: Some(TxOut {
+            value: 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.get_address(New);
+        let mut builder = wallet.build_tx();
+        builder.drain_to(addr.script_pubkey()).drain_wallet();
+        let mut psbt = builder.finish().unwrap().0;
+
+        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.get_address(New);
+        let mut builder = wallet.build_tx();
+        builder.drain_to(addr.script_pubkey()).drain_wallet();
+        let mut psbt = builder.finish().unwrap().0;
+
+        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.get_address(New);
+    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(bdk::Error::Signer(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();
+    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("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    assert_eq!(
+        wallet.get_address(LastUnused).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(
+        wallet.get_address(LastUnused).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+}
+
+#[test]
+fn test_next_unused_address() {
+    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
+    let mut wallet = Wallet::new(descriptor, None, Network::Testnet).unwrap();
+    assert_eq!(wallet.derivation_index(KeychainKind::External), None);
+
+    assert_eq!(
+        wallet.get_address(LastUnused).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
+    assert_eq!(
+        wallet.get_address(LastUnused).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
+
+    // use the above address
+    receive_output_in_latest_block(&mut wallet, 25_000);
+
+    assert_eq!(
+        wallet.get_address(LastUnused).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+    assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
+}
+
+#[test]
+fn test_peek_address_at_index() {
+    let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    assert_eq!(
+        wallet.get_address(Peek(1)).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.get_address(Peek(0)).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+
+    assert_eq!(
+        wallet.get_address(Peek(2)).to_string(),
+        "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
+    );
+
+    // current new address is not affected
+    assert_eq!(
+        wallet.get_address(New).to_string(),
+        "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+    );
+
+    assert_eq!(
+        wallet.get_address(New).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+}
+
+#[test]
+fn test_peek_address_at_index_not_derivable() {
+    let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+                                 None, Network::Testnet).unwrap();
+
+    assert_eq!(
+        wallet.get_address(Peek(1)).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.get_address(Peek(0)).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+
+    assert_eq!(
+        wallet.get_address(Peek(2)).to_string(),
+        "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+    );
+}
+
+#[test]
+fn test_returns_index_and_address() {
+    let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                 None, Network::Testnet).unwrap();
+
+    // new index 0
+    assert_eq!(
+        wallet.get_address(New),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // new index 1
+    assert_eq!(
+        wallet.get_address(New),
+        AddressInfo {
+            index: 1,
+            address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // peek index 25
+    assert_eq!(
+        wallet.get_address(Peek(25)),
+        AddressInfo {
+            index: 25,
+            address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    // new index 2
+    assert_eq!(
+        wallet.get_address(New),
+        AddressInfo {
+            index: 2,
+            address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
+            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();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 45_000);
+    builder.finish().unwrap();
+}
+
+#[test]
+fn test_get_address() {
+    use bdk::descriptor::template::Bip84;
+    let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+    let mut wallet = Wallet::new(
+        Bip84(key, KeychainKind::External),
+        Some(Bip84(key, KeychainKind::Internal)),
+        Network::Regtest,
+    )
+    .unwrap();
+
+    assert_eq!(
+        wallet.get_address(AddressIndex::New),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
+            keychain: KeychainKind::External,
+        }
+    );
+
+    assert_eq!(
+        wallet.get_internal_address(AddressIndex::New),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79").unwrap(),
+            keychain: KeychainKind::Internal,
+        }
+    );
+
+    let mut wallet =
+        Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+
+    assert_eq!(
+        wallet.get_internal_address(AddressIndex::New),
+        AddressInfo {
+            index: 0,
+            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
+            keychain: KeychainKind::Internal,
+        },
+        "when there's no internal descriptor it should just use external"
+    );
+}
+
+#[test]
+fn test_get_address_no_reuse_single_descriptor() {
+    use bdk::descriptor::template::Bip84;
+    use std::collections::HashSet;
+
+    let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
+    let mut wallet =
+        Wallet::new(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+
+    let mut used_set = HashSet::new();
+
+    (0..3).for_each(|_| {
+        let external_addr = wallet.get_address(AddressIndex::New).address;
+        assert!(used_set.insert(external_addr));
+
+        let internal_addr = wallet.get_internal_address(AddressIndex::New).address;
+        assert!(used_set.insert(internal_addr));
+    });
+}
+
+#[test]
+fn test_taproot_psbt_populate_tap_key_origins() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+    let addr = wallet.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
+
+    let path = vec![("e5mmg3xh".to_string(), vec![0])]
+        .into_iter()
+        .collect();
+
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), 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!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
+                (
+                    vec![],
+                    (FromStr::from_str("871fd295").unwrap(), vec![].into())
+                )
+            ),
+            (
+                from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
+                (
+                    vec![
+                        from_str!(
+                            "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
+                        ),
+                        from_str!(
+                            "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
+                        ),
+                    ],
+                    (FromStr::from_str("ece52657").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 bdk::bitcoin::psbt::serialize::Deserialize;
+    use bdk::bitcoin::psbt::TapTree;
+    use bitcoin::hashes::hex::FromHex;
+    use bitcoin::util::taproot;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
+    let addr = wallet.get_address(AddressIndex::Peek(0));
+
+    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(
+            FromHex::from_hex("61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986")
+                .unwrap()
+        ),
+    );
+    assert_eq!(
+        psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
+        vec![
+            (taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (from_str!("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac"), taproot::LeafVersion::TapScript)),
+            (taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (from_str!("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac"), 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
+    );
+
+    assert_eq!(
+        psbt.outputs[0].tap_tree,
+        Some(TapTree::deserialize(&Vec::<u8>::from_hex("01c022208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac01c0222051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",).unwrap()).unwrap())
+    );
+}
+
+#[test]
+fn test_taproot_sign_missing_witness_utxo() {
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.get_address(New);
+    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(Error::Signer(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.get_address(New);
+    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 = wallet.get_tx(prev_txid, true).unwrap().transaction;
+    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();
+    let utxo = wallet2.list_unspent().remove(0);
+    let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
+    let foreign_utxo_satisfaction = wallet2
+        .get_descriptor_for_keychain(KeychainKind::External)
+        .max_satisfaction_weight()
+        .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(), 60_000)
+        .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
+        .unwrap();
+    let (psbt, details) = builder.finish().unwrap();
+
+    assert_eq!(
+        details.sent - details.received,
+        10_000 + details.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.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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::util::taproot::TapLeafHash;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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::util::taproot::TapLeafHash;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
+    let addr = wallet.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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::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.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 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.get_address(AddressIndex::New);
+
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), 25_000);
+    let (mut psbt, _) = builder.finish().unwrap();
+
+    // re-create the wallet with an empty db
+    let wallet_empty = Wallet::new(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.get_address(New);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .sighash(SchnorrSighashType::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 = SchnorrSighashType::NonePlusAnyoneCanPay;
+
+    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
+    let addr = wallet.get_address(New);
+    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(Error::Signer(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(Error::Signer(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();
+    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(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: 1,
+        lock_time: bitcoin::PackedLockTime(0),
+        input: vec![TxIn {
+            previous_output: OutPoint::null(),
+            ..Default::default()
+        }],
+        output: vec![TxOut {
+            value: 25_000,
+            script_pubkey: wallet.get_address(New).address.script_pubkey(),
+        }],
+    };
+    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: 25_000,
+            trusted_pending: 0,
+            untrusted_pending: 0,
+            confirmed: 0
+        }
+    );
+
+    // We try to create a transaction, only to notice that all
+    // our funds are unspendable
+    let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
+    let mut builder = wallet.build_tx();
+    builder
+        .add_recipient(addr.script_pubkey(), balance.immature / 2)
+        .current_height(confirmation_height);
+    assert!(matches!(
+        builder.finish(),
+        Err(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(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: 0,
+            trusted_pending: 0,
+            untrusted_pending: 0,
+            confirmed: 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.get_address(New);
+
+    let mut builder = wallet.build_tx();
+
+    builder.add_recipient(addr.script_pubkey(), 0);
+
+    assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
+
+    let mut builder = wallet.build_tx();
+
+    builder
+        .allow_dust(true)
+        .add_recipient(addr.script_pubkey(), 0);
+
+    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.get_address(New);
+    let fee_rate = FeeRate::from_sat_per_vb(1.0);
+    let mut builder = wallet.build_tx();
+    let mut data = vec![0];
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_rate(fee_rate)
+        .add_data(&data);
+    let (mut psbt, details) = builder.finish().unwrap();
+    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[0] += 1;
+        psbt.unsigned_tx.output[op_return_vout].script_pubkey = Script::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, details.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.get_address(New);
+    let fee_rate = FeeRate::from_sat_per_vb(1.0);
+    let mut builder = wallet.build_tx();
+    builder
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
+        .fee_rate(fee_rate);
+    let (mut psbt, details) = builder.finish().unwrap();
+
+    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, details.fee.unwrap_or(0), fee_rate);
+}
+
+#[cfg(feature = "test-hardware-signer")]
+#[test]
+fn test_create_signer() {
+    use std::sync::Arc;
+
+    use bdk::signer::SignerOrdering;
+    use bdk::wallet::hardwaresigner::HWISigner;
+    use hwi::types::HWIChain;
+    use hwi::HWIClient;
+
+    let mut devices = HWIClient::enumerate().unwrap();
+    if devices.is_empty() {
+        panic!("No devices found!");
+    }
+    let device = devices.remove(0).unwrap();
+    let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
+    let descriptors = client.get_descriptors::<String>(None).unwrap();
+    let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
+
+    let (mut wallet, _) = get_funded_wallet(&descriptors.internal[0]);
+    wallet.add_signer(
+        KeychainKind::External,
+        SignerOrdering(200),
+        Arc::new(custom_signer),
+    );
+
+    let addr = wallet.get_address(LastUnused);
+    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);
+}
+
+#[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 (mut wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
+    let addr = wallet.get_address(New);
+
+    assert_eq!(
+        addr.to_string(),
+        "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5"
+    );
+}