Skip to main content

miden_agglayer/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5use miden_assembly::Library;
6use miden_assembly::serde::Deserializable;
7use miden_core::{Felt, Word};
8use miden_protocol::account::{
9    Account,
10    AccountBuilder,
11    AccountComponent,
12    AccountId,
13    AccountStorageMode,
14    AccountType,
15};
16use miden_protocol::asset::TokenSymbol;
17use miden_protocol::note::NoteScript;
18use miden_protocol::vm::Program;
19use miden_standards::account::access::Ownable2Step;
20use miden_standards::account::auth::NoAuth;
21use miden_standards::account::mint_policies::OwnerControlled;
22use miden_utils_sync::LazyLock;
23
24pub mod b2agg_note;
25pub mod bridge;
26pub mod claim_note;
27pub mod config_note;
28pub mod errors;
29pub mod eth_types;
30pub mod faucet;
31pub mod update_ger_note;
32pub mod utils;
33
34pub use b2agg_note::B2AggNote;
35pub use bridge::{AggLayerBridge, AgglayerBridgeError};
36pub use claim_note::{
37    CgiChainHash,
38    ClaimNoteStorage,
39    ExitRoot,
40    LeafData,
41    LeafValue,
42    ProofData,
43    SmtNode,
44    create_claim_note,
45};
46pub use config_note::ConfigAggBridgeNote;
47#[cfg(any(test, feature = "testing"))]
48pub use eth_types::GlobalIndexExt;
49pub use eth_types::{
50    EthAddress,
51    EthAmount,
52    EthAmountError,
53    EthEmbeddedAccountId,
54    GlobalIndex,
55    GlobalIndexError,
56    MetadataHash,
57};
58pub use faucet::{AggLayerFaucet, AgglayerFaucetError};
59pub use update_ger_note::UpdateGerNote;
60pub use utils::Keccak256Output;
61
62// AGGLAYER NOTE SCRIPTS
63// ================================================================================================
64
65// Initialize the CLAIM note script only once
66static CLAIM_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
67    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/CLAIM.masb"));
68    let program = Program::read_from_bytes(bytes).expect("shipped CLAIM script is well-formed");
69    NoteScript::new(program)
70});
71
72/// Returns the CLAIM (Bridge from AggLayer) note script.
73pub fn claim_script() -> NoteScript {
74    CLAIM_SCRIPT.clone()
75}
76
77// AGGLAYER ACCOUNT COMPONENTS
78// ================================================================================================
79
80static AGGLAYER_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
81    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/agglayer.masl"));
82    Library::read_from_bytes(bytes).expect("shipped AggLayer library is well-formed")
83});
84
85static BRIDGE_COMPONENT_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
86    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/bridge.masl"));
87    Library::read_from_bytes(bytes).expect("shipped bridge component library is well-formed")
88});
89
90static FAUCET_COMPONENT_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
91    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/faucet.masl"));
92    Library::read_from_bytes(bytes).expect("shipped faucet component library is well-formed")
93});
94
95/// Returns the AggLayer Library containing all agglayer modules.
96pub fn agglayer_library() -> Library {
97    AGGLAYER_LIBRARY.clone()
98}
99
100/// Returns the Bridge component library.
101fn agglayer_bridge_component_library() -> Library {
102    BRIDGE_COMPONENT_LIBRARY.clone()
103}
104
105/// Returns the Faucet component library.
106fn agglayer_faucet_component_library() -> Library {
107    FAUCET_COMPONENT_LIBRARY.clone()
108}
109
110// AGGLAYER ACCOUNT CREATION HELPERS
111// ================================================================================================
112
113/// Creates an agglayer faucet account component with the specified configuration.
114///
115/// This function creates all the necessary storage slots for an agglayer faucet:
116/// - Network faucet metadata slot (token_supply, max_supply, decimals, token_symbol)
117/// - Conversion info slot 1: first 4 felts of origin token address
118/// - Conversion info slot 2: 5th address felt + origin network + scale
119/// - Owner config slot: bridge account ID for MINT note authorization
120///
121/// # Parameters
122/// - `token_symbol`: The symbol for the fungible token (e.g., "AGG")
123/// - `decimals`: Number of decimal places for the token
124/// - `max_supply`: Maximum supply of the token
125/// - `token_supply`: Initial outstanding token supply (0 for new faucets)
126/// - `bridge_account_id`: The account ID of the bridge account for validation
127/// - `origin_token_address`: The EVM origin token address
128/// - `origin_network`: The origin network/chain ID
129/// - `scale`: The decimal scaling factor (exponent for 10^scale)
130///
131/// # Returns
132/// Returns an [`AccountComponent`] configured for agglayer faucet operations.
133///
134/// # Panics
135/// Panics if the token symbol is invalid or metadata validation fails.
136#[allow(clippy::too_many_arguments)]
137fn create_agglayer_faucet_component(
138    token_symbol: &str,
139    decimals: u8,
140    max_supply: Felt,
141    token_supply: Felt,
142    origin_token_address: &EthAddress,
143    origin_network: u32,
144    scale: u8,
145    metadata_hash: MetadataHash,
146) -> AccountComponent {
147    let symbol = TokenSymbol::new(token_symbol).expect("token symbol should be valid");
148    AggLayerFaucet::new(
149        symbol,
150        decimals,
151        max_supply,
152        token_supply,
153        *origin_token_address,
154        origin_network,
155        scale,
156        metadata_hash,
157    )
158    .expect("agglayer faucet metadata should be valid")
159    .into()
160}
161
162/// Creates a complete bridge account builder with the standard configuration.
163///
164/// The bridge starts with an empty faucet registry. Faucets are registered at runtime
165/// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`.
166fn create_bridge_account_builder(
167    seed: Word,
168    bridge_admin_id: AccountId,
169    ger_manager_id: AccountId,
170) -> AccountBuilder {
171    Account::builder(seed.into())
172        .storage_mode(AccountStorageMode::Network)
173        .with_component(AggLayerBridge::new(bridge_admin_id, ger_manager_id))
174}
175
176/// Creates a new bridge account with the standard configuration.
177///
178/// This creates a new account suitable for production use.
179pub fn create_bridge_account(
180    seed: Word,
181    bridge_admin_id: AccountId,
182    ger_manager_id: AccountId,
183) -> Account {
184    create_bridge_account_builder(seed, bridge_admin_id, ger_manager_id)
185        .with_auth_component(AccountComponent::from(NoAuth))
186        .build()
187        .expect("bridge account should be valid")
188}
189
190/// Creates an existing bridge account with the standard configuration.
191///
192/// This creates an existing account suitable for testing scenarios.
193#[cfg(any(feature = "testing", test))]
194pub fn create_existing_bridge_account(
195    seed: Word,
196    bridge_admin_id: AccountId,
197    ger_manager_id: AccountId,
198) -> Account {
199    create_bridge_account_builder(seed, bridge_admin_id, ger_manager_id)
200        .with_auth_component(AccountComponent::from(NoAuth))
201        .build_existing()
202        .expect("bridge account should be valid")
203}
204
205/// Creates a complete agglayer faucet account builder with the specified configuration.
206///
207/// The builder includes:
208/// - The `AggLayerFaucet` component (conversion metadata + token metadata).
209/// - The `Ownable2Step` component (bridge account ID as owner for mint authorization).
210/// - The `OwnerControlled` component (mint policy management required by
211///   `network_fungible::mint_and_send`).
212#[allow(clippy::too_many_arguments)]
213fn create_agglayer_faucet_builder(
214    seed: Word,
215    token_symbol: &str,
216    decimals: u8,
217    max_supply: Felt,
218    token_supply: Felt,
219    bridge_account_id: AccountId,
220    origin_token_address: &EthAddress,
221    origin_network: u32,
222    scale: u8,
223    metadata_hash: MetadataHash,
224) -> AccountBuilder {
225    let agglayer_component = create_agglayer_faucet_component(
226        token_symbol,
227        decimals,
228        max_supply,
229        token_supply,
230        origin_token_address,
231        origin_network,
232        scale,
233        metadata_hash,
234    );
235
236    Account::builder(seed.into())
237        .account_type(AccountType::FungibleFaucet)
238        .storage_mode(AccountStorageMode::Network)
239        .with_component(agglayer_component)
240        .with_component(Ownable2Step::new(bridge_account_id))
241        .with_component(OwnerControlled::owner_only())
242}
243
244/// Creates a new agglayer faucet account with the specified configuration.
245///
246/// This creates a new account suitable for production use.
247#[allow(clippy::too_many_arguments)]
248pub fn create_agglayer_faucet(
249    seed: Word,
250    token_symbol: &str,
251    decimals: u8,
252    max_supply: Felt,
253    bridge_account_id: AccountId,
254    origin_token_address: &EthAddress,
255    origin_network: u32,
256    scale: u8,
257    metadata_hash: MetadataHash,
258) -> Account {
259    create_agglayer_faucet_builder(
260        seed,
261        token_symbol,
262        decimals,
263        max_supply,
264        Felt::ZERO,
265        bridge_account_id,
266        origin_token_address,
267        origin_network,
268        scale,
269        metadata_hash,
270    )
271    .with_auth_component(AccountComponent::from(NoAuth))
272    .build()
273    .expect("agglayer faucet account should be valid")
274}
275
276/// Creates an existing agglayer faucet account with the specified configuration.
277///
278/// This creates an existing account suitable for testing scenarios.
279#[cfg(any(feature = "testing", test))]
280#[allow(clippy::too_many_arguments)]
281pub fn create_existing_agglayer_faucet(
282    seed: Word,
283    token_symbol: &str,
284    decimals: u8,
285    max_supply: Felt,
286    token_supply: Felt,
287    bridge_account_id: AccountId,
288    origin_token_address: &EthAddress,
289    origin_network: u32,
290    scale: u8,
291    metadata_hash: MetadataHash,
292) -> Account {
293    create_agglayer_faucet_builder(
294        seed,
295        token_symbol,
296        decimals,
297        max_supply,
298        token_supply,
299        bridge_account_id,
300        origin_token_address,
301        origin_network,
302        scale,
303        metadata_hash,
304    )
305    .with_auth_component(AccountComponent::from(NoAuth))
306    .build_existing()
307    .expect("agglayer faucet account should be valid")
308}