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, IOmnilinkConstructor
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
| Constant | Value | Purpose |
|---|---|---|
PAYLOAD_VERSION | 2 | Separates v2 read IDs and payloads from older versions |
ENDPOINT_ACTIVATION_TIME | 7 days | Delay for a peer that is not the same address as the local Omnilink |
REQUEST_CANCEL_DELAY | 1 hours | Earliest cancellation time for an unanswered OMNI reward |
MAX_CLOCK_SKEW | 5 minutes | Maximum accepted future source timestamp |
MIN_READ_GAS | 10,000 | Minimum configured source staticcall gas |
MAX_READ_GAS | 5,000,000 | Maximum configured source staticcall gas |
MAX_CALLDATA_BYTES | 4,096 | Maximum registered call data |
MAX_RESULT_BYTES | 8,192 | Maximum returned ABI data |
MAX_MESSAGE_BYTES | 9,216 | Maximum 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
| Event | Meaning |
|---|---|
PeerSet | Immutable peer/chain identity initialized |
PeerEnabledChanged | Route enabled or disabled |
LayerZeroDelegateSet | Endpoint delegate changed |
PauseStateChanged | Inbound/outbound pause state changed |
ProtocolFeeChanged | Flat read fee changed |
ProtocolFeeReceiverChanged | Fee withdrawal recipient changed |
ProtocolFeesWithdrawn | Accounted native protocol fees withdrawn |
NativeSurplusRecovered | Unaccounted native balance recovered |
ERC20Recovered | Accidental ERC-20 surplus recovered |
OmniTokenSet | One-time OMNI address configured |
ReadConfigured | Exact source target/calldata path configured |
ReadSent | Source result submitted to one destination |
ReadReceived | Result stored on the destination |
StaleReadIgnored | Older path sequence received without overwriting state |
ReadRequested | Local OMNI reward locked for an exact incoming path |
ReadRequestCancelled | Requester recovered an unanswered reward after one hour |
ReadRewardAwarded | Matching incoming result credited its source sender |
OmniRewardClaimed | Credited OMNI transferred to the chosen recipient |
Errors
| Error | Thrown when |
|---|---|
InboundIsPaused / OutboundIsPaused | The corresponding message direction is stopped |
InvalidAddress | A required address or reward recipient is zero or unauthorized |
InvalidChainId | Chain/EID mapping is zero, local, duplicate, or inconsistent |
InvalidEndpoint | Caller or destination EID is invalid |
InvalidFee | Native payment/accounting amount is insufficient or invalid |
InvalidPayload | Message identity or required metadata is malformed |
InvalidPeer / PeerAlreadySet / PeerIsDisabled | Peer identity, activation, or enabled state is invalid |
InvalidRead | Target or calldata fails read-definition bounds |
InvalidReward | OMNI is unset, amount/state is invalid, or no claim exists |
MessageAlreadyConsumed | A LayerZero GUID is delivered twice |
MessageTooLarge | Complete payload exceeds the message bound |
NativeFeeEndpointUnsupported | Endpoint requires an ERC-20 LayerZero fee token |
ProtocolFeeTooHigh | Proposed flat fee exceeds the immutable cap |
ReadCallFailed | Source staticcall reverted or ran out of configured gas |
ReadIsDisabled / ReadNotConfigured | Exact source path is unavailable |
ReadResultEmpty / ReadResultTooLarge | Staticcall return data violates bounds |
RequestNotFound / RequestNotCancelable / RequesterOnly | Request cancellation is unknown, early, or unauthorized |
RefundFailed | Native refund, withdrawal, or recovery transfer failed |
ResultFromFuture / ResultNotFound / ResultStale | Stored observation fails time or existence checks |
ResultWordOutOfBounds | Requested 32-byte ABI word is not present |
SurplusExceeded | Recovery would consume accounted protocol fees or OMNI liabilities |
TokenAlreadySet | Attempt to replace the configured OMNI token |
See Relay a read with Omnilink for the end-to-end operator flow.