Protocol Overview
To send a cross-chain message, a user must write a transaction on both the source and destination blockchains.
A successfully deployed Omnichain Application (OApp) communicates with the LayerZero Endpoint contract to seamlessly send messages via the protocol from a source to destination chain.
The OApp Contract Standard abstracts away the core Endpoint interfaces, allowing you to focus on building your application's implementation and features without the need to implement functions at the protocol level.
How It Works
A user calls the
_lzSend
method inside the OApp passing an arbitrary message, a destination LayerZero Endpoint, the destination OApp address, and other protocol handling options. This call invokes the Endpoint'ssend
method on the same chain./// @dev MESSAGING STEP 1 - OApp need to transfer the fees to the endpoint before sending the message
/// @param _params the messaging parameters
/// @param _refundAddress the address to refund both the native and lzToken
function send(
MessagingParams calldata _params,
address _refundAddress
) external payable sendContext(_params.dstEid, msg.sender) returns (MessagingReceipt memory) {
if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LzTokenUnavailable();
// send message
(MessagingReceipt memory receipt, address sendLibrary) = _send(msg.sender, _params);
// OApp can simulate with 0 native value it will fail with error including the required fee, which can be provided in the actual call
// this trick can be used to avoid the need to write the quote() function
// however, without the quote view function it will be hard to compose an oapp on chain
uint256 suppliedNative = _suppliedNative();
uint256 suppliedLzToken = _suppliedLzToken(_params.payInLzToken);
_assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken);
// handle native fees
_payNative(receipt.fee.nativeFee, suppliedNative, sendLibrary, _refundAddress);proto
// handle lz token fees
_payToken(lzToken, receipt.fee.lzTokenFee, suppliedLzToken, sendLibrary, _refundAddress);
return receipt;
}The Endpoint uses an on-chain Message Library (MessageLib) configured by the OApp owner to determine how to generate the Message Packet. The MessageLib ensures that the correct OApp Configuration specified by the contract owners will be used by the protocol.
After generating the packet using the correct configuration, the Endpoint emits the message packet as an event containing information about the target blockchain, the receiving address, message handling instructions, and the message itself.
The Security Stack (as configured by the OApp) listens for the event emitted, awaits a specified number of block confirmations, and then verifies the
payloadHash
of the packet in the destination chain's MessageLib. ThispayloadHash
confirms that the contents of the Message Packet on the destination blockchain matches the packet emitted on source.After the configured threshold of DVNs verify the
payloadHash
on the target chain, the message's nonce can then be committed to the Endpoint's messaging channel by any caller (e.g., Executor) for execution.Finally, a caller (e.g., Executor) invokes the
lzReceive
function in the destination Endpoint contract, triggering message execution./// @dev MESSAGING STEP 3 - the last step
/// @dev execute a verified message to the designated receiver
/// @dev the execution provides the execution context (caller, extraData) to the receiver. the receiver can optionally assert the caller and validate the untrusted extraData
/// @dev cant reentrant because the payload is cleared before execution
/// @param _origin the origin of the message
/// @param _receiver the receiver of the message
/// @param _guid the guid of the message
/// @param _message the message
/// @param _extraData the extra data provided by the executor. this data is untrusted and should be validated.
function lzReceive(
Origin calldata _origin,
address _receiver,
bytes32 _guid,
bytes calldata _message,
bytes calldata _extraData
) external payable {
// clear the payload first to prevent reentrancy, and then execute the message
_clearPayload(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, abi.encodePacked(_guid, _message));
ILayerZeroReceiver(_receiver).lzReceive{ value: msg.value }(_origin, _guid, _message, msg.sender, _extraData);
emit PacketDelivered(_origin, _receiver);
}- The destination Endpoint delivers this packet to the destination OApp contract and triggers arbitrary logic via the OApp's defined
_lzReceive
function.
- The destination Endpoint delivers this packet to the destination OApp contract and triggers arbitrary logic via the OApp's defined
See this flow in action by following the Getting Started guide!
Security Guarantees
Update Isolation: Protocol updates are always optional MessageLibs are immutable and cannot be disabled by the Endpoint. LayerZero can deploy new MessageLibs for security and performance optimization (e.g. more efficient proof technologies) without impacting existing applications.
Configuration Ownership: Only the OApp owner can change their application's Security Configuration. Contract owners have the final say on how the protocol transmits and verifies their OApp's messages.
Protocol Immutability: All core contracts are immutable, preventing smart contract upgrades from introducing vulnerabilities.