Skip to main content

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