From 3789c1dcd69827247d7a6afaf1bf36506fa311aa Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 16 Dec 2023 19:24:02 -0600 Subject: [PATCH] feat: add calculate_fee and calculate_fee_rate on wallet --- .../org/bitcoindevkit/LiveWalletTest.kt | 8 +- bdk-ffi/src/bdk.udl | 19 ++++- bdk-ffi/src/bitcoin.rs | 21 ++++- bdk-ffi/src/error.rs | 82 +++++++++++++++++++ bdk-ffi/src/esplora.rs | 2 +- bdk-ffi/src/lib.rs | 3 + bdk-ffi/src/types.rs | 14 ++++ bdk-ffi/src/wallet.rs | 26 ++++-- .../org/bitcoindevkit/LiveWalletTest.kt | 8 +- bdk-python/tests/test_live_wallet.py | 5 ++ .../BitcoinDevKitTests/LiveWalletTests.swift | 5 ++ 11 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 bdk-ffi/src/error.rs diff --git a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt index b9a7d54a..093dde52 100644 --- a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt +++ b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt @@ -59,8 +59,14 @@ class LiveWalletTest { assertTrue(walletDidSign) val tx: Transaction = psbt.extractTx() - println("Txid is: ${tx.txid()}") + + val txFee: ULong = wallet.calculateFee(tx) + println("Tx fee is: ${txFee}") + + val feeRate: FeeRate = wallet.calculateFeeRate(tx) + println("Tx fee rate is: ${feeRate.asSatPerVb()} sat/vB") + esploraClient.broadcast(tx) } } diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index e86b4651..fe41450d 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -82,6 +82,17 @@ enum BdkError { "Psbt", }; +[Error] +interface CalculateFeeError { + MissingTxOut(sequence out_points); + NegativeFee(i64 fee); +}; + +interface FeeRate { + f32 as_sat_per_vb(); + f32 sat_per_kwu(); +}; + enum ChangeSpendPolicy { "ChangeAllowed", "OnlyChange", @@ -111,6 +122,12 @@ interface Wallet { SentAndReceivedValues sent_and_received([ByRef] Transaction tx); sequence transactions(); + + [Throws=CalculateFeeError] + u64 calculate_fee([ByRef] Transaction tx); + + [Throws=CalculateFeeError] + FeeRate calculate_fee_rate([ByRef] Transaction tx); }; interface Update {}; @@ -350,4 +367,4 @@ interface PartiallySignedTransaction { dictionary OutPoint { string txid; u32 vout; -}; +}; \ No newline at end of file diff --git a/bdk-ffi/src/bitcoin.rs b/bdk-ffi/src/bitcoin.rs index f95fa270..2a249125 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -213,9 +213,15 @@ impl From for Transaction { } } -impl From for BdkTransaction { - fn from(tx: Transaction) -> Self { - tx.inner +impl From<&BdkTransaction> for Transaction { + fn from(tx: &BdkTransaction) -> Self { + Transaction { inner: tx.clone() } + } +} + +impl From<&Transaction> for BdkTransaction { + fn from(tx: &Transaction) -> Self { + tx.inner.clone() } } @@ -310,6 +316,15 @@ impl From<&OutPoint> for BdkOutPoint { } } +impl From<&BdkOutPoint> for OutPoint { + fn from(outpoint: &BdkOutPoint) -> Self { + OutPoint { + txid: outpoint.txid.to_string(), + vout: outpoint.vout, + } + } +} + #[derive(Debug, Clone)] pub struct TxOut { pub value: u64, diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs new file mode 100644 index 00000000..47a36abf --- /dev/null +++ b/bdk-ffi/src/error.rs @@ -0,0 +1,82 @@ +use crate::bitcoin::OutPoint; + +use bdk::chain::tx_graph::CalculateFeeError as BdkCalculateFeeError; + +use std::fmt; + +#[derive(Debug)] +pub enum CalculateFeeError { + MissingTxOut { out_points: Vec }, + NegativeFee { fee: i64 }, +} + +impl fmt::Display for CalculateFeeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + CalculateFeeError::MissingTxOut { out_points } => { + write!(f, "Missing transaction output: {:?}", out_points) + } + CalculateFeeError::NegativeFee { fee } => write!(f, "Negative fee value: {}", fee), + } + } +} + +impl From for CalculateFeeError { + fn from(error: BdkCalculateFeeError) -> Self { + match error { + BdkCalculateFeeError::MissingTxOut(out_points) => CalculateFeeError::MissingTxOut { + out_points: out_points.iter().map(|op| op.into()).collect(), + }, + BdkCalculateFeeError::NegativeFee(fee) => CalculateFeeError::NegativeFee { fee }, + } + } +} + +impl std::error::Error for CalculateFeeError {} + +#[cfg(test)] +mod test { + use crate::CalculateFeeError; + use crate::OutPoint; + + #[test] + fn test_error_missing_tx_out() { + let out_points: Vec = vec![ + OutPoint { + txid: "0000000000000000000000000000000000000000000000000000000000000001" + .to_string(), + vout: 0, + }, + OutPoint { + txid: "0000000000000000000000000000000000000000000000000000000000000002" + .to_string(), + vout: 1, + }, + ]; + + let error = CalculateFeeError::MissingTxOut { out_points }; + + let expected_message: String = format!( + "Missing transaction output: [{:?}, {:?}]", + OutPoint { + txid: "0000000000000000000000000000000000000000000000000000000000000001" + .to_string(), + vout: 0 + }, + OutPoint { + txid: "0000000000000000000000000000000000000000000000000000000000000002" + .to_string(), + vout: 1 + } + ); + + assert_eq!(error.to_string(), expected_message); + } + + #[test] + fn test_error_negative_fee() { + let error = CalculateFeeError::NegativeFee { fee: -100 }; + + assert_eq!(error.to_string(), "Negative fee value: -100"); + } +} diff --git a/bdk-ffi/src/esplora.rs b/bdk-ffi/src/esplora.rs index 27c2f3f4..0fc3a17a 100644 --- a/bdk-ffi/src/esplora.rs +++ b/bdk-ffi/src/esplora.rs @@ -59,7 +59,7 @@ impl EsploraClient { // pub fn sync(); pub fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> { - let bdk_transaction: BdkTransaction = transaction.clone().into(); + let bdk_transaction: BdkTransaction = transaction.into(); self.0 .broadcast(&bdk_transaction) .map_err(|e| BdkError::Generic(e.to_string())) diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index b4ce7fda..ec86e0f4 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -1,5 +1,6 @@ mod bitcoin; mod descriptor; +mod error; mod esplora; mod keys; mod types; @@ -13,6 +14,7 @@ use crate::bitcoin::Script; use crate::bitcoin::Transaction; use crate::bitcoin::TxOut; use crate::descriptor::Descriptor; +use crate::error::CalculateFeeError; use crate::esplora::EsploraClient; use crate::keys::DerivationPath; use crate::keys::DescriptorPublicKey; @@ -21,6 +23,7 @@ use crate::keys::Mnemonic; use crate::types::AddressIndex; use crate::types::AddressInfo; use crate::types::Balance; +use crate::types::FeeRate; use crate::types::LocalUtxo; use crate::types::ScriptAmount; use crate::wallet::BumpFeeTxBuilder; diff --git a/bdk-ffi/src/types.rs b/bdk-ffi/src/types.rs index 1f03ea0f..bfef1015 100644 --- a/bdk-ffi/src/types.rs +++ b/bdk-ffi/src/types.rs @@ -7,8 +7,22 @@ use bdk::KeychainKind; use bdk::LocalUtxo as BdkLocalUtxo; +use bdk::FeeRate as BdkFeeRate; + use std::sync::Arc; +pub struct FeeRate(pub BdkFeeRate); + +impl FeeRate { + pub fn as_sat_per_vb(&self) -> f32 { + self.0.as_sat_per_vb() + } + + pub fn sat_per_kwu(&self) -> f32 { + self.0.sat_per_kwu() + } +} + pub struct ScriptAmount { pub script: Arc