miden_client/notes/
mod.rsuse alloc::{collections::BTreeSet, string::ToString, vec::Vec};
use miden_lib::transaction::TransactionKernel;
use miden_objects::{accounts::AccountId, crypto::rand::FeltRng};
use crate::{
store::{InputNoteRecord, NoteFilter, OutputNoteRecord},
Client, ClientError, IdPrefixFetchError,
};
pub mod script_roots;
mod import;
mod note_screener;
pub use miden_lib::notes::{
create_p2id_note, create_p2idr_note, create_swap_note,
utils::{build_p2id_recipient, build_swap_tag},
};
pub use miden_objects::{
notes::{
Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteFile, NoteId,
NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType,
Nullifier,
},
NoteError,
};
pub use note_screener::{NoteConsumability, NoteRelevance, NoteScreener, NoteScreenerError};
impl<R: FeltRng> Client<R> {
pub async fn get_input_notes(
&self,
filter: NoteFilter,
) -> Result<Vec<InputNoteRecord>, ClientError> {
self.store.get_input_notes(filter).await.map_err(|err| err.into())
}
pub async fn get_consumable_notes(
&self,
account_id: Option<AccountId>,
) -> Result<Vec<(InputNoteRecord, Vec<NoteConsumability>)>, ClientError> {
let commited_notes = self.store.get_input_notes(NoteFilter::Committed).await?;
let note_screener = NoteScreener::new(self.store.clone());
let mut relevant_notes = Vec::new();
for input_note in commited_notes {
let mut account_relevance =
note_screener.check_relevance(&input_note.clone().try_into()?).await?;
if let Some(account_id) = account_id {
account_relevance.retain(|(id, _)| *id == account_id);
}
if account_relevance.is_empty() {
continue;
}
relevant_notes.push((input_note, account_relevance));
}
Ok(relevant_notes)
}
pub async fn get_note_consumability(
&self,
note: InputNoteRecord,
) -> Result<Vec<NoteConsumability>, ClientError> {
let note_screener = NoteScreener::new(self.store.clone());
note_screener
.check_relevance(¬e.clone().try_into()?)
.await
.map_err(|err| err.into())
}
pub async fn get_input_note(&self, note_id: NoteId) -> Result<InputNoteRecord, ClientError> {
Ok(self
.store
.get_input_notes(NoteFilter::Unique(note_id))
.await?
.pop()
.expect("The vector always has one element for NoteFilter::Unique"))
}
pub async fn get_output_notes(
&self,
filter: NoteFilter,
) -> Result<Vec<OutputNoteRecord>, ClientError> {
self.store.get_output_notes(filter).await.map_err(|err| err.into())
}
pub async fn get_output_note(&self, note_id: NoteId) -> Result<OutputNoteRecord, ClientError> {
Ok(self
.store
.get_output_notes(NoteFilter::Unique(note_id))
.await?
.pop()
.expect("The vector always has one element for NoteFilter::Unique"))
}
pub fn compile_note_script(&self, note_script_ast: &str) -> Result<NoteScript, ClientError> {
NoteScript::compile(note_script_ast, TransactionKernel::assembler())
.map_err(ClientError::NoteError)
}
}
pub async fn get_input_note_with_id_prefix<R: FeltRng>(
client: &Client<R>,
note_id_prefix: &str,
) -> Result<InputNoteRecord, IdPrefixFetchError> {
let mut input_note_records = client
.get_input_notes(NoteFilter::All)
.await
.map_err(|err| {
tracing::error!("Error when fetching all notes from the store: {err}");
IdPrefixFetchError::NoMatch(format!("note ID prefix {note_id_prefix}").to_string())
})?
.into_iter()
.filter(|note_record| note_record.id().to_hex().starts_with(note_id_prefix))
.collect::<Vec<_>>();
if input_note_records.is_empty() {
return Err(IdPrefixFetchError::NoMatch(
format!("note ID prefix {note_id_prefix}").to_string(),
));
}
if input_note_records.len() > 1 {
let input_note_record_ids = input_note_records
.iter()
.map(|input_note_record| input_note_record.id())
.collect::<Vec<_>>();
tracing::error!(
"Multiple notes found for the prefix {}: {:?}",
note_id_prefix,
input_note_record_ids
);
return Err(IdPrefixFetchError::MultipleMatches(
format!("note ID prefix {note_id_prefix}").to_string(),
));
}
Ok(input_note_records
.pop()
.expect("input_note_records should always have one element"))
}
pub struct NoteUpdates {
new_input_notes: Vec<InputNoteRecord>,
new_output_notes: Vec<OutputNoteRecord>,
updated_input_notes: Vec<InputNoteRecord>,
updated_output_notes: Vec<OutputNoteRecord>,
}
impl NoteUpdates {
pub fn new(
new_input_notes: Vec<InputNoteRecord>,
new_output_notes: Vec<OutputNoteRecord>,
updated_input_notes: Vec<InputNoteRecord>,
updated_output_notes: Vec<OutputNoteRecord>,
) -> Self {
Self {
new_input_notes,
new_output_notes,
updated_input_notes,
updated_output_notes,
}
}
pub fn combine_with(mut self, other: Self) -> Self {
self.new_input_notes.extend(other.new_input_notes);
self.new_output_notes.extend(other.new_output_notes);
self.updated_input_notes.extend(other.updated_input_notes);
self.updated_output_notes.extend(other.updated_output_notes);
self
}
pub fn new_input_notes(&self) -> &[InputNoteRecord] {
&self.new_input_notes
}
pub fn new_output_notes(&self) -> &[OutputNoteRecord] {
&self.new_output_notes
}
pub fn updated_input_notes(&self) -> &[InputNoteRecord] {
&self.updated_input_notes
}
pub fn updated_output_notes(&self) -> &[OutputNoteRecord] {
&self.updated_output_notes
}
pub fn is_empty(&self) -> bool {
self.updated_input_notes.is_empty()
&& self.updated_output_notes.is_empty()
&& self.new_input_notes.is_empty()
&& self.new_output_notes.is_empty()
}
pub fn committed_note_ids(&self) -> BTreeSet<NoteId> {
let committed_output_note_ids = self
.updated_output_notes
.iter()
.filter_map(|note_record| note_record.is_committed().then_some(note_record.id()));
let committed_input_note_ids = self
.updated_input_notes
.iter()
.filter_map(|note_record| note_record.is_committed().then_some(note_record.id()));
BTreeSet::from_iter(committed_input_note_ids.chain(committed_output_note_ids))
}
pub fn consumed_note_ids(&self) -> BTreeSet<NoteId> {
let consumed_output_note_ids = self
.updated_output_notes
.iter()
.filter_map(|note_record| note_record.is_consumed().then_some(note_record.id()));
let consumed_input_note_ids = self
.updated_input_notes
.iter()
.filter_map(|note_record| note_record.is_consumed().then_some(note_record.id()));
BTreeSet::from_iter(consumed_input_note_ids.chain(consumed_output_note_ids))
}
}