Documentation menu

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 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 bridge emits MessageFailed(guid, srcEid, originalChainId, originalToken, recipient, amount, reasonHash) and stores:

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 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 (it usually is - gas-related failures simply re-execute), 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.

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, and emit FailedMessageRestored(guid, restoreGuid, dstEid, refundRecipient, 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.
Feedback