Merge bitcoindevkit/bdk#1402: [wallet] Improve address API

d39b319ddf6bf3365d5d01841a8d366c6231db06 test(wallet): Test wallet addresses (valued mammal)
a266b4718f7839759a3e615805931041b67b8e91 chore(wallet)!: Remove enum AddressIndex (valued mammal)
d87874780b4a4c7be282381ae1102c028f3ae581 refactor(wallet)!: Remove method get_address (valued mammal)
d3763e5e37569cea0860217fba980ab0389e4e64 feat(wallet): Add new address methods (valued mammal)

Pull request description:

  Improvements to the wallet address API, see commit messages for details.

  ### Notes to the reviewers

  The logic of getting addresses is roughly the same as before when using `AddressIndex`, following this mapping:

  - `New` -> `reveal_next_address`
  - `LastUnused` -> `next_unused_address` (assuming this is what `LastUnused` really means)
  - `Peek` -> `peek_address`

  Wondering whether it makes sense to expose [`is_used`](358e842dcd/crates/chain/src/keychain/txout_index.rs (L236)) for Wallet as well.

  fixes #898

  ### Changelog notice

  Added:

  - Added Wallet methods:
    - `peek_address`
    - `reveal_next_address`
    - `next_unused_address`
    - `reveal_addresses_to`
    - `list_unused_addresses`
    - `mark_used`
    - `unmark_used`

  Removed:

  - Removed Wallet methods:
    - `get_address`
    - `get_internal_address`
    - `try_get_address`
    - `try_get_internal_address`

  - Removed type AddressIndex

  ### Checklists

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  ### Feature

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature
  * [x] This pull request breaks the existing API
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  evanlinjin:
    ACK d39b319ddf6bf3365d5d01841a8d366c6231db06

Tree-SHA512: ab7f3031f552ee6ea58ae4f3c5412bbedc0ea63e662fe9fa402de0f68a50448521be1e118e89f70bf970d5bf44ea1dc66bbeeff3e9312bae966bebd3072a7073
This commit is contained in:
志宇 2024-04-20 15:41:56 +08:00
commit e0bcca32b1
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
10 changed files with 379 additions and 302 deletions

View File

@ -21,7 +21,6 @@ use bitcoin::Network;
use miniscript::policy::Concrete;
use miniscript::Descriptor;
use bdk::wallet::AddressIndex::New;
use bdk::{KeychainKind, Wallet};
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
@ -51,7 +50,7 @@ fn main() -> Result<(), Box<dyn Error>> {
println!(
"First derived address from the descriptor: \n{}",
wallet.get_address(New)
wallet.next_unused_address(KeychainKind::External)?,
);
// BDK also has it's own `Policy` structure to represent the spending condition in a more

View File

@ -74,7 +74,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::Wallet;
/// # use bdk::wallet::AddressIndex::New;
/// # use bdk::KeychainKind;
/// use bdk::template::P2Pkh;
///
/// let key =
@ -82,7 +82,9 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?;
///
/// assert_eq!(
/// wallet.get_address(New).to_string(),
/// wallet
/// .next_unused_address(KeychainKind::External)?
/// .to_string(),
/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
/// );
/// # Ok::<_, Box<dyn std::error::Error>>(())
@ -102,15 +104,17 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::Wallet;
/// # use bdk::KeychainKind;
/// use bdk::template::P2Wpkh_P2Sh;
/// use bdk::wallet::AddressIndex;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?;
///
/// assert_eq!(
/// wallet.get_address(AddressIndex::New).to_string(),
/// wallet
/// .next_unused_address(KeychainKind::External)?
/// .to_string(),
/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
/// );
/// # Ok::<_, Box<dyn std::error::Error>>(())
@ -131,15 +135,17 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet};
/// # use bdk::KeychainKind;
/// use bdk::template::P2Wpkh;
/// use bdk::wallet::AddressIndex::New;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?;
///
/// assert_eq!(
/// wallet.get_address(New).to_string(),
/// wallet
/// .next_unused_address(KeychainKind::External)?
/// .to_string(),
/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
/// );
/// # Ok::<_, Box<dyn std::error::Error>>(())
@ -159,7 +165,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::Wallet;
/// # use bdk::wallet::AddressIndex::New;
/// # use bdk::KeychainKind;
/// use bdk::template::P2TR;
///
/// let key =
@ -167,7 +173,9 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?;
///
/// assert_eq!(
/// wallet.get_address(New).to_string(),
/// wallet
/// .next_unused_address(KeychainKind::External)?
/// .to_string(),
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
/// );
/// # Ok::<_, Box<dyn std::error::Error>>(())
@ -192,7 +200,6 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@ -202,7 +209,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -229,7 +236,6 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
@ -240,7 +246,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -267,7 +273,6 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@ -277,7 +282,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -304,7 +309,6 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
@ -315,7 +319,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -342,7 +346,6 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@ -352,7 +355,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -379,7 +382,6 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
@ -390,7 +392,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -417,7 +419,6 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86;
///
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@ -427,7 +428,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
@ -454,7 +455,6 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86Public;
///
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
@ -465,7 +465,7 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
/// Network::Testnet,
/// )?;
///
/// assert_eq!(wallet.get_address(New).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
/// assert_eq!(wallet.next_unused_address(KeychainKind::External)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```

View File

@ -178,28 +178,6 @@ impl
}
}
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
pub enum AddressIndex {
/// Return a new address after incrementing the current descriptor index.
New,
/// Return the address for the current descriptor index if it has not been used in a received
/// transaction. Otherwise return a new address as with [`AddressIndex::New`].
///
/// Use with caution, if the wallet has not yet detected an address has been used it could
/// return an already used address. This function is primarily meant for situations where the
/// caller is untrusted; for example when deriving donation addresses on-demand for a public
/// web page.
LastUnused,
/// Return the address for a specific descriptor index. Does not change the current descriptor
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address may have already been used.
Peek(u32),
}
/// A derived address and the index it was found at.
/// For convenience this automatically derefs to `Address`
#[derive(Debug, PartialEq, Eq)]
@ -256,36 +234,6 @@ impl Wallet {
}
}
impl Wallet {
/// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
self.try_get_address(address_index).unwrap()
}
/// Infallibly return a derived address using the internal (change) descriptor.
///
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
///
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
self.try_get_internal_address(address_index).unwrap()
}
}
/// The error type when constructing a fresh [`Wallet`].
///
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
@ -670,104 +618,146 @@ impl Wallet {
self.indexed_graph.index.keychains()
}
/// Return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
/// Peek an address of the given `keychain` at `index` without revealing it.
///
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
/// to the `PersistBackend`.
/// For non-wildcard descriptors this returns the same address at every provided index.
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn try_get_address(&mut self, address_index: AddressIndex) -> anyhow::Result<AddressInfo> {
self._get_address(KeychainKind::External, address_index)
}
/// Return a derived address using the internal (change) descriptor.
///
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
///
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
/// to the `PersistBackend`.
///
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
pub fn try_get_internal_address(
&mut self,
address_index: AddressIndex,
) -> anyhow::Result<AddressInfo> {
self._get_address(KeychainKind::Internal, address_index)
}
/// Return a derived address using the specified `keychain` (external/internal).
///
/// If `keychain` is [`KeychainKind::External`], external addresses will be derived (used for
/// receiving funds).
///
/// If `keychain` is [`KeychainKind::Internal`], internal addresses will be derived (used for
/// creating change outputs). If the wallet does not have an internal keychain, it will use the
/// external keychain to derive change outputs.
///
/// See [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will
/// always be returned for any [`AddressIndex`].
///
/// # Panics
///
/// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index.
fn _get_address(
&mut self,
keychain: KeychainKind,
address_index: AddressIndex,
) -> anyhow::Result<AddressInfo> {
/// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
let keychain = self.map_keychain(keychain);
let txout_index = &mut self.indexed_graph.index;
let (index, spk, changeset) = match address_index {
AddressIndex::New => {
let ((index, spk), index_changeset) = txout_index.reveal_next_spk(&keychain);
(index, spk.into(), Some(index_changeset))
}
AddressIndex::LastUnused => {
let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain);
(index, spk.into(), Some(index_changeset))
}
AddressIndex::Peek(mut peek_index) => {
let mut spk_iter = txout_index.unbounded_spk_iter(&keychain);
if !spk_iter.descriptor().has_wildcard() {
peek_index = 0;
}
let (index, spk) = spk_iter
.nth(peek_index as usize)
.expect("derivation index is out of bounds");
(index, spk, None)
}
};
if let Some(changeset) = changeset {
self.persist
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
changeset,
)));
self.persist.commit()?;
let mut spk_iter = self.indexed_graph.index.unbounded_spk_iter(&keychain);
if !spk_iter.descriptor().has_wildcard() {
index = 0;
}
let (index, spk) = spk_iter
.nth(index as usize)
.expect("derivation index is out of bounds");
AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
}
}
/// Attempt to reveal the next address of the given `keychain`.
///
/// This will increment the internal derivation index. If the keychain's descriptor doesn't
/// contain a wildcard or every address is already revealed up to the maximum derivation
/// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki),
/// then returns the last revealed address.
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self.indexed_graph.index.reveal_next_spk(&keychain);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(AddressInfo {
index,
address: Address::from_script(&spk, self.network)
.expect("descriptor must have address form"),
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Reveal addresses up to and including the target `index` and return an iterator
/// of newly revealed addresses.
///
/// If the target `index` is unreachable, we make a best effort to reveal up to the last
/// possible index. If all addresses up to the given `index` are already revealed, then
/// no new addresses are returned.
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn reveal_addresses_to(
&mut self,
keychain: KeychainKind,
index: u32,
) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
let keychain = self.map_keychain(keychain);
let (spk_iter, index_changeset) =
self.indexed_graph.index.reveal_to_target(&keychain, index);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(spk_iter.map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
}))
}
/// Get the next unused address for the given `keychain`, i.e. the address with the lowest
/// derivation index that hasn't been used.
///
/// This will attempt to derive and reveal a new address if no newly revealed addresses
/// are available. See also [`reveal_next_address`](Self::reveal_next_address).
///
/// # Errors
///
/// If writing to persistent storage fails.
pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self.indexed_graph.index.next_unused_spk(&keychain);
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Marks an address used of the given `keychain` at `index`.
///
/// Returns whether the given index was present and then removed from the unused set.
pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
self.indexed_graph.index.mark_used(keychain, index)
}
/// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted
/// back into the unused set.
///
/// Since this is only a superficial marker, it will have no effect if the address at the given
/// `index` was actually used, i.e. the wallet has previously indexed a tx output for the
/// derived spk.
///
/// [`mark_used`]: Self::mark_used
pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool {
self.indexed_graph.index.unmark_used(keychain, index)
}
/// List addresses that are revealed but unused.
///
/// Note if the returned iterator is empty you can reveal more addresses
/// by using [`reveal_next_address`](Self::reveal_next_address) or
/// [`reveal_addresses_to`](Self::reveal_addresses_to).
pub fn list_unused_addresses(
&self,
keychain: KeychainKind,
) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
let keychain = self.map_keychain(keychain);
self.indexed_graph
.index
.unused_keychain_spks(&keychain)
.map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
keychain,
})
}
/// Return whether or not a `script` is part of this wallet (either internal or external)
pub fn is_mine(&self, script: &Script) -> bool {
self.indexed_graph.index.index_of_spk(script).is_some()
@ -2491,7 +2481,7 @@ macro_rules! doctest_wallet {
() => {{
use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
use $crate::chain::{ConfirmationTime, BlockId};
use $crate::wallet::{AddressIndex, Wallet};
use $crate::{KeychainKind, wallet::Wallet};
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
@ -2501,7 +2491,7 @@ macro_rules! doctest_wallet {
Network::Regtest,
)
.unwrap();
let address = wallet.get_address(AddressIndex::New).address;
let address = wallet.peek_address(KeychainKind::External, 0).address;
let tx = Transaction {
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,

View File

@ -1,6 +1,6 @@
#![allow(unused)]
use bdk::{wallet::AddressIndex, KeychainKind, LocalOutput, Wallet};
use bdk::{KeychainKind, LocalOutput, Wallet};
use bdk_chain::indexed_tx_graph::Indexer;
use bdk_chain::{BlockId, ConfirmationTime};
use bitcoin::hashes::Hash;
@ -20,7 +20,7 @@ pub fn get_funded_wallet_with_change(
change: Option<&str>,
) -> (Wallet, bitcoin::Txid) {
let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
let change_address = wallet.get_address(AddressIndex::New).address;
let change_address = wallet.peek_address(KeychainKind::External, 0).address;
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
.expect("address")
.require_network(Network::Regtest)

View File

@ -1,7 +1,5 @@
use bdk::bitcoin::{Amount, FeeRate, Psbt, TxIn};
use bdk::wallet::AddressIndex;
use bdk::wallet::AddressIndex::New;
use bdk::{psbt, SignOptions};
use bdk::{psbt, KeychainKind, SignOptions};
use core::str::FromStr;
mod common;
use common::*;
@ -14,7 +12,7 @@ const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6
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 send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let mut psbt = builder.finish().unwrap();
@ -31,7 +29,7 @@ fn test_psbt_malformed_psbt_input_legacy() {
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 send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let mut psbt = builder.finish().unwrap();
@ -47,7 +45,7 @@ fn test_psbt_malformed_psbt_input_segwit() {
#[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 send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let mut psbt = builder.finish().unwrap();
@ -63,7 +61,7 @@ fn test_psbt_malformed_tx_input() {
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 send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let mut psbt = builder.finish().unwrap();
@ -84,7 +82,7 @@ fn test_psbt_fee_rate_with_witness_utxo() {
let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New);
let addr = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
builder.fee_rate(expected_fee_rate);
@ -109,7 +107,7 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() {
let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New);
let addr = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
builder.fee_rate(expected_fee_rate);
@ -133,7 +131,7 @@ fn test_psbt_fee_rate_with_missing_txout() {
let expected_fee_rate = FeeRate::from_sat_per_kwu(310);
let (mut wpkh_wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wpkh_wallet.get_address(New);
let addr = wpkh_wallet.peek_address(KeychainKind::External, 0);
let mut builder = wpkh_wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
builder.fee_rate(expected_fee_rate);
@ -145,7 +143,7 @@ fn test_psbt_fee_rate_with_missing_txout() {
assert!(wpkh_psbt.fee_rate().is_none());
let (mut pkh_wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = pkh_wallet.get_address(New);
let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
let mut builder = pkh_wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
builder.fee_rate(expected_fee_rate);
@ -174,7 +172,7 @@ fn test_psbt_multiple_internalkey_signers() {
let (mut wallet, _) = get_funded_wallet(&desc);
let to_spend = wallet.get_balance().total();
let send_to = wallet.get_address(AddressIndex::New);
let send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx();
builder.drain_to(send_to.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();

View File

@ -7,8 +7,8 @@ use bdk::signer::{SignOptions, SignerError};
use bdk::wallet::coin_selection::{self, LargestFirstCoinSelection};
use bdk::wallet::error::CreateTxError;
use bdk::wallet::tx_builder::AddForeignUtxoError;
use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet};
use bdk::wallet::{AddressIndex::*, NewError};
use bdk::wallet::NewError;
use bdk::wallet::{AddressInfo, Balance, Wallet};
use bdk::KeychainKind;
use bdk_chain::COINBASE_MATURITY;
use bdk_chain::{BlockId, ConfirmationTime};
@ -26,12 +26,13 @@ mod common;
use common::*;
fn receive_output(wallet: &mut Wallet, value: u64, height: ConfirmationTime) -> OutPoint {
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let tx = Transaction {
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(LastUnused).script_pubkey(),
script_pubkey: addr.script_pubkey(),
value: Amount::from_sat(value),
}],
};
@ -76,7 +77,7 @@ fn load_recovers_wallet() {
let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet)
.expect("must init wallet");
wallet.try_get_address(New).unwrap();
wallet.reveal_next_address(KeychainKind::External).unwrap();
wallet.spk_index().clone()
};
@ -343,7 +344,7 @@ fn test_create_tx_empty_recipients() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -354,7 +355,7 @@ fn test_create_tx_manually_selected_empty_utxos() {
#[test]
fn test_create_tx_version_0() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -365,7 +366,7 @@ fn test_create_tx_version_0() {
#[test]
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -376,7 +377,7 @@ fn test_create_tx_version_1_csv() {
#[test]
fn test_create_tx_custom_version() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -390,7 +391,7 @@ fn test_create_tx_custom_version() {
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -403,7 +404,7 @@ fn test_create_tx_default_locktime_is_last_sync_height() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -419,7 +420,7 @@ fn test_create_tx_fee_sniping_locktime_last_sync() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -430,7 +431,7 @@ fn test_create_tx_default_locktime_cltv() {
#[test]
fn test_create_tx_custom_locktime() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -447,7 +448,7 @@ fn test_create_tx_custom_locktime() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -460,7 +461,7 @@ fn test_create_tx_custom_locktime_compatible_with_cltv() {
#[test]
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -473,7 +474,7 @@ fn test_create_tx_custom_locktime_incompatible_with_cltv() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -484,7 +485,7 @@ fn test_create_tx_no_rbf_csv() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -498,7 +499,7 @@ fn test_create_tx_with_default_rbf_csv() {
#[test]
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -511,7 +512,7 @@ fn test_create_tx_with_custom_rbf_csv() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -522,7 +523,7 @@ fn test_create_tx_no_rbf_cltv() {
#[test]
fn test_create_tx_invalid_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -533,7 +534,7 @@ fn test_create_tx_invalid_rbf_sequence() {
#[test]
fn test_create_tx_custom_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -546,7 +547,7 @@ fn test_create_tx_custom_rbf_sequence() {
#[test]
fn test_create_tx_default_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -557,7 +558,7 @@ fn test_create_tx_default_sequence() {
#[test]
fn test_create_tx_change_policy_no_internal() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -580,7 +581,7 @@ macro_rules! check_fee {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -599,7 +600,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
.unwrap()
.assume_checked();
let drain_addr = wallet.get_address(New);
let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 20_000)
@ -625,7 +626,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
let mut builder = wallet.build_tx();
builder
@ -646,7 +647,7 @@ fn test_create_tx_drain_to_and_utxos() {
#[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 drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(drain_addr.script_pubkey());
builder.finish().unwrap();
@ -655,7 +656,7 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
#[test]
fn test_create_tx_default_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -667,7 +668,7 @@ fn test_create_tx_default_fee_rate() {
#[test]
fn test_create_tx_custom_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -681,7 +682,7 @@ fn test_create_tx_custom_fee_rate() {
#[test]
fn test_create_tx_absolute_fee() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -701,7 +702,7 @@ fn test_create_tx_absolute_fee() {
#[test]
fn test_create_tx_absolute_zero_fee() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -722,7 +723,7 @@ fn test_create_tx_absolute_zero_fee() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -736,7 +737,7 @@ 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -755,7 +756,7 @@ fn test_create_tx_add_change() {
#[test]
fn test_create_tx_skip_change_dust() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 49_800);
let psbt = builder.finish().unwrap();
@ -770,7 +771,7 @@ fn test_create_tx_skip_change_dust() {
#[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);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
// very high fee rate, so that the only output would be below dust
let mut builder = wallet.build_tx();
builder
@ -783,7 +784,7 @@ fn test_create_tx_drain_to_dust_amount() {
#[test]
fn test_create_tx_ordering_respected() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -804,7 +805,7 @@ fn test_create_tx_ordering_respected() {
#[test]
fn test_create_tx_default_sighash() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 30_000);
let psbt = builder.finish().unwrap();
@ -815,7 +816,7 @@ fn test_create_tx_default_sighash() {
#[test]
fn test_create_tx_custom_sighash() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -834,7 +835,7 @@ fn test_create_tx_input_hd_keypaths() {
use core::str::FromStr;
let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -856,7 +857,7 @@ fn test_create_tx_output_hd_keypaths() {
let (mut wallet, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -878,7 +879,7 @@ fn test_create_tx_set_redeem_script_p2sh() {
let (mut wallet, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -901,7 +902,7 @@ fn test_create_tx_set_witness_script_p2wsh() {
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -922,7 +923,7 @@ fn test_create_tx_set_witness_script_p2wsh() {
fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
let (mut wallet, _) =
get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -940,7 +941,7 @@ fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() {
fn test_create_tx_non_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -953,7 +954,7 @@ fn test_create_tx_non_witness_utxo() {
fn test_create_tx_only_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -969,7 +970,7 @@ fn test_create_tx_only_witness_utxo() {
fn test_create_tx_shwpkh_has_witness_utxo() {
let (mut wallet, _) =
get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -981,7 +982,7 @@ fn test_create_tx_shwpkh_has_witness_utxo() {
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap();
@ -996,8 +997,11 @@ fn test_create_tx_add_utxo() {
let small_output_tx = Transaction {
input: vec![],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
version: transaction::Version::non_standard(0),
lock_time: absolute::LockTime::ZERO,
@ -1042,8 +1046,11 @@ fn test_create_tx_manually_selected_insufficient() {
let small_output_tx = Transaction {
input: vec![],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
version: transaction::Version::non_standard(0),
lock_time: absolute::LockTime::ZERO,
@ -1094,8 +1101,11 @@ fn test_create_tx_policy_path_no_csv() {
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(50_000),
script_pubkey: wallet.get_address(New).script_pubkey(),
}],
};
wallet
@ -1167,7 +1177,7 @@ fn test_create_tx_global_xpubs_with_origin() {
use bitcoin::hex::FromHex;
let (mut wallet, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1430,7 +1440,7 @@ fn test_get_psbt_input() {
)]
fn test_create_tx_global_xpubs_origin_missing() {
let (mut wallet, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1444,7 +1454,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
use bitcoin::hex::FromHex;
let (mut wallet, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1465,7 +1475,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -1482,7 +1492,7 @@ fn test_bump_fee_irreplaceable_tx() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let psbt = builder.finish().unwrap();
@ -1506,7 +1516,7 @@ fn test_bump_fee_confirmed_tx() {
#[test]
fn test_bump_fee_low_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1540,7 +1550,7 @@ fn test_bump_fee_low_fee_rate() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1563,7 +1573,7 @@ fn test_bump_fee_low_abs() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -1777,8 +1787,11 @@ fn test_bump_fee_drain_wallet() {
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).script_pubkey(),
}],
};
wallet
@ -1842,7 +1855,10 @@ fn test_bump_fee_remove_output_manually_selected_only() {
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(New).script_pubkey(),
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
};
@ -1896,7 +1912,10 @@ fn test_bump_fee_add_input() {
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![TxOut {
script_pubkey: wallet.get_address(New).script_pubkey(),
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
}],
};
@ -2378,7 +2397,7 @@ fn test_fee_amount_negative_drain_val() {
#[test]
fn test_sign_single_xprv() {
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2393,7 +2412,7 @@ fn test_sign_single_xprv() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2408,7 +2427,7 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2423,7 +2442,7 @@ fn test_sign_single_xprv_bip44_path() {
#[test]
fn test_sign_single_xprv_sh_wpkh() {
let (mut wallet, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2439,7 +2458,7 @@ fn test_sign_single_xprv_sh_wpkh() {
fn test_sign_single_wif() {
let (mut wallet, _) =
get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2454,7 +2473,7 @@ fn test_sign_single_wif() {
#[test]
fn test_sign_single_xprv_no_hd_keypaths() {
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2539,7 +2558,7 @@ 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2569,7 +2588,7 @@ 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2603,7 +2622,7 @@ fn test_sign_nonstandard_sighash() {
let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -2649,12 +2668,25 @@ fn test_unused_address() {
let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet).unwrap();
// `list_unused_addresses` should be empty if we haven't revealed any
assert!(wallet
.list_unused_addresses(KeychainKind::External)
.next()
.is_none());
assert_eq!(
wallet.get_address(LastUnused).to_string(),
wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(LastUnused).to_string(),
wallet
.list_unused_addresses(KeychainKind::External)
.next()
.unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
}
@ -2666,24 +2698,46 @@ fn test_next_unused_address() {
assert_eq!(wallet.derivation_index(KeychainKind::External), None);
assert_eq!(
wallet.get_address(LastUnused).to_string(),
wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
// calling next_unused again gives same address
assert_eq!(
wallet.get_address(LastUnused).to_string(),
wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0));
// test mark used / unused
assert!(wallet.mark_used(KeychainKind::External, 0));
let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
assert_eq!(next_unused_addr.index, 1);
assert!(wallet.unmark_used(KeychainKind::External, 0));
let next_unused_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
assert_eq!(next_unused_addr.index, 0);
// use the above address
receive_output_in_latest_block(&mut wallet, 25_000);
assert_eq!(
wallet.get_address(LastUnused).to_string(),
wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1));
// trying to mark index 0 unused should return false
assert!(!wallet.unmark_used(KeychainKind::External, 0));
}
#[test]
@ -2692,49 +2746,55 @@ fn test_peek_address_at_index() {
None, Network::Testnet).unwrap();
assert_eq!(
wallet.get_address(Peek(1)).to_string(),
wallet.peek_address(KeychainKind::External, 1).to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(0)).to_string(),
wallet.peek_address(KeychainKind::External, 0).to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(Peek(2)).to_string(),
wallet.peek_address(KeychainKind::External, 2).to_string(),
"tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
);
// current new address is not affected
assert_eq!(
wallet.get_address(New).to_string(),
wallet
.reveal_next_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
);
assert_eq!(
wallet.get_address(New).to_string(),
wallet
.reveal_next_address(KeychainKind::External)
.unwrap()
.to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
}
#[test]
fn test_peek_address_at_index_not_derivable() {
let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet).unwrap();
assert_eq!(
wallet.get_address(Peek(1)).to_string(),
wallet.peek_address(KeychainKind::External, 1).to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(0)).to_string(),
wallet.peek_address(KeychainKind::External, 0).to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
assert_eq!(
wallet.get_address(Peek(2)).to_string(),
wallet.peek_address(KeychainKind::External, 2).to_string(),
"tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
);
}
@ -2746,7 +2806,7 @@ fn test_returns_index_and_address() {
// new index 0
assert_eq!(
wallet.get_address(New),
wallet.reveal_next_address(KeychainKind::External).unwrap(),
AddressInfo {
index: 0,
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a")
@ -2758,7 +2818,7 @@ fn test_returns_index_and_address() {
// new index 1
assert_eq!(
wallet.get_address(New),
wallet.reveal_next_address(KeychainKind::External).unwrap(),
AddressInfo {
index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
@ -2770,7 +2830,7 @@ fn test_returns_index_and_address() {
// peek index 25
assert_eq!(
wallet.get_address(Peek(25)),
wallet.peek_address(KeychainKind::External, 25),
AddressInfo {
index: 25,
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2")
@ -2782,7 +2842,7 @@ fn test_returns_index_and_address() {
// new index 2
assert_eq!(
wallet.get_address(New),
wallet.reveal_next_address(KeychainKind::External).unwrap(),
AddressInfo {
index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
@ -2808,7 +2868,7 @@ fn test_sending_to_bip350_bech32m_address() {
fn test_get_address() {
use bdk::descriptor::template::Bip84;
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let mut wallet = Wallet::new_no_persist(
let wallet = Wallet::new_no_persist(
Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)),
Network::Regtest,
@ -2816,7 +2876,7 @@ fn test_get_address() {
.unwrap();
assert_eq!(
wallet.get_address(AddressIndex::New),
wallet.peek_address(KeychainKind::External, 0),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
@ -2827,7 +2887,7 @@ fn test_get_address() {
);
assert_eq!(
wallet.get_internal_address(AddressIndex::New),
wallet.peek_address(KeychainKind::Internal, 0),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79")
@ -2837,11 +2897,11 @@ fn test_get_address() {
}
);
let mut wallet =
let wallet =
Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
assert_eq!(
wallet.get_internal_address(AddressIndex::New),
wallet.peek_address(KeychainKind::Internal, 0),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
@ -2853,6 +2913,28 @@ fn test_get_address() {
);
}
#[test]
fn test_reveal_addresses() {
let desc = get_test_tr_single_sig_xprv();
let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap();
let keychain = KeychainKind::External;
let last_revealed_addr = wallet
.reveal_addresses_to(keychain, 9)
.unwrap()
.last()
.unwrap();
assert_eq!(wallet.derivation_index(keychain), Some(9));
let unused_addrs = wallet.list_unused_addresses(keychain).collect::<Vec<_>>();
assert_eq!(unused_addrs.len(), 10);
assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr);
// revealing to an already revealed index returns nothing
let mut already_revealed = wallet.reveal_addresses_to(keychain, 9).unwrap();
assert!(already_revealed.next().is_none());
}
#[test]
fn test_get_address_no_reuse_single_descriptor() {
use bdk::descriptor::template::Bip84;
@ -2865,10 +2947,16 @@ fn test_get_address_no_reuse_single_descriptor() {
let mut used_set = HashSet::new();
(0..3).for_each(|_| {
let external_addr = wallet.get_address(AddressIndex::New).address;
let external_addr = wallet
.reveal_next_address(KeychainKind::External)
.unwrap()
.address;
assert!(used_set.insert(external_addr));
let internal_addr = wallet.get_internal_address(AddressIndex::New).address;
let internal_addr = wallet
.reveal_next_address(KeychainKind::Internal)
.unwrap()
.address;
assert!(used_set.insert(internal_addr));
});
}
@ -2877,7 +2965,7 @@ fn test_get_address_no_reuse_single_descriptor() {
fn test_taproot_remove_tapfields_after_finalize_sign_option() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -2902,7 +2990,7 @@ fn test_taproot_remove_tapfields_after_finalize_sign_option() {
#[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 addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -2937,7 +3025,7 @@ fn test_taproot_psbt_populate_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 addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
let path = vec![("rn4nre9c".to_string(), vec![0])]
.into_iter()
@ -3003,7 +3091,7 @@ fn test_taproot_psbt_input_tap_tree() {
use bitcoin::taproot;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree());
let addr = wallet.get_address(AddressIndex::Peek(0));
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
@ -3046,7 +3134,7 @@ fn test_taproot_psbt_input_tap_tree() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -3086,7 +3174,7 @@ fn test_taproot_sign_missing_witness_utxo() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet();
let mut psbt = builder.finish().unwrap();
@ -3154,7 +3242,7 @@ fn test_taproot_foreign_utxo() {
}
fn test_spend_from_wallet(mut wallet: Wallet) {
let addr = wallet.get_address(AddressIndex::New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3178,7 +3266,7 @@ fn test_spend_from_wallet(mut 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3213,7 +3301,7 @@ fn test_taproot_script_spend() {
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3244,7 +3332,7 @@ fn test_taproot_script_spend_sign_include_some_leaves() {
use bitcoin::taproot::TapLeafHash;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
let addr = wallet.get_address(AddressIndex::New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3284,7 +3372,7 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() {
use bitcoin::taproot::TapLeafHash;
let (mut wallet, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
let addr = wallet.get_address(AddressIndex::New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3322,7 +3410,7 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() {
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3345,7 +3433,7 @@ fn test_taproot_script_spend_sign_no_leaves() {
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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
@ -3366,7 +3454,7 @@ fn test_taproot_sign_derive_index_from_psbt() {
#[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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3386,7 +3474,7 @@ fn test_taproot_sign_non_default_sighash() {
let sighash = TapSighashType::NonePlusAnyoneCanPay;
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3470,8 +3558,11 @@ fn test_spend_coinbase() {
..Default::default()
}],
output: vec![TxOut {
script_pubkey: wallet
.next_unused_address(KeychainKind::External)
.unwrap()
.script_pubkey(),
value: Amount::from_sat(25_000),
script_pubkey: wallet.get_address(New).address.script_pubkey(),
}],
};
wallet
@ -3559,7 +3650,7 @@ fn test_spend_coinbase() {
fn test_allow_dust_limit() {
let (mut wallet, _) = get_funded_wallet(get_test_single_sig_cltv());
let addr = wallet.get_address(New);
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
@ -3585,7 +3676,7 @@ fn test_fee_rate_sign_no_grinding_high_r() {
// 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
let mut builder = wallet.build_tx();
let mut data = PushBytesBuf::try_from(vec![0]).unwrap();
@ -3651,7 +3742,7 @@ fn test_fee_rate_sign_grinding_low_r() {
// 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 addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let fee_rate = FeeRate::from_sat_per_vb_unchecked(1);
let mut builder = wallet.build_tx();
builder
@ -3684,8 +3775,8 @@ fn test_taproot_load_descriptor_duplicated_keys() {
//
// 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);
let (wallet, _) = get_funded_wallet(get_test_tr_dup_keys());
let addr = wallet.peek_address(KeychainKind::External, 0);
assert_eq!(
addr.to_string(),

View File

@ -6,7 +6,6 @@
//! # use bdk::bitcoin::Network;
//! # use bdk::signer::SignerOrdering;
//! # use bdk_hwi::HWISigner;
//! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{KeychainKind, SignOptions, Wallet};
//! # use hwi::HWIClient;
//! # use std::sync::Arc;

View File

@ -8,8 +8,8 @@ use std::str::FromStr;
use bdk::bitcoin::Address;
use bdk::wallet::Update;
use bdk::SignOptions;
use bdk::{bitcoin::Network, Wallet};
use bdk::{KeychainKind, SignOptions};
use bdk_electrum::{
electrum_client::{self, ElectrumApi},
ElectrumExt, ElectrumUpdate,
@ -29,7 +29,7 @@ fn main() -> Result<(), anyhow::Error> {
Network::Testnet,
)?;
let address = wallet.try_get_address(bdk::wallet::AddressIndex::New)?;
let address = wallet.next_unused_address(KeychainKind::External)?;
println!("Generated Address: {}", address);
let balance = wallet.get_balance();

View File

@ -2,8 +2,8 @@ use std::{io::Write, str::FromStr};
use bdk::{
bitcoin::{Address, Network},
wallet::{AddressIndex, Update},
SignOptions, Wallet,
wallet::Update,
KeychainKind, SignOptions, Wallet,
};
use bdk_esplora::{esplora_client, EsploraAsyncExt};
use bdk_file_store::Store;
@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> {
Network::Testnet,
)?;
let address = wallet.try_get_address(AddressIndex::New)?;
let address = wallet.next_unused_address(KeychainKind::External)?;
println!("Generated Address: {}", address);
let balance = wallet.get_balance();

View File

@ -7,8 +7,8 @@ use std::{io::Write, str::FromStr};
use bdk::{
bitcoin::{Address, Network},
wallet::{AddressIndex, Update},
SignOptions, Wallet,
wallet::Update,
KeychainKind, SignOptions, Wallet,
};
use bdk_esplora::{esplora_client, EsploraExt};
use bdk_file_store::Store;
@ -26,7 +26,7 @@ fn main() -> Result<(), anyhow::Error> {
Network::Testnet,
)?;
let address = wallet.try_get_address(AddressIndex::New)?;
let address = wallet.next_unused_address(KeychainKind::External)?;
println!("Generated Address: {}", address);
let balance = wallet.get_balance();