forked from bitcoindevkit/bdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example bitcoind rpc wallet using regtest
- Loading branch information
1 parent
46d39be
commit e009549
Showing
3 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |