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