Skip to content

Commit

Permalink
feat: send transactions (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
darioAnongba authored Jan 21, 2025
1 parent 7b5403e commit 6dba9a7
Show file tree
Hide file tree
Showing 12 changed files with 320 additions and 62 deletions.
1 change: 0 additions & 1 deletion .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ jobs:
matrix:
rust:
- stable
- 1.73.0 # MSRV
features:
- all
- debug,default
Expand Down
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::{FullScanRequest, SyncRequest, Update},
types::{FullScanRequest, SyncRequest, Transaction, Update},
};
use std::time::Duration;

Expand Down Expand Up @@ -46,6 +46,11 @@ impl EsploraClient {
let result = self.client.sync(request, parallel_requests).await?;
Ok(result.into())
}

pub async fn broadcast(&self, transaction: &Transaction) -> JsResult<()> {
self.client.broadcast(transaction).await?;
Ok(())
}
}

#[derive(Clone)]
Expand Down
42 changes: 36 additions & 6 deletions src/bitcoin/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use bdk_wallet::Wallet as BdkWallet;
use bdk_wallet::{SignOptions, Wallet as BdkWallet};
use js_sys::Date;
use serde_wasm_bindgen::to_value;
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};

use crate::{
result::JsResult,
types::{
AddressInfo, Balance, ChangeSet, CheckPoint, DescriptorPair, FullScanRequest, KeychainKind, Network,
AddressInfo, Balance, ChangeSet, CheckPoint, FeeRate, FullScanRequest, KeychainKind, Network, Psbt, Recipient,
SyncRequest, Update,
},
};
Expand All @@ -16,16 +16,30 @@ pub struct Wallet(BdkWallet);

#[wasm_bindgen]
impl Wallet {
pub fn create(network: Network, descriptors: DescriptorPair) -> JsResult<Wallet> {
let wallet = BdkWallet::create(descriptors.external(), descriptors.internal())
pub fn create(network: Network, external_descriptor: String, internal_descriptor: String) -> JsResult<Wallet> {
let wallet = BdkWallet::create(external_descriptor, internal_descriptor)
.network(network.into())
.create_wallet_no_persist()?;

Ok(Wallet(wallet))
}

pub fn load(changeset: ChangeSet) -> JsResult<Wallet> {
let wallet_opt = BdkWallet::load().load_wallet_no_persist(changeset.into())?;
pub fn load(
changeset: ChangeSet,
external_descriptor: Option<String>,
internal_descriptor: Option<String>,
) -> JsResult<Wallet> {
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,
Expand Down Expand Up @@ -108,4 +122,20 @@ impl Wallet {
pub fn public_descriptor(&self, keychain: KeychainKind) -> String {
self.0.public_descriptor(keychain.into()).to_string()
}

pub fn build_tx(&mut self, fee_rate: FeeRate, recipients: Vec<Recipient>) -> JsResult<Psbt> {
let mut builder = self.0.build_tx();

builder
.set_recipients(recipients.into_iter().map(Into::into).collect())
.fee_rate(fee_rate.into());

let psbt = builder.finish()?;
Ok(psbt.into())
}

pub fn sign(&self, psbt: &mut Psbt) -> JsResult<bool> {
let result = self.0.sign(psbt, SignOptions::default())?;
Ok(result)
}
}
42 changes: 39 additions & 3 deletions src/types/address.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::ops::Deref;
use std::{ops::Deref, str::FromStr};

use bdk_wallet::{bitcoin::AddressType as BdkAddressType, AddressInfo as BdkAddressInfo};
use bitcoin::Address as BdkAddress;
use wasm_bindgen::prelude::wasm_bindgen;

use super::KeychainKind;
use crate::result::JsResult;

use super::{KeychainKind, Network};

/// A derived address and the index it was found at.
#[wasm_bindgen]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct AddressInfo(BdkAddressInfo);

#[wasm_bindgen]
Expand Down Expand Up @@ -55,6 +58,39 @@ impl From<BdkAddressInfo> for AddressInfo {
}
}

/// An owned, growable script.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Address(BdkAddress);

impl Deref for Address {
type Target = BdkAddress;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[wasm_bindgen]
impl Address {
pub fn new(address_str: &str, network: Network) -> JsResult<Self> {
let address = BdkAddress::from_str(address_str)?.require_network(network.into())?;
Ok(Address(address))
}
}

impl From<BdkAddress> for Address {
fn from(inner: BdkAddress) -> Self {
Address(inner)
}
}

impl From<Address> for BdkAddress {
fn from(address: Address) -> Self {
address.0
}
}

/// The different types of addresses.
#[wasm_bindgen]
#[derive(Debug)]
Expand Down
19 changes: 18 additions & 1 deletion src/types/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@ use std::ops::Deref;
use bitcoin::{Amount as BdkAmount, Denomination as BdkDenomination};
use wasm_bindgen::prelude::wasm_bindgen;

use crate::result::JsResult;

/// Amount
///
/// The [Amount] type can be used to express Bitcoin amounts that support
/// arithmetic and conversion to various denominations.
#[wasm_bindgen]
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Amount(BdkAmount);

#[wasm_bindgen]
impl Amount {
pub fn from_btc(btc: f64) -> JsResult<Self> {
let amount = BdkAmount::from_btc(btc)?;
Ok(Amount(amount))
}

pub fn from_sat(satoshi: u64) -> Self {
Amount(BdkAmount::from_sat(satoshi))
}

/// Gets the number of satoshis in this [`Amount`].
pub fn to_sat(&self) -> u64 {
self.0.to_sat()
Expand Down Expand Up @@ -47,6 +58,12 @@ impl From<BdkAmount> for Amount {
}
}

impl From<Amount> for BdkAmount {
fn from(amount: Amount) -> Self {
amount.0
}
}

/// A set of denominations in which amounts can be expressed.
#[wasm_bindgen]
#[derive(Debug)]
Expand Down
29 changes: 0 additions & 29 deletions src/types/descriptor.rs

This file was deleted.

6 changes: 4 additions & 2 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ mod block;
mod chain;
mod changeset;
mod checkpoint;
mod descriptor;
mod keychain;
mod network;
mod psbt;
mod slip10;
mod transaction;

pub use address::*;
pub use amount::*;
Expand All @@ -17,7 +18,8 @@ pub use block::*;
pub use chain::*;
pub use changeset::*;
pub use checkpoint::*;
pub use descriptor::*;
pub use keychain::*;
pub use network::*;
pub use psbt::*;
pub use slip10::*;
pub use transaction::*;
115 changes: 115 additions & 0 deletions src/types/psbt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::ops::{Deref, DerefMut};

use bitcoin::{Amount as BdkAmount, FeeRate as BdkFeeRate, Psbt as BdkPsbt, ScriptBuf as BdkScriptBuf};
use wasm_bindgen::prelude::wasm_bindgen;

use crate::result::JsResult;

use super::{Address, Amount, Transaction};

/// A Partially Signed Transaction.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Psbt(BdkPsbt);

impl Deref for Psbt {
type Target = BdkPsbt;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Psbt {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

#[wasm_bindgen]
impl Psbt {
pub fn extract_tx(self) -> JsResult<Transaction> {
let tx = self.0.extract_tx()?;
Ok(tx.into())
}
}

impl From<BdkPsbt> for Psbt {
fn from(inner: BdkPsbt) -> Self {
Psbt(inner)
}
}

impl From<Psbt> for BdkPsbt {
fn from(psbt: Psbt) -> Self {
psbt.0
}
}

/// A Transaction recipient
#[wasm_bindgen]
#[derive(Debug)]
pub struct Recipient {
address: Address,
amount: Amount,
}

#[wasm_bindgen]
impl Recipient {
#[wasm_bindgen(constructor)]
pub fn new(address: Address, amount: Amount) -> Self {
Recipient { address, amount }
}

#[wasm_bindgen(getter)]
pub fn address(&self) -> Address {
self.address.clone()
}

#[wasm_bindgen(getter)]
pub fn amount(&self) -> Amount {
self.amount.clone()
}
}

impl From<Recipient> for (BdkScriptBuf, BdkAmount) {
fn from(r: Recipient) -> Self {
(r.address().script_pubkey(), r.amount().into())
}
}

/// Represents fee rate.
///
/// This is an integer newtype representing fee rate in `sat/kwu`. It provides protection against mixing
/// up the types as well as basic formatting features.
#[wasm_bindgen]
#[derive(Debug)]
pub struct FeeRate(BdkFeeRate);

impl Deref for FeeRate {
type Target = BdkFeeRate;

fn deref(&self) -> &Self::Target {
&self.0
}
}

#[wasm_bindgen]
impl FeeRate {
#[wasm_bindgen(constructor)]
pub fn new(sat_vb: u64) -> Self {
FeeRate(BdkFeeRate::from_sat_per_vb_unchecked(sat_vb))
}
}

impl From<BdkFeeRate> for FeeRate {
fn from(inner: BdkFeeRate) -> Self {
FeeRate(inner)
}
}

impl From<FeeRate> for BdkFeeRate {
fn from(fee_rate: FeeRate) -> Self {
fee_rate.0
}
}
Loading

0 comments on commit 6dba9a7

Please sign in to comment.