Skip to main content

miden_agglayer/
claim_note.rs

1use alloc::string::ToString;
2use alloc::vec;
3use alloc::vec::Vec;
4
5use miden_core::{Felt, Word};
6use miden_protocol::account::AccountId;
7use miden_protocol::crypto::SequentialCommit;
8use miden_protocol::crypto::rand::FeltRng;
9use miden_protocol::errors::NoteError;
10use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, NoteStorage, NoteType};
11use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint};
12
13use crate::utils::Keccak256Output;
14use crate::{EthAddress, EthAmount, GlobalIndex, MetadataHash, claim_script};
15
16// CLAIM NOTE TYPE ALIASES
17// ================================================================================================
18
19/// SMT node representation (32-byte Keccak256 hash)
20pub type SmtNode = Keccak256Output;
21
22/// Exit root representation (32-byte Keccak256 hash)
23pub type ExitRoot = Keccak256Output;
24
25/// Leaf value representation (32-byte Keccak256 hash)
26pub type LeafValue = Keccak256Output;
27
28/// Claimed Global Index (CGI) chain hash representation (32-byte Keccak256 hash)
29pub type CgiChainHash = Keccak256Output;
30
31/// Proof data for CLAIM note creation.
32/// Contains SMT proofs and root hashes using typed representations.
33#[derive(Clone)]
34pub struct ProofData {
35    /// SMT proof for local exit root (32 SMT nodes)
36    pub smt_proof_local_exit_root: [SmtNode; 32],
37    /// SMT proof for rollup exit root (32 SMT nodes)
38    pub smt_proof_rollup_exit_root: [SmtNode; 32],
39    /// Global index (uint256 as 32 bytes)
40    pub global_index: GlobalIndex,
41    /// Mainnet exit root hash
42    pub mainnet_exit_root: ExitRoot,
43    /// Rollup exit root hash
44    pub rollup_exit_root: ExitRoot,
45}
46
47impl SequentialCommit for ProofData {
48    type Commitment = Word;
49
50    fn to_elements(&self) -> Vec<Felt> {
51        const PROOF_DATA_ELEMENT_COUNT: usize = 536; // 32*8 + 32*8 + 8 + 8 + 8 (proofs + global_index + 2 exit roots)
52        let mut elements = Vec::with_capacity(PROOF_DATA_ELEMENT_COUNT);
53
54        // Convert SMT proof elements to felts (each node is 8 felts)
55        for node in self.smt_proof_local_exit_root.iter() {
56            elements.extend(node.to_elements());
57        }
58
59        for node in self.smt_proof_rollup_exit_root.iter() {
60            elements.extend(node.to_elements());
61        }
62
63        // Global index (uint256 as 32 bytes)
64        elements.extend(self.global_index.to_elements());
65
66        // Mainnet and rollup exit roots
67        elements.extend(self.mainnet_exit_root.to_elements());
68        elements.extend(self.rollup_exit_root.to_elements());
69
70        elements
71    }
72}
73
74/// Leaf data for CLAIM note creation.
75/// Contains network, address, amount, and metadata using typed representations.
76#[derive(Clone)]
77pub struct LeafData {
78    /// Origin network identifier (uint32)
79    pub origin_network: u32,
80    /// Origin token address
81    pub origin_token_address: EthAddress,
82    /// Destination network identifier (uint32)
83    pub destination_network: u32,
84    /// Destination address
85    pub destination_address: EthAddress,
86    /// Amount of tokens (uint256)
87    pub amount: EthAmount,
88    /// Metadata hash (32 bytes)
89    pub metadata_hash: MetadataHash,
90}
91
92impl SequentialCommit for LeafData {
93    type Commitment = Word;
94
95    fn to_elements(&self) -> Vec<Felt> {
96        const LEAF_DATA_ELEMENT_COUNT: usize = 32; // 1 + 1 + 5 + 1 + 5 + 8 + 8 + 3 (leafType + networks + addresses + amount + metadata + padding)
97        let mut elements = Vec::with_capacity(LEAF_DATA_ELEMENT_COUNT);
98
99        // LeafType (uint32 as Felt): 0u32 for transfer Ether / ERC20 tokens, 1u32 for message
100        // passing.
101        // for a `CLAIM` note, leafType is always 0 (transfer Ether / ERC20 tokens)
102        elements.push(Felt::ZERO);
103
104        // Origin network (encode as little-endian bytes for keccak)
105        let origin_network = u32::from_le_bytes(self.origin_network.to_be_bytes());
106        elements.push(Felt::from(origin_network));
107
108        // Origin token address (5 u32 felts)
109        elements.extend(self.origin_token_address.to_elements());
110
111        // Destination network (encode as little-endian bytes for keccak)
112        let destination_network = u32::from_le_bytes(self.destination_network.to_be_bytes());
113        elements.push(Felt::from(destination_network));
114
115        // Destination address (5 u32 felts)
116        elements.extend(self.destination_address.to_elements());
117
118        // Amount (uint256 as 8 u32 felts)
119        elements.extend(self.amount.to_elements());
120
121        // Metadata hash (8 u32 felts)
122        elements.extend(self.metadata_hash.to_elements());
123
124        // Padding
125        elements.extend(vec![Felt::ZERO; 3]);
126
127        elements
128    }
129}
130
131/// Data for creating a CLAIM note.
132///
133/// This struct groups the core data needed to create a CLAIM note that exactly
134/// matches the agglayer claimAsset function signature.
135#[derive(Clone)]
136pub struct ClaimNoteStorage {
137    /// Proof data containing SMT proofs and root hashes
138    pub proof_data: ProofData,
139    /// Leaf data containing network, address, amount, and metadata
140    pub leaf_data: LeafData,
141    /// Miden claim amount (scaled-down token amount as Felt)
142    pub miden_claim_amount: Felt,
143}
144
145impl TryFrom<ClaimNoteStorage> for NoteStorage {
146    type Error = NoteError;
147
148    fn try_from(storage: ClaimNoteStorage) -> Result<Self, Self::Error> {
149        // proof_data + leaf_data + miden_claim_amount
150        // 536 + 32 + 1
151        let mut claim_storage = Vec::with_capacity(569);
152
153        claim_storage.extend(storage.proof_data.to_elements());
154        claim_storage.extend(storage.leaf_data.to_elements());
155        claim_storage.push(storage.miden_claim_amount);
156
157        NoteStorage::new(claim_storage)
158    }
159}
160
161// CLAIM NOTE CREATION
162// ================================================================================================
163
164/// Generates a CLAIM note - a note that instructs the bridge to validate a claim and create
165/// a MINT note for the AggLayer Faucet.
166///
167/// # Parameters
168/// - `storage`: The core storage for creating the CLAIM note
169/// - `target_bridge_id`: The account ID of the bridge that should consume this note. Encoded as a
170///   `NetworkAccountTarget` attachment on the note metadata.
171/// - `sender_account_id`: The account ID of the CLAIM note creator
172/// - `rng`: Random number generator for creating the CLAIM note serial number
173///
174/// # Errors
175/// Returns an error if note creation fails.
176pub fn create_claim_note<R: FeltRng>(
177    storage: ClaimNoteStorage,
178    target_bridge_id: AccountId,
179    sender_account_id: AccountId,
180    rng: &mut R,
181) -> Result<Note, NoteError> {
182    let note_storage = NoteStorage::try_from(storage.clone())?;
183
184    let attachment = NetworkAccountTarget::new(target_bridge_id, NoteExecutionHint::Always)
185        .map_err(|e| NoteError::other(e.to_string()))?
186        .into();
187
188    let metadata =
189        NoteMetadata::new(sender_account_id, NoteType::Public).with_attachment(attachment);
190
191    let recipient = NoteRecipient::new(rng.draw_word(), claim_script(), note_storage);
192    let assets = NoteAssets::new(vec![])?;
193
194    Ok(Note::new(assets, metadata, recipient))
195}