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(&note.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}