feat: add calculate_fee and calculate_fee_rate on wallet
This commit is contained in:
		
							parent
							
								
									fc25cd709a
								
							
						
					
					
						commit
						3789c1dcd6
					
				| @ -59,8 +59,14 @@ class LiveWalletTest { | |||||||
|         assertTrue(walletDidSign) |         assertTrue(walletDidSign) | ||||||
| 
 | 
 | ||||||
|         val tx: Transaction = psbt.extractTx() |         val tx: Transaction = psbt.extractTx() | ||||||
| 
 |  | ||||||
|         println("Txid is: ${tx.txid()}") |         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) |         esploraClient.broadcast(tx) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -82,6 +82,17 @@ enum BdkError { | |||||||
|   "Psbt", |   "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 { | enum ChangeSpendPolicy { | ||||||
|   "ChangeAllowed", |   "ChangeAllowed", | ||||||
|   "OnlyChange", |   "OnlyChange", | ||||||
| @ -111,6 +122,12 @@ interface Wallet { | |||||||
|   SentAndReceivedValues sent_and_received([ByRef] Transaction tx); |   SentAndReceivedValues sent_and_received([ByRef] Transaction tx); | ||||||
| 
 | 
 | ||||||
|   sequence<Transaction> transactions(); |   sequence<Transaction> transactions(); | ||||||
|  | 
 | ||||||
|  |   [Throws=CalculateFeeError] | ||||||
|  |   u64 calculate_fee([ByRef] Transaction tx); | ||||||
|  | 
 | ||||||
|  |   [Throws=CalculateFeeError] | ||||||
|  |   FeeRate calculate_fee_rate([ByRef] Transaction tx); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface Update {}; | interface Update {}; | ||||||
|  | |||||||
| @ -213,9 +213,15 @@ impl From<BdkTransaction> for Transaction { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl From<Transaction> for BdkTransaction { | impl From<&BdkTransaction> for Transaction { | ||||||
|     fn from(tx: Transaction) -> Self { |     fn from(tx: &BdkTransaction) -> Self { | ||||||
|         tx.inner |         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)] | #[derive(Debug, Clone)] | ||||||
| pub struct TxOut { | pub struct TxOut { | ||||||
|     pub value: u64, |     pub value: u64, | ||||||
|  | |||||||
							
								
								
									
										82
									
								
								bdk-ffi/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								bdk-ffi/src/error.rs
									
									
									
									
									
										Normal 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"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -59,7 +59,7 @@ impl EsploraClient { | |||||||
|     // pub fn sync();
 |     // pub fn sync();
 | ||||||
| 
 | 
 | ||||||
|     pub fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> { |     pub fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> { | ||||||
|         let bdk_transaction: BdkTransaction = transaction.clone().into(); |         let bdk_transaction: BdkTransaction = transaction.into(); | ||||||
|         self.0 |         self.0 | ||||||
|             .broadcast(&bdk_transaction) |             .broadcast(&bdk_transaction) | ||||||
|             .map_err(|e| BdkError::Generic(e.to_string())) |             .map_err(|e| BdkError::Generic(e.to_string())) | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| mod bitcoin; | mod bitcoin; | ||||||
| mod descriptor; | mod descriptor; | ||||||
|  | mod error; | ||||||
| mod esplora; | mod esplora; | ||||||
| mod keys; | mod keys; | ||||||
| mod types; | mod types; | ||||||
| @ -13,6 +14,7 @@ use crate::bitcoin::Script; | |||||||
| use crate::bitcoin::Transaction; | use crate::bitcoin::Transaction; | ||||||
| use crate::bitcoin::TxOut; | use crate::bitcoin::TxOut; | ||||||
| use crate::descriptor::Descriptor; | use crate::descriptor::Descriptor; | ||||||
|  | use crate::error::CalculateFeeError; | ||||||
| use crate::esplora::EsploraClient; | use crate::esplora::EsploraClient; | ||||||
| use crate::keys::DerivationPath; | use crate::keys::DerivationPath; | ||||||
| use crate::keys::DescriptorPublicKey; | use crate::keys::DescriptorPublicKey; | ||||||
| @ -21,6 +23,7 @@ use crate::keys::Mnemonic; | |||||||
| use crate::types::AddressIndex; | use crate::types::AddressIndex; | ||||||
| use crate::types::AddressInfo; | use crate::types::AddressInfo; | ||||||
| use crate::types::Balance; | use crate::types::Balance; | ||||||
|  | use crate::types::FeeRate; | ||||||
| use crate::types::LocalUtxo; | use crate::types::LocalUtxo; | ||||||
| use crate::types::ScriptAmount; | use crate::types::ScriptAmount; | ||||||
| use crate::wallet::BumpFeeTxBuilder; | use crate::wallet::BumpFeeTxBuilder; | ||||||
|  | |||||||
| @ -7,8 +7,22 @@ use bdk::KeychainKind; | |||||||
| 
 | 
 | ||||||
| use bdk::LocalUtxo as BdkLocalUtxo; | use bdk::LocalUtxo as BdkLocalUtxo; | ||||||
| 
 | 
 | ||||||
|  | use bdk::FeeRate as BdkFeeRate; | ||||||
|  | 
 | ||||||
| use std::sync::Arc; | 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 struct ScriptAmount { | ||||||
|     pub script: Arc<Script>, |     pub script: Arc<Script>, | ||||||
|     pub amount: u64, |     pub amount: u64, | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| use crate::bitcoin::{OutPoint, PartiallySignedTransaction, Transaction}; | use crate::bitcoin::{OutPoint, PartiallySignedTransaction, Transaction}; | ||||||
| use crate::descriptor::Descriptor; | use crate::descriptor::Descriptor; | ||||||
| use crate::types::Balance; | use crate::error::CalculateFeeError; | ||||||
| use crate::types::ScriptAmount; | use crate::types::ScriptAmount; | ||||||
|  | use crate::types::{Balance, FeeRate}; | ||||||
| use crate::Script; | use crate::Script; | ||||||
| use crate::{AddressIndex, AddressInfo, Network}; | 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::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid}; | ||||||
| use bdk::wallet::tx_builder::ChangeSpendPolicy; | use bdk::wallet::tx_builder::ChangeSpendPolicy; | ||||||
| use bdk::wallet::Update as BdkUpdate; | 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 bdk::{SignOptions, Wallet as BdkWallet}; | ||||||
| 
 | 
 | ||||||
| use std::collections::HashSet; | use std::collections::HashSet; | ||||||
| @ -88,16 +89,29 @@ impl Wallet { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues { |     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 } |         SentAndReceivedValues { sent, received } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn transactions(&self) -> Vec<Arc<Transaction>> { |     pub fn transactions(&self) -> Vec<Arc<Transaction>> { | ||||||
|         self.get_wallet() |         self.get_wallet() | ||||||
|             .transactions() |             .transactions() | ||||||
|             .map(|tx| Arc::new(tx.tx_node.tx.clone().into())) |             .map(|tx| Arc::new(tx.tx_node.tx.into())) | ||||||
|             .collect() |             .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 { | pub struct SentAndReceivedValues { | ||||||
| @ -473,7 +487,7 @@ impl TxBuilder { | |||||||
|             tx_builder.manually_selected_only(); |             tx_builder.manually_selected_only(); | ||||||
|         } |         } | ||||||
|         if let Some(sat_per_vb) = self.fee_rate { |         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 { |         if let Some(fee_amount) = self.fee_absolute { | ||||||
|             tx_builder.fee_absolute(fee_amount); |             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()))?; |             Txid::from_str(self.txid.as_str()).map_err(|e| BdkError::Generic(e.to_string()))?; | ||||||
|         let mut wallet = wallet.get_wallet(); |         let mut wallet = wallet.get_wallet(); | ||||||
|         let mut tx_builder = wallet.build_fee_bump(txid)?; |         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 { |         if let Some(allow_shrinking) = &self.allow_shrinking { | ||||||
|             tx_builder.allow_shrinking(allow_shrinking.0.clone())?; |             tx_builder.allow_shrinking(allow_shrinking.0.clone())?; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -55,8 +55,14 @@ class LiveWalletTest { | |||||||
|         assertTrue(walletDidSign) |         assertTrue(walletDidSign) | ||||||
| 
 | 
 | ||||||
|         val tx: Transaction = psbt.extractTx() |         val tx: Transaction = psbt.extractTx() | ||||||
| 
 |  | ||||||
|         println("Txid is: ${tx.txid()}") |         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) |         esploraClient.broadcast(tx) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -64,6 +64,11 @@ class TestLiveWallet(unittest.TestCase): | |||||||
|         walletDidSign = wallet.sign(psbt) |         walletDidSign = wallet.sign(psbt) | ||||||
|         self.assertTrue(walletDidSign) |         self.assertTrue(walletDidSign) | ||||||
|         tx = psbt.extract_tx() |         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) |         esploraClient.broadcast(tx) | ||||||
|      |      | ||||||
|  | |||||||
| @ -69,6 +69,11 @@ final class LiveWalletTests: XCTestCase { | |||||||
|          |          | ||||||
|         let tx: Transaction = psbt.extractTx() |         let tx: Transaction = psbt.extractTx() | ||||||
|         print(tx.txid()) |         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) |         try esploraClient.broadcast(transaction: tx) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user