Skip to main content

miden_agglayer/
claim_note.rs

1use alloc::vec;
2use alloc::vec::Vec;
3
4use miden_core::{Felt, Word};
5use miden_protocol::account::AccountId;
6use miden_protocol::crypto::SequentialCommit;
7use miden_protocol::crypto::rand::FeltRng;
8use miden_protocol::errors::NoteError;
9use miden_protocol::note::{
10    Note,
11    NoteAssets,
12    NoteAttachment,
13    NoteAttachments,
14    NoteRecipient,
15    NoteScript,
16    NoteScriptRoot,
17    NoteStorage,
18    NoteType,
19    PartialNoteMetadata,
20};
21use miden_standards::interop::eth::{EthAddress, EthAmount};
22use miden_standards::note::costs::NoteConsumptionCost;
23use miden_standards::note::{MintNote, NetworkAccountTarget, NoteExecutionHint};
24use miden_utils_sync::LazyLock;
25
26use crate::costs::CLAIM_CONSUMPTION_CYCLES;
27use crate::utils::Keccak256Output;
28use crate::{GlobalIndex, MetadataHash, note_script};
29
30// NOTE SCRIPT
31// ================================================================================================
32
33/// Path to the CLAIM note script procedure in the agglayer package.
34const CLAIM_SCRIPT_PATH: &str = "::agglayer::notes::claim::main";
35
36// Initialize the CLAIM note script only once
37static CLAIM_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| note_script(CLAIM_SCRIPT_PATH));
38
39// CLAIM NOTE
40// ================================================================================================
41
42/// CLAIM (Bridge from AggLayer) note.
43///
44/// This note instructs the AggLayer bridge to validate a claim against its registered GERs and
45/// emit a corresponding MINT note for the AggLayer faucet. CLAIM notes are always public.
46pub struct ClaimNote;
47
48impl ClaimNote {
49    // PUBLIC ACCESSORS
50    // --------------------------------------------------------------------------------------------
51
52    /// Returns the CLAIM (Bridge from AggLayer) note script.
53    pub fn script() -> NoteScript {
54        CLAIM_SCRIPT.clone()
55    }
56
57    /// Returns the CLAIM note script root.
58    pub fn script_root() -> NoteScriptRoot {
59        CLAIM_SCRIPT.root()
60    }
61
62    // BUILDERS
63    // --------------------------------------------------------------------------------------------
64
65    /// Creates a CLAIM note — a note that instructs the bridge to validate a claim and create
66    /// a MINT note for the AggLayer Faucet. CLAIM notes are always public.
67    ///
68    /// # Parameters
69    /// - `storage`: The core storage for creating the CLAIM note
70    /// - `target_bridge_id`: The account ID of the bridge that should consume this note. Encoded as
71    ///   a `NetworkAccountTarget` attachment on the note metadata.
72    /// - `sender_account_id`: The account ID of the CLAIM note creator
73    /// - `rng`: Random number generator for creating the CLAIM note serial number
74    ///
75    /// # Errors
76    /// Returns an error if note creation fails.
77    pub fn create<R: FeltRng>(
78        storage: ClaimNoteStorage,
79        target_bridge_id: AccountId,
80        sender_account_id: AccountId,
81        rng: &mut R,
82    ) -> Result<Note, NoteError> {
83        let note_storage = NoteStorage::try_from(storage)?;
84
85        let attachment = NetworkAccountTarget::new(target_bridge_id, NoteExecutionHint::Always)
86            .map_err(|error| {
87                NoteError::other_with_source("failed to create claim network account target", error)
88            })?;
89        let attachments = NoteAttachments::from(NoteAttachment::from(attachment));
90
91        let metadata = PartialNoteMetadata::new(sender_account_id, NoteType::Public);
92
93        let recipient = NoteRecipient::new(rng.draw_word(), Self::script(), note_storage);
94        let assets = NoteAssets::new(vec![])?;
95
96        Ok(Note::with_attachments(assets, metadata, recipient, attachments))
97    }
98}
99
100// CLAIM NOTE TYPE ALIASES
101// ================================================================================================
102
103/// SMT node representation (32-byte Keccak256 hash)
104pub type SmtNode = Keccak256Output;
105
106/// Exit root representation (32-byte Keccak256 hash)
107pub type ExitRoot = Keccak256Output;
108
109/// Leaf value representation (32-byte Keccak256 hash)
110pub type LeafValue = Keccak256Output;
111
112/// Claimed Global Index (CGI) chain hash representation (32-byte Keccak256 hash)
113pub type CgiChainHash = Keccak256Output;
114
115/// Proof data for CLAIM note creation.
116/// Contains SMT proofs and root hashes using typed representations.
117#[derive(Clone)]
118pub struct ProofData {
119    /// SMT proof for local exit root (32 SMT nodes)
120    pub smt_proof_local_exit_root: [SmtNode; 32],
121    /// SMT proof for rollup exit root (32 SMT nodes)
122    pub smt_proof_rollup_exit_root: [SmtNode; 32],
123    /// Global index (uint256 as 32 bytes)
124    pub global_index: GlobalIndex,
125    /// Mainnet exit root hash
126    pub mainnet_exit_root: ExitRoot,
127    /// Rollup exit root hash
128    pub rollup_exit_root: ExitRoot,
129}
130
131impl SequentialCommit for ProofData {
132    type Commitment = Word;
133
134    fn to_elements(&self) -> Vec<Felt> {
135        const PROOF_DATA_ELEMENT_COUNT: usize = 536; // 32*8 + 32*8 + 8 + 8 + 8 (proofs + global_index + 2 exit roots)
136        let mut elements = Vec::with_capacity(PROOF_DATA_ELEMENT_COUNT);
137
138        // Convert SMT proof elements to felts (each node is 8 felts)
139        for node in self.smt_proof_local_exit_root.iter() {
140            elements.extend(node.to_elements());
141        }
142
143        for node in self.smt_proof_rollup_exit_root.iter() {
144            elements.extend(node.to_elements());
145        }
146
147        // Global index (uint256 as 32 bytes)
148        elements.extend(self.global_index.to_elements());
149
150        // Mainnet and rollup exit roots
151        elements.extend(self.mainnet_exit_root.to_elements());
152        elements.extend(self.rollup_exit_root.to_elements());
153
154        elements
155    }
156}
157
158/// Leaf data for CLAIM note creation.
159/// Contains network, address, amount, and metadata using typed representations.
160#[derive(Clone)]
161pub struct LeafData {
162    /// Origin network identifier (uint32)
163    pub origin_network: u32,
164    /// Origin token address
165    pub origin_token_address: EthAddress,
166    /// Destination network identifier (uint32)
167    pub destination_network: u32,
168    /// Destination address
169    pub destination_address: EthAddress,
170    /// Amount of tokens (uint256)
171    pub amount: EthAmount,
172    /// Metadata hash (32 bytes)
173    pub metadata_hash: MetadataHash,
174}
175
176impl SequentialCommit for LeafData {
177    type Commitment = Word;
178
179    fn to_elements(&self) -> Vec<Felt> {
180        const LEAF_DATA_ELEMENT_COUNT: usize = 32; // 1 + 1 + 5 + 1 + 5 + 8 + 8 + 3 (leafType + networks + addresses + amount + metadata + padding)
181        let mut elements = Vec::with_capacity(LEAF_DATA_ELEMENT_COUNT);
182
183        // LeafType (uint32 as Felt): 0u32 for transfer Ether / ERC20 tokens, 1u32 for message
184        // passing.
185        // for a `CLAIM` note, leafType is always 0 (transfer Ether / ERC20 tokens)
186        elements.push(Felt::ZERO);
187
188        // Origin network (encode as little-endian bytes for keccak)
189        let origin_network = u32::from_le_bytes(self.origin_network.to_be_bytes());
190        elements.push(Felt::from(origin_network));
191
192        // Origin token address (5 u32 felts)
193        elements.extend(self.origin_token_address.to_elements());
194
195        // Destination network (encode as little-endian bytes for keccak)
196        let destination_network = u32::from_le_bytes(self.destination_network.to_be_bytes());
197        elements.push(Felt::from(destination_network));
198
199        // Destination address (5 u32 felts)
200        elements.extend(self.destination_address.to_elements());
201
202        // Amount (uint256 as 8 u32 felts)
203        elements.extend(self.amount.to_elements());
204
205        // Metadata hash (8 u32 felts)
206        elements.extend(self.metadata_hash.to_elements());
207
208        // Padding
209        elements.extend(vec![Felt::ZERO; 3]);
210
211        elements
212    }
213}
214
215/// Data for creating a CLAIM note.
216///
217/// This struct groups the core data needed to create a CLAIM note that exactly
218/// matches the agglayer claimAsset function signature.
219#[derive(Clone)]
220pub struct ClaimNoteStorage {
221    /// Proof data containing SMT proofs and root hashes
222    pub proof_data: ProofData,
223    /// Leaf data containing network, address, amount, and metadata
224    pub leaf_data: LeafData,
225    /// Miden claim amount (scaled-down token amount as Felt)
226    pub miden_claim_amount: Felt,
227}
228
229impl TryFrom<ClaimNoteStorage> for NoteStorage {
230    type Error = NoteError;
231
232    fn try_from(storage: ClaimNoteStorage) -> Result<Self, Self::Error> {
233        // proof_data + leaf_data + miden_claim_amount
234        // 536 + 32 + 1
235        let mut claim_storage = Vec::with_capacity(569);
236
237        claim_storage.extend(storage.proof_data.to_elements());
238        claim_storage.extend(storage.leaf_data.to_elements());
239        claim_storage.push(storage.miden_claim_amount);
240
241        NoteStorage::new(claim_storage)
242    }
243}
244
245// NOTE CONSUMPTION COST
246// ================================================================================================
247
248impl NoteConsumptionCost for ClaimNote {
249    fn consumption_cycles() -> u32 {
250        CLAIM_CONSUMPTION_CYCLES
251    }
252
253    /// Consuming a CLAIM note creates the MINT note routed to the agglayer faucet (a network
254    /// account).
255    fn created_notes() -> Vec<NoteScriptRoot> {
256        vec![MintNote::script_root()]
257    }
258}