Skip to content

Commit

Permalink
feat: add calculate_fee and calculate_fee_rate on wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
reez committed Jan 8, 2024
1 parent fc25cd7 commit 3789c1d
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
19 changes: 18 additions & 1 deletion bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ enum BdkError {
"Psbt",
};

[Error]
interface CalculateFeeError {
MissingTxOut(sequence<OutPoint> out_points);
NegativeFee(i64 fee);
};

interface FeeRate {
f32 as_sat_per_vb();
f32 sat_per_kwu();
};

enum ChangeSpendPolicy {
"ChangeAllowed",
"OnlyChange",
Expand Down Expand Up @@ -111,6 +122,12 @@ interface Wallet {
SentAndReceivedValues sent_and_received([ByRef] Transaction tx);

sequence<Transaction> transactions();

[Throws=CalculateFeeError]
u64 calculate_fee([ByRef] Transaction tx);

[Throws=CalculateFeeError]
FeeRate calculate_fee_rate([ByRef] Transaction tx);
};

interface Update {};
Expand Down Expand Up @@ -350,4 +367,4 @@ interface PartiallySignedTransaction {
dictionary OutPoint {
string txid;
u32 vout;
};
};
21 changes: 18 additions & 3 deletions bdk-ffi/src/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,15 @@ impl From<BdkTransaction> for Transaction {
}
}

impl From<Transaction> 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()
}
}

Expand Down Expand Up @@ -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,
Expand Down
82 changes: 82 additions & 0 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
@@ -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<OutPoint> },
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<BdkCalculateFeeError> 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<OutPoint> = 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");
}
}
2 changes: 1 addition & 1 deletion bdk-ffi/src/esplora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
3 changes: 3 additions & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod bitcoin;
mod descriptor;
mod error;
mod esplora;
mod keys;
mod types;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
14 changes: 14 additions & 0 deletions bdk-ffi/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Script>,
pub amount: u64,
Expand Down
26 changes: 20 additions & 6 deletions bdk-ffi/src/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::bitcoin::{OutPoint, PartiallySignedTransaction, Transaction};
use crate::descriptor::Descriptor;
use crate::types::Balance;
use crate::error::CalculateFeeError;
use crate::types::ScriptAmount;
use crate::types::{Balance, FeeRate};
use crate::Script;
use crate::{AddressIndex, AddressInfo, Network};

Expand All @@ -10,7 +11,7 @@ use bdk::bitcoin::psbt::PartiallySignedTransaction as BdkPartiallySignedTransact
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::Update as BdkUpdate;
use bdk::{Error as BdkError, FeeRate};
use bdk::{Error as BdkError, FeeRate as BdkFeeRate};
use bdk::{SignOptions, Wallet as BdkWallet};

use std::collections::HashSet;
Expand Down Expand Up @@ -88,16 +89,29 @@ impl Wallet {
}

pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.clone().into());
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
SentAndReceivedValues { sent, received }
}

pub fn transactions(&self) -> Vec<Arc<Transaction>> {
self.get_wallet()
.transactions()
.map(|tx| Arc::new(tx.tx_node.tx.clone().into()))
.map(|tx| Arc::new(tx.tx_node.tx.into()))
.collect()
}

pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
self.get_wallet()
.calculate_fee(&tx.into())
.map_err(|e| e.into())
}

pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<Arc<FeeRate>, CalculateFeeError> {
self.get_wallet()
.calculate_fee_rate(&tx.into())
.map(|bdk_fee_rate| Arc::new(FeeRate(bdk_fee_rate)))
.map_err(|e| e.into())
}
}

pub struct SentAndReceivedValues {
Expand Down Expand Up @@ -473,7 +487,7 @@ impl TxBuilder {
tx_builder.manually_selected_only();
}
if let Some(sat_per_vb) = self.fee_rate {
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(sat_per_vb));
}
if let Some(fee_amount) = self.fee_absolute {
tx_builder.fee_absolute(fee_amount);
Expand Down Expand Up @@ -551,7 +565,7 @@ impl BumpFeeTxBuilder {
Txid::from_str(self.txid.as_str()).map_err(|e| BdkError::Generic(e.to_string()))?;
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_fee_bump(txid)?;
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(self.fee_rate));
if let Some(allow_shrinking) = &self.allow_shrinking {
tx_builder.allow_shrinking(allow_shrinking.0.clone())?;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,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)
}
}
5 changes: 5 additions & 0 deletions bdk-python/tests/test_live_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ def test_broadcast_transaction(self):
walletDidSign = wallet.sign(psbt)
self.assertTrue(walletDidSign)
tx = psbt.extract_tx()
print(f"Transaction Id: {tx.txid}")
fee = wallet.calculate_fee(tx)
print(f"Transaction Fee: {fee}")
fee_rate = wallet.calculate_fee_rate(tx)
print(f"Transaction Fee Rate: {fee_rate.as_sat_per_vb()} sat/vB")

esploraClient.broadcast(tx)

Expand Down
5 changes: 5 additions & 0 deletions bdk-swift/Tests/BitcoinDevKitTests/LiveWalletTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ final class LiveWalletTests: XCTestCase {

let tx: Transaction = psbt.extractTx()
print(tx.txid())
let fee: UInt64 = try wallet.calculateFee(tx: tx)
print("Transaction Fee: \(fee)")
let feeRate: FeeRate = try wallet.calculateFeeRate(tx: tx)
print("Transaction Fee Rate: \(feeRate.asSatPerVb()) sat/vB")

try esploraClient.broadcast(transaction: tx)
}
}

0 comments on commit 3789c1d

Please sign in to comment.