diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 5cad6bb86..9090d22f9 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -45,6 +45,7 @@ use bitcoin::{ use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt}; use bitcoin::{constants::genesis_block, Amount}; use bitcoin::{secp256k1::Secp256k1, Weight}; +use core::cmp::Ordering; use core::fmt; use core::mem; use core::ops::Deref; @@ -291,6 +292,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, ConfirmationBlockTime>; + impl Wallet { /// Build a new single descriptor [`Wallet`]. /// @@ -1002,9 +1006,9 @@ impl Wallet { self.indexed_graph.index.sent_and_received(tx, ..) } - /// Get a single transaction from the wallet as a [`CanonicalTx`] (if the transaction exists). + /// Get a single transaction from the wallet as a [`WalletTx`] (if the transaction exists). /// - /// `CanonicalTx` contains the full transaction alongside meta-data such as: + /// `WalletTx` contains the full transaction alongside meta-data such as: /// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that exist /// in the best chain. /// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is @@ -1018,13 +1022,13 @@ impl Wallet { /// # let wallet: Wallet = todo!(); /// # let my_txid: bitcoin::Txid = todo!(); /// - /// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); + /// let wallet_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); /// /// // get reference to full transaction - /// println!("my tx: {:#?}", canonical_tx.tx_node.tx); + /// println!("my tx: {:#?}", wallet_tx.tx_node.tx); /// /// // list all transaction anchors - /// for anchor in canonical_tx.tx_node.anchors { + /// for anchor in wallet_tx.tx_node.anchors { /// println!( /// "tx is anchored by block of hash {}", /// anchor.anchor_block().hash @@ -1032,7 +1036,7 @@ impl Wallet { /// } /// /// // get confirmation status of transaction - /// match canonical_tx.chain_position { + /// match wallet_tx.chain_position { /// ChainPosition::Confirmed(anchor) => println!( /// "tx is confirmed at height {}, we know this since {}:{} is in the best chain", /// anchor.block_id.height, anchor.block_id.height, anchor.block_id.hash, @@ -1045,13 +1049,10 @@ impl Wallet { /// ``` /// /// [`Anchor`]: bdk_chain::Anchor - pub fn get_tx( - &self, - txid: Txid, - ) -> Option, ConfirmationBlockTime>> { + 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(), @@ -1102,14 +1103,34 @@ impl Wallet { } /// Iterate over the transactions in the wallet. - pub fn transactions( - &self, - ) -> impl Iterator, ConfirmationBlockTime>> + '_ { + pub fn transactions(&self) -> impl Iterator + '_ { self.indexed_graph .graph() .list_canonical_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, 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 { diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 2da25cd28..21f391ca6 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -13,7 +13,7 @@ use bdk_wallet::error::CreateTxError; use bdk_wallet::psbt::PsbtUtils; use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::tx_builder::AddForeignUtxoError; -use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister}; +use bdk_wallet::{AddressInfo, Balance, ChangeSet, Wallet, WalletPersister, WalletTx}; use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError}; use bitcoin::constants::ChainHash; use bitcoin::hashes::Hash; @@ -4203,3 +4203,18 @@ fn single_descriptor_wallet_can_create_tx_and_receive_change() { "tx change should go to external keychain" ); } + +#[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()); +}