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::{
60    collections::{BTreeMap, BTreeSet},
61    string::ToString,
62    vec::Vec,
63};
64
65use miden_lib::transaction::TransactionKernel;
66use miden_objects::account::AccountId;
67
68use crate::{
69    Client, ClientError, IdPrefixFetchError,
70    store::{InputNoteRecord, NoteFilter, OutputNoteRecord},
71};
72
73pub mod script_roots;
74
75mod import;
76mod note_screener;
77
78// RE-EXPORTS
79// ================================================================================================
80
81pub use miden_lib::note::{
82    create_p2id_note, create_p2idr_note, create_swap_note,
83    utils::{build_p2id_recipient, build_swap_tag},
84    well_known_note::WellKnownNote,
85};
86pub use miden_objects::{
87    NoteError,
88    block::BlockNumber,
89    note::{
90        Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteId,
91        NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType,
92        Nullifier,
93    },
94};
95pub use note_screener::{NoteConsumability, NoteRelevance, NoteScreener, NoteScreenerError};
96
97/// Note retrieval methods.
98impl Client {
99    // INPUT NOTE DATA RETRIEVAL
100    // --------------------------------------------------------------------------------------------
101
102    /// Retrieves the input notes managed by the client from the store.
103    ///
104    /// # Errors
105    ///
106    /// Returns a [`ClientError::StoreError`] if the filter is [`NoteFilter::Unique`] and there is
107    /// no Note with the provided ID.
108    pub async fn get_input_notes(
109        &self,
110        filter: NoteFilter,
111    ) -> Result<Vec<InputNoteRecord>, ClientError> {
112        self.store.get_input_notes(filter).await.map_err(Into::into)
113    }
114
115    /// Returns the input notes and their consumability.
116    ///
117    /// If `account_id` is None then all consumable input notes are returned.
118    pub async fn get_consumable_notes(
119        &self,
120        account_id: Option<AccountId>,
121    ) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
122        let commited_notes = self.store.get_input_notes(NoteFilter::Committed).await?;
123
124        let note_screener = NoteScreener::new(self.store.clone());
125
126        let mut relevant_notes = Vec::new();
127        for input_note in commited_notes {
128            let mut account_relevance =
129                note_screener.check_relevance(&input_note.clone().try_into()?).await?;
130
131            if let Some(account_id) = account_id {
132                account_relevance.retain(|(id, _)| *id == account_id);
133            }
134
135            if account_relevance.is_empty() {
136                continue;
137            }
138
139            relevant_notes.push((input_note, account_relevance));
140        }
141
142        Ok(relevant_notes)
143    }
144
145    /// Returns the consumability of the provided note.
146    pub async fn get_note_consumability(
147        &self,
148        note: InputNoteRecord,
149    ) -> Result<Vec<NoteConsumability>, ClientError> {
150        let note_screener = NoteScreener::new(self.store.clone());
151        note_screener
152            .check_relevance(&note.clone().try_into()?)
153            .await
154            .map_err(Into::into)
155    }
156
157    /// Retrieves the input note given a [`NoteId`]. Returns `None` if the note is not found.
158    pub async fn get_input_note(
159        &self,
160        note_id: NoteId,
161    ) -> Result<Option<InputNoteRecord>, ClientError> {
162        Ok(self.store.get_input_notes(NoteFilter::Unique(note_id)).await?.pop())
163    }
164
165    // OUTPUT NOTE DATA RETRIEVAL
166    // --------------------------------------------------------------------------------------------
167
168    /// Returns output notes managed by this client.
169    pub async fn get_output_notes(
170        &self,
171        filter: NoteFilter,
172    ) -> Result<Vec<OutputNoteRecord>, ClientError> {
173        self.store.get_output_notes(filter).await.map_err(Into::into)
174    }
175
176    /// Retrieves the output note given a [`NoteId`]. Returns `None` if the note is not found.
177    pub async fn get_output_note(
178        &self,
179        note_id: NoteId,
180    ) -> Result<Option<OutputNoteRecord>, ClientError> {
181        Ok(self.store.get_output_notes(NoteFilter::Unique(note_id)).await?.pop())
182    }
183
184    /// Compiles the provided program into a [`NoteScript`].
185    ///
186    /// The assembler uses the debug mode if the client was instantiated with debug mode on.
187    pub fn compile_note_script(&self, note_script: &str) -> Result<NoteScript, ClientError> {
188        let assembler = TransactionKernel::assembler().with_debug_mode(self.in_debug_mode);
189        NoteScript::compile(note_script, assembler).map_err(ClientError::NoteError)
190    }
191}
192
193/// Returns the client input note whose ID starts with `note_id_prefix`.
194///
195/// # Errors
196///
197/// - Returns [`IdPrefixFetchError::NoMatch`] if we were unable to find any note where
198///   `note_id_prefix` is a prefix of its ID.
199/// - Returns [`IdPrefixFetchError::MultipleMatches`] if there were more than one note found where
200///   `note_id_prefix` is a prefix of its ID.
201pub async fn get_input_note_with_id_prefix(
202    client: &Client,
203    note_id_prefix: &str,
204) -> Result<InputNoteRecord, IdPrefixFetchError> {
205    let mut input_note_records = client
206        .get_input_notes(NoteFilter::All)
207        .await
208        .map_err(|err| {
209            tracing::error!("Error when fetching all notes from the store: {err}");
210            IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}").to_string())
211        })?
212        .into_iter()
213        .filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
214        .collect::<Vec<_>>();
215
216    if input_note_records.is_empty() {
217        return Err(IdPrefixFetchError::NoMatch(
218            format!("note ID prefix {note_id_prefix}").to_string(),
219        ));
220    }
221    if input_note_records.len() > 1 {
222        let input_note_record_ids =
223            input_note_records.iter().map(InputNoteRecord::id).collect::<Vec<_>>();
224        tracing::error!(
225            "Multiple notes found for the prefix {}: {:?}",
226            note_id_prefix,
227            input_note_record_ids
228        );
229        return Err(IdPrefixFetchError::MultipleMatches(
230            format!("note ID prefix {note_id_prefix}").to_string(),
231        ));
232    }
233
234    Ok(input_note_records
235        .pop()
236        .expect("input_note_records should always have one element"))
237}
238
239// NOTE UPDATES
240// ------------------------------------------------------------------------------------------------
241
242/// Contains note changes to apply to the store.
243#[derive(Clone, Debug, Default)]
244pub struct NoteUpdates {
245    /// A map of new and updated input note records to be upserted in the store.
246    updated_input_notes: BTreeMap<NoteId, InputNoteRecord>,
247    /// A map of updated output note records to be upserted in the store.
248    updated_output_notes: BTreeMap<NoteId, OutputNoteRecord>,
249}
250
251impl NoteUpdates {
252    /// Creates a [`NoteUpdates`].
253    pub fn new(
254        updated_input_notes: impl IntoIterator<Item = InputNoteRecord>,
255        updated_output_notes: impl IntoIterator<Item = OutputNoteRecord>,
256    ) -> Self {
257        Self {
258            updated_input_notes: updated_input_notes
259                .into_iter()
260                .map(|note| (note.id(), note))
261                .collect(),
262            updated_output_notes: updated_output_notes
263                .into_iter()
264                .map(|note| (note.id(), note))
265                .collect(),
266        }
267    }
268
269    /// Returns all input note records that have been updated.
270    /// This may include:
271    /// - New notes that have been created that should be inserted.
272    /// - Existing tracked notes that should be updated.
273    pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteRecord> {
274        self.updated_input_notes.values()
275    }
276
277    /// Returns all output note records that have been updated.
278    /// This may include:
279    /// - New notes that have been created that should be inserted.
280    /// - Existing tracked notes that should be updated.
281    pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteRecord> {
282        self.updated_output_notes.values()
283    }
284
285    /// Returns whether no new note-related information has been retrieved.
286    pub fn is_empty(&self) -> bool {
287        self.updated_input_notes.is_empty() && self.updated_output_notes.is_empty()
288    }
289
290    /// Returns any note that has been committed into the chain in this update (either new or
291    /// already locally tracked)
292    pub fn committed_input_notes(&self) -> impl Iterator<Item = &InputNoteRecord> {
293        self.updated_input_notes.values().filter(|note| note.is_committed())
294    }
295
296    /// Returns the IDs of all notes that have been committed in this update.
297    /// This includes both new notes and tracked expected notes that were committed in this update.
298    pub fn committed_note_ids(&self) -> BTreeSet<NoteId> {
299        let committed_output_note_ids = self
300            .updated_output_notes
301            .values()
302            .filter_map(|note_record| note_record.is_committed().then_some(note_record.id()));
303
304        let committed_input_note_ids = self
305            .updated_input_notes
306            .values()
307            .filter_map(|note_record| note_record.is_committed().then_some(note_record.id()));
308
309        committed_input_note_ids
310            .chain(committed_output_note_ids)
311            .collect::<BTreeSet<_>>()
312    }
313
314    /// Returns the IDs of all notes that have been consumed.
315    /// This includes both notes that have been consumed locally or externally in this update.
316    pub fn consumed_note_ids(&self) -> BTreeSet<NoteId> {
317        let consumed_output_note_ids = self
318            .updated_output_notes
319            .values()
320            .filter_map(|note_record| note_record.is_consumed().then_some(note_record.id()));
321
322        let consumed_input_note_ids = self
323            .updated_input_notes
324            .values()
325            .filter_map(|note_record| note_record.is_consumed().then_some(note_record.id()));
326
327        consumed_input_note_ids.chain(consumed_output_note_ids).collect::<BTreeSet<_>>()
328    }
329}