]> Untitled Git - bdk/commitdiff
[wallet] Refactor get_*_address() into get_address(AddressIndex), update CHANGELOG
authorSteve Myers <steve@notmandatory.org>
Tue, 9 Mar 2021 00:17:10 +0000 (16:17 -0800)
committerSteve Myers <steve@notmandatory.org>
Mon, 15 Mar 2021 15:58:11 +0000 (08:58 -0700)
CHANGELOG.md
README.md
examples/address_validator.rs
examples/compiler.rs
src/descriptor/template.rs
src/lib.rs
src/wallet/address_validator.rs
src/wallet/mod.rs
testutils-macros/src/lib.rs

index 99a9ad220f520c39e54a6a28c25c9ec0bc5d6f6f..2fb53a4eebfb3fe74e24a1df1cc53ce2c2d71d1f 100644 (file)
@@ -13,9 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Wallet
 #### Changed
 - `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
+- `get_new_address()` refactored to `get_address(AddressIndex::New)` to support different `get_address()` index selection strategies
 
 #### Added
-- Added `get_unused_address()` which returns the last generated address if it has not been used or if used in a received transaction returns a new address
+- Added `get_address(AddressIndex::LastUnused)` which returns the last derived address if it has not been used or if used in a received transaction returns a new address
+- Added `get_address(AddressIndex::Peek(u32))` which returns a derived address for a specified descriptor index
 
 ## [v0.4.0] - [v0.3.0]
 
index 2b85314e4539d91bcf44b8ad07a86275a699f7ee..69e0fe476fb6c93ac65b90a1af5f152b90002989 100644 (file)
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ fn main() -> Result<(), bdk::Error> {
 
 ```rust
 use bdk::{Wallet, database::MemoryDatabase};
+use bdk::wallet::AddressIndex::New;
 
 fn main() -> Result<(), bdk::Error> {
     let wallet = Wallet::new_offline(
@@ -76,9 +77,9 @@ fn main() -> Result<(), bdk::Error> {
         MemoryDatabase::default(),
     )?;
 
-    println!("Address #0: {}", wallet.get_new_address()?);
-    println!("Address #1: {}", wallet.get_new_address()?);
-    println!("Address #2: {}", wallet.get_new_address()?);
+    println!("Address #0: {}", wallet.get_address(New)?);
+    println!("Address #1: {}", wallet.get_address(New)?);
+    println!("Address #2: {}", wallet.get_address(New)?);
 
     Ok(())
 }
@@ -92,6 +93,7 @@ use bdk::database::MemoryDatabase;
 use bdk::blockchain::{noop_progress, ElectrumBlockchain};
 
 use bdk::electrum_client::Client;
+use bdk::wallet::AddressIndex::New;
 
 use bitcoin::consensus::serialize;
 
@@ -107,7 +109,7 @@ fn main() -> Result<(), bdk::Error> {
 
     wallet.sync(noop_progress(), None)?;
 
-    let send_to = wallet.get_new_address()?;
+    let send_to = wallet.get_address(New)?;
     let (psbt, details) = {
         let mut builder = wallet.build_tx();
         builder
index a925d4fd87a5137c5609bcd4ba8514c06593b1ae..5ce73e5382a3d1d8711a54a2024a4141d9564bee 100644 (file)
@@ -18,6 +18,7 @@ use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
 use bdk::KeychainKind;
 use bdk::Wallet;
 
+use bdk::wallet::AddressIndex::New;
 use bitcoin::hashes::hex::FromHex;
 use bitcoin::util::bip32::Fingerprint;
 use bitcoin::{Network, Script};
@@ -52,9 +53,9 @@ fn main() -> Result<(), bdk::Error> {
 
     wallet.add_address_validator(Arc::new(DummyValidator));
 
-    wallet.get_new_address()?;
-    wallet.get_new_address()?;
-    wallet.get_new_address()?;
+    wallet.get_address(New)?;
+    wallet.get_address(New)?;
+    wallet.get_address(New)?;
 
     Ok(())
 }
index 88cb078938204b0f7544694c3b3480f12bd9b4cc..e1af021166bef7c50a1336b40e5601de6b11f96d 100644 (file)
@@ -28,6 +28,7 @@ use miniscript::policy::Concrete;
 use miniscript::Descriptor;
 
 use bdk::database::memory::MemoryDatabase;
+use bdk::wallet::AddressIndex::New;
 use bdk::{KeychainKind, Wallet};
 
 fn main() -> Result<(), Box<dyn Error>> {
@@ -90,7 +91,7 @@ fn main() -> Result<(), Box<dyn Error>> {
         .unwrap_or(Network::Testnet);
     let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
 
-    info!("... First address: {}", wallet.get_new_address()?);
+    info!("... First address: {}", wallet.get_address(New)?);
 
     if matches.is_present("parsed_policy") {
         let spending_policy = wallet.policies(KeychainKind::External)?;
index 012d8966ac4045417f9a85f5531883841a52344f..d48bc434e689fafb8031e3bc0d5c05b8adbfe2c1 100644 (file)
@@ -74,6 +74,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::P2PKH;
 ///
 /// let key =
@@ -86,7 +87,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_new_address()?.to_string(),
+///     wallet.get_address(New)?.to_string(),
 ///     "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -107,6 +108,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::P2WPKH_P2SH;
 ///
 /// let key =
@@ -119,7 +121,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_new_address()?.to_string(),
+///     wallet.get_address(New)?.to_string(),
 ///     "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -141,6 +143,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::P2WPKH;
 ///
 /// let key =
@@ -153,7 +156,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
 /// )?;
 ///
 /// assert_eq!(
-///     wallet.get_new_address()?.to_string(),
+///     wallet.get_address(New)?.to_string(),
 ///     "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd"
 /// );
 /// # Ok::<_, Box<dyn std::error::Error>>(())
@@ -179,6 +182,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP44;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@@ -189,7 +193,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -217,6 +221,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP44Public;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
@@ -228,7 +233,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -253,6 +258,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP49;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@@ -263,7 +269,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -291,6 +297,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP49Public;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
@@ -302,7 +309,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -327,6 +334,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP84;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
@@ -337,7 +345,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
@@ -365,6 +373,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
 /// # use bdk::bitcoin::{PrivateKey, Network};
 /// # use bdk::{Wallet,  KeychainKind};
 /// # use bdk::database::MemoryDatabase;
+/// # use bdk::wallet::AddressIndex::New;
 /// use bdk::template::BIP84Public;
 ///
 /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
@@ -376,7 +385,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
 ///     MemoryDatabase::default()
 /// )?;
 ///
-/// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
+/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
 /// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
index e9ce3f5b8b0ada27b00604cbc53d3c64fb59939e..44d067d933185737902626b473c68e463a3faf17 100644 (file)
 //! ```
 //! use bdk::{Wallet};
 //! use bdk::database::MemoryDatabase;
+//! use bdk::wallet::AddressIndex::New;
 //!
 //! fn main() -> Result<(), bdk::Error> {
-//!     let wallet = Wallet::new_offline(
+//! let wallet = Wallet::new_offline(
 //!         "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
 //!         Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
 //!         bitcoin::Network::Testnet,
 //!         MemoryDatabase::default(),
 //!     )?;
 //!
-//!     println!("Address #0: {}", wallet.get_new_address()?);
-//!     println!("Address #1: {}", wallet.get_new_address()?);
-//!     println!("Address #2: {}", wallet.get_new_address()?);
+//!     println!("Address #0: {}", wallet.get_address(New)?);
+//!     println!("Address #1: {}", wallet.get_address(New)?);
+//!     println!("Address #2: {}", wallet.get_address(New)?);
 //!
 //!     Ok(())
 //! }
 //! use bdk::electrum_client::Client;
 //!
 //! use bitcoin::consensus::serialize;
+//! use bdk::wallet::AddressIndex::New;
 //!
 //! fn main() -> Result<(), bdk::Error> {
 //!     let client = Client::new("ssl://electrum.blockstream.info:60002")?;
 //!
 //!     wallet.sync(noop_progress(), None)?;
 //!
-//!     let send_to = wallet.get_new_address()?;
+//!     let send_to = wallet.get_address(New)?;
 //!     let (psbt, details) = wallet.build_tx()
 //!         .add_recipient(send_to.script_pubkey(), 50_000)
 //!         .enable_rbf()
index 368d278ebd4dfd06325cbc2ad192dc4f4fe070f7..01ef35efd5b83e9007df04e2fbe2f1c03398b887 100644 (file)
@@ -20,7 +20,7 @@
 //! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
 //! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
 //! whenever a new address is generated (either explicitly by the user with
-//! [`Wallet::get_new_address`](super::Wallet::get_new_address) or internally to create a change
+//! [`Wallet::get_address`](super::Wallet::get_address) or internally to create a change
 //! address) all the attached validators will be polled, in sequence. All of them must complete
 //! successfully to continue.
 //!
@@ -32,6 +32,7 @@
 //! # use bdk::address_validator::*;
 //! # use bdk::database::*;
 //! # use bdk::*;
+//! # use bdk::wallet::AddressIndex::New;
 //! #[derive(Debug)]
 //! struct PrintAddressAndContinue;
 //!
@@ -57,7 +58,7 @@
 //! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
 //! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
 //!
-//! let address = wallet.get_new_address()?;
+//! let address = wallet.get_address(New)?;
 //! println!("Address: {}", address);
 //! # Ok::<(), bdk::Error>(())
 //! ```
@@ -115,6 +116,7 @@ mod test {
 
     use super::*;
     use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
+    use crate::wallet::AddressIndex::New;
 
     #[derive(Debug)]
     struct TestValidator;
@@ -135,7 +137,7 @@ mod test {
         let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
         wallet.add_address_validator(Arc::new(TestValidator));
 
-        wallet.get_new_address().unwrap();
+        wallet.get_address(New).unwrap();
     }
 
     #[test]
index 2b17871a219dbcbeb372417a3ff544e33e35f112..406ca3c7f2fe202a98379397edf2483fa0cb782a 100644 (file)
@@ -67,7 +67,7 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
 ///
 /// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
 /// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
-/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance),
+/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
 /// [creating transactions](Wallet::build_tx), etc.
 ///
 /// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
@@ -162,54 +162,87 @@ where
     }
 }
 
+/// The address index selection strategy to use to derived an address from the wallet's external
+/// descriptor. See [`Wallet::get_address`].
+#[derive(Debug)]
+pub enum AddressIndex {
+    /// 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),
+    /// 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,
+}
+
 // offline actions, always available
 impl<B, D> Wallet<B, D>
 where
     D: BatchDatabase,
 {
-    /// Return a newly generated address using the external descriptor
-    pub fn get_new_address(&self) -> Result<Address, Error> {
-        let index = self.fetch_and_increment_index(KeychainKind::External)?;
-
+    // Return derived address for the external descriptor at a specific index
+    fn peek_address(&self, index: u32) -> Result<Address, Error> {
         self.descriptor
             .as_derived(index, &self.secp)
             .address(self.network)
             .map_err(|_| Error::ScriptDoesntHaveAddressForm)
     }
 
-    /// Return the the last previously generated address if it has not been used in a received
-    /// transaction. Otherwise return a new address using [`Wallet::get_new_address`].
-    ///
-    /// 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 generating donation addresses on-demand for a public
-    /// web page.
-    ///
-    pub fn get_unused_address(&self) -> Result<Address, Error> {
-        let index = self.fetch_index(KeychainKind::External)?;
+    // Return a newly derived address using the external descriptor
+    fn get_new_address(&self) -> Result<Address, Error> {
+        let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
 
-        let script = self
-            .descriptor
-            .as_derived(index, &self.secp)
-            .script_pubkey();
+        self.descriptor
+            .as_derived(incremented_index, &self.secp)
+            .address(self.network)
+            .map_err(|_| Error::ScriptDoesntHaveAddressForm)
+    }
+
+    // Return the the last previously derived address if it has not been used in a received
+    // transaction. Otherwise return a new address using [`Wallet::get_new_address`].
+    fn get_unused_address(&self) -> Result<Address, Error> {
+        let current_index = self.fetch_index(KeychainKind::External)?;
+
+        let derived_key = self.descriptor.as_derived(current_index, &self.secp);
+
+        let script_pubkey = derived_key.script_pubkey();
 
         let found_used = self
             .list_transactions(true)?
             .iter()
             .flat_map(|tx_details| tx_details.transaction.as_ref())
             .flat_map(|tx| tx.output.iter())
-            .any(|o| o.script_pubkey == script);
+            .any(|o| o.script_pubkey == script_pubkey);
 
         if found_used {
             self.get_new_address()
         } else {
-            self.descriptor
-                .as_derived(index, &self.secp)
+            derived_key
                 .address(self.network)
                 .map_err(|_| Error::ScriptDoesntHaveAddressForm)
         }
     }
 
+    /// Return a derived address using the external descriptor, see [`AddressIndex`] for
+    /// available address index selection strategies. If the wallet descriptor is not derivable
+    /// (ie. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
+    pub fn get_address(&self, address_index: AddressIndex) -> Result<Address, Error> {
+        match address_index {
+            AddressIndex::New => self.get_new_address(),
+            AddressIndex::LastUnused => self.get_unused_address(),
+            AddressIndex::Peek(index) => self.peek_address(index),
+        }
+    }
+
     /// Return whether or not a `script` is part of this wallet (either internal or external)
     pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
         self.database.borrow().is_mine(script)
@@ -1417,6 +1450,7 @@ mod test {
     use crate::types::KeychainKind;
 
     use super::*;
+    use crate::wallet::AddressIndex::{LastUnused, New, Peek};
 
     #[test]
     fn test_cache_addresses_fixed() {
@@ -1430,11 +1464,11 @@ mod test {
         .unwrap();
 
         assert_eq!(
-            wallet.get_new_address().unwrap().to_string(),
+            wallet.get_address(New).unwrap().to_string(),
             "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
         );
         assert_eq!(
-            wallet.get_new_address().unwrap().to_string(),
+            wallet.get_address(New).unwrap().to_string(),
             "tb1qj08ys4ct2hzzc2hcz6h2hgrvlmsjynaw43s835"
         );
 
@@ -1458,11 +1492,11 @@ mod test {
         let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
 
         assert_eq!(
-            wallet.get_new_address().unwrap().to_string(),
+            wallet.get_address(New).unwrap().to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
         assert_eq!(
-            wallet.get_new_address().unwrap().to_string(),
+            wallet.get_address(New).unwrap().to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
 
@@ -1486,7 +1520,7 @@ mod test {
         let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
 
         assert_eq!(
-            wallet.get_new_address().unwrap().to_string(),
+            wallet.get_address(New).unwrap().to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
         assert!(wallet
@@ -1497,7 +1531,7 @@ mod test {
             .is_some());
 
         for _ in 0..CACHE_ADDR_BATCH_SIZE {
-            wallet.get_new_address().unwrap();
+            wallet.get_address(New).unwrap();
         }
 
         assert!(wallet
@@ -1594,7 +1628,7 @@ mod test {
     #[should_panic(expected = "NoUtxosSelected")]
     fn test_create_tx_manually_selected_empty_utxos() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1606,7 +1640,7 @@ mod test {
     #[should_panic(expected = "Invalid version `0`")]
     fn test_create_tx_version_0() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1620,7 +1654,7 @@ mod test {
     )]
     fn test_create_tx_version_1_csv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1631,7 +1665,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_version() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1644,7 +1678,7 @@ mod test {
     #[test]
     fn test_create_tx_default_locktime() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1655,7 +1689,7 @@ mod test {
     #[test]
     fn test_create_tx_default_locktime_cltv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1666,7 +1700,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_locktime() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1679,7 +1713,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_locktime_compatible_with_cltv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1695,7 +1729,7 @@ mod test {
     )]
     fn test_create_tx_custom_locktime_incompatible_with_cltv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1706,7 +1740,7 @@ mod test {
     #[test]
     fn test_create_tx_no_rbf_csv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1717,7 +1751,7 @@ mod test {
     #[test]
     fn test_create_tx_with_default_rbf_csv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1734,7 +1768,7 @@ mod test {
     )]
     fn test_create_tx_with_custom_rbf_csv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1745,7 +1779,7 @@ mod test {
     #[test]
     fn test_create_tx_no_rbf_cltv() {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1757,7 +1791,7 @@ mod test {
     #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
     fn test_create_tx_invalid_rbf_sequence() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1768,7 +1802,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_rbf_sequence() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1781,7 +1815,7 @@ mod test {
     #[test]
     fn test_create_tx_default_sequence() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1795,7 +1829,7 @@ mod test {
     )]
     fn test_create_tx_change_policy_no_internal() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1806,7 +1840,7 @@ mod test {
     #[test]
     fn test_create_tx_single_recipient_drain_wallet() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -1823,7 +1857,7 @@ mod test {
     #[test]
     fn test_create_tx_default_fee_rate() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, details) = builder.finish().unwrap();
@@ -1834,7 +1868,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_fee_rate() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1847,7 +1881,7 @@ mod test {
     #[test]
     fn test_create_tx_absolute_fee() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -1866,7 +1900,7 @@ mod test {
     #[test]
     fn test_create_tx_absolute_zero_fee() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -1886,7 +1920,7 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_create_tx_absolute_high_fee() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -1900,7 +1934,7 @@ mod test {
         use super::tx_builder::TxOrdering;
 
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -1918,7 +1952,7 @@ mod test {
     #[test]
     fn test_create_tx_skip_change_dust() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 49_800);
         let (psbt, details) = builder.finish().unwrap();
@@ -1932,7 +1966,7 @@ mod test {
     #[should_panic(expected = "InsufficientFunds")]
     fn test_create_tx_single_recipient_dust_amount() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         // very high fee rate, so that the only output would be below dust
         let mut builder = wallet.build_tx();
         builder
@@ -1945,7 +1979,7 @@ mod test {
     #[test]
     fn test_create_tx_ordering_respected() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
@@ -1965,7 +1999,7 @@ mod test {
     #[test]
     fn test_create_tx_default_sighash() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 30_000);
         let (psbt, _) = builder.finish().unwrap();
@@ -1976,7 +2010,7 @@ mod test {
     #[test]
     fn test_create_tx_custom_sighash() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 30_000)
@@ -1995,7 +2029,7 @@ mod test {
         use std::str::FromStr;
 
         let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2019,7 +2053,7 @@ mod test {
 
         let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
         // cache some addresses
-        wallet.get_new_address().unwrap();
+        wallet.get_address(New).unwrap();
 
         let addr = testutils!(@external descriptors, 5);
         let mut builder = wallet.build_tx();
@@ -2044,7 +2078,7 @@ mod test {
 
         let (wallet, _, _) =
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2069,7 +2103,7 @@ mod test {
 
         let (wallet, _, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2094,7 +2128,7 @@ mod test {
 
         let (wallet, _, _) =
             get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2116,7 +2150,7 @@ mod test {
     fn test_create_tx_non_witness_utxo() {
         let (wallet, _, _) =
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2131,7 +2165,7 @@ mod test {
     fn test_create_tx_only_witness_utxo() {
         let (wallet, _, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2146,7 +2180,7 @@ mod test {
     fn test_create_tx_shwpkh_has_witness_utxo() {
         let (wallet, _, _) =
             get_funded_wallet("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2161,7 +2195,7 @@ mod test {
     fn test_create_tx_both_non_witness_utxo_and_witness_utxo() {
         let (wallet, _, _) =
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -2280,7 +2314,7 @@ mod test {
         use bitcoin::util::psbt::raw::Key;
 
         let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2486,7 +2520,7 @@ mod test {
     )]
     fn test_create_tx_global_xpubs_origin_missing() {
         let (wallet, _, _) = get_funded_wallet("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2501,7 +2535,7 @@ mod test {
         use bitcoin::util::psbt::raw::Key;
 
         let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2525,7 +2559,7 @@ mod test {
     #[should_panic(expected = "IrreplaceableTransaction")]
     fn test_bump_fee_irreplaceable_tx() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, mut details) = builder.finish().unwrap();
@@ -2543,7 +2577,7 @@ mod test {
     #[should_panic(expected = "TransactionConfirmed")]
     fn test_bump_fee_confirmed_tx() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder.add_recipient(addr.script_pubkey(), 25_000);
         let (psbt, mut details) = builder.finish().unwrap();
@@ -2562,7 +2596,7 @@ mod test {
     #[should_panic(expected = "FeeRateTooLow")]
     fn test_bump_fee_low_fee_rate() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2584,7 +2618,7 @@ mod test {
     #[should_panic(expected = "FeeTooLow")]
     fn test_bump_fee_low_abs() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -2606,7 +2640,7 @@ mod test {
     #[should_panic(expected = "FeeTooLow")]
     fn test_bump_fee_zero_abs() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .add_recipient(addr.script_pubkey(), 25_000)
@@ -3349,7 +3383,7 @@ mod test {
     #[test]
     fn test_sign_single_xprv() {
         let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -3383,7 +3417,7 @@ mod test {
     #[test]
     fn test_sign_single_xprv_bip44_path() {
         let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -3400,7 +3434,7 @@ mod test {
     #[test]
     fn test_sign_single_xprv_sh_wpkh() {
         let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -3418,7 +3452,7 @@ mod test {
     fn test_sign_single_wif() {
         let (wallet, _, _) =
             get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -3435,7 +3469,7 @@ mod test {
     #[test]
     fn test_sign_single_xprv_no_hd_keypaths() {
         let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
-        let addr = wallet.get_new_address().unwrap();
+        let addr = wallet.get_address(New).unwrap();
         let mut builder = wallet.build_tx();
         builder
             .set_single_recipient(addr.script_pubkey())
@@ -3512,11 +3546,11 @@ mod test {
                                          None, Network::Testnet, db).unwrap();
 
         assert_eq!(
-            wallet.get_unused_address().unwrap().to_string(),
+            wallet.get_address(LastUnused).unwrap().to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
         assert_eq!(
-            wallet.get_unused_address().unwrap().to_string(),
+            wallet.get_address(LastUnused).unwrap().to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
     }
@@ -3534,7 +3568,7 @@ mod test {
         .unwrap();
 
         assert_eq!(
-            wallet.get_unused_address().unwrap().to_string(),
+            wallet.get_address(LastUnused).unwrap().to_string(),
             "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
         );
 
@@ -3546,7 +3580,62 @@ mod test {
         );
 
         assert_eq!(
-            wallet.get_unused_address().unwrap().to_string(),
+            wallet.get_address(LastUnused).unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+    }
+
+    #[test]
+    fn test_peek_address_at_index() {
+        let db = MemoryDatabase::new();
+        let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+                                         None, Network::Testnet, db).unwrap();
+
+        assert_eq!(
+            wallet.get_address(Peek(1)).unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+
+        assert_eq!(
+            wallet.get_address(Peek(0)).unwrap().to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+
+        assert_eq!(
+            wallet.get_address(Peek(2)).unwrap().to_string(),
+            "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2"
+        );
+
+        // current new address is not affected
+        assert_eq!(
+            wallet.get_address(New).unwrap().to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+
+        assert_eq!(
+            wallet.get_address(New).unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+    }
+
+    #[test]
+    fn test_peek_address_at_index_not_derivable() {
+        let db = MemoryDatabase::new();
+        let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+                                         None, Network::Testnet, db).unwrap();
+
+        assert_eq!(
+            wallet.get_address(Peek(1)).unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+
+        assert_eq!(
+            wallet.get_address(Peek(0)).unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+
+        assert_eq!(
+            wallet.get_address(Peek(2)).unwrap().to_string(),
             "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
         );
     }
index 05188911b638758c49274cd1d875f33db3675860..44c74e12c0da2de3ac4cea7e9b21a4bdb7100484 100644 (file)
@@ -71,6 +71,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
                 use #root_ident::database::MemoryDatabase;
                 use #root_ident::types::KeychainKind;
                 use #root_ident::{Wallet, TxBuilder, FeeRate};
+                use #root_ident::wallet::AddressIndex::New;
 
                 use super::*;
 
@@ -532,7 +533,7 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
                 #[serial]
                 fn test_sync_receive_coinbase() {
                     let (wallet, descriptors, mut test_client) = init_single_sig();
-                    let wallet_addr = wallet.get_new_address().unwrap();
+                    let wallet_addr = wallet.get_address(New).unwrap();
 
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 0);