]> Untitled Git - bdk/commitdiff
feat(chain): add input/output indices to sent_and_received_txouts
authorSteve Myers <steve@notmandatory.org>
Thu, 11 Dec 2025 17:26:25 +0000 (11:26 -0600)
committerSteve Myers <steve@notmandatory.org>
Wed, 28 Jan 2026 21:52:15 +0000 (15:52 -0600)
Return tuple of (index, TxOut) in sent_and_received_txouts methods to
identify which input/output positions the TxOuts correspond to in the
original transaction.

crates/chain/src/indexer.rs
crates/chain/src/indexer/keychain_txout.rs
crates/chain/src/indexer/spk_txout.rs
crates/chain/src/lib.rs
crates/chain/tests/test_spk_txout_index.rs

index 22e8398152be2b3e74be4474d8c071388291f426..75d5ad4324588ba7cedb89a511e69501da03c055 100644 (file)
@@ -1,11 +1,18 @@
 //! [`Indexer`] provides utilities for indexing transaction data.
 
+use alloc::vec::Vec;
 use bitcoin::{OutPoint, Transaction, TxOut};
 
 #[cfg(feature = "miniscript")]
 pub mod keychain_txout;
 pub mod spk_txout;
 
+/// Type alias for a list of indexed transaction outputs.
+///
+/// Each element is a tuple of `(index, TxOut)` where index is the index of the input or output in
+/// the original [`Transaction`].
+pub type IndexedTxOuts = Vec<(usize, TxOut)>;
+
 /// Utilities for indexing transaction data.
 ///
 /// Types which implement this trait can be used to construct an [`IndexedTxGraph`].
index b243460e7d600b999072024f6cd8f8f364cac423..df84dc2c6ea76a4e4e0a7f91601294eb9d5b447f 100644 (file)
@@ -8,7 +8,7 @@ use crate::{
     spk_client::{FullScanRequestBuilder, SyncRequestBuilder},
     spk_iter::BIP32_MAX_INDEX,
     spk_txout::SpkTxOutIndex,
-    DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
+    DescriptorExt, DescriptorId, Indexed, IndexedTxOuts, Indexer, KeychainIndexed, SpkIterator,
 };
 use alloc::{borrow::ToOwned, vec::Vec};
 use bitcoin::{
@@ -428,7 +428,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
         &self,
         tx: &Transaction,
         range: impl RangeBounds<K>,
-    ) -> (Vec<TxOut>, Vec<TxOut>) {
+    ) -> (IndexedTxOuts, IndexedTxOuts) {
         self.inner
             .sent_and_received_txouts(tx, self.map_to_inner_bounds(range))
     }
index a66ffef94bd828a34fa0d517efd58a24e8953a9e..73aee0c8b50390427ceb427a9dd283dc42d30fee 100644 (file)
@@ -5,7 +5,7 @@ use core::ops::RangeBounds;
 
 use crate::{
     collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
-    Indexer,
+    IndexedTxOuts, Indexer,
 };
 use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
 
@@ -348,15 +348,15 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
     ///
     /// // Display addresses and amounts
     /// println!("Sent:");
-    /// for txout in sent_txouts {
+    /// for (i, txout) in sent_txouts {
     ///     let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
-    ///     println!(" from {} - {} sats", address, txout.value.to_sat());
+    ///     println!("input {}: from {} - {} sats", i, address, txout.value.to_sat());
     /// }
     ///
     /// println!("Received:");
-    /// for txout in received_txouts {
+    /// for (i, txout) in received_txouts {
     ///     let address = Address::from_script(&txout.script_pubkey, Network::Bitcoin)?;
-    ///     println!(" to {} - {} sats", address, txout.value.to_sat());
+    ///     println!("output {}: to {} + {} sats", i, address, txout.value.to_sat());
     /// }
     /// # Ok(())
     /// # }
@@ -365,22 +365,22 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
         &self,
         tx: &Transaction,
         range: impl RangeBounds<I>,
-    ) -> (Vec<TxOut>, Vec<TxOut>) {
+    ) -> (IndexedTxOuts, IndexedTxOuts) {
         let mut sent = Vec::new();
         let mut received = Vec::new();
 
-        for txin in &tx.input {
+        for (i, txin) in tx.input.iter().enumerate() {
             if let Some((index, txout)) = self.txout(txin.previous_output) {
                 if range.contains(index) {
-                    sent.push(txout.clone());
+                    sent.push((i, txout.clone()));
                 }
             }
         }
 
-        for txout in &tx.output {
+        for (i, txout) in tx.output.iter().enumerate() {
             if let Some(index) = self.index_of_spk(txout.script_pubkey.clone()) {
                 if range.contains(index) {
-                    received.push(txout.clone());
+                    received.push((i, txout.clone()));
                 }
             }
         }
index be9170b1a5b310161ec41aa5e392a0cd768c743a..e81fef78e0644e630095fca819dfd42eeab8e82d 100644 (file)
@@ -36,7 +36,7 @@ pub mod indexed_tx_graph;
 pub use indexed_tx_graph::IndexedTxGraph;
 pub mod indexer;
 pub use indexer::spk_txout;
-pub use indexer::Indexer;
+pub use indexer::{IndexedTxOuts, Indexer};
 pub mod local_chain;
 mod tx_data_traits;
 pub use tx_data_traits::*;
index 537add2aee216056fad994524ca2263ca78ff398..ea460bbbc10b06ca1f4a19deb94fda6b7f6a5dca 100644 (file)
@@ -98,24 +98,29 @@ fn spk_txout_sent_and_received_txouts() {
             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(),
-        }]
+        vec![(
+            0,
+            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(),
-        }]
+        vec![(
+            0,
+            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());
@@ -147,49 +152,67 @@ fn spk_txout_sent_and_received_txouts() {
     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(),
-        }]
+        vec![(
+            0,
+            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(),
-            }
+            (
+                0,
+                TxOut {
+                    value: Amount::from_sat(20_000),
+                    script_pubkey: spk2.clone(),
+                }
+            ),
+            (
+                1,
+                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(),
-        }]
+        vec![(
+            0,
+            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(),
-        }]
+        vec![(
+            1,
+            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(),
-        }]
+        vec![(
+            0,
+            TxOut {
+                value: Amount::from_sat(20_000),
+                script_pubkey: spk2.clone(),
+            }
+        )]
     );
 }