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//! Client,
23//! crypto::FeltRng,
24//! note::{NoteScreener, get_input_note_with_id_prefix},
25//! store::NoteFilter,
26//! };
27//! use miden_objects::account::AccountId;
28//!
29//! # async fn example(client: &Client) -> Result<(), Box<dyn std::error::Error>> {
30//! // Retrieve all committed input notes
31//! let input_notes = client.get_input_notes(NoteFilter::Committed).await?;
32//! println!("Found {} committed input notes.", input_notes.len());
33//!
34//! // Check consumability for a specific note
35//! if let Some(note) = input_notes.first() {
36//! let consumability = client.get_note_consumability(note.clone()).await?;
37//! println!("Note consumability: {:?}", consumability);
38//! }
39//!
40//! // Retrieve an input note by a partial ID match
41//! let note_prefix = "0x70b7ec";
42//! match get_input_note_with_id_prefix(client, note_prefix).await {
43//! Ok(note) => println!("Found note with matching prefix: {}", note.id().to_hex()),
44//! Err(err) => println!("Error retrieving note: {err:?}"),
45//! }
46//!
47//! // Compile the note script
48//! let script_src = "begin push.9 push.12 add end";
49//! let note_script = client.script_builder().compile_note_script(script_src)?;
50//! println!("Compiled note script successfully.");
51//!
52//! # Ok(())
53//! # }
54//! ```
55//!
56//! For more details on the API and error handling, see the documentation for the specific functions
57//! and types in this module.
58
59use alloc::{string::ToString, vec::Vec};
60
61use miden_objects::account::AccountId;
62
63use crate::{
64 Client, ClientError, IdPrefixFetchError,
65 store::{InputNoteRecord, NoteFilter, OutputNoteRecord},
66};
67
68mod import;
69mod note_screener;
70mod note_update_tracker;
71
72// RE-EXPORTS
73// ================================================================================================
74
75pub use miden_lib::note::{
76 create_p2id_note, create_swap_note,
77 utils::{build_p2id_recipient, build_swap_tag},
78 well_known_note::WellKnownNote,
79};
80pub use miden_objects::{
81 NoteError,
82 block::BlockNumber,
83 note::{
84 Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteId,
85 NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType,
86 Nullifier,
87 },
88};
89pub use note_screener::{NoteConsumability, NoteRelevance, NoteScreener, NoteScreenerError};
90pub use note_update_tracker::{
91 InputNoteUpdate, NoteUpdateTracker, NoteUpdateType, OutputNoteUpdate,
92};
93
94/// Note retrieval methods.
95impl Client {
96 // INPUT NOTE DATA RETRIEVAL
97 // --------------------------------------------------------------------------------------------
98
99 /// Retrieves the input notes managed by the client from the store.
100 ///
101 /// # Errors
102 ///
103 /// Returns a [`ClientError::StoreError`] if the filter is [`NoteFilter::Unique`] and there is
104 /// no Note with the provided ID.
105 pub async fn get_input_notes(
106 &self,
107 filter: NoteFilter,
108 ) -> Result<Vec<InputNoteRecord>, ClientError> {
109 self.store.get_input_notes(filter).await.map_err(Into::into)
110 }
111
112 /// Returns the input notes and their consumability. Assuming the notes will be consumed by a
113 /// normal consume transaction. If `account_id` is None then all consumable input notes are
114 /// returned.
115 ///
116 /// The note screener runs a series of checks to determine whether the note can be executed as
117 /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
118 /// if it's compatible with the account), it will be returned as part of the result list.
119 pub async fn get_consumable_notes(
120 &self,
121 account_id: Option<AccountId>,
122 ) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
123 let committed_notes = self.store.get_input_notes(NoteFilter::Committed).await?;
124
125 let note_screener = NoteScreener::new(self.store.clone(), self.authenticator.clone());
126
127 let mut relevant_notes = Vec::new();
128 for input_note in committed_notes {
129 let mut account_relevance =
130 note_screener.check_relevance(&input_note.clone().try_into()?).await?;
131
132 if let Some(account_id) = account_id {
133 account_relevance.retain(|(id, _)| *id == account_id);
134 }
135
136 if account_relevance.is_empty() {
137 continue;
138 }
139
140 relevant_notes.push((input_note, account_relevance));
141 }
142
143 Ok(relevant_notes)
144 }
145
146 /// Returns the consumability conditions for the provided note.
147 ///
148 /// The note screener runs a series of checks to determine whether the note can be executed as
149 /// part of a transaction for a specific account. If the specific account ID can consume it (ie,
150 /// if it's compatible with the account), it will be returned as part of the result list.
151 pub async fn get_note_consumability(
152 &self,
153 note: InputNoteRecord,
154 ) -> Result<Vec<NoteConsumability>, ClientError> {
155 let note_screener = NoteScreener::new(self.store.clone(), self.authenticator.clone());
156 note_screener
157 .check_relevance(¬e.clone().try_into()?)
158 .await
159 .map_err(Into::into)
160 }
161
162 /// Retrieves the input note given a [`NoteId`]. Returns `None` if the note is not found.
163 pub async fn get_input_note(
164 &self,
165 note_id: NoteId,
166 ) -> Result<Option<InputNoteRecord>, ClientError> {
167 Ok(self.store.get_input_notes(NoteFilter::Unique(note_id)).await?.pop())
168 }
169
170 // OUTPUT NOTE DATA RETRIEVAL
171 // --------------------------------------------------------------------------------------------
172
173 /// Returns output notes managed by this client.
174 pub async fn get_output_notes(
175 &self,
176 filter: NoteFilter,
177 ) -> Result<Vec<OutputNoteRecord>, ClientError> {
178 self.store.get_output_notes(filter).await.map_err(Into::into)
179 }
180
181 /// Retrieves the output note given a [`NoteId`]. Returns `None` if the note is not found.
182 pub async fn get_output_note(
183 &self,
184 note_id: NoteId,
185 ) -> Result<Option<OutputNoteRecord>, ClientError> {
186 Ok(self.store.get_output_notes(NoteFilter::Unique(note_id)).await?.pop())
187 }
188}
189
190/// Returns the client input note whose ID starts with `note_id_prefix`.
191///
192/// # Errors
193///
194/// - Returns [`IdPrefixFetchError::NoMatch`] if we were unable to find any note where
195/// `note_id_prefix` is a prefix of its ID.
196/// - Returns [`IdPrefixFetchError::MultipleMatches`] if there were more than one note found where
197/// `note_id_prefix` is a prefix of its ID.
198pub async fn get_input_note_with_id_prefix(
199 client: &Client,
200 note_id_prefix: &str,
201) -> Result<InputNoteRecord, IdPrefixFetchError> {
202 let mut input_note_records = client
203 .get_input_notes(NoteFilter::All)
204 .await
205 .map_err(|err| {
206 tracing::error!("Error when fetching all notes from the store: {err}");
207 IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}").to_string())
208 })?
209 .into_iter()
210 .filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
211 .collect::<Vec<_>>();
212
213 if input_note_records.is_empty() {
214 return Err(IdPrefixFetchError::NoMatch(
215 format!("note ID prefix {note_id_prefix}").to_string(),
216 ));
217 }
218 if input_note_records.len() > 1 {
219 let input_note_record_ids =
220 input_note_records.iter().map(InputNoteRecord::id).collect::<Vec<_>>();
221 tracing::error!(
222 "Multiple notes found for the prefix {}: {:?}",
223 note_id_prefix,
224 input_note_record_ids
225 );
226 return Err(IdPrefixFetchError::MultipleMatches(
227 format!("note ID prefix {note_id_prefix}").to_string(),
228 ));
229 }
230
231 Ok(input_note_records
232 .pop()
233 .expect("input_note_records should always have one element"))
234}