@@ -690,3 +690,153 @@ the little-endian bytes within each limb in `NoteStorage` and the big-endian-byt
690690The encoding is a bijection over the set of valid ` AccountId ` values: for every valid
691691` AccountId ` , ` from_account_id ` followed by ` to_account_id ` (or the MASM equivalent)
692692recovers the original.
693+
694+ ---
695+
696+ ## 6. Faucet Registry
697+
698+ The AggLayer bridge connects multiple chains, each with its own native token ecosystem.
699+ When tokens move between chains, they need a representation on the destination chain.
700+ This section describes how tokens are registered for bridging and the role of the
701+ faucet registry.
702+
703+ Terminology:
704+
705+ - Native token: a token originally issued on a given chain. For example, USDC on Ethereum
706+ is native to Ethereum; a fungible faucet created directly on Miden is native to Miden.
707+ - Non-native (wrapped) token: a representation of a foreign token, created to track
708+ bridged balances. On Miden, each non-native ERC20 is represented by a dedicated
709+ AggLayer faucet. On EVM chains, each non-native Miden token would be represented by a
710+ deployed wrapped ERC20 contract.
711+
712+ A faucet must be registered in the bridge's faucet registry before it can participate in
713+ bridging. The registry is a map in the bridge account's storage
714+ (` miden::agglayer::bridge::faucet_registry ` ) that records which faucet account IDs are
715+ authorized for bridge-out operations. Without registration, ` bridge_out::bridge_out `
716+ rejects the asset (see ` bridge_config::assert_faucet_registered ` ).
717+
718+ ### 6.1 Bridging-in: Registering non-native faucets on Miden
719+
720+ When a new ERC20 token is bridged to Miden for the first time, a corresponding AggLayer
721+ faucet account must be created and registered. The faucet serves as the mint/burn
722+ authority for the wrapped token on Miden.
723+
724+ The ` AggLayerFaucet ` struct (Rust, ` src/lib.rs ` ) captures the full faucet configuration:
725+
726+ - Token metadata: symbol, decimals, max_supply (stored in the standard
727+ ` NetworkFungibleFaucet ` metadata slot)
728+ - Bridge account ID: the bridge this faucet is paired with
729+ (` miden::agglayer::faucet ` )
730+ - Origin token address: the ERC20 contract address on the origin chain
731+ (` miden::agglayer::faucet::conversion_info_1 ` , ` conversion_info_2 ` )
732+ - Origin network: the chain ID of the origin chain (stored in ` conversion_info_2 ` )
733+ - Scale factor: the exponent used to convert between EVM U256 amounts and Miden felt
734+ amounts (stored in ` conversion_info_2 ` )
735+ - Metadata hash: ` keccak256(abi.encode(name, symbol, decimals)) ` , stored across two
736+ value slots (` miden::agglayer::faucet::metadata_hash_lo ` ,
737+ ` miden::agglayer::faucet::metadata_hash_hi ` )
738+
739+ Registration is performed via ` CONFIG_AGG_BRIDGE ` notes (see Section 3.3). The bridge
740+ operator creates a ` CONFIG_AGG_BRIDGE ` note containing the faucet's account ID and sends
741+ it to the bridge account. On consumption, the note script calls
742+ ` bridge_config::register_faucet ` , which:
743+
744+ 1 . Asserts the note sender matches the bridge admin stored in
745+ ` miden::agglayer::bridge::admin ` .
746+ 2 . Writes the faucet ID into the ` faucet_registry ` map with value ` [1, 0, 0, 0] ` .
747+
748+ The bridge admin is a trusted role. There is currently no on-chain verification that the
749+ faucet's stored metadata (origin address, scale, metadata hash) is correct. The bridge
750+ operator is trusted to deploy faucets with accurate configuration.
751+
752+ Implementation status:
753+
754+ - Implemented: Faucet creation with metadata hash, faucet storage layout, FPI retrieval
755+ of metadata hash via ` agglayer_faucet::get_metadata_hash ` , registration via
756+ ` CONFIG_AGG_BRIDGE ` notes, faucet registry lookup in ` bridge_config::assert_faucet_registered ` .
757+ - Not yet implemented: On-chain verification of the metadata hash during registration.
758+ This would require the token name to be available in faucet storage and
759+ ` abi.encode(string, string, uint8) ` to be implemented in MASM, so the bridge could
760+ recompute ` keccak256(abi.encode(name, symbol, decimals)) ` and compare it against the
761+ stored hash. See the TODO in ` bridge_config::register_faucet ` .
762+ - Not yet implemented: Token name storage in the faucet. Currently only the symbol is
763+ stored in the standard faucet metadata slot; the full name is not persisted on-chain.
764+ A separate PR is in progress to add this.
765+
766+ ### 6.2 Bridging-out: How Miden-native tokens are registered on other chains
767+
768+ When an asset is bridged out from Miden, ` bridge_out::bridge_out ` constructs a leaf for
769+ the Local Exit Tree. The leaf includes the metadata hash, which the bridge fetches from
770+ the faucet via FPI (` agglayer_faucet::get_metadata_hash ` ). The full leaf structure
771+ contains: leaf type, origin network, origin token address, destination network,
772+ destination address, amount (U256), and metadata hash (see ` bridge_out.masm ` ,
773+ ` add_leaf_bridge ` ).
774+
775+ On the EVM side, when a user claims the bridged asset via
776+ ` PolygonZkEVMBridgeV2.claimAsset() ` , the wrapped token is deployed lazily on first claim.
777+ The claimer provides the raw metadata bytes (the ABI-encoded name, symbol, and decimals)
778+ as a parameter to ` claimAsset() ` . The EVM bridge verifies that
779+ ` keccak256(metadata_bytes) == metadataHash ` from the Merkle leaf. If the hash matches and
780+ no wrapped token exists yet, the bridge deploys a new ` TokenWrapped ` ERC20 using the
781+ decoded name, symbol, and decimals from the metadata bytes.
782+
783+ Not yet implemented - registration of Miden-native faucets for bridging out:
784+
785+ The current implementation assumes all registered faucets wrap foreign (ERC20) tokens.
786+ For a Miden-native faucet (one that does not wrap an ERC20) to bridge out to an EVM
787+ chain, it would need to:
788+
789+ - Store a metadata hash computed from its token name, symbol, and decimals.
790+ - Be registered in the bridge's faucet registry.
791+ - Provide origin token address and origin network values that make sense for a
792+ Miden-native asset.
793+
794+ The design for how a Miden-native faucet registers itself for outbound bridging has not
795+ been finalized. An issue should be created to design this registration flow, covering
796+ questions such as: what origin network ID represents Miden, how the origin token address
797+ is derived for Miden-native tokens, and whether the bridge admin must approve registration
798+ or if self-registration is possible.
799+
800+ ### 6.3 Metadata hash
801+
802+ The metadata hash serves two purposes in the AggLayer bridge:
803+
804+ 1 . Leaf verification: the metadata hash is included in each Local Exit Tree leaf. When a
805+ claim is verified on the destination chain, the leaf's metadata hash must match the
806+ keccak256 of the caller-provided metadata bytes. This binds the token identity to the
807+ Merkle proof.
808+ 2 . Wrapped token deployment: on EVM chains, the raw metadata bytes (name, symbol,
809+ decimals) provided during ` claimAsset() ` are used to deploy the wrapped ERC20 contract.
810+ The hash verification ensures the deployed token has the correct name, symbol, and
811+ decimals.
812+
813+ The metadata hash is computed as:
814+
815+ ```
816+ keccak256(abi.encode(string name, string symbol, uint8 decimals))
817+ ```
818+
819+ This matches the encoding produced by the Solidity bridge's ` getTokenMetadata ` function.
820+ The ` abi.encode ` format for ` (string, string, uint8) ` is:
821+
822+ - 3 x 32-byte head slots: offset to name, offset to symbol, decimals value
823+ - Name: 32-byte length word + data padded to 32-byte boundary
824+ - Symbol: 32-byte length word + data padded to 32-byte boundary
825+
826+ In the Rust implementation, ` MetadataHash::from_token_info(name, symbol, decimals) `
827+ (defined in ` src/eth_types/metadata_hash.rs ` ) performs this encoding and hashing. The
828+ helper ` encode_token_metadata ` produces the raw ABI-encoded bytes, and
829+ ` MetadataHash::from_abi_encoded ` computes the keccak256 digest.
830+
831+ The metadata hash is computed off-chain at faucet creation time and stored pre-computed
832+ in faucet storage across two value slots (` metadata_hash_lo ` and ` metadata_hash_hi ` ,
833+ each holding 4 u32 felts). During bridge-out, the bridge retrieves the hash via FPI
834+ (` agglayer_faucet::get_metadata_hash ` ) rather than recomputing it on-chain.
835+
836+ Correctness argument: if a faucet stores an incorrect metadata hash, the EVM-side
837+ ` claimAsset() ` call will fail. The claimer must provide metadata bytes whose keccak256
838+ matches the leaf's metadata hash. Since keccak256 is preimage-resistant, the claimer
839+ cannot produce valid metadata bytes for an incorrect hash. This means a misconfigured
840+ faucet would result in unclaimable assets on the destination chain, providing a strong
841+ incentive for the bridge operator to set the correct metadata hash at faucet creation
842+ time.
0 commit comments