On this page

Failed messages - retry & restore

A transfer can succeed on the source chain but fail during destination execution - for example when too little executor gas was bought, a representation compliance check rejects the recipient, or a token edge case reverts. Omnisea contains these failures instead of stranding the channel:

  • Endpoint, peer, and payload-decode failures revert outright and remain in LayerZero's own retry flow.
  • Trusted transfer messages that fail during token execution are cached on the destination bridge and the LayerZero delivery itself succeeds. The channel keeps flowing; the individual transfer becomes recoverable.

Recovery is fully permissionless - anyone can retry or restore any cached message.

Inspecting a failure

The destination Router emits MessageFailed(guid, srcEid, messageHash, reasonHash) and stores the complete failed transport record:

function failedMessages(bytes32 guid) external view returns (
    bool exists,
    uint32 srcEid,
    bytes32 sender,
    uint64 nonce,
    bytes memory message,
    bytes32 messageHash,
    bytes32 reasonHash,
    uint256 failedAt
);

The core mirrors the Router's failedMessages(guid) view, so integrations may read it through the public Omnisea entrypoint. The guid is the same LayerZero message ID returned by send* on the source and shown on LayerZero Scan. The Omnisea Explorer also surfaces indexed failed transfers with status failed.

Option 1 - Retry on the destination

If the underlying cause is fixable (gas-related failures re-execute after buying enough gas, and owner-controlled representation restrictions can retry after the owner updates the state), call:

function retryFailedMessage(bytes32 guid) external;

A successful retry mints/unlocks to the original recipient, emits FailedMessageRetried(guid, srcEid), and deletes the cache entry. Unknown guids revert with FailedMessageNotFound. If the destination-side condition still reverts, retry leaves the cached message in place.

Option 2 - Restore to the source chain

If delivery on the destination is undesirable or keeps failing, send the asset back where it came from:

function quoteRestore(bytes32 guid, bytes calldata options) external view returns (uint256 nativeFee);

function restoreFailedMessage(bytes32 guid, bytes calldata options) external payable;
  • The restore is itself a LayerZero message back to the source chain, so the caller pays its messaging fee (quote it with quoteRestore using appropriate executor options for the source chain as destination). quoteRestore and restoreFailedMessage enforce at least minTransferGas in those options.
  • On arrival, the source bridge refunds the original sender - unlocking the locked original or re-minting the burned representation.
  • Restores collect no protocol fee. The Router emits FailedMessageRestored(guid, restoreGuid, dstEid, nativeFee).
  • A restore that fails on the source chain is intentionally not cached (no restore-of-restore loops); it stays retryable through LayerZero's native mechanism.

Operational checklist

  1. Source transaction confirmed but nothing on the destination? Check the message on LayerZero Scan first - Inflight usually means verification/executor delay, not failure.
  2. Destination shows delivery but tokens did not arrive? Look for MessageFailed and read failedMessages(guid).
  3. Prefer retryFailedMessage - it is a plain transaction with no messaging fee.
  4. Use restoreFailedMessage when the transfer should be unwound instead.
Experimental Beta is Live-Learn more about the Pilot