From e1eb0253cf34053a4cb93e7be0ec2d74281eff47 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Tue, 21 Feb 2023 12:38:16 +1100 Subject: [PATCH] Make doctests work --- README.md | 16 +-- examples/compiler.rs | 2 +- examples/electrum_backend.rs | 87 ----------------- examples/esplora.rs | 112 --------------------- examples/esplora_backend_asynchronous.rs | 93 ------------------ examples/esplora_backend_synchronous.rs | 89 ----------------- examples/hardware_signer.rs | 3 +- examples/psbt_signer.rs | 118 ----------------------- examples/utils/mod.rs | 7 +- src/descriptor/dsl.rs | 38 ++++---- src/descriptor/template.rs | 70 ++++---------- src/lib.rs | 107 ++------------------ src/wallet/coin_selection.rs | 4 +- src/wallet/export.rs | 10 +- src/wallet/hardwaresigner.rs | 4 +- src/wallet/mod.rs | 64 ++++++++---- src/wallet/signer.rs | 3 +- src/wallet/tx_builder.rs | 8 +- 18 files changed, 113 insertions(+), 722 deletions(-) delete mode 100644 examples/electrum_backend.rs delete mode 100644 examples/esplora.rs delete mode 100644 examples/esplora_backend_asynchronous.rs delete mode 100644 examples/esplora_backend_synchronous.rs delete mode 100644 examples/psbt_signer.rs diff --git a/README.md b/README.md index 0f15de96..95ce4315 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,6 @@ The `bdk` library aims to be the core building block for Bitcoin wallets of any ```rust,no_run use bdk::Wallet; -use bdk::database::MemoryDatabase; use bdk::blockchain::ElectrumBlockchain; use bdk::SyncOptions; use bdk::electrum_client::Client; @@ -52,7 +51,6 @@ fn main() -> Result<(), bdk::Error> { "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Network::Testnet, - MemoryDatabase::default(), )?; wallet.sync(&blockchain, SyncOptions::default())?; @@ -66,16 +64,15 @@ fn main() -> Result<(), bdk::Error> { ### Generate a few addresses ```rust -use bdk::{Wallet, database::MemoryDatabase}; +use bdk::Wallet; use bdk::wallet::AddressIndex::New; use bdk::bitcoin::Network; fn main() -> Result<(), bdk::Error> { - let wallet = Wallet::new( + let wallet = Wallet::new_no_persist( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Network::Testnet, - MemoryDatabase::default(), )?; println!("Address #0: {}", wallet.get_address(New)); @@ -90,7 +87,6 @@ fn main() -> Result<(), bdk::Error> { ```rust,no_run use bdk::{FeeRate, Wallet, SyncOptions}; -use bdk::database::MemoryDatabase; use bdk::blockchain::ElectrumBlockchain; use bdk::electrum_client::Client; @@ -102,11 +98,10 @@ use bdk::bitcoin::Network; fn main() -> Result<(), bdk::Error> { let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); - let wallet = Wallet::new( + let wallet = Wallet::new_no_persist( "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), Network::Testnet, - MemoryDatabase::default(), )?; wallet.sync(&blockchain, SyncOptions::default())?; @@ -132,18 +127,17 @@ fn main() -> Result<(), bdk::Error> { ### Sign a transaction ```rust,no_run -use bdk::{Wallet, SignOptions, database::MemoryDatabase}; +use bdk::{Wallet, SignOptions}; use base64; use bdk::bitcoin::consensus::deserialize; use bdk::bitcoin::Network; fn main() -> Result<(), bdk::Error> { - let wallet = Wallet::new( + let wallet = Wallet::new_no_persist( "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), Network::Testnet, - MemoryDatabase::default(), )?; let psbt = "..."; diff --git a/examples/compiler.rs b/examples/compiler.rs index 98f43394..f8918895 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -54,7 +54,7 @@ fn main() -> Result<(), Box> { info!("Compiled into following Descriptor: \n{}", descriptor); // Create a new wallet from this descriptor - let wallet = Wallet::new(&format!("{}", descriptor), None, Network::Regtest)?; + let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?; info!( "First derived address from the descriptor: \n{}", diff --git a/examples/electrum_backend.rs b/examples/electrum_backend.rs deleted file mode 100644 index 5259865f..00000000 --- a/examples/electrum_backend.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::str::FromStr; - -use bdk::bitcoin::util::bip32::ExtendedPrivKey; -use bdk::bitcoin::Network; -use bdk::blockchain::{Blockchain, ElectrumBlockchain}; -use bdk::database::MemoryDatabase; -use bdk::template::Bip84; -use bdk::wallet::export::FullyNodedExport; -use bdk::{KeychainKind, SyncOptions, Wallet}; - -use bdk::electrum_client::Client; -use bdk::wallet::AddressIndex; -use bitcoin::util::bip32; - -pub mod utils; - -use crate::utils::tx::build_signed_tx; - -/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server. -/// If enough amount is available, this will send a transaction to an address. -/// Otherwise, this will display a wallet address to receive funds. -/// -/// This can be run with `cargo run --example electrum_backend` in the root folder. -fn main() { - let network = Network::Testnet; - - let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; - - let electrum_url = "ssl://electrum.blockstream.info:60002"; - - run(&network, electrum_url, xpriv); -} - -fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { - Wallet::new( - Bip84(*xpriv, KeychainKind::External), - Some(Bip84(*xpriv, KeychainKind::Internal)), - *network, - MemoryDatabase::default(), - ) - .unwrap() -} - -fn run(network: &Network, electrum_url: &str, xpriv: &str) { - let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); - - // Apparently it works only with Electrs (not EletrumX) - let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap()); - - let wallet = create_wallet(network, &xpriv); - - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - - let address = wallet.get_address(AddressIndex::New).unwrap().address; - - println!("address: {}", address); - - let balance = wallet.get_balance().unwrap(); - - println!("Available coins in BDK wallet : {} sats", balance); - - if balance.confirmed > 6500 { - // the wallet sends the amount to itself. - let recipient_address = wallet - .get_address(AddressIndex::New) - .unwrap() - .address - .to_string(); - - let amount = 5359; - - let tx = build_signed_tx(&wallet, &recipient_address, amount); - - blockchain.broadcast(&tx).unwrap(); - - println!("tx id: {}", tx.txid()); - } else { - println!("Insufficient Funds. Fund the wallet with the address above"); - } - - let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) - .map_err(ToString::to_string) - .map_err(bdk::Error::Generic) - .unwrap(); - - println!("------\nWallet Backup: {}", export.to_string()); -} diff --git a/examples/esplora.rs b/examples/esplora.rs deleted file mode 100644 index 16596664..00000000 --- a/examples/esplora.rs +++ /dev/null @@ -1,112 +0,0 @@ -use bdk::{ - blockchain::esplora::{esplora_client, BlockingClientExt}, - wallet::AddressIndex, - Wallet, -}; -use bdk_test_client::{RpcApi, TestClient}; -use bitcoin::{Amount, Network}; -use rand::Rng; -use std::error::Error; - -fn main() -> Result<(), Box> { - let _ = env_logger::init(); - const DESCRIPTOR: &'static str ="tr([73c5da0a/86'/0'/0']tprv8cSrHfiTQQWzKVejDHvBcvW4pdLEDLMvtVdbUXFfceQ4kbZKMsuFWbd3LUN3omNrQfafQaPwXUFXtcofkE9UjFZ3i9deezBHQTGvYV2xUzz/0/*)"; - const CHANGE_DESCRIPTOR: &'static str = "tr(tprv8ZgxMBicQKsPeQe98SGJ53vEJ7MNEFkQ4CkZmrr6PNom3vn6GqxuyoE78smkzpuP347zR9MXPg38PoZ8tbxLqSx4CufufHAGbQ9Hf7yTTwn/44'/0'/0'/1/*)#pxy2d75a"; - - let mut test_client = TestClient::default(); - let esplora_url = format!( - "http://{}", - test_client.electrsd.esplora_url.as_ref().unwrap() - ); - let client = esplora_client::Builder::new(&esplora_url).build_blocking()?; - - let wallet = Wallet::new(DESCRIPTOR, Some(CHANGE_DESCRIPTOR), Network::Regtest) - .expect("parsing descriptors failed"); - // note we don't *need* the Mutex for this example but it helps to show when the wallet does and - // doesn't need to be mutablek - let wallet = std::sync::Mutex::new(wallet); - let n_initial_transactions = 10; - - let addresses = { - // we need it to be mutable to get a new address. - // This incremenents the derivatoin index of the keychain. - let mut wallet = wallet.lock().unwrap(); - core::iter::repeat_with(|| wallet.get_address(AddressIndex::New)) - .filter(|_| rand::thread_rng().gen_bool(0.5)) - .take(n_initial_transactions) - .collect::>() - }; - - // get some coins for the internal node - test_client.generate(100, None); - - for address in addresses { - let exp_txid = test_client - .send_to_address( - &address, - Amount::from_sat(10_000), - None, - None, - None, - None, - None, - None, - ) - .expect("tx should send"); - eprintln!( - "💸 sending some coins to: {} (index {}) in tx {}", - address, address.index, exp_txid - ); - // sometimes generate a block after we send coins to the address - if rand::thread_rng().gen_bool(0.3) { - let height = test_client.generate(1, None); - eprintln!("📦 created a block at height {}", height); - } - } - - let wait_for_esplora_sync = std::time::Duration::from_secs(5); - - println!("⏳ waiting {}s for esplora to catch up..", wait_for_esplora_sync.as_secs()); - std::thread::sleep(wait_for_esplora_sync); - - - let wallet_scan_input = { - let wallet = wallet.lock().unwrap(); - wallet.start_wallet_scan() - }; - - let start = std::time::Instant::now(); - let stop_gap = 5; - eprintln!( - "🔎 startig scanning all keychains with stop gap of {}", - stop_gap - ); - let wallet_scan = client.wallet_scan(wallet_scan_input, stop_gap, &Default::default(), 5)?; - - // we've got an update so briefly take a lock the wallet to apply it - { - let mut wallet = wallet.lock().unwrap(); - match wallet.apply_wallet_scan(wallet_scan) { - Ok(changes) => { - eprintln!("🎉 success! ({}ms)", start.elapsed().as_millis()); - eprintln!("wallet balance after: {:?}", wallet.get_balance()); - //XXX: esplora is not indexing mempool transactions right now (or not doing it fast enough) - eprintln!( - "wallet found {} new transactions", - changes.tx_additions().count(), - ); - if changes.tx_additions().count() != n_initial_transactions { - eprintln!( - "(it should have found {} but maybe stop gap wasn't large enough?)", - n_initial_transactions - ); - } - } - Err(reason) => { - eprintln!("❌ esplora produced invalid wallet scan {}", reason); - } - } - } - - Ok(()) -} diff --git a/examples/esplora_backend_asynchronous.rs b/examples/esplora_backend_asynchronous.rs deleted file mode 100644 index 4aa149ba..00000000 --- a/examples/esplora_backend_asynchronous.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::str::FromStr; - -use bdk::blockchain::Blockchain; -use bdk::{ - blockchain::esplora::EsploraBlockchain, - database::MemoryDatabase, - template::Bip84, - wallet::{export::FullyNodedExport, AddressIndex}, - KeychainKind, SyncOptions, Wallet, -}; -use bitcoin::{ - util::bip32::{self, ExtendedPrivKey}, - Network, -}; - -pub mod utils; - -use crate::utils::tx::build_signed_tx; - -/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server, -/// using non blocking asynchronous calls with `reqwest`. -/// If enough amount is available, this will send a transaction to an address. -/// Otherwise, this will display a wallet address to receive funds. -/// -/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous` -/// in the root folder. -#[tokio::main(flavor = "current_thread")] -async fn main() { - let network = Network::Signet; - - let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; - - let esplora_url = "https://explorer.bc-2.jp/api"; - - run(&network, esplora_url, xpriv).await; -} - -fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { - Wallet::new( - Bip84(*xpriv, KeychainKind::External), - Some(Bip84(*xpriv, KeychainKind::Internal)), - *network, - MemoryDatabase::default(), - ) - .unwrap() -} - -async fn run(network: &Network, esplora_url: &str, xpriv: &str) { - let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); - - let blockchain = EsploraBlockchain::new(esplora_url, 20); - - let wallet = create_wallet(network, &xpriv); - - wallet - .sync(&blockchain, SyncOptions::default()) - .await - .unwrap(); - - let address = wallet.get_address(AddressIndex::New).unwrap().address; - - println!("address: {}", address); - - let balance = wallet.get_balance().unwrap(); - - println!("Available coins in BDK wallet : {} sats", balance); - - if balance.confirmed > 10500 { - // the wallet sends the amount to itself. - let recipient_address = wallet - .get_address(AddressIndex::New) - .unwrap() - .address - .to_string(); - - let amount = 9359; - - let tx = build_signed_tx(&wallet, &recipient_address, amount); - - let _ = blockchain.broadcast(&tx); - - println!("tx id: {}", tx.txid()); - } else { - println!("Insufficient Funds. Fund the wallet with the address above"); - } - - let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) - .map_err(ToString::to_string) - .map_err(bdk::Error::Generic) - .unwrap(); - - println!("------\nWallet Backup: {}", export.to_string()); -} diff --git a/examples/esplora_backend_synchronous.rs b/examples/esplora_backend_synchronous.rs deleted file mode 100644 index 31907f83..00000000 --- a/examples/esplora_backend_synchronous.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::str::FromStr; - -use bdk::blockchain::Blockchain; -use bdk::{ - blockchain::esplora::EsploraBlockchain, - database::MemoryDatabase, - template::Bip84, - wallet::{export::FullyNodedExport, AddressIndex}, - KeychainKind, SyncOptions, Wallet, -}; -use bitcoin::{ - util::bip32::{self, ExtendedPrivKey}, - Network, -}; - -pub mod utils; - -use crate::utils::tx::build_signed_tx; - -/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server, -/// using blocking calls with `ureq`. -/// If enough amount is available, this will send a transaction to an address. -/// Otherwise, this will display a wallet address to receive funds. -/// -/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous` -/// in the root folder. -fn main() { - let network = Network::Signet; - - let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy"; - - let esplora_url = "https://explorer.bc-2.jp/api"; - - run(&network, esplora_url, xpriv); -} - -fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet { - Wallet::new( - Bip84(*xpriv, KeychainKind::External), - Some(Bip84(*xpriv, KeychainKind::Internal)), - *network, - MemoryDatabase::default(), - ) - .unwrap() -} - -fn run(network: &Network, esplora_url: &str, xpriv: &str) { - let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap(); - - let blockchain = EsploraBlockchain::new(esplora_url, 20); - - let wallet = create_wallet(network, &xpriv); - - wallet.sync(&blockchain, SyncOptions::default()).unwrap(); - - let address = wallet.get_address(AddressIndex::New).unwrap().address; - - println!("address: {}", address); - - let balance = wallet.get_balance().unwrap(); - - println!("Available coins in BDK wallet : {} sats", balance); - - if balance.confirmed > 10500 { - // the wallet sends the amount to itself. - let recipient_address = wallet - .get_address(AddressIndex::New) - .unwrap() - .address - .to_string(); - - let amount = 9359; - - let tx = build_signed_tx(&wallet, &recipient_address, amount); - - blockchain.broadcast(&tx).unwrap(); - - println!("tx id: {}", tx.txid()); - } else { - println!("Insufficient Funds. Fund the wallet with the address above"); - } - - let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) - .map_err(ToString::to_string) - .map_err(bdk::Error::Generic) - .unwrap(); - - println!("------\nWallet Backup: {}", export.to_string()); -} diff --git a/examples/hardware_signer.rs b/examples/hardware_signer.rs index 514f99a6..afaf1948 100644 --- a/examples/hardware_signer.rs +++ b/examples/hardware_signer.rs @@ -1,6 +1,5 @@ use bdk::bitcoin::{Address, Network}; use bdk::blockchain::{Blockchain, ElectrumBlockchain}; -use bdk::database::MemoryDatabase; use bdk::hwi::{types::HWIChain, HWIClient}; use bdk::miniscript::{Descriptor, DescriptorPublicKey}; use bdk::signer::SignerOrdering; @@ -42,7 +41,7 @@ fn main() -> Result<(), Box> { // Creating a custom signer from the device let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?; - let mut wallet = Wallet::new( + let mut wallet = Wallet::new_no_persist( descriptors.receive[0].clone(), Some(descriptors.internal[0].clone()), Network::Testnet, diff --git a/examples/psbt_signer.rs b/examples/psbt_signer.rs deleted file mode 100644 index c7d4156d..00000000 --- a/examples/psbt_signer.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2020-2022 Bitcoin Dev Kit Developers -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -use bdk::blockchain::{Blockchain, ElectrumBlockchain}; -use bdk::database::MemoryDatabase; -use bdk::wallet::AddressIndex; -use bdk::{descriptor, SyncOptions}; -use bdk::{FeeRate, SignOptions, Wallet}; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::{Address, Network}; -use electrum_client::Client; -use miniscript::descriptor::DescriptorSecretKey; -use std::error::Error; -use std::str::FromStr; - -/// This example shows how to sign and broadcast the transaction for a PSBT (Partially Signed -/// Bitcoin Transaction) for a single key, witness public key hash (WPKH) based descriptor wallet. -/// The electrum protocol is used to sync blockchain data from the testnet bitcoin network and -/// wallet data is stored in an ephemeral in-memory database. The process steps are: -/// 1. Create a "signing" wallet and a "watch-only" wallet based on the same private keys. -/// 2. Deposit testnet funds into the watch only wallet. -/// 3. Sync the watch only wallet and create a spending transaction to return all funds to the testnet faucet. -/// 4. Sync the signing wallet and sign and finalize the PSBT created by the watch only wallet. -/// 5. Broadcast the transactions from the finalized PSBT. -fn main() -> Result<(), Box> { - // test key created with `bdk-cli key generate` and `bdk-cli key derive` commands - let external_secret_xkey = DescriptorSecretKey::from_str("[e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/0/*").unwrap(); - let internal_secret_xkey = DescriptorSecretKey::from_str("[e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/1/*").unwrap(); - - let secp = Secp256k1::new(); - let external_public_xkey = external_secret_xkey.to_public(&secp).unwrap(); - let internal_public_xkey = internal_secret_xkey.to_public(&secp).unwrap(); - - let signing_external_descriptor = descriptor!(wpkh(external_secret_xkey)).unwrap(); - let signing_internal_descriptor = descriptor!(wpkh(internal_secret_xkey)).unwrap(); - - let watch_only_external_descriptor = descriptor!(wpkh(external_public_xkey)).unwrap(); - let watch_only_internal_descriptor = descriptor!(wpkh(internal_public_xkey)).unwrap(); - - // create client for Blockstream's testnet electrum server - let blockchain = - ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?); - - // create watch only wallet - let watch_only_wallet: Wallet = Wallet::new( - watch_only_external_descriptor, - Some(watch_only_internal_descriptor), - Network::Testnet, - )?; - - // create signing wallet - let signing_wallet: Wallet = Wallet::new( - signing_external_descriptor, - Some(signing_internal_descriptor), - Network::Testnet, - )?; - - println!("Syncing watch only wallet."); - watch_only_wallet.sync(&blockchain, SyncOptions::default())?; - - // get deposit address - let deposit_address = watch_only_wallet.get_address(AddressIndex::New); - - let balance = watch_only_wallet.get_balance()?; - println!("Watch only wallet balances in SATs: {}", balance); - - if balance.get_total() < 10000 { - println!( - "Send at least 10000 SATs (0.0001 BTC) from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}", - addr = deposit_address.address - ); - } else if balance.get_spendable() < 10000 { - println!( - "Wait for at least 10000 SATs of your wallet transactions to be confirmed...\nBe patient, this could take 10 mins or longer depending on how testnet is behaving." - ); - for tx_details in watch_only_wallet - .transactions() - .iter() - .filter(|txd| txd.received > 0 && txd.confirmation_time.is_none()) - { - println!( - "See unconfirmed tx for {} SATs: https://mempool.space/testnet/tx/{}", - tx_details.received, tx_details.txid - ); - } - } else { - println!("Creating a PSBT sending 9800 SATs plus fee to the u01.net testnet faucet return address 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt'."); - let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?; - let mut builder = watch_only_wallet.build_tx(); - builder - .add_recipient(return_address.script_pubkey(), 9_800) - .enable_rbf() - .fee_rate(FeeRate::from_sat_per_vb(1.0)); - - let (mut psbt, details) = builder.finish()?; - println!("Transaction details: {:#?}", details); - println!("Unsigned PSBT: {}", psbt); - - // Sign and finalize the PSBT with the signing wallet - let finalized = signing_wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized, "The PSBT was not finalized!"); - println!("The PSBT has been signed and finalized."); - - // Broadcast the transaction - let raw_transaction = psbt.extract_tx(); - let txid = raw_transaction.txid(); - - blockchain.broadcast(&raw_transaction)?; - println!("Transaction broadcast! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid); - } - - Ok(()) -} diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 25249fa7..c7f032e9 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -2,11 +2,11 @@ pub(crate) mod tx { use std::str::FromStr; - use bdk::{database::BatchDatabase, SignOptions, Wallet}; + use bdk::{SignOptions, Wallet, persist}; use bitcoin::{Address, Transaction}; - pub fn build_signed_tx( - wallet: &Wallet, + pub fn build_signed_tx<()>( + wallet: &Wallet<()>, recipient_address: &str, amount: u64, ) -> Transaction { @@ -15,6 +15,7 @@ pub(crate) mod tx { let to_address = Address::from_str(recipient_address).unwrap(); + // Set recipient of the transaction tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]); diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index fdd78a46..e689a540 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -203,8 +203,8 @@ macro_rules! impl_node_opcode_two { a_keymap.extend(b_keymap.into_iter()); let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant( - alloc::sync::Arc::new(a_minisc), - alloc::sync::Arc::new(b_minisc), + $crate::alloc::sync::Arc::new(a_minisc), + $crate::alloc::sync::Arc::new(b_minisc), ))?; minisc.check_miniscript()?; @@ -234,9 +234,9 @@ macro_rules! impl_node_opcode_three { let networks = $crate::keys::merge_networks(&networks, &c_networks); let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant( - alloc::sync::Arc::new(a_minisc), - alloc::sync::Arc::new(b_minisc), - alloc::sync::Arc::new(c_minisc), + $crate::alloc::sync::Arc::new(a_minisc), + $crate::alloc::sync::Arc::new(b_minisc), + $crate::alloc::sync::Arc::new(c_minisc), ))?; minisc.check_miniscript()?; @@ -263,7 +263,7 @@ macro_rules! impl_sortedmulti { )* ]; - keys.into_iter().collect::, _>>() + keys.into_iter().collect::, _>>() .map_err($crate::descriptor::DescriptorError::Key) .and_then(|keys| $crate::keys::make_sortedmulti($thresh, keys, $build_desc, &secp)) }); @@ -274,7 +274,7 @@ macro_rules! impl_sortedmulti { #[macro_export] macro_rules! parse_tap_tree { ( @merge $tree_a:expr, $tree_b:expr) => {{ - use alloc::sync::Arc; + use $crate::alloc::sync::Arc; use $crate::miniscript::descriptor::TapTree; $tree_a @@ -318,7 +318,7 @@ macro_rules! parse_tap_tree { // Single leaf ( $op:ident ( $( $minisc:tt )* ) ) => {{ - use alloc::sync::Arc; + use $crate::alloc::sync::Arc; use $crate::miniscript::descriptor::TapTree; $crate::fragment!( $op ( $( $minisc )* ) ) @@ -337,7 +337,7 @@ macro_rules! apply_modifier { .and_then(|(minisc, keymap, networks)| { let minisc = $crate::miniscript::Miniscript::from_ast( $crate::miniscript::miniscript::decode::Terminal::$terminal_variant( - alloc::sync::Arc::new(minisc), + $crate::alloc::sync::Arc::new(minisc), ), )?; @@ -374,8 +374,8 @@ macro_rules! apply_modifier { $inner.and_then(|(a_minisc, a_keymap, a_networks)| { $crate::impl_leaf_opcode_value_two!( AndV, - alloc::sync::Arc::new(a_minisc), - alloc::sync::Arc::new($crate::fragment!(true).unwrap().0) + $crate::alloc::sync::Arc::new(a_minisc), + $crate::alloc::sync::Arc::new($crate::fragment!(true).unwrap().0) ) .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) }) @@ -384,8 +384,8 @@ macro_rules! apply_modifier { $inner.and_then(|(a_minisc, a_keymap, a_networks)| { $crate::impl_leaf_opcode_value_two!( OrI, - alloc::sync::Arc::new($crate::fragment!(false).unwrap().0), - alloc::sync::Arc::new(a_minisc) + $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0), + $crate::alloc::sync::Arc::new(a_minisc) ) .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) }) @@ -394,8 +394,8 @@ macro_rules! apply_modifier { $inner.and_then(|(a_minisc, a_keymap, a_networks)| { $crate::impl_leaf_opcode_value_two!( OrI, - alloc::sync::Arc::new(a_minisc), - alloc::sync::Arc::new($crate::fragment!(false).unwrap().0) + $crate::alloc::sync::Arc::new(a_minisc), + $crate::alloc::sync::Arc::new($crate::fragment!(false).unwrap().0) ) .map(|(minisc, _, _)| (minisc, a_keymap, a_networks)) }) @@ -599,7 +599,7 @@ macro_rules! group_multi_keys { )* ]; - keys.into_iter().collect::, _>>() + keys.into_iter().collect::, _>>() .map_err($crate::descriptor::DescriptorError::Key) }}; } @@ -744,8 +744,8 @@ macro_rules! fragment { ( thresh_vec ( $thresh:expr, $items:expr ) ) => ({ use $crate::miniscript::descriptor::KeyMap; - let (items, key_maps_networks): (alloc::vec::Vec<_>, alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip(); - let items = items.into_iter().map(alloc::sync::Arc::new).collect(); + let (items, key_maps_networks): ($crate::alloc::vec::Vec<_>, $crate::alloc::vec::Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip(); + let items = items.into_iter().map($crate::alloc::sync::Arc::new).collect(); let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| { keys_acc.extend(key.into_iter()); @@ -760,7 +760,7 @@ macro_rules! fragment { ( thresh ( $thresh:expr, $( $inner:tt )* ) ) => ({ let items = $crate::fragment_internal!( @v $( $inner )* ); - items.into_iter().collect::, _>>() + items.into_iter().collect::, _>>() .and_then(|items| $crate::fragment!(thresh_vec($thresh, items))) }); ( multi_vec ( $thresh:expr, $keys:expr ) ) => ({ diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index d6697bec..cf0296c9 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -73,19 +73,13 @@ impl IntoWalletDescriptor for T { /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; -/// # use bdk::{Wallet}; -/// # use bdk::database::MemoryDatabase; +/// # use bdk::Wallet; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2Pkh; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new( -/// P2Pkh(key), -/// None, -/// Network::Testnet, -/// MemoryDatabase::default(), -/// )?; +/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?; /// /// assert_eq!( /// wallet.get_address(New).to_string(), @@ -107,22 +101,16 @@ impl> DescriptorTemplate for P2Pkh { /// /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; -/// # use bdk::{Wallet}; -/// # use bdk::database::MemoryDatabase; -/// # use bdk::wallet::AddressIndex::New; +/// # use bdk::Wallet; /// use bdk::template::P2Wpkh_P2Sh; +/// use bdk::wallet::AddressIndex; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new( -/// P2Wpkh_P2Sh(key), -/// None, -/// Network::Testnet, -/// MemoryDatabase::default(), -/// )?; +/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?; /// /// assert_eq!( -/// wallet.get_address(New).to_string(), +/// wallet.get_address(AddressIndex::New).to_string(), /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" /// ); /// # Ok::<_, Box>(()) @@ -143,18 +131,12 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// ``` /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet}; -/// # use bdk::database::MemoryDatabase; -/// # use bdk::wallet::AddressIndex::New; /// use bdk::template::P2Wpkh; +/// use bdk::wallet::AddressIndex::New; /// /// let key = /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let wallet = Wallet::new( -/// P2Wpkh(key), -/// None, -/// Network::Testnet, -/// MemoryDatabase::default(), -/// )?; +/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?; /// /// assert_eq!( /// wallet.get_address(New).to_string(), @@ -182,20 +164,18 @@ impl> DescriptorTemplate for P2Wpkh { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip44(key.clone(), KeychainKind::External), /// Some(Bip44(key, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip44>(pub K, pub KeychainKind); @@ -221,21 +201,19 @@ impl> DescriptorTemplate for Bip44 { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); @@ -261,20 +239,18 @@ impl> DescriptorTemplate for Bip44Public { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip49(key.clone(), KeychainKind::External), /// Some(Bip49(key, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip49>(pub K, pub KeychainKind); @@ -300,21 +276,19 @@ impl> DescriptorTemplate for Bip49 { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); @@ -340,20 +314,18 @@ impl> DescriptorTemplate for Bip49Public { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84; /// /// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip84(key.clone(), KeychainKind::External), /// Some(Bip84(key, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip84>(pub K, pub KeychainKind); @@ -379,21 +351,19 @@ impl> DescriptorTemplate for Bip84 { /// # use std::str::FromStr; /// # use bdk::bitcoin::{PrivateKey, Network}; /// # use bdk::{Wallet, KeychainKind}; -/// # use bdk::database::MemoryDatabase; /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84Public; /// /// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; -/// let wallet = Wallet::new( +/// let mut wallet = Wallet::new_no_persist( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Network::Testnet, -/// MemoryDatabase::default() /// )?; /// /// assert_eq!(wallet.get_address(New).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg"); +/// assert_eq!(wallet.public_descriptor(KeychainKind::External).unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv"); /// # Ok::<_, Box>(()) /// ``` pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); diff --git a/src/lib.rs b/src/lib.rs index 6a208919..f4d73caa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,64 +31,18 @@ //! * It is built to be cross-platform: the core logic works on desktop, mobile, and even WebAssembly. //! * It is very easy to extend: developers can implement customized logic for blockchain backends, databases, signers, coin selection, and more, without having to fork and modify this library. //! -//! # A Tour of BDK -//! -//! BDK consists of a number of modules that provide a range of functionality -//! essential for implementing descriptor based Bitcoin wallet applications in Rust. In this -//! section, we will take a brief tour of BDK, summarizing the major APIs and -//! their uses. -//! -//! The easiest way to get started is to add bdk to your dependencies with the default features. -//! The default features include a simple key-value database ([`sled`](sled)) to cache -//! blockchain data and an [electrum](https://docs.rs/electrum-client/) blockchain client to -//! interact with the bitcoin P2P network. -//! -//! # Examples -#![cfg_attr( - feature = "electrum", - doc = r##" -## Sync the balance of a descriptor - -```no_run -use bdk::{Wallet, SyncOptions}; -use bdk::database::MemoryDatabase; -use bdk::blockchain::ElectrumBlockchain; -use bdk::electrum_client::Client; - -fn main() -> Result<(), bdk::Error> { - let client = Client::new("ssl://electrum.blockstream.info:60002")?; - let blockchain = ElectrumBlockchain::from(client); - let wallet = Wallet::new( - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", - Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), - bitcoin::Network::Testnet, - MemoryDatabase::default(), - )?; - - wallet.sync(&blockchain, SyncOptions::default())?; - - println!("Descriptor balance: {} SAT", wallet.get_balance()?); - - Ok(()) -} -``` -"## -)] -//! //! ## Generate a few addresses //! //! ### Example //! ``` //! use bdk::{Wallet}; -//! use bdk::database::MemoryDatabase; //! use bdk::wallet::AddressIndex::New; //! //! fn main() -> Result<(), bdk::Error> { -//! let wallet = Wallet::new( -//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", -//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), -//! bitcoin::Network::Testnet, -//! MemoryDatabase::default(), +//! let mut wallet = Wallet::new_no_persist( +//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", +//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), +//! bitcoin::Network::Testnet, //! )?; //! //! println!("Address #0: {}", wallet.get_address(New)); @@ -98,52 +52,6 @@ fn main() -> Result<(), bdk::Error> { //! Ok(()) //! } //! ``` -#![cfg_attr( - feature = "electrum", - doc = r##" -## Create a transaction - -```no_run -use bdk::{FeeRate, Wallet, SyncOptions}; -use bdk::database::MemoryDatabase; -use bdk::blockchain::ElectrumBlockchain; -use bdk::electrum_client::Client; - -use bitcoin::consensus::serialize; -use bdk::wallet::AddressIndex::New; - -fn main() -> Result<(), bdk::Error> { - let client = Client::new("ssl://electrum.blockstream.info:60002")?; - let wallet = Wallet::new( - "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", - Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"), - bitcoin::Network::Testnet, - MemoryDatabase::default(), - )?; - let blockchain = ElectrumBlockchain::from(client); - - wallet.sync(&blockchain, SyncOptions::default())?; - - let send_to = wallet.get_address(New); - let (psbt, details) = { - let mut builder = wallet.build_tx(); - builder - .add_recipient(send_to.script_pubkey(), 50_000) - .enable_rbf() - .do_not_spend_change() - .fee_rate(FeeRate::from_sat_per_vb(5.0)); - builder.finish()? - }; - - println!("Transaction details: {:#?}", details); - println!("Unsigned PSBT: {}", &psbt); - - Ok(()) -} -``` -"## -)] -//! //! ## Sign a transaction //! //! ```no_run @@ -152,14 +60,12 @@ fn main() -> Result<(), bdk::Error> { //! use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; //! //! use bdk::{Wallet, SignOptions}; -//! use bdk::database::MemoryDatabase; //! //! fn main() -> Result<(), bdk::Error> { -//! let wallet = Wallet::new( +//! let wallet = Wallet::new_no_persist( //! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"), //! bitcoin::Network::Testnet, -//! MemoryDatabase::default(), //! )?; //! //! let psbt = "..."; @@ -193,8 +99,9 @@ fn main() -> Result<(), bdk::Error> { #[macro_use] extern crate std; +#[doc(hidden)] #[macro_use] -extern crate alloc; +pub extern crate alloc; pub extern crate bitcoin; #[cfg(feature = "hardware-signer")] diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs index 12c89a7b..736b7319 100644 --- a/src/wallet/coin_selection.rs +++ b/src/wallet/coin_selection.rs @@ -27,7 +27,6 @@ //! # use std::str::FromStr; //! # use bitcoin::*; //! # use bdk::wallet::{self, coin_selection::*}; -//! # use bdk::database::Database; //! # use bdk::*; //! # use bdk::wallet::coin_selection::decide_change; //! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4) * 4; @@ -37,7 +36,6 @@ //! impl CoinSelectionAlgorithm for AlwaysSpendEverything { //! fn coin_select( //! &self, -//! database: &D, //! required_utxos: Vec, //! optional_utxos: Vec, //! fee_rate: FeeRate, @@ -79,7 +77,7 @@ //! } //! } //! -//! # let wallet = doctest_wallet!(); +//! # let mut wallet = doctest_wallet!(); //! // create wallet, sync, ... //! //! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); diff --git a/src/wallet/export.rs b/src/wallet/export.rs index 469a50ac..3ec43d2b 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -20,7 +20,6 @@ //! ``` //! # use std::str::FromStr; //! # use bitcoin::*; -//! # use bdk::database::*; //! # use bdk::wallet::export::*; //! # use bdk::*; //! let import = r#"{ @@ -30,11 +29,10 @@ //! }"#; //! //! let import = FullyNodedExport::from_str(import)?; -//! let wallet = Wallet::new( +//! let wallet = Wallet::new_no_persist( //! &import.descriptor(), //! import.change_descriptor().as_ref(), //! Network::Testnet, -//! MemoryDatabase::default(), //! )?; //! # Ok::<_, bdk::Error>(()) //! ``` @@ -42,14 +40,12 @@ //! ### Export a `Wallet` //! ``` //! # use bitcoin::*; -//! # use bdk::database::*; //! # use bdk::wallet::export::*; //! # use bdk::*; -//! let wallet = Wallet::new( +//! let wallet = Wallet::new_no_persist( //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), //! Network::Testnet, -//! MemoryDatabase::default() //! )?; //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true) //! .map_err(ToString::to_string) @@ -232,7 +228,7 @@ mod test { change_descriptor: Option<&str>, network: Network, ) -> Wallet<()> { - let mut wallet = Wallet::new(descriptor, change_descriptor, (), network).unwrap(); + let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); let transaction = Transaction { input: vec![], output: vec![], diff --git a/src/wallet/hardwaresigner.rs b/src/wallet/hardwaresigner.rs index 230cd4cf..b014bb53 100644 --- a/src/wallet/hardwaresigner.rs +++ b/src/wallet/hardwaresigner.rs @@ -15,7 +15,6 @@ //! used with hardware wallets. //! ```no_run //! # use bdk::bitcoin::Network; -//! # use bdk::database::MemoryDatabase; //! # use bdk::signer::SignerOrdering; //! # use bdk::wallet::hardwaresigner::HWISigner; //! # use bdk::wallet::AddressIndex::New; @@ -31,11 +30,10 @@ //! let first_device = devices.remove(0)?; //! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?; //! -//! # let mut wallet = Wallet::new( +//! # let mut wallet = Wallet::new_no_persist( //! # "", //! # None, //! # Network::Testnet, -//! # MemoryDatabase::default(), //! # )?; //! # //! // Adding the hardware signer to the BDK wallet diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 0f00dba9..d4dd571f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -25,17 +25,15 @@ use bdk_chain::{ keychain::{KeychainChangeSet, KeychainScan, KeychainTracker}, sparse_chain, BlockId, ConfirmationTime, IntoOwned, }; -use bitcoin::secp256k1::Secp256k1; -use core::fmt; -use core::ops::Deref; - use bitcoin::consensus::encode::serialize; +use bitcoin::secp256k1::Secp256k1; use bitcoin::util::psbt; use bitcoin::{ Address, BlockHash, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence, Transaction, TxOut, Txid, Witness, }; - +use core::fmt; +use core::ops::Deref; use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; #[allow(unused_imports)] @@ -79,10 +77,8 @@ const COINBASE_MATURITY: u32 = 100; /// Its main components are: /// /// 1. output *descriptors* from which it can derive addresses. -/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors. -/// 3. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors. +/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors. /// -/// [`Database`]: crate::database::Database /// [`signer`]: crate::signer #[derive(Debug)] pub struct Wallet { @@ -573,8 +569,7 @@ impl Wallet { /// ``` /// # use bdk::{Wallet, KeychainKind}; /// # use bdk::bitcoin::Network; - /// # use bdk::database::MemoryDatabase; - /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet, MemoryDatabase::new())?; + /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?; /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) { /// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/* /// println!("secret_key: {}", secret_key); @@ -599,9 +594,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::database::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let wallet = doctest_wallet!(); + /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// let (psbt, details) = { /// let mut builder = wallet.build_tx(); @@ -1006,9 +1000,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::database::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let wallet = doctest_wallet!(); + /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// let (mut psbt, _) = { /// let mut builder = wallet.build_tx(); @@ -1168,9 +1161,8 @@ impl Wallet { /// # use std::str::FromStr; /// # use bitcoin::*; /// # use bdk::*; - /// # use bdk::database::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let wallet = doctest_wallet!(); + /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// let (mut psbt, _) = { /// let mut builder = wallet.build_tx(); @@ -1380,7 +1372,6 @@ impl Wallet { /// Informs the wallet that you no longer intend to broadcast a tx that was built from it. /// /// This frees up the change address used when creating the tx for use in future transactions. - /// // TODO: Make this free up reserved utxos when that's implemented pub fn cancel_tx(&mut self, tx: &Transaction) { let txout_index = &mut self.keychain_tracker.txout_index; @@ -1397,7 +1388,7 @@ impl Wallet { if keychain == KeychainKind::Internal && self.public_descriptor(KeychainKind::Internal).is_none() { - return KeychainKind::External; + KeychainKind::External } else { keychain } @@ -1765,3 +1756,40 @@ where Ok(wallet_name) } + +#[macro_export] +#[doc(hidden)] +/// Macro for getting a wallet for use in a doctest +macro_rules! doctest_wallet { + () => {{ + use $crate::bitcoin::{BlockHash, Transaction, PackedLockTime, TxOut, Network, hashes::Hash}; + use $crate::chain::{ConfirmationTime, BlockId}; + use $crate::wallet::{AddressIndex, Wallet}; + let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; + let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)"; + + let mut wallet = Wallet::new_no_persist( + descriptor, + Some(change_descriptor), + Network::Regtest, + ) + .unwrap(); + let address = wallet.get_address(AddressIndex::New).address; + let tx = Transaction { + version: 1, + lock_time: PackedLockTime(0), + input: vec![], + output: vec![TxOut { + value: 500_000, + script_pubkey: address.script_pubkey(), + }], + }; + let _ = wallet.insert_checkpoint(BlockId { height: 1_000, hash: BlockHash::all_zeros() }); + let _ = wallet.insert_tx(tx.clone(), ConfirmationTime::Confirmed { + height: 500, + time: 50_000 + }); + + wallet + }} +} diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 51106d02..68dc4645 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -21,7 +21,6 @@ //! # use bitcoin::*; //! # use bitcoin::util::psbt; //! # use bdk::signer::*; -//! # use bdk::database::*; //! # use bdk::*; //! # #[derive(Debug)] //! # struct CustomHSM; @@ -70,7 +69,7 @@ //! let custom_signer = CustomSigner::connect(); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; +//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index d4d34cdc..c5492c50 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -19,7 +19,7 @@ //! # use bdk::*; //! # use bdk::wallet::tx_builder::CreateTx; //! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); -//! # let wallet = doctest_wallet!(); +//! # let mut wallet = doctest_wallet!(); //! // create a TxBuilder from a wallet //! let mut tx_builder = wallet.build_tx(); //! @@ -80,7 +80,7 @@ impl TxBuilderContext for BumpFee {} /// # use bdk::wallet::tx_builder::*; /// # use bitcoin::*; /// # use core::str::FromStr; -/// # let wallet = doctest_wallet!(); +/// # let mut wallet = doctest_wallet!(); /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// # let addr2 = addr1.clone(); /// // chaining @@ -242,7 +242,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, /// # use bitcoin::*; /// # use bdk::*; /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); - /// # let wallet = doctest_wallet!(); + /// # let mut wallet = doctest_wallet!(); /// let mut path = BTreeMap::new(); /// path.insert("aabbccdd".to_string(), vec![0, 1]); /// @@ -623,7 +623,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// # use bdk::*; /// # use bdk::wallet::tx_builder::CreateTx; /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); - /// # let wallet = doctest_wallet!(); + /// # let mut wallet = doctest_wallet!(); /// let mut tx_builder = wallet.build_tx(); /// /// tx_builder