1use 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
78pub 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
97impl Client {
99 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 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 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(¬e.clone().try_into()?)
153 .await
154 .map_err(Into::into)
155 }
156
157 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 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 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 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
193pub 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#[derive(Clone, Debug, Default)]
244pub struct NoteUpdates {
245 updated_input_notes: BTreeMap<NoteId, InputNoteRecord>,
247 updated_output_notes: BTreeMap<NoteId, OutputNoteRecord>,
249}
250
251impl NoteUpdates {
252 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 pub fn updated_input_notes(&self) -> impl Iterator<Item = &InputNoteRecord> {
274 self.updated_input_notes.values()
275 }
276
277 pub fn updated_output_notes(&self) -> impl Iterator<Item = &OutputNoteRecord> {
282 self.updated_output_notes.values()
283 }
284
285 pub fn is_empty(&self) -> bool {
287 self.updated_input_notes.is_empty() && self.updated_output_notes.is_empty()
288 }
289
290 pub fn committed_input_notes(&self) -> impl Iterator<Item = &InputNoteRecord> {
293 self.updated_input_notes.values().filter(|note| note.is_committed())
294 }
295
296 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 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}