Skip to main content
A configured bridge route connects a source-chain BridgeDepositVault to a Metal BridgeGateway and MIP410 pool. Its inboxes authenticate messages in both directions. The route provider supplies the contract addresses, supported assets, circuit artifacts and proving tools.

Route and asset reads

The gateway exposes these read methods. Struct fields are listed in ABI order. An unknown route has a zero inbox. An unknown asset route or claim has a zero asset. Each asset route maps a source token to a pool asset, with a reverse map for withdrawals. Amounts retain their raw integer units across the route, so confirm the asset denomination before displaying balances or constructing a note. pending counts claimed deposits awaiting minting. reserve counts minted deposits minus withdrawals through that route. New claims require reserve + pending + amount <= cap, while withdrawals require reserve >= amount for the selected route. The gateway authority registers each route once, and applications use that registered configuration. The source vault exposes:

Deposit and claim

Use parseAbi from the public viem package to encode these calls and decode these events. vaultAbi describes the source vault, and gatewayAbi describes the Metal gateway.
The depositor approves the vault to spend the exact amount of the supported ERC-20 token, then calls deposit. Amounts must satisfy 0 < amount < 2^128. The note commitment must be nonzero and below the BN254 field modulus. The token must return true from transfers and deliver the exact amount. A fee-on-transfer deposit reverts DepositDeltaMismatch and rolls back escrow. The vault derives the deposit ID from the pre-increment deposit nonce:
The successful transaction records depositOf(depositId) and queues claimDeposit(bytes32 depositId,address foreignToken,uint256 amount,bytes32 noteCommitment) on the Metal gateway. Decode the actual ID and outboxNonce from DepositReceived. A transaction simulation’s predicted ID can change if another deposit executes first. The gateway accepts claimDeposit only from a registered route’s inbox while foreignSender() equals that route’s vault. An accepted claim increases pending. Duplicate claims revert ClaimAlreadyRecorded. An unknown asset, out-of-range amount, or insufficient cap reverts without consuming the inbox nonce, so delivery remains retryable.

Screening and bridge mint proof

When screeningAuthority() is nonzero, only that authority can call screen(bytes32 depositId,bool passed). It sets an irreversible verdict, and mint requires Passed. Unset reverts NotScreened, while Failed reverts ScreeningFailed. A zero authority disables screening and refunds, and minting proceeds with the proof after the claim is recorded. A failed verdict releases the claim’s pending amount and queues refundDeposit(bytes32 depositId) on the source vault. The gateway’s DepositRefunded event identifies this message. Funds return only when that message executes through the vault’s metalInbox() with the gateway as metalSender(). The vault returns the recorded amount to the original depositor, marks refunded, and emits its own DepositRefunded. Call gateway mint(depositId, sanctionsRoot, mintProof) after the claim and required verdict. Any signing account can submit, and its balance in the gas token pays the transaction fee. The gateway calls the pool with the recorded asset, amount and note commitment. The owner prepares a BridgeMint UltraHonk proof whose ordered public inputs are: The proof establishes the note opening, owner authorization, and sanctions checks. It must use the pool’s current BridgeMint circuit verification key. For both minting and withdrawal, the sanctions root must equal the pool’s current root at execution. The pool appends the note, increases bridgeLiabilityOf(asset), sets bridgeDepositMinted(depositId) to true, and emits BridgeMinted(address indexed asset,bytes32 indexed depositId,uint256 amount,bytes32 indexed noteCommitment). The gateway moves the amount from pending to reserve in the same transaction. A duplicate mint reverts BridgeDepositAlreadyMinted. The mapped pool asset must have local shielding disabled and no local token backing. Deposits create shielded notes, and bridged value exits through the bridge withdrawal path.

Withdrawal proof and routing

The gateway’s withdraw accepts this request and the owner’s proof:
BridgeWithdrawn comes from the pool. WithdrawalRouted comes from the gateway. The route ID is a nonzero canonical BN254 field element assigned by the route provider. destCommitment contains a commitment to a claimant address and salt. The claimant authorizes the destination address when signing the sweep.
The owner signs the withdrawal’s full public statement with the spending key. The BridgeWithdraw proof uses these 13 public inputs in order: Metal chain ID, pool address, asset address, amount, route ID, claimant commitment, commitment root, nullifier 0, nullifier 1, input lock ID 0, input lock ID 1, change note commitment, and current sanctions root. Addresses are field elements. Nullifier and lock arrays each contain exactly two entries, with zero padding for an unused input. The change commitment is zero when there is no change. The pool verifies the proof against its current circuit and checks the historical commitment root, current sanctions root, unlocked inputs and unspent nullifiers. It spends the notes, appends any change note, and decreases bridge liability. The gateway decreases the selected route’s reserve and queues this destination-vault call:
The withdrawal ID is derived using the pool’s pre-increment withdrawal nonce:
Read the actual ID and message nonce from the successful receipt. An insufficient route reserve, invalid proof, or failed outbox call reverts the whole transaction, including note spending.

Queue, settle, and sweep

Only the destination vault’s inbox executing a message from its configured Metal gateway can call queueRelease. It records releasableAt = block.timestamp + withdrawalDelay using the destination chain’s queue block. The release begins with settled = false and swept = false. A duplicate release ID reverts ReleaseAlreadyQueued.
Anyone can call settle at or after releasableAt. It transfers the recorded amount to claimAddress(withdrawalId) and sets settled. That address is the CREATE2 address derived from the vault, withdrawal ID as salt, and claimInitCodeHash. Early settlement reverts ReleaseNotMature. The claimant then authorizes the final transfer through sweep. The claimant authorizes sweep with a secp256k1 signature. The signed message is the 32-byte inner hash below with the EIP-191 personal-message prefix:
With a viem claimant account, sign inner using signMessage({ message: { raw: inner } }) so the EIP-191 prefix is applied once. The vault’s sweepDigest returns the already-prefixed digest. The contract accepts the signature while block.timestamp <= deadline and requires the recovered address to equal the claimant address used in the commitment. Any signing account can submit the signature and pay the destination-chain transaction fee. sweep checks the commitment and signature, deploys the claim contract at the holding address, and transfers that address’s entire token balance to the signed destination. It marks swept and emits Swept with the release’s recorded amount. Unsolicited tokens sent to the holding address can make the actual transfer larger than that event amount. Verify the token transfer and destination balance separately. The claimant and salt become public in the sweep calldata. The sweep runs once. Missing settlement, an expired authorization, an incorrect commitment, an invalid signature, or a repeated sweep reverts. Failed token transfers roll back the corresponding settlement or sweep. Retain the claimant key and salt until final destination delivery is verified.