diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 883e954a7..a9fda0e97 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -39,6 +39,7 @@ use bitcoin::{ }; use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt}; use bitcoin::{constants::genesis_block, Amount}; +use core::cmp::Ordering; use core::fmt; use core::ops::Deref; use descriptor::error::Error as DescriptorError; @@ -343,6 +344,9 @@ impl fmt::Display for ApplyBlockError { #[cfg(feature = "std")] impl std::error::Error for ApplyBlockError {} +/// A `CanonicalTx` managed by a `Wallet`. +pub type WalletTx<'a> = CanonicalTx<'a, Arc, ConfirmationTimeHeightAnchor>; + impl Wallet { /// Initialize an empty [`Wallet`]. pub fn new( @@ -1037,13 +1041,10 @@ impl Wallet { /// ``` /// /// [`Anchor`]: bdk_chain::Anchor - pub fn get_tx( - &self, - txid: Txid, - ) -> Option, ConfirmationTimeHeightAnchor>> { + pub fn get_tx(&self, txid: Txid) -> Option { let graph = self.indexed_graph.graph(); - Some(CanonicalTx { + Some(WalletTx { chain_position: graph.get_chain_position( &self.chain, self.chain.tip().block_id(), @@ -1136,15 +1137,35 @@ impl Wallet { } /// Iterate over the transactions in the wallet. - pub fn transactions( - &self, - ) -> impl Iterator, ConfirmationTimeHeightAnchor>> + '_ - { + pub fn transactions(&self) -> impl Iterator + '_ { self.indexed_graph .graph() .list_chain_txs(&self.chain, self.chain.tip().block_id()) } + /// Array of transactions in the wallet sorted with a comparator function. + /// + /// # Example + /// + /// ```rust,no_run + /// # use bdk_wallet::Wallet; + /// # use bdk_wallet::wallet::WalletTx; + /// # let changeset = Default::default(); + /// # let mut wallet = Wallet::load_from_changeset(changeset)?; + /// // Transactions by chain position: first unconfirmed then descending by confirmed height. + /// let sorted_txs: Vec = + /// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position)); + /// # Ok::<(), anyhow::Error>(()) + /// ``` + pub fn transactions_sort_by(&self, compare: F) -> Vec + where + F: FnMut(&WalletTx, &WalletTx) -> Ordering, + { + let mut txs: Vec = self.transactions().collect(); + txs.sort_unstable_by(compare); + txs + } + /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. pub fn balance(&self) -> Balance { @@ -1287,7 +1308,7 @@ impl Wallet { let version = match params.version { Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0), Some(tx_builder::Version(1)) if requirements.csv.is_some() => { - return Err(CreateTxError::Version1Csv) + return Err(CreateTxError::Version1Csv); } Some(tx_builder::Version(x)) => x, None if requirements.csv.is_some() => 2, @@ -1340,7 +1361,7 @@ impl Wallet { return Err(CreateTxError::LockTime { requested: x, required: requirements.timelock.unwrap(), - }) + }); } }; @@ -1360,13 +1381,13 @@ impl Wallet { // RBF with a specific value but that value is too high (Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => { - return Err(CreateTxError::RbfSequence) + return Err(CreateTxError::RbfSequence); } // RBF with a specific value requested, but the value is incompatible with CSV (Some(tx_builder::RbfValue::Value(rbf)), Some(csv)) if !check_nsequence_rbf(rbf, csv) => { - return Err(CreateTxError::RbfSequenceCsv { rbf, csv }) + return Err(CreateTxError::RbfSequenceCsv { rbf, csv }); } // RBF enabled with the default value with CSV also enabled. CSV takes precedence diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 7303bdcd8..74494088c 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -12,7 +12,7 @@ use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection}; use bdk_wallet::wallet::error::CreateTxError; use bdk_wallet::wallet::tx_builder::AddForeignUtxoError; -use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet}; +use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet, WalletTx}; use bdk_wallet::KeychainKind; use bitcoin::hashes::Hash; use bitcoin::key::Secp256k1; @@ -4079,3 +4079,18 @@ fn test_thread_safety() { fn thread_safe() {} thread_safe::(); // compiles only if true } + +#[test] +fn test_transactions_sort_by() { + let (mut wallet, _txid) = get_funded_wallet_wpkh(); + receive_output(&mut wallet, 25_000, ConfirmationTime::unconfirmed(0)); + + // sort by chain position, unconfirmed then confirmed by descending block height + let sorted_txs: Vec = + wallet.transactions_sort_by(|t1, t2| t2.chain_position.cmp(&t1.chain_position)); + let conf_heights: Vec> = sorted_txs + .iter() + .map(|tx| tx.chain_position.confirmation_height_upper_bound()) + .collect(); + assert_eq!([None, Some(2000), Some(1000)], conf_heights.as_slice()); +}