Skip to main content
Version: Endpoint V2

LayerZero Protocol Architecture

LayerZero is an omnichain interoperability protocol that provides a stable, immutable interface for cross-chain messaging.

By separating interface, verification, and execution into independent layers, LayerZero enables composable cross-chain architectures without compromising security.

Architecture: Five Independent Layers

Loading diagram...

Each layer has one job. They work together but remain independent.

1. Business Logic Interface (OApp)

Omnichain Applications (OApps) are LayerZero's definition for smart contracts that use LayerZero to send and receive cross-chain messages.

Building on Module 1's data messaging concepts, the LayerZero protocol allows applications to define any custom data as bytes and send them as cross-chain messages. The OApp standard provides a consistent application interface for invoking the LayerZero protocol with this custom business logic:

// SPDX-License-Identifier: MIT
import {OApp, MessagingFee, Origin} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";
import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";

contract MyOmnichainApp is OApp {
using OptionsBuilder for bytes;

constructor(address endpoint) OApp(endpoint, msg.sender) {}

function quoteMessage(uint32 dstEid, bytes memory message, bool payInLzToken)
external view returns (MessagingFee memory fee) {
bytes memory options = OptionsBuilder.newOptions()
.addExecutorLzReceiveOption(200_000, 0);

// Returns the cost of sending a message in either native gas token or ZRO
return _quote(dstEid, message, options, payInLzToken);
}

function sendMessage(uint32 dstEid, bytes memory message) external payable {
// Unordered by default; requests destination chain gas units for message delivery
bytes memory options = OptionsBuilder.newOptions()
.addExecutorLzReceiveOption(200_000, 0);

// If you need ordering:
// options = options.addExecutorOrderedExecutionOption();
_lzSend(
dstEid,
message,
combineOptions(dstEid, SEND, options),
MessagingFee(msg.value, 0),
payable(msg.sender)
);
}

function _lzReceive(
Origin calldata origin,
bytes32 guid,
bytes calldata message,
address /*executor*/,
bytes calldata /*extraData*/
) internal override {
// your logic
}
}

The OApp standard acts as a facade that wraps the raw LayerZero protocol interface with developer-friendly methods. Instead of calling endpoint.send() and endpoint.lzReceive() directly, OApps use _lzSend() and _lzReceive() which handle common patterns like fee quoting, message validation, and error handling.

Your application code focuses on business logic (encoding/decoding custom data, state transitions, etc.) while the OApp facade manages protocol interaction.

2. Protocol Interface (Endpoint)

The LayerZero Endpoint serves as the single entrypoint and exitpoint for all cross-chain messaging on a blockchain. Each chain has one LayerZero Endpoint contract that can send and receive messages between any other LayerZero Endpoint contract on any supported chain.

The Endpoint provides the universal, immutable protocol interface:

// Excerpt of ILayerZeroEndpointV2 - core messaging functions

// Core data structures
struct MessagingParams {
uint32 dstEid; // Destination endpoint ID
bytes32 receiver; // Receiver address (bytes32 for cross-chain compatibility)
bytes message; // Message containing application-specific business logic
bytes options; // Execution options (gas, native drops, etc.)
bool payInLzToken; // Payment method (native chain token vs LZ token)
}

struct MessagingReceipt {
bytes32 guid; // Globally unique identifier for tracking
uint64 nonce; // Message sequence number for ordering
MessagingFee fee; // Actual fee charged
}

struct MessagingFee {
uint256 nativeFee; // Fee in native gas token
uint256 lzTokenFee; // Fee in LZ token (alternative payment)
}

struct Origin {
uint32 srcEid; // Source endpoint ID
bytes32 sender; // Sender address (bytes32 for cross-chain)
uint64 nonce; // Message nonce for ordering
}

interface ILayerZeroEndpointV2 {
// Events for tracking message lifecycle
event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);
event PacketVerified(Origin origin, address receiver, bytes32 payloadHash);
event PacketDelivered(Origin origin, address receiver);

// Quote fees before sending
function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);

// Core send primitive - emits message and returns receipt
function send(
MessagingParams calldata _params,
address _refundAddress
) external payable returns (MessagingReceipt memory);

// Mark message as verified
function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;

// Check if message can be verified
function verifiable(Origin calldata _origin, address _receiver) external view returns (bool);

// Check if message passes the target receiver's checks
function initializable(Origin calldata _origin, address _receiver) external view returns (bool);

// Core receive primitive - delivers message to destination contract
function lzReceive(
Origin calldata _origin,
address _receiver,
bytes32 _guid,
bytes calldata _message,
bytes calldata _extraData
) external payable;

// ... additional configuration and management functions
}

Because every LayerZero Endpoint is immutable and permissionless to interact with, anyone can use a LayerZero Endpoint for cross-chain messaging without requiring authorization, integration approval, or dependency on external bridge operators. This creates a fully independent transport layer that, as we'll expand on in later sections, serves as the ideal cross-chain messaging interface for nearly all cross-chain applications.

The immutable nature ensures that the interface will never change, providing permanent compatibility for applications built on LayerZero. The permissionless design means no gatekeepers can restrict access or censor transactions, creating a truly open cross-chain infrastructure.

Key Properties:

  • Universal: Same interface on every supported chain
  • Immutable: Cannot be upgraded or changed
  • Permissionless: Anyone can call (with proper fees)
  • Chain-agnostic: Works across EVM, Solana, Aptos, etc. (see non-EVM sections for specific sister implementations)

Channels: OApp-to-OApp Communication

A channel in LayerZero is uniquely defined by four components:

  • Sender OApp: The contract initiating the message
  • Source Endpoint: The LayerZero endpoint on the source chain
  • Destination Endpoint: The LayerZero endpoint on the destination chain
  • Receiver OApp: The contract receiving the message

Each unique combination creates an independent channel with its own configuration:

Loading diagram...

Note: Channel 3 shows the pull messaging pattern where data is queried directly from target contracts without involving the destination chain's LayerZero endpoint. We'll expand on this lzRead functionality in future modules.


Channel Identification

In practice, each channel can be identified from a source chain using only the destination Endpoint ID (EID) and receiver contract address. The source EID is already known (stored in the local Endpoint) and the caller is the OApp making the request, simplifying pathway management:

// Standard EIDs for endpoint-to-endpoint channels
uint32 constant ETHEREUM_EID = 30101; // Ethereum mainnet endpoint
uint32 constant ARBITRUM_EID = 30110; // Arbitrum mainnet endpoint
uint32 constant POLYGON_EID = 30109; // Polygon mainnet endpoint

// Channel identification from source chain:
// - Source EID: Known (local endpoint)
// - Sender OApp: Known (msg.sender)
// - Destination EID: Provided as argument
// - Receiver OApp: Provided as argument

// Example: From Ethereum, only need (30110, PeerOApp) to identify Ethereum → Arbitrum channel

Custom Channel IDs: For lzRead pull-messaging, the sender and receiver are the same OApp (request/response pattern). Since we need to distinguish read requests from standard push messages to the same contract, we use arbitrary channel IDs:

// Custom channel ID for lzRead workflows
uint32 constant CUSTOM_READ_CHANNEL = 4294967295; // Arbitrary identifier

// lzRead pattern:
// - Sender OApp: The contract requesting data (e.g., on Ethereum)
// - Receiver OApp: Same contract receiving the response (same Ethereum contract)
// - Custom EID: Distinguishes this as a read request, not a push message
// - Target: The contract being queried (e.g., on Arbitrum)

Channel-Specific Nonce Tracking

Each channel maintains its own independent nonce sequence, enabling parallel messaging without ordering conflicts:

Benefits of Per-Channel Nonces:

  • Parallel messaging: No ordering dependencies between different channels
  • Independent scaling: High-traffic channels don't block low-traffic channels
  • Replay protection: Each channel has its own sequence for security
  • Channel isolation: Message ordering only matters within the same communication pathway
Loading diagram...

Message Packet Generation Per Channel

When applications send messages, the Endpoint wraps the raw message data in a standardized packet container with channel-specific metadata. Based on the EndpointV2 implementation:

// Endpoint constructs packet with unique identifiers per channel
Packet memory packet = Packet({
nonce: latestNonce, // Sequential message number per channel
srcEid: eid, // Source endpoint ID (this chain)
sender: _sender, // Sender contract address (the OApp)
dstEid: _params.dstEid, // Destination endpoint ID
receiver: _params.receiver, // Receiver contract address
guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver), // Globally unique ID
message: _params.message // Raw application data (bytes)
});

Key Properties:

  • Nonce: Sequential per channel for ordering and replay protection - each channel maintains its own nonce sequence
  • GUID: Globally unique identifier generated from channel components + nonce
  • Channel identification: (sender, srcEid, dstEid, receiver) uniquely identifies the communication pathway
  • Message isolation: Raw application data separated from protocol metadata

Managing Channel Configuration

The Endpoint provides the interface for managing channel configurations via modular Message Libraries:

struct SetConfigParam {
uint32 eid; // Target endpoint ID
uint32 configType; // Configuration type identifier
bytes config; // Encoded configuration data
}

interface IMessageLibManager {
// Library selection per pathway
function setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;
function setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;

// Library configuration per pathway
function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;
function getConfig(address _oapp, address _lib, uint32 _eid, uint32 _configType)
external view returns (bytes memory config);
}

Each OApp can configure libraries to define the messaging behavior for its specific channels. These libraries determine how messages are verified, executed, and delivered for that OApp's communication needs.

3. Configurable Protocol Libraries (Message Libraries)

Message Libraries are on-chain rulesets that handle how messages are sent off-chain and arrive on-chain between Endpoints. They define the complete workflow: message processing, verification requirements, and delivery coordination while maintaining the universal Endpoint interface.

// Excerpt of IMessageLib, ISendLib - core library functions
interface IMessageLib {
// Configure verification and execution rules per pathway
function setConfig(address _oapp, SetConfigParam[] calldata _config) external;

// Send path: Transform endpoint calls into off-chain instructions
function send(
Packet calldata _packet,
bytes calldata _options
) external returns (MessagingFee memory, bytes memory);

// Receive path: Verifier networks call this to mark messages verified
function verify(
Origin calldata _origin,
address _receiver,
bytes32 _payloadHash
) external;

// Check compatibility and versioning
function version() external view returns (uint64 major, uint8 minor, uint8 endpointVersion);
}

struct Packet {
uint64 nonce; // Message sequence number
uint32 srcEid; // Source endpoint ID
address sender; // Sender contract address
uint32 dstEid; // Destination endpoint ID
bytes32 receiver; // Receiver address (bytes32 for cross-chain)
bytes32 guid; // Globally unique identifier
bytes message; // Message business logic
}

Message Library Types & Workflows

Message Libraries can define different workflows and have specialized roles, such as Sending, Receiving, or Reading cross-chain state:

Push Messaging Workflow (Send/Receive ULN)

Loading diagram...

Pull Messaging Workflow (Request/Response lzRead)

Loading diagram...

Different libraries enable OApps to handle different channel types with distinct behaviour, security, and delivery characteristics. Each channel represents a unique communication pathway between specific OApps with configurable message processing rules.

LayerZero Labs may deploy new libraries with different workflows, features, or versions to enable developers to opt into new changes per channel.

Channel-Specific Library Configuration

Each channel gets unique settings through a two-step process from the Endpoint:

  1. Library Selection: Use setSendLibrary/setReceiveLibrary to assign specific libraries per channel
  2. Library Configuration: Use setConfig to customize each library's settings per channel

This creates truly independent channel configurations - the same library type (e.g., SendUln302) can have completely different verification, finality, and execution settings for different channels.

Each unique combination creates an independent channel setting with its own library configuration:

Loading diagram...

What Can Be Configured Per Channel

Message Libraries enable three main types of configuration for each channel:

  • Finality: How many block confirmations are required before verification begins
  • Verification: Which verifier networks must verify messages and in what combinations
  • Execution: Which execution services deliver messages and with what parameters

X-of-Y-of-N Verification Coordination

LayerZero libraries today use an X-of-Y-of-N configuration pattern for verification, where:

  • X: Required verifier networks that MUST always verify (non-fungible)
  • Y: Total verifications needed (required + threshold of optional)
  • N: Total pool of available verifier networks

Example: 2-of-4-of-6 configuration

  • 2 specific networks must always verify (X = required)
  • 4 total verifications needed (Y = required + optional threshold)
  • 6 networks available in the pool (N = total options)
  • Any 2 of the remaining 4 optional networks can provide the additional verifications

Each "verifier network" is an independent verification system that implements one of the verification approaches from Module 1 (ZK proofs, committee consensus, light clients, etc.). This creates a quorum of quorums - each network must reach its own internal verification criteria before contributing to the overall X-of-Y-of-N requirement.

Example: 2-of-2-of-2 Verifier Networks

Loading diagram...

info

This simple 2-of-2-of-2 configuration shows two required verifier networks with no optional networks.

1-of-2-of-3 Verifier Networks

Loading diagram...

info

This 1-of-2-of-3 configuration shows 1 required verifier network (solid border) and 2 optional verifier networks (dashed borders). Only 1 of the 2 optional networks needs to verify, providing flexibility while maintaining the required verification.

Configuration Per Channel

Message Libraries enable each channel to specify different X-of-Y-of-N configurations based on security requirements:

Loading diagram...

Each channel's Message Library enforces these X-of-Y-of-N requirements on-chain, ensuring that the specified verification pattern is met before message execution.

Configuration Structures

Message Libraries define specific configuration structures for the three configurable aspects:

info

Note: The configuration structures reference "DVNs" (Decentralized Verifier Networks) and "Executors". You don't need to understand their implementation details yet - just know that DVNs help determine which verifier networks to select, and Executors handle message delivery. Both will be explained in detail in Module 4: Verification & Execution Services.

Push Messaging Configuration (ULN)

struct UlnConfig {
uint64 confirmations; // Block confirmations required for finality
uint8 requiredDVNCount; // Number of required DVNs (0 = DEFAULT, NIL_DVN_COUNT = NONE)
uint8 optionalDVNCount; // Number of optional DVNs available
uint8 optionalDVNThreshold; // How many optional DVNs needed: (0, optionalDVNCount]
address[] requiredDVNs; // Required DVN addresses (sorted, no duplicates)
address[] optionalDVNs; // Optional DVN addresses (sorted, no duplicates)
}

struct ExecutorConfig {
uint32 maxMessageSize; // Maximum message size in bytes
address executor; // Executor contract address
}

Pull Messaging Configuration (lzRead)

struct ReadLibConfig {
address executor; // Executor for read operations
uint8 requiredDVNCount; // Number of required DVNs
uint8 optionalDVNCount; // Number of optional DVNs available
uint8 optionalDVNThreshold; // How many optional DVNs needed
address[] requiredDVNs; // Required DVN addresses (sorted, no duplicates)
address[] optionalDVNs; // Optional DVN addresses (sorted, no duplicates)
}

Key Insight: Message Libraries are the on-chain glue that makes verifier networks and delivery services pluggable while maintaining security guarantees. Different libraries can implement completely different messaging paradigms and verification requirements.

Cross-Chain Services (Workers)

Message Libraries coordinate two types of off-chain worker services that handle verification and execution:

4. DVNs (Decentralized Verifier Networks)

DVNs are LayerZero's official term for the "verifier networks" discussed in previous sections. Each DVN is an independent verification service that implements one of the verification approaches from Module 1 (ZK proofs, committee consensus, light clients, middlechains, etc.).

Key Properties:

  • Independent operation: Each DVN runs its own verification logic
  • Configurable per pathway: Different DVN sets per channel
  • X-of-Y-of-N coordination: Multiple DVNs work together via Message Library rules
  • Verification diversity: Different DVNs use different trust models and verification methods

5. Executors

Executors are permissionless services that deliver verified messages to destination chains. Anyone can run an executor, making message delivery competitive and censorship-resistant.

Key Properties:

  • Permissionless: Anyone can operate an executor
  • Competitive: Multiple executors compete on speed and cost
  • Optional: Applications can opt out and execute manually
  • Separation from verification: Executors deliver, DVNs verify

Worker Service Architecture

// Message Libraries coordinate both worker types:
// 1. DVNs verify messages according to X-of-Y-of-N rules
// 2. Executors deliver verified messages to destination contracts

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2
interface IReceiveUlnE2 {
/// @notice for each dvn to verify the payload
/// @dev this function signature 0x0223536e
function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;

/// @notice verify the payload at endpoint, will check if all DVNs verified
function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external;
}

interface ILayerZeroEndpointV2 {
function lzReceive(
Origin calldata _origin,
address _receiver,
bytes32 _guid,
bytes calldata _message,
bytes calldata _extraData
) external payable;
}

Coordination: Message Libraries define the rules, DVNs verify according to X-of-Y-of-N configurations, and Executors deliver once verification requirements are met. This separation enables independent scaling and competitive markets for both verification and execution services.

For detailed implementation, configuration examples, and provider information, see Module 4: Verification & Execution Services.

Exit Criteria

Before proceeding to Module 4, you should be able to:

  1. Describe the four layers (application, endpoint, libraries, workers)
  2. Explain what DVNs do and how to configure them
  3. Show how to set different verification per route

See Also