feat: add calculate_fee and calculate_fee_rate on wallet

This commit is contained in:
Matthew 2023-12-16 19:24:02 -06:00
parent fc25cd709a
commit 3789c1dcd6
No known key found for this signature in database
GPG Key ID: 8D4FCD82DD54DDD2
11 changed files with 180 additions and 13 deletions

View File

@ -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)
}
}

View File

@ -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",
@ -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 {};

View File

@ -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()
}
}
@ -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,

82
bdk-ffi/src/error.rs Normal file
View File

@ -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");
}
}

View File

@ -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()))

View File

@ -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;

View File

@ -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,

View File

@ -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};
@ -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;
@ -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 {
@ -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);
@ -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())?;
}

View File

@ -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)
}
}

View File

@ -64,6 +64,11 @@ class TestLiveWallet(unittest.TestCase):
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)

View File

@ -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)
}
}