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!("Found note with matching prefix: {}", note.id().to_hex()),
45//!     Err(err) => println!("Error retrieving note: {err:?}"),
46//! }
47//!
48//! // Compile the note script
49//! let script_src = "begin push.9 push.12 add end";
50//! let note_script = client.code_builder().compile_note_script(script_src)?;
51//! println!("Compiled note script successfully.");
52//!
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! For more details on the API and error handling, see the documentation for the specific functions
58//! and types in this module.
59
60use alloc::vec::Vec;
61
62use miden_protocol::account::AccountId;
63use miden_tx::auth::TransactionAuthenticator;
64
65use crate::store::{InputNoteRecord, NoteFilter, OutputNoteRecord};
66use crate::{Client, ClientError, IdPrefixFetchError};
67
68mod import;
69mod note_screener;
70mod note_update_tracker;
71
72// RE-EXPORTS
73// ================================================================================================
74
75pub use miden_protocol::block::BlockNumber;
76pub use miden_protocol::errors::NoteError;
77pub use miden_protocol::note::{
78    Note,
79    NoteAssets,
80    NoteAttachment,
81    NoteAttachmentKind,
82    NoteAttachmentScheme,
83    NoteDetails,
84    NoteFile,
85    NoteHeader,
86    NoteId,
87    NoteInclusionProof,
88    NoteLocation,
89    NoteMetadata,
90    NoteRecipient,
91    NoteScript,
92    NoteStorage,
93    NoteTag,
94    NoteType,
95    Nullifier,
96    PartialNote,
97};
98pub use miden_protocol::transaction::ToInputNoteCommitments;
99pub use miden_standards::note::{
100    NetworkAccountTarget,
101    NoteConsumptionStatus,
102    NoteExecutionHint,
103    P2idNote,
104    P2idNoteStorage,
105    StandardNote,
106    SwapNote,
107};
108pub use miden_tx::{FailedNote, NoteConsumptionInfo};
109pub use note_screener::{NoteConsumability, NoteScreener, NoteScreenerError};
110pub use note_update_tracker::{
111    InputNoteUpdate,
112    NoteUpdateTracker,
113    NoteUpdateType,
114    OutputNoteUpdate,
115};
116/// Note retrieval methods.
117impl<AUTH> Client<AUTH>
118where
119    AUTH: TransactionAuthenticator + Sync,
120{
121    // INPUT NOTE DATA RETRIEVAL
122    // --------------------------------------------------------------------------------------------
123
124    /// Retrieves the input notes managed by the client from the store.
125    ///
126    /// # Errors
127    ///
128    /// Returns a [`ClientError::StoreError`] if the filter is [`NoteFilter::Unique`] and there is
129    /// no Note with the provided ID.
130    pub async fn get_input_notes(
131        &self,
132        filter: NoteFilter,
133    ) -> Result<Vec<InputNoteRecord>, ClientError> {
134        self.store.get_input_notes(filter).await.map_err(Into::into)
135    }
136
137    /// Returns the input notes and their consumability. Assuming the notes will be consumed by a
138    /// normal consume transaction. If `account_id` is None then all consumable input notes are
139    /// returned.
140    ///
141    /// The note screener runs a series of checks to determine whether the note can be executed as
142    /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
143    /// if it's compatible with the account), it will be returned as part of the result list.
144    pub async fn get_consumable_notes(
145        &self,
146        account_id: Option<AccountId>,
147    ) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
148        let committed_notes = self.store.get_input_notes(NoteFilter::Committed).await?;
149        let notes = committed_notes
150            .iter()
151            .cloned()
152            .map(TryInto::try_into)
153            .collect::<Result<Vec<Note>, _>>()?;
154
155        let note_screener = self.note_screener();
156        let mut note_relevances = note_screener.can_consume_batch(&notes).await?;
157
158        let mut relevant_notes = Vec::new();
159        for input_note in committed_notes {
160            let note_id = input_note.id();
161            let Some(mut account_relevance) = note_relevances.remove(&note_id) else {
162                continue;
163            };
164
165            if let Some(account_id) = account_id {
166                account_relevance.retain(|(id, _)| *id == account_id);
167            }
168
169            if account_relevance.is_empty() {
170                continue;
171            }
172
173            relevant_notes.push((input_note, account_relevance));
174        }
175
176        Ok(relevant_notes)
177    }
178
179    /// Returns the consumability conditions for the provided note.
180    ///
181    /// The note screener runs a series of checks to determine whether the note can be executed as
182    /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
183    /// if it's compatible with the account), it will be returned as part of the result list.
184    pub async fn get_note_consumability(
185        &self,
186        note: InputNoteRecord,
187    ) -> Result<Vec<NoteConsumability>, ClientError> {
188        self.note_screener().can_consume(&note.try_into()?).await.map_err(Into::into)
189    }
190
191    /// Retrieves the input note given a [`NoteId`]. Returns `None` if the note is not found.
192    pub async fn get_input_note(
193        &self,
194        note_id: NoteId,
195    ) -> Result<Option<InputNoteRecord>, ClientError> {
196        Ok(self.store.get_input_notes(NoteFilter::Unique(note_id)).await?.pop())
197    }
198
199    // OUTPUT NOTE DATA RETRIEVAL
200    // --------------------------------------------------------------------------------------------
201
202    /// Returns output notes managed by this client.
203    pub async fn get_output_notes(
204        &self,
205        filter: NoteFilter,
206    ) -> Result<Vec<OutputNoteRecord>, ClientError> {
207        self.store.get_output_notes(filter).await.map_err(Into::into)
208    }
209
210    /// Retrieves the output note given a [`NoteId`]. Returns `None` if the note is not found.
211    pub async fn get_output_note(
212        &self,
213        note_id: NoteId,
214    ) -> Result<Option<OutputNoteRecord>, ClientError> {
215        Ok(self.store.get_output_notes(NoteFilter::Unique(note_id)).await?.pop())
216    }
217}
218
219/// Returns the client input note whose ID starts with `note_id_prefix`.
220///
221/// # Errors
222///
223/// - Returns [`IdPrefixFetchError::NoMatch`] if we were unable to find any note where
224///   `note_id_prefix` is a prefix of its ID.
225/// - Returns [`IdPrefixFetchError::MultipleMatches`] if there were more than one note found where
226///   `note_id_prefix` is a prefix of its ID.
227pub async fn get_input_note_with_id_prefix<AUTH>(
228    client: &Client<AUTH>,
229    note_id_prefix: &str,
230) -> Result<InputNoteRecord, IdPrefixFetchError>
231where
232    AUTH: TransactionAuthenticator + Sync,
233{
234    let mut input_note_records = client
235        .get_input_notes(NoteFilter::All)
236        .await
237        .map_err(|err| {
238            tracing::error!("Error when fetching all notes from the store: {err}");
239            IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}"))
240        })?
241        .into_iter()
242        .filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
243        .collect::<Vec<_>>();
244
245    if input_note_records.is_empty() {
246        return Err(IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}")));
247    }
248    if input_note_records.len() > 1 {
249        let input_note_record_ids =
250            input_note_records.iter().map(InputNoteRecord::id).collect::<Vec<_>>();
251        tracing::error!(
252            "Multiple notes found for the prefix {}: {:?}",
253            note_id_prefix,
254            input_note_record_ids
255        );
256        return Err(IdPrefixFetchError::MultipleMatches(format!(
257            "note ID prefix {note_id_prefix}"
258        )));
259    }
260
261    Ok(input_note_records
262        .pop()
263        .expect("input_note_records should always have one element"))
264}