diff --git a/README.md b/README.md index 812c0a5c4..2237ed71f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Fully working examples of how to use these components are in `/example-crates`: - [`wallet_esplora_blocking`](./example-crates/wallet_esplora_blocking): Uses the `Wallet` to sync and spend using the Esplora blocking interface. - [`wallet_esplora_async`](./example-crates/wallet_esplora_async): Uses the `Wallet` to sync and spend using the Esplora asynchronous interface. - [`wallet_electrum`](./example-crates/wallet_electrum): Uses the `Wallet` to sync and spend using Electrum. +- [`wallet_rpc`](./example-crates/wallet_rpc): Uses the `Wallet` to sync and spend using Bitcoin RPC. [`BDK 1.0 project page`]: https://github.com/orgs/bitcoindevkit/projects/14 [`rust-miniscript`]: https://github.com/rust-bitcoin/rust-miniscript diff --git a/example-crates/wallet_electrum/Cargo.toml b/example-crates/wallet_electrum/Cargo.toml index 10b662e8f..426e18e51 100644 --- a/example-crates/wallet_electrum/Cargo.toml +++ b/example-crates/wallet_electrum/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "wallet_electrum_example" +name = "wallet_electrum" version = "0.2.0" edition = "2021" [dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } +bdk_wallet = { path = "../../crates/wallet", features = ["rusqlite"] } bdk_electrum = { path = "../../crates/electrum" } anyhow = "1" diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 35413a962..e67f4658c 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -1,47 +1,42 @@ -use bdk_wallet::file_store::Store; -use bdk_wallet::Wallet; use std::io::Write; -use std::str::FromStr; -use bdk_electrum::electrum_client; -use bdk_electrum::BdkElectrumClient; -use bdk_wallet::bitcoin::Network; -use bdk_wallet::bitcoin::{Address, Amount}; -use bdk_wallet::chain::collections::HashSet; -use bdk_wallet::{KeychainKind, SignOptions}; +use bdk_electrum::{electrum_client, BdkElectrumClient}; +use bdk_wallet::{ + bitcoin::{Amount, Network}, + chain::collections::HashSet, + rusqlite::Connection, + KeychainKind, SignOptions, Wallet, +}; -const DB_MAGIC: &str = "bdk_wallet_electrum_example"; const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 50; +const STOP_GAP: usize = 20; const BATCH_SIZE: usize = 5; -const NETWORK: Network = Network::Testnet; +const NETWORK: Network = Network::Signet; const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; -const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002"; +const ELECTRUM_URL: &str = "ssl://mempool.space:60602"; fn main() -> Result<(), anyhow::Error> { - let db_path = "bdk-electrum-example.db"; - - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?; + let mut conn = Connection::open_in_memory().expect("must open connection"); let wallet_opt = Wallet::load() .descriptors(EXTERNAL_DESC, INTERNAL_DESC) .network(NETWORK) - .load_wallet(&mut db)?; + .load_wallet(&mut conn)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) .network(NETWORK) - .create_wallet(&mut db)?, + .create_wallet(&mut conn)?, }; let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; + wallet.persist(&mut conn)?; println!("Generated Address: {}", address); let balance = wallet.balance(); - println!("Wallet balance before syncing: {} sats", balance.total()); + println!("Wallet balance before syncing: {}", balance.total()); print!("Syncing..."); let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); @@ -54,9 +49,9 @@ fn main() -> Result<(), anyhow::Error> { .start_full_scan() .inspect_spks_for_all_keychains({ let mut once = HashSet::::new(); - move |k, spk_i, _| { - if once.insert(k) { - print!("\nScanning keychain [{:?}]", k) + move |kind, spk_i, _| { + if once.insert(kind) { + print!("\nScanning keychain [{:?}] {:<3}", kind, spk_i) } else { print!(" {:<3}", spk_i) } @@ -72,25 +67,22 @@ fn main() -> Result<(), anyhow::Error> { println!(); wallet.apply_update(update)?; - wallet.persist(&mut db)?; + wallet.persist(&mut conn)?; let balance = wallet.balance(); - println!("Wallet balance after syncing: {} sats", balance.total()); + println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { println!( - "Please send at least {} sats to the receiving address", + "Please send at least {} to the receiving address", SEND_AMOUNT ); std::process::exit(0); } - let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")? - .require_network(Network::Testnet)?; - let mut tx_builder = wallet.build_tx(); tx_builder - .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT) + .add_recipient(address.script_pubkey(), SEND_AMOUNT) .enable_rbf(); let mut psbt = tx_builder.finish()?; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index cccd83395..55605ea3a 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -1,18 +1,17 @@ -use std::{collections::BTreeSet, io::Write}; +use std::io::Write; use anyhow::Ok; use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ - bitcoin::{Amount, Network}, + bitcoin::{Amount, Network, Script}, rusqlite::Connection, KeychainKind, SignOptions, Wallet, }; const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 5; -const PARALLEL_REQUESTS: usize = 5; +const STOP_GAP: usize = 20; +const PARALLEL_REQUESTS: usize = 3; -const DB_PATH: &str = "bdk-example-esplora-async.sqlite"; const NETWORK: Network = Network::Signet; const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; @@ -20,7 +19,7 @@ const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - let mut conn = Connection::open(DB_PATH)?; + let mut conn = Connection::open_in_memory().expect("must open connection"); let wallet_opt = Wallet::load() .descriptors(EXTERNAL_DESC, INTERNAL_DESC) @@ -38,21 +37,32 @@ async fn main() -> Result<(), anyhow::Error> { println!("Next unused address: ({}) {}", address.index, address); let balance = wallet.balance(); - println!("Wallet balance before syncing: {} sats", balance.total()); + println!("Wallet balance before syncing: {}", balance.total()); print!("Syncing..."); let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; - let request = wallet.start_full_scan().inspect_spks_for_all_keychains({ - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{:?}] ", keychain); - } - print!(" {:<3}", spk_i); - std::io::stdout().flush().expect("must flush") + fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static { + let mut once = Some(()); + let mut stdout = std::io::stdout(); + move |spk_i, _| { + match once.take() { + Some(_) => print!("\nScanning keychain [{:?}] {:<3}", kind, spk_i), + None => print!(" {:<3}", spk_i), + }; + stdout.flush().expect("must flush"); } - }); + } + let request = wallet + .start_full_scan() + .inspect_spks_for_keychain( + KeychainKind::External, + generate_inspect(KeychainKind::External), + ) + .inspect_spks_for_keychain( + KeychainKind::Internal, + generate_inspect(KeychainKind::Internal), + ); let mut update = client .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) @@ -60,16 +70,18 @@ async fn main() -> Result<(), anyhow::Error> { let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); let _ = update.graph_update.update_last_seen_unconfirmed(now); + println!(); + wallet.apply_update(update)?; wallet.persist(&mut conn)?; println!(); let balance = wallet.balance(); - println!("Wallet balance after syncing: {} sats", balance.total()); + println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { println!( - "Please send at least {} sats to the receiving address", + "Please send at least {} to the receiving address", SEND_AMOUNT ); std::process::exit(0); diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 4c4fe99ed..606bd23e1 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -1,17 +1,15 @@ -use std::{collections::BTreeSet, io::Write}; +use std::io::Write; use bdk_esplora::{esplora_client, EsploraExt}; use bdk_wallet::{ - bitcoin::{Amount, Network}, - file_store::Store, + bitcoin::{Amount, Network, Script}, + rusqlite::Connection, KeychainKind, SignOptions, Wallet, }; -const DB_MAGIC: &str = "bdk_wallet_esplora_example"; -const DB_PATH: &str = "bdk-example-esplora-blocking.db"; const SEND_AMOUNT: Amount = Amount::from_sat(5000); -const STOP_GAP: usize = 5; -const PARALLEL_REQUESTS: usize = 5; +const STOP_GAP: usize = 20; +const PARALLEL_REQUESTS: usize = 3; const NETWORK: Network = Network::Signet; const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; @@ -19,59 +17,66 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; fn main() -> Result<(), anyhow::Error> { - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?; + let mut conn = Connection::open_in_memory().expect("must open connection"); let wallet_opt = Wallet::load() .descriptors(EXTERNAL_DESC, INTERNAL_DESC) .network(NETWORK) - .load_wallet(&mut db)?; + .load_wallet(&mut conn)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) .network(NETWORK) - .create_wallet(&mut db)?, + .create_wallet(&mut conn)?, }; let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!( - "Next unused address: ({}) {}", - address.index, address.address - ); + wallet.persist(&mut conn)?; + println!("Generated Address: {}", address); let balance = wallet.balance(); - println!("Wallet balance before syncing: {} sats", balance.total()); + println!("Wallet balance before syncing: {}", balance.total()); print!("Syncing..."); let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); - let request = wallet.start_full_scan().inspect_spks_for_all_keychains({ - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{:?}] ", keychain); - } - print!(" {:<3}", spk_i); - std::io::stdout().flush().expect("must flush") + fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static { + let mut once = Some(()); + let mut stdout = std::io::stdout(); + move |spk_i, _| { + match once.take() { + Some(_) => print!("\nScanning keychain [{:?}] {:<3}", kind, spk_i), + None => print!(" {:<3}", spk_i), + }; + stdout.flush().expect("must flush"); } - }); + } + let request = wallet + .start_full_scan() + .inspect_spks_for_keychain( + KeychainKind::External, + generate_inspect(KeychainKind::External), + ) + .inspect_spks_for_keychain( + KeychainKind::Internal, + generate_inspect(KeychainKind::Internal), + ); let mut update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?; let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); let _ = update.graph_update.update_last_seen_unconfirmed(now); - wallet.apply_update(update)?; - if let Some(changeset) = wallet.take_staged() { - db.append_changeset(&changeset)?; - } println!(); + wallet.apply_update(update)?; + wallet.persist(&mut conn)?; + let balance = wallet.balance(); - println!("Wallet balance after syncing: {} sats", balance.total()); + println!("Wallet balance after syncing: {}", balance.total()); if balance.total() < SEND_AMOUNT { println!( - "Please send at least {} sats to the receiving address", + "Please send at least {} to the receiving address", SEND_AMOUNT ); std::process::exit(0); diff --git a/example-crates/wallet_rpc/Cargo.toml b/example-crates/wallet_rpc/Cargo.toml index ffda1d3ef..0d0558fa2 100644 --- a/example-crates/wallet_rpc/Cargo.toml +++ b/example-crates/wallet_rpc/Cargo.toml @@ -6,9 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] } +bdk_wallet = { path = "../../crates/wallet" } bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" } - anyhow = "1" clap = { version = "3.2.25", features = ["derive", "env"] } ctrlc = "2.0.1" diff --git a/example-crates/wallet_rpc/README.md b/example-crates/wallet_rpc/README.md index 28eb07b1f..8b023cd7f 100644 --- a/example-crates/wallet_rpc/README.md +++ b/example-crates/wallet_rpc/README.md @@ -7,21 +7,18 @@ wallet_rpc 0.1.0 Bitcoind RPC example using `bdk_wallet::Wallet` USAGE: - wallet_rpc [OPTIONS] [CHANGE_DESCRIPTOR] + wallet_rpc [OPTIONS] ARGS: Wallet descriptor [env: DESCRIPTOR=] Wallet change descriptor [env: CHANGE_DESCRIPTOR=] OPTIONS: - --db-path - Where to store wallet data [env: BDK_DB_PATH=] [default: .bdk_wallet_rpc_example.db] - -h, --help Print help information --network - Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: testnet] + Bitcoin network to connect to [env: BITCOIN_NETWORK=] [default: signet] --rpc-cookie RPC auth cookie file [env: RPC_COOKIE=] @@ -33,10 +30,10 @@ OPTIONS: RPC auth username [env: RPC_USER=] --start-height - Earliest block height to start sync from [env: START_HEIGHT=] [default: 481824] + Earliest block height to start sync from [env: START_HEIGHT=] [default: 100000] --url - RPC URL [env: RPC_URL=] [default: 127.0.0.1:8332] + RPC URL [env: RPC_URL=] [default: 127.0.0.1:38332] -V, --version Print version information diff --git a/example-crates/wallet_rpc/src/main.rs b/example-crates/wallet_rpc/src/main.rs index 2533de644..fd958a29b 100644 --- a/example-crates/wallet_rpc/src/main.rs +++ b/example-crates/wallet_rpc/src/main.rs @@ -1,16 +1,19 @@ +use std::path::PathBuf; +use std::sync::mpsc::sync_channel; +use std::thread::spawn; +use std::time::Instant; + +use clap::{self, Parser}; + use bdk_bitcoind_rpc::{ bitcoincore_rpc::{Auth, Client, RpcApi}, Emitter, }; use bdk_wallet::{ bitcoin::{Block, Network, Transaction}, - file_store::Store, + rusqlite::Connection, Wallet, }; -use clap::{self, Parser}; -use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant}; - -const DB_MAGIC: &str = "bdk-rpc-wallet-example"; /// Bitcoind RPC example using `bdk_wallet::Wallet`. /// @@ -27,21 +30,14 @@ pub struct Args { #[clap(env = "CHANGE_DESCRIPTOR")] pub change_descriptor: String, /// Earliest block height to start sync from - #[clap(env = "START_HEIGHT", long, default_value = "481824")] + #[clap(env = "START_HEIGHT", long, default_value = "100000")] pub start_height: u32, /// Bitcoin network to connect to - #[clap(env = "BITCOIN_NETWORK", long, default_value = "testnet")] + #[clap(env = "BITCOIN_NETWORK", long, default_value = "signet")] pub network: Network, - /// Where to store wallet data - #[clap( - env = "BDK_DB_PATH", - long, - default_value = ".bdk_wallet_rpc_example.db" - )] - pub db_path: PathBuf, /// RPC URL - #[clap(env = "RPC_URL", long, default_value = "127.0.0.1:8332")] + #[clap(env = "RPC_URL", long, default_value = "127.0.0.1:38332")] pub url: String, /// RPC auth cookie file #[clap(env = "RPC_COOKIE", long)] @@ -86,17 +82,17 @@ fn main() -> anyhow::Result<()> { ); let start_load_wallet = Instant::now(); - let mut db = - Store::::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?; + let mut conn = Connection::open_in_memory().expect("must open connection"); + let wallet_opt = Wallet::load() .descriptors(args.descriptor.clone(), args.change_descriptor.clone()) .network(args.network) - .load_wallet(&mut db)?; + .load_wallet(&mut conn)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, None => Wallet::create(args.descriptor, args.change_descriptor) .network(args.network) - .create_wallet(&mut db)?, + .create_wallet(&mut conn)?, }; println!( "Loaded wallet in {}s", @@ -104,7 +100,7 @@ fn main() -> anyhow::Result<()> { ); let balance = wallet.balance(); - println!("Wallet balance before syncing: {} sats", balance.total()); + println!("Wallet balance before syncing: {}", balance.total()); let wallet_tip = wallet.latest_checkpoint(); println!( @@ -146,7 +142,7 @@ fn main() -> anyhow::Result<()> { 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)?; + wallet.persist(&mut conn)?; let elapsed = start_apply_block.elapsed().as_secs_f32(); println!( "Applied block {} at height {} in {}s", @@ -156,7 +152,7 @@ fn main() -> anyhow::Result<()> { Emission::Mempool(mempool_emission) => { let start_apply_mempool = Instant::now(); wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time))); - wallet.persist(&mut db)?; + wallet.persist(&mut conn)?; println!( "Applied unconfirmed transactions in {}s", start_apply_mempool.elapsed().as_secs_f32() @@ -177,7 +173,7 @@ fn main() -> anyhow::Result<()> { wallet_tip_end.height(), wallet_tip_end.hash() ); - println!("Wallet balance is {} sats", balance.total()); + println!("Wallet balance is {}", balance.total()); println!( "Wallet has {} transactions and {} utxos", wallet.transactions().count(),