Skip to main content

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}