From 1390843c6caba240a00d471a915b16560763a880 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 16 Jun 2026 11:01:42 -0500 Subject: [PATCH 1/5] feat: expose `Wallet::load_from_two_path_descriptor` --- bdk-ffi/src/tests/wallet.rs | 38 +++++++++++++++++++++++++++++++++++++ bdk-ffi/src/wallet.rs | 30 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/bdk-ffi/src/tests/wallet.rs b/bdk-ffi/src/tests/wallet.rs index 6219fa7b..c8fcd86f 100644 --- a/bdk-ffi/src/tests/wallet.rs +++ b/bdk-ffi/src/tests/wallet.rs @@ -150,3 +150,41 @@ fn test_create_two_path_wallet() { assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0)); assert_eq!(wallet.derivation_index(KeychainKind::Internal), Some(0)); } + +#[test] +fn test_load_from_two_path_descriptor() { + let persister = Arc::new(Persister::new_in_memory().unwrap()); + let wallet = Wallet::create_from_two_path_descriptor( + two_path_descriptor(), + Network::Signet, + Arc::clone(&persister), + 25, + ) + .unwrap(); + + wallet.reveal_next_address(KeychainKind::External); + wallet.reveal_next_address(KeychainKind::Internal); + assert!(wallet.persist(Arc::clone(&persister)).unwrap()); + + let loaded_wallet = + Wallet::load_from_two_path_descriptor(two_path_descriptor(), Arc::clone(&persister), 25) + .unwrap(); + + assert_eq!(loaded_wallet.network(), Network::Signet); + assert_eq!( + loaded_wallet.derivation_index(KeychainKind::External), + Some(0) + ); + assert_eq!( + loaded_wallet.derivation_index(KeychainKind::Internal), + Some(0) + ); + assert_eq!( + loaded_wallet.next_derivation_index(KeychainKind::External), + 1 + ); + assert_eq!( + loaded_wallet.next_derivation_index(KeychainKind::Internal), + 1 + ); +} diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index acd73c6a..9958ef24 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -168,6 +168,36 @@ impl Wallet { }) } + /// Build a two-path descriptor `Wallet` by loading from persistence. + /// + /// Checks that the provided two-path descriptor matches exactly what is loaded + /// for both the external and internal keychains. + /// + /// Note that descriptor secret keys are not persisted to the db. This method + /// extracts keys from the provided descriptor while loading. + #[uniffi::constructor(default(lookahead = 25))] + pub fn load_from_two_path_descriptor( + two_path_descriptor: Arc, + persister: Arc, + lookahead: u32, + ) -> Result { + let descriptor = two_path_descriptor.to_string_with_secret(); + let mut persist_lock = persister.inner.lock().unwrap(); + let deref = persist_lock.deref_mut(); + + let wallet: PersistedWallet = BdkWallet::load() + .two_path_descriptor(descriptor) + .lookahead(lookahead) + .extract_keys() + .load_wallet(deref) + .map_err(LoadWithPersistError::from)? + .ok_or(LoadWithPersistError::CouldNotLoad)?; + + Ok(Wallet { + inner_mutex: Mutex::new(wallet), + }) + } + /// Build a single-descriptor Wallet by loading from persistence. /// /// Note that the descriptor secret keys are not persisted to the db. From a25919cc1944f25843f9991c2eb7d087dc58d0ba Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 1 Jul 2026 12:20:36 -0500 Subject: [PATCH 2/5] refactor: avoid secret rendering for two-path load --- bdk-ffi/src/wallet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 9958ef24..0098fe15 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -181,7 +181,7 @@ impl Wallet { persister: Arc, lookahead: u32, ) -> Result { - let descriptor = two_path_descriptor.to_string_with_secret(); + let descriptor = two_path_descriptor.to_string(); let mut persist_lock = persister.inner.lock().unwrap(); let deref = persist_lock.deref_mut(); From f201fbc9bd32df031c03e8eff207b457cae3d86c Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 1 Jul 2026 12:23:39 -0500 Subject: [PATCH 3/5] docs: clarify public two-path descriptor requirement --- bdk-ffi/src/wallet.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 0098fe15..0fa524fa 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -109,13 +109,18 @@ impl Wallet { /// Build a new `Wallet` from a two-path descriptor. /// - /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet using the existing receive and change wallet creation logic. + /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet + /// using the existing receive and change wallet creation logic. Use this method with public + /// extended keys (`xpub` prefix) to create watch-only wallets. /// - /// Multipath descriptors follow [BIP-389](https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki) and allow defining both receive and change derivation paths in a single descriptor using the <0;1> syntax. + /// Multipath descriptors follow [BIP-389](https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki) + /// and allow defining both receive and change derivation paths in a single descriptor using + /// the `<0;1>` syntax. /// /// If you have previously created a wallet, use load instead. /// - /// Returns an error if the descriptor is invalid or not a 2-path multipath descriptor. + /// Returns an error if the descriptor is not a 2-path multipath descriptor. Private multipath + /// descriptors cannot be constructed as `Descriptor` values for this API. #[uniffi::constructor(default(lookahead = 25))] pub fn create_from_two_path_descriptor( two_path_descriptor: Arc, @@ -173,6 +178,9 @@ impl Wallet { /// Checks that the provided two-path descriptor matches exactly what is loaded /// for both the external and internal keychains. /// + /// Use this method with public extended keys (`xpub` prefix) to load watch-only wallets. + /// Private multipath descriptors cannot be constructed as `Descriptor` values for this API. + /// /// Note that descriptor secret keys are not persisted to the db. This method /// extracts keys from the provided descriptor while loading. #[uniffi::constructor(default(lookahead = 25))] From 90b12f86917e7102adfe423ff118b625173de755 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 9 Jul 2026 11:37:58 -0500 Subject: [PATCH 4/5] fix: avoid extracting keys for two path descriptor load --- bdk-ffi/src/wallet.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 0fa524fa..cba36b02 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -196,7 +196,6 @@ impl Wallet { let wallet: PersistedWallet = BdkWallet::load() .two_path_descriptor(descriptor) .lookahead(lookahead) - .extract_keys() .load_wallet(deref) .map_err(LoadWithPersistError::from)? .ok_or(LoadWithPersistError::CouldNotLoad)?; From 3141d04279fad07f1359a8cbf8a694dbb43a07ea Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 9 Jul 2026 11:41:14 -0500 Subject: [PATCH 5/5] docs: align two path descriptor requirements --- bdk-ffi/src/wallet.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index cba36b02..0a06ccd0 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -110,8 +110,9 @@ impl Wallet { /// Build a new `Wallet` from a two-path descriptor. /// /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet - /// using the existing receive and change wallet creation logic. Use this method with public - /// extended keys (`xpub` prefix) to create watch-only wallets. + /// using the existing receive and change wallet creation logic. + /// + /// The provided descriptor may only contain extended public keys (`xpub`) with exactly 2 paths. /// /// Multipath descriptors follow [BIP-389](https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki) /// and allow defining both receive and change derivation paths in a single descriptor using @@ -119,8 +120,7 @@ impl Wallet { /// /// If you have previously created a wallet, use load instead. /// - /// Returns an error if the descriptor is not a 2-path multipath descriptor. Private multipath - /// descriptors cannot be constructed as `Descriptor` values for this API. + /// Returns an error if the descriptor is not a 2-path multipath descriptor. #[uniffi::constructor(default(lookahead = 25))] pub fn create_from_two_path_descriptor( two_path_descriptor: Arc, @@ -178,11 +178,7 @@ impl Wallet { /// Checks that the provided two-path descriptor matches exactly what is loaded /// for both the external and internal keychains. /// - /// Use this method with public extended keys (`xpub` prefix) to load watch-only wallets. - /// Private multipath descriptors cannot be constructed as `Descriptor` values for this API. - /// - /// Note that descriptor secret keys are not persisted to the db. This method - /// extracts keys from the provided descriptor while loading. + /// The provided descriptor may only contain extended public keys (`xpub`) with exactly 2 paths. #[uniffi::constructor(default(lookahead = 25))] pub fn load_from_two_path_descriptor( two_path_descriptor: Arc,