Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6caf61f
feat: add KeyRing type
thunderbiscuit Sep 25, 2025
9e2e4a0
test: add simple KeyRing tests
thunderbiscuit Sep 25, 2025
9c7eb2e
feat: add KeyRing changeset
thunderbiscuit Sep 25, 2025
8865a9e
refactor!: comment out `Wallet` dependant code.
110CodingP Sep 28, 2025
bfcf9b3
feat!: modify Wallet struct to hold a `KeyRing`
110CodingP Sep 29, 2025
896cbb8
feat: Add a basic constructor for `Wallet`
110CodingP Sep 29, 2025
295d0d2
feat!: modify AddressInfo, add reveal_next_address
110CodingP Sep 29, 2025
229040f
feat: add `Wallet::with_custom_params` API
110CodingP Sep 29, 2025
8d17644
docs: add `KeychainKind` based example
110CodingP Oct 4, 2025
79676b1
docs: add complex multi-keychain example
thunderbiscuit Oct 7, 2025
b55c648
refactor!: remove policy module and related code
110CodingP Oct 10, 2025
0e8d677
feat!: check desc, return changeset in add_desc fn
110CodingP Oct 10, 2025
9be5c5b
feat!: introduce `from_changeset` for `Wallet`
110CodingP Oct 15, 2025
88ced7a
feat: add getter for network of keyring
110CodingP Oct 18, 2025
864872a
feat: add functions to persist the wallet
110CodingP Oct 18, 2025
f35e716
refactor: use `with_custom_params` in `new`
110CodingP Oct 18, 2025
f0753cd
feat!: make Update take in a generic
110CodingP Oct 27, 2025
8b3062f
feat: add method to get latest checkpoint.
110CodingP Oct 28, 2025
366dbbe
feat: add getters for `Wallet` fields
110CodingP Oct 28, 2025
07a1902
feat: add balance method to `Wallet`
110CodingP Oct 28, 2025
ecb3662
wallet: `balance_with_params_conf_threshold`
ValuedMammal Nov 4, 2025
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ name = "esplora_blocking"

[[example]]
name = "bitcoind_rpc"

[[example]]
name = "simple_keyring"
required-features = ["rusqlite"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ To persist `Wallet` state use a data storage crate that reads and writes [`Chang
* [`bdk_file_store`]: Stores wallet changes in a simple flat file.
* `rusqlite`: Stores wallet changes in a SQLite database.

**Example**
<!-- **Example**
```rust,no_run
use bdk_wallet::rusqlite;
Expand Down Expand Up @@ -110,7 +110,7 @@ wallet.persist(&mut conn)?;
println!("Next receive address: {}", address_info.address);
Ok::<_, anyhow::Error>(())
```
``` -->

## Minimum Supported Rust Version (MSRV)

Expand Down
221 changes: 111 additions & 110 deletions examples/bitcoind_rpc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(unused)]
use bdk_bitcoind_rpc::{
bitcoincore_rpc::{Auth, Client, RpcApi},
Emitter, MempoolEvent,
Expand Down Expand Up @@ -84,125 +85,125 @@ enum Emission {
}

fn main() -> anyhow::Result<()> {
let args = Args::parse();
// let args = Args::parse();

let rpc_client = Arc::new(args.client()?);
println!(
"Connected to Bitcoin Core RPC at {:?}",
rpc_client.get_blockchain_info().unwrap()
);
// let rpc_client = Arc::new(args.client()?);
// println!(
// "Connected to Bitcoin Core RPC at {:?}",
// rpc_client.get_blockchain_info().unwrap()
// );

let start_load_wallet = Instant::now();
let mut db = Connection::open(args.db_path)?;
let wallet_opt = Wallet::load()
.descriptor(KeychainKind::External, Some(args.descriptor.clone()))
.descriptor(KeychainKind::Internal, args.change_descriptor.clone())
.extract_keys()
.check_network(args.network)
.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)?,
},
};
println!(
"Loaded wallet in {}s",
start_load_wallet.elapsed().as_secs_f32()
);
// let start_load_wallet = Instant::now();
// let mut db = Connection::open(args.db_path)?;
// let wallet_opt = Wallet::load()
// .descriptor(KeychainKind::External, Some(args.descriptor.clone()))
// .descriptor(KeychainKind::Internal, args.change_descriptor.clone())
// .extract_keys()
// .check_network(args.network)
// .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)?,
// },
// };
// println!(
// "Loaded wallet in {}s",
// start_load_wallet.elapsed().as_secs_f32()
// );

let address = wallet.reveal_next_address(KeychainKind::External).address;
println!("Wallet address: {address}");
// let address = wallet.reveal_next_address(KeychainKind::External).address;
// println!("Wallet address: {address}");

let balance = wallet.balance();
println!("Wallet balance before syncing: {}", balance.total());
// let balance = wallet.balance();
// println!("Wallet balance before syncing: {}", balance.total());

let wallet_tip = wallet.latest_checkpoint();
println!(
"Wallet tip: {} at height {}",
wallet_tip.hash(),
wallet_tip.height()
);
// let wallet_tip = wallet.latest_checkpoint();
// println!(
// "Wallet tip: {} at height {}",
// wallet_tip.hash(),
// wallet_tip.height()
// );

let (sender, receiver) = sync_channel::<Emission>(21);
// let (sender, receiver) = sync_channel::<Emission>(21);

let signal_sender = sender.clone();
let _ = ctrlc::set_handler(move || {
signal_sender
.send(Emission::SigTerm)
.expect("failed to send sigterm")
});
// let signal_sender = sender.clone();
// let _ = ctrlc::set_handler(move || {
// signal_sender
// .send(Emission::SigTerm)
// .expect("failed to send sigterm")
// });

let mut emitter = Emitter::new(
rpc_client,
wallet_tip,
args.start_height,
wallet
.transactions()
.filter(|tx| tx.chain_position.is_unconfirmed()),
);
spawn(move || -> Result<(), anyhow::Error> {
while let Some(emission) = emitter.next_block()? {
sender.send(Emission::Block(emission))?;
}
sender.send(Emission::Mempool(emitter.mempool()?))?;
Ok(())
});
// let mut emitter = Emitter::new(
// rpc_client,
// wallet_tip,
// args.start_height,
// wallet
// .transactions()
// .filter(|tx| tx.chain_position.is_unconfirmed()),
// );
// spawn(move || -> Result<(), anyhow::Error> {
// while let Some(emission) = emitter.next_block()? {
// sender.send(Emission::Block(emission))?;
// }
// sender.send(Emission::Mempool(emitter.mempool()?))?;
// Ok(())
// });

let mut blocks_received = 0_usize;
for emission in receiver {
match emission {
Emission::SigTerm => {
println!("Sigterm received, exiting...");
break;
}
Emission::Block(block_emission) => {
blocks_received += 1;
let height = block_emission.block_height();
let hash = block_emission.block_hash();
let connected_to = block_emission.connected_to();
let start_apply_block = Instant::now();
wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?;
wallet.persist(&mut db)?;
let elapsed = start_apply_block.elapsed().as_secs_f32();
println!("Applied block {hash} at height {height} in {elapsed}s");
}
Emission::Mempool(event) => {
let start_apply_mempool = Instant::now();
wallet.apply_evicted_txs(event.evicted);
wallet.apply_unconfirmed_txs(event.update);
wallet.persist(&mut db)?;
println!(
"Applied unconfirmed transactions in {}s",
start_apply_mempool.elapsed().as_secs_f32()
);
break;
}
}
}
let wallet_tip_end = wallet.latest_checkpoint();
let balance = wallet.balance();
println!(
"Synced {} blocks in {}s",
blocks_received,
start_load_wallet.elapsed().as_secs_f32(),
);
println!(
"Wallet tip is '{}:{}'",
wallet_tip_end.height(),
wallet_tip_end.hash()
);
println!("Wallet balance is {}", balance.total());
println!(
"Wallet has {} transactions and {} utxos",
wallet.transactions().count(),
wallet.list_unspent().count()
);
// let mut blocks_received = 0_usize;
// for emission in receiver {
// match emission {
// Emission::SigTerm => {
// println!("Sigterm received, exiting...");
// break;
// }
// Emission::Block(block_emission) => {
// blocks_received += 1;
// let height = block_emission.block_height();
// let hash = block_emission.block_hash();
// let connected_to = block_emission.connected_to();
// let start_apply_block = Instant::now();
// wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?;
// wallet.persist(&mut db)?;
// let elapsed = start_apply_block.elapsed().as_secs_f32();
// println!("Applied block {hash} at height {height} in {elapsed}s");
// }
// Emission::Mempool(event) => {
// let start_apply_mempool = Instant::now();
// wallet.apply_evicted_txs(event.evicted);
// wallet.apply_unconfirmed_txs(event.update);
// wallet.persist(&mut db)?;
// println!(
// "Applied unconfirmed transactions in {}s",
// start_apply_mempool.elapsed().as_secs_f32()
// );
// break;
// }
// }
// }
// let wallet_tip_end = wallet.latest_checkpoint();
// let balance = wallet.balance();
// println!(
// "Synced {} blocks in {}s",
// blocks_received,
// start_load_wallet.elapsed().as_secs_f32(),
// );
// println!(
// "Wallet tip is '{}:{}'",
// wallet_tip_end.height(),
// wallet_tip_end.hash()
// );
// println!("Wallet balance is {}", balance.total());
// println!(
// "Wallet has {} transactions and {} utxos",
// wallet.transactions().count(),
// wallet.list_unspent().count()
// );

Ok(())
}
81 changes: 43 additions & 38 deletions examples/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.

#![allow(unused)]
extern crate bdk_wallet;
extern crate bitcoin;
extern crate miniscript;
extern crate serde_json;
// extern crate bitcoin;
// extern crate miniscript;
// extern crate serde_json;

use std::error::Error;
use std::str::FromStr;
Expand All @@ -32,47 +32,52 @@ use bdk_wallet::{KeychainKind, Wallet};
/// This example demonstrates the interaction between a bdk wallet and miniscript policy.
#[allow(clippy::print_stdout)]
fn main() -> Result<(), Box<dyn Error>> {
// We start with a miniscript policy string
let policy_str = "or(
10@thresh(4,
pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)
),1@and(
older(4209713),
thresh(2,
pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)
)
)
)"
.replace(&[' ', '\n', '\t'][..], "");
// // We start with a miniscript policy string
// let policy_str = "or(
// 10@thresh(4,
// pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),
// pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),
// pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),
// pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),
// pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2) ),1@and(
// older(4209713),
// thresh(2,
//
// pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),
// pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),
// pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068) )
// )
// )"
// .replace(&[' ', '\n', '\t'][..], "");

println!("Compiling policy: \n{policy_str}");
// println!("Compiling policy: \n{policy_str}");

// Parse the string as a [`Concrete`] type miniscript policy.
let policy = Concrete::<String>::from_str(&policy_str)?;
// // Parse the string as a [`Concrete`] type miniscript policy.
// let policy = Concrete::<String>::from_str(&policy_str)?;

// Create a `wsh` type descriptor from the policy.
// `policy.compile()` returns the resulting miniscript from the policy.
let descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();
// // Create a `wsh` type descriptor from the policy.
// // `policy.compile()` returns the resulting miniscript from the policy.
// let descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();

println!("Compiled into Descriptor: \n{descriptor}");
// 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()?;
// // Create a new wallet from descriptors
// let mut wallet = Wallet::create_single(descriptor)
// .network(Network::Regtest)
// .create_wallet_no_persist()?;

println!(
"First derived address from the descriptor: \n{}",
wallet.next_unused_address(KeychainKind::External),
);
// println!(
// "First derived address from the descriptor: \n{}",
// wallet.next_unused_address(KeychainKind::External),
// );

// BDK also has it's own `Policy` structure to represent the spending condition in a more
// human readable json format.
let spending_policy = wallet.policies(KeychainKind::External)?;
println!(
"The BDK spending policy: \n{}",
serde_json::to_string_pretty(&spending_policy)?
);
// // BDK also has it's own `Policy` structure to represent the spending condition in a more
// // human readable json format.
// let spending_policy = wallet.policies(KeychainKind::External)?;
// println!(
// "The BDK spending policy: \n{}",
// serde_json::to_string_pretty(&spending_policy)?
// );

Ok(())
}
Loading