From 9a381f6d32321f2a726092bd2b54ab10d105e9c6 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Mon, 17 Oct 2022 16:14:55 -0500 Subject: [PATCH 1/2] Rename PartiallySignedBitcoinTransaction to PartiallySignedTransaction --- src/bdk.udl | 12 ++++++------ src/lib.rs | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index 97d3483..d696b02 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -137,7 +137,7 @@ interface Blockchain { constructor(BlockchainConfig config); [Throws=BdkError] - void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); + void broadcast([ByRef] PartiallySignedTransaction psbt); [Throws=BdkError] u32 get_height(); @@ -188,7 +188,7 @@ interface Wallet { Balance get_balance(); [Throws=BdkError] - boolean sign([ByRef] PartiallySignedBitcoinTransaction psbt); + boolean sign([ByRef] PartiallySignedTransaction psbt); [Throws=BdkError] sequence list_transactions(); @@ -202,7 +202,7 @@ interface Wallet { sequence list_unspent(); }; -interface PartiallySignedBitcoinTransaction { +interface PartiallySignedTransaction { [Throws=BdkError] constructor(string psbt_base64); @@ -213,11 +213,11 @@ interface PartiallySignedBitcoinTransaction { sequence extract_tx(); [Throws=BdkError] - PartiallySignedBitcoinTransaction combine(PartiallySignedBitcoinTransaction other); + PartiallySignedTransaction combine(PartiallySignedTransaction other); }; dictionary TxBuilderResult { - PartiallySignedBitcoinTransaction psbt; + PartiallySignedTransaction psbt; TransactionDetails transaction_details; }; @@ -270,7 +270,7 @@ interface BumpFeeTxBuilder { BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence); [Throws=BdkError] - PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet); + PartiallySignedTransaction finish([ByRef] Wallet wallet); }; interface Mnemonic { diff --git a/src/lib.rs b/src/lib.rs index 259dbc3..ed98e42 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -210,7 +210,7 @@ impl Blockchain { self.blockchain_mutex.lock().expect("blockchain") } - fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), BdkError> { + fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> { let tx = psbt.internal.lock().unwrap().clone().extract_tx(); self.get_blockchain().broadcast(&tx) } @@ -341,14 +341,14 @@ impl fmt::Debug for ProgressHolder { } #[derive(Debug)] -pub struct PartiallySignedBitcoinTransaction { - internal: Mutex, +pub struct PartiallySignedTransaction { + internal: Mutex, } -impl PartiallySignedBitcoinTransaction { +impl PartiallySignedTransaction { fn new(psbt_base64: String) -> Result { - let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?; - Ok(PartiallySignedBitcoinTransaction { + let psbt: BdkPartiallySignedTransaction = BdkPartiallySignedTransaction::from_str(&psbt_base64)?; + Ok(PartiallySignedTransaction { internal: Mutex::new(psbt), }) } @@ -379,13 +379,13 @@ impl PartiallySignedBitcoinTransaction { /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` fn combine( &self, - other: Arc, - ) -> Result, BdkError> { + other: Arc, + ) -> Result, BdkError> { let other_psbt = other.internal.lock().unwrap().clone(); let mut original_psbt = self.internal.lock().unwrap().clone(); original_psbt.combine(other_psbt)?; - Ok(Arc::new(PartiallySignedBitcoinTransaction { + Ok(Arc::new(PartiallySignedTransaction { internal: Mutex::new(original_psbt), })) } @@ -460,7 +460,7 @@ impl Wallet { } /// Sign a transaction with all the wallet’s signers. - fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result { + fn sign(&self, psbt: &PartiallySignedTransaction) -> Result { let mut psbt = psbt.internal.lock().unwrap(); self.get_wallet().sign(&mut psbt, SignOptions::default()) } @@ -532,7 +532,7 @@ enum RbfValue { /// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and /// transaction details. pub struct TxBuilderResult { - pub psbt: Arc, + pub psbt: Arc, pub transaction_details: TransactionDetails, } @@ -770,7 +770,7 @@ impl TxBuilder { tx_builder .finish() .map(|(psbt, tx_details)| TxBuilderResult { - psbt: Arc::new(PartiallySignedBitcoinTransaction { + psbt: Arc::new(PartiallySignedTransaction { internal: Mutex::new(psbt), }), transaction_details: TransactionDetails::from(&tx_details), @@ -828,7 +828,7 @@ impl BumpFeeTxBuilder { } /// Finish building the transaction. Returns the BIP174 PSBT. - fn finish(&self, wallet: &Wallet) -> Result, BdkError> { + fn finish(&self, wallet: &Wallet) -> Result, BdkError> { let wallet = wallet.get_wallet(); let txid = Txid::from_str(self.txid.as_str())?; let mut tx_builder = wallet.build_fee_bump(txid)?; @@ -851,7 +851,7 @@ impl BumpFeeTxBuilder { } tx_builder .finish() - .map(|(psbt, _)| PartiallySignedBitcoinTransaction { + .map(|(psbt, _)| PartiallySignedTransaction { internal: Mutex::new(psbt), }) .map(Arc::new) From ae1ea99ed3f26f33ead92d4a91588ae32d9e110b Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 18 Oct 2022 15:59:07 -0500 Subject: [PATCH 2/2] Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction --- src/bdk.udl | 11 +++++++++++ src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index d696b02..a1e6fe5 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -202,6 +202,13 @@ interface Wallet { sequence list_unspent(); }; +interface FeeRate { + [Name=from_sat_per_vb] + constructor(float sat_per_vb); + + float as_sat_per_vb(); +}; + interface PartiallySignedTransaction { [Throws=BdkError] constructor(string psbt_base64); @@ -214,6 +221,10 @@ interface PartiallySignedTransaction { [Throws=BdkError] PartiallySignedTransaction combine(PartiallySignedTransaction other); + + u64? fee_amount(); + + FeeRate? fee_rate(); }; dictionary TxBuilderResult { diff --git a/src/lib.rs b/src/lib.rs index ed98e42..cb4b528 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use bdk::bitcoin::hashes::hex::ToHex; use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath; use bdk::bitcoin::util::psbt::serialize::Serialize; -use bdk::bitcoin::util::psbt::PartiallySignedTransaction; +use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction; use bdk::bitcoin::Sequence; use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; @@ -22,6 +22,7 @@ use bdk::keys::{ DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey, }; use bdk::miniscript::BareCtx; +use bdk::psbt::PsbtUtils; use bdk::wallet::tx_builder::ChangeSpendPolicy; use bdk::wallet::AddressIndex as BdkAddressIndex; use bdk::wallet::AddressInfo as BdkAddressInfo; @@ -347,7 +348,8 @@ pub struct PartiallySignedTransaction { impl PartiallySignedTransaction { fn new(psbt_base64: String) -> Result { - let psbt: BdkPartiallySignedTransaction = BdkPartiallySignedTransaction::from_str(&psbt_base64)?; + let psbt: BdkPartiallySignedTransaction = + BdkPartiallySignedTransaction::from_str(&psbt_base64)?; Ok(PartiallySignedTransaction { internal: Mutex::new(psbt), }) @@ -389,6 +391,20 @@ impl PartiallySignedTransaction { internal: Mutex::new(original_psbt), })) } + + /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats. + /// If the PSBT is missing a TxOut for an input returns None. + fn fee_amount(&self) -> Option { + self.internal.lock().unwrap().fee_amount() + } + + /// The transaction's fee rate. This value will only be accurate if calculated AFTER the + /// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the + /// transaction. + /// If the PSBT is missing a TxOut for an input returns None. + fn fee_rate(&self) -> Option> { + self.internal.lock().unwrap().fee_rate().map(Arc::new) + } } /// A Bitcoin wallet. @@ -1246,4 +1262,32 @@ mod test { "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936" ) } + + #[test] + fn test_psbt_fee() { + let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let (funded_wallet, _, _) = get_funded_wallet(test_wpkh); + let test_wallet = Wallet { + wallet_mutex: Mutex::new(funded_wallet), + }; + let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string(); + let tx_builder = TxBuilder::new() + .fee_rate(2.0) + .drain_wallet() + .drain_to(drain_to_address.clone()); + //dbg!(&tx_builder); + assert!(tx_builder.drain_wallet); + assert_eq!(tx_builder.drain_to, Some(drain_to_address)); + + let tx_builder_result = tx_builder.finish(&test_wallet).unwrap(); + + assert!(tx_builder_result.psbt.fee_rate().is_some()); + assert_eq!( + tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(), + 2.682927 + ); + + assert!(tx_builder_result.psbt.fee_amount().is_some()); + assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220); + } }