diff --git a/src/bitcoin/wallet.rs b/src/bitcoin/wallet.rs index 51165f7..0c340c5 100644 --- a/src/bitcoin/wallet.rs +++ b/src/bitcoin/wallet.rs @@ -7,8 +7,8 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; use crate::{ result::JsResult, types::{ - AddressInfo, Balance, ChangeSet, CheckPoint, DescriptorPair, FeeRate, FullScanRequest, KeychainKind, Network, - Psbt, Recipient, SyncRequest, Update, + AddressInfo, Balance, ChangeSet, CheckPoint, FeeRate, FullScanRequest, KeychainKind, Network, Psbt, Recipient, + SyncRequest, Update, }, }; @@ -17,16 +17,30 @@ pub struct Wallet(BdkWallet); #[wasm_bindgen] impl Wallet { - pub fn create(network: Network, descriptors: DescriptorPair) -> JsResult { - let wallet = BdkWallet::create(descriptors.external(), descriptors.internal()) + pub fn create(network: Network, external_descriptor: String, internal_descriptor: String) -> JsResult { + let wallet = BdkWallet::create(external_descriptor, internal_descriptor) .network(network.into()) .create_wallet_no_persist()?; Ok(Wallet(wallet)) } - pub fn load(changeset: ChangeSet) -> JsResult { - let wallet_opt = BdkWallet::load().load_wallet_no_persist(changeset.into())?; + pub fn load( + changeset: ChangeSet, + external_descriptor: Option, + internal_descriptor: Option, + ) -> JsResult { + let mut builder = BdkWallet::load(); + + if external_descriptor.is_some() { + builder = builder.descriptor(KeychainKind::External.into(), external_descriptor); + } + + if internal_descriptor.is_some() { + builder = builder.descriptor(KeychainKind::Internal.into(), internal_descriptor); + } + + let wallet_opt = builder.extract_keys().load_wallet_no_persist(changeset.into())?; let wallet = match wallet_opt { Some(wallet) => wallet, diff --git a/src/types/descriptor.rs b/src/types/descriptor.rs deleted file mode 100644 index 05f68b1..0000000 --- a/src/types/descriptor.rs +++ /dev/null @@ -1,29 +0,0 @@ -use wasm_bindgen::prelude::wasm_bindgen; - -/// Pair of descriptors for external and internal keychains -#[wasm_bindgen] -#[derive(Debug)] -pub struct DescriptorPair { - /// External descriptor - external: String, - /// Internal descriptor - internal: String, -} - -#[wasm_bindgen] -impl DescriptorPair { - #[wasm_bindgen(constructor)] - pub fn new(external: String, internal: String) -> Self { - DescriptorPair { external, internal } - } - - #[wasm_bindgen(getter)] - pub fn internal(&self) -> String { - self.internal.clone() - } - - #[wasm_bindgen(getter)] - pub fn external(&self) -> String { - self.external.clone() - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs index c38c1bb..730f3be 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,7 +5,6 @@ mod block; mod chain; mod changeset; mod checkpoint; -mod descriptor; mod keychain; mod network; mod psbt; @@ -19,7 +18,6 @@ pub use block::*; pub use chain::*; pub use changeset::*; pub use checkpoint::*; -pub use descriptor::*; pub use keychain::*; pub use network::*; pub use psbt::*; diff --git a/src/utils/descriptor.rs b/src/utils/descriptor.rs index 8409f38..e0c35fb 100644 --- a/src/utils/descriptor.rs +++ b/src/utils/descriptor.rs @@ -4,11 +4,39 @@ use bdk_wallet::keys::ExtendedKey; use bitcoin::bip32::{Fingerprint, Xpriv, Xpub}; use serde_wasm_bindgen::from_value; -use crate::types::{AddressType, DescriptorPair, Network, SLIP10Node}; +use crate::types::{AddressType, Network, SLIP10Node}; use wasm_bindgen::prelude::{wasm_bindgen, JsError, JsValue}; use super::result::JsResult; +/// Pair of descriptors for external and internal keychains +#[wasm_bindgen] +#[derive(Debug, Clone)] +pub struct DescriptorPair { + /// External descriptor + external: String, + /// Internal descriptor + internal: String, +} + +#[wasm_bindgen] +impl DescriptorPair { + #[wasm_bindgen(constructor)] + pub fn new(external: String, internal: String) -> Self { + DescriptorPair { external, internal } + } + + #[wasm_bindgen(getter)] + pub fn internal(&self) -> String { + self.internal.clone() + } + + #[wasm_bindgen(getter)] + pub fn external(&self) -> String { + self.external.clone() + } +} + #[wasm_bindgen] pub fn seed_to_descriptor(seed: &[u8], network: Network, address_type: AddressType) -> JsResult { let (external, internal) = crate::bitcoin::seed_to_descriptor(seed, network.into(), address_type.into()) diff --git a/tests/esplora.rs b/tests/esplora.rs index 4191cac..0a8694a 100644 --- a/tests/esplora.rs +++ b/tests/esplora.rs @@ -4,11 +4,10 @@ extern crate wasm_bindgen_test; -use bdk_wallet::bip39::Mnemonic; use bitcoindevkit::{ bitcoin::{EsploraClient, Wallet}, - seed_to_descriptor, set_panic_hook, - types::{Address, AddressType, Amount, DescriptorPair, FeeRate, KeychainKind, Network, Recipient}, + set_panic_hook, + types::{Address, Amount, FeeRate, KeychainKind, Network, Recipient}, }; use wasm_bindgen_test::*; @@ -18,17 +17,18 @@ const ESPLORA_URL: &str = "https://mutinynet.com/api"; const STOP_GAP: usize = 5; const PARALLEL_REQUESTS: usize = 1; const NETWORK: Network = Network::Signet; -const EXTERNAL_DESC: &str = "wpkh([aafa6322/84'/1'/0']tpubDCfvzhCuifJtWDVdrBcPvZU7U5uyixL7QULk8hXA7KjqiNnry9Te1nwm7yStqenPCQhy5MwzxKkLBD2GmKNgvMYqXgo53iYqQ7Vu4vQbN2N/0/*)#mlua264t"; -const INTERNAL_DESC: &str = "wpkh([aafa6322/84'/1'/0']tpubDCfvzhCuifJtWDVdrBcPvZU7U5uyixL7QULk8hXA7KjqiNnry9Te1nwm7yStqenPCQhy5MwzxKkLBD2GmKNgvMYqXgo53iYqQ7Vu4vQbN2N/1/*)#2teuh09n"; +const SEND_ADMOUNT: u64 = 1000; +const FEE_RATE: u64 = 2; const RECIPIENT_ADDRESS: &str = "tb1qd28npep0s8frcm3y7dxqajkcy2m40eysplyr9v"; -const SEND_AMOUNT: u64 = 10000; #[wasm_bindgen_test] async fn test_esplora_client() { set_panic_hook(); - let mut wallet = - Wallet::create(NETWORK, DescriptorPair::new(EXTERNAL_DESC.into(), INTERNAL_DESC.into())).expect("wallet"); + let external_desc = "wpkh(tprv8ZgxMBicQKsPf6vydw7ixvsLKY79hmeXujBkGCNCApyft92yVYng2y28JpFZcneBYTTHycWSRpokhHE25GfHPBxnW5GpSm2dMWzEi9xxEyU/84'/1'/0'/0/*)#uel0vg9p"; + let internal_desc = "wpkh(tprv8ZgxMBicQKsPf6vydw7ixvsLKY79hmeXujBkGCNCApyft92yVYng2y28JpFZcneBYTTHycWSRpokhHE25GfHPBxnW5GpSm2dMWzEi9xxEyU/84'/1'/0'/1/*)#dd6w3a4e"; + + let mut wallet = Wallet::create(NETWORK, external_desc.into(), internal_desc.into()).expect("wallet"); let mut blockchain_client = EsploraClient::new(ESPLORA_URL).expect("esplora_client"); let block_height = wallet.latest_checkpoint().height(); @@ -54,44 +54,30 @@ async fn test_esplora_client() { wallet.apply_update(update).expect("full_scan apply_update"); let balance = wallet.balance(); - assert!(balance.total().to_sat() > 0); - - let loaded_wallet = Wallet::load(wallet.take_staged().unwrap()).expect("load"); + web_sys::console::log_2(&"balance: ".into(), &balance.total().to_sat().into()); + assert!(balance.trusted_spendable().to_sat() > SEND_ADMOUNT); + + // Important to test that we can load the wallet from a changeset with the signing descriptors and be able to sign a transaction + // as the changeset does not contain the private signing information. + let mut loaded_wallet = Wallet::load( + wallet.take_staged().unwrap(), + Some(external_desc.into()), + Some(internal_desc.into()), + ) + .expect("load"); assert_eq!(loaded_wallet.balance(), wallet.balance()); -} - -#[wasm_bindgen_test] -async fn test_send() { - set_panic_hook(); - - let seed = Mnemonic::parse("journey embrace permit coil indoor stereo welcome maid movie easy clock spider tent slush bright luxury awake waste legal modify awkward answer acid goose") - .unwrap() - .to_seed(""); - let descriptors = seed_to_descriptor(&seed, NETWORK, AddressType::P2wpkh).expect("seed_to_descriptor"); - let mut wallet = Wallet::create(NETWORK, descriptors).expect("wallet"); - let mut blockchain_client = EsploraClient::new(ESPLORA_URL).expect("esplora_client"); - - let full_scan_request = wallet.start_full_scan(); - let update = blockchain_client - .full_scan(full_scan_request, STOP_GAP, PARALLEL_REQUESTS) - .await - .expect("full_scan"); - wallet.apply_update(update).expect("full_scan apply_update"); - - let balance = wallet.balance(); - assert!(balance.total().to_sat() > SEND_AMOUNT); - web_sys::console::log_2(&"Balance: ".into(), &balance.total().to_btc().into()); let recipient = Address::new(RECIPIENT_ADDRESS, NETWORK).expect("recipient_address"); - let amount = Amount::from_sat(SEND_AMOUNT); - let mut psbt = wallet - .build_tx(FeeRate::new(2), vec![Recipient::new(recipient, amount)]) + let amount = Amount::from_sat(SEND_ADMOUNT); + let mut psbt = loaded_wallet + .build_tx(FeeRate::new(FEE_RATE), vec![Recipient::new(recipient, amount)]) .expect("build_tx"); - assert!(wallet.sign(&mut psbt).expect("sign")); + let finalized = loaded_wallet.sign(&mut psbt).expect("sign"); + assert!(finalized); let tx = psbt.extract_tx().expect("extract_tx"); blockchain_client.broadcast(&tx).await.expect("broadcast"); - web_sys::console::log_1(&tx.compute_txid().into()); + web_sys::console::log_2(&"txid: ".into(), &tx.compute_txid().into()); } diff --git a/tests/wallet.rs b/tests/wallet.rs index 0ab9c52..1841331 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -24,7 +24,7 @@ async fn test_wallet() { let seed = Mnemonic::parse(MNEMONIC).unwrap().to_seed(""); let descriptors = seed_to_descriptor(&seed, NETWORK, ADDRESS_TYPE).expect("seed_to_descriptor"); - let mut wallet = Wallet::create(NETWORK, descriptors).expect("wallet"); + let mut wallet = Wallet::create(NETWORK, descriptors.external(), descriptors.internal()).expect("wallet"); let balance = wallet.balance(); assert_eq!(balance.total().to_sat(), 0); @@ -50,7 +50,7 @@ async fn test_changeset() { let seed = Mnemonic::parse(MNEMONIC).unwrap().to_seed(""); let descriptors = seed_to_descriptor(&seed, NETWORK, ADDRESS_TYPE).expect("seed_to_descriptor"); - let mut wallet = Wallet::create(NETWORK, descriptors).expect("wallet"); + let mut wallet = Wallet::create(NETWORK, descriptors.external(), descriptors.internal()).expect("wallet"); let mut changeset = wallet.take_staged().expect("initial_changeset"); assert!(!changeset.is_empty());