Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions crates/miden-agglayer/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,3 +689,182 @@ the little-endian bytes within each limb in `NoteStorage` and the big-endian-byt
The encoding is a bijection over the set of valid `AccountId` values: for every valid
`AccountId`, `from_account_id` followed by `to_account_id` (or the MASM equivalent)
recovers the original.

---

## 6. Faucet Registry

The AggLayer bridge connects multiple chains, each with its own native token ecosystem.
When tokens move between chains, they need a representation on the destination chain.
This section describes how tokens are registered for bridging and the role of the
faucet and token registries.

Terminology:

- Native token: a token originally issued on a given chain. For example, USDC on Ethereum
is native to Ethereum; a fungible faucet created directly on Miden is native to Miden.
- Non-native (wrapped) token: a representation of a foreign token, created to track
bridged balances. On Miden, each non-native ERC20 is represented by a dedicated
AggLayer faucet. On EVM chains, each non-native Miden token would be represented by a
deployed wrapped ERC20 contract.

A faucet must be registered in the bridge before it can participate in bridging. The
bridge maintains two registry maps:

- **Faucet registry** (`agglayer::bridge::faucet_registry_map`): maps faucet account IDs
to a registration flag. Used during bridge-out to verify an asset's faucet is authorized
(see `bridge_config::assert_faucet_registered`).
- **Token registry** (`agglayer::bridge::token_registry_map`): maps Poseidon2 hashes of
origin token addresses to faucet account IDs. Used during bridge-in to look up the
correct faucet for a given origin token (see
`bridge_config::lookup_faucet_by_token_address`).

Both registries are populated atomically by `bridge_config::register_faucet`.

### 6.1 Bridging-in: Registering non-native faucets on Miden

When a new ERC20 token is bridged to Miden for the first time, a corresponding AggLayer
faucet account must be created and registered. The faucet serves as the mint/burn
authority for the wrapped token on Miden.

The `AggLayerFaucet` struct (Rust, `src/faucet.rs`) captures the faucet-specific
configuration:

- Token metadata: symbol, decimals, max_supply, token_supply (stored in the standard
`TokenMetadata` slot)
- Origin token address: the ERC20 contract address on the origin chain
(`agglayer::faucet::conversion_info_1`, `conversion_info_2`)
- Origin network: the chain ID of the origin chain (stored in `conversion_info_2`)
- Scale factor: the exponent used to convert between EVM U256 amounts and Miden felt
amounts (stored in `conversion_info_2`)
- Metadata hash: `keccak256(abi.encode(name, symbol, decimals))`, stored across two
value slots (`agglayer::faucet::metadata_hash_lo`,
`agglayer::faucet::metadata_hash_hi`)

The faucet account also requires two companion components:

- `Ownable2Step`: stores the bridge account ID as the faucet's owner, used for
authorization of mint operations.
- `OwnerControlled`: provides mint policy management (active policy proc root, allowed
policy proc roots, policy authority).

Registration is performed via `CONFIG_AGG_BRIDGE` notes (see Section 3.3). The bridge
operator creates a `CONFIG_AGG_BRIDGE` note containing the faucet's account ID and the
origin token address, then sends it to the bridge account. On consumption, the note
script calls `bridge_config::register_faucet`, which performs a two-step registration:

1. Asserts the note sender matches the bridge admin stored in
`agglayer::bridge::admin_account_id`.
2. Writes the faucet ID into the `faucet_registry_map`:
key `[0, 0, faucet_id_suffix, faucet_id_prefix]`, value `[1, 0, 0, 0]`.
3. Hashes the origin token address (5 felts) using `Poseidon2::hash_elements` and writes
the mapping into the `token_registry_map`:
key `hash(origin_token_addr)`, value `[0, 0, faucet_id_suffix, faucet_id_prefix]`.

The token registry enables the bridge to resolve which faucet corresponds to a given
origin token address during CLAIM note processing. When the bridge's `claim` procedure
processes a CLAIM note, it reads the origin token address from the leaf data and calls
`bridge_config::lookup_faucet_by_token_address` to find the registered faucet. This
lookup hashes the address with Poseidon2 and retrieves the faucet ID from the token
registry map. If the token address is not registered, the lookup panics.

The bridge admin is a trusted role. There is currently no on-chain verification that the
faucet's stored metadata (origin address, scale, metadata hash) is correct. The bridge
operator is trusted to deploy faucets with accurate configuration.

Implementation status:

- Implemented: Faucet creation with metadata hash, faucet storage layout, FPI retrieval
of metadata hash via `agglayer_faucet::get_metadata_hash`, two-step registration via
`CONFIG_AGG_BRIDGE` notes (faucet registry + token registry), faucet registry lookup in
`bridge_config::assert_faucet_registered`, token registry lookup in
`bridge_config::lookup_faucet_by_token_address`.
- Not yet implemented: On-chain verification of the metadata hash during registration
([#2586](https://github.com/0xMiden/protocol/issues/2586)).
This would require the token name to be available in faucet storage and
`abi.encode(string, string, uint8)` to be implemented in MASM, so the bridge could
recompute `keccak256(abi.encode(name, symbol, decimals))` and compare it against the
stored hash.
- Not yet implemented: Token name storage in the faucet
([#2585](https://github.com/0xMiden/protocol/issues/2585),
related: [PR #2439](https://github.com/0xMiden/protocol/pull/2439)).
Currently only the symbol is stored in the standard faucet metadata slot; the full
name is not persisted on-chain.

### 6.2 Bridging-out: How Miden-native tokens are registered on other chains

When an asset is bridged out from Miden, `bridge_out::bridge_out` constructs a leaf for
the Local Exit Tree. The leaf includes the metadata hash, which the bridge fetches from
the faucet via FPI (`agglayer_faucet::get_metadata_hash`). The full leaf structure
contains: leaf type, origin network, origin token address, destination network,
destination address, amount (U256), and metadata hash (see `bridge_out.masm`,
`add_leaf_bridge`).

On the EVM side, when a user claims the bridged asset via
`PolygonZkEVMBridgeV2.claimAsset()`, the wrapped token is deployed lazily on first claim.
The claimer provides the raw metadata bytes (the ABI-encoded name, symbol, and decimals)
as a parameter to `claimAsset()`. The EVM bridge verifies that
`keccak256(metadata_bytes) == metadataHash` from the Merkle leaf. If the hash matches and
no wrapped token exists yet, the bridge deploys a new `TokenWrapped` ERC20 using the
decoded name, symbol, and decimals from the metadata bytes.

Not yet implemented - registration of Miden-native faucets for bridging out:

The current implementation assumes all registered faucets wrap foreign (ERC20) tokens.
For a Miden-native faucet (one that does not wrap an ERC20) to bridge out to an EVM
chain, it would need to:

- Store a metadata hash computed from its token name, symbol, and decimals.
- Be registered in the bridge's faucet registry.
- Provide origin token address and origin network values that make sense for a
Miden-native asset.

The design for how a Miden-native faucet registers itself for outbound bridging has not
been finalized. An issue should be created to design this registration flow, covering
questions such as: what origin network ID represents Miden, how the origin token address
is derived for Miden-native tokens, and whether the bridge admin must approve registration
or if self-registration is possible.

### 6.3 Metadata hash

The metadata hash serves two purposes in the AggLayer bridge:

1. Leaf verification: the metadata hash is included in each Local Exit Tree leaf. When a
claim is verified on the destination chain, the leaf's metadata hash must match the
keccak256 of the caller-provided metadata bytes. This binds the token identity to the
Merkle proof.
2. Wrapped token deployment: on EVM chains, the raw metadata bytes (name, symbol,
decimals) provided during `claimAsset()` are used to deploy the wrapped ERC20 contract.
The hash verification ensures the deployed token has the correct name, symbol, and
decimals.

The metadata hash is computed as:

```
keccak256(abi.encode(string name, string symbol, uint8 decimals))
```

This matches the encoding produced by the Solidity bridge's `getTokenMetadata` function.
The `abi.encode` format for `(string, string, uint8)` is:

- 3 x 32-byte head slots: offset to name, offset to symbol, decimals value
- Name: 32-byte length word + data padded to 32-byte boundary
- Symbol: 32-byte length word + data padded to 32-byte boundary

In the Rust implementation, `MetadataHash::from_token_info(name, symbol, decimals)`
(defined in `src/eth_types/metadata_hash.rs`) performs this encoding and hashing. The
helper `encode_token_metadata` produces the raw ABI-encoded bytes, and
`MetadataHash::from_abi_encoded` computes the keccak256 digest.

The metadata hash is computed off-chain at faucet creation time and stored pre-computed
in faucet storage across two value slots (`metadata_hash_lo` and `metadata_hash_hi`,
each holding 4 u32 felts). During bridge-out, the bridge retrieves the hash via FPI
(`agglayer_faucet::get_metadata_hash`) rather than recomputing it on-chain.

Correctness argument: if a faucet stores an incorrect metadata hash, the EVM-side
`claimAsset()` call will fail. The claimer must provide metadata bytes whose keccak256
matches the leaf's metadata hash. Since keccak256 is preimage-resistant, the claimer
cannot produce valid metadata bytes for an incorrect hash. This means a misconfigured
faucet would result in unclaimable assets on the destination chain, providing a strong
incentive for the bridge operator to set the correct metadata hash at faucet creation
time.
Loading