use alloc::vec::Vec;
use miden_protocol::account::AccountId;
use miden_protocol::assembly::Path;
use miden_protocol::asset::{Asset, FungibleAsset, NonFungibleAsset};
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteRecipient,
NoteScript,
NoteScriptRoot,
NoteStorage,
NoteTag,
NoteType,
PartialNoteMetadata,
};
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word};
use crate::StandardsLib;
const MINT_SCRIPT_PATH: &str = "::miden::standards::notes::mint::main";
static MINT_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
let standards_lib = StandardsLib::default();
let path = Path::new(MINT_SCRIPT_PATH);
NoteScript::from_library_reference(standards_lib.as_ref(), path)
.expect("Standards library contains MINT note script procedure")
});
#[derive(Debug, Clone)]
pub struct MintNote {
sender: AccountId,
storage: MintNoteStorage,
serial_number: Word,
attachments: NoteAttachments,
}
#[bon::bon]
impl MintNote {
#[builder]
pub fn new(
#[builder(field)] attachments: Vec<NoteAttachment>,
sender: AccountId,
#[builder(name = mint_storage)] storage: MintNoteStorage,
serial_number: Word,
) -> Result<Self, NoteError> {
let attachments = NoteAttachments::new(attachments)?;
Ok(Self {
sender,
storage,
serial_number,
attachments,
})
}
}
impl MintNote {
pub const NUM_STORAGE_ITEMS_PRIVATE: usize = 13;
pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;
pub const NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE: usize = 9;
pub const NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 16;
pub fn script() -> NoteScript {
MINT_SCRIPT.clone()
}
pub fn script_root() -> NoteScriptRoot {
MINT_SCRIPT.root()
}
pub fn faucet_id(&self) -> AccountId {
self.storage.faucet_id()
}
pub fn sender(&self) -> AccountId {
self.sender
}
pub fn storage(&self) -> &MintNoteStorage {
&self.storage
}
pub fn serial_number(&self) -> Word {
self.serial_number
}
pub fn attachments(&self) -> &NoteAttachments {
&self.attachments
}
}
impl<S: mint_note_builder::State> MintNoteBuilder<S> {
pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
self.attachments.push(attachment.into());
self
}
pub fn attachments(
mut self,
attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
) -> Self {
self.attachments.extend(attachments.into_iter().map(Into::into));
self
}
}
impl<S: mint_note_builder::State> MintNoteBuilder<S>
where
S::SerialNumber: mint_note_builder::IsUnset,
{
pub fn generate_serial_number(
self,
rng: &mut impl FeltRng,
) -> MintNoteBuilder<mint_note_builder::SetSerialNumber<S>> {
self.serial_number(rng.draw_word())
}
}
impl From<MintNote> for Note {
fn from(note: MintNote) -> Self {
let faucet_id = note.storage.faucet_id();
let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
.with_tag(NoteTag::with_account_target(faucet_id));
let recipient = NoteRecipient::new(
note.serial_number,
MintNote::script(),
NoteStorage::from(note.storage),
);
Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MintNoteStorage {
FungiblePrivate {
recipient_digest: Word,
asset: FungibleAsset,
tag: NoteTag,
},
FungiblePublic {
recipient: NoteRecipient,
asset: FungibleAsset,
tag: NoteTag,
},
NonFungiblePrivate {
recipient_digest: Word,
asset: NonFungibleAsset,
tag: NoteTag,
},
NonFungiblePublic {
recipient: NoteRecipient,
asset: NonFungibleAsset,
tag: NoteTag,
},
}
impl MintNoteStorage {
pub fn new_fungible_private(
recipient_digest: Word,
asset: FungibleAsset,
tag: NoteTag,
) -> Self {
Self::FungiblePrivate { recipient_digest, asset, tag }
}
pub fn new_fungible_public(
recipient: NoteRecipient,
asset: FungibleAsset,
tag: NoteTag,
) -> Result<Self, NoteError> {
let total_storage_items =
MintNote::MIN_NUM_STORAGE_ITEMS_PUBLIC + recipient.storage().num_items() as usize;
if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
return Err(NoteError::TooManyStorageItems(total_storage_items));
}
Ok(Self::FungiblePublic { recipient, asset, tag })
}
pub fn new_non_fungible_private(
recipient_digest: Word,
asset: NonFungibleAsset,
tag: NoteTag,
) -> Self {
Self::NonFungiblePrivate { recipient_digest, asset, tag }
}
pub fn new_non_fungible_public(
recipient: NoteRecipient,
asset: NonFungibleAsset,
tag: NoteTag,
) -> Result<Self, NoteError> {
let total_storage_items = MintNote::NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC
+ recipient.storage().num_items() as usize;
if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
return Err(NoteError::TooManyStorageItems(total_storage_items));
}
Ok(Self::NonFungiblePublic { recipient, asset, tag })
}
pub fn faucet_id(&self) -> AccountId {
match self {
Self::FungiblePrivate { asset, .. } | Self::FungiblePublic { asset, .. } => {
asset.faucet_id()
},
Self::NonFungiblePrivate { asset, .. } | Self::NonFungiblePublic { asset, .. } => {
asset.faucet_id()
},
}
}
}
impl From<MintNoteStorage> for NoteStorage {
fn from(mint_storage: MintNoteStorage) -> Self {
match mint_storage {
MintNoteStorage::FungiblePrivate { recipient_digest, asset, tag } => {
let mut storage_values = Vec::with_capacity(MintNote::NUM_STORAGE_ITEMS_PRIVATE);
storage_values.extend_from_slice(recipient_digest.as_elements());
storage_values.extend_from_slice(&Asset::from(asset).as_elements());
storage_values.push(tag.into());
NoteStorage::new(storage_values)
.expect("number of storage items should not exceed max storage items")
},
MintNoteStorage::FungiblePublic { recipient, asset, tag } => {
let mut storage_values = Vec::new();
storage_values.extend_from_slice(recipient.script().root().as_elements());
storage_values.extend_from_slice(recipient.serial_num().as_elements());
storage_values.extend_from_slice(&Asset::from(asset).as_elements());
storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
storage_values.extend_from_slice(recipient.storage().items());
NoteStorage::new(storage_values)
.expect("number of storage items should not exceed max storage items")
},
MintNoteStorage::NonFungiblePrivate { recipient_digest, asset, tag } => {
let mut storage_values =
Vec::with_capacity(MintNote::NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE);
storage_values.extend_from_slice(recipient_digest.as_elements());
storage_values.extend_from_slice(asset.to_value_word().as_elements());
storage_values.push(tag.into());
NoteStorage::new(storage_values)
.expect("number of storage items should not exceed max storage items")
},
MintNoteStorage::NonFungiblePublic { recipient, asset, tag } => {
let mut storage_values = Vec::new();
storage_values.extend_from_slice(recipient.script().root().as_elements());
storage_values.extend_from_slice(recipient.serial_num().as_elements());
storage_values.extend_from_slice(asset.to_value_word().as_elements());
storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
storage_values.extend_from_slice(recipient.storage().items());
NoteStorage::new(storage_values)
.expect("number of storage items should not exceed max storage items")
},
}
}
}
#[cfg(test)]
mod tests {
use miden_protocol::account::AccountType;
use miden_protocol::crypto::rand::RandomCoin;
use super::*;
fn faucet() -> AccountId {
AccountId::builder().account_type(AccountType::Public).build_with_seed([1; 32])
}
fn owner() -> AccountId {
AccountId::builder().account_type(AccountType::Private).build_with_seed([2; 32])
}
#[test]
fn builder_builds_public_mint_note() {
let mut rng = RandomCoin::new(Word::empty());
let asset = FungibleAsset::new(faucet(), 50).unwrap();
let mint_storage =
MintNoteStorage::new_fungible_private(Word::empty(), asset, NoteTag::default());
let mint_note = MintNote::builder()
.sender(owner())
.mint_storage(mint_storage)
.generate_serial_number(&mut rng)
.build()
.unwrap();
assert_eq!(mint_note.faucet_id(), faucet());
assert_eq!(mint_note.sender(), owner());
let note = Note::from(mint_note);
assert_eq!(note.metadata().note_type(), NoteType::Public);
assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet()));
assert_eq!(note.assets().num_assets(), 0);
}
}