miden_standards/note/mint.rs
1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::asset::{Asset, FungibleAsset};
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachments,
12 NoteRecipient,
13 NoteScript,
14 NoteScriptRoot,
15 NoteStorage,
16 NoteTag,
17 NoteType,
18 PartialNoteMetadata,
19};
20use miden_protocol::utils::sync::LazyLock;
21use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word};
22
23use crate::StandardsLib;
24
25// NOTE SCRIPT
26// ================================================================================================
27
28/// Path to the MINT note script procedure in the standards library.
29const MINT_SCRIPT_PATH: &str = "::miden::standards::notes::mint::main";
30
31// Initialize the MINT note script only once
32static MINT_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
33 let standards_lib = StandardsLib::default();
34 let path = Path::new(MINT_SCRIPT_PATH);
35 NoteScript::from_library_reference(standards_lib.as_ref(), path)
36 .expect("Standards library contains MINT note script procedure")
37});
38
39// MINT NOTE
40// ================================================================================================
41
42/// TODO: add docs
43pub struct MintNote;
44
45impl MintNote {
46 // CONSTANTS
47 // --------------------------------------------------------------------------------------------
48
49 /// Expected number of storage items of the MINT note (private mode).
50 ///
51 /// Layout: RECIPIENT(4) + ASSET_KEY(4) + ASSET_VALUE(4) + tag(1).
52 pub const NUM_STORAGE_ITEMS_PRIVATE: usize = 13;
53
54 /// Minimum number of storage items of the MINT note (public mode).
55 ///
56 /// Layout: SCRIPT_ROOT(4) + SERIAL_NUM(4) + ASSET_KEY(4) + ASSET_VALUE(4) + tag(1) +
57 /// padding(3) + variable output-note storage. The variable portion starts at offset 20
58 /// (word-aligned) and may contain zero or more items.
59 pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;
60
61 // PUBLIC ACCESSORS
62 // --------------------------------------------------------------------------------------------
63
64 /// Returns the script of the MINT note.
65 pub fn script() -> NoteScript {
66 MINT_SCRIPT.clone()
67 }
68
69 /// Returns the MINT note script root.
70 pub fn script_root() -> NoteScriptRoot {
71 MINT_SCRIPT.root()
72 }
73
74 // BUILDERS
75 // --------------------------------------------------------------------------------------------
76
77 /// Generates a MINT note: a note that instructs a network faucet to mint the asset
78 /// embedded in its storage.
79 ///
80 /// The MINT script reads the asset (`ASSET_KEY` + `ASSET_VALUE`) directly from the note's
81 /// storage and passes it to the faucet's `mint_and_send` procedure, which rejects an asset
82 /// that does not belong to the consuming faucet. A MINT note bound to faucet A therefore
83 /// cannot be redirected to faucet B even when both share an owner.
84 ///
85 /// MINT notes are always PUBLIC (for network execution). Output notes can be either PRIVATE
86 /// or PUBLIC depending on the [`MintNoteStorage`] variant used.
87 ///
88 /// The passed-in `rng` is used to generate a serial number for the note. The note's tag
89 /// is automatically set to the faucet's account ID for proper routing.
90 ///
91 /// # Parameters
92 /// - `faucet_id`: The account ID of the network faucet that will mint the asset. Must equal the
93 /// faucet ID of the asset embedded in `mint_storage`.
94 /// - `sender`: The account ID of the note creator (must be the faucet owner)
95 /// - `mint_storage`: The storage configuration specifying private or public output mode
96 /// - `attachments`: The [`NoteAttachments`] of the MINT note
97 /// - `rng`: Random number generator for creating the serial number
98 ///
99 /// # Errors
100 /// Returns an error if `faucet_id` does not match the faucet of the embedded asset, or if
101 /// note creation fails.
102 pub fn create<R: FeltRng>(
103 faucet_id: AccountId,
104 sender: AccountId,
105 mint_storage: MintNoteStorage,
106 attachments: NoteAttachments,
107 rng: &mut R,
108 ) -> Result<Note, NoteError> {
109 if faucet_id != mint_storage.asset().faucet_id() {
110 return Err(NoteError::other(
111 "faucet_id must equal the faucet ID of the asset embedded in mint_storage",
112 ));
113 }
114
115 let note_script = Self::script();
116 let serial_num = rng.draw_word();
117
118 // MINT notes are always public for network execution
119 let note_type = NoteType::Public;
120
121 // Convert MintNoteStorage to NoteStorage
122 let storage = NoteStorage::from(mint_storage);
123
124 let tag = NoteTag::with_account_target(faucet_id);
125
126 let metadata = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
127 let assets = NoteAssets::new(vec![])?; // MINT notes have no assets
128 let recipient = NoteRecipient::new(serial_num, note_script, storage);
129
130 Ok(Note::with_attachments(assets, metadata, recipient, attachments))
131 }
132}
133
134// MINT NOTE STORAGE
135// ================================================================================================
136
137/// Represents the different storage formats for MINT notes.
138///
139/// - Private: Creates a private output note using a precomputed recipient digest (13 MINT note
140/// storage items: RECIPIENT + ASSET_KEY + ASSET_VALUE + tag).
141/// - Public: Creates a public output note by providing script root, serial number, and
142/// variable-length storage (20+ MINT note storage items: 20 fixed + variable output note storage
143/// items, with the variable section word-aligned at offset 20).
144///
145/// The asset (`ASSET_KEY` + `ASSET_VALUE`, 8 felts) is embedded in storage so that the
146/// faucet executing the MINT note can be checked against the asset's faucet ID at mint time.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum MintNoteStorage {
149 Private {
150 recipient_digest: Word,
151 asset: FungibleAsset,
152 tag: Felt,
153 },
154 Public {
155 recipient: NoteRecipient,
156 asset: FungibleAsset,
157 tag: Felt,
158 },
159}
160
161impl MintNoteStorage {
162 pub fn new_private(recipient_digest: Word, asset: FungibleAsset, tag: Felt) -> Self {
163 Self::Private { recipient_digest, asset, tag }
164 }
165
166 pub fn new_public(
167 recipient: NoteRecipient,
168 asset: FungibleAsset,
169 tag: Felt,
170 ) -> Result<Self, NoteError> {
171 let total_storage_items =
172 MintNote::MIN_NUM_STORAGE_ITEMS_PUBLIC + recipient.storage().num_items() as usize;
173
174 if total_storage_items > MAX_NOTE_STORAGE_ITEMS {
175 return Err(NoteError::TooManyStorageItems(total_storage_items));
176 }
177
178 Ok(Self::Public { recipient, asset, tag })
179 }
180
181 /// Returns the asset that will be minted on consumption.
182 pub fn asset(&self) -> FungibleAsset {
183 match self {
184 Self::Private { asset, .. } | Self::Public { asset, .. } => *asset,
185 }
186 }
187}
188
189impl From<MintNoteStorage> for NoteStorage {
190 fn from(mint_storage: MintNoteStorage) -> Self {
191 match mint_storage {
192 MintNoteStorage::Private { recipient_digest, asset, tag } => {
193 let mut storage_values = Vec::with_capacity(MintNote::NUM_STORAGE_ITEMS_PRIVATE);
194 storage_values.extend_from_slice(recipient_digest.as_elements());
195 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
196 storage_values.push(tag);
197 NoteStorage::new(storage_values)
198 .expect("number of storage items should not exceed max storage items")
199 },
200 MintNoteStorage::Public { recipient, asset, tag } => {
201 let mut storage_values = Vec::new();
202 storage_values.extend_from_slice(recipient.script().root().as_elements());
203 storage_values.extend_from_slice(recipient.serial_num().as_elements());
204 storage_values.extend_from_slice(&Asset::from(asset).as_elements());
205 // tag followed by 3 padding felts so the variable storage that follows starts at
206 // a word-aligned offset (20).
207 storage_values.extend_from_slice(&[tag, Felt::ZERO, Felt::ZERO, Felt::ZERO]);
208 storage_values.extend_from_slice(recipient.storage().items());
209 NoteStorage::new(storage_values)
210 .expect("number of storage items should not exceed max storage items")
211 },
212 }
213 }
214}