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;
25use crate::note::P2idNote;
26use crate::note::costs::{MINT_CONSUMPTION_CYCLES, NoteConsumptionCost};
27
28const MINT_SCRIPT_PATH: &str = "::miden::standards::notes::mint::main";
33
34static MINT_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
36 let standards_lib = StandardsLib::default();
37 let path = Path::new(MINT_SCRIPT_PATH);
38 NoteScript::from_package_reference(standards_lib.as_ref(), path)
39 .expect("Standards library contains MINT note script procedure")
40});
41
42#[derive(Debug, Clone)]
58pub struct MintNote {
59 sender: AccountId,
60 storage: MintNoteStorage,
61 serial_number: Word,
62 attachments: NoteAttachments,
63}
64
65#[bon::bon]
66impl MintNote {
67 #[builder]
74 pub fn new(
75 #[builder(field)] attachments: Vec<NoteAttachment>,
76 sender: AccountId,
77 #[builder(name = mint_storage)] storage: MintNoteStorage,
78 serial_number: Word,
79 ) -> Result<Self, NoteError> {
80 let attachments = NoteAttachments::new(attachments)?;
81
82 Ok(Self {
83 sender,
84 storage,
85 serial_number,
86 attachments,
87 })
88 }
89}
90
91impl MintNote {
92 pub const NUM_STORAGE_ITEMS_PRIVATE: usize = 13;
99
100 pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;
106
107 pub const NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE: usize = 9;
111
112 pub const NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 16;
117
118 pub fn script() -> NoteScript {
123 MINT_SCRIPT.clone()
124 }
125
126 pub fn script_root() -> NoteScriptRoot {
128 MINT_SCRIPT.root()
129 }
130
131 pub fn faucet_id(&self) -> AccountId {
133 self.storage.faucet_id()
134 }
135
136 pub fn sender(&self) -> AccountId {
138 self.sender
139 }
140
141 pub fn storage(&self) -> &MintNoteStorage {
143 &self.storage
144 }
145
146 pub fn serial_number(&self) -> Word {
148 self.serial_number
149 }
150
151 pub fn attachments(&self) -> &NoteAttachments {
153 &self.attachments
154 }
155}
156
157impl<S: mint_note_builder::State> MintNoteBuilder<S> {
161 pub fn attachment(mut self, attachment: impl Into<NoteAttachment>) -> Self {
163 self.attachments.push(attachment.into());
164 self
165 }
166
167 pub fn attachments(
169 mut self,
170 attachments: impl IntoIterator<Item = impl Into<NoteAttachment>>,
171 ) -> Self {
172 self.attachments.extend(attachments.into_iter().map(Into::into));
173 self
174 }
175}
176
177impl<S: mint_note_builder::State> MintNoteBuilder<S>
178where
179 S::SerialNumber: mint_note_builder::IsUnset,
180{
181 pub fn generate_serial_number(
183 self,
184 rng: &mut impl FeltRng,
185 ) -> MintNoteBuilder<mint_note_builder::SetSerialNumber<S>> {
186 self.serial_number(rng.draw_word())
187 }
188}
189
190impl From<MintNote> for Note {
194 fn from(note: MintNote) -> Self {
195 let faucet_id = note.storage.faucet_id();
198 let metadata = PartialNoteMetadata::new(note.sender, NoteType::Public)
199 .with_tag(NoteTag::with_account_target(faucet_id));
200 let recipient = NoteRecipient::new(
201 note.serial_number,
202 MintNote::script(),
203 NoteStorage::from(note.storage),
204 );
205
206 Note::with_attachments(NoteAssets::default(), metadata, recipient, note.attachments)
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
228pub enum MintNoteStorage {
229 FungiblePrivate {
230 recipient_digest: Word,
231 asset: FungibleAsset,
232 tag: NoteTag,
233 },
234 FungiblePublic {
235 recipient: NoteRecipient,
236 asset: FungibleAsset,
237 tag: NoteTag,
238 },
239 NonFungiblePrivate {
240 recipient_digest: Word,
241 asset: NonFungibleAsset,
242 tag: NoteTag,
243 },
244 NonFungiblePublic {
245 recipient: NoteRecipient,
246 asset: NonFungibleAsset,
247 tag: NoteTag,
248 },
249}
250
251impl MintNoteStorage {
252 pub fn new_fungible_private(
254 recipient_digest: Word,
255 asset: FungibleAsset,
256 tag: NoteTag,
257 ) -> Self {
258 Self::FungiblePrivate { recipient_digest, asset, tag }
259 }
260
261 pub fn new_fungible_public(
263 recipient: NoteRecipient,
264 asset: FungibleAsset,
265 tag: NoteTag,
266 ) -> Result<Self, NoteError> {
267 let total_storage_items =
268 MintNote::MIN_NUM_STORAGE_ITEMS_PUBLIC + recipient.storage().num_items() as usize;
269
270 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
271 return Err(NoteError::TooManyStorageItems(total_storage_items));
272 }
273
274 Ok(Self::FungiblePublic { recipient, asset, tag })
275 }
276
277 pub fn new_non_fungible_private(
279 recipient_digest: Word,
280 asset: NonFungibleAsset,
281 tag: NoteTag,
282 ) -> Self {
283 Self::NonFungiblePrivate { recipient_digest, asset, tag }
284 }
285
286 pub fn new_non_fungible_public(
288 recipient: NoteRecipient,
289 asset: NonFungibleAsset,
290 tag: NoteTag,
291 ) -> Result<Self, NoteError> {
292 let total_storage_items = MintNote::NON_FUNGIBLE_MIN_NUM_STORAGE_ITEMS_PUBLIC
293 + recipient.storage().num_items() as usize;
294
295 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
296 return Err(NoteError::TooManyStorageItems(total_storage_items));
297 }
298
299 Ok(Self::NonFungiblePublic { recipient, asset, tag })
300 }
301
302 pub fn faucet_id(&self) -> AccountId {
304 match self {
305 Self::FungiblePrivate { asset, .. } | Self::FungiblePublic { asset, .. } => {
306 asset.faucet_id()
307 },
308 Self::NonFungiblePrivate { asset, .. } | Self::NonFungiblePublic { asset, .. } => {
309 asset.faucet_id()
310 },
311 }
312 }
313}
314
315impl From<MintNoteStorage> for NoteStorage {
316 fn from(mint_storage: MintNoteStorage) -> Self {
317 match mint_storage {
318 MintNoteStorage::FungiblePrivate { recipient_digest, asset, tag } => {
319 let mut storage_values = Vec::with_capacity(MintNote::NUM_STORAGE_ITEMS_PRIVATE);
320 storage_values.extend_from_slice(recipient_digest.as_elements());
321 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
322 storage_values.push(tag.into());
323 NoteStorage::new(storage_values)
324 .expect("number of storage items should not exceed max storage items")
325 },
326 MintNoteStorage::FungiblePublic { recipient, asset, tag } => {
327 let mut storage_values = Vec::new();
328 storage_values.extend_from_slice(recipient.script().root().as_elements());
329 storage_values.extend_from_slice(recipient.serial_num().as_elements());
330 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
331 storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
334 storage_values.extend_from_slice(recipient.storage().items());
335 NoteStorage::new(storage_values)
336 .expect("number of storage items should not exceed max storage items")
337 },
338 MintNoteStorage::NonFungiblePrivate { recipient_digest, asset, tag } => {
339 let mut storage_values =
340 Vec::with_capacity(MintNote::NON_FUNGIBLE_NUM_STORAGE_ITEMS_PRIVATE);
341 storage_values.extend_from_slice(recipient_digest.as_elements());
342 storage_values.extend_from_slice(asset.to_value_word().as_elements());
343 storage_values.push(tag.into());
344 NoteStorage::new(storage_values)
345 .expect("number of storage items should not exceed max storage items")
346 },
347 MintNoteStorage::NonFungiblePublic { recipient, asset, tag } => {
348 let mut storage_values = Vec::new();
349 storage_values.extend_from_slice(recipient.script().root().as_elements());
350 storage_values.extend_from_slice(recipient.serial_num().as_elements());
351 storage_values.extend_from_slice(asset.to_value_word().as_elements());
352 storage_values.extend_from_slice(&[tag.into(), Felt::ZERO, Felt::ZERO, Felt::ZERO]);
355 storage_values.extend_from_slice(recipient.storage().items());
356 NoteStorage::new(storage_values)
357 .expect("number of storage items should not exceed max storage items")
358 },
359 }
360 }
361}
362
363impl NoteConsumptionCost for MintNote {
367 fn consumption_cycles() -> u32 {
368 MINT_CONSUMPTION_CYCLES
369 }
370
371 fn created_notes() -> Vec<NoteScriptRoot> {
374 vec![P2idNote::script_root()]
375 }
376}
377
378#[cfg(test)]
382mod tests {
383 use miden_protocol::account::AccountType;
384 use miden_protocol::crypto::rand::RandomCoin;
385
386 use super::*;
387
388 fn faucet() -> AccountId {
389 AccountId::builder().account_type(AccountType::Public).build_with_seed([1; 32])
390 }
391
392 fn owner() -> AccountId {
393 AccountId::builder().account_type(AccountType::Private).build_with_seed([2; 32])
394 }
395
396 #[test]
398 fn builder_builds_public_mint_note() {
399 let mut rng = RandomCoin::new(Word::empty());
400 let asset = FungibleAsset::new(faucet(), 50).unwrap();
401 let mint_storage =
402 MintNoteStorage::new_fungible_private(Word::empty(), asset, NoteTag::default());
403 let mint_note = MintNote::builder()
404 .sender(owner())
405 .mint_storage(mint_storage)
406 .generate_serial_number(&mut rng)
407 .build()
408 .unwrap();
409
410 assert_eq!(mint_note.faucet_id(), faucet());
411 assert_eq!(mint_note.sender(), owner());
412
413 let note = Note::from(mint_note);
414 assert_eq!(note.metadata().note_type(), NoteType::Public);
415 assert_eq!(note.metadata().tag(), NoteTag::with_account_target(faucet()));
416 assert_eq!(note.assets().num_assets(), 0);
417 }
418}