Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ class CreatingWalletTest {
)
}

// Create a wallet with a single descriptor.
@Test
fun createWalletWithSingleDescriptor() {
Wallet.createSingle(
descriptor = BIP86_DESCRIPTOR,
network = Network.TESTNET,
persister = conn
)
}

// Create a wallet with a public multipath descriptor.
@Test
fun createWalletWithMultipathDescriptor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,24 @@ class WalletTest {
actual = wallet.balance().total.toSat()
)
}

// Single-descriptor wallets return an address on the external keychain even if a change descriptor is not provided
// and the wallet.revealNextAddress(KeychainKind.INTERNAL) or wallet.peekAddress(KeychainKind.EXTERNAL, 0u) APIs are
// used.
@Test
fun singleDescriptorWalletCanCreateAddresses() {
val wallet: Wallet = Wallet.createSingle(
descriptor = BIP84_DESCRIPTOR,
network = Network.TESTNET,
persister = conn
)
val address1 = wallet.peekAddress(KeychainKind.EXTERNAL, 0u)
val address2 = wallet.peekAddress(KeychainKind.INTERNAL, 0u)

assertEquals(
expected = address1.address,
actual = address2.address,
"Addresses should be the same"
)
}
}
32 changes: 32 additions & 0 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,23 @@ impl From<BdkCreateWithPersistError<chain::rusqlite::Error>> for CreateWithPersi
}
}

impl From<BdkCreateWithPersistError<PersistenceError>> for CreateWithPersistError {
fn from(error: BdkCreateWithPersistError<PersistenceError>) -> Self {
match error {
BdkCreateWithPersistError::Persist(e) => CreateWithPersistError::Persist {
error_message: e.to_string(),
},
BdkCreateWithPersistError::Descriptor(e) => CreateWithPersistError::Descriptor {
error_message: e.to_string(),
},
// Objects cannot currently be used in enumerations
BdkCreateWithPersistError::DataAlreadyExists(_e) => {
CreateWithPersistError::DataAlreadyExists
}
}
}
}

impl From<AddUtxoError> for CreateTxError {
fn from(error: AddUtxoError) -> Self {
match error {
Expand Down Expand Up @@ -1248,6 +1265,21 @@ impl From<BdkLoadWithPersistError<chain::rusqlite::Error>> for LoadWithPersistEr
}
}

impl From<BdkLoadWithPersistError<PersistenceError>> for LoadWithPersistError {
fn from(error: BdkLoadWithPersistError<PersistenceError>) -> Self {
match error {
BdkLoadWithPersistError::Persist(e) => LoadWithPersistError::Persist {
error_message: e.to_string(),
},
BdkLoadWithPersistError::InvalidChangeSet(e) => {
LoadWithPersistError::InvalidChangeSet {
error_message: e.to_string(),
}
}
}
}
}

impl From<BdkSqliteError> for PersistenceError {
fn from(error: BdkSqliteError) -> Self {
PersistenceError::Reason {
Expand Down
52 changes: 43 additions & 9 deletions bdk-ffi/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,47 @@ impl Wallet {
.network(network)
.lookahead(lookahead)
.create_wallet(deref)
.map_err(|e| CreateWithPersistError::Persist {
error_message: e.to_string(),
})?;
.map_err(CreateWithPersistError::from)?;

Ok(Wallet {
inner_mutex: Mutex::new(wallet),
})
}

/// Build a new single descriptor `Wallet`.
///
/// If you have previously created a wallet, use `Wallet::load` instead.
///
/// # Note
///
/// Only use this method when creating a wallet designed to be used with a single
/// descriptor and keychain. Otherwise the recommended way to construct a new wallet is
/// by using `Wallet::new`. It's worth noting that not all features are available
/// with single descriptor wallets, for example setting a `change_policy` on `TxBuilder`
/// and related methods such as `do_not_spend_change`. This is because all payments are
/// received on the external keychain (including change), and without a change keychain
/// BDK lacks enough information to distinguish between change and outside payments.
///
/// Additionally because this wallet has no internal (change) keychain, all methods that
/// require a `KeychainKind` as input, e.g. `reveal_next_address` should only be called
/// using the `External` variant. In most cases passing `Internal` is treated as the
/// equivalent of `External` but this behavior must not be relied on.
#[uniffi::constructor(default(lookahead = 25))]
pub fn create_single(
descriptor: Arc<Descriptor>,
network: Network,
persister: Arc<Persister>,
lookahead: u32,
) -> Result<Self, CreateWithPersistError> {
let descriptor = descriptor.to_string_with_secret();
let mut persist_lock = persister.inner.lock().unwrap();
let deref = persist_lock.deref_mut();

let wallet: PersistedWallet<PersistenceType> = BdkWallet::create_single(descriptor)
.network(network)
.lookahead(lookahead)
.create_wallet(deref)
.map_err(CreateWithPersistError::from)?;

Ok(Wallet {
inner_mutex: Mutex::new(wallet),
Expand Down Expand Up @@ -92,9 +130,7 @@ impl Wallet {
.network(network)
.lookahead(lookahead)
.create_wallet(deref)
.map_err(|e| CreateWithPersistError::Persist {
error_message: e.to_string(),
})?;
.map_err(CreateWithPersistError::from)?;

Ok(Wallet {
inner_mutex: Mutex::new(wallet),
Expand Down Expand Up @@ -122,9 +158,7 @@ impl Wallet {
.lookahead(lookahead)
.extract_keys()
.load_wallet(deref)
.map_err(|e| LoadWithPersistError::Persist {
error_message: e.to_string(),
})?
.map_err(LoadWithPersistError::from)?
.ok_or(LoadWithPersistError::CouldNotLoad)?;

Ok(Wallet {
Expand Down