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