1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::asset::{Asset, FungibleAsset, NonFungibleAsset};
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachment,
12 NoteAttachments,
13 NoteRecipient,
14 NoteScript,
15 NoteScriptRoot,
16 NoteStorage,
17 NoteTag,
18 NoteType,
19 PartialNoteMetadata,
20};
21use miden_protocol::utils::sync::LazyLock;
22use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word};
23
24use crate::StandardsLib;
25
26const MINT_SCRIPT_PATH: &str = "::miden::standards::notes::mint::main";
31
32static MINT_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
34 let standards_lib = StandardsLib::default();
35 let path = Path::new(MINT_SCRIPT_PATH);
36 NoteScript::from_library_reference(standards_lib.as_ref(), path)
37 .expect("Standards library contains MINT note script procedure")
38});
39
40#[derive(Debug, Clone)]
56pub struct MintNote {
57 sender: AccountId,
58 storage: MintNoteStorage,
59 serial_number: Word,
60 attachments: NoteAttachments,
61}
62
63#[bon::bon]
64impl MintNote {
65 #[builder]
72 pub fn new(
73 #[builder(field)] attachments: Vec<NoteAttachment>,
74 sender: AccountId,
75 #[builder(name = mint_storage)] storage: MintNoteStorage,
76 serial_number: Word,
77 ) -> Result<Self, NoteError> {
78 let attachments = NoteAttachments::new(attachments)?;
79
80 Ok(Self {
81 sender,
82 storage,
83 serial_number,
84 attachments,
85 })
86 }
87}
88
89impl MintNote {
90 pub const NUM_STORAGE_ITEMS_PRIVATE: usize = 13;
97
98 pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;
104
105 pub const NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE: usize = 9;
109
110 pub const NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 16;
115
116 pub fn script() -> NoteScript {
121 MINT_SCRIPT.clone()
122 }
123
124 pub fn script_root() -> NoteScriptRoot {
126 MINT_SCRIPT.root()
127 }
128
129 pub fn faucet_id(&self) -> AccountId {
131 self.storage.faucet_id()
132 }
133
134 pub fn sender(&self) -> AccountId {
136 self.sender
137 }
138
139 pub fn storage(&self) -> &MintNoteStorage {
141 &self.storage
142 }
143
144 pub fn serial_number(&self) -> Word {
146 self.serial_number
147 }
148
149 pub fn attachments(&self) -> &NoteAttachments {
151 &self.attachments
152 }
153}
154
155impl<S: mint_note_builder::State> MintNoteBuilder<S> {
159 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
161 self.attachments.push(attachment.into());
162 self
163 }
164
165 pub fn attachments(
167 mut self,
168 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
169 ) -> Self {
170 self.attachments.extend(attachments.into_iter().map(Into::into));
171 self
172 }
173}
174
175impl<S: mint_note_builder::State> MintNoteBuilder<S>
176where
177 S::SerialNumber: mint_note_builder::IsUnset,
178{
179 pub fn generate_serial_number(
181 self,
182 rng: &mut impl FeltRng,
183 ) -> MintNoteBuilder<mint_note_builder::SetSerialNumber<S>> {
184 self.serial_number(rng.draw_word())
185 }
186}
187
188impl From<MintNote> for Note {
192 fn from(note: MintNote) -> Self {
193 let faucet_id = note.storage.faucet_id();
196 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
197 .with_tag(NoteTag::with_account_target(faucet_id));
198 let recipient = NoteRecipient::new(
199 note.serial_number,
200 MintNote::script(),
201 NoteStorage::from(note.storage),
202 );
203
204 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
226pub enum MintNoteStorage {
227 FungiblePrivate {
228 recipient_digest: Word,
229 asset: FungibleAsset,
230 tag: NoteTag,
231 },
232 FungiblePublic {
233 recipient: NoteRecipient,
234 asset: FungibleAsset,
235 tag: NoteTag,
236 },
237 NonFungiblePrivate {
238 recipient_digest: Word,
239 asset: NonFungibleAsset,
240 tag: NoteTag,
241 },
242 NonFungiblePublic {
243 recipient: NoteRecipient,
244 asset: NonFungibleAsset,
245 tag: NoteTag,
246 },
247}
248
249impl MintNoteStorage {
250 pub fn new_fungible_private(
252 recipient_digest: Word,
253 asset: FungibleAsset,
254 tag: NoteTag,
255 ) -> Self {
256 Self::FungiblePrivate { recipient_digest, asset, tag }
257 }
258
259 pub fn new_fungible_public(
261 recipient: NoteRecipient,
262 asset: FungibleAsset,
263 tag: NoteTag,
264 ) -> Result<Self, NoteError> {
265 let total_storage_items =
266 MintNote::MIN_NUM_STORAGE_ITEMS_PUBLIC + recipient.storage().num_items() as usize;
267
268 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
269 return Err(NoteError::TooManyStorageItems(total_storage_items));
270 }
271
272 Ok(Self::FungiblePublic { recipient, asset, tag })
273 }
274
275 pub fn new_non_fungible_private(
277 recipient_digest: Word,
278 asset: NonFungibleAsset,
279 tag: NoteTag,
280 ) -> Self {
281 Self::NonFungiblePrivate { recipient_digest, asset, tag }
282 }
283
284 pub fn new_non_fungible_public(
286 recipient: NoteRecipient,
287 asset: NonFungibleAsset,
288 tag: NoteTag,
289 ) -> Result<Self, NoteError> {
290 let total_storage_items = MintNote::NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC
291 + recipient.storage().num_items() as usize;
292
293 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
294 return Err(NoteError::TooManyStorageItems(total_storage_items));
295 }
296
297 Ok(Self::NonFungiblePublic { recipient, asset, tag })
298 }
299
300 pub fn faucet_id(&self) -> AccountId {
302 match self {
303 Self::FungiblePrivate { asset, .. } | Self::FungiblePublic { asset, .. } => {
304 asset.faucet_id()
305 },
306 Self::NonFungiblePrivate { asset, .. } | Self::NonFungiblePublic { asset, .. } => {
307 asset.faucet_id()
308 },
309 }
310 }
311}
312
313impl From<MintNoteStorage> for NoteStorage {
314 fn from(mint_storage: MintNoteStorage) -> Self {
315 match mint_storage {
316 MintNoteStorage::FungiblePrivate { recipient_digest, asset, tag } => {
317 let mut storage_values = Vec::with_capacity(MintNote::NUM_STORAGE_ITEMS_PRIVATE);
318 storage_values.extend_from_slice(recipient_digest.as_elements());
319 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
320 storage_values.push(tag.into());
321 NoteStorage::new(storage_values)
322 .expect("number of storage items should not exceed max storage items")
323 },
324 MintNoteStorage::FungiblePublic { recipient, asset, tag } => {
325 let mut storage_values = Vec::new();
326 storage_values.extend_from_slice(recipient.script().root().as_elements());
327 storage_values.extend_from_slice(recipient.serial_num().as_elements());
328 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
329 storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
332 storage_values.extend_from_slice(recipient.storage().items());
333 NoteStorage::new(storage_values)
334 .expect("number of storage items should not exceed max storage items")
335 },
336 MintNoteStorage::NonFungiblePrivate { recipient_digest, asset, tag } => {
337 let mut storage_values =
338 Vec::with_capacity(MintNote::NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE);
339 storage_values.extend_from_slice(recipient_digest.as_elements());
340 storage_values.extend_from_slice(asset.to_value_word().as_elements());
341 storage_values.push(tag.into());
342 NoteStorage::new(storage_values)
343 .expect("number of storage items should not exceed max storage items")
344 },
345 MintNoteStorage::NonFungiblePublic { recipient, asset, tag } => {
346 let mut storage_values = Vec::new();
347 storage_values.extend_from_slice(recipient.script().root().as_elements());
348 storage_values.extend_from_slice(recipient.serial_num().as_elements());
349 storage_values.extend_from_slice(asset.to_value_word().as_elements());
350 storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
353 storage_values.extend_from_slice(recipient.storage().items());
354 NoteStorage::new(storage_values)
355 .expect("number of storage items should not exceed max storage items")
356 },
357 }
358 }
359}
360
361#[cfg(test)]
365mod tests {
366 use miden_protocol::account::AccountType;
367 use miden_protocol::crypto::rand::RandomCoin;
368
369 use super::*;
370
371 fn faucet() -> AccountId {
372 AccountId::builder().account_type(AccountType::Public).build_with_seed([1; 32])
373 }
374
375 fn owner() -> AccountId {
376 AccountId::builder().account_type(AccountType::Private).build_with_seed([2; 32])
377 }
378
379 #[test]
381 fn builder_builds_public_mint_note() {
382 let mut rng = RandomCoin::new(Word::empty());
383 let asset = FungibleAsset::new(faucet(), 50).unwrap();
384 let mint_storage =
385 MintNoteStorage::new_fungible_private(Word::empty(), asset, NoteTag::default());
386 let mint_note = MintNote::builder()
387 .sender(owner())
388 .mint_storage(mint_storage)
389 .generate_serial_number(&mut rng)
390 .build()
391 .unwrap();
392
393 assert_eq!(mint_note.faucet_id(), faucet());
394 assert_eq!(mint_note.sender(), owner());
395
396 let note = Note::from(mint_note);
397 assert_eq!(note.metadata().note_type(), NoteType::Public);
398 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet()));
399 assert_eq!(note.assets().num_assets(), 0);
400 }
401}