> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bridge vault and gateway

> Deposit claims, shielded bridge proofs, delayed releases, and claimant-authorized sweeps

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.

| Gateway method | Return type or ordered fields |
| - | - |
| `pool()` | `address` |
| `outbox()` | `address` |
| `authority()` | `address` |
| `screeningAuthority()` | `address`, zero disables screening |
| `routeOf(bytes32 routeId)` | `(address inbox, address vault, uint64 chainId)` |
| `routeIdOf(address inbox)` | `bytes32` |
| `assetRouteOf(bytes32 routeId, address foreignToken)` | `(address asset, uint128 cap, uint128 reserve, uint128 pending)` |
| `foreignTokenOf(bytes32 routeId, address asset)` | `address` |
| `claimOf(bytes32 depositId)` | `(address asset, uint128 amount, bytes32 noteCommitment, bytes32 routeId, address foreignToken)` |
| `verdictOf(bytes32 depositId)` | `uint8`, `0 = Unset`, `1 = Passed`, `2 = Failed` |

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:

| Vault method | Return type or ordered fields |
| - | - |
| `outbox()` | `address` |
| `metalChainId()` | `uint64` |
| `bridgeGateway()` | `address` |
| `metalInbox()` | `address` |
| `withdrawalDelay()` | `uint64`, seconds on the vault's chain |
| `supportedToken(address token)` | `bool` |
| `nextDepositNonce()` | `uint256` |
| `depositOf(bytes32 depositId)` | `(address depositor, address token, uint128 amount, bool refunded)` |
| `releaseOf(bytes32 withdrawalId)` | `(address token, uint128 amount, bytes32 claimCommitment, uint64 releasableAt, bool settled, bool swept)` |
| `claimAddress(bytes32 withdrawalId)` | `address` |
| `claimInitCodeHash()` | `bytes32` |
| `claimCommitment(address claimant, bytes32 salt)` | `bytes32` |
| `sweepDigest(bytes32 withdrawalId, address destination, uint64 deadline)` | `bytes32` |

## 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.

```typescript theme={null}
import { parseAbi } from 'viem'

const vaultAbi = parseAbi([
  'function deposit(address token, uint256 amount, bytes32 noteCommitment) returns (bytes32 depositId)',
  'function depositOf(bytes32 depositId) view returns ((address depositor, address token, uint128 amount, bool refunded))',
  'event DepositReceived(bytes32 indexed depositId, address indexed token, address indexed depositor, uint256 amount, bytes32 noteCommitment, uint256 outboxNonce)',
  'event DepositRefunded(bytes32 indexed depositId, address indexed token, address indexed depositor, uint256 amount)',
])

const gatewayAbi = parseAbi([
  'function claimOf(bytes32 depositId) view returns ((address asset, uint128 amount, bytes32 noteCommitment, bytes32 routeId, address foreignToken))',
  'function verdictOf(bytes32 depositId) view returns (uint8)',
  'function mint(bytes32 depositId, bytes32 sanctionsRoot, bytes mintProof) returns (bytes32)',
  'event DepositClaimed(bytes32 indexed depositId, bytes32 indexed routeId, address indexed asset, uint256 amount, bytes32 noteCommitment)',
  'event DepositScreened(bytes32 indexed depositId, uint8 verdict)',
  'event DepositRefunded(bytes32 indexed depositId, bytes32 indexed routeId, uint256 outboxNonce)',
])
```

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](https://docs.metalntwx.com/reference/assets-and-payments/mip410.md#commitment-and-field-rules). 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:

```text theme={null}
depositId = bytes32(uint256(keccak256(abi.encode(
  uint256(sourceChainId), address(vault), uint256(depositNonce),
  address(token), uint256(amount), bytes32(noteCommitment)
))) >> 8)
```

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:

| Index | Public input |
| - | - |
| 0 | Metal chain ID |
| 1 | Pool address as a field element |
| 2 | Note commitment |
| 3 | Amount |
| 4 | Pool asset address as a field element |
| 5 | Deposit ID |
| 6 | Current sanctions root |

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:

```typescript theme={null}
const withdrawalAbi = parseAbi([
  'function withdraw((address asset, uint256 amount, bytes32 routeId, bytes32 destCommitment, bytes32 commitmentRoot, bytes32[] inputLockIds, bytes32[] nullifiers, bytes32 changeNoteCommitment, bytes32 sanctionsRoot) request, bytes withdrawProof) returns (bytes32 withdrawalId)',
  'event WithdrawalRouted(bytes32 indexed withdrawalId, bytes32 indexed routeId, address indexed asset, address foreignToken, uint256 amount, uint256 outboxNonce)',
  'event BridgeWithdrawn(address indexed asset, bytes32 indexed routeId, bytes32 indexed withdrawalId, uint256 amount, bytes32 destCommitment)',
])
```

`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.

```text theme={null}
destCommitment = bytes32(uint256(keccak256(abi.encode(
  address(claimant), bytes32(salt)
))) >> 8)
```

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:

```text theme={null}
queueRelease(bytes32 withdrawalId, address token, uint256 amount, bytes32 claimCommitment)
```

The withdrawal ID is derived using the pool's pre-increment withdrawal nonce:

```text theme={null}
withdrawalId = bytes32(uint256(keccak256(abi.encode(
  uint256(metalChainId), address(pool), uint256(withdrawalNonce),
  address(asset), uint256(amount), bytes32(routeId), bytes32(destCommitment)
))) >> 8)
```

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`.

```typescript theme={null}
const releaseAbi = parseAbi([
  'function releaseOf(bytes32 withdrawalId) view returns ((address token, uint128 amount, bytes32 claimCommitment, uint64 releasableAt, bool settled, bool swept))',
  'function claimAddress(bytes32 withdrawalId) view returns (address)',
  'function claimCommitment(address claimant, bytes32 salt) pure returns (bytes32)',
  'function sweepDigest(bytes32 withdrawalId, address destination, uint64 deadline) view returns (bytes32)',
  'function settle(bytes32 withdrawalId)',
  'function sweep(bytes32 withdrawalId, address claimant, bytes32 salt, address destination, uint64 deadline, bytes signature)',
  'event ReleaseQueued(bytes32 indexed withdrawalId, address indexed token, uint256 amount, bytes32 claimCommitment, uint64 releasableAt)',
  'event Settled(bytes32 indexed withdrawalId, address indexed token, address indexed claimAddress, uint256 amount)',
  'event Swept(bytes32 indexed withdrawalId, address indexed token, address indexed destination, uint256 amount)',
])
```

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:

```text theme={null}
inner = keccak256(abi.encode(
  uint256(destinationChainId), address(vault), bytes32(withdrawalId),
  address(destination), uint64(deadline)
))
digest = keccak256("\x19Ethereum Signed Message:\n32" || inner)
```

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.