Skip to content

Commit

Permalink
example bitcoind rpc wallet using regtest
Browse files Browse the repository at this point in the history
  • Loading branch information
ValuedMammal committed Nov 19, 2023
1 parent 46d39be commit e009549
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"example-crates/example_electrum",
"example-crates/example_esplora",
"example-crates/example_bitcoind_rpc_polling",
"example-crates/mywallet_rpc",
"example-crates/wallet_electrum",
"example-crates/wallet_esplora_blocking",
"example-crates/wallet_esplora_async",
Expand Down
12 changes: 12 additions & 0 deletions example-crates/mywallet_rpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "mywallet_rpc"
version = "0.1.0"
edition = "2021"
authors.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
bdk = { path = "../../crates/bdk", features = ["all-keys", "keys-bip39"] }
bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" }
206 changes: 206 additions & 0 deletions example-crates/mywallet_rpc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use bdk::bitcoin::bip32::DerivationPath;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::Amount;
use bdk::bitcoin::Network;

use bdk::descriptor;
use bdk::descriptor::IntoWalletDescriptor;
use bdk_bitcoind_rpc::bitcoincore_rpc;
use bdk_bitcoind_rpc::bitcoincore_rpc::Auth;
use bdk_bitcoind_rpc::bitcoincore_rpc::Client;
use bdk_bitcoind_rpc::bitcoincore_rpc::RpcApi;
use bdk_bitcoind_rpc::Emitter;

use bdk::keys::bip39::Language;
use bdk::keys::bip39::Mnemonic;
use bdk::keys::bip39::WordCount;
use bdk::keys::GeneratableKey;
use bdk::keys::GeneratedKey;

use bdk::miniscript::miniscript::Tap;
use bdk::wallet::AddressIndex;
use bdk::SignOptions;
use bdk::Wallet;

use std::str::FromStr;

const AMOUNT: u64 = 2_000_000;

fn main() -> anyhow::Result<()> {
// Make descriptors
let (recv_desc, chng_desc) = create_descriptors()?;

// Note: Every time we run, we need to ensure bitcoind has a clean datadir
let network = Network::Regtest;

// Create Bdk wallet
let mut wallet = Wallet::new_no_persist(&recv_desc, Some(&chng_desc), network)?;

let balance = wallet.get_balance().total();
println!("Wallet balance before sync: {balance} sat");

let bdk_addr = wallet.get_address(AddressIndex::New).address;
println!("Wallet new address: {bdk_addr}");

// Configure Core Rpc interface
let auth = Auth::UserPass("admin".to_string(), "password".to_string());

let client = bitcoincore_rpc::Client::new("http://127.0.0.1:18443/wallet/test", auth)?;

// Test connection
println!("Best block hash: {:?}", client.get_best_block_hash()?);

// Create Core wallet
client.create_wallet(
"test", /*disable_private_keys: */ None, /*blank: */ None,
/*passphrase: */ None, /*avoid_reuse: */ None,
)?;

// Get new Core address
let core_addr = client
.get_new_address(/*label: */ None, /*address_type: */ None)?
.assume_checked();

// Generate blocks
println!("Generating blocks...");
client.generate_to_address(101, &core_addr)?;

// Get Core balance
let core_bal = client
.get_balance(/*minconf: */ None, /*include_watch_only: */ None)?
.to_btc();
println!("Core balance: {core_bal} BTC");
println!();

// Configure chain source emitter
let start_height = 0u32;
let mut emitter = Emitter::new(&client, wallet.latest_checkpoint(), start_height);

// Sync Bdk wallet (#1)
sync(&mut wallet, &mut emitter)?;

// Fund Bdk wallet
println!("Sending 2M sat Core -> Bdk");
#[rustfmt::skip]
client.send_to_address(
&bdk_addr,
Amount::from_sat(AMOUNT),
/*comment: */ None,
/*comment_to: */ None,
/*subtract_fee: */ None,
/*replaceable: */ None,
/*confirmation_target: */ None,
/*estimate_mode: */ None,
)?;

// Query mempool tx
let unconfirmed = emitter.mempool()?;
wallet.batch_insert_relevant_unconfirmed(unconfirmed.iter().map(|(tx, time)| (tx, *time)));
wallet.commit()?;

// Show pending balance
let balance = wallet.get_balance().untrusted_pending;
println!("Wallet pending: {balance} sat");

// Confirm tx by generating blocks
client.generate_to_address(1, &core_addr)?;

// Sync (#2)
sync(&mut wallet, &mut emitter)?;

// Show confirmed
let balance = wallet.get_balance().confirmed;
assert_eq!(balance, AMOUNT);
println!("Confirmed! {balance} sat");
println!();

// Build tx with Bdk wallet
println!("Sending 1M sat Bdk -> Core");
let mut tx_builder = wallet.build_tx();
let amount = AMOUNT / 2;
tx_builder.add_recipient(core_addr.script_pubkey(), amount);

// Finish building tx and get a new Psbt
let mut psbt = tx_builder.finish()?;

// Sign it
wallet.sign(&mut psbt, SignOptions::default())?;

// Extract raw tx from signed psbt and broadcast
let tx = psbt.extract_tx();
let fee = wallet.calculate_fee(&tx)?;
let txid = client.send_raw_transaction(&tx)?;
println!("Txid: {txid}");

// Confirm tx
client.generate_to_address(1, &core_addr)?;

// Sync (#3)
sync(&mut wallet, &mut emitter)?;

// Display balances
let core_bal = client.get_balance(None, None)?.to_btc();
let bdk_bal = wallet.get_balance().total();
assert!(core_bal < 150.0);
assert!(bdk_bal < amount);
println!("Core balance: {core_bal} BTC");
println!("Wallet bal: {bdk_bal} sat ");
println!("Fee: {fee} sat");

Ok(())
}

/// Creates wallet `Descriptor`s from a master secret
fn create_descriptors() -> anyhow::Result<(String, String)> {
// Create new secp context
let secp = Secp256k1::new();

// Generate mnemonic
let _mnemonic: GeneratedKey<_, Tap> =
Mnemonic::generate((WordCount::Words12, Language::English)).expect("generate secret");
//println!("Mnemonic: {}", *_mnemonic);

// Provide our own mnemonic
let mnemonic = Mnemonic::from_str(
"print region fury craft unique forest humble famous river cargo job egg",
)?;
println!("Mnemonic: {}", mnemonic);

// Set an optional passphrase to unlock the mnemonic
let passphrase: Option<String> = None;
let mnemonic_with_passphrase = (mnemonic, passphrase);

// define external and internal derivation key path
let external_path = DerivationPath::from_str("m/86h/1h/0h/0")?;
let internal_path = DerivationPath::from_str("m/86h/1h/0h/1")?;

// generate external and internal descriptor from mnemonic
let (external_descriptor, ext_keymap) =
descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
.into_wallet_descriptor(&secp, Network::Regtest)?;

let (internal_descriptor, int_keymap) =
descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
.into_wallet_descriptor(&secp, Network::Regtest)?;

println!("External: {}", external_descriptor.to_string());
println!("Internal: {}", internal_descriptor.to_string());

Ok((
external_descriptor.to_string_with_secret(&ext_keymap),
internal_descriptor.to_string_with_secret(&int_keymap),
))
}

/// Calls `Emitter::next_block`, applying transactions we care about to the wallet's
/// transaction graph until all updates have been consumed.
fn sync(wallet: &mut Wallet, emitter: &mut Emitter<Client>) -> anyhow::Result<()> {
print!("Syncing... ");
while let Some((height, block)) = emitter.next_block()? {
wallet.apply_block_relevant(block, height)?;
}
wallet.commit()?;

println!("Ok");
Ok(())
}

0 comments on commit e009549

Please sign in to comment.