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