Skip to content

Commit

Permalink
done
Browse files Browse the repository at this point in the history
  • Loading branch information
darioAnongba committed Feb 13, 2025
1 parent 8550be9 commit 3a00e38
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 49 deletions.
7 changes: 6 additions & 1 deletion src/bitcoin/esplora_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use wasm_bindgen::prelude::wasm_bindgen;

use crate::{
result::JsResult,
types::{FeeEstimates, FullScanRequest, SyncRequest, Transaction, Update},
types::{FeeEstimates, FullScanRequest, SyncRequest, Transaction, Txid, Update},
};
use std::time::Duration;

Expand Down Expand Up @@ -56,6 +56,11 @@ impl EsploraClient {
let fee_estimates = self.client.get_fee_estimates().await?;
Ok(fee_estimates.into())
}

pub async fn get_tx(&self, txid: Txid) -> JsResult<Option<Transaction>> {
let tx = self.client.get_tx(&txid.into()).await?;
Ok(tx.map(Into::into))
}
}

#[derive(Clone)]
Expand Down
20 changes: 13 additions & 7 deletions src/bitcoin/tx_builder.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
use std::{cell::RefCell, rc::Rc};

use bdk_wallet::Wallet as BdkWallet;
use bitcoin::ScriptBuf;
use wasm_bindgen::prelude::wasm_bindgen;

use crate::{
bitcoin::Wallet,
result::JsResult,
types::{Address, FeeRate, Outpoint, Psbt, Recipient},
};

/// A transaction builder
/// A transaction builder.
///
/// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After
/// assigning it, you set options on it until finally calling [`finish`] to consume the builder and
/// generate the transaction.
///
/// Each option setting method on `TxBuilder` takes and returns a new builder so you can chain calls
#[wasm_bindgen]
pub struct TxBuilder {
wallet: Wallet,
wallet: Rc<RefCell<BdkWallet>>,
recipients: Vec<Recipient>,
unspendable: Vec<Outpoint>,
fee_rate: FeeRate,
Expand All @@ -22,8 +29,7 @@ pub struct TxBuilder {

#[wasm_bindgen]
impl TxBuilder {
#[wasm_bindgen(constructor)]
pub fn new(wallet: Wallet) -> TxBuilder {
pub(crate) fn new(wallet: Rc<RefCell<BdkWallet>>) -> TxBuilder {
TxBuilder {
wallet,
recipients: vec![],
Expand Down Expand Up @@ -108,8 +114,8 @@ impl TxBuilder {
///
/// Returns a new [`Psbt`] per [`BIP174`].
pub fn finish(self) -> JsResult<Psbt> {
let mut bdk_wallet: BdkWallet = self.wallet.into();
let mut builder = bdk_wallet.build_tx();
let mut wallet = self.wallet.borrow_mut();
let mut builder = wallet.build_tx();

builder
.set_recipients(self.recipients.into_iter().map(Into::into).collect())
Expand Down
67 changes: 34 additions & 33 deletions src/bitcoin/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{cell::RefCell, rc::Rc};

use bdk_wallet::{SignOptions, Wallet as BdkWallet};
use js_sys::Date;
use wasm_bindgen::{prelude::wasm_bindgen, JsError};
Expand All @@ -9,8 +11,10 @@ use crate::{
},
};

use super::TxBuilder;

#[wasm_bindgen]
pub struct Wallet(BdkWallet);
pub struct Wallet(Rc<RefCell<BdkWallet>>);

#[wasm_bindgen]
impl Wallet {
Expand All @@ -19,7 +23,7 @@ impl Wallet {
.network(network.into())
.create_wallet_no_persist()?;

Ok(Wallet(wallet))
Ok(Wallet(Rc::new(RefCell::new(wallet))))
}

pub fn load(
Expand All @@ -44,87 +48,84 @@ impl Wallet {
None => return Err(JsError::new("Failed to load wallet, check the changeset")),
};

Ok(Wallet(wallet))
Ok(Wallet(Rc::new(RefCell::new(wallet))))
}

pub fn start_full_scan(&self) -> FullScanRequest {
self.0.start_full_scan().build().into()
self.0.borrow().start_full_scan().build().into()
}

pub fn start_sync_with_revealed_spks(&self) -> SyncRequest {
self.0.start_sync_with_revealed_spks().build().into()
self.0.borrow().start_sync_with_revealed_spks().build().into()
}

pub fn apply_update(&mut self, update: Update) -> JsResult<()> {
pub fn apply_update(&self, update: Update) -> JsResult<()> {
self.apply_update_at(update, (Date::now() / 1000.0) as u64)
}

pub fn apply_update_at(&mut self, update: Update, seen_at: u64) -> JsResult<()> {
self.0.apply_update_at(update, seen_at)?;
pub fn apply_update_at(&self, update: Update, seen_at: u64) -> JsResult<()> {
self.0.borrow_mut().apply_update_at(update, seen_at)?;
Ok(())
}

pub fn network(&self) -> Network {
self.0.network().into()
self.0.borrow().network().into()
}

pub fn balance(&self) -> Balance {
self.0.balance().into()
self.0.borrow().balance().into()
}

pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo {
self.0.next_unused_address(keychain.into()).into()
pub fn next_unused_address(&self, keychain: KeychainKind) -> AddressInfo {
self.0.borrow_mut().next_unused_address(keychain.into()).into()
}

pub fn peek_address(&self, keychain: KeychainKind, index: u32) -> AddressInfo {
self.0.peek_address(keychain.into(), index).into()
self.0.borrow().peek_address(keychain.into(), index).into()
}

pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo {
self.0.reveal_next_address(keychain.into()).into()
pub fn reveal_next_address(&self, keychain: KeychainKind) -> AddressInfo {
self.0.borrow_mut().reveal_next_address(keychain.into()).into()
}

pub fn reveal_addresses_to(&mut self, keychain: KeychainKind, index: u32) -> Vec<AddressInfo> {
pub fn reveal_addresses_to(&self, keychain: KeychainKind, index: u32) -> Vec<AddressInfo> {
self.0
.borrow_mut()
.reveal_addresses_to(keychain.into(), index)
.map(Into::into)
.collect()
}

pub fn list_unused_addresses(&self, keychain: KeychainKind) -> Vec<AddressInfo> {
self.0.list_unused_addresses(keychain.into()).map(Into::into).collect()
self.0
.borrow()
.list_unused_addresses(keychain.into())
.map(Into::into)
.collect()
}

pub fn latest_checkpoint(&self) -> CheckPoint {
self.0.latest_checkpoint().into()
self.0.borrow().latest_checkpoint().into()
}

pub fn take_staged(&mut self) -> Option<ChangeSet> {
self.0.take_staged().map(Into::into)
pub fn take_staged(&self) -> Option<ChangeSet> {
self.0.borrow_mut().take_staged().map(Into::into)
}

pub fn public_descriptor(&self, keychain: KeychainKind) -> String {
self.0.public_descriptor(keychain.into()).to_string()
self.0.borrow().public_descriptor(keychain.into()).to_string()
}

pub fn sign(&self, psbt: &mut Psbt) -> JsResult<bool> {
let result = self.0.sign(psbt, SignOptions::default())?;
let result = self.0.borrow().sign(psbt, SignOptions::default())?;
Ok(result)
}

pub fn derivation_index(&self, keychain: KeychainKind) -> Option<u32> {
self.0.derivation_index(keychain.into())
self.0.borrow().derivation_index(keychain.into())
}
}

impl From<BdkWallet> for Wallet {
fn from(inner: BdkWallet) -> Self {
Wallet(inner)
}
}

impl From<Wallet> for BdkWallet {
fn from(wallet: Wallet) -> Self {
wallet.0
pub fn build_tx(&self) -> TxBuilder {
TxBuilder::new(self.0.clone())
}
}
21 changes: 15 additions & 6 deletions tests/esplora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
extern crate wasm_bindgen_test;

use bitcoindevkit::{
bitcoin::{EsploraClient, TxBuilder, Wallet},
bitcoin::{EsploraClient, Wallet},
set_panic_hook,
types::{Address, Amount, FeeRate, KeychainKind, Network, Recipient},
};
Expand All @@ -29,7 +29,7 @@ async fn test_esplora_client() {
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 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();
Expand Down Expand Up @@ -67,11 +67,14 @@ async fn test_esplora_client() {
.expect("load");
assert_eq!(loaded_wallet.balance(), wallet.balance());

let initial_derivation_index = wallet.derivation_index(KeychainKind::Internal).unwrap();

let fees = blockchain_client.get_fee_estimates().await.expect("get_fee_estimates");
let recipient = Address::new(RECIPIENT_ADDRESS, NETWORK).expect("recipient_address");
let amount = Amount::from_sat(SEND_ADMOUNT);
let fee_rate = fees.get(CONFIRMATION_TARGET).expect("fee_estimation");
let mut psbt = TxBuilder::new(wallet)
let mut psbt = loaded_wallet
.build_tx()
.fee_rate(FeeRate::new(fee_rate as u64))
.add_recipient(Recipient::new(recipient, amount))
.finish()
Expand All @@ -86,7 +89,12 @@ async fn test_esplora_client() {
let tx = psbt.extract_tx().expect("extract_tx");
blockchain_client.broadcast(&tx).await.expect("broadcast");

web_sys::console::log_1(&tx.compute_txid().to_string().into());
// Assert that we are aware of newly created addresses that were revealed during PSBT creation
let current_derivation_index = loaded_wallet.derivation_index(KeychainKind::Internal).unwrap();
assert!(initial_derivation_index < current_derivation_index);

let fetched_tx = blockchain_client.get_tx(tx.compute_txid()).await.expect("get_tx");
assert!(fetched_tx.is_some())
}

#[wasm_bindgen_test]
Expand All @@ -96,7 +104,7 @@ async fn test_drain() {
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 wallet = Wallet::create(NETWORK, external_desc.into(), internal_desc.into()).expect("wallet");
let mut blockchain_client = EsploraClient::new(ESPLORA_URL).expect("esplora_client");

let full_scan_request = wallet.start_full_scan();
Expand All @@ -108,7 +116,8 @@ async fn test_drain() {

// No need to test actual values as we are just wrapping BDK and assume the underlying package is computing fees properly
let recipient = Address::new(RECIPIENT_ADDRESS, NETWORK).expect("recipient_address");
let psbt = TxBuilder::new(wallet)
let psbt = wallet
.build_tx()
.drain_wallet()
.fee_rate(FeeRate::new(FEE_RATE))
.drain_to(recipient)
Expand Down
4 changes: 2 additions & 2 deletions tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.external(), descriptors.internal()).expect("wallet");
let wallet = Wallet::create(NETWORK, descriptors.external(), descriptors.internal()).expect("wallet");

let balance = wallet.balance();
assert_eq!(balance.total().to_sat(), 0);
Expand All @@ -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.external(), descriptors.internal()).expect("wallet");
let wallet = Wallet::create(NETWORK, descriptors.external(), descriptors.internal()).expect("wallet");

let mut changeset = wallet.take_staged().expect("initial_changeset");
assert!(!changeset.is_empty());
Expand Down

0 comments on commit 3a00e38

Please sign in to comment.