Skip to main content

miden_client/note/
mod.rs

1//! Contains the Client APIs related to notes. Notes can contain assets and scripts that are
2//! executed as part of transactions.
3//!
4//! This module enables the tracking, retrieval, and processing of notes.
5//! It offers methods to query input and output notes from the store, check their consumability,
6//! compile note scripts, and retrieve notes based on partial ID matching.
7//!
8//! ## Overview
9//!
10//! The module exposes APIs to:
11//!
12//! - Retrieve input notes and output notes.
13//! - Determine the consumability of notes using the [`NoteScreener`].
14//! - Compile note scripts from source code with `compile_note_script`.
15//! - Retrieve an input note by a prefix of its ID using the helper function
16//!   [`get_input_note_with_id_prefix`].
17//!
18//! ## Example
19//!
20//! ```rust
21//! use miden_client::{
22//!     auth::TransactionAuthenticator,
23//!     Client,
24//!     crypto::FeltRng,
25//!     note::{NoteScreener, get_input_note_with_id_prefix},
26//!     store::NoteFilter,
27//! };
28//! use miden_protocol::account::AccountId;
29//!
30//! # async fn example<AUTH: TransactionAuthenticator + Sync>(client: &Client<AUTH>) -> Result<(), Box<dyn std::error::Error>> {
31//! // Retrieve all committed input notes
32//! let input_notes = client.get_input_notes(NoteFilter::Committed).await?;
33//! println!("Found {} committed input notes.", input_notes.len());
34//!
35//! // Check consumability for a specific note
36//! if let Some(note) = input_notes.first() {
37//!     let consumability = client.get_note_consumability(note.clone()).await?;
38//!     println!("Note consumability: {:?}", consumability);
39//! }
40//!
41//! // Retrieve an input note by a partial ID match
42//! let note_prefix = "0x70b7ec";
43//! match get_input_note_with_id_prefix(client, note_prefix).await {
44//!     Ok(note) => println!(
45//!         "Found note with matching prefix: {}",
46//!         note.id().expect("note matched by ID prefix has an ID").to_hex()
47//!     ),
48//!     Err(err) => println!("Error retrieving note: {err:?}"),
49//! }
50//!
51//! // Compile the note script
52//! let script_src = "@note_script\npub proc main\n    push.9 push.12 add\nend";
53//! let note_script = client.code_builder().compile_note_script(script_src)?;
54//! println!("Compiled note script successfully.");
55//!
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! For more details on the API and error handling, see the documentation for the specific functions
61//! and types in this module.
62
63use alloc::vec::Vec;
64
65use miden_protocol::account::AccountId;
66use miden_tx::auth::TransactionAuthenticator;
67
68use crate::store::{InputNoteRecord, NoteFilter, OutputNoteRecord};
69use crate::{Client, ClientError, IdPrefixFetchError};
70
71mod import;
72mod note_reader;
73mod note_screener;
74mod note_update_tracker;
75
76// RE-EXPORTS
77// ================================================================================================
78
79pub use miden_protocol::block::BlockNumber;
80pub use miden_protocol::errors::NoteError;
81pub use miden_protocol::note::{
82    Note,
83    NoteAssets,
84    NoteAttachment,
85    NoteAttachmentContent,
86    NoteAttachmentHeader,
87    NoteAttachmentScheme,
88    NoteAttachments,
89    NoteDetails,
90    NoteDetailsCommitment,
91    NoteFile,
92    NoteHeader,
93    NoteId,
94    NoteInclusionProof,
95    NoteLocation,
96    NoteMetadata,
97    NoteRecipient,
98    NoteScript,
99    NoteScriptRoot,
100    NoteStorage,
101    NoteTag,
102    NoteType,
103    Nullifier,
104    PartialNote,
105    PartialNoteMetadata,
106};
107pub use miden_protocol::transaction::ToInputNoteCommitments;
108/// Raw access to `miden-standards` note modules for items not curated by `miden-client`.
109pub use miden_standards::note as standards;
110pub use miden_standards::note::{
111    MintNote,
112    MintNoteStorage,
113    NetworkAccountTarget,
114    NoteConsumptionStatus,
115    NoteExecutionHint,
116    P2idNote,
117    P2idNoteStorage,
118    P2ideNote,
119    P2ideNoteStorage,
120    PswapNote,
121    StandardNote,
122    SwapNote,
123};
124pub use miden_tx::{FailedNote, NoteConsumptionInfo};
125pub use note_reader::InputNoteReader;
126pub use note_screener::{NoteConsumability, NoteScreener, NoteScreenerError};
127pub use note_update_tracker::{
128    InputNoteUpdate,
129    NoteConsumption,
130    NoteUpdateTracker,
131    NoteUpdateType,
132    OutputNoteUpdate,
133};
134
135/// Note retrieval methods.
136impl<AUTH> Client<AUTH>
137where
138    AUTH: TransactionAuthenticator + Sync,
139{
140    // INPUT NOTE DATA RETRIEVAL
141    // --------------------------------------------------------------------------------------------
142
143    /// Retrieves the input notes managed by the client from the store.
144    ///
145    /// # Errors
146    ///
147    /// Returns a [`ClientError::StoreError`] if the filter is [`NoteFilter::Unique`] and there is
148    /// no Note with the provided ID.
149    pub async fn get_input_notes(
150        &self,
151        filter: NoteFilter,
152    ) -> Result<Vec<InputNoteRecord>, ClientError> {
153        self.store.get_input_notes(filter).await.map_err(Into::into)
154    }
155
156    /// Returns the input notes and their consumability. Assuming the notes will be consumed by a
157    /// normal consume transaction. If `account_id` is None then all consumable input notes are
158    /// returned.
159    ///
160    /// The note screener runs a series of checks to determine whether the note can be executed as
161    /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
162    /// if it's compatible with the account), it will be returned as part of the result list.
163    pub async fn get_consumable_notes(
164        &self,
165        account_id: Option<AccountId>,
166    ) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
167        let committed_notes = self.store.get_input_notes(NoteFilter::Committed).await?;
168        let notes = committed_notes
169            .iter()
170            .cloned()
171            .map(TryInto::try_into)
172            .collect::<Result<Vec<Note>, _>>()?;
173
174        let note_screener = self.note_screener();
175        let mut note_relevances = note_screener.can_consume_batch(&notes).await?;
176
177        let mut relevant_notes = Vec::new();
178        for input_note in committed_notes {
179            // Committed notes always have metadata, so id() is `Some`.
180            let Some(note_id) = input_note.id() else { continue };
181            let Some(mut account_relevance) = note_relevances.remove(&note_id) else {
182                continue;
183            };
184
185            if let Some(account_id) = account_id {
186                account_relevance.retain(|(id, _)| *id == account_id);
187            }
188
189            if account_relevance.is_empty() {
190                continue;
191            }
192
193            relevant_notes.push((input_note, account_relevance));
194        }
195
196        Ok(relevant_notes)
197    }
198
199    /// Returns the consumability conditions for the provided note.
200    ///
201    /// The note screener runs a series of checks to determine whether the note can be executed as
202    /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
203    /// if it's compatible with the account), it will be returned as part of the result list.
204    pub async fn get_note_consumability(
205        &self,
206        note: InputNoteRecord,
207    ) -> Result<Vec<NoteConsumability>, ClientError> {
208        self.note_screener().can_consume(&note.try_into()?).await.map_err(Into::into)
209    }
210
211    /// Retrieves the input note given a [`NoteId`]. Returns `None` if the note is not found.
212    pub async fn get_input_note(
213        &self,
214        note_id: NoteId,
215    ) -> Result<Option<InputNoteRecord>, ClientError> {
216        Ok(self.store.get_input_notes(NoteFilter::Unique(note_id)).await?.pop())
217    }
218
219    // OUTPUT NOTE DATA RETRIEVAL
220    // --------------------------------------------------------------------------------------------
221
222    /// Returns output notes managed by this client.
223    pub async fn get_output_notes(
224        &self,
225        filter: NoteFilter,
226    ) -> Result<Vec<OutputNoteRecord>, ClientError> {
227        self.store.get_output_notes(filter).await.map_err(Into::into)
228    }
229
230    /// Retrieves the output note given a [`NoteId`]. Returns `None` if the note is not found.
231    pub async fn get_output_note(
232        &self,
233        note_id: NoteId,
234    ) -> Result<Option<OutputNoteRecord>, ClientError> {
235        Ok(self.store.get_output_notes(NoteFilter::Unique(note_id)).await?.pop())
236    }
237
238    /// Returns an [`InputNoteReader`] that lazily iterates over consumed input notes
239    /// for the given consumer account.
240    ///
241    /// The consumer is required because ordering is only guaranteed among notes
242    /// consumed by the same account.
243    ///
244    /// # Example
245    ///
246    /// ```rust,ignore
247    /// let mut reader = client.input_note_reader(account_id);
248    ///
249    /// while let Some(note) = reader.next().await? {
250    ///     process(note);
251    /// }
252    /// ```
253    pub fn input_note_reader(&self, consumer: AccountId) -> InputNoteReader {
254        InputNoteReader::new(self.store.clone(), consumer)
255    }
256}
257
258/// Returns the client input note whose ID starts with `note_id_prefix`.
259///
260/// # Errors
261///
262/// - Returns [`IdPrefixFetchError::NoMatch`] if we were unable to find any note where
263///   `note_id_prefix` is a prefix of its ID.
264/// - Returns [`IdPrefixFetchError::MultipleMatches`] if there were more than one note found where
265///   `note_id_prefix` is a prefix of its ID.
266pub async fn get_input_note_with_id_prefix<AUTH>(
267    client: &Client<AUTH>,
268    note_id_prefix: &str,
269) -> Result<InputNoteRecord, IdPrefixFetchError>
270where
271    AUTH: TransactionAuthenticator + Sync,
272{
273    let mut input_note_records = client
274        .get_input_notes(NoteFilter::All)
275        .await
276        .map_err(|err| {
277            tracing::error!("Error when fetching all notes from the store: {err}");
278            IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}"))
279        })?
280        .into_iter()
281        .filter(|note_record| {
282            note_record.id().is_some_and(|id| id.to_hex().starts_with(note_id_prefix))
283        })
284        .collect::<Vec<_>>();
285
286    if input_note_records.is_empty() {
287        return Err(IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}")));
288    }
289    if input_note_records.len() > 1 {
290        let input_note_record_ids =
291            input_note_records.iter().map(InputNoteRecord::id).collect::<Vec<_>>();
292        tracing::error!(
293            "Multiple notes found for the prefix {}: {:?}",
294            note_id_prefix,
295            input_note_record_ids
296        );
297        return Err(IdPrefixFetchError::MultipleMatches(format!(
298            "note ID prefix {note_id_prefix}"
299        )));
300    }
301
302    Ok(input_note_records
303        .pop()
304        .expect("input_note_records should always have one element"))
305}