Skip to content

Commit 3789c1d

Browse files
committed
feat: add calculate_fee and calculate_fee_rate on wallet
1 parent fc25cd7 commit 3789c1d

File tree

11 files changed

+180
-13
lines changed

11 files changed

+180
-13
lines changed

bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/LiveWalletTest.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,14 @@ class LiveWalletTest {
5959
assertTrue(walletDidSign)
6060

6161
val tx: Transaction = psbt.extractTx()
62-
6362
println("Txid is: ${tx.txid()}")
63+
64+
val txFee: ULong = wallet.calculateFee(tx)
65+
println("Tx fee is: ${txFee}")
66+
67+
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
68+
println("Tx fee rate is: ${feeRate.asSatPerVb()} sat/vB")
69+
6470
esploraClient.broadcast(tx)
6571
}
6672
}

bdk-ffi/src/bdk.udl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ enum BdkError {
8282
"Psbt",
8383
};
8484

85+
[Error]
86+
interface CalculateFeeError {
87+
MissingTxOut(sequence<OutPoint> out_points);
88+
NegativeFee(i64 fee);
89+
};
90+
91+
interface FeeRate {
92+
f32 as_sat_per_vb();
93+
f32 sat_per_kwu();
94+
};
95+
8596
enum ChangeSpendPolicy {
8697
"ChangeAllowed",
8798
"OnlyChange",
@@ -111,6 +122,12 @@ interface Wallet {
111122
SentAndReceivedValues sent_and_received([ByRef] Transaction tx);
112123

113124
sequence<Transaction> transactions();
125+
126+
[Throws=CalculateFeeError]
127+
u64 calculate_fee([ByRef] Transaction tx);
128+
129+
[Throws=CalculateFeeError]
130+
FeeRate calculate_fee_rate([ByRef] Transaction tx);
114131
};
115132

116133
interface Update {};
@@ -350,4 +367,4 @@ interface PartiallySignedTransaction {
350367
dictionary OutPoint {
351368
string txid;
352369
u32 vout;
353-
};
370+
};

bdk-ffi/src/bitcoin.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,15 @@ impl From<BdkTransaction> for Transaction {
213213
}
214214
}
215215

216-
impl From<Transaction> for BdkTransaction {
217-
fn from(tx: Transaction) -> Self {
218-
tx.inner
216+
impl From<&BdkTransaction> for Transaction {
217+
fn from(tx: &BdkTransaction) -> Self {
218+
Transaction { inner: tx.clone() }
219+
}
220+
}
221+
222+
impl From<&Transaction> for BdkTransaction {
223+
fn from(tx: &Transaction) -> Self {
224+
tx.inner.clone()
219225
}
220226
}
221227

@@ -310,6 +316,15 @@ impl From<&OutPoint> for BdkOutPoint {
310316
}
311317
}
312318

319+
impl From<&BdkOutPoint> for OutPoint {
320+
fn from(outpoint: &BdkOutPoint) -> Self {
321+
OutPoint {
322+
txid: outpoint.txid.to_string(),
323+
vout: outpoint.vout,
324+
}
325+
}
326+
}
327+
313328
#[derive(Debug, Clone)]
314329
pub struct TxOut {
315330
pub value: u64,

bdk-ffi/src/error.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::bitcoin::OutPoint;
2+
3+
use bdk::chain::tx_graph::CalculateFeeError as BdkCalculateFeeError;
4+
5+
use std::fmt;
6+
7+
#[derive(Debug)]
8+
pub enum CalculateFeeError {
9+
MissingTxOut { out_points: Vec<OutPoint> },
10+
NegativeFee { fee: i64 },
11+
}
12+
13+
impl fmt::Display for CalculateFeeError {
14+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15+
match self {
16+
CalculateFeeError::MissingTxOut { out_points } => {
17+
write!(f, "Missing transaction output: {:?}", out_points)
18+
}
19+
CalculateFeeError::NegativeFee { fee } => write!(f, "Negative fee value: {}", fee),
20+
}
21+
}
22+
}
23+
24+
impl From<BdkCalculateFeeError> for CalculateFeeError {
25+
fn from(error: BdkCalculateFeeError) -> Self {
26+
match error {
27+
BdkCalculateFeeError::MissingTxOut(out_points) => CalculateFeeError::MissingTxOut {
28+
out_points: out_points.iter().map(|op| op.into()).collect(),
29+
},
30+
BdkCalculateFeeError::NegativeFee(fee) => CalculateFeeError::NegativeFee { fee },
31+
}
32+
}
33+
}
34+
35+
impl std::error::Error for CalculateFeeError {}
36+
37+
#[cfg(test)]
38+
mod test {
39+
use crate::CalculateFeeError;
40+
use crate::OutPoint;
41+
42+
#[test]
43+
fn test_error_missing_tx_out() {
44+
let out_points: Vec<OutPoint> = vec![
45+
OutPoint {
46+
txid: "0000000000000000000000000000000000000000000000000000000000000001"
47+
.to_string(),
48+
vout: 0,
49+
},
50+
OutPoint {
51+
txid: "0000000000000000000000000000000000000000000000000000000000000002"
52+
.to_string(),
53+
vout: 1,
54+
},
55+
];
56+
57+
let error = CalculateFeeError::MissingTxOut { out_points };
58+
59+
let expected_message: String = format!(
60+
"Missing transaction output: [{:?}, {:?}]",
61+
OutPoint {
62+
txid: "0000000000000000000000000000000000000000000000000000000000000001"
63+
.to_string(),
64+
vout: 0
65+
},
66+
OutPoint {
67+
txid: "0000000000000000000000000000000000000000000000000000000000000002"
68+
.to_string(),
69+
vout: 1
70+
}
71+
);
72+
73+
assert_eq!(error.to_string(), expected_message);
74+
}
75+
76+
#[test]
77+
fn test_error_negative_fee() {
78+
let error = CalculateFeeError::NegativeFee { fee: -100 };
79+
80+
assert_eq!(error.to_string(), "Negative fee value: -100");
81+
}
82+
}

bdk-ffi/src/esplora.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl EsploraClient {
5959
// pub fn sync();
6060

6161
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> {
62-
let bdk_transaction: BdkTransaction = transaction.clone().into();
62+
let bdk_transaction: BdkTransaction = transaction.into();
6363
self.0
6464
.broadcast(&bdk_transaction)
6565
.map_err(|e| BdkError::Generic(e.to_string()))

bdk-ffi/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod bitcoin;
22
mod descriptor;
3+
mod error;
34
mod esplora;
45
mod keys;
56
mod types;
@@ -13,6 +14,7 @@ use crate::bitcoin::Script;
1314
use crate::bitcoin::Transaction;
1415
use crate::bitcoin::TxOut;
1516
use crate::descriptor::Descriptor;
17+
use crate::error::CalculateFeeError;
1618
use crate::esplora::EsploraClient;
1719
use crate::keys::DerivationPath;
1820
use crate::keys::DescriptorPublicKey;
@@ -21,6 +23,7 @@ use crate::keys::Mnemonic;
2123
use crate::types::AddressIndex;
2224
use crate::types::AddressInfo;
2325
use crate::types::Balance;
26+
use crate::types::FeeRate;
2427
use crate::types::LocalUtxo;
2528
use crate::types::ScriptAmount;
2629
use crate::wallet::BumpFeeTxBuilder;

bdk-ffi/src/types.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,22 @@ use bdk::KeychainKind;
77

88
use bdk::LocalUtxo as BdkLocalUtxo;
99

10+
use bdk::FeeRate as BdkFeeRate;
11+
1012
use std::sync::Arc;
1113

14+
pub struct FeeRate(pub BdkFeeRate);
15+
16+
impl FeeRate {
17+
pub fn as_sat_per_vb(&self) -> f32 {
18+
self.0.as_sat_per_vb()
19+
}
20+
21+
pub fn sat_per_kwu(&self) -> f32 {
22+
self.0.sat_per_kwu()
23+
}
24+
}
25+
1226
pub struct ScriptAmount {
1327
pub script: Arc<Script>,
1428
pub amount: u64,

bdk-ffi/src/wallet.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::bitcoin::{OutPoint, PartiallySignedTransaction, Transaction};
22
use crate::descriptor::Descriptor;
3-
use crate::types::Balance;
3+
use crate::error::CalculateFeeError;
44
use crate::types::ScriptAmount;
5+
use crate::types::{Balance, FeeRate};
56
use crate::Script;
67
use crate::{AddressIndex, AddressInfo, Network};
78

@@ -10,7 +11,7 @@ use bdk::bitcoin::psbt::PartiallySignedTransaction as BdkPartiallySignedTransact
1011
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
1112
use bdk::wallet::tx_builder::ChangeSpendPolicy;
1213
use bdk::wallet::Update as BdkUpdate;
13-
use bdk::{Error as BdkError, FeeRate};
14+
use bdk::{Error as BdkError, FeeRate as BdkFeeRate};
1415
use bdk::{SignOptions, Wallet as BdkWallet};
1516

1617
use std::collections::HashSet;
@@ -88,16 +89,29 @@ impl Wallet {
8889
}
8990

9091
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
91-
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.clone().into());
92+
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
9293
SentAndReceivedValues { sent, received }
9394
}
9495

9596
pub fn transactions(&self) -> Vec<Arc<Transaction>> {
9697
self.get_wallet()
9798
.transactions()
98-
.map(|tx| Arc::new(tx.tx_node.tx.clone().into()))
99+
.map(|tx| Arc::new(tx.tx_node.tx.into()))
99100
.collect()
100101
}
102+
103+
pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
104+
self.get_wallet()
105+
.calculate_fee(&tx.into())
106+
.map_err(|e| e.into())
107+
}
108+
109+
pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<Arc<FeeRate>, CalculateFeeError> {
110+
self.get_wallet()
111+
.calculate_fee_rate(&tx.into())
112+
.map(|bdk_fee_rate| Arc::new(FeeRate(bdk_fee_rate)))
113+
.map_err(|e| e.into())
114+
}
101115
}
102116

103117
pub struct SentAndReceivedValues {
@@ -473,7 +487,7 @@ impl TxBuilder {
473487
tx_builder.manually_selected_only();
474488
}
475489
if let Some(sat_per_vb) = self.fee_rate {
476-
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
490+
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(sat_per_vb));
477491
}
478492
if let Some(fee_amount) = self.fee_absolute {
479493
tx_builder.fee_absolute(fee_amount);
@@ -551,7 +565,7 @@ impl BumpFeeTxBuilder {
551565
Txid::from_str(self.txid.as_str()).map_err(|e| BdkError::Generic(e.to_string()))?;
552566
let mut wallet = wallet.get_wallet();
553567
let mut tx_builder = wallet.build_fee_bump(txid)?;
554-
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
568+
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(self.fee_rate));
555569
if let Some(allow_shrinking) = &self.allow_shrinking {
556570
tx_builder.allow_shrinking(allow_shrinking.0.clone())?;
557571
}

bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/LiveWalletTest.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,14 @@ class LiveWalletTest {
5555
assertTrue(walletDidSign)
5656

5757
val tx: Transaction = psbt.extractTx()
58-
5958
println("Txid is: ${tx.txid()}")
59+
60+
val txFee: ULong = wallet.calculateFee(tx)
61+
println("Tx fee is: ${txFee}")
62+
63+
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
64+
println("Tx fee rate is: ${feeRate.asSatPerVb()} sat/vB")
65+
6066
esploraClient.broadcast(tx)
6167
}
6268
}

bdk-python/tests/test_live_wallet.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ def test_broadcast_transaction(self):
6464
walletDidSign = wallet.sign(psbt)
6565
self.assertTrue(walletDidSign)
6666
tx = psbt.extract_tx()
67+
print(f"Transaction Id: {tx.txid}")
68+
fee = wallet.calculate_fee(tx)
69+
print(f"Transaction Fee: {fee}")
70+
fee_rate = wallet.calculate_fee_rate(tx)
71+
print(f"Transaction Fee Rate: {fee_rate.as_sat_per_vb()} sat/vB")
6772

6873
esploraClient.broadcast(tx)
6974

0 commit comments

Comments
 (0)