Skip to content
Open
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
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,15 @@ required-features = ["bdk-tx"]
name = "replace_by_fee"
required-features = ["bdk-tx"]

[[example]]
name = "multi_keychain_custom_keychain"
path = "examples/multi_keychain/custom_keychain.rs"

[[example]]
name = "multi_keychain_address_generation"
path = "examples/multi_keychain/address_generation.rs"

[[example]]
name = "multi_keychain_persistence"
path = "examples/multi_keychain/persistence.rs"
required-features = ["rusqlite"]
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ To persist `Wallet` state use a data storage crate that reads and writes [`Chang

```rust,no_run
use bdk_wallet::rusqlite;
use bdk_wallet::{KeychainKind, Wallet};
use bdk_wallet::{KeyRing, KeychainKind, Wallet};

// Open or create a new SQLite database for wallet data.
let db_path = "my_wallet.sqlite";
Expand All @@ -96,9 +96,11 @@ let mut wallet = match Wallet::load()
.load_wallet(&mut conn)?
{
Some(wallet) => wallet,
None => Wallet::create(descriptor, change_descriptor)
.network(network)
.create_wallet(&mut conn)?,
None => {
let mut keyring = KeyRing::new(network, KeychainKind::External, descriptor)?;
keyring.add_descriptor(KeychainKind::Internal, change_descriptor)?;
Wallet::create(keyring).create_wallet(&mut conn)?
}
};

// Get a new address to receive bitcoin!
Expand Down
23 changes: 15 additions & 8 deletions examples/bitcoind_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bdk_bitcoind_rpc::{
Emitter, MempoolEvent,
bitcoincore_rpc::{Auth, Client, RpcApi},
};
use bdk_wallet::KeyRing;
use bdk_wallet::rusqlite::Connection;
use bdk_wallet::{
KeychainKind, Wallet,
Expand Down Expand Up @@ -101,14 +102,20 @@ fn main() -> anyhow::Result<()> {
.load_wallet(&mut db)?;
let mut wallet = match wallet_opt {
Some(wallet) => wallet,
None => match &args.change_descriptor {
Some(change_desc) => Wallet::create(args.descriptor.clone(), change_desc.clone())
.network(args.network)
.create_wallet(&mut db)?,
None => Wallet::create_single(args.descriptor.clone())
.network(args.network)
.create_wallet(&mut db)?,
},
None => {
let mut keyring = KeyRing::new(
args.network,
KeychainKind::External,
args.descriptor.clone(),
)
.expect("valid descriptor");
if let Some(change_desc) = &args.change_descriptor {
keyring
.add_descriptor(KeychainKind::Internal, change_desc.clone())
.expect("valid change descriptor");
}
Wallet::create(keyring).create_wallet(&mut db)?
}
};
println!(
"Loaded wallet in {}s",
Expand Down
10 changes: 6 additions & 4 deletions examples/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use miniscript::policy::Concrete;
use bdk_wallet::descriptor::ExtractPolicy;
use bdk_wallet::descriptor::policy::BuildSatisfaction;
use bdk_wallet::signer::SignersContainer;
use bdk_wallet::{KeychainKind, Wallet};
use bdk_wallet::{KeyRing, KeychainKind, Wallet};

/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
/// rust-miniscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
Expand Down Expand Up @@ -60,9 +60,11 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Compiled into Descriptor: \n{descriptor}");

// Create a new wallet from descriptors
let mut wallet = Wallet::create_single(descriptor)
.network(Network::Regtest)
.create_wallet_no_persist()?;
let mut wallet = Wallet::create(
KeyRing::new(Network::Regtest, KeychainKind::External, descriptor)
.expect("valid descriptors"),
)
.create_wallet_no_persist();

println!(
"First derived address from the descriptor: \n{}",
Expand Down
13 changes: 9 additions & 4 deletions examples/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use bdk_wallet::descriptor::IntoWalletDescriptor;
use bdk_wallet::miniscript::descriptor::KeyMapWrapper;
use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::rusqlite::Connection;
use bdk_wallet::{KeychainKind, SignOptions};
use bdk_wallet::{KeyRing, KeychainKind, SignOptions};
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;
Expand Down Expand Up @@ -43,9 +43,14 @@ fn main() -> Result<(), anyhow::Error> {
.load_wallet(&mut db)?;
let mut wallet = match wallet_opt {
Some(wallet) => wallet,
None => Wallet::create(external_descriptor, internal_descriptor)
.network(NETWORK)
.create_wallet(&mut db)?,
None => {
let mut keyring = KeyRing::new(NETWORK, KeychainKind::External, external_descriptor)
.expect("valid descriptor");
keyring
.add_descriptor(KeychainKind::Internal, internal_descriptor)
.expect("valid change descriptor");
Wallet::create(keyring).create_wallet(&mut db)?
}
};

let address = wallet.next_unused_address(KeychainKind::External);
Expand Down
12 changes: 9 additions & 3 deletions examples/esplora_async.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bdk_esplora::{EsploraAsyncExt, esplora_client};
use bdk_wallet::KeyRing;
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
use bdk_wallet::descriptor::IntoWalletDescriptor;
use bdk_wallet::miniscript::descriptor::KeyMapWrapper;
Expand Down Expand Up @@ -40,9 +41,14 @@ async fn main() -> Result<(), anyhow::Error> {
.load_wallet(&mut db)?;
let mut wallet = match wallet_opt {
Some(wallet) => wallet,
None => Wallet::create(external_descriptor, internal_descriptor)
.network(NETWORK)
.create_wallet(&mut db)?,
None => {
let mut keyring = KeyRing::new(NETWORK, KeychainKind::External, external_descriptor)
.expect("valid descriptor");
keyring
.add_descriptor(KeychainKind::Internal, internal_descriptor)
.expect("valid change descriptor");
Wallet::create(keyring).create_wallet(&mut db)?
}
};

let address = wallet.next_unused_address(KeychainKind::External);
Expand Down
12 changes: 9 additions & 3 deletions examples/esplora_blocking.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bdk_esplora::{EsploraExt, esplora_client};
use bdk_wallet::KeyRing;
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
use bdk_wallet::descriptor::IntoWalletDescriptor;
use bdk_wallet::miniscript::descriptor::KeyMapWrapper;
Expand Down Expand Up @@ -40,9 +41,14 @@ fn main() -> Result<(), anyhow::Error> {
.load_wallet(&mut db)?;
let mut wallet = match wallet_opt {
Some(wallet) => wallet,
None => Wallet::create(external_descriptor, internal_descriptor)
.network(NETWORK)
.create_wallet(&mut db)?,
None => {
let mut keyring = KeyRing::new(NETWORK, KeychainKind::External, external_descriptor)
.expect("valid descriptor");
keyring
.add_descriptor(KeychainKind::Internal, internal_descriptor)
.expect("valid change descriptor");
Wallet::create(keyring).create_wallet(&mut db)?
}
};

let address = wallet.next_unused_address(KeychainKind::External);
Expand Down
76 changes: 76 additions & 0 deletions examples/multi_keychain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Multi-keychain examples

A `Wallet<K>` tracks any number of keychains, each identified by a value of type `K`. The default
is `KeychainKind`, which gives the familiar two-keychain wallet; supplying your own `K` gives you
as many keychains as you need.

The wallet requires `K: Ord + Clone + Debug` and nothing more, so `K` can carry whatever metadata
your application wants to attach to a keychain. `Ord` is required because keychains are held in a
`BTreeMap`, which also means it decides the order they iterate in.

| Example | Shows |
| --- | --- |
| [`custom_keychain.rs`](./custom_keychain.rs) | A keychain identifier that carries application metadata, and using it to choose which keychain to reveal from |
| [`address_generation.rs`](./address_generation.rs) | Per-keychain derivation indices, `reveal_next_address` vs `next_unused_address`, reading back the last revealed index |
| [`persistence.rs`](./persistence.rs) | Persisting a multi-keychain wallet to sqlite and loading it back, including load-time descriptor checks |

Run them with:

```shell
cargo run --example multi_keychain_custom_keychain
cargo run --example multi_keychain_address_generation
cargo run --example multi_keychain_persistence --features rusqlite
```

## Building a wallet

Descriptors go into a `KeyRing`, which validates each one against the network and rejects duplicate
keychains and duplicate descriptors as they are added:

```rust,ignore
let mut keyring = KeyRing::new(Network::Signet, Keychain::A, DESC_A)?;
keyring.add_descriptor(Keychain::B, DESC_B)?;

let mut wallet = Wallet::create(keyring).create_wallet_no_persist();
```

Because the `KeyRing` has already done that checking, building the wallet from it cannot fail —
`create_wallet_no_persist` returns a `Wallet<K>` rather than a `Result`. Descriptor errors surface
at `KeyRing::new` and `KeyRing::add_descriptor`, where the descriptor was actually supplied.

The conventional two-keychain wallet is the same two calls with `KeychainKind`:

```rust,ignore
let mut keyring = KeyRing::new(Network::Signet, KeychainKind::External, external_desc)?;
keyring.add_descriptor(KeychainKind::Internal, internal_desc)?;
```

## Building transactions

`Wallet::create_psbt` and `Wallet::replace_by_fee` work on any `Wallet<K>`. Because a wallet
generic over `K` has no canonical change keychain, you name one explicitly:

```rust,ignore
let mut params = PsbtParams::default();
params
.add_recipients([(recipient_spk, Amount::from_sat(10_000))])
.change_keychain(Keychain::Change);

let (psbt, finalizer) = wallet.create_psbt(params)?;
```

Set `change_keychain` or `change_script` — without one, PSBT creation fails with
`CreatePsbtError::NoChangeSource`, and naming a keychain the wallet does not hold fails with
`CreatePsbtError::UnknownChangeKeychain`. Change derived from `change_keychain` is revealed and
staged, so it stays tracked; you must persist the resulting changeset.

## Current limitations

- **No `TxBuilder`.** The older `TxBuilder` API still lives on `impl Wallet<KeychainKind>`, since it
has no way to be told which keychain change belongs to. Use `create_psbt` instead.
- **Nothing is trusted before it is mined.** `balance()` cannot know which of your keychains hold
self-owned change, so all unconfirmed output counts as untrusted-pending.

To persist a custom keychain type, implement `rusqlite`'s `ToSql` and `FromSql` for it (see
[`persistence.rs`](./persistence.rs)); for the file store, implement `serde::Serialize` and
`serde::Deserialize`.
62 changes: 62 additions & 0 deletions examples/multi_keychain/address_generation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Adapted from bitcoindevkit/bdk_wallet#318 (examples/multi_keychain/address_generation.rs).
//
// Address generation across several keychains:
// - revealing addresses from any keychain
// - each keychain keeping its own derivation index
// - `next_unused_address` vs `reveal_next_address`
// - reading back the last revealed index per keychain

use bdk_wallet::bitcoin::Network;
use bdk_wallet::{KeyRing, Wallet};

const DESC_A: &str = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/0/*)";
const DESC_B: &str = "wpkh([5bc5d243/84'/1'/0']tpubDCA4DcMLVSDifbfUxyJaVVAx57ztsVjke6DRYF95jFFgJqvzA9oENovVd7n34NNURmZxFNRB1VLGyDEqxvaZNXie3ZroEGFbeTS2xLYuaN1/0/*)";
const DESC_C: &str = "pkh([5bc5d243/44'/1'/0']tpubDDNQtvd8Sg1mXtSGtxRWEcgg7PbPwUSAyAmBonDSL3HLuutthe54Yih4XDYcywVdcduwqaQonpbTAGjjSh5kcLeCj5MTjYooa9ve2Npx6ho/0/*)";

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Keychain {
A,
B,
C,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut keyring = KeyRing::new(Network::Signet, Keychain::A, DESC_A)?;
keyring.add_descriptor(Keychain::B, DESC_B)?;
keyring.add_descriptor(Keychain::C, DESC_C)?;

let mut wallet = Wallet::create(keyring).create_wallet_no_persist();

println!("Created a wallet with 3 keychains (A, B, C)\n");

// Each keychain keeps its own derivation index, exactly as the external and internal
// keychains do on a standard two-keychain wallet.
println!("1. Revealing addresses from each keychain");
for (keychain, count) in [(Keychain::A, 5), (Keychain::B, 3), (Keychain::C, 2)] {
println!(" Keychain {keychain:?}:");
for _ in 0..count {
let addr = wallet.reveal_next_address(keychain);
println!(" - index {}: {}", addr.index, addr.address);
}
}

// `next_unused_address` hands back the lowest revealed address that has not been used yet,
// rather than revealing a new one. With no transactions in this wallet, nothing has been used,
// so it returns index 0 instead of advancing past what we revealed above.
println!("\n2. next_unused_address does not advance the index");
for keychain in [Keychain::A, Keychain::B, Keychain::C] {
let addr = wallet.next_unused_address(keychain);
println!(" Keychain {keychain:?}: index {}", addr.index);
}

println!("\n3. Last revealed index per keychain");
for (keychain, _descriptor) in wallet.keychains() {
// `derivation_index` is `None` until something has actually been revealed.
println!(
" Keychain {keychain:?}: {:?}",
wallet.derivation_index(keychain)
);
}

Ok(())
}
Loading
Loading