On this page

Omnilink

Omnilink relays raw ABI results from approved source-chain staticcall paths and stores them on trusted destination peers.

The implementation is generic: result decoding belongs in source validators and destination consumer adapters. Omnilink v2 is constructor- and payload-incompatible with the earlier Chainlink-specific implementation.

contract Omnilink is Ownable2Step, ReentrancyGuard, IOmnilink

Constructor

constructor(
    address endpoint,
    uint32 localEndpointId,
    uint256 localChainId,
    address initialOwner,
    address payable protocolFeeReceiver,
    uint256 fixedProtocolFee,
    uint256 maxProtocolFee
);

The constructor binds the LayerZero Endpoint V2, sets its delegate, records the local EID and EVM chain ID, and caps future flat-fee changes at the immutable maxProtocolFee.

Omnilink currently supports endpoints paid in the chain's native gas token. Quoting on an endpoint whose nativeToken() is nonzero reverts with NativeFeeEndpointUnsupported.

Constants

ConstantValuePurpose
PAYLOAD_VERSION2Separates v2 read IDs and payloads from older versions
ENDPOINT_ACTIVATION_TIME7 daysDelay for a peer that is not the same address as the local Omnilink
REQUEST_CANCEL_DELAY1 hoursEarliest cancellation time for an unanswered OMNI reward
MAX_CLOCK_SKEW5 minutesMaximum accepted future source timestamp
MIN_READ_GAS10,000Minimum configured source staticcall gas
MAX_READ_GAS5,000,000Maximum configured source staticcall gas
MAX_CALLDATA_BYTES4,096Maximum registered call data
MAX_RESULT_BYTES8,192Maximum returned ABI data
MAX_MESSAGE_BYTES9,216Maximum complete LayerZero message

Structs

struct ReadConfig {
    address validator;
    uint64 gasLimit;
    uint32 maxResultBytes;
    bool enabled;
    bool exists;
}

struct ReadResult {
    uint256 sourceChainId;
    address readSender;
    address target;
    bytes4 selector;
    bytes32 callDataHash;
    bytes32 targetCodeHash;
    bytes32 resultHash;
    bytes32 guid;
    uint64 sequence;
    uint64 sourceTimestamp;
    uint64 sourceBlockNumber;
    uint64 deliveredAt;
    bool exists;
    bytes result;
}

ReadPayload contains the same source identity and observation fields while a message is in transit. The destination recomputes the read ID and rejects malformed chain identity, empty results, oversized results, and timestamps too far in the future.

OMNI rewards use:

enum RewardStatus {
    None,
    Locked,
    Awarded,
    Cancelled
}

struct RewardLock {
    address requester;
    address target;
    bytes32 callDataHash;
    uint256 sourceChainId;
    uint256 rewardAmount;
    uint64 createdAt;
    RewardStatus status;
}

Read identity

function readId(
    uint256 sourceChainId,
    address target,
    bytes calldata callData
) public pure returns (bytes32);

function readIdFromHash(
    uint256 sourceChainId,
    address target,
    bytes32 callDataHash
) public pure returns (bytes32);

The hash covers PAYLOAD_VERSION, source chain ID, target, and complete calldata hash. Arguments therefore form part of the path identity.

Configure and execute reads

configureRead

function configureRead(
    address target,
    bytes calldata callData,
    address validator,
    uint64 gasLimit,
    uint32 maxResultBytes,
    bool enabled
) external onlyOwner returns (bytes32 id);

Registers the exact source call and its bounds. If validator is nonzero, Omnilink calls:

interface IOmnilinkReadValidator {
    function validate(
        address target,
        bytes calldata callData,
        bytes calldata result
    ) external view;
}

The validator must revert to reject a result.

previewRead

function previewRead(
    address target,
    bytes calldata callData
) external view returns (bytes memory result);

Executes the registered bounded staticcall and optional validator without sending a message.

quoteRead

function quoteRead(
    uint32 dstEid,
    address target,
    bytes calldata callData,
    bytes calldata options
) external view returns (uint256 totalFee);

Returns the current LayerZero native fee plus fixedProtocolFee. Quoting executes the source read to build the exact payload.

sendRead

function sendRead(
    uint32 dstEid,
    address target,
    bytes calldata callData,
    bytes calldata options
) external payable nonReentrant returns (MessagingReceipt memory receipt);

Executes the source read, increments that read path's sequence, sends the result to one destination, accounts for one fixed protocol fee, and refunds excess msg.value.

The source caller becomes readSender. This address is included in the payload and receives any matching destination OMNI reward.

Read results

function getResult(bytes32 id)
    public view returns (ReadResult memory result);

function getResult(
    uint256 sourceChainId,
    address target,
    bytes calldata callData
) external view returns (ReadResult memory result);

function getResultByHash(
    uint256 sourceChainId,
    address target,
    bytes32 callDataHash
) external view returns (ReadResult memory result);

function readWord(bytes32 id, uint256 wordIndex)
    public view returns (bytes32 word);

function readUint256(bytes32 id, uint256 wordIndex)
    external view returns (uint256);

function readInt256(bytes32 id, uint256 wordIndex)
    external view returns (int256);

function requireFresh(bytes32 id, uint64 maxAge) public view;

requireFresh compares sourceTimestamp, not destination delivery time. A source timestamp up to MAX_CLOCK_SKEW in the future is tolerated; a larger future timestamp is rejected during receipt or freshness checking.

If an older sequence arrives after a newer one, Omnilink emits StaleReadIgnored, keeps the newer result, and does not award a currently locked reward to the stale sender.

Optional OMNI rewards

Configure OMNI

function setOmniToken(address token) external onlyOwner;

The OMNI address can be set once. One-time configuration prevents outstanding liabilities from being re-denominated into another token.

Request a read locally

function requestRead(
    uint256 sourceChainId,
    address target,
    bytes calldata callData,
    uint256 rewardAmount
) external nonReentrant returns (bytes32 readId);

Locks exactly rewardAmount OMNI under the destination's deterministic read ID. Fee-on-transfer behavior is rejected by checking the balance delta. The source route must already have an active peer, but this function sends no cross-chain message.

One read ID can have at most one currently locked reward. After an award or cancellation, a new request can reuse the path.

Cancel or claim

function cancelReadRequest(bytes32 readId)
    external nonReentrant;

function claimOmniRewards(address recipient)
    external nonReentrant returns (uint256 amount);

Only the requester can cancel, and only after REQUEST_CANCEL_DELAY. Successful receipt credits the matching locked amount to claimableOmni[readSender]; claiming performs the token transfer separately from LayerZero delivery.

Peer and pause controls

function setPeer(uint32 eid, bytes32 peer, uint256 chainId) external onlyOwner;
function setPeerEnabled(uint32 eid, bool enabled) external onlyOwner;
function setLayerZeroDelegate(address delegate) external onlyOwner;
function setPauseState(bool inboundPaused, bool outboundPaused) external onlyOwner;
function peerActivationTime(uint32 eid) public view returns (uint64);
function isPeerActive(uint32 eid) public view returns (bool);
function isTrustedPeer(uint32 eid, bytes32 peer) public view returns (bool);

Peer identity and its chain ID are assigned once. Repeating the same assignment is a no-op; replacing either value reverts. The same source chain ID cannot be assigned to multiple EIDs.

Same-address peers activate immediately. A different-address peer activates after seven days. setPeerEnabled provides a reversible route stop without changing identity.

Inbound and outbound pause states are independent. Pausing inbound delivery causes LayerZero execution to revert for later retry; it does not delete stored results.

LayerZero receiver functions

function lzReceive(
    Origin calldata origin,
    bytes32 guid,
    bytes calldata message,
    address executor,
    bytes calldata extraData
) external payable;

function allowInitializePath(Origin calldata origin)
    external view returns (bool);

function nextNonce(uint32 eid, bytes32 sender)
    external pure returns (uint64);

Only the configured endpoint can call lzReceive. It accepts messages only from an active trusted peer, rejects duplicate GUIDs, validates the source chain and deterministic read ID, and marks the GUID consumed after result storage and reward accounting.

Fees and recovery

function setProtocolFeeReceiver(address payable newReceiver) external onlyOwner;
function setFixedProtocolFee(uint256 newFee) external onlyOwner;
function withdrawProtocolFees(uint256 amount) external nonReentrant;
function recoverNativeSurplus(address payable recipient, uint256 amount) external onlyOwner nonReentrant;
function recoverERC20(address token, address recipient, uint256 amount) external onlyOwner nonReentrant;

The owner cannot set fixedProtocolFee above immutable maxProtocolFee. Protocol fees remain reserved from native surplus recovery. recoverERC20 cannot withdraw OMNI backing reservedOmni; accidental surplus above that liability remains recoverable.

withdrawProtocolFees can be called by the owner or current fee receiver, but always sends funds to protocolFeeReceiver.

Public state

ILayerZeroEndpointV2 public immutable endpoint;
uint32 public immutable localEndpointId;
uint256 public immutable localChainId;
uint256 public immutable maxProtocolFee;

address payable public protocolFeeReceiver;
address public omniToken;
uint256 public fixedProtocolFee;
uint256 public collectedProtocolFees;
uint256 public reservedOmni;
bool public inboundPaused;
bool public outboundPaused;

mapping(uint32 => bytes32) public peers;
mapping(uint32 => uint64) public peerSetAt;
mapping(uint32 => uint256) public chainIdForEid;
mapping(uint256 => uint32) public eidForChainId;
mapping(uint32 => bool) public peerEnabled;
mapping(bytes32 => ReadConfig) public readConfigs;
mapping(bytes32 => uint64) public sentSequences;
mapping(bytes32 => bool) public consumedMessages;
mapping(bytes32 => RewardLock) public rewardLocks;
mapping(address => uint256) public claimableOmni;

Events

EventMeaning
PeerSetImmutable peer/chain identity initialized
PeerEnabledChangedRoute enabled or disabled
LayerZeroDelegateSetEndpoint delegate changed
PauseStateChangedInbound/outbound pause state changed
ProtocolFeeChangedFlat read fee changed
ProtocolFeeReceiverChangedFee withdrawal recipient changed
ProtocolFeesWithdrawnAccounted native protocol fees withdrawn
NativeSurplusRecoveredUnaccounted native balance recovered
ERC20RecoveredAccidental ERC-20 surplus recovered
OmniTokenSetOne-time OMNI address configured
ReadConfiguredExact source target/calldata path configured
ReadSentSource result submitted to one destination
ReadReceivedResult stored on the destination
StaleReadIgnoredOlder path sequence received without overwriting state
ReadRequestedLocal OMNI reward locked for an exact incoming path
ReadRequestCancelledRequester recovered an unanswered reward after one hour
ReadRewardAwardedMatching incoming result credited its source sender
OmniRewardClaimedCredited OMNI transferred to the chosen recipient

Errors

ErrorThrown when
InboundIsPaused / OutboundIsPausedThe corresponding message direction is stopped
InvalidAddressA required address or reward recipient is zero or unauthorized
InvalidChainIdChain/EID mapping is zero, local, duplicate, or inconsistent
InvalidEndpointCaller or destination EID is invalid
InvalidFeeNative payment/accounting amount is insufficient or invalid
InvalidPayloadMessage identity or required metadata is malformed
InvalidPeer / PeerAlreadySet / PeerIsDisabledPeer identity, activation, or enabled state is invalid
InvalidReadTarget or calldata fails read-definition bounds
InvalidRewardOMNI is unset, amount/state is invalid, or no claim exists
MessageAlreadyConsumedA LayerZero GUID is delivered twice
MessageTooLargeComplete payload exceeds the message bound
NativeFeeEndpointUnsupportedEndpoint requires an ERC-20 LayerZero fee token
ProtocolFeeTooHighProposed flat fee exceeds the immutable cap
ReadCallFailedSource staticcall reverted or ran out of configured gas
ReadIsDisabled / ReadNotConfiguredExact source path is unavailable
ReadResultEmpty / ReadResultTooLargeStaticcall return data violates bounds
RequestNotFound / RequestNotCancelable / RequesterOnlyRequest cancellation is unknown, early, or unauthorized
RefundFailedNative refund, withdrawal, or recovery transfer failed
ResultFromFuture / ResultNotFound / ResultStaleStored observation fails time or existence checks
ResultWordOutOfBoundsRequested 32-byte ABI word is not present
SurplusExceededRecovery would consume accounted protocol fees or OMNI liabilities
TokenAlreadySetAttempt to replace the configured OMNI token

See Relay a read with Omnilink for the end-to-end operator flow.

Experimental Beta is Live-Learn more about the Pilot