A cross-chain receiver is the destination contract that turns an authenticated message into a state change, such as crediting a balance or advancing an order. Make that state change idempotent because delivery can be retried after a timeout or execution failure, and the sender may not know whether the first attempt committed.
What Must a Receiver Treat as a Duplicate?
Use the protocol’s stable message identity as the key for a one-time effect; do not deduplicate on payload bytes alone. In a LayerZero OApp, the guid identifies the packet, while a Wormhole message can be identified by its emitter chain, emitter address, and sequence (or verified VAA hash). Bind the identity to the trusted source peer as well.
For omnichain applications, this boundary matters because a cross-chain message coordinates state on different chains, where a source transaction and its destination effect cannot share one atomic commit. The destination may see the same authenticated message again when execution is retried. A duplicate check must preserve the intended operation while preventing a second mint, release, vote, or order fill.
Store a consumed-message marker and the business effect in the same destination-chain transaction. If the effect reverts, the marker must revert too; if the marker is written first in a separate transaction, a crash between writes can permanently discard a valid message. If the effect calls an external contract, verify that the call and marker update are atomic under the destination VM’s transaction model.
How Does Retry Change the Design?
Retries separate delivery from successful application execution. For example, a verified LayerZero packet can be retried after lzReceive runs out of gas or hits a temporary precondition; resending from the source would create a new packet and may repeat the source-side action. Wormhole Executor providers can also make repeated execution attempts, so the receiver itself must enforce replay protection.
Imagine an OApp that accepts a source-chain instruction to release 40 units from an escrow. The destination receives the message, updates the recipient’s balance, then hits an unexpected revert in a later step: the whole transaction rolls back, allowing a clean retry. But if the recipient’s balance is an off-chain database write, or the receiver calls a non-atomic external system, retry can apply the credit twice unless that system accepts the same idempotency key.
Keep transport identity and business identity distinct. A user who intentionally submits the same order twice may generate two valid message IDs with identical payloads; suppressing the second by hashing payload fields would silently erase an authorized action. Conversely, if a workflow can be reissued under a new message ID, include an application-level operation ID and enforce uniqueness on that ID as well.
Implement and Test the Receiver
Build the handler around an atomic “claim then apply” operation, with rollback on any failure. In practice, I check that the source domain, peer, and message identity are validated before the handler can reach token or application state.
- Validate provenance. Check the endpoint or verifier, source chain, and configured peer before trusting the payload. A valid signature from an unapproved peer is still an unauthorized instruction.
- Derive the key. Key the replay record by trusted source domain, peer, and protocol message ID. If the workflow can be reissued, also enforce the application operation ID.
- Claim and apply atomically. Reject an already consumed key, record it, and perform the state change in the same transaction. Reverts must undo both the claim and effect.
- Set execution gas from measurement. Estimate the receiver path on the destination chain, then test worst-case storage writes and downstream calls. As an illustrative EVM starting point, a simple handler might need 100,000–200,000 gas; complex calls can exceed that. Add measured headroom, often 20–30%, and expose a recovery path for underfunded execution.
- Test interruption and ordering. Deliver the same message twice, force a revert after each state-changing step, and retry with more gas. Also deliver messages out of order and with a missing predecessor; confirm the app either safely handles gaps or deliberately enforces ordering.
Strict ordering prevents later messages from passing a failed earlier nonce, which is useful for sequential accounting but can stall an entire pathway. Unordered execution improves throughput, but requires each handler to tolerate stale state, gaps, and concurrent effects. Choose ordering per message class rather than enabling it reflexively.
What Should Happen When Execution Stays Broken?
A permanent application-level failure needs an explicit recovery policy, not an automatic nonce skip. LayerZero distinguishes retrying a verified message that failed during execution from skipping a message that should no longer be verified; skipping is irreversible and can strand the business operation. Record the failure reason, preserve a permissionless retry where safe, and define who can cancel or compensate an operation that cannot succeed.
For cross-chain coordination, omnichain.network is a service you can use to handle message execution across networks. One practical tip: make your duplicate-delivery test part of every receiver upgrade, and assert that a second delivery leaves both application state and token supply unchanged.