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

# Receive messages on Metal

> Prove a message commitment from a source outbox and deliver its authenticated call on Metal

> [!NOTE]
>
> **What you'll be able to do**
>
> * Deliver a message from another chain to Metal.
> * Handle authenticated incoming messages in a receiving contract.

An incoming message starts in an `Outbox` on the source chain. A relayer proves
its stored commitment under a checkpoint accepted by Metal's light client for
that chain. `ForeignInbox` verifies the evidence and calls the receiving
contract with the original payload.

## Check the route and receiving contract

Obtain the route provider's authenticated source chain ID, source RPC endpoint,
source `Outbox` address, artifact and approved runtime bytecode hash, Metal
`ForeignInbox` address and artifact, and light-client configuration. You also
need the receiving contract's ABI, address and authorized source sender.
Fund the relayer's signing account on Metal and the source transaction's signing
account on its chain for their respective fees.

Verify runtime bytecode against the supplied artifacts. Read the inbox's
`lightClient()`, `foreignOutbox()`, `foreignOutboxCodeHash()` and
`foreignChainId()` and compare them to the route configuration. The code hash
pins the runtime bytecode of a direct, immutable `Outbox`. A new `Outbox`
version requires a new inbox. Preserve application records across route
replacement to prevent completed instructions from executing again. The source
RPC must support `eth_getProof` at a checkpoint that the configured light client
accepts.

Inside the receiving function, require both `msg.sender == inbox` and
`IForeignInbox(inbox).foreignSender() == authorizedSourceSender` before changing
application state. These checks authenticate the route and source application.
The [inbox interface](https://docs.metalntwx.com/reference/interop/inbound-messages.md#foreign-inbox) exposes
`foreignSender()` only during execution and returns zero otherwise. Anyone may
relay a valid message, while the receiving contract authorizes its source
sender.

## Queue the source call

Encode the receiving contract's function selector and arguments with its ABI.
From the authorized source application, call
[`Outbox.send`](https://docs.metalntwx.com/reference/interop/inbound-messages#send-on-the-source-chain)
with Metal's chain ID as `uint64`, the receiving contract address, and that
payload. A source contract must make this call when the Metal contract
authorizes that contract as source sender. Calling the outbox directly from an
externally owned account authenticates that account as the source sender.

Save the signed transaction and its hash before broadcast. Decode `MessageSent`
from the configured source outbox in its successful receipt. Match destination
chain ID, `target`, `sender` and `payload`, then save the emitted nonce,
source block number and block hash. Wait for source-chain finality before
proceeding. If the submission result is uncertain, look up its saved transaction
hash to resume the same message.

## Select an accepted source checkpoint

Choose a finalized source block at or after the message's block and confirm
`isAccepted(checkpointBlockHash)` on the light client. Read its `checkpoint`
return value and retain the accepted block number and state root. The
[light-client reference](https://docs.metalntwx.com/reference/interop/light-clients.md) gives the exact
return fields and update signatures.

| Source | Checkpoint procedure |
| - | - |
| Ethereum | Read `latestBlockHash()` and `checkpoint(hash)` from the configured light client. Wait until its execution block number covers the message |
| Tempo | Relay the route provider's finalized header and recovered threshold certificate through `update`, or use an accepted covering checkpoint |
| Arc | Relay the finalized header and ordered precommit votes through `update`, or use an accepted covering checkpoint |

Metal block execution maintains Ethereum's light-client checkpoints.
`latestUpdateTimestamp()` can advance without a newer execution checkpoint, so
use the checkpoint's block number to decide whether it covers the message.

Tempo and Arc require a public certificate source and documented conversion to
their exact update arguments. Authenticate the provider's pinned group key or
validator set independently of that certificate feed. Each light client uses its
source chain's certificate format, as specified in the reference.

## Request the account and storage proofs

Install the public `viem` dependency with `pnpm add viem`. Using a source-chain
public client configured with the supplied endpoint, derive the storage key and
request its proof at the selected block number:

```typescript theme={null}
import { encodeAbiParameters, keccak256, parseAbiParameters } from 'viem'

const storageKey = keccak256(encodeAbiParameters(
  parseAbiParameters('uint256,uint256'),
  [nonce, 0n],
))
const sourceBlock = await sourceClient.getBlock({
  blockNumber: checkpoint.blockNumber,
})
if (sourceBlock.hash !== checkpointBlockHash ||
    sourceBlock.stateRoot !== checkpoint.stateRoot) {
  throw new Error('RPC block does not match the accepted checkpoint')
}
const proof = await sourceClient.getProof({
  address: sourceOutbox,
  storageKeys: [storageKey],
  blockNumber: checkpoint.blockNumber,
})
const sourceCode = await sourceClient.getCode({
  address: sourceOutbox,
  blockNumber: checkpoint.blockNumber,
})
if (!sourceCode || sourceCode === '0x' ||
    keccak256(sourceCode) !== outboxCodeHash || proof.codeHash !== outboxCodeHash) {
  throw new Error('Outbox runtime bytecode does not match the approved route')
}
```

`nonce` is the saved message nonce, and `sourceOutbox` is the authenticated
outbox address. `outboxCodeHash` is the inbox's `foreignOutboxCodeHash()` value,
already matched to the provider's approved runtime bytecode artifact. The
outbox's `messages` mapping is at storage slot zero. Select the `storageProof`
entry whose key equals `storageKey`, comparing keys as 256-bit integers if
their hex padding differs. Require a nonzero value and compare it to the message
commitment below:

```typescript theme={null}
const expectedCommitment = keccak256(encodeAbiParameters(
  parseAbiParameters('uint256,uint256,uint256,address,address,bytes'),
  [sourceChainId, metalChainId, nonce, sender, target, payload],
))
```

The account proof establishes both the outbox's pinned code hash and its storage
root under the accepted state root. The selected storage proof establishes the
message commitment under that storage root. Pass the response's `accountProof`
and the selected entry's `proof` separately so the inbox can verify both.

If the endpoint provides proofs only at the latest block, collect the block and
proof, then read the block again. Retry if it changed, and obtain checkpoint
acceptance for the exact block hash and state root used by the proof. Keep the
original proofs until acceptance, since a later checkpoint requires new proofs.
For Ethereum, Metal must record that exact execution checkpoint. Use a
historical proof provider if the endpoint cannot supply proofs for an accepted
checkpoint.

## Execute and record the result

Read `ForeignInbox.consumed(nonce)`. If false, simulate and submit
[`execute`](https://docs.metalntwx.com/reference/interop/inbound-messages#foreign-inbox) with
`checkpointBlockHash`, `nonce`, `sender`, `target`, `payload`,
`accountProof`, and `storageProof`, in that order. The relayer submits this
Metal transaction from its signing account. The signing account's balance in the
gas token pays the transaction fee. Save the signed bytes and transaction hash
before submission.

Require a successful receipt and `MessageExecuted` from the configured inbox
with the expected `nonce`, `target` and `sender`. Verify `consumed(nonce)` is
true and check the receiving contract's application event or state change. Check
[Metal finality](https://docs.metalntwx.com/reference/network-fundamentals/settlement.md) and the receipt's canonical block before
applying an irreversible downstream effect.

Key each delivery by Metal chain ID, inbox address and nonce, retaining the
source transaction and message fields. On an uncertain response, recover the
saved transaction hash and consumed state to find the existing execution. If
still pending, rebroadcast the same signed transaction. Retrying delivery keeps
the original message nonce, while a new `send` queues a separate instruction.

## Recover and test failed delivery

| Result | Recovery |
| - | - |
| Unknown checkpoint | Wait for or submit a covering checkpoint, then collect matching proofs |
| Account or storage proof failure | Check outbox, storage key, block hash and exact accepted state root |
| `CodeHashMismatch` | Compare the outbox's runtime bytecode hash with the inbox's approved hash, then correct the route or checkpoint selection |
| `MessageMismatch` | Compare the original message fields and both chain IDs with the submitted values |
| `AlreadyConsumed` | Recover the successful execution and verify its effect and finality |
| `TargetReverted` | Resolve the receiving contract's condition and retry the unchanged message |

A receiving contract's revert rolls back the consumed flag and every change in
the dispatched call. Track retries per message because the inbox allows other
nonces to execute first. Define any ordering, expiry or cancellation rules in
the application contract.

Test the configured route with a disposable receiving contract. An altered
payload or proof must fail without consuming the nonce. Cause the receiving
contract to revert and verify that application state stays unchanged, then
resolve that condition and deliver the same message. After success, replay must
fail without repeating the effect. Save failed and successful attempts so a
restarted relayer can distinguish retryable and completed delivery.

Use a controlled proof fixture with a different account code hash to verify
rejection before nonce consumption even when the storage value matches.


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