Skip to main content

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.

Protocol V2 Light Protocol V2 Dark

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

  1. 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's send 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.

  2. 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. This payloadHash 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.

  3. 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.

info

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.