Skip to main content

miden_client/note/
note_reader.rs

1//! Provides a lazy iterator over consumed input notes.
2
3use alloc::sync::Arc;
4
5use miden_protocol::account::AccountId;
6use miden_protocol::block::BlockNumber;
7
8use crate::ClientError;
9use crate::store::{InputNoteRecord, NoteFilter, Store};
10
11/// A lazy iterator over consumed input notes for a specific consumer account.
12///
13/// Each call to [`InputNoteReader::next`] executes a store query and returns the
14/// next matching note. Use builder methods to configure filters before iterating.
15///
16/// # Ordering
17///
18/// Notes are returned in on-chain consumption order: first by block number, then by
19/// per-account transaction order within the block.
20pub struct InputNoteReader {
21    store: Arc<dyn Store>,
22    consumer: AccountId,
23    block_range: Option<(BlockNumber, BlockNumber)>,
24    offset: u32,
25}
26
27impl InputNoteReader {
28    /// Creates a new `InputNoteReader` that iterates over consumed input notes
29    /// for the given consumer account.
30    ///
31    /// The consumer is required because ordering is only guaranteed among notes
32    /// consumed by the same account.
33    pub fn new(store: Arc<dyn Store>, consumer: AccountId) -> Self {
34        Self {
35            store,
36            consumer,
37            block_range: None,
38            offset: 0,
39        }
40    }
41
42    /// Restricts iteration to notes consumed within the given block range (inclusive).
43    #[must_use]
44    pub fn in_block_range(mut self, from: BlockNumber, to: BlockNumber) -> Self {
45        self.block_range = Some((from, to));
46        self
47    }
48
49    /// Resets the iterator to the beginning.
50    pub fn reset(&mut self) {
51        self.offset = 0;
52    }
53
54    /// Returns the next consumed input note, or `None` when all matching notes have been
55    /// returned.
56    ///
57    /// Each call executes a single store query.
58    pub async fn next(&mut self) -> Result<Option<InputNoteRecord>, ClientError> {
59        let (block_start, block_end) = match self.block_range {
60            Some((from, to)) => (Some(from), Some(to)),
61            None => (None, None),
62        };
63
64        // TODO: The note filter should be configurable instead of hardcoding `NoteFilter::Consumed`
65        let note = self
66            .store
67            .get_input_note_by_offset(
68                NoteFilter::Consumed,
69                self.consumer,
70                block_start,
71                block_end,
72                self.offset,
73            )
74            .await
75            .map_err(ClientError::StoreError)?;
76
77        if note.is_some() {
78            self.offset += 1;
79        }
80        Ok(note)
81    }
82}