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