From 268457befb899a8af673ec58d7b1ac880c3925e2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 16 Jun 2026 19:16:50 -0500 Subject: [PATCH] feat: expose `Wallet::keychains` --- bdk-ffi/src/tests/wallet.rs | 45 +++++++++++++++++++++++++++++++++++++ bdk-ffi/src/types.rs | 16 +++++++++++++ bdk-ffi/src/wallet.rs | 18 ++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/bdk-ffi/src/tests/wallet.rs b/bdk-ffi/src/tests/wallet.rs index 01967986e..6219fa7b9 100644 --- a/bdk-ffi/src/tests/wallet.rs +++ b/bdk-ffi/src/tests/wallet.rs @@ -46,6 +46,39 @@ fn test_create_wallet() { assert_eq!(wallet.next_derivation_index(KeychainKind::External), 0); } +#[test] +fn test_keychains() { + let wallet = build_wallet(); + + let keychains = wallet.keychains(); + + assert_eq!(keychains.len(), 2); + + let external = keychains + .iter() + .find(|keychain| keychain.keychain == KeychainKind::External) + .unwrap(); + let internal = keychains + .iter() + .find(|keychain| keychain.keychain == KeychainKind::Internal) + .unwrap(); + let external_public_descriptor = external.public_descriptor.to_string(); + let internal_public_descriptor = internal.public_descriptor.to_string(); + + assert_eq!( + external_public_descriptor, + wallet.public_descriptor(KeychainKind::External) + ); + assert_eq!( + internal_public_descriptor, + wallet.public_descriptor(KeychainKind::Internal) + ); + assert!(external.public_descriptor.has_wildcard()); + assert!(internal.public_descriptor.has_wildcard()); + assert!(!external_public_descriptor.contains("tprv")); + assert!(!internal_public_descriptor.contains("tprv")); +} + #[test] fn test_reveal_next_address() { let wallet = build_wallet(); @@ -69,6 +102,18 @@ fn test_create_single_wallet() { assert_eq!(wallet.derivation_index(KeychainKind::External), None); + let keychains = wallet.keychains(); + + assert_eq!(keychains.len(), 1); + assert_eq!(keychains[0].keychain, KeychainKind::External); + let public_descriptor = keychains[0].public_descriptor.to_string(); + assert_eq!( + public_descriptor, + wallet.public_descriptor(KeychainKind::External) + ); + assert!(keychains[0].public_descriptor.has_wildcard()); + assert!(!public_descriptor.contains("tprv")); + let address_info = wallet.reveal_next_address(KeychainKind::External); assert_eq!(address_info.index, 0); diff --git a/bdk-ffi/src/types.rs b/bdk-ffi/src/types.rs index 49532e689..0cfe874cf 100644 --- a/bdk-ffi/src/types.rs +++ b/bdk-ffi/src/types.rs @@ -209,6 +209,22 @@ impl From for AddressInfo { } } +/// A wallet keychain and its public descriptor. +#[uniffi::export(Display)] +#[derive(uniffi::Record)] +pub struct WalletKeychain { + /// Type of keychain. + pub keychain: KeychainKind, + /// Public descriptor for the keychain. + pub public_descriptor: Arc, +} + +impl Display for WalletKeychain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}: {}", self.keychain, self.public_descriptor) + } +} + /// Balance, differentiated into various categories. #[derive(uniffi::Record)] pub struct Balance { diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index becde3c1e..acd73c6ae 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -8,10 +8,11 @@ use crate::store::{PersistenceType, Persister}; use crate::types::{ AddressInfo, Balance, BlockId, CanonicalTx, ChangeSet, EvictedTx, FullScanRequestBuilder, KeychainAndIndex, KeychainKind, LocalOutput, Policy, SentAndReceivedValues, SignOptions, - SyncRequestBuilder, UnconfirmedTx, Update, WalletEvent, + SyncRequestBuilder, UnconfirmedTx, Update, WalletEvent, WalletKeychain, }; use bdk_wallet::bitcoin::Network; +use bdk_wallet::keys::KeyMap; #[allow(deprecated)] use bdk_wallet::signer::SignOptions as BdkSignOptions; use bdk_wallet::{PersistedWallet, Wallet as BdkWallet}; @@ -424,6 +425,21 @@ impl Wallet { self.get_wallet().network() } + /// Iterator over all keychains in this wallet + pub fn keychains(&self) -> Vec { + let wallet = self.get_wallet(); + wallet + .keychains() + .map(|(keychain, descriptor)| WalletKeychain { + keychain, + public_descriptor: Arc::new(Descriptor { + extended_descriptor: descriptor.clone(), + key_map: KeyMap::default(), + }), + }) + .collect() + } + /// Return the balance, separated into available, trusted-pending, untrusted-pending and /// immature values. pub fn balance(&self) -> Balance {