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::{Account, AccountBuilder, AccountComponent, AccountId, AccountType};
9use miden_protocol::asset::TokenSymbol;
10use miden_standards::account::access::{Authority, Ownable2Step};
11use miden_standards::account::auth::AuthNetworkAccount;
12use miden_standards::account::policies::{
13    BurnAllowAll,
14    BurnPolicyConfig,
15    MintPolicyConfig,
16    PolicyRegistration,
17    TokenPolicyManager,
18    TransferPolicy,
19};
20use miden_utils_sync::LazyLock;
21
22pub mod b2agg_note;
23pub mod bridge;
24pub mod claim_note;
25pub mod config_note;
26pub mod errors;
27pub mod eth_types;
28pub mod faucet;
29#[cfg(feature = "testing")]
30pub mod testing;
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    ClaimNote,
39    ClaimNoteStorage,
40    ExitRoot,
41    LeafData,
42    LeafValue,
43    ProofData,
44    SmtNode,
45};
46pub use config_note::{ConfigAggBridgeNote, ConversionMetadata};
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 ACCOUNT COMPONENTS
63// ================================================================================================
64
65static AGGLAYER_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
66    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/agglayer.masl"));
67    Library::read_from_bytes(bytes).expect("shipped AggLayer library is well-formed")
68});
69
70static BRIDGE_COMPONENT_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
71    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/bridge.masl"));
72    Library::read_from_bytes(bytes).expect("shipped bridge component library is well-formed")
73});
74
75static FAUCET_COMPONENT_LIBRARY: LazyLock<Library> = LazyLock::new(|| {
76    let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/components/faucet.masl"));
77    Library::read_from_bytes(bytes).expect("shipped faucet component library is well-formed")
78});
79
80/// Returns the AggLayer Library containing all agglayer modules.
81pub fn agglayer_library() -> Library {
82    AGGLAYER_LIBRARY.clone()
83}
84
85/// Returns the Bridge component library.
86fn agglayer_bridge_component_library() -> Library {
87    BRIDGE_COMPONENT_LIBRARY.clone()
88}
89
90/// Returns the Faucet component library.
91fn agglayer_faucet_component_library() -> Library {
92    FAUCET_COMPONENT_LIBRARY.clone()
93}
94
95// AGGLAYER ACCOUNT CREATION HELPERS
96// ================================================================================================
97
98/// Creates an agglayer faucet account component with the specified configuration.
99///
100/// The faucet holds only token metadata; conversion metadata (origin address, origin network,
101/// scale, metadata hash) lives on the bridge and is populated at registration time.
102///
103/// # Parameters
104/// - `token_symbol`: The symbol for the fungible token (e.g., "AGG")
105/// - `decimals`: Number of decimal places for the token
106/// - `max_supply`: Maximum supply of the token
107/// - `token_supply`: Initial outstanding token supply (0 for new faucets)
108///
109/// # Returns
110/// Returns an [`AccountComponent`] configured for agglayer faucet operations.
111///
112/// # Panics
113/// Panics if the token symbol is invalid or metadata validation fails.
114fn create_agglayer_faucet_component(
115    token_symbol: &str,
116    decimals: u8,
117    max_supply: Felt,
118    token_supply: Felt,
119) -> AccountComponent {
120    let symbol = TokenSymbol::new(token_symbol).expect("token symbol should be valid");
121    AggLayerFaucet::new(symbol, decimals, max_supply, token_supply)
122        .expect("agglayer faucet metadata should be valid")
123        .into()
124}
125
126/// Creates a complete bridge account builder with the standard configuration.
127///
128/// The bridge starts with an empty faucet registry. Faucets are registered at runtime
129/// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`.
130///
131/// The builder is pre-wired with the [`AuthNetworkAccount`] auth component, initialized with
132/// [`AggLayerBridge::allowed_notes()`] so the bridge only accepts its sanctioned input notes.
133fn create_bridge_account_builder(
134    seed: Word,
135    bridge_admin_id: AccountId,
136    ger_manager_id: AccountId,
137    network_id: u32,
138) -> AccountBuilder {
139    Account::builder(seed.into())
140        .account_type(AccountType::Public)
141        .with_component(AggLayerBridge::new(bridge_admin_id, ger_manager_id, network_id))
142        .with_auth_component(
143            AuthNetworkAccount::with_allowed_notes(AggLayerBridge::allowed_notes())
144                .expect("bridge note allowlist is non-empty"),
145        )
146}
147
148/// Creates a new bridge account with the standard configuration.
149///
150/// This creates a new account suitable for production use.
151pub fn create_bridge_account(
152    seed: Word,
153    bridge_admin_id: AccountId,
154    ger_manager_id: AccountId,
155    network_id: u32,
156) -> Account {
157    create_bridge_account_builder(seed, bridge_admin_id, ger_manager_id, network_id)
158        .build()
159        .expect("bridge account should be valid")
160}
161
162/// Creates an existing bridge account with the standard configuration.
163///
164/// This creates an existing account suitable for testing scenarios.
165#[cfg(any(feature = "testing", test))]
166pub fn create_existing_bridge_account(
167    seed: Word,
168    bridge_admin_id: AccountId,
169    ger_manager_id: AccountId,
170    network_id: u32,
171) -> Account {
172    create_bridge_account_builder(seed, bridge_admin_id, ger_manager_id, network_id)
173        .build_existing()
174        .expect("bridge account should be valid")
175}
176
177/// Creates a complete agglayer faucet account builder with the specified configuration.
178///
179/// The builder includes:
180/// - The `AggLayerFaucet` component (token metadata only).
181/// - The `Ownable2Step` component (bridge account ID as owner for mint authorization).
182/// - A [`TokenPolicyManager`] (owner-controlled) configured with `MintPolicyConfig::OwnerOnly` and
183///   `BurnPolicyConfig::OwnerOnly`. The manager additionally registers `BurnAllowAll::root()` as an
184///   allowed burn policy so the owner can open burns at runtime via `set_burn_policy`. The active
185///   mint policy component (`MintOwnerOnly`) and burn policy component (`BurnOwnerOnly`) are
186///   produced by the manager; `BurnAllowAll` is installed separately as the additional allowed burn
187///   policy procedure.
188/// - The [`AuthNetworkAccount`] auth component, initialized with
189///   [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes.
190fn create_agglayer_faucet_builder(
191    seed: Word,
192    token_symbol: &str,
193    decimals: u8,
194    max_supply: Felt,
195    token_supply: Felt,
196    bridge_account_id: AccountId,
197) -> AccountBuilder {
198    let agglayer_component =
199        create_agglayer_faucet_component(token_symbol, decimals, max_supply, token_supply);
200
201    // `allow_all` is explicitly registered as Reserved so the owner can open burns at runtime
202    // via `set_burn_policy`.
203    let token_policy_manager = TokenPolicyManager::new()
204        .with_mint_policy(MintPolicyConfig::OwnerOnly, PolicyRegistration::Active)
205        .expect("active mint policy is registered exactly once")
206        .with_burn_policy(BurnPolicyConfig::OwnerOnly, PolicyRegistration::Active)
207        .expect("active burn policy is registered exactly once")
208        .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Reserved)
209        .expect("reserved burn policy registration does not conflict")
210        .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)
211        .expect("active send policy is registered exactly once")
212        .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)
213        .expect("active receive policy is registered exactly once");
214
215    Account::builder(seed.into())
216        .account_type(AccountType::Public)
217        .with_component(agglayer_component)
218        .with_component(Ownable2Step::new(bridge_account_id))
219        .with_component(Authority::OwnerControlled)
220        .with_components(token_policy_manager)
221        .with_component(BurnAllowAll)
222        .with_auth_component(
223            AuthNetworkAccount::with_allowed_notes(AggLayerFaucet::allowed_notes())
224                .expect("faucet note allowlist is non-empty"),
225        )
226}
227
228/// Creates a new agglayer faucet account with the specified configuration.
229///
230/// This creates a new account suitable for production use.
231pub fn create_agglayer_faucet(
232    seed: Word,
233    token_symbol: &str,
234    decimals: u8,
235    max_supply: Felt,
236    bridge_account_id: AccountId,
237) -> Account {
238    create_agglayer_faucet_builder(
239        seed,
240        token_symbol,
241        decimals,
242        max_supply,
243        Felt::ZERO,
244        bridge_account_id,
245    )
246    .build()
247    .expect("agglayer faucet account should be valid")
248}
249
250/// Creates an existing agglayer faucet account with the specified configuration.
251///
252/// This creates an existing account suitable for testing scenarios.
253#[cfg(any(feature = "testing", test))]
254pub fn create_existing_agglayer_faucet(
255    seed: Word,
256    token_symbol: &str,
257    decimals: u8,
258    max_supply: Felt,
259    token_supply: Felt,
260    bridge_account_id: AccountId,
261) -> Account {
262    create_agglayer_faucet_builder(
263        seed,
264        token_symbol,
265        decimals,
266        max_supply,
267        token_supply,
268        bridge_account_id,
269    )
270    .build_existing()
271    .expect("agglayer faucet account should be valid")
272}