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
quoteRestoreusing appropriate executor options for the source chain as destination).quoteRestoreandrestoreFailedMessageenforce at leastminTransferGasin 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
- Source transaction confirmed but nothing on the destination? Check the message on LayerZero Scan first -
Inflightusually means verification/executor delay, not failure. - Destination shows delivery but tokens did not arrive? Look for
MessageFailedand readfailedMessages(guid). - Prefer
retryFailedMessage- it is a plain transaction with no messaging fee. - Use
restoreFailedMessagewhen the transfer should be unwound instead.