]> Untitled Git - bdk/commitdiff
[wallet] Add get_unused_address() function, update CHANGELOG
authorSteve Myers <steve@notmandatory.org>
Mon, 8 Mar 2021 05:57:19 +0000 (21:57 -0800)
committerSteve Myers <steve@notmandatory.org>
Mon, 15 Mar 2021 15:58:09 +0000 (08:58 -0700)
CHANGELOG.md
src/wallet/mod.rs

index 96d32ff98c34a31ff856ad529032ffae71080e15..99a9ad220f520c39e54a6a28c25c9ec0bc5d6f6f 100644 (file)
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 #### Changed
 - `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
 
+#### 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
+
 ## [v0.4.0] - [v0.3.0]
 
 ### Keys
index 29070bd3e7b9951e16df7f99243a21517d337595..2b17871a219dbcbeb372417a3ff544e33e35f112 100644 (file)
@@ -177,6 +177,39 @@ where
             .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)?;
+
+        let script = self
+            .descriptor
+            .as_derived(index, &self.secp)
+            .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);
+
+        if found_used {
+            self.get_new_address()
+        } else {
+            self.descriptor
+                .as_derived(index, &self.secp)
+                .address(self.network)
+                .map_err(|_| Error::ScriptDoesntHaveAddressForm)
+        }
+    }
+
     /// 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)
@@ -3471,4 +3504,50 @@ mod test {
             "should finalized input it signed"
         )
     }
+
+    #[test]
+    fn test_unused_address() {
+        let db = MemoryDatabase::new();
+        let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", 
+                                         None, Network::Testnet, db).unwrap();
+
+        assert_eq!(
+            wallet.get_unused_address().unwrap().to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+        assert_eq!(
+            wallet.get_unused_address().unwrap().to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+    }
+
+    #[test]
+    fn test_next_unused_address() {
+        let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
+        let descriptors = testutils!(@descriptors (descriptor));
+        let wallet = Wallet::new_offline(
+            &descriptors.0,
+            None,
+            Network::Testnet,
+            MemoryDatabase::new(),
+        )
+        .unwrap();
+
+        assert_eq!(
+            wallet.get_unused_address().unwrap().to_string(),
+            "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a"
+        );
+
+        // use the above address
+        crate::populate_test_db!(
+            wallet.database.borrow_mut(),
+            testutils! (@tx ( (@external descriptors, 0) => 25_000 ) (@confirmations 1)),
+            Some(100),
+        );
+
+        assert_eq!(
+            wallet.get_unused_address().unwrap().to_string(),
+            "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7"
+        );
+    }
 }