diff --git a/side-node/src/clients/btc_electrum_client.rs b/side-node/src/clients/btc_electrum_client.rs index c18541a..ddb1271 100644 --- a/side-node/src/clients/btc_electrum_client.rs +++ b/side-node/src/clients/btc_electrum_client.rs @@ -1,23 +1,22 @@ use crate::{keys, utils}; - -use bdk::bitcoin::bip32::ExtendedPrivKey; use bdk::bitcoin::psbt::PartiallySignedTransaction; use bdk::bitcoin::Network; use bdk::database::MemoryDatabase; +use bdk::keys::ExtendedKey; use bdk::template::Bip84; use bdk::wallet::AddressIndex::{self, New}; use bdk::wallet::AddressInfo; use bdk::{blockchain::ElectrumBlockchain, electrum_client, SyncOptions}; -use bdk::{FeeRate, SignOptions, TransactionDetails, Wallet}; +use bdk::{FeeRate, KeychainKind, SignOptions, TransactionDetails, Wallet}; pub async fn run() -> Result<(), anyhow::Error> { let dave = utils::home(&"dave".to_string()); let sammy = utils::home(&"sammy".to_string()); - let dave_keys = keys::bitcoin::load_from_file(&dave).unwrap(); - let sammy_keys = keys::bitcoin::load_from_file(&sammy).unwrap(); + let dave_key = keys::bitcoin::load_from_file(&dave).unwrap(); + let sammy_key = keys::bitcoin::load_from_file(&sammy).unwrap(); - let dave_wallet = create_wallet(dave_keys)?; - let sammy_wallet = create_wallet(sammy_keys)?; + let dave_wallet = create_wallet(dave_key)?; + let sammy_wallet = create_wallet(sammy_key)?; let dave_address = dave_wallet.get_address(AddressIndex::Peek(0))?.to_string(); let sammy_address = sammy_wallet.get_address(AddressIndex::Peek(0))?.to_string(); @@ -50,10 +49,13 @@ pub async fn run() -> Result<(), anyhow::Error> { } /// Create a BDK wallet using BIP 84 descriptor ("m/84h/1h/0h/0" and "m/84h/1h/0h/1") -pub fn create_wallet( - keys: (Bip84, Option>), -) -> anyhow::Result> { - let (external_descriptor, internal_descriptor) = keys; +pub fn create_wallet(xkey: ExtendedKey) -> anyhow::Result> { + let xprv = xkey + .into_xprv(Network::Testnet) + .expect("couldn't turn xkey into xprv"); + + let external_descriptor = Bip84(xprv, KeychainKind::External); + let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal)); let wallet = Wallet::new( external_descriptor, diff --git a/side-node/src/clients/btc_esplora_client.rs b/side-node/src/clients/btc_esplora_client.rs index 880231b..b14aa12 100644 --- a/side-node/src/clients/btc_esplora_client.rs +++ b/side-node/src/clients/btc_esplora_client.rs @@ -1,32 +1,56 @@ -use std::{collections::BTreeSet, io::Write, str::FromStr}; +use std::{collections::BTreeSet, fs, io::Write, str::FromStr}; +use bdk::keys::bip39::Mnemonic; use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ bitcoin::{Address, Amount, Network, Script}, + keys::{DerivableKey, ExtendedKey}, KeychainKind, SignOptions, Wallet, }; use bdk_sqlite::{rusqlite::Connection, Store}; +use crate::{keys, utils}; + const SEND_AMOUNT: Amount = Amount::from_sat(5000); const STOP_GAP: usize = 50; const PARALLEL_REQUESTS: usize = 5; /// Demonstrates the use of bdk with the Esplora client. pub async fn run() -> Result<(), anyhow::Error> { + let dave_dir = utils::home(&"dave".to_string()); + + let mnemonic_path = crate::utils::side_paths(dave_dir).1; // TODO: this tuple stinks + let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); + println!("Creating wallet from mnemonic: {mnemonic_words}"); + let mnemonic = Mnemonic::parse(mnemonic_words).unwrap(); + + // Generate the extended key + let xkey: ExtendedKey = mnemonic + .into_extended_key() + .expect("couldn't turn mnemonic into xkey"); + + let xprv = xkey + .into_xprv(Network::Signet) + .expect("problem converting xkey to xprv") + .to_string(); + + println!("Setting up esplora database"); + let db_path = "/tmp/bdk-esplora-async-example.sqlite"; let conn = Connection::open(db_path)?; let mut db = Store::new(conn)?; - let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; - let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - let changeset = db.read()?; + let external_descriptor = format!("wpkh({xprv}/84'/1'/0'/0/*)"); + let internal_descriptor = format!("wpkh({xprv}/84'/1'/0'/1/*)"); + let changeset = db.read().expect("couldn't read esplora database"); let mut wallet = Wallet::new_or_load( - external_descriptor, - internal_descriptor, + &external_descriptor, + &internal_descriptor, changeset, Network::Signet, - )?; + ) + .expect("problem setting up wallet"); let address = wallet.next_unused_address(KeychainKind::External); if let Some(changeset) = wallet.take_staged() { diff --git a/side-node/src/keys/bitcoin.rs b/side-node/src/keys/bitcoin.rs index 8b33719..920e942 100644 --- a/side-node/src/keys/bitcoin.rs +++ b/side-node/src/keys/bitcoin.rs @@ -1,12 +1,9 @@ use bdk::{ - bitcoin::{bip32::ExtendedPrivKey, Network}, keys::{ bip39::{Language, Mnemonic, WordCount}, DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey, }, miniscript, - template::Bip84, - KeychainKind, }; use std::{ @@ -21,6 +18,9 @@ pub fn make_mnemonic() -> String { mnemonic.to_string() } +/// Write the mnemonic to a file in the node's side directory +/// +/// TODO: obviously spitting the mnemonic out to the console is not for production pub(crate) fn write(mnemonic_path: &PathBuf) -> Result<(), std::io::Error> { let mnemonic = make_mnemonic(); let mut file = File::create(mnemonic_path)?; @@ -30,27 +30,19 @@ pub(crate) fn write(mnemonic_path: &PathBuf) -> Result<(), std::io::Error> { Ok(()) } -/// Creates a Signet Bitcoin descriptor wallet from a mnemonic -pub fn get( - mnemonic_words: String, -) -> anyhow::Result<(Bip84, Option>)> { +/// Creates Signet Bitcoin descriptors from a mnemonic +pub fn get(mnemonic_words: String) -> anyhow::Result { let mnemonic = Mnemonic::parse(mnemonic_words).unwrap(); // Generate the extended key - let xkey: ExtendedKey = mnemonic.into_extended_key()?; + let xkey: ExtendedKey = mnemonic + .into_extended_key() + .expect("couldn't turn mnemonic into xkey"); - // Get private key from the extended key - let xprv = xkey.into_xprv(Network::Signet).unwrap(); - - let external_descriptor = Bip84(xprv, KeychainKind::External); - let internal_descriptor = Some(Bip84(xprv, KeychainKind::Internal)); - - Ok((external_descriptor, internal_descriptor)) + Ok(xkey) } -pub(crate) fn load_from_file( - side_dir: &PathBuf, -) -> anyhow::Result<(Bip84, Option>)> { +pub(crate) fn load_from_file(side_dir: &PathBuf) -> anyhow::Result { let mnemonic_path = crate::utils::side_paths(side_dir.clone()).1; // TODO: this tuple stinks let mnemonic_words = fs::read_to_string(mnemonic_path).expect("couldn't read bitcoin key file"); println!("Creating wallet from mnemonic: {mnemonic_words}"); diff --git a/side-node/src/lib.rs b/side-node/src/lib.rs index dba3920..f8ac9f2 100644 --- a/side-node/src/lib.rs +++ b/side-node/src/lib.rs @@ -31,7 +31,7 @@ pub async fn run() { node.start().await; } Some(Commands::Btc {}) => { - let _ = clients::btc_electrum_client::run().await; + let _ = clients::btc_esplora_client::run().await; } None => println!("No command provided. Exiting. See --help for more information."), }