.sent_and_received(tx, self.map_to_inner_bounds(range))
}
+ /// Returns the sent and received [`TxOut`]s for this `tx` relative to the script pubkeys
+ /// belonging to the keychains in `range`. A TxOut is *sent* when a script pubkey in the
+ /// `range` is on an input and *received* when it is on an output. For `sent` to be computed
+ /// correctly, the index must have already scanned the output being spent. Calculating
+ /// received just uses the [`Transaction`] outputs directly, so it will be correct even if
+ /// it has not been scanned.
+ pub fn sent_and_received_txouts(
+ &self,
+ tx: &Transaction,
+ range: impl RangeBounds<K>,
+ ) -> (Vec<TxOut>, Vec<TxOut>) {
+ self.inner
+ .sent_and_received_txouts(tx, self.map_to_inner_bounds(range))
+ }
+
/// Computes the net value that this transaction gives to the script pubkeys in the index and
/// *takes* from the transaction outputs in the index. Shorthand for calling
/// [`sent_and_received`] and subtracting sent from received.
};
use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
+use alloc::vec::Vec;
+
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
///
/// The basic idea is that you insert script pubkeys you care about into the index with
(sent, received)
}
+ /// Collects the sent and received [`TxOut`]s for `tx` on the script pubkeys in `range`.
+ /// TxOuts are *sent* when a script pubkey in the `range` is on an input and *received* when
+ /// it is on an output. For `sent` to be computed correctly, the index must have already
+ /// scanned the output being spent. Calculating received just uses the [`Transaction`]
+ /// outputs directly, so it will be correct even if it has not been scanned.
+ ///
+ /// Returns a tuple of (sent_txouts, received_txouts).
+ ///
+ /// # Example
+ /// Shows the addresses of the TxOut sent from or received by a Transaction relevant to all spks
+ /// in this index.
+ ///
+ /// ```rust
+ /// # use bdk_chain::spk_txout::SpkTxOutIndex;
+ /// # use bitcoin::{Address, Network, Transaction};
+ /// # use std::str::FromStr;
+ /// #
+ /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut index = SpkTxOutIndex::<u32>::default();
+ ///
+ /// // ... scan transactions to populate the index ...
+ /// # let tx = Transaction { version: bitcoin::transaction::Version::TWO, lock_time: bitcoin::locktime::absolute::LockTime::ZERO, input: vec![], output: vec![] };
+ ///
+ /// // Get sent and received txouts for a transaction across all tracked addresses
+ /// let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx, ..);
+ ///
+ /// // Display addresses and amounts
+ /// println!("Sent:");
+ /// for txout in sent_txouts {
+ /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
+ /// println!(" from {} - {} sats", address, txout.value.to_sat());
+ /// }
+ ///
+ /// println!("Received:");
+ /// for txout in received_txouts {
+ /// let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
+ /// println!(" to {} - {} sats", address, txout.value.to_sat());
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn sent_and_received_txouts(
+ &self,
+ tx: &Transaction,
+ range: impl RangeBounds<I>,
+ ) -> (Vec<TxOut>, Vec<TxOut>) {
+ let mut sent = Vec::new();
+ let mut received = Vec::new();
+
+ for txin in &tx.input {
+ if let Some((index, txout)) = self.txout(txin.previous_output) {
+ if range.contains(index) {
+ sent.push(txout.clone());
+ }
+ }
+ }
+
+ for txout in &tx.output {
+ if let Some(index) = self.index_of_spk(txout.script_pubkey.clone()) {
+ if range.contains(index) {
+ received.push(txout.clone());
+ }
+ }
+ }
+
+ (sent, received)
+ }
+
/// Computes the net value transfer effect of `tx` on the script pubkeys in `range`. Shorthand
/// for calling [`sent_and_received`] and subtracting sent from received.
///
assert_eq!(index.net_value(&tx2, ..), SignedAmount::from_sat(8_000));
}
+#[test]
+fn spk_txout_sent_and_received_txouts() {
+ let spk1 = ScriptBuf::from_hex("001404f1e52ce2bab3423c6a8c63b7cd730d8f12542c").unwrap();
+ let spk2 = ScriptBuf::from_hex("00142b57404ae14f08c3a0c903feb2af7830605eb00f").unwrap();
+
+ let mut index = SpkTxOutIndex::default();
+ index.insert_spk(0, spk1.clone());
+ index.insert_spk(1, spk2.clone());
+
+ let tx1 = Transaction {
+ version: transaction::Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ input: vec![],
+ output: vec![TxOut {
+ value: Amount::from_sat(42_000),
+ script_pubkey: spk1.clone(),
+ }],
+ };
+
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, ..);
+ assert!(sent_txouts.is_empty());
+ assert_eq!(
+ received_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(42_000),
+ script_pubkey: spk1.clone(),
+ }]
+ );
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, ..1);
+ assert!(sent_txouts.is_empty());
+ assert_eq!(
+ received_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(42_000),
+ script_pubkey: spk1.clone(),
+ }]
+ );
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx1, 1..);
+ assert!(sent_txouts.is_empty() && received_txouts.is_empty());
+
+ index.index_tx(&tx1);
+
+ let tx2 = Transaction {
+ version: transaction::Version::ONE,
+ lock_time: absolute::LockTime::ZERO,
+ input: vec![TxIn {
+ previous_output: OutPoint {
+ txid: tx1.compute_txid(),
+ vout: 0,
+ },
+ ..Default::default()
+ }],
+ output: vec![
+ TxOut {
+ value: Amount::from_sat(20_000),
+ script_pubkey: spk2.clone(),
+ },
+ TxOut {
+ script_pubkey: spk1.clone(),
+ value: Amount::from_sat(30_000),
+ },
+ ],
+ };
+
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..);
+ assert_eq!(
+ sent_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(42_000),
+ script_pubkey: spk1.clone(),
+ }]
+ );
+ assert_eq!(
+ received_txouts,
+ vec![
+ TxOut {
+ value: Amount::from_sat(20_000),
+ script_pubkey: spk2.clone(),
+ },
+ TxOut {
+ value: Amount::from_sat(30_000),
+ script_pubkey: spk1.clone(),
+ }
+ ]
+ );
+
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, ..1);
+ assert_eq!(
+ sent_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(42_000),
+ script_pubkey: spk1.clone(),
+ }]
+ );
+ assert_eq!(
+ received_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(30_000),
+ script_pubkey: spk1.clone(),
+ }]
+ );
+
+ let (sent_txouts, received_txouts) = index.sent_and_received_txouts(&tx2, 1..);
+ assert!(sent_txouts.is_empty());
+ assert_eq!(
+ received_txouts,
+ vec![TxOut {
+ value: Amount::from_sat(20_000),
+ script_pubkey: spk2.clone(),
+ }]
+ );
+}
+
#[test]
fn mark_used() {
let spk1 = ScriptBuf::from_hex("001404f1e52ce2bab3423c6a8c63b7cd730d8f12542c").unwrap();