# Table of Contents
- [LayerZero Documentation | LayerZero](#layerzero-documentation-layerzero)
- [What is LayerZero? | LayerZero](#what-is-layerzero-layerzero)
- [LayerZero V2 Solana Programs (BETA) | LayerZero](#layerzero-v2-solana-programs-beta-layerzero)
- [LayerZero V2 Aptos Move Standards | LayerZero](#layerzero-v2-aptos-move-standards-layerzero)
- [Read External State (LayerZero Read) | LayerZero](#read-external-state-layerzero-read-layerzero)
- [LayerZero V2 Solidity Contract Standards | LayerZero](#layerzero-v2-solidity-contract-standards-layerzero)
- [Deployed Endpoints, Message Libraries, and Executors | LayerZero](#deployed-endpoints-message-libraries-and-executors-layerzero)
- [Tools Overview | LayerZero](#tools-overview-layerzero)
- [Community Support | LayerZero](#community-support-layerzero)
- [Introduction to LayerZero V1 | LayerZero](#introduction-to-layerzero-v1-layerzero)
- [Solana Endpoint V1 Support | LayerZero](#solana-endpoint-v1-support-layerzero)
- [LayerZero Glossary | LayerZero](#layerzero-glossary-layerzero)
- [Protocol Overview | LayerZero](#protocol-overview-layerzero)
- [Aptos Overview | LayerZero](#aptos-overview-layerzero)
- [Getting Started with LayerZero V2 on Solana | LayerZero](#getting-started-with-layerzero-v2-on-solana-layerzero)
- [LayerZero Endpoint | LayerZero](#layerzero-endpoint-layerzero)
- [What is LayerZero V2? | LayerZero](#what-is-layerzero-v2-layerzero)
- [Quickstart - Create Your First Omnichain App | LayerZero](#quickstart-create-your-first-omnichain-app-layerzero)
- [Getting Started with Contract Standards | LayerZero](#getting-started-with-contract-standards-layerzero)
- [Sending Tokenized Assets | LayerZero](#sending-tokenized-assets-layerzero)
- [LayerZero V2 EVM Technical Overview | LayerZero](#layerzero-v2-evm-technical-overview-layerzero)
- [Project Configuration | LayerZero](#project-configuration-layerzero)
- [Testing Contracts | LayerZero](#testing-contracts-layerzero)
- [Deploying Contracts | LayerZero](#deploying-contracts-layerzero)
- [Configuring Contracts | LayerZero](#configuring-contracts-layerzero)
- [Debugging LayerZero Errors | LayerZero](#debugging-layerzero-errors-layerzero)
- [LayerZero V2 OApp Quickstart | LayerZero](#layerzero-v2-oapp-quickstart-layerzero)
- [LayerZero V2 ONFT Quickstart | LayerZero](#layerzero-v2-onft-quickstart-layerzero)
- [LayerZero V2 OFT Quickstart | LayerZero](#layerzero-v2-oft-quickstart-layerzero)
- [Omnichain Composability | LayerZero](#omnichain-composability-layerzero)
- [Design Patterns & Extensions | LayerZero](#design-patterns-extensions-layerzero)
- [OFT Patterns and Extensions | LayerZero](#oft-patterns-and-extensions-layerzero)
- [Best Practices for Contract Ownership | LayerZero](#best-practices-for-contract-ownership-layerzero)
- [Read CLI Setup Guide | LayerZero](#read-cli-setup-guide-layerzero)
- [Transaction Pricing | LayerZero](#transaction-pricing-layerzero)
- [Supported Data Types | LayerZero](#supported-data-types-layerzero)
- [LayerZero EVM Chain Compatibility | LayerZero](#layerzero-evm-chain-compatibility-layerzero)
- [LayerZero V2 Integration Checklist | LayerZero](#layerzero-v2-integration-checklist-layerzero)
- [LayerZero Experimental Simple Config Generator | LayerZero](#layerzero-experimental-simple-config-generator-layerzero)
---
# LayerZero Documentation | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
**LayerZero** is an **omnichain interoperability protocol** that enables seamless communication between different blockchains. It allows developers to build omnichain applications (OApps) that can interact across multiple chains as if they were on a single chain.
All **core protocol contracts** are **immutable** and **non-upgradeable**, ensuring you never face unplanned changes from the protocol. Application contract owners have the flexibility to define, customize, and lock configurations, allowing them to optimize security and cost according to their specific needs.

Why LayerZero?[](#why-layerzero "Direct link to Why LayerZero?")
------------------------------------------------------------------
Before LayerZero, developers had to choose a single blockchain for their applications, limiting their reach and functionality. Existing cross-chain solutions frequently rely on centralized intermediaries or fragmented bridges, introducing security risks, a poor user experience, and fragmented liquidity. LayerZero solves these problems by providing a **secure, efficient, and user-friendly** way to build truly omnichain applications.
With LayerZero, you can:
* **Reach more users**: Deploy your dApp on any supported chain, interacting with users and assets across all other supported chains.
* **Unify liquidity**: Avoid breaking up liquidity across multiple blockchains, allowing your dApp to harness a shared liquidity pool.
* **Simplify development**: Build as if you're deploying on a single chain, using familiar tools and programming languages.
* **Enhance security**: Benefit from a decentralized, independently configurable security model (Decentralized Verifier Networks, or DVNs).
* **Improve user experience**: Offer seamless cross-chain interactions without requiring users to understand underlying complexities.
### Use Cases[](#use-cases "Direct link to Use Cases")
LayerZero unlocks a wide range of possibilities for decentralized applications, including:
* **Omnichain tokens**: Create and manage tokens that are available on multiple chains with a unified supply.
* **Cross-chain lending and borrowing**: Lend on one chain and borrow on another, all within a single transaction flow.
* **Unified liquidity DEXes**: Aggregate liquidity from multiple chains to provide deeper liquidity and better prices.
* **Omnichain NFTs**: Mint, transfer, and manage NFTs across different chains while preserving ownership records.
* **Cross-chain governance**: Allow governance protocols to operate across multiple chains for broader participation.
* **Cross-chain gaming**: Build games that leverage assets and functionalities from different chains, enriching gameplay experiences.
* **Cross-chain data queries**: Securely read real-time state from multiple networks, such as price feeds or contract balances, to trigger onchain operations like rebalancing liquidity pools, adjusting lending parameters or executing conditional logic across chains.
Quick Start Guide[](#quick-start-guide "Direct link to Quick Start Guide")
----------------------------------------------------------------------------
Ready to start building with LayerZero? Here’s your roadmap:
### 1\. Choose Your Environment[](#1-choose-your-environment "Direct link to 1. Choose Your Environment")
[### Solidity (EVM)\
\
Resources to help you quickly build, launch, and scale your EVM omnichain applications](/v2/developers/evm/overview)
[### Solana\
\
Resources to build your LayerZero applications on the Solana blockchain (Beta)](/v2/developers/solana/overview)
[### Aptos Move\
\
Resources to build your LayerZero applications on Aptos Move blockchains (Beta)](/v2/developers/aptos-move/overview)
### 2\. Build Your First OApp[](#2-build-your-first-oapp "Direct link to 2. Build Your First OApp")
[### OApp Quickstart\
\
A step-by-step tutorial on creating and deploying a simple OApp with create-lz-oapp](/v2/developers/evm/create-lz-oapp/start)
### 3\. Understand Core Concepts & Architecture[](#3-understand-core-concepts--architecture "Direct link to 3. Understand Core Concepts & Architecture")
* [**What is LayerZero?**](/v2/concepts/v2-overview)
: Understand what LayerZero fundamentally solves and why it exists.
* [**Protocol Overview**](/v2/concepts/protocol/protocol-overview)
: Understand how messages flow between chains.
* [**Security Stack (DVNs)**](/v2/concepts/modular-security/security-stack-dvns)
: Learn about decentralized verifier networks that validate cross-chain messages.
* [**Executors**](/v2/concepts/permissionless-execution/executors)
: Discover how messages are delivered and executed on destination chains.
### 4\. Dive Deeper[](#4-dive-deeper "Direct link to 4. Dive Deeper")
* **[OApp Standard](/v2/developers/evm/oapp/overview)
** for generic message passing.
* **[OFTs](/v2/developers/evm/oft/quickstart)
** & **[ONFTs](/v2/developers/evm/onft/quickstart)
** for omnichain ERC20 / ERC721.
* **[Advanced Patterns](/v2/developers/evm/oapp/message-design-patterns)
** for composability and order control.
* **[Security & Executor Config](/v2/developers/evm/configuration/dvn-executor-config)
** to lock in your DVNs and message libraries.
* **[Gas Fees & Options](/v2/developers/evm/configuration/options)
** for cross-chain transaction execution settings.
### 5\. LayerZero Read (Optional)[](#5-layerzero-read-optional "Direct link to 5. LayerZero Read (Optional)")
* **[LayerZero Read](/v2/developers/evm/lzread/overview)
**: Learn how to query external on-chain data from another blockchain.
* **[CLI Setup Guide](/v2/developers/evm/lzread/read-cli)
** to try it out.
Key Concepts[](#key-concepts "Direct link to Key Concepts")
-------------------------------------------------------------
Below are common terms you’ll encounter in the docs. A more detailed listing can be found in the [Glossary](/v2/concepts/glossary)
.
* **OApp (Omnichain Application)**: A smart contract that uses LayerZero to send and receive messages across different blockchains.
* **Endpoint**: Immutable smart contracts deployed on each supported chain that serve as the entry and exit points for LayerZero messages.
* **Endpoint ID (EID)**: A unique identifier for each LayerZero Endpoint contract, used for routing messages between chains.
* **Decentralized Verifier Networks (DVNs)**: Independent entities that validate messages between chains for security and integrity.
* **Executor**: An off-chain service that executes messages on the destination chain after verification.
* **Message Library (MessageLib)**: Smart contracts for packing message payloads on the source chain and verifying them on the destination.
* **Security Stack**: The combination of DVNs and other parameters that an application configures for message authenticity.
* **Composed Message**: A cross-chain message that triggers another cross-chain message (nested calls).
Developer Tooling[](#developer-tooling "Direct link to Developer Tooling")
----------------------------------------------------------------------------
LayerZero offers a suite of tools to streamline your development experience:
* **[LayerZero Scan](https://layerzeroscan.com)
**: Comprehensive block explorer for tracking and debugging cross-chain messages.
* **[TestHelper (Foundry)](/v2/developers/evm/tooling/test-helper)
**: Simulate cross-chain transactions in Foundry tests.
* **[Hardhat Toolbox](https://github.com/LayerZero-Labs/devtools/tree/main/packages/toolbox-hardhat)
**: A framework for building, configuring, and deploying OApps locally.
Community and Support[](#community-and-support "Direct link to Community and Support")
----------------------------------------------------------------------------------------
Join our community to connect with other developers, ask questions, and share your projects:
* [Discord](https://layerzero.network/community)
* [Telegram](https://t.me/joinchat/VcqxYkStIDsyN2Rh)
* [Twitter](https://x.com/layerzero_core)
* [GitHub](https://github.com/LayerZero-Labs/)
Start Building[](#start-building "Direct link to Start Building")
-------------------------------------------------------------------
You are now ready to build the next generation of omnichain applications with LayerZero. Dive into the documentation, explore the examples, and join our community to unleash the full potential of cross-chain development!
### Your Feedback Shapes This Documentation[](#your-feedback-shapes-this-documentation "Direct link to Your Feedback Shapes This Documentation")
If anything is unclear or missing, we invite you to [open an issue on GitHub](https://github.com/LayerZero-Labs/)
, ask in [Discord](https://layerzero.network/community)
, or get in touch with the team. Happy building!
* [Why LayerZero?](#why-layerzero)
* [Use Cases](#use-cases)
* [Quick Start Guide](#quick-start-guide)
* [1\. Choose Your Environment](#1-choose-your-environment)
* [2\. Build Your First OApp](#2-build-your-first-oapp)
* [3\. Understand Core Concepts & Architecture](#3-understand-core-concepts--architecture)
* [4\. Dive Deeper](#4-dive-deeper)
* [5\. LayerZero Read (Optional)](#5-layerzero-read-optional)
* [Key Concepts](#key-concepts)
* [Developer Tooling](#developer-tooling)
* [Community and Support](#community-and-support)
* [Start Building](#start-building)
* [Your Feedback Shapes This Documentation](#your-feedback-shapes-this-documentation)
---
# What is LayerZero? | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero is an omnichain messaging protocol — a permissionless, open framework designed to securely move information between blockchains. It empowers any application to bring its own security, execution, and cross-chain interaction, providing a predictable and adaptable foundation for decentralized applications living on multiple networks.
Before LayerZero[](#before-layerzero "Direct link to Before LayerZero")
-------------------------------------------------------------------------

Before LayerZero, cross-chain communication was a patchwork of monolithic bridges and isolated solutions. Achieving true cross-chain communication was a complex and often fragile endeavor.
Traditional methods relied on monolithic bridges with centralized verifiers or a fixed set of signers — approaches that imposed rigid structures and created single points of failure. When any component of these systems faltered, every connected application was put at risk, stifling innovation and leaving developers scrambling for secure solutions.
The LayerZero Framework[](#the-layerzero-framework "Direct link to The LayerZero Framework")
----------------------------------------------------------------------------------------------
LayerZero redefines cross-chain interactions by combining several key architectural elements:
* **Immutable Smart Contracts:**
Non-upgradeable endpoint contracts are deployed on each blockchain. These immutable contracts serve as secure entry and exit points for messages, ensuring consistency and trust across all networks.
* **Configurable Message Libraries:**
LayerZero offers flexible libraries that developers can select to tailor the way messages are emitted off-chain. This adaptability means applications can optimize message formatting and handling according to specific needs without being tied to a one-size-fits-all solution.
* **Modular Security Owned by the Application:**
Instead of relying on a centralized verifier network, LayerZero enables each application to configure its own security stack. Developers can choose from various decentralized verifier networks (DVNs) and set parameters like finality and execution rules. This modular approach shifts control to the application, allowing for tailored security that evolves with emerging technologies.
* **Permissionless Execution:**
By making the execution of cross-chain messages available to anyone, LayerZero ensures that once a message is verified, it can be executed without gatekeepers. This open design removes bottlenecks and facilitates seamless interaction across the blockchain mesh.
Together, these elements create a robust foundation that makes the following primitives possible.
Key Primitives Built into LayerZero[](#key-primitives-built-into-layerzero "Direct link to Key Primitives Built into LayerZero")
----------------------------------------------------------------------------------------------------------------------------------
LayerZero’s architecture provides a robust set of core primitives that redefine cross-chain interaction. Each primitive has its own dedicated deep-dive section in our documentation to help you fully leverage its capabilities:
* **Omnichain Message Passing (Generic Messaging):**
This primitive enables applications to send and receive arbitrary data across a fully-connected mesh of blockchains. Applications can push state transitions to any network in the LayerZero mesh.
_Learn more in our [Omnichain Applications (OApp)](/v2/concepts/applications/oapp-standard)
overview._
* **Omnichain Tokens (OFT & ONFT):** Unified token standards that empower the cross-chain transfer of both fungible and non-fungible tokens. These standards ensure a consistent global supply through mechanisms like burn/mint or lock/unlock—abstracting away the differences across blockchain environments and providing a seamless token experience.
_For additional details, refer to our [Omnichain Tokens (OFT & ONFT)](/v2/concepts/applications/oft-standard)
section._
* **Omnichain State Queries (lzRead):**
Go beyond simple messaging—this primitive allows smart contracts to request and retrieve on-chain state from other blockchains securely. It empowers your applications to “pull” data across chains efficiently.
_Dive deeper into this capability in our [Omnichain Queries (lzRead)](/v2/concepts/applications/read-standard)
section._
* **Omnichain Composability:**
By decoupling security from execution, this design enables developers to build complex, multi-step workflows across chains. It breaks down cross-chain operations into discrete, manageable messages that achieve instant finality, facilitating advanced use cases and improved user experiences.
_For detailed insights, refer to our [Omnichain Composability](/v2/concepts/applications/composer-standard)
documentation._
These primitives provide the building blocks for predictable, secure, and scalable cross-chain interactions within the LayerZero mesh network.
Further Reading[](#further-reading "Direct link to Further Reading")
----------------------------------------------------------------------
To dive deeper into LayerZero and its omnichain capabilities, explore our detailed documentation across three core sections:
* [Protocol Overview](/v2/concepts/protocol/protocol-overview)
: Understand the technical architecture behind LayerZero—from immutable smart contracts and configurable message libraries to the secure transmission of cross-chain messages.
* [Workers Overview](/v2/concepts/workers)
: Learn about the off-chain service providers—Decentralized Verifier Networks (DVNs) and Executors—that play a critical role in verifying and executing cross-chain messages.
* [Omnichain Applications (OApp) Standard](/v2/concepts/applications/oapp-standard)
: Discover how to build applications that leverage LayerZero’s omnichain messaging interface, allowing for generic message passing, dynamic fee estimation, and secure, composable cross-chain interactions.
These sections offer comprehensive guides, best practices, and technical references to help you build secure, scalable, and truly omnichain solutions.
* [Before LayerZero](#before-layerzero)
* [The LayerZero Framework](#the-layerzero-framework)
* [Key Primitives Built into LayerZero](#key-primitives-built-into-layerzero)
* [Further Reading](#further-reading)
---
# LayerZero V2 Solana Programs (BETA) | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero V2 Solana Programs (BETA)
===================================
caution
The Solana OFT, Endpoint, and ULN Programs are currently in **Mainnet Beta**!
The LayerZero Protocol consists of several programs built on the Solana blockchain designed to facilitate the secure movement of data, tokens, and digital assets between different blockchain environments.
LayerZero provides **Solana Programs** that can communicate directly with the equivalent [Solidity Contract Libraries](/v2/developers/evm/overview)
deployed on EVM-based chains.
#### Solana Programs[](#solana-programs "Direct link to Solana Programs")
[### Getting Started on Solana\
\
Learn how the LayerZero V2 Protocol operates on the Solana blockchain.](/v2/developers/solana/getting-started)
[### OApp Reference\
\
Build the Endpoint instructions necessary for sending arbitrary data and external function calls cross-chain.](/v2/developers/solana/oapp/overview)
[### OFT Program\
\
Create and send Omnichain Fungible Tokens (OFTs) on the Solana blockchain.](/v2/developers/solana/oft/program)
#### Solana Protocol Configurations[](#solana-protocol-configurations "Direct link to Solana Protocol Configurations")
[### Configure Security Stack\
\
Configure which decentralized verifier networks (DVNs) secure your messages.](/v2/developers/solana/configuration/dvn-executor-config#custom-configuration)
[### Configure Executor\
\
Configure who executes your messages on the destination chain.](/v2/developers/solana/configuration/dvn-executor-config#custom-configuration)
[### Set Execution Options\
\
Set the amount of gas to deliver to the destination chain.](/v2/developers/solana/gas-settings/options)
info
You can find all [**LayerZero Solana Programs**](https://github.com/LayerZero-Labs/LayerZero-v2/tree/main/packages/layerzero-v2/solana/programs)
here.
### Tooling and Resources[](#tooling-and-resources "Direct link to Tooling and Resources")
Solana development relies heavily on Rust and the Solana CLI. For more information, see an [Overview of Developing Solana Programs](https://solana.com/docs/programs/overview)
.
LayerZero provides developer tooling to simplify the contract creation, testing, and deployment process:
[LayerZero Scan](http://localhost:3000/v2/developers/evm/tooling/layerzeroscan)
: a comprehensive block explorer, search, API, and analytics platform for tracking and debugging your omnichain transactions.
You can also ask for help or follow development in the [Discord](https://layerzero.network/community)
.
* [Tooling and Resources](#tooling-and-resources)
---
# LayerZero V2 Aptos Move Standards | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero V2 Aptos Move Standards
=================================
**Move** is a safe and flexible programming language for smart contracts, initially developed for the Libra (now Diem) blockchain and later adopted by blockchains like Aptos.
With the introduction of LayerZero support for **Aptos Move**, developers can now build omnichain applications (OApps) on Aptos Move-based chains such as **Aptos**, **Initia**, and **Movement**.
info
All of these chains utilize the same version of Move based on the [**Aptos flavor**](https://aptos.dev/en)
, meaning the Move modules in this section all natively support each chain.
LayerZero Move Contract Standards[](#layerzero-move-contract-standards "Direct link to LayerZero Move Contract Standards")
----------------------------------------------------------------------------------------------------------------------------
[### OApp Standard\
\
The Omnichain Application (OApp) Standard, the boilerplate base for implementing cross-chain messaging.](/v2/developers/aptos-move/contract-modules/oapp)
[### OFT Standard\
\
Extension of OApp, combining the fungible token standard with core bridge logic to create Omnichain Fungible Tokens.](/v2/developers/aptos-move/contract-modules/oft)
Configuration[](#configuration "Direct link to Configuration")
----------------------------------------------------------------
[### Deploy / Wire Aptos (CLI)\
\
Configure which decentralized verifier networks (DVNs) and Executors secure your messages.](/v2/developers/aptos-move/create-lz-oapp/start)
[### Aptos Executor Options\
\
Configure how much gas limit and native gas token should be delivered during message execution.](/v2/developers/aptos-move/configuration/options)
[### Protocol Configurations\
\
Configure which decentralized verifier networks (DVNs) and Executors secure your messages.](/v2/developers/aptos-move/configuration/dvn-executor-config)
tip
To find all of LayerZero's contracts for Aptos Move, visit the [**LayerZero V2 Protocol Repo**](https://github.com/LayerZero-Labs/LayerZero-v2/packages/layerzero-v2/aptos/contracts)
.
Tooling[](#tooling "Direct link to Tooling")
----------------------------------------------
LayerZero provides developer tooling to simplify the contract creation, testing, and deployment process on Move-based chains:
* LayerZero Scan: A comprehensive block explorer, search, API, and analytics platform for tracking and debugging your omnichain transactions.
You can also ask for help or follow development in the Discord.
* [LayerZero Move Contract Standards](#layerzero-move-contract-standards)
* [Configuration](#configuration)
* [Tooling](#tooling)
---
# Read External State (LayerZero Read) | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
**LayerZero Read** extends the existing LayerZero omnichain messaging protocol to enable developers to not only send cross-chain messages, but also **request** and **retrieve** on-chain state from other supported blockchains.
This new capability transforms how cross-chain data is accessed, manipulated, and computed, allowing developers to offload complex computations from on-chain applications to [Decentralized Verifier Networks (DVNs)](/v2/concepts/modular-security/security-stack-dvns)
.
Blockchain Query Language[](#blockchain-query-language "Direct link to Blockchain Query Language")
----------------------------------------------------------------------------------------------------
`lzRead` is the implementation of LayerZero's **Blockchain Query Language** — a universal, standardized language for constructing, retrieving, and processing data requests on-chain across multiple blockchains and off-chain sources.
Using `lzRead`, developers can configure secure, low-latency data queries within smart contracts and manage custom security settings, offering significant flexibility in balancing security, cost, and data accuracy.
The first implementation of BQL is `ReadCodecV1.sol`, enabling smart contracts to request data in the form of `calldata`, `public` state variables, and non state-changing function calls (i.e., `view` and `pure` functions).
As `lzRead` evolves, additional capabilities such as querying event logs, private state variables, and even external data sources may be integrated, further extending the versatility of `lzRead` as a comprehensive data sourcing utility for on-chain applications.
Workflow[](#workflow "Direct link to Workflow")
-------------------------------------------------
 
1. **Request Definition**: An `OApp` sends a `lzRead` command by calling `EndpointV2.send()` with a read-specific `eid` argument called a channel identifier, and encoding the specific target chain and the block from which the state needs to be retrieved. This command is processed using a custom Send Library (`ReadLib1002`) that serializes the request and directs it to the appropriate chain via the application's configured `DVN(s)`.
2. **DVN Fetch and Return**: The `DVN` assigned to the request fetches the data from an archival node on the target chain. The DVN executes `eth_call` on the node with the provided calldata for the target chain, and optionally processes the return data off-chain via compute logic defined by the application in a target `OApp.lzMap()` and `OApp.lzReduce()` function, before finally delivering the return data hash to be validated by the configured `DVN threshold` in the Receive Library (in this case the same `ReadLib1002`).
3. **Response Handling (lzReceive)**: Once the response hash is verified, the `Executor` delivers the actual response data to `OApp.lzReceive()` on the original requesting chain. The same receive workflow used by LayerZero's V2 protocol is triggered via `EndpointV2.lzReceive()`.
4. **Execution and Custom Logic**: The `OApp` processes the response data using the logic defined in the application smart contract via an internal `_lzReceive()`, allowing the developer to customize how the retrieved state is processed and used.
In this way, **lzRead** transforms the normal messaging workflow from cross-chain message delivery to a `request` and `response` model.
| | **Description** | **endpoint.send** | **endpoint.lzReceive** |
| --- | --- | --- | --- |
| **Omnichain Message** | The `bytes` sent match the `bytes` received. | `bytes _message` | `bytes _message` |
| **Omnichain Read** | The `bytes` request differs from the `bytes` response. | `bytes _request` | `bytes _response` |
Instead of sending a source message and using Decentralized Verifier Networks (DVNs) to deliver to the destination, you can now use Decentralized Verifier Networks (DVNs) to read directly from nodes on the destination blockchain.
Supported Chains[](#supported-chains "Direct link to Supported Chains")
-------------------------------------------------------------------------
`lzRead` requires a read compatible Message Library and DVN, meaning you can only use `lzRead` on chains with both supported implementations deployed.
You can find these Message Libraries (`ReadLib1002`) and Compatible DVNs under ["Read Paths"](/v2/deployments/read-contracts)
.
 
info
Each DVN must also support the target chain to read from (i.e., running an archival node for the target data chain).
Installation[](#installation "Direct link to Installation")
-------------------------------------------------------------
To start using LayerZero contracts, you can install the [OApp package](https://github.com/LayerZero-Labs/devtools/tree/main/packages/oapp-evm)
to an existing project:
* npm
* yarn
* pnpm
* forge
npm install @layerzerolabs/oapp-evm
yarn add @layerzerolabs/oapp-evm
pnpm add @layerzerolabs/oapp-evm
forge init
forge install https://github.com/LayerZero-Labs/devtools
forge install https://github.com/LayerZero-Labs/layerzero-v2
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0
Then add to your `foundry.toml` under `[profile.default]`:
[profile.default]src = "src"out = "out"libs = ["lib"]remappings = [ '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',]# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
info
LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable)
and V4 contracts. Specify your desired version in your project's package.json:
"resolutions": { "@openzeppelin/contracts": "^5.0.1",}
Usage[](#usage "Direct link to Usage")
----------------------------------------
To start using `lzRead`, you will need to:
1. Decide what origin chains to deploy your application on and what target data chains to read data from.
2. Decide which `lzRead` compatible DVNs support your target chains.
3. Deploy an `lzRead` compatible OApp.
4. Set your application's send and receive library to `ReadLib1002` via `EndpointV2.setSendLibrary()` and `EndpointV2.setReceiveLibrary()`.
5. Set your application's DVN Config via `EndpointV2.setConfig()`.
### Inherit OAppRead[](#inherit-oappread "Direct link to Inherit OAppRead")
Begin by inheriting the `OAppRead.sol` contract in your own smart contract. Familiarize yourself with the `OAppRead` implementation to understand how it interacts with LayerZero’s endpoint for both message and read commands.
import { OAppRead } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppRead.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";contract MyOApp is OAppRead { constructor(address _endpoint, address _delegate) OAppRead(_endpoint, _delegate) Ownable(_delegate) {}}
### Build Read Query[](#build-read-query "Direct link to Build Read Query")
To retrieve data from other chains and process it when returned, you need to at minimum create a read request. Below is a step-by-step guide on how to construct these read requests, using an example where we read the price of a Uniswap V3 pool token from multiple chains.
#### Getting the Command[](#getting-the-command "Direct link to Getting the Command")
As mentioned above, **lzRead** uses the same send interface as traditional message passing via `EndpointV2.send()`.
To start reading data from target chains, you will encode your requests as `bytes` using the `ReadCodecV1.sol` and pass the encoding as the `bytes _message` parameter in the send interface. From here, we'll refer to the `_message` as the **read command**, or `_cmd`.
Here's an example of how you can initiate the read request:
/** * @notice Sends a read request to LayerZero, querying Uniswap QuoterV2 for WETH/USDC prices on configured chains. * @param _extraOptions Additional messaging options, including gas and fee settings. * @return receipt The LayerZero messaging receipt for the request. */function readAverageUniswapPrice( bytes calldata _extraOptions) external payable returns (MessagingReceipt memory receipt) { bytes memory cmd = getCmd(); return _lzSend( READ_CHANNEL, cmd, combineOptions(READ_CHANNEL, READ_MSG_TYPE, _extraOptions), MessagingFee(msg.value, 0), payable(msg.sender) );}
For example, this `getCmd()` creates multiple read requests:
/** * @notice Constructs a command to query the Uniswap QuoterV2 for WETH/USDC prices on all configured chains. * @return cmd The encoded command to request Uniswap quotes. */function getCmd() public view returns (bytes memory) { uint256 pairCount = targetEids.length; EVMCallRequestV1[] memory readRequests = new EVMCallRequestV1[](pairCount); for (uint256 i = 0; i < pairCount; i++) { uint32 targetEid = targetEids[i]; ChainConfig memory config = chainConfigs[targetEid]; // Define the QuoteExactInputSingleParams IQuoterV2.QuoteExactInputSingleParams memory params = IQuoterV2.QuoteExactInputSingleParams({ tokenIn: config.tokenInAddress, tokenOut: config.tokenOutAddress, amountIn: 1 ether, // amountIn: 1 WETH fee: config.fee, sqrtPriceLimitX96: 0 // No price limit }); // @notice Encode the function call // @dev From Uniswap Docs, this function is not marked view because it relies on calling non-view // functions and reverting to compute the result. It is also not gas efficient and should not // be called on-chain. We take advantage of lzRead to call this function off-chain and get the result // returned back on-chain to the OApp's _lzReceive method. // https://docs.uniswap.org/contracts/v3/reference/periphery/interfaces/IQuoterV2 bytes memory callData = abi.encodeWithSelector(IQuoterV2.quoteExactInputSingle.selector, params); readRequests[i] = EVMCallRequestV1({ appRequestLabel: uint16(i + 1), targetEid: targetEid, isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: config.confirmations, to: config.quoterAddress, callData: callData }); } EVMCallComputeV1 memory computeSettings = EVMCallComputeV1({ computeSetting: 2, // lzMap() and lzReduce() targetEid: ILayerZeroEndpointV2(endpoint).eid(), isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: 15, to: address(this) }); return ReadCodecV1.encode(0, readRequests, computeSettings);}
tip
Following best practices, you should create a dedicated function to construct your specific command requests.
This involves creating an array of `EVMCallRequestV1` structs, each representing a read operation on a specific chain and contract.
info
`_lzSend` is an internal `virtual` method provided in the `OApp` contract standard, which can be used to invoke the `endpoint.send` if certain security checks pass:
function _lzSend( uint32 _dstEid, bytes memory _message, bytes memory _options, MessagingFee memory _fee, address _refundAddress) internal virtual returns (MessagingReceipt memory receipt) { // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint. uint256 messageValue = _payNative(_fee.nativeFee); if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee); return // solhint-disable-next-line check-send-result endpoint.send{ value: messageValue }( MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0), _refundAddress );}
If using the `OApp` standard, consider `_lzSend` the recommended way to call `EndpointV2.send()`.
#### Call Requests[](#call-requests "Direct link to Call Requests")
A properly formatted read request is of type `EVMCallRequestV1`, which includes the following fields:
readRequests[i] = EVMCallRequestV1({ appRequestLabel: uint16(i + 1), targetEid: targetEid, isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: config.confirmations, to: config.quoterAddress, callData: callData});
| Name | Type | Description |
| --- | --- | --- |
| appRequestLabel | `uint16` | A label to identify the request within your application logic. Useful for tracking response types. |
| targetEid | `uint32` | The Endpoint ID of the target chain where the data is to be read from. |
| isBlockNum | `bool` | A boolean indicating whether `blockNumOrTimestamp` is a block number (`true`) or a timestamp (`false`). |
| blockNumOrTimestamp | `uint64` | Specifies the `block.number` or `block.timestamp` on the target chain (`targetEid`) at which to read the state. |
| confirmations | `uint16` | The number of confirmations required to wait for the block or timestamp finality on the target chain. |
| to | `address` | The target contract address on the destination chain (e.g., the Uniswap V3 Quoter contract). |
| callData | `bytes` | The ABI-encoded function calldata. |
The `callData` field contains the encoded function call that will be executed on the target contract.
For example, `IQuoterV2.quoteExactInputSingle.selector` specifies the `quoteExactInputSingle` function in the Uniswap V3 `QuoterV2` contract to request a price quote for swapping `tokenIn` to `tokenOut` on the specified Uniswap V3 pool.
danger
Your target function calldata for the call request should **never revert**. If the target function ever reverts, the DVN can never complete the verification workflow, and the message will be stuck in-flight and block the pathway until the OApp delegate calls `EndpointV2.skip()` on the reverted nonce.
Ensure that your target function either uses `try-catch` to handle reverts, or be confident that your call parameters will never trigger a revert.
info
The `EVMCallRequestV1` struct is limited to chain `calldata` because it encodes function calls into a format that can be transmitted and understood across chains using the EVM's ABI encoding.
This limitation means that `lzRead` can only handle data types compatible with `calldata`, specifically value types and fixed-size data structures.
#### Optional: Compute Logic[](#optional-compute-logic "Direct link to Optional: Compute Logic")
You can optionally specify compute instructions to handle processing the return data after the read request.
**Compute Logic** allows you to transform and condense the data retrieved by your DVNs, ensuring that only the most relevant and actionable information is sent back to your application on the origin chain.
Compute logic should be formatted as an `EVMCallComputeV1` struct:
EVMCallComputeV1 memory computeSettings = EVMCallComputeV1({ computeSetting: 2, // lzMap() and lzReduce() targetEid: ILayerZeroEndpointV2(endpoint).eid(), isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: 15, to: address(this)});
| Name | Type | Description |
| --- | --- | --- |
| computeSetting | `uint8` | Specifies the compute operations to perform, such as `lzMap()` (0), `lzReduce()`, (1), or `lzMap()` AND `lzReduce()` (2). More on this later. |
| targetEid | `uint32` | The Endpoint ID of the target chain where the `lzMap()` or `lzReduce()` implementation can be read from. |
| isBlockNum | `bool` | A boolean indicating whether `blockNumOrTimestamp` is a block number (`true`) or a timestamp (`false`). |
| blockNumOrTimestamp | `uint64` | Specifies the `block.number` or `block.timestamp` on the target chain at which to read the state. |
| confirmations | `uint16` | The number of confirmations required to wait for the block or timestamp finality on the target chain. |
| to | `address` | The target contract address to view `lzMap()` and / or `lzReduce()`. |
info
Note: The next section will dive deeper into how to define the compute logic within `lzMap()` and `lzReduce()`.
#### Encoding Command[](#encoding-command "Direct link to Encoding Command")
The `ReadCodecV1.encode()` has been overloaded to support both the `readRequest` and `evmCompute`.
If you only need to read the raw data returned by your call requests, you can finish the encoding and omit the compute logic.
```solidityint16 appLabel = 0; // Application label (set as needed)// Encoding the command without compute logicreturn ReadCodecV1.encode(appLabel, readRequests);
If you have defined the command's `EVMCallRequestV1` and the `EVMCallComputeV1`, you can use the `ReadCodecV1` to encode the command to end your getter function.
int16 appLabel = 0; // Application label (set as needed)// Encoding the commandreturn ReadCodecV1.encode(appLabel, readRequests, evmCompute);
caution
Different chains have different interpretations of `block.number` and `block.timestamp`.
For example, when calling `block.number` on Arbitrum L2 the value returned is the block number on Ethereum L1, rather than `block.number` of the Arbitrum chain itself.
Consider how these edge cases specific to each `targetEid` impact your application before finalizing the encoding process for your command from `EVMCallRequestV1` and `EVMCallComputeV1`.
### Optional: Declare Compute Logic[](#optional-declare-compute-logic "Direct link to Optional: Declare Compute Logic")
Compute logic is executed off-chain via your application's configured Decentralized Verifier Networks (DVNs). To define specific compute logic, you must target a contract that implements either `IOAppMapper` or `IOAppReducer`, which define the `lzMap()` and `lzReduce()` functions.
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;/// @title IOAppMapper Interface/// @notice Defines the lzMap function for mapping operationsinterface IOAppMapper { /** * @notice Processes a single mapping operation * @param _request The request data in bytes * @param _response The response data in bytes * @return A bytes array resulting from the mapping operation */ function lzMap( bytes calldata _request, bytes calldata _response ) external view returns (bytes memory);}/// @title IOAppReducer Interface/// @notice Defines the lzReduce function for reducing operationsinterface IOAppReducer { /** * @notice Processes a reduction operation over multiple responses * @param _cmd The command data in bytes * @param _responses An array of response data in bytes * @return A bytes array resulting from the reduction operation */ function lzReduce( bytes calldata _cmd, bytes[] calldata _responses ) external view returns (bytes memory);}
These optional `view` or `pure` functions enable your DVNs to read and compute state changes based on the `_response` or `_responses` from each request.
#### Compute Settings[](#compute-settings "Direct link to Compute Settings")
When you declare an `EVMCallComputeV1`, you also select a compute setting for whether your configured DVNs should mutate the `_response` data using `lzMap()` (`0`), `lzReduce()`, (`1`), both `lzMap()` and `lzReduce()` (`2`), or no compute setting (`3`).
uint8 internal constant MAP_ONLY = 0;uint8 internal constant REDUCE_ONLY = 1;uint8 internal constant MAP_AND_REDUCE = 2;EVMCallComputeV1 memory evmCompute = EVMCallComputeV1({ computeSetting: MAP_AND_REDUCE, // lzMap() and lzReduce() targetEid: eid, // Current chain's EID isBlockNum: true, blockNumOrTimestamp: uint64(block.number), confirmations: 2, to: address(this) // Compute executed in this contract});
* If only `lzMap()`, the returned data to `OApp._lzReceive()` will be the concatenation of every return output for each `_request` your `lzMap()` processes. You will need to track the `index` of each request made in the command to later decode in your receive logic.
* If only `lzReduce`, the `lzReduce()` implementation will intake the concatenation in order of how the `EVMCallRequestV1[]` were made as a `bytes` argument, and return a single `bytes` output. You can use `lzReduce()` to aggregate all responses.
* If both, data will be manipulated on a per request level first via `lzMap()` and an aggregate level via `lzReduce()`, before being returned to `OApp._lzReceive()`.
Implementation details can be found below.
#### Mapping Requests[](#mapping-requests "Direct link to Mapping Requests")
Mapping refers to defining how to format or process the data returned by each individual request. In the Uniswap example, the `lzMap()` simplifies the returned data by only decoding the `amountOut` for use in the `lzReduce()` step.
/** * @notice Processes individual Uniswap QuoterV2 responses, encoding the result. * @param _response The response from the read request. * @return Encoded token output amount (USDC amount). */function lzMap(bytes calldata, bytes calldata _response) external pure returns (bytes memory) { require(_response.length >= 32, "Invalid response length"); // quoteExactInputSingle returns multiple values // Decode the response to extract amountOut (uint256 amountOut, , , ) = abi.decode(_response, (uint256, uint160, uint32, uint256)); return abi.encode(amountOut);}
info
The decoding above only decodes to demonstrate how an `lzMap()` can intake a `_response`, mutate the returned `bytes` per `_request`, and return a new encoded output depending on the needs of your application.
#### Reducing Requests[](#reducing-requests "Direct link to Reducing Requests")
Once you've mapped the individual responses, the `lzReduce()` function aggregates these mapped responses to produce a final result.
In the Uniswap example, `lzReduce()` calculates the average `amountOut` from all responses.
/** * @notice Aggregates individual token output amounts to compute an average. * @param _responses Array of mapped responses containing token output amounts. * @return Encoded average token output amount. */function lzReduce(bytes calldata, bytes[] calldata _responses) external pure returns (bytes memory) { require(_responses.length == 3, "Expected responses from 3 chains"); uint256 total = 0; for (uint256 i = 0; i < _responses.length; i++) { uint256 amountOut = abi.decode(_responses[i], (uint256)); total += amountOut; } uint256 averageAmountOut = total / _responses.length; return abi.encode(averageAmountOut);}
### Creating Request Options[](#creating-request-options "Direct link to Creating Request Options")
`lzRead` uses a new options type, `addExecutorLzReadOption` to send requests for target data.
OptionsBuilder.newOptions().addExecutorLzReadOption(100_000, 64, 0)
If unfamiliar with `_options`, you can read the full scope in [Execution Options](/v2/developers/evm/configuration/options)
, but for reference this send parameter allows you to deliver an amount of `gasLimit` and/or native gas token automatically to `endpoint.lzReceive` from your configured Executor.
For `lzRead`, an additional requirement for `_options` is to profile the `calldata` size of your returned data type:
OptionsBuilder.newOptions().addExecutorLzReadOption(GAS_LIMIT, CALLDATA_SIZE, MSG_VALUE)
These `_options` can be enforced as usual by inheriting the [Enforced Options Helper](/v2/developers/evm/oapp/overview#optional-enforced-options)
in your `OAppRead` contract:
import { OAppRead } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppRead.sol";import { OAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";contract MyOAppRead is OAppRead, OAppOptionsType3 {}
caution
Adding any other `_options` types than the `lzReadOption` type in `endpoint.quote()` or `endpoint.send()` call will cause the transaction to revert on source.
caution
The Executor will not deliver the request if the `ACTUAL_CALLDATA_SIZE > OPTION_CALLDATA_SIZE`. You will need to manually execute the transaction via `endpoint.lzReceive()`.
To avoid this, estimate the return calldata size from your target function, and enforce that you add at least the calldata size to your message `options`.
### Receive Responses[](#receive-responses "Direct link to Receive Responses")
Finally, you need to define your internal handler for incoming messages from the LayerZero protocol. This is where you'll process the final aggregated result and use it within your contract.
function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) internal
In the Uniswap example, the handler processes the message and emits an event with the average token price:
/// @notice Emitted when the average amount out is computed and received.event AggregatedPrice(uint256 averageAmountOut);/** * @notice Handles the aggregated average price from Uniswap V3 pool responses received from LayerZero. * @dev Emits the AggregatedPrice event with the calculated average amount. * @param _message Encoded average token output amount. */function _lzReceive( Origin calldata /*_origin*/, bytes32 /*_guid*/, bytes calldata _message, address /*_executor*/, bytes calldata /*_extraData*/) internal override { require(_message.length == 32, "Invalid message length"); uint256 averagePrice = abi.decode(_message, (uint256)); emit AggregatedPrice(averagePrice);}
By using `lzRead`, you can drastically simplify the amount of logic needed for updating state on-chain.
### Setting Libraries and DVNs[](#setting-libraries-and-dvns "Direct link to Setting Libraries and DVNs")
You MUST call `EndpointV2.setSendLibrary()`, `EndpointV2.setReceiveLibrary()`, and `EndpointV2.setConfig()` with a valid `ReadLib1002` and supporting `DVN` configuration.
Set your configuration for the Read Library and required DVNs that support `lzRead`. [See all available DVNs](/v2/deployments/dvn-addresses)
.
You can set your DVN configuration either via the [LayerZero CLI tool](/v2/developers/evm/create-lz-oapp/start)
or via [example scripts](/v2/developers/evm/configuration/dvn-executor-config)
.
### Setting Read Channel[](#setting-read-channel "Direct link to Setting Read Channel")
Once your configuration has been applied, simply call `OApp.setReadChannel()` for the specific channel configuration to set and begin sending messages!
Channels let you define multiple types of read logic per application. You can specify per request label, per application label, or per channel.
// OAppRead.sol// Only Ownerfunction setReadChannel(uint32 _channelId, bool _active) public virtual onlyOwner { _setPeer(_channelId, _active ? AddressCast.toBytes32(address(this)) : bytes32(0));}
To see the available channels for each target data chain, see [Read Paths](/v2/deployments/read-contracts)
.
### Debugging Read Commands[](#debugging-read-commands "Direct link to Debugging Read Commands")
When working with `lzRead`, you might encounter issues where read commands (Requests and Compute logic) fail to execute as expected. LayerZero provides tools and best practices to help you debug these scenarios effectively.
When checking LayerZero Scan for the read status, you may encounter the following errors.
#### Malformed Command[](#malformed-command "Direct link to Malformed Command")
Malformed indicates that the read command was constructed incorrectly and does not adhere to the required serialization format. This can be due to:
* Incorrect packing of the command parameters.
* Errors in encoding the `calldata`.
* Misuse of the `ReadCodecV1.sol` library during command construction.
To resolve a **Malformed Command**, review how the command was packed on the origin chain by examining your `getCmd` implementation. Ensure that you are correctly using the `ReadCodecV1.sol` library for serialization.
#### Unresolvable Command[](#unresolvable-command "Direct link to Unresolvable Command")
The target data chain cannot fulfill the read request due to issues related to invoking the `calldata` on the target chain, such as:
**Contract Address Issues**:
* The specified contract does not exist on the target chain.
* The contract address is incorrect for the given block number or timestamp.
**Method Identifier Issues**:
* The calldata contains an invalid or non-existent method selector.
**State Issues**:
* The contract is deployed, but the state at the specified `block.number` or `block.timestamp` does not support the requested operation.
To resolve an **Unresolvable Command**:
* Ensure that the contract exists at the specified address on the target chain.
* Verify that the contract is deployed and initialized correctly.
* Double-check the contract address and ensure it matches the target chain's deployment.
* Confirm that the `block.number` or `block.timestamp` specified in the read request corresponds to a state where the contract is available.
* Ensure that the method selector used in the `calldata` exists in the target contract.
* Verify that the function signatures match and that the target contract implements the requested methods.
You can use the [Read CLI Debugging Task](/v2/developers/evm/lzread/read-cli#debugging-malformed-or-unresolvable-commands)
to help with the debugging process by ensuring the command decodes and reads the target data chain correctly.
### Dual Messaging Mode[](#dual-messaging-mode "Direct link to Dual Messaging Mode")
Since `lzRead` is an extension to the base messaging protocol, determine the type of cross-chain functionality your app needs:
1. **LayerZero Messaging**: For standard cross-chain messages without needing to read external state.
2. **LayerZero Reads (lzRead)**: To retrieve and use on-chain state from other blockchains.
3. **Hybrid (Both)**: To implement both messaging and read capabilities.
Here’s an example of how `OAppRead` is structured to handle both message-based and read-based responses by implementing a `_messageLzReceive` and `_readLzReceive`:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;import {OAppRead} from "@layerzerolabs/oapp-evm/contracts/oapp/OAppRead.sol";import {Origin} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";/// @title MsgAndReadExample/// @notice An example contract that extends OAppRead to handle both messaging and read capabilities./// @dev Inherits from OApp and adds functionality specific to lzRead.contract MsgAndReadExample is OAppRead { /// lzRead responses are sent from arbitrary channels with Endpoint IDs in the range of /// `eid > 4294965694` (which is `type(uint32).max - 1600`). uint32 constant READ_CHANNEL_EID_THRESHOLD = 4294965694; /// @param _endpoint The address of the LayerZero endpoint. /// @param _delegate The address of the delegate contract. constructor(address _endpoint, address _delegate) OAppRead(_endpoint, _delegate) Ownable(_delegate) {} /// @notice Internal function to handle incoming messages and read responses. /// @dev Filters messages based on `srcEid` to determine the type of incoming data. /// @param _origin The origin information containing the source Endpoint ID (`srcEid`). /// @param _guid The unique identifier for the received message. /// @param _message The encoded message data. /// @param _executor The executor address. /// @param _extraData Additional data. function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData ) internal virtual override { /** * @dev The `srcEid` (source Endpoint ID) is used to determine the type of incoming message. * - If `srcEid` is greater than READ_CHANNEL_EID_THRESHOLD (4294965694), * it corresponds to arbitrary channel IDs for lzRead responses. * - All other `srcEid` values correspond to standard LayerZero messages. */ if (_origin.srcEid > READ_CHANNEL_EID_THRESHOLD) { // Handle lzRead responses from arbitrary channels. _readLzReceive(_origin, _guid, _message, _executor, _extraData); } else { // Handle standard LayerZero messages. _messageLzReceive(_origin, _guid, _message, _executor, _extraData); } } /// @notice Internal function to handle standard LayerZero messages. /// @dev _origin The origin information (unused in this implementation). /// @dev _guid The unique identifier for the received message (unused in this implementation). /// @param _message The encoded message data. /// @dev _executor The executor address (unused in this implementation). /// @dev _extraData Additional data (unused in this implementation). function _messageLzReceive( Origin calldata /* _origin */, bytes32 /* _guid */, bytes calldata _message, address /* _executor */, bytes calldata /* _extraData */ ) internal virtual { // Implement message handling logic here. bool _messageDoSomething = abi.decode(_message, (bool)); } /// @notice Internal function to handle lzRead responses. /// @dev _origin The origin information (unused in this implementation). /// @dev _guid The unique identifier for the received message (unused in this implementation). /// @param _message The encoded message data. /// @dev _executor The executor address (unused in this implementation). /// @dev _extraData Additional data (unused in this implementation). function _readLzReceive( Origin calldata /* _origin */, bytes32 /* _guid */, bytes calldata _message, address /* _executor */, bytes calldata /* _extraData */ ) internal virtual { // Implement lzRead response handling logic here. bool _readDoSomething = abi.decode(_message, (bool)); }}
* [Blockchain Query Language](#blockchain-query-language)
* [Workflow](#workflow)
* [Supported Chains](#supported-chains)
* [Installation](#installation)
* [Usage](#usage)
* [Inherit OAppRead](#inherit-oappread)
* [Build Read Query](#build-read-query)
* [Getting the Command](#getting-the-command)
* [Call Requests](#call-requests)
* [Optional: Compute Logic](#optional-compute-logic)
* [Encoding Command](#encoding-command)
* [Optional: Declare Compute Logic](#optional-declare-compute-logic)
* [Compute Settings](#compute-settings)
* [Mapping Requests](#mapping-requests)
* [Reducing Requests](#reducing-requests)
* [Creating Request Options](#creating-request-options)
* [Receive Responses](#receive-responses)
* [Setting Libraries and DVNs](#setting-libraries-and-dvns)
* [Setting Read Channel](#setting-read-channel)
* [Debugging Read Commands](#debugging-read-commands)
* [Malformed Command](#malformed-command)
* [Unresolvable Command](#unresolvable-command)
* [Dual Messaging Mode](#dual-messaging-mode)
---
# LayerZero V2 Solidity Contract Standards | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero V2 Solidity Contract Standards
========================================
LayerZero enable seamless cross-chain messaging, configurations for security, and other quality of life improvements to simplify cross-chain development.
#### LayerZero Solidity Contract Standards[](#layerzero-solidity-contract-standards "Direct link to LayerZero Solidity Contract Standards")
[### OApp Standard\
\
The base generic message passing standard, enabling cross-chain data transfer and external function calls.](/v2/developers/evm/oapp/overview)
[### OFT Standard\
\
Extension of OApp, combining the ERC20 token standard with core bridge logic to make Omnichain Fungible Tokens.](/v2/developers/evm/oft/quickstart)
[### ONFT Standard\
\
Combines the ERC721 token standard with core bridge logic to make Omnichain Non-Fungible Tokens.](/v2/developers/evm/onft/quickstart)
#### LayerZero Solidity Protocol Configurations[](#layerzero-solidity-protocol-configurations "Direct link to LayerZero Solidity Protocol Configurations")
[### Configure Security Stack\
\
Configure which decentralized verifier networks (DVNs) secure your messages.](/v2/developers/evm/configuration/dvn-executor-config#custom-configuration)
[### Configure Executor\
\
Configure who executes your messages on the destination chain.](/v2/developers/evm/configuration/dvn-executor-config#custom-configuration)
[### Set Execution Options\
\
Set the amount of gas to deliver to the destination chain.](/v2/developers/evm/configuration/options)
info
To find all of LayerZero's contracts visit the [**LayerZero V2 Protocol Repo**](https://github.com/LayerZero-Labs/LayerZero-v2)
.
### Installation[](#installation "Direct link to Installation")
To start sending cross-chain messages with LayerZero, you can find install instructions for each contract package in [OApp Quickstart](/v2/developers/evm/oapp/overview)
, [OFT Quickstart](/v2/developers/evm/oft/quickstart)
, or [ONFT Quickstart](/v2/developers/evm/onft/quickstart)
.
LayerZero also provides [create-lz-oapp](/v2/developers/evm/create-lz-oapp/start)
, an all-in-one npx package that allows developers to create a project from any of the available omnichain standards in <4 minutes!
tip
Get started by running the following from your command line:
npx create-lz-oapp@latest
### Usage[](#usage "Direct link to Usage")
Once installed, you can use the contracts in the library by importing them:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";import { OApp } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";contract MyOApp is OApp { constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {} // ... rest of OApp interface functions}
To keep your system secure, you should **always** use the installed code as-is, and neither copy-paste it from online sources, nor modify it directly.
Most of the LayerZero contracts are expected to be used via inheritance: you will inherit from them when writing your own contracts.
Tooling[](#tooling "Direct link to Tooling")
----------------------------------------------
LayerZero also provides developer tooling to simplify the contract creation, testing, and deployment process:
* [LayerZero Scan](/v2/developers/evm/tooling/layerzeroscan)
: a comprehensive block explorer, search, API, and analytics platform for tracking and debugging your omnichain transactions.
* [TestHelper (Foundry)](/v2/developers/evm/foundry)
: a suite of functions to simulate cross-chain transactions and validate the behavior of OApps locally in your Foundry unit tests.
You can also ask for help or follow development in the [Discord](https://layerzero.network/community)
.
* [Installation](#installation)
* [Usage](#usage)
* [Tooling](#tooling)
---
# Deployed Endpoints, Message Libraries, and Executors | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
Below you can find a description of the main LayerZero V2 contracts and find the corresponding deployment information for each blockchain network LayerZero supports.
Contract Address Table[](#contract-address-table "Direct link to Contract Address Table")
-------------------------------------------------------------------------------------------
Total Mainnet Networks: 119
Network Type:
All

Chains:
All

Show Recently Added:
Open in MetadataReset
| Chain | Chain Id | Endpoint | Endpoint Id | Libraries & Executor | RPC |
| --- | --- | --- | --- | --- | --- |
| [### Abstract Mainnet](#abstract) | 2741 | [EndpointV2
(0x5c6c...)](https://layerzeroscan.com/api/explorer/abstract/address/0x5c6cfF4b7C49805F8295Ff73C204ac83f3bC4AE7) | 30324 | [SendUln302
(0x166C...)](https://layerzeroscan.com/api/explorer/abstract/address/0x166CAb679EBDB0853055522D3B523621b94029a1)
[ReceiveUln302
(0x9d79...)](https://layerzeroscan.com/api/explorer/abstract/address/0x9d799c1935c51CA399e6465Ed9841DEbCcEc413E)
[ReadLib1002
(0x2B79...)](https://layerzeroscan.com/api/explorer/abstract/address/0x2B79f3C2EE059E465a24bf5A2F4FE989546053B1)
[BlockedMessageLib
(0x3258...)](https://layerzeroscan.com/api/explorer/abstract/address/0x3258287147fb7887d8a643006e26e19368057377)
[LZ Executor
(0x643E...)](https://layerzeroscan.com/api/explorer/abstract/address/0x643E1471f37c4680Df30cF0C540Cd379a0fF58A5)
[LZ Dead DVN
(0x06e5...)](https://layerzeroscan.com/api/explorer/abstract/address/0x06e56abD0cb3C88880644bA3C534A498cA18E5C8) |  |
| [### Abstract Testnet](#abstract-testnet) | 11124 | [EndpointV2
(0x16c6...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0x16c693A3924B947298F7227792953Cd6BBb21Ac8) | 40313 | [SendUln302
(0xF636...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0xF636882f80cb5039D80F08cDEee1b166D700091b)
[ReceiveUln302
(0x2443...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0x2443297aEd720EACED2feD76d1C6044471382EA2)
[BlockedMessageLib
(0x7224...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0x7224ff87e2ee26a56d6be7534e16621828e0e2b5)
[LZ Executor
(0x5c12...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0x5c123dB6f87CC0d7e320C5CC9EaAfD336B5f6eF3)
[LZ Dead DVN
(0x2DCC...)](https://layerzeroscan.com/api/explorer/abstract-testnet/address/0x2DCC8cFb612fDbC0Fb657eA1B51A6F77b8b86448) |  |
| [### Ape Mainnet](#ape) | 33139 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/ape/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30312 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/ape/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/ape/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReadLib1002
(0x1641...)](https://layerzeroscan.com/api/explorer/ape/address/0x1641D737e97916f1400CB6BA032eceE765484D9C)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/ape/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/ape/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/ape/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Apexfusionnexus Testnet](#apexfusionnexus-testnet)
Recently Added | 9070 | [EndpointV2
(0xb23b...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0xb23b28012ee92E8dE39DEb57Af31722223034747) | 40355 | [SendUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[ReceiveUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[BlockedMessageLib
(0xd697...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0xd69769251c2de60f0c44f4c2dbfdae9d1897e4c4)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/apexfusionnexus-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Aptos](#aptos) | 1 | [EndpointV2
(0xe600...)](https://layerzeroscan.com/api/explorer/aptos/address/0xe60045e20fc2c99e869c1c34a65b9291c020cd12a0d37a00a53ac1348af4f43c) | 30108 | [SendUln302
(0xc337...)](https://layerzeroscan.com/api/explorer/aptos/address/0xc33752e0220faf79e45385dd73fb28d681dcd9f1569a1480725507c1f3c3aba9)
[ReceiveUln302
(0xc337...)](https://layerzeroscan.com/api/explorer/aptos/address/0xc33752e0220faf79e45385dd73fb28d681dcd9f1569a1480725507c1f3c3aba9)
[LZ Executor
(0x15a5...)](https://layerzeroscan.com/api/explorer/aptos/address/0x15a5bbf1eb7998a22c9f23810d424abe40bd59ddd8e6ab7e59529853ebed41c4) | [](https://aptos.dev/en/network/nodes/networks "Open Aptos page") |
| [### Aptos Testnet](#aptos-testnet) | 2 | [EndpointV2
(0x7f03...)](https://layerzeroscan.com/api/explorer/aptos-testnet/address/0x7f03103b83c51c8b09be1751a797a65ac6e755f72947ecdecffc203d32d816c6) | 40108 | [SendUln302
(0xcc1c...)](https://layerzeroscan.com/api/explorer/aptos-testnet/address/0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690)
[ReceiveUln302
(0xcc1c...)](https://layerzeroscan.com/api/explorer/aptos-testnet/address/0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690)
[LZ Executor
(0x9335...)](https://layerzeroscan.com/api/explorer/aptos-testnet/address/0x93353700091200ef9fdc536ce6a86182cc7e62da25f94356be9421c6310b9585) | [](https://aptos.dev/en/network/nodes/networks "Open Aptos page") |
| [### Arbitrum Mainnet](#arbitrum) | 42161 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x1a44076050125825900e736c501f859c50fE728c) | 30110 | [SendUln302
(0x975b...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x975bcD720be66659e3EB3C0e4F1866a3020E493A)
[ReceiveUln302
(0x7B9E...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x7B9E184e07a6EE1aC23eAe0fe8D6Be2f663f05e6)
[ReadLib1002
(0xbcd4...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0xbcd4CADCac3F767C57c4F402932C4705DF62BEFf)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x31CA...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x31CAe3B7fB82d847621859fb1585353c5720660D)
[LZ Dead DVN
(0x758C...)](https://layerzeroscan.com/api/explorer/arbitrum/address/0x758C419533ad64Ce9D3413BC8d3A97B026098EC1) |  |
| [### Arbitrum Nova Mainnet](#nova) | 42170 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/nova/address/0x1a44076050125825900e736c501f859c50fE728c) | 30175 | [SendUln302
(0xef32...)](https://layerzeroscan.com/api/explorer/nova/address/0xef32f931ac53808e695B7eF3D1b6C5016024a68f)
[ReceiveUln302
(0xB81F...)](https://layerzeroscan.com/api/explorer/nova/address/0xB81F326b95e79eaC4aba800Ae545efb4C602973D)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/nova/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x8Ee0...)](https://layerzeroscan.com/api/explorer/nova/address/0x8Ee02736F8a0c28164a20c25f3d199a74DF7F24B)
[LZ Dead DVN
(0x1BE6...)](https://layerzeroscan.com/api/explorer/nova/address/0x1BE6093E02A7AbF324053eE3f501CF2c049dA471) |  |
| [### Arbitrum Sepolia Testnet](#arbitrum-sepolia) | 421614 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40231 | [SendUln302
(0x4f7c...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E)
[ReceiveUln302
(0x75Db...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x75Db67CDab2824970131D5aa9CECfC9F69c69636)
[ReadLib1002
(0x5432...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x54320b901FDe49Ba98de821Ccf374BA4358a8bf6)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x5Df3...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897)
[LZ Dead DVN
(0xA85B...)](https://layerzeroscan.com/api/explorer/arbitrum-sepolia/address/0xA85BE08A6Ce2771C730661766AACf2c8Bb24C611) |  |
| [### Astar Mainnet](#astar) | 592 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/astar/address/0x1a44076050125825900e736c501f859c50fE728c) | 30210 | [SendUln302
(0x30C3...)](https://layerzeroscan.com/api/explorer/astar/address/0x30C3074669d866933db74DF1Fbe4b3703e6ec139)
[ReceiveUln302
(0xF08f...)](https://layerzeroscan.com/api/explorer/astar/address/0xF08f13c080fcc530B1C21DE827C27B7b66874DDc)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/astar/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x3C55...)](https://layerzeroscan.com/api/explorer/astar/address/0x3C5575898f59c097681d1Fc239c2c6Ad36B7b41c)
[LZ Dead DVN
(0x6626...)](https://layerzeroscan.com/api/explorer/astar/address/0x6626D0739b2B04b70b372394274EB77CAd0824b2) |  |
| [### Astar Testnet](#astar-testnet) | 81 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/astar-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40210 | [SendUln302
(0x3617...)](https://layerzeroscan.com/api/explorer/astar-testnet/address/0x3617dA335F75164809B540bA31bdf79DE6cB1Ee3)
[ReceiveUln302
(0xdBdC...)](https://layerzeroscan.com/api/explorer/astar-testnet/address/0xdBdC042321A87DFf222C6BF26be68Ad7b3d7543f)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/astar-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9130...)](https://layerzeroscan.com/api/explorer/astar-testnet/address/0x9130D98D47984BF9dc796829618C36CBdA43EBb9) |  |
| [### Astar zkEVM Mainnet](#zkatana) | 3776 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/zkatana/address/0x1a44076050125825900e736c501f859c50fE728c) | 30257 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/zkatana/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/zkatana/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/zkatana/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/zkatana/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x01cA...)](https://layerzeroscan.com/api/explorer/zkatana/address/0x01cA5322ce196568a62780C480bb0CC0B595Efec) |  |
| [### Aurora Testnet](#aurora-testnet) | 1313161555 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/aurora-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40201 | [SendUln302
(0x19D1...)](https://layerzeroscan.com/api/explorer/aurora-testnet/address/0x19D1198b0f43Ec076a897bF98dEb0FD1D6CE8B9f)
[ReceiveUln302
(0x0E91...)](https://layerzeroscan.com/api/explorer/aurora-testnet/address/0x0E91e0239971B6CF7519e458a742e2eA4Ffb7458)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/aurora-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9dD6...)](https://layerzeroscan.com/api/explorer/aurora-testnet/address/0x9dD6727B9636761ff50E375D0A7039BD5447ceDB) |  |
| [### Avalanche Fuji Testnet](#fuji) | 43113 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/fuji/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40106 | [SendUln302
(0x69BF...)](https://layerzeroscan.com/api/explorer/fuji/address/0x69BF5f48d2072DfeBc670A1D19dff91D0F4E8170)
[ReceiveUln302
(0x819F...)](https://layerzeroscan.com/api/explorer/fuji/address/0x819F0FAF2cb1Fba15b9cB24c9A2BDaDb0f895daf)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/fuji/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xa7BF...)](https://layerzeroscan.com/api/explorer/fuji/address/0xa7BFA9D51032F82D649A501B6a1f922FC2f7d4e3) |  |
| [### Avalanche Mainnet](#avalanche) | 43114 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x1a44076050125825900e736c501f859c50fE728c) | 30106 | [SendUln302
(0x197D...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x197D1333DEA5Fe0D6600E9b396c7f1B1cFCc558a)
[ReceiveUln302
(0xbf35...)](https://layerzeroscan.com/api/explorer/avalanche/address/0xbf3521d309642FA9B1c91A08609505BA09752c61)
[ReadLib1002
(0x8839...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x8839D3f169f473193423b402BDC4B5c51daAABDc)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x90E5...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x90E595783E43eb89fF07f63d27B8430e6B44bD9c)
[LZ Dead DVN
(0x90cC...)](https://layerzeroscan.com/api/explorer/avalanche/address/0x90cCA24D1338Bd284C25776D9c12f96764Bde5e1) |  |
| [### BNB Smart Chain Mainnet](#bsc) | 56 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/bsc/address/0x1a44076050125825900e736c501f859c50fE728c) | 30102 | [SendUln302
(0x9F8C...)](https://layerzeroscan.com/api/explorer/bsc/address/0x9F8C645f2D0b2159767Bd6E0839DE4BE49e823DE)
[ReceiveUln302
(0xB217...)](https://layerzeroscan.com/api/explorer/bsc/address/0xB217266c3A98C8B2709Ee26836C98cf12f6cCEC1)
[ReadLib1002
(0x3737...)](https://layerzeroscan.com/api/explorer/bsc/address/0x37375049CDc522Bd6bAeEbf527A42D54688d784c)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/bsc/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x3ebD...)](https://layerzeroscan.com/api/explorer/bsc/address/0x3ebD570ed38B1b3b4BC886999fcF507e9D584859)
[LZ Dead DVN
(0xe9b5...)](https://layerzeroscan.com/api/explorer/bsc/address/0xe9b5E4f9395a60799F4F608Ba3ABebDfC0ee6D9C) |  |
| [### BNB Smart Chain Testnet](#bsc-testnet) | 97 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/bsc-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40102 | [SendUln302
(0x55f1...)](https://layerzeroscan.com/api/explorer/bsc-testnet/address/0x55f16c442907e86D764AFdc2a07C2de3BdAc8BB7)
[ReceiveUln302
(0x188d...)](https://layerzeroscan.com/api/explorer/bsc-testnet/address/0x188d4bbCeD671A7aA2b5055937F79510A32e9683)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/bsc-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x3189...)](https://layerzeroscan.com/api/explorer/bsc-testnet/address/0x31894b190a8bAbd9A067Ce59fde0BfCFD2B18470) |  |
| [### BOB Mainnet](#bob) | 60808 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/bob/address/0x1a44076050125825900e736c501f859c50fE728c) | 30279 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/bob/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bob/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x0000...)](https://layerzeroscan.com/api/explorer/bob/address/0x0000000000000000000000000000000000000000)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/bob/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0x11bA...)](https://layerzeroscan.com/api/explorer/bob/address/0x11bA0F5c3832044A416B2E177EA773eceBCCEE1f) |  |
| [### Bahamut Horizen Testnet](#bahamut-testnet) | 2552 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40347 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/bahamut-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Bahamut Mainnet](#bahamut)
Recently Added | 5165 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/bahamut/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30363 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/bahamut/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bahamut/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/bahamut/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/bahamut/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/bahamut/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Base Mainnet](#base) | 8453 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/base/address/0x1a44076050125825900e736c501f859c50fE728c) | 30184 | [SendUln302
(0xB532...)](https://layerzeroscan.com/api/explorer/base/address/0xB5320B0B3a13cC860893E2Bd79FCd7e13484Dda2)
[ReceiveUln302
(0xc70A...)](https://layerzeroscan.com/api/explorer/base/address/0xc70AB6f32772f59fBfc23889Caf4Ba3376C84bAf)
[ReadLib1002
(0x1273...)](https://layerzeroscan.com/api/explorer/base/address/0x1273141a3f7923AA2d9edDfA402440cE075ed8Ff)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/base/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x2CCA...)](https://layerzeroscan.com/api/explorer/base/address/0x2CCA08ae69E0C44b18a57Ab2A87644234dAebaE4)
[LZ Dead DVN
(0x6498...)](https://layerzeroscan.com/api/explorer/base/address/0x6498b0632f3834D7647367334838111c8C889703) |  |
| [### Base Sepolia Testnet](#base-sepolia) | 84532 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40245 | [SendUln302
(0xC186...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0xC1868e054425D378095A003EcbA3823a5D0135C9)
[ReceiveUln302
(0x1252...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d)
[ReadLib1002
(0x2927...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x29270F0CFC54432181C853Cd25E2Fb60A68E03f2)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x8A3D...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x8A3D588D9f6AC041476b094f97FF94ec30169d3D)
[LZ Dead DVN
(0x7855...)](https://layerzeroscan.com/api/explorer/base-sepolia/address/0x78551ADC2553EF1858a558F5300F7018Aad2FA7e) |  |
| [### Beam Testnet](#beam-testnet) | 13337 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/beam-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40178 | [SendUln302
(0x6f3a...)](https://layerzeroscan.com/api/explorer/beam-testnet/address/0x6f3a314C1279148E53f51AF154817C3EF2C827B1)
[ReceiveUln302
(0x0F7D...)](https://layerzeroscan.com/api/explorer/beam-testnet/address/0x0F7De6155DDC16A96c0d110A488bc966Aad3991b)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/beam-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xA60A...)](https://layerzeroscan.com/api/explorer/beam-testnet/address/0xA60A7a9D9723d6Adda826f5bDae29c6038B68DD3) |  |
| [### Bepolia Testnet](#bepolia-testnet)
Recently Added | 80069 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40371 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/bepolia-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) | |
| [### Berachain Bartio Testnet](#bartio) | 80084 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/bartio/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40291 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bartio/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bartio/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/bartio/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/bartio/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Berachain Mainnet](#bera)
Recently Added | 80094 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/bera/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30362 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/bera/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bera/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/bera/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/bera/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/bera/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Bevm Mainnet](#bevm) | 11501 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/bevm/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30317 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bevm/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/bevm/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/bevm/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/bevm/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0xce83...)](https://layerzeroscan.com/api/explorer/bevm/address/0xce8358bc28dd8296Ce8cAF1CD2b44787abd65887) |  |
| [### Bevm Testnet](#bevm-testnet) | 11503 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40324 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/bevm-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Bitlayer Mainnet](#bitlayer) | 200901 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30314 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/bitlayer/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Bitlayer Testnet](#bitlayer-testnet) | 200810 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40320 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/bitlayer-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Blast Mainnet](#blast) | 81457 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/blast/address/0x1a44076050125825900e736c501f859c50fE728c) | 30243 | [SendUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/blast/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[ReceiveUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/blast/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/blast/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/blast/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x73Dd...)](https://layerzeroscan.com/api/explorer/blast/address/0x73Dd395E80A2dD6D88dB11E69f15d534D182F019) |  |
| [### Blast Testnet](#blast-testnet) | 168587773 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/blast-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40243 | [SendUln302
(0x701f...)](https://layerzeroscan.com/api/explorer/blast-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[ReceiveUln302
(0x9dB9...)](https://layerzeroscan.com/api/explorer/blast-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/blast-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xE62d...)](https://layerzeroscan.com/api/explorer/blast-testnet/address/0xE62d066e71fcA410eD48ad2f2A5A860443C04035) |  |
| [### Ble Testnet](#ble-testnet) | 52085143 | [EndpointV2
(0x6Ac7...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0x6Ac7bdc07A0583A362F1497252872AE6c0A5F5B8) | 40330 | [SendUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[ReceiveUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[BlockedMessageLib
(0xa97f...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0xa97f783e717567ab8d0fc72110714f4fa7967373)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93)
[LZ Dead DVN
(0x55c1...)](https://layerzeroscan.com/api/explorer/ble-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Botanix Testnet](#botanix-testnet) | 3636 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/botanix-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40281 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/botanix-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/botanix-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/botanix-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Bouncebit Mainnet](#bouncebit) | 6001 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0x1a44076050125825900e736c501f859c50fE728c) | 30293 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x1F7E...)](https://layerzeroscan.com/api/explorer/bouncebit/address/0x1F7E674143031e74bc48a0c570c174A07aA9C5d0) |  |
| [### Bouncebit Testnet](#bouncebit-testnet) | 6000 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/bouncebit-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40289 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bouncebit-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bouncebit-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/bouncebit-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/bouncebit-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Camp Testnet](#camp-testnet) | 325000 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/camp-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40295 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/camp-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/camp-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/camp-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/camp-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Canto Mainnet](#canto) | 7700 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/canto/address/0x1a44076050125825900e736c501f859c50fE728c) | 30159 | [SendUln302
(0x61Ab...)](https://layerzeroscan.com/api/explorer/canto/address/0x61Ab01Ce58D1dFf3562bb25870020d555e39D849)
[ReceiveUln302
(0x6BD7...)](https://layerzeroscan.com/api/explorer/canto/address/0x6BD792911F4B3714E88FbDf32B351632e7d22c70)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/canto/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x8E72...)](https://layerzeroscan.com/api/explorer/canto/address/0x8E721E1930B4559AcAfDf06eE591af2FFCB93b8D)
[LZ Dead DVN
(0x8B84...)](https://layerzeroscan.com/api/explorer/canto/address/0x8B84482bd8BdD718DEa9b791eA76997EBb4F2D0E) |  |
| [### Canto Testnet](#canto-testnet) | 7701 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/canto-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40159 | [SendUln302
(0x5Bb7...)](https://layerzeroscan.com/api/explorer/canto-testnet/address/0x5Bb7F2FFF085f0066430Be92541Db302B9F1e6Af)
[ReceiveUln302
(0x5c68...)](https://layerzeroscan.com/api/explorer/canto-testnet/address/0x5c68f65B7156cdDC79C1C6f32b3073eB8BBe6e58)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/canto-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xcA01...)](https://layerzeroscan.com/api/explorer/canto-testnet/address/0xcA01DAa8e559Cb6a810ce7906eC2AeA39BDeccE4) |  |
| [### Celo Alfajores Testnet](#alfajores) | 44787 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/alfajores/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40125 | [SendUln302
(0x0043...)](https://layerzeroscan.com/api/explorer/alfajores/address/0x00432463F40E100F6A99fA2E60B09F0182D828DE)
[ReceiveUln302
(0xdb5A...)](https://layerzeroscan.com/api/explorer/alfajores/address/0xdb5A808eF72Aa3224D9fA6c15B717E8029B89a4f)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/alfajores/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x5468...)](https://layerzeroscan.com/api/explorer/alfajores/address/0x5468b60ed00F9b389B5Ba660189862Db058D7dC8) |  |
| [### Celo Mainnet](#celo) | 42220 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/celo/address/0x1a44076050125825900e736c501f859c50fE728c) | 30125 | [SendUln302
(0x42b4...)](https://layerzeroscan.com/api/explorer/celo/address/0x42b4E9C6495B4cFDaE024B1eC32E09F28027620e)
[ReceiveUln302
(0xaDDe...)](https://layerzeroscan.com/api/explorer/celo/address/0xaDDed4478B423d991C21E525Cd3638FBce1AaD17)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/celo/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1dDb...)](https://layerzeroscan.com/api/explorer/celo/address/0x1dDbaF8b75F2291A97C22428afEf411b7bB19e28)
[LZ Dead DVN
(0xc67f...)](https://layerzeroscan.com/api/explorer/celo/address/0xc67f8f84d00A4908581B235F1Abe0FE3aFC8126F) |  |
| [### Citrea Testnet](#citrea-testnet) | 5115 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40344 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/citrea-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Codex Mainnet](#codex) | 81224 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/codex/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30323 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/codex/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/codex/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/codex/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/codex/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0xce83...)](https://layerzeroscan.com/api/explorer/codex/address/0xce8358bc28dd8296Ce8cAF1CD2b44787abd65887) |  |
| [### Concrete](#concrete)
Recently Added | 12739 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/concrete/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30366 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/concrete/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/concrete/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/concrete/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/concrete/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/concrete/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) | |
| [### Conflux Testnet](#conflux-testnet) | 71 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/conflux-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40211 | [SendUln302
(0x9325...)](https://layerzeroscan.com/api/explorer/conflux-testnet/address/0x9325bE62062a8844839C0fF9cbb0bA97b2d9EAF9)
[ReceiveUln302
(0x9971...)](https://layerzeroscan.com/api/explorer/conflux-testnet/address/0x99710d5cd4650A0E6b34438d0bD860F5A426EFd6)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/conflux-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xE699...)](https://layerzeroscan.com/api/explorer/conflux-testnet/address/0xE699078689c771383C8e262DCFeE520c9171ED53) |  |
| [### Conflux eSpace Mainnet](#conflux) | 1030 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/conflux/address/0x1a44076050125825900e736c501f859c50fE728c) | 30212 | [SendUln302
(0xb360...)](https://layerzeroscan.com/api/explorer/conflux/address/0xb360A579Dc6f77d6a3E8710A9d983811129C428d)
[ReceiveUln302
(0x16Cc...)](https://layerzeroscan.com/api/explorer/conflux/address/0x16Cc4EF7c128d7FEa96Cf46FFD9dD20f76170347)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/conflux/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x07Dd...)](https://layerzeroscan.com/api/explorer/conflux/address/0x07Dd1bf9F684D81f59B6a6760438d383ad755355)
[LZ Dead DVN
(0x3E76...)](https://layerzeroscan.com/api/explorer/conflux/address/0x3E7647e24553d486eD1B1Db94B86C7677eA9aB65) |  |
| [### CoreDAO Mainnet](#coredao) | 1116 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/coredao/address/0x1a44076050125825900e736c501f859c50fE728c) | 30153 | [SendUln302
(0x0BcA...)](https://layerzeroscan.com/api/explorer/coredao/address/0x0BcAC336466ef7F1e0b5c184aAB2867C108331aF)
[ReceiveUln302
(0x8F76...)](https://layerzeroscan.com/api/explorer/coredao/address/0x8F76bAcC52b5730c1f1A2413B8936D4df12aF4f6)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/coredao/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1785...)](https://layerzeroscan.com/api/explorer/coredao/address/0x1785c94d31E3E3Ab1079e7ca8a9fbDf33EEf9dd5)
[LZ Dead DVN
(0xB872...)](https://layerzeroscan.com/api/explorer/coredao/address/0xB872d80dD876FB59085872fB99b1aDE3dbef5390) |  |
| [### CoreDAO Testnet](#coredao-testnet) | 1115 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/coredao-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40153 | [SendUln302
(0xc836...)](https://layerzeroscan.com/api/explorer/coredao-testnet/address/0xc8361Fac616435eB86B9F6e2faaff38F38B0d68C)
[ReceiveUln302
(0xD1bb...)](https://layerzeroscan.com/api/explorer/coredao-testnet/address/0xD1bbdB62826eDdE4934Ff3A4920eB053ac9D5569)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/coredao-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x3Bdb...)](https://layerzeroscan.com/api/explorer/coredao-testnet/address/0x3Bdb89Df44e50748fAed8cf851eB25bf95f37d19) |  |
| [### Corn Mainnet](#mp1) | 21000000 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/mp1/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30331 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/mp1/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/mp1/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/mp1/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/mp1/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/mp1/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Corn Testnet](#mp1-testnet) | 21000001 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40345 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/mp1-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Cronos EVM Mainnet](#cronosevm) | 25 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30359 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0x4b80...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0x4b80F7d25c451D204b1C93D9bdf2aB3B04f3EA4a)
[LZ Dead DVN
(0xc097...)](https://layerzeroscan.com/api/explorer/cronosevm/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f) |  |
| [### Cronos zkEVM Mainnet](#cronoszkevm)
Recently Added | 388 | [EndpointV2
(0x5c6c...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x5c6cfF4b7C49805F8295Ff73C204ac83f3bC4AE7) | 30360 | [SendUln302
(0x0104...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x01047601DB5E63b1574aae317BAd9C684E3C9056)
[ReceiveUln302
(0x9AB6...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x9AB633555E460C01f8c7b8ab24C88dD4986dD5A1)
[BlockedMessageLib
(0x3258...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x3258287147fb7887d8a643006e26e19368057377)
[LZ Executor
(0x5533...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x553313dB58dEeFa3D55B1457D27EAB3Fe5EC87E8)
[LZ Dead DVN
(0x0483...)](https://layerzeroscan.com/api/explorer/cronoszkevm/address/0x04830f6deCF08Dec9eD6C3fCAD215245B78A59e1) |  |
| [### Curtis Testnet](#curtis-testnet) | 33111 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40306 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/curtis-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Cyber Mainnet](#cyber) | 7560 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/cyber/address/0x1a44076050125825900e736c501f859c50fE728c) | 30283 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/cyber/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/cyber/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/cyber/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/cyber/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0x9c8D...)](https://layerzeroscan.com/api/explorer/cyber/address/0x9c8D8A224545c15024cB50C7c02cf3EA9AA1bF36) |  |
| [### Cyber Testnet](#cyber-testnet) | 111557560 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/cyber-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40280 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/cyber-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/cyber-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/cyber-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/cyber-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### DFK Chain](#dfk) | 53935 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/dfk/address/0x1a44076050125825900e736c501f859c50fE728c) | 30115 | [SendUln302
(0xc802...)](https://layerzeroscan.com/api/explorer/dfk/address/0xc80233AD8251E668BecbC3B0415707fC7075501e)
[ReceiveUln302
(0x556d...)](https://layerzeroscan.com/api/explorer/dfk/address/0x556d7664d5b4Db11f381c714B6b47A8Bf0b494FD)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/dfk/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1a7C...)](https://layerzeroscan.com/api/explorer/dfk/address/0x1a7CE89220b945e82f80380B14aA6FDC5E5e3B2A)
[LZ Dead DVN
(0x4caC...)](https://layerzeroscan.com/api/explorer/dfk/address/0x4caC2E674d1c3C4548a00fbeCBBa713C902579cf) |  |
| [### DFK Chain Testnet](#dfk-testnet) | 335 | [EndpointV2
(0x94FF...)](https://layerzeroscan.com/api/explorer/dfk-testnet/address/0x94FF3a4d9E9792dc59193ff753B5038A14c59570) | 40115 | [SendUln302
(0xd453...)](https://layerzeroscan.com/api/explorer/dfk-testnet/address/0xd45316d099dC4f3B15f2462888D62D919bc07a61)
[ReceiveUln302
(0x5709...)](https://layerzeroscan.com/api/explorer/dfk-testnet/address/0x5709988a03d1CC02197F222D2C72CcC6018bCE0B)
[BlockedMessageLib
(0xbddc...)](https://layerzeroscan.com/api/explorer/dfk-testnet/address/0xbddcf3fa3c748d54ba7f75f3006342ee98953399)
[LZ Executor
(0x1b36...)](https://layerzeroscan.com/api/explorer/dfk-testnet/address/0x1b3649C2C06F1fb0d3e57FB001c8B592f5E3CAc6) |  |
| [### DM2 Verse Mainnet](#dm2verse) | 68770 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30315 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/dm2verse/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### DOS Chain Mainnet](#dos) | 7979 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/dos/address/0x1a44076050125825900e736c501f859c50fE728c) | 30149 | [SendUln302
(0x72C9...)](https://layerzeroscan.com/api/explorer/dos/address/0x72C91c46d7033dfF1707091Ef32D4951a73bD099)
[ReceiveUln302
(0xEF77...)](https://layerzeroscan.com/api/explorer/dos/address/0xEF7781FC1C4F7B2Fd3Cf03f4d65b6835b27C1A0d)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/dos/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x5B23...)](https://layerzeroscan.com/api/explorer/dos/address/0x5B23E2bAe5C5f00e804EA2C4C9abe601604378fa)
[LZ Dead DVN
(0x4474...)](https://layerzeroscan.com/api/explorer/dos/address/0x4474B891BF3D93e61676912F0739e04B86232dd5) |  |
| [### DOS Testnet](#dos-testnet) | 3939 | [EndpointV2
(0x0841...)](https://layerzeroscan.com/api/explorer/dos-testnet/address/0x08416c0eAa8ba93F907eC8D6a9cAb24821C53E64) | 40286 | [SendUln302
(0xa805...)](https://layerzeroscan.com/api/explorer/dos-testnet/address/0xa805000DcA12b38690558785878642BA19Bc4981)
[ReceiveUln302
(0x00D0...)](https://layerzeroscan.com/api/explorer/dos-testnet/address/0x00D0cd55beAfb96f0A5c37452f56D06DA3765ce8)
[BlockedMessageLib
(0x5f67...)](https://layerzeroscan.com/api/explorer/dos-testnet/address/0x5f670a51065fbcd8cad924ff64b3ad032ce51c58)
[LZ Executor
(0x06f0...)](https://layerzeroscan.com/api/explorer/dos-testnet/address/0x06f021541521Ae6dcfaeED4EC9A8bF800528E805) |  |
| [### Degen Mainnet](#degen) | 666666666 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/degen/address/0x1a44076050125825900e736c501f859c50fE728c) | 30267 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/degen/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/degen/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/degen/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/degen/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0xf80c...)](https://layerzeroscan.com/api/explorer/degen/address/0xf80cB5F7467B67cBEC77DcE6a13C89f210b554c0) |  |
| [### Dexalot Subnet Mainnet](#dexalot) | 432204 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/dexalot/address/0x1a44076050125825900e736c501f859c50fE728c) | 30118 | [SendUln302
(0x439C...)](https://layerzeroscan.com/api/explorer/dexalot/address/0x439C059878fA7A747ead101e2e20A65AcA01C7A8)
[ReceiveUln302
(0xe01F...)](https://layerzeroscan.com/api/explorer/dexalot/address/0xe01F3c1CD14F39303D175c31c16f58707B28976b)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/dexalot/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcbD3...)](https://layerzeroscan.com/api/explorer/dexalot/address/0xcbD35a9b849342AD34a71e072D9947D4AFb4E164)
[LZ Dead DVN
(0x9291...)](https://layerzeroscan.com/api/explorer/dexalot/address/0x92918f4AD410517B635a8961A64e77bDF8798dDC) |  |
| [### Dexalot Testnet](#dexalot-testnet) | 432201 | [EndpointV2
(0x7288...)](https://layerzeroscan.com/api/explorer/dexalot-testnet/address/0x72884B17f92a863fD056Ec3695Bd3484D601f39a) | 40118 | [SendUln302
(0x4B68...)](https://layerzeroscan.com/api/explorer/dexalot-testnet/address/0x4B68C45f6A276485870D56f1699DCf451FEC076F)
[ReceiveUln302
(0x3De7...)](https://layerzeroscan.com/api/explorer/dexalot-testnet/address/0x3De74963B7223343ffD168e230fC4e374282d37b)
[BlockedMessageLib
(0xcda3...)](https://layerzeroscan.com/api/explorer/dexalot-testnet/address/0xcda3dcef65eb98db22be03f9d84ce747bc120bd1)
[LZ Executor
(0x13EA...)](https://layerzeroscan.com/api/explorer/dexalot-testnet/address/0x13EA72039D7f02848CDDd67a2F948dd334cDE70e) |  |
| [### Dm2verse Testnet](#dm2verse-testnet) | 68775 | [EndpointV2
(0x3aCA...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0x3aCAAf60502791D199a5a5F0B173D78229eBFe32) | 40321 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0xa229...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0xa229b65cc2191bf60bc24efcda3487d7b5c0c9f0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/dm2verse-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### EDU Chain Mainnet](#edu) | 41923 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/edu/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30328 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/edu/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/edu/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/edu/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0x41Bd...)](https://layerzeroscan.com/api/explorer/edu/address/0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d)
[LZ Dead DVN
(0xce83...)](https://layerzeroscan.com/api/explorer/edu/address/0xce8358bc28dd8296Ce8cAF1CD2b44787abd65887) |  |
| [### EVM on Flow Mainnet](#flow) | 747 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/flow/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30336 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/flow/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/flow/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/flow/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/flow/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/flow/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### EVM on Flow Testnet](#flow-testnet)
Recently Added | 545 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40351 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/flow-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Ethereum Holesky Testnet](#holesky-testnet) | 17000 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/holesky-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40217 | [SendUln302
(0x21F3...)](https://layerzeroscan.com/api/explorer/holesky-testnet/address/0x21F33EcF7F65D61f77e554B4B4380829908cD076)
[ReceiveUln302
(0xbAe5...)](https://layerzeroscan.com/api/explorer/holesky-testnet/address/0xbAe52D605770aD2f0D17533ce56D146c7C964A0d)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/holesky-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xBc0C...)](https://layerzeroscan.com/api/explorer/holesky-testnet/address/0xBc0C24E6f24eC2F1fd7E859B8322A1277F80aaD5) |  |
| [### Ethereum Mainnet](#ethereum) | 1 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/ethereum/address/0x1a44076050125825900e736c501f859c50fE728c) | 30101 | [SendUln302
(0xbB2E...)](https://layerzeroscan.com/api/explorer/ethereum/address/0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1)
[ReceiveUln302
(0xc02A...)](https://layerzeroscan.com/api/explorer/ethereum/address/0xc02Ab410f0734EFa3F14628780e6e695156024C2)
[ReadLib1002
(0x74F5...)](https://layerzeroscan.com/api/explorer/ethereum/address/0x74F55Bc2a79A27A0bF1D1A35dB5d0Fc36b9FDB9D)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/ethereum/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1732...)](https://layerzeroscan.com/api/explorer/ethereum/address/0x173272739Bd7Aa6e4e214714048a9fE699453059)
[LZ Dead DVN
(0x747C...)](https://layerzeroscan.com/api/explorer/ethereum/address/0x747C741496a507E4B404b50463e691A8d692f6Ac) |  |
| [### Ethereum Sepolia Testnet](#sepolia) | 11155111 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40161 | [SendUln302
(0xcc1a...)](https://layerzeroscan.com/api/explorer/sepolia/address/0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE)
[ReceiveUln302
(0xdAf0...)](https://layerzeroscan.com/api/explorer/sepolia/address/0xdAf00F5eE2158dD58E0d3857851c432E34A3A851)
[ReadLib1002
(0x908E...)](https://layerzeroscan.com/api/explorer/sepolia/address/0x908E86e9cb3F16CC94AE7569Bf64Ce2CE04bbcBE)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x718B...)](https://layerzeroscan.com/api/explorer/sepolia/address/0x718B92b5CB0a5552039B593faF724D182A881eDA)
[LZ Dead DVN
(0x8b45...)](https://layerzeroscan.com/api/explorer/sepolia/address/0x8b450b0acF56E1B0e25C581bB04FBAbeeb0644b8) |  |
| caution
LayerZero Testnet Endpoints use the real Mainnet pricefeed for cross-chain transfers. That means Ethereum Sepolia uses the real Ethereum gwei price. For testing EVM <> EVM transfers, it may be cheaper to use another EVM testnet with a cheaper cost of blockspace. | | | | | |
| [### Etherlink Mainnet](#etherlink) | 42793 | [EndpointV2
(0xAaB5...)](https://layerzeroscan.com/api/explorer/etherlink/address/0xAaB5A48CFC03Efa9cC34A2C1aAcCCB84b4b770e4) | 30292 | [SendUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/etherlink/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[ReceiveUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/etherlink/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[BlockedMessageLib
(0x3adb...)](https://layerzeroscan.com/api/explorer/etherlink/address/0x3adb8d9c040fae1fbac9b579799cd4ca8c768f8a)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/etherlink/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x6F95...)](https://layerzeroscan.com/api/explorer/etherlink/address/0x6F95f0e1903BDb57b0761c8EfE9BC3bfB7E416BB) |  |
| [### Etherlink Testnet](#etherlink-testnet) | 128123 | [EndpointV2
(0xec28...)](https://layerzeroscan.com/api/explorer/etherlink-testnet/address/0xec28645346D781674B4272706D8a938dB2BAA2C6) | 40239 | [SendUln302
(0xE62d...)](https://layerzeroscan.com/api/explorer/etherlink-testnet/address/0xE62d066e71fcA410eD48ad2f2A5A860443C04035)
[ReceiveUln302
(0x2072...)](https://layerzeroscan.com/api/explorer/etherlink-testnet/address/0x2072a32Df77bAE5713853d666f26bA5e47E54717)
[BlockedMessageLib
(0xb5a6...)](https://layerzeroscan.com/api/explorer/etherlink-testnet/address/0xb5a6567364189010e432c457d5332edae0bb5730)
[LZ Executor
(0x417c...)](https://layerzeroscan.com/api/explorer/etherlink-testnet/address/0x417cb9E12cfe7301c8b6ef8f63ffac55263e147C) |  |
| [### Fantom Mainnet](#fantom) | 250 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/fantom/address/0x1a44076050125825900e736c501f859c50fE728c) | 30112 | [SendUln302
(0xC17B...)](https://layerzeroscan.com/api/explorer/fantom/address/0xC17BaBeF02a937093363220b0FB57De04A535D5E)
[ReceiveUln302
(0xe1Dd...)](https://layerzeroscan.com/api/explorer/fantom/address/0xe1Dd69A2D08dF4eA6a30a91cC061ac70F98aAbe3)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/fantom/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x2957...)](https://layerzeroscan.com/api/explorer/fantom/address/0x2957eBc0D2931270d4a539696514b047756b3056)
[LZ Dead DVN
(0xdD8D...)](https://layerzeroscan.com/api/explorer/fantom/address/0xdD8D6cc54Fdb9Ec81Cb8EFb8988ee17aBB8Eecd1) |  |
| [### Fantom Testnet](#fantom-testnet) | 4002 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/fantom-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40112 | [SendUln302
(0x3f41...)](https://layerzeroscan.com/api/explorer/fantom-testnet/address/0x3f41017De79aA979b8f33E2e9518203888458273)
[ReceiveUln302
(0xe4a4...)](https://layerzeroscan.com/api/explorer/fantom-testnet/address/0xe4a446690Dfaf438EEA2b06394E1fdd0A9435178)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/fantom-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x0453...)](https://layerzeroscan.com/api/explorer/fantom-testnet/address/0x0453b4730BB550363F726aD8eeC9441e763F2835) |  |
| [### Fi Sepolia Testnet](#fi-testnet) | 18026 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40301 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/fi-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Flare Mainnet](#flare) | 14 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/flare/address/0x1a44076050125825900e736c501f859c50fE728c) | 30295 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/flare/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/flare/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/flare/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/flare/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x28B6...)](https://layerzeroscan.com/api/explorer/flare/address/0x28B6140ead70cb2Fb669705b3598ffB4BEaA060b) |  |
| [### Flare Testnet](#flare-testnet) | 114 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/flare-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40294 | [SendUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/flare-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[ReceiveUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/flare-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/flare-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/flare-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014) |  |
| [### Form Testnet](#form-testnet) | 478 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/form-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40270 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/form-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/form-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/form-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Fraxtal Mainnet](#fraxtal) | 252 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x1a44076050125825900e736c501f859c50fE728c) | 30255 | [SendUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[ReceiveUln302
(0x8bC1...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x8bC1e36F015b9902B54b1387A4d733cebc2f5A4e)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x41Bd...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d)
[LZ Dead DVN
(0x6A69...)](https://layerzeroscan.com/api/explorer/fraxtal/address/0x6A6991E0bF27E3CcCDe6B73dE94b7DA6e240FF6E) |  |
| [### Fraxtal Testnet](#fraxtal-testnet) | 2522 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/fraxtal-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40255 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/fraxtal-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/fraxtal-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/fraxtal-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/fraxtal-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Fuse Mainnet](#fuse) | 122 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/fuse/address/0x1a44076050125825900e736c501f859c50fE728c) | 30138 | [SendUln302
(0x2762...)](https://layerzeroscan.com/api/explorer/fuse/address/0x2762409Baa1804D94D8c0bCFF8400B78Bf915D5B)
[ReceiveUln302
(0xB125...)](https://layerzeroscan.com/api/explorer/fuse/address/0xB12514e226E50844E4655696c92c0c36B8A53141)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/fuse/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc905...)](https://layerzeroscan.com/api/explorer/fuse/address/0xc905E74BEb8229E258c3C6E5bC0D6Cc54C534688)
[LZ Dead DVN
(0xFB01...)](https://layerzeroscan.com/api/explorer/fuse/address/0xFB01E486d8B2556a70Fe66E4A86d76DEAb4Ba974) |  |
| [### Fusespark Testnet](#fusespark) | 123 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/fusespark/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40138 | [SendUln302
(0x098F...)](https://layerzeroscan.com/api/explorer/fusespark/address/0x098Fed01ABd66C63e706Ed9b368726DE54FefBEb)
[ReceiveUln302
(0x253E...)](https://layerzeroscan.com/api/explorer/fusespark/address/0x253E37074D299b70d11F72eF547cc2EF59fD7f9C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/fusespark/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x86d0...)](https://layerzeroscan.com/api/explorer/fusespark/address/0x86d08462EaA1559345d7F41f937B2C804209DB8A) |  |
| [### Gameswift Testnet](#gameswift-testnet) | 10888 | [EndpointV2
(0x6Ac7...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0x6Ac7bdc07A0583A362F1497252872AE6c0A5F5B8) | 40339 | [SendUln302
(0xB048...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0xB0487596a0B62D1A71D0C33294bd6eB635Fc6B09)
[ReceiveUln302
(0x073f...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0x073f5b4FdF17BBC16b0980d49f6C56123477bb51)
[ReadLib1002
(0xcF1B...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0xa97f...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0xa97f783e717567ab8d0fc72110714f4fa7967373)
[LZ Executor
(0x8dF5...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0x8dF53a660a00C3D977d7E778fB7385ECf4482D16)
[LZ Dead DVN
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/gameswift-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### Glue Mainnet](#glue) | 1300 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/glue/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30342 | [SendUln302
(0x15e5...)](https://layerzeroscan.com/api/explorer/glue/address/0x15e51701F245F6D5bd0FEE87bCAf55B0841451B3)
[ReceiveUln302
(0xfd76...)](https://layerzeroscan.com/api/explorer/glue/address/0xfd76d9CB0Bac839725aB79127E7411fe71b1e3CA)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/glue/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/glue/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0xc097...)](https://layerzeroscan.com/api/explorer/glue/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f) |  |
| [### Glue Testnet](#glue-testnet) | 1300 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/glue-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40296 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/glue-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/glue-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/glue-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/glue-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Gnosis Chiado Testnet](#chiado) | 10200 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/chiado/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40145 | [SendUln302
(0xddF3...)](https://layerzeroscan.com/api/explorer/chiado/address/0xddF3266fEAa899ACcf805F4379E5137144cb0A7D)
[ReceiveUln302
(0xC228...)](https://layerzeroscan.com/api/explorer/chiado/address/0xC22825d9982365d31E63CC3b5589B17067e795b1)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/chiado/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xe382...)](https://layerzeroscan.com/api/explorer/chiado/address/0xe3826C822a53a736cC4d8f6FD884a6E3A461d29F) |  |
| [### Gnosis Mainnet](#gnosis) | 100 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x1a44076050125825900e736c501f859c50fE728c) | 30145 | [SendUln302
(0x3C15...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x3C156b1f625D2B4E004D43E91aC2c3a719C29c7B)
[ReceiveUln302
(0x9714...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x9714Ccf1dedeF14BaB5013625DB92746C1358cb4)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x3834...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x38340337f9ADF5D76029Ab3A667d34E5a032F7BA)
[LZ Dead DVN
(0x8a89...)](https://layerzeroscan.com/api/explorer/gnosis/address/0x8a893567f27893e6E0c7b6bba8769d9ab3E911Ff) |  |
| [### Goat Mainnet](#goat)
Recently Added | 2345 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/goat/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30361 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/goat/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/goat/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/goat/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/goat/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/goat/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Goat Testnet](#goat-testnet)
Recently Added | 48816 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40356 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/goat-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Gravity Mainnet](#gravity) | 1625 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/gravity/address/0x1a44076050125825900e736c501f859c50fE728c) | 30294 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/gravity/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/gravity/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/gravity/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/gravity/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0xc70A...)](https://layerzeroscan.com/api/explorer/gravity/address/0xc70AB6f32772f59fBfc23889Caf4Ba3376C84bAf) |  |
| [### Gunzilla Testnet](#gunzilla-testnet) | 49321 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/gunzilla-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40236 | [SendUln302
(0x82b7...)](https://layerzeroscan.com/api/explorer/gunzilla-testnet/address/0x82b7dc04A4ABCF2b4aE570F317dcab49f5a10f24)
[ReceiveUln302
(0x3062...)](https://layerzeroscan.com/api/explorer/gunzilla-testnet/address/0x306202702AF38152D3604cD82af71C3db0eE08CF)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/gunzilla-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9554...)](https://layerzeroscan.com/api/explorer/gunzilla-testnet/address/0x955412C07d9bC1027eb4d481621ee063bFd9f4C6) |  |
| [### Harmony Mainnet](#harmony) | 1666600000 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/harmony/address/0x1a44076050125825900e736c501f859c50fE728c) | 30116 | [SendUln302
(0x795F...)](https://layerzeroscan.com/api/explorer/harmony/address/0x795F8325aF292Ff6E58249361d1954893BE15Aff)
[ReceiveUln302
(0x177d...)](https://layerzeroscan.com/api/explorer/harmony/address/0x177d36dBE2271A4DdB2Ad8304d82628eb921d790)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/harmony/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xd27B...)](https://layerzeroscan.com/api/explorer/harmony/address/0xd27B2Fe1d0a60E06A0ec7e64501d2f15e6c65Bd9)
[LZ Dead DVN
(0x801B...)](https://layerzeroscan.com/api/explorer/harmony/address/0x801BfD947905C337d552F8E30cb4E80435771674) |  |
| [### Hedera Mainnet](#hedera) | 295 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/hedera/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30316 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/hedera/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/hedera/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/hedera/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/hedera/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0xc097...)](https://layerzeroscan.com/api/explorer/hedera/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f) |  |
| [### Hedera Testnet](#hedera-testnet) | 296 | [EndpointV2
(0xbD67...)](https://layerzeroscan.com/api/explorer/hedera-testnet/address/0xbD672D1562Dd32C23B563C989d8140122483631d) | 40285 | [SendUln302
(0x1707...)](https://layerzeroscan.com/api/explorer/hedera-testnet/address/0x1707575f7cecdc0ad53fde9ba9bda3ed5d4440f4)
[ReceiveUln302
(0xc0c3...)](https://layerzeroscan.com/api/explorer/hedera-testnet/address/0xc0c34919A04d69415EF2637A3Db5D637a7126cd0)
[BlockedMessageLib
(0xeee3...)](https://layerzeroscan.com/api/explorer/hedera-testnet/address/0xeee3044f72093d25f70dc6c684fbb0c81dde62d1)
[LZ Executor
(0xe514...)](https://layerzeroscan.com/api/explorer/hedera-testnet/address/0xe514D331c54d7339108045bF4794F8d71cad110e) |  |
| caution
The Hedera EVM has 8 decimals while their JSON RPC uses 18 decimals for \`msg.value\`, please take precaution when calling \`quoteFee\` | | | | | |
| [### Hemi Mainnet](#hemi) | 43111 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/hemi/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30329 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/hemi/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/hemi/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/hemi/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/hemi/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/hemi/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Hemi Testnet](#hemi-testnet) | 743111 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40338 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/hemi-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Homeverse Mainnet](#homeverse) | 19011 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/homeverse/address/0x1a44076050125825900e736c501f859c50fE728c) | 30265 | [SendUln302
(0x9802...)](https://layerzeroscan.com/api/explorer/homeverse/address/0x980205D352F198748B626f6f7C38A8a5663Ec981)
[ReceiveUln302
(0xFe7C...)](https://layerzeroscan.com/api/explorer/homeverse/address/0xFe7C30860D01e28371D40434806F4A8fcDD3A098)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/homeverse/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/homeverse/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0x25dC...)](https://layerzeroscan.com/api/explorer/homeverse/address/0x25dCD7AdC3Ab4c00b8bcf78F33d95A19211Eab48) |  |
| caution
The Homeverse Endpoint uses an alternative ERC20 token instead of the native gas token for omnichain fees. You will need to modify your \_payNative() function in OApp to handle ERC20 fees (see OFTAlt). | | | | | |
| [### Homeverse Testnet](#homeverse-testnet) | 40875 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/homeverse-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40265 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/homeverse-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/homeverse-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/homeverse-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/homeverse-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| caution
The Homeverse Endpoint uses an alternative ERC20 token instead of the native gas token for omnichain fees. You will need to modify your \_payNative() function in OApp to handle ERC20 fees (see OFTAlt). | | | | | |
| [### Horizen EON Mainnet](#eon) | 7332 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/eon/address/0x1a44076050125825900e736c501f859c50fE728c) | 30215 | [SendUln302
(0x5EB6...)](https://layerzeroscan.com/api/explorer/eon/address/0x5EB6b3Db915d29fc624b8a0e42AC029e36a1D86B)
[ReceiveUln302
(0xF622...)](https://layerzeroscan.com/api/explorer/eon/address/0xF622DFb40bf7340DBCf1e5147D6CFD95d7c5cF1F)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/eon/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xA09d...)](https://layerzeroscan.com/api/explorer/eon/address/0xA09dB5142654e3eB5Cf547D66833FAe7097B21C3)
[LZ Dead DVN
(0xf942...)](https://layerzeroscan.com/api/explorer/eon/address/0xf9420F9D2552640e242Ad89CD5D3b625F92705C9) |  |
| [### Hubble Mainnet](#hubble) | 1992 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/hubble/address/0x1a44076050125825900e736c501f859c50fE728c) | 30182 | [SendUln302
(0xBB96...)](https://layerzeroscan.com/api/explorer/hubble/address/0xBB967E3A329F4c47F654B82a2F7d11E69E5A7143)
[ReceiveUln302
(0x6f16...)](https://layerzeroscan.com/api/explorer/hubble/address/0x6f1686189f32e78f1D83e7c6Ed433FCeBc3A5B51)
[LZ Executor
(0xe9AE...)](https://layerzeroscan.com/api/explorer/hubble/address/0xe9AE261D3aFf7d3fCCF38Fa2d612DD3897e07B2d) |  |
| [### HyperEVM Mainnet](#hyperliquid)
Recently Added | 999 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30367 | [SendUln302
(0xfd76...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0xfd76d9CB0Bac839725aB79127E7411fe71b1e3CA)
[ReceiveUln302
(0x7cac...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0x7cacBe439EaD55fa1c22790330b12835c6884a91)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0x41Bd...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d)
[LZ Dead DVN
(0xcCE4...)](https://layerzeroscan.com/api/explorer/hyperliquid/address/0xcCE466a522984415bC91338c232d98869193D46e) | |
| [### HyperEVM Testnet](#hyperliquid-testnet)
Recently Added | 998 | [EndpointV2
(0xf9e1...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0xf9e1815F151024bDE4B7C10BAC10e8Ba9F6b53E1) | 40362 | [SendUln302
(0x43E5...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0x43E505ba192aaC7BABdC1A796c87844171011684)
[ReceiveUln302
(0x012f...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0x012f6eaE2A0Bf5916f48b5F37C62Bcfb7C1ffdA1)
[BlockedMessageLib
(0x25e8...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0x25e8806a598df0c7f4c24d3e6efcde0d5ca7787d)
[LZ Executor
(0x72e3...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0x72e34F44Eb09058bdDaf1aeEebDEC062f1844b00)
[LZ Dead DVN
(0xb881...)](https://layerzeroscan.com/api/explorer/hyperliquid-testnet/address/0xb8815f3f882614048CbE201a67eF9c6F10fe5035) | |
| [### InclusiveLayer Testnet](#bl2-testnet) | 71461164656 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40331 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/bl2-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Initia Testnet](#initia-testnet) | | [EndpointV2
(0xcc4e...)](https://layerzeroscan.com/api/explorer/initia-testnet/address/0xcc4e9fda80712972deb0338d85b84822a42d5155b645ef1b2eeae42cedd41b04) | 40326 | [SendUln302
(0x3e1b...)](https://layerzeroscan.com/api/explorer/initia-testnet/address/0x3e1b182c40965a986133798e1da76302ef327de2c32c58110361587560285e88)
[ReceiveUln302
(0x3e1b...)](https://layerzeroscan.com/api/explorer/initia-testnet/address/0x3e1b182c40965a986133798e1da76302ef327de2c32c58110361587560285e88)
[LZ Executor
(0x798c...)](https://layerzeroscan.com/api/explorer/initia-testnet/address/0x798c194c8740dde76a0e3f46f444f7ee974765abb2a9db98be03a0ee89ce050c) | |
| [### Ink Mainnet](#ink) | 57073 | [EndpointV2
(0xca29...)](https://layerzeroscan.com/api/explorer/ink/address/0xca29f3A6f966Cb2fc0dE625F8f325c0C46dbE958) | 30339 | [SendUln302
(0x7611...)](https://layerzeroscan.com/api/explorer/ink/address/0x76111DE813F83AAAdBD62773Bf41247634e2319a)
[ReceiveUln302
(0x4731...)](https://layerzeroscan.com/api/explorer/ink/address/0x473132bb594caEF281c68718F4541f73FE14Dc89)
[BlockedMessageLib
(0x7968...)](https://layerzeroscan.com/api/explorer/ink/address/0x796862c4849662bfc30fe7559780923d519d3192)
[LZ Executor
(0xFEbC...)](https://layerzeroscan.com/api/explorer/ink/address/0xFEbCF17b11376C724AB5a5229803C6e838b6eAe5)
[LZ Dead DVN
(0x5ba2...)](https://layerzeroscan.com/api/explorer/ink/address/0x5ba261D2b595966A81548B4FbE3851a6dA9Cf92c) |  |
| [### Ink Testnet](#ink-testnet)
Recently Added | 763373 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40358 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/ink-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Iota Mainnet](#iota) | 8822 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/iota/address/0x1a44076050125825900e736c501f859c50fE728c) | 30284 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/iota/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/iota/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/iota/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/iota/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0xE6f1...)](https://layerzeroscan.com/api/explorer/iota/address/0xE6f1C3c1674d3Bae71ef33300441e7469a0021fF) |  |
| [### Iota Testnet](#iota-testnet) | 1075 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40307 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/iota-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Japan Open Chain Mainnet](#joc) | 81 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/joc/address/0x1a44076050125825900e736c501f859c50fE728c) | 30285 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/joc/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/joc/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/joc/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/joc/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0xcC2d...)](https://layerzeroscan.com/api/explorer/joc/address/0xcC2d3d4B88b87775Bec386d92F6951Ee7f8d52D9) |  |
| [### Japan Open Chain Testnet](#joc-testnet) | 10081 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/joc-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40242 | [SendUln302
(0x9eCf...)](https://layerzeroscan.com/api/explorer/joc-testnet/address/0x9eCf72299027e8AeFee5DC5351D6d92294F46d2b)
[ReceiveUln302
(0xB048...)](https://layerzeroscan.com/api/explorer/joc-testnet/address/0xB0487596a0B62D1A71D0C33294bd6eB635Fc6B09)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/joc-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x4dFa...)](https://layerzeroscan.com/api/explorer/joc-testnet/address/0x4dFa426aEAA55E6044d2b47682842460a04aF45c) |  |
| [### Kaia Mainnet (formerly Klaytn)](#klaytn) | 8217 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/klaytn/address/0x1a44076050125825900e736c501f859c50fE728c) | 30150 | [SendUln302
(0x9714...)](https://layerzeroscan.com/api/explorer/klaytn/address/0x9714Ccf1dedeF14BaB5013625DB92746C1358cb4)
[ReceiveUln302
(0x937A...)](https://layerzeroscan.com/api/explorer/klaytn/address/0x937AbA873827BF883CeD83CA557697427eAA46Ee)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/klaytn/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xe149...)](https://layerzeroscan.com/api/explorer/klaytn/address/0xe149187a987F129FD3d397ED04a60b0b89D1669f)
[LZ Dead DVN
(0xdc58...)](https://layerzeroscan.com/api/explorer/klaytn/address/0xdc58A279Bd69B208a4AdfdA0Aa066f76e33E2901) |  |
| [### Kaia Testnet (formerly Klaytn)](#klaytn-baobab) | 1001 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/klaytn-baobab/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40150 | [SendUln302
(0x6bd9...)](https://layerzeroscan.com/api/explorer/klaytn-baobab/address/0x6bd925aA58325fba65Ea7d4412DDB2E5D2D9427d)
[ReceiveUln302
(0xFc4e...)](https://layerzeroscan.com/api/explorer/klaytn-baobab/address/0xFc4eA96c3de3Ba60516976390fA4E945a0b8817B)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/klaytn-baobab/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xddF3...)](https://layerzeroscan.com/api/explorer/klaytn-baobab/address/0xddF3266fEAa899ACcf805F4379E5137144cb0A7D) |  |
| [### Kava Mainnet](#kava) | 2222 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/kava/address/0x1a44076050125825900e736c501f859c50fE728c) | 30177 | [SendUln302
(0x83Fb...)](https://layerzeroscan.com/api/explorer/kava/address/0x83Fb937054918cB7AccB15bd6cD9234dF9ebb357)
[ReceiveUln302
(0xb7e9...)](https://layerzeroscan.com/api/explorer/kava/address/0xb7e97ad5661134185Fe608b2A31fe8cEf2147Ba9)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/kava/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x41ED...)](https://layerzeroscan.com/api/explorer/kava/address/0x41ED8065dd9bC6c0caF21c39766eDCBA0F21851c)
[LZ Dead DVN
(0x1B3b...)](https://layerzeroscan.com/api/explorer/kava/address/0x1B3b79f03EE74d4C88f2Bdd84112b58a01EA0167) |  |
| [### Kava Testnet](#kava-testnet) | 2221 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/kava-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40172 | [SendUln302
(0x4B68...)](https://layerzeroscan.com/api/explorer/kava-testnet/address/0x4B68C45f6A276485870D56f1699DCf451FEC076F)
[ReceiveUln302
(0x3De7...)](https://layerzeroscan.com/api/explorer/kava-testnet/address/0x3De74963B7223343ffD168e230fC4e374282d37b)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/kava-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x13EA...)](https://layerzeroscan.com/api/explorer/kava-testnet/address/0x13EA72039D7f02848CDDd67a2F948dd334cDE70e) |  |
| [### Lif3 Testnet](#lif3-testnet) | 1811 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40300 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/lif3-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Lightlink Mainnet](#lightlink) | 1890 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/lightlink/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30309 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/lightlink/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/lightlink/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/lightlink/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/lightlink/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/lightlink/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Lightlink Testnet](#lightlink-testnet) | 1891 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40309 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/lightlink-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Linea Mainnet](#linea) | 59144 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/linea/address/0x1a44076050125825900e736c501f859c50fE728c) | 30183 | [SendUln302
(0x3204...)](https://layerzeroscan.com/api/explorer/linea/address/0x32042142DD551b4EbE17B6FEd53131dd4b4eEa06)
[ReceiveUln302
(0xE22E...)](https://layerzeroscan.com/api/explorer/linea/address/0xE22ED54177CE1148C557de74E4873619e6c6b205)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/linea/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x0408...)](https://layerzeroscan.com/api/explorer/linea/address/0x0408804C5dcD9796F22558464E6fE5bDdF16A7c7)
[LZ Dead DVN
(0x1b36...)](https://layerzeroscan.com/api/explorer/linea/address/0x1b368a0d7c57080a01054862114B5a42e54CBb98) |  |
| [### Linea Sepolia Testnet](#lineasep-testnet) | 59141 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/lineasep-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40287 | [SendUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/lineasep-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[ReceiveUln302
(0x9eCf...)](https://layerzeroscan.com/api/explorer/lineasep-testnet/address/0x9eCf72299027e8AeFee5DC5351D6d92294F46d2b)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/lineasep-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xe1a1...)](https://layerzeroscan.com/api/explorer/lineasep-testnet/address/0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6) |  |
| [### Lisk Mainnet](#lisk) | 1135 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/lisk/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30321 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/lisk/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/lisk/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/lisk/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/lisk/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/lisk/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Lisk Testnet](#lisk-testnet) | 4202 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40327 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/lisk-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Loot Mainnet](#loot) | 5151706 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/loot/address/0x1a44076050125825900e736c501f859c50fE728c) | 30197 | [SendUln302
(0xCFf0...)](https://layerzeroscan.com/api/explorer/loot/address/0xCFf08a35A5f27F306e2DA99ff198dB90f13DEF77)
[ReceiveUln302
(0xBB96...)](https://layerzeroscan.com/api/explorer/loot/address/0xBB967E3A329F4c47F654B82a2F7d11E69E5A7143)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/loot/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x000C...)](https://layerzeroscan.com/api/explorer/loot/address/0x000CC1A759bC3A15e664Ed5379E321Be5de1c9B6)
[LZ Dead DVN
(0x3440...)](https://layerzeroscan.com/api/explorer/loot/address/0x34406a8ef674f133B57F32083656787722aEE4dE) |  |
| [### Loot Testnet](#loot-testnet) | 9088912 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/loot-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40197 | [SendUln302
(0x6271...)](https://layerzeroscan.com/api/explorer/loot-testnet/address/0x6271e24A43cCB1509FBDC22284Ab6176237562EE)
[ReceiveUln302
(0x40d0...)](https://layerzeroscan.com/api/explorer/loot-testnet/address/0x40d0DC337feCDC4C09774e7F92Cb963674CF7Ef2)
[LZ Executor
(0x6460...)](https://layerzeroscan.com/api/explorer/loot-testnet/address/0x6460EE1b9D5bDE8375ca928767Cc63FBFA111A98) |  |
| [### Lyra Mainnet](#lyra) | 957 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/lyra/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30311 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/lyra/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/lyra/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/lyra/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/lyra/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0xce83...)](https://layerzeroscan.com/api/explorer/lyra/address/0xce8358bc28dd8296Ce8cAF1CD2b44787abd65887) |  |
| [### Lyra Testnet](#lyra-testnet) | 901 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40308 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/lyra-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Manta Pacific Mainnet](#manta) | 169 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/manta/address/0x1a44076050125825900e736c501f859c50fE728c) | 30217 | [SendUln302
(0xD165...)](https://layerzeroscan.com/api/explorer/manta/address/0xD1654C656455E40E2905E96b6B91088AC2B362a2)
[ReceiveUln302
(0xC1EC...)](https://layerzeroscan.com/api/explorer/manta/address/0xC1EC25A9e8a8DE5Aa346f635B33e5B74c4c081aF)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/manta/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x8DD9...)](https://layerzeroscan.com/api/explorer/manta/address/0x8DD9197E51dC6082853aD71D35912C53339777A7)
[LZ Dead DVN
(0xf942...)](https://layerzeroscan.com/api/explorer/manta/address/0xf9420F9D2552640e242Ad89CD5D3b625F92705C9) |  |
| [### Manta Sepolia Testnet](#mantasep-testnet) | 3441006 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/mantasep-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40272 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/mantasep-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/mantasep-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/mantasep-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/mantasep-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Mantle Mainnet](#mantle) | 5000 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/mantle/address/0x1a44076050125825900e736c501f859c50fE728c) | 30181 | [SendUln302
(0xde19...)](https://layerzeroscan.com/api/explorer/mantle/address/0xde19274c009A22921E3966a1Ec868cEba40A5DaC)
[ReceiveUln302
(0x8da6...)](https://layerzeroscan.com/api/explorer/mantle/address/0x8da6512De9379fBF4F09BF520Caf7a85435ed93e)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/mantle/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x4Fc3...)](https://layerzeroscan.com/api/explorer/mantle/address/0x4Fc3f4A38Acd6E4cC0ccBc04B3Dd1CCAeFd7F3Cd)
[LZ Dead DVN
(0x2e2A...)](https://layerzeroscan.com/api/explorer/mantle/address/0x2e2AF282E98bfADed5dd6EC51c7240D818DDBBD9) |  |
| [### Mantle Sepolia Testnet](#mantle-sepolia) | 5003 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/mantle-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40246 | [SendUln302
(0x9A28...)](https://layerzeroscan.com/api/explorer/mantle-sepolia/address/0x9A289B849b32FF69A95F8584a03343a33Ff6e5Fd)
[ReceiveUln302
(0x8A3D...)](https://layerzeroscan.com/api/explorer/mantle-sepolia/address/0x8A3D588D9f6AC041476b094f97FF94ec30169d3D)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/mantle-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x8BEE...)](https://layerzeroscan.com/api/explorer/mantle-sepolia/address/0x8BEEe743829af63F5b37e52D5ef8477eF12511dE) |  |
| [### Memecoreformicarium Testnet](#memecoreformicarium-testnet)
Recently Added | 43521 | [EndpointV2
(0x145C...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0x145C041566B21Bec558B2A37F1a5Ff261aB55998) | 40354 | [SendUln302
(0x1252...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d)
[ReceiveUln302
(0x9dB9...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[BlockedMessageLib
(0x6f09...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0x6f09f1430c4c204c4b5433abe24c15f342b70699)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/memecoreformicarium-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Meritcircle Mainnet](#beam) | 4337 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/beam/address/0x1a44076050125825900e736c501f859c50fE728c) | 30198 | [SendUln302
(0x763B...)](https://layerzeroscan.com/api/explorer/beam/address/0x763BfcE1Ed335885D0EeC1F182fE6E6B85BAbC92)
[ReceiveUln302
(0xe767...)](https://layerzeroscan.com/api/explorer/beam/address/0xe767e048221197A2b590CeB5C63C3AAD8ebf87eA)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/beam/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x9Bdf...)](https://layerzeroscan.com/api/explorer/beam/address/0x9Bdf3aE7E2e3D211811E5e782a808Ca0a75BF1Fc)
[LZ Dead DVN
(0x690b...)](https://layerzeroscan.com/api/explorer/beam/address/0x690b1857EaA8c55850547d7C22148C0B99a71dCd) |  |
| [### Merlin Mainnet](#merlin) | 4200 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/merlin/address/0x1a44076050125825900e736c501f859c50fE728c) | 30266 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/merlin/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/merlin/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/merlin/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/merlin/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0x5EE3...)](https://layerzeroscan.com/api/explorer/merlin/address/0x5EE3Cb252978C2A51671e6AAD109491e62f04d8f) |  |
| [### Merlin Testnet](#merlin-testnet) | 686868 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/merlin-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40264 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/merlin-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/merlin-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/merlin-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/merlin-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Meter Mainnet](#meter) | 82 | [EndpointV2
(0xef02...)](https://layerzeroscan.com/api/explorer/meter/address/0xef02BacD67C0AB45510927749009F6B9ffCE0631) | 30176 | [SendUln302
(0xD721...)](https://layerzeroscan.com/api/explorer/meter/address/0xD721315eB3d2e7e8cFDfC7d82C02a1DCe144f8E4)
[ReceiveUln302
(0xffA3...)](https://layerzeroscan.com/api/explorer/meter/address/0xffA387da7E7c2d444A78cd9ebcfA89AfBF980d71)
[BlockedMessageLib
(0x5250...)](https://layerzeroscan.com/api/explorer/meter/address/0x5250cd435254987de4cb824ec7363507461efd84)
[LZ Executor
(0x27b7...)](https://layerzeroscan.com/api/explorer/meter/address/0x27b7Bf5f95c2DD6Bc07Ce4ed8598b20Fb73fF5c1)
[LZ Dead DVN
(0x6008...)](https://layerzeroscan.com/api/explorer/meter/address/0x6008B58840B2353996797D65f8539d42e01Bb297) |  |
| [### Meter Testnet](#meter-testnet) | 83 | [EndpointV2
(0x3E03...)](https://layerzeroscan.com/api/explorer/meter-testnet/address/0x3E03163f253ec436d4562e5eFd038cf98827B7eC) | 40156 | [SendUln302
(0x6B94...)](https://layerzeroscan.com/api/explorer/meter-testnet/address/0x6B946AF0b8F3B4D33a36f90C5227D0054722FF32)
[ReceiveUln302
(0xeA2B...)](https://layerzeroscan.com/api/explorer/meter-testnet/address/0xeA2B12219472e0d2a7795c7f61B0602bF5c36E25)
[BlockedMessageLib
(0x82bd...)](https://layerzeroscan.com/api/explorer/meter-testnet/address/0x82bd6930d894a2c35145a39d95a1b849540b2ebd)
[LZ Executor
(0x6892...)](https://layerzeroscan.com/api/explorer/meter-testnet/address/0x68921A9530579203EE812ebddd0eE31ED43E7040) |  |
| [### Metis Mainnet](#metis) | 1088 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/metis/address/0x1a44076050125825900e736c501f859c50fE728c) | 30151 | [SendUln302
(0x63e3...)](https://layerzeroscan.com/api/explorer/metis/address/0x63e39ccB510926d05a0ae7817c8f1CC61C5BdD6c)
[ReceiveUln302
(0x5539...)](https://layerzeroscan.com/api/explorer/metis/address/0x5539Eb17a84E1D59d37C222Eb2CC4C81b502D1Ac)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/metis/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xE6AB...)](https://layerzeroscan.com/api/explorer/metis/address/0xE6AB3B3E632f3C65c3cb4c250DcC42f5E915A1cf)
[LZ Dead DVN
(0x4CC0...)](https://layerzeroscan.com/api/explorer/metis/address/0x4CC028221B4567c7439dC618D2d7f7a22315C1e4) |  |
| [### Metis Sepolia Testnet](#metissep-testnet) | 59902 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/metissep-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40292 | [SendUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/metissep-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[ReceiveUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/metissep-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/metissep-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/metissep-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014) |  |
| [### Mode Mainnet](#mode) | 34443 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/mode/address/0x1a44076050125825900e736c501f859c50fE728c) | 30260 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/mode/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/mode/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/mode/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/mode/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x7B9E...)](https://layerzeroscan.com/api/explorer/mode/address/0x7B9E184e07a6EE1aC23eAe0fe8D6Be2f663f05e6) |  |
| [### Mode Testnet](#mode-testnet) | 919 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/mode-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40260 | [SendUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/mode-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[ReceiveUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/mode-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/mode-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/mode-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014) |  |
| [### Moksha Testnet](#moksha-testnet) | 14800 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40342 | [SendUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[ReceiveUln302
(0x9eCf...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x9eCf72299027e8AeFee5DC5351D6d92294F46d2b)
[ReadLib1002
(0x4584...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x8dF5...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x8dF53a660a00C3D977d7E778fB7385ECf4482D16)
[LZ Dead DVN
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/moksha-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### Monad Testnet](#monad-testnet)
Recently Added | 10143 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40204 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/monad-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Moonbase Alpha Testnet](#moonbase) | 1287 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/moonbase/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40126 | [SendUln302
(0x4CC5...)](https://layerzeroscan.com/api/explorer/moonbase/address/0x4CC50568EdC84101097E06bCf736918f637e6aB7)
[ReceiveUln302
(0x5468...)](https://layerzeroscan.com/api/explorer/moonbase/address/0x5468b60ed00F9b389B5Ba660189862Db058D7dC8)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/moonbase/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xd10f...)](https://layerzeroscan.com/api/explorer/moonbase/address/0xd10fe0817Ebb477Bc05Df7d503dE9d022B6B0831) |  |
| [### Moonbeam Mainnet](#moonbeam) | 1284 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0x1a44076050125825900e736c501f859c50fE728c) | 30126 | [SendUln302
(0xeac1...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0xeac136456d078bB76f59DCcb2d5E008b31AfE1cF)
[ReceiveUln302
(0x2F4C...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0x2F4C6eeA955e95e6d65E08620D980C0e0e92211F)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xEC09...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0xEC0906949f88f72bF9206E84764163e24a56a499)
[LZ Dead DVN
(0x28eE...)](https://layerzeroscan.com/api/explorer/moonbeam/address/0x28eEE23B2b6C5582112037FD43A4d8C359F54D4D) |  |
| [### Moonriver Mainnet](#moonriver) | 1285 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/moonriver/address/0x1a44076050125825900e736c501f859c50fE728c) | 30167 | [SendUln302
(0x1BAc...)](https://layerzeroscan.com/api/explorer/moonriver/address/0x1BAcC2205312534375c8d1801C27D28370656cFf)
[ReceiveUln302
(0xe8BA...)](https://layerzeroscan.com/api/explorer/moonriver/address/0xe8BAa65CeD8E46DA43520375Af6fAbC31D7bCb8B)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/moonriver/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1E1E...)](https://layerzeroscan.com/api/explorer/moonriver/address/0x1E1E9A04735B9ca509eF8a46255f5104C10C6e99)
[LZ Dead DVN
(0x24D7...)](https://layerzeroscan.com/api/explorer/moonriver/address/0x24D7ff228a81e827Efc29ec45E7b30a99B96C653) |  |
| [### Morph Mainnet](#morph) | 2818 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/morph/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30322 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/morph/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/morph/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/morph/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/morph/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/morph/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Morph Testnet](#morph-testnet) | 2810 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40322 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/morph-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Movement Mainnet](#movement) | | [EndpointV2
(0xe600...)](https://layerzeroscan.com/api/explorer/movement/address/0xe60045e20fc2c99e869c1c34a65b9291c020cd12a0d37a00a53ac1348af4f43c) | 30325 | [SendUln302
(0xc337...)](https://layerzeroscan.com/api/explorer/movement/address/0xc33752e0220faf79e45385dd73fb28d681dcd9f1569a1480725507c1f3c3aba9)
[ReceiveUln302
(0xc337...)](https://layerzeroscan.com/api/explorer/movement/address/0xc33752e0220faf79e45385dd73fb28d681dcd9f1569a1480725507c1f3c3aba9)
[LZ Executor
(0x15a5...)](https://layerzeroscan.com/api/explorer/movement/address/0x15a5bbf1eb7998a22c9f23810d424abe40bd59ddd8e6ab7e59529853ebed41c4) | [](https://aptos.dev/en/network/nodes/networks "Open Aptos page") |
| [### Movement Testnet](#movement-testnet) | | [EndpointV2
(0x7f03...)](https://layerzeroscan.com/api/explorer/movement-testnet/address/0x7f03103b83c51c8b09be1751a797a65ac6e755f72947ecdecffc203d32d816c6) | 40325 | [SendUln302
(0xcc1c...)](https://layerzeroscan.com/api/explorer/movement-testnet/address/0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690)
[ReceiveUln302
(0xcc1c...)](https://layerzeroscan.com/api/explorer/movement-testnet/address/0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690)
[LZ Executor
(0x9335...)](https://layerzeroscan.com/api/explorer/movement-testnet/address/0x93353700091200ef9fdc536ce6a86182cc7e62da25f94356be9421c6310b9585) | [](https://aptos.dev/en/network/nodes/networks "Open Aptos page") |
| [### Near Aurora Mainnet](#aurora) | 1313161554 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/aurora/address/0x1a44076050125825900e736c501f859c50fE728c) | 30211 | [SendUln302
(0x1aCe...)](https://layerzeroscan.com/api/explorer/aurora/address/0x1aCe9DD1BC743aD036eF2D92Af42Ca70A1159df5)
[ReceiveUln302
(0x000C...)](https://layerzeroscan.com/api/explorer/aurora/address/0x000CC1A759bC3A15e664Ed5379E321Be5de1c9B6)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/aurora/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xA2b4...)](https://layerzeroscan.com/api/explorer/aurora/address/0xA2b402FFE8dd7460a8b425644B6B9f50667f0A61)
[LZ Dead DVN
(0x412C...)](https://layerzeroscan.com/api/explorer/aurora/address/0x412CEc9FC5044bCba04ED6875729540cE35C6C6f) |  |
| [### Nibiru Mainnet](#nibiru)
Recently Added | 6900 | [EndpointV2
(0x2a5E...)](https://layerzeroscan.com/api/explorer/nibiru/address/0x2a5E79DEE6E3544588BB3b675B1Cc3354Df2AEFD) | 30369 | [SendUln302
(0xd1FA...)](https://layerzeroscan.com/api/explorer/nibiru/address/0xd1FA2df582C6C986Ec573e1a3B0218049CF1E5c7)
[ReceiveUln302
(0xeB8b...)](https://layerzeroscan.com/api/explorer/nibiru/address/0xeB8b16D080B0FcB0C6A89544f4Dd31e595382E8B)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/nibiru/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0x6A02...)](https://layerzeroscan.com/api/explorer/nibiru/address/0x6A02D83e8d433304bba74EF1c427913958187142)
[LZ Dead DVN
(0x3823...)](https://layerzeroscan.com/api/explorer/nibiru/address/0x3823094993190Fbb3bFABfEC8365b8C18517566F) | |
| [### Nibiru Testnet](#nibiru-testnet)
Recently Added | 7210 | [EndpointV2
(0x19Aa...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x19Aa25541F9f1414dcEd4C9bA4225c2a24c77CFe) | 40369 | [SendUln302
(0x3aAD...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x3aADdd4ECcbB187017Ff1215Babc4Ee90B97BA23)
[ReceiveUln302
(0x9DAA...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x9DAA2CB7756a9D80b4105Dd57AA4ad130b746B76)
[BlockedMessageLib
(0x1653...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x1653130d80bbd93a5c43f25e311a0ee3565984fe)
[LZ Executor
(0x1567...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x1567AD0EE3F4818DcA5cC2A76fE13fFC11bfeb10)
[LZ Dead DVN
(0x70Fc...)](https://layerzeroscan.com/api/explorer/nibiru-testnet/address/0x70FcAA7A17ee7E678ec808E095268417A3c054b4) | |
| [### OKX Mainnet](#okx) | 66 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/okx/address/0x1a44076050125825900e736c501f859c50fE728c) | 30155 | [SendUln302
(0x7807...)](https://layerzeroscan.com/api/explorer/okx/address/0x7807888fAC5c6f23F6EeFef0E6987DF5449C1BEb)
[ReceiveUln302
(0x51Ae...)](https://layerzeroscan.com/api/explorer/okx/address/0x51Ae634318E7191C7ffc5778E2D9f860e1e60361)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/okx/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1658...)](https://layerzeroscan.com/api/explorer/okx/address/0x1658766898B42547297A429a51FDea03BC4a863F)
[LZ Dead DVN
(0x641A...)](https://layerzeroscan.com/api/explorer/okx/address/0x641A8990001199692fd8042dc37445F07355d6CE) |  |
| [### OKX Testnet](#okx-testnet) | 65 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/okx-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40155 | [SendUln302
(0x4eb3...)](https://layerzeroscan.com/api/explorer/okx-testnet/address/0x4eb38E1743669C6753C44A58B2F11E0c592183eD)
[ReceiveUln302
(0xaaed...)](https://layerzeroscan.com/api/explorer/okx-testnet/address/0xaaed103E18acf972b9b68743E3d4bDeBb9Ce5E5b)
[LZ Executor
(0x826b...)](https://layerzeroscan.com/api/explorer/okx-testnet/address/0x826b93439CB1d53467566d04A9Ddc03F73614e59) |  |
| [### Odyssey Testnet](#odyssey-testnet) | 1516 | [EndpointV2
(0x6Ac7...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0x6Ac7bdc07A0583A362F1497252872AE6c0A5F5B8) | 40340 | [SendUln302
(0xB048...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0xB0487596a0B62D1A71D0C33294bd6eB635Fc6B09)
[ReceiveUln302
(0x073f...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0x073f5b4FdF17BBC16b0980d49f6C56123477bb51)
[ReadLib1002
(0xcF1B...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[LZ Executor
(0x8dF5...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0x8dF53a660a00C3D977d7E778fB7385ECf4482D16)
[LZ Dead DVN
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/odyssey-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### Olive Testnet](#olive-testnet) | 8101902 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/olive-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40277 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/olive-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/olive-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/olive-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/olive-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Opencampus Testnet](#opencampus-testnet) | 656476 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40297 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/opencampus-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Optimism Mainnet](#optimism) | 10 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/optimism/address/0x1a44076050125825900e736c501f859c50fE728c) | 30111 | [SendUln302
(0x1322...)](https://layerzeroscan.com/api/explorer/optimism/address/0x1322871e4ab09Bc7f5717189434f97bBD9546e95)
[ReceiveUln302
(0x3c49...)](https://layerzeroscan.com/api/explorer/optimism/address/0x3c4962Ff6258dcfCafD23a814237B7d6Eb712063)
[ReadLib1002
(0x01B2...)](https://layerzeroscan.com/api/explorer/optimism/address/0x01B29c03fAD8F455184573D6624a8136cF6106Fb)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/optimism/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x2D2e...)](https://layerzeroscan.com/api/explorer/optimism/address/0x2D2ea0697bdbede3F01553D2Ae4B8d0c486B666e)
[LZ Dead DVN
(0xEbc3...)](https://layerzeroscan.com/api/explorer/optimism/address/0xEbc3065003e67CaaC747836dA272d9E5271A37e1) |  |
| [### Optimism Sepolia](#optimism-sepolia) | 11155420 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/optimism-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40232 | [SendUln302
(0xB31D...)](https://layerzeroscan.com/api/explorer/optimism-sepolia/address/0xB31D2cb502E25B30C651842C7C3293c51Fe6d16f)
[ReceiveUln302
(0x9284...)](https://layerzeroscan.com/api/explorer/optimism-sepolia/address/0x9284fd59B95b9143AF0b9795CAC16eb3C723C9Ca)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/optimism-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xDc0D...)](https://layerzeroscan.com/api/explorer/optimism-sepolia/address/0xDc0D68899405673b932F0DB7f8A49191491A5bcB) |  |
| [### Orderly Mainnet](#orderly) | 291 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/orderly/address/0x1a44076050125825900e736c501f859c50fE728c) | 30213 | [SendUln302
(0x5B23...)](https://layerzeroscan.com/api/explorer/orderly/address/0x5B23E2bAe5C5f00e804EA2C4C9abe601604378fa)
[ReceiveUln302
(0xCFf0...)](https://layerzeroscan.com/api/explorer/orderly/address/0xCFf08a35A5f27F306e2DA99ff198dB90f13DEF77)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/orderly/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1aCe...)](https://layerzeroscan.com/api/explorer/orderly/address/0x1aCe9DD1BC743aD036eF2D92Af42Ca70A1159df5)
[LZ Dead DVN
(0x690b...)](https://layerzeroscan.com/api/explorer/orderly/address/0x690b1857EaA8c55850547d7C22148C0B99a71dCd) |  |
| [### Orderly Sepolia Testnet](#orderly-testnet) | 4460 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/orderly-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40200 | [SendUln302
(0x8e3D...)](https://layerzeroscan.com/api/explorer/orderly-testnet/address/0x8e3Dc55b7A1f7Fe4ce328A1c90dC1B935a30Cc42)
[ReceiveUln302
(0x3013...)](https://layerzeroscan.com/api/explorer/orderly-testnet/address/0x3013C32e5F45E69ceA9baD4d96786704C2aE148c)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/orderly-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1e56...)](https://layerzeroscan.com/api/explorer/orderly-testnet/address/0x1e567E344B2d990D2ECDFa4e14A1c9a1Beb83e96) |  |
| [### Otherworld Space Mainnet](#space) | 8227 | [EndpointV2
(0xAaB5...)](https://layerzeroscan.com/api/explorer/space/address/0xAaB5A48CFC03Efa9cC34A2C1aAcCCB84b4b770e4) | 30341 | [SendUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/space/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[ReceiveUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/space/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[BlockedMessageLib
(0x3adb...)](https://layerzeroscan.com/api/explorer/space/address/0x3adb8d9c040fae1fbac9b579799cd4ca8c768f8a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/space/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x282b...)](https://layerzeroscan.com/api/explorer/space/address/0x282b3386571f7f794450d5789911a9804FA346b4) |  |
| [### Otherworld Testnet](#otherworld-testnet) | 48795 | [EndpointV2
(0xBa8d...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0xBa8dF7424dAE9C2CDB4BC1aD2b63ABD97194fDb6) | 40337 | [SendUln302
(0x012f...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0x012f6eaE2A0Bf5916f48b5F37C62Bcfb7C1ffdA1)
[ReceiveUln302
(0x4aEF...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0x4aEFf6244dce72a2C71A3c2a75d35b2C396d7C5d)
[BlockedMessageLib
(0x1cb7...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0x1cb71fa146e1ff13fbebd1e4394d57211c931b67)
[LZ Executor
(0xfa91...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0xfa91bFC0BF66fA4AA4340e6fb920485d4f2c153D)
[LZ Dead DVN
(0xF626...)](https://layerzeroscan.com/api/explorer/otherworld-testnet/address/0xF6268056Ce73E997450F42aa79DE88103CfEfd09) |  |
| [### Ozean Testnet](#ozean-testnet) | 7849306 | [EndpointV2
(0x145C...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0x145C041566B21Bec558B2A37F1a5Ff261aB55998) | 40323 | [SendUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[ReceiveUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[BlockedMessageLib
(0x6f09...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0x6f09f1430c4c204c4b5433abe24c15f342b70699)
[LZ Executor
(0xe1a1...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6)
[LZ Dead DVN
(0x701f...)](https://layerzeroscan.com/api/explorer/ozean-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243) |  |
| [### Peaq Mainnet](#peaq) | 3338 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/peaq/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30302 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/peaq/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/peaq/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/peaq/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/peaq/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/peaq/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Peaq Testnet](#peaq-testnet) | 9990 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40299 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/peaq-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Plume Mainnet](#plumephoenix)
Recently Added | 98866 | [EndpointV2
(0xC1b1...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0xC1b15d3B262bEeC0e3565C11C9e0F6134BdaCB36) | 30370 | [SendUln302
(0xFe7C...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0xFe7C30860D01e28371D40434806F4A8fcDD3A098)
[ReceiveUln302
(0x5B19...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0x5B19bd330A84c049b62D5B0FC2bA120217a18C1C)
[BlockedMessageLib
(0x9e61...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0x9e611db91ade3312534064ae6ae700f5b531844c)
[LZ Executor
(0x41Bd...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d)
[LZ Dead DVN
(0x4514...)](https://layerzeroscan.com/api/explorer/plumephoenix/address/0x4514FC667a944752ee8A29F544c1B20b1A315f25) | |
| [### Plume Testnet](#plume2-testnet) | 18230 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/plume2-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40329 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/plume2-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/plume2-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/plume2-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/plume2-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Polygon Amoy Testnet](#amoy-testnet) | 80002 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/amoy-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40267 | [SendUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/amoy-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[ReceiveUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/amoy-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/amoy-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/amoy-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### Polygon Mainnet](#polygon) | 137 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/polygon/address/0x1a44076050125825900e736c501f859c50fE728c) | 30109 | [SendUln302
(0x6c26...)](https://layerzeroscan.com/api/explorer/polygon/address/0x6c26c61a97006888ea9E4FA36584c7df57Cd9dA3)
[ReceiveUln302
(0x1322...)](https://layerzeroscan.com/api/explorer/polygon/address/0x1322871e4ab09Bc7f5717189434f97bBD9546e95)
[ReadLib1002
(0xc214...)](https://layerzeroscan.com/api/explorer/polygon/address/0xc214d690031d3f873365f94d381d6d50c35aa7fa)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/polygon/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xCd3F...)](https://layerzeroscan.com/api/explorer/polygon/address/0xCd3F213AD101472e1713C72B1697E727C803885b)
[LZ Dead DVN
(0x43CF...)](https://layerzeroscan.com/api/explorer/polygon/address/0x43CFcc293CdF99F7D021F21FfD443f174AB0e843) |  |
| [### Polygon zkEVM Mainnet](#zkevm) | 1101 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/zkevm/address/0x1a44076050125825900e736c501f859c50fE728c) | 30158 | [SendUln302
(0x28B6...)](https://layerzeroscan.com/api/explorer/zkevm/address/0x28B6140ead70cb2Fb669705b3598ffB4BEaA060b)
[ReceiveUln302
(0x581b...)](https://layerzeroscan.com/api/explorer/zkevm/address/0x581b26F362AD383f7B51eF8A165Efa13DDe398a4)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/zkevm/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xbE4f...)](https://layerzeroscan.com/api/explorer/zkevm/address/0xbE4fB271cfB7bcbB47EA9573321c7bfe309fc220)
[LZ Dead DVN
(0xbD8F...)](https://layerzeroscan.com/api/explorer/zkevm/address/0xbD8F7f0B165213Aaabb5a9eA0D572d5FD9829664) |  |
| [### Polygon zkEVM Sepolia Testnet](#zkpolygon-sepolia) | 2442 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/zkpolygon-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40247 | [SendUln302
(0x88B2...)](https://layerzeroscan.com/api/explorer/zkpolygon-sepolia/address/0x88B27057A9e00c5F05DDa29241027afF63f9e6e0)
[ReceiveUln302
(0xF49d...)](https://layerzeroscan.com/api/explorer/zkpolygon-sepolia/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/zkpolygon-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/zkpolygon-sepolia/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014) |  |
| [### Rari Chain Mainnet](#rarible) | 1380012617 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/rarible/address/0x1a44076050125825900e736c501f859c50fE728c) | 30235 | [SendUln302
(0xA09d...)](https://layerzeroscan.com/api/explorer/rarible/address/0xA09dB5142654e3eB5Cf547D66833FAe7097B21C3)
[ReceiveUln302
(0x148f...)](https://layerzeroscan.com/api/explorer/rarible/address/0x148f693af10ddfaE81cDdb36F4c93B31A90076e1)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/rarible/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1E4C...)](https://layerzeroscan.com/api/explorer/rarible/address/0x1E4CAc6c2c955cAED779ef24d5B8C5EE90b1f914)
[LZ Dead DVN
(0xFE9e...)](https://layerzeroscan.com/api/explorer/rarible/address/0xFE9e60eE82C8E800bd48c4fc2aE1B7716528cc56) |  |
| [### Rarible Testnet](#rarible-testnet) | 1918988905 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/rarible-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40235 | [SendUln302
(0x7C42...)](https://layerzeroscan.com/api/explorer/rarible-testnet/address/0x7C424244B51d03cEEc115647ccE151baF112a42e)
[ReceiveUln302
(0xbf06...)](https://layerzeroscan.com/api/explorer/rarible-testnet/address/0xbf06c8886E6904a95dD42440Bd237C4ac64940C8)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/rarible-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x19DC...)](https://layerzeroscan.com/api/explorer/rarible-testnet/address/0x19DC7b94ACAFbAD3EFa1Bc782d1367a8b173Ba73) |  |
| [### Reya Mainnet](#reya) | 1729 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/reya/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30313 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/reya/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/reya/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/reya/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/reya/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/reya/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Reya Testnet](#reya-testnet) | 89346162 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40319 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/reya-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Rootstock Mainnet](#rootstock) | 30 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/rootstock/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30333 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/rootstock/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/rootstock/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x19a6...)](https://layerzeroscan.com/api/explorer/rootstock/address/0x19a642a780f4cfc27c12c8ac79e586b5007ba8c5)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/rootstock/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/rootstock/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Rootstock Testnet](#rootstock-testnet)
Recently Added | 31 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40350 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x7f2e...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0x7f2e3456388687825f0d6f1c5daab1ba07bf17d5)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/rootstock-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Sanko Mainnet](#sanko) | 1996 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/sanko/address/0x1a44076050125825900e736c501f859c50fE728c) | 30278 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/sanko/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/sanko/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x0000...)](https://layerzeroscan.com/api/explorer/sanko/address/0x0000000000000000000000000000000000000000)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/sanko/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0x10aC...)](https://layerzeroscan.com/api/explorer/sanko/address/0x10aC9B7EB034fAb1F3bc446E81479D7dC089Be83) |  |
| [### Sanko Testnet](#sanko-testnet) | 1992 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/sanko-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40278 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/sanko-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/sanko-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/sanko-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/sanko-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Scroll Mainnet](#scroll) | 534352 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/scroll/address/0x1a44076050125825900e736c501f859c50fE728c) | 30214 | [SendUln302
(0x9BbE...)](https://layerzeroscan.com/api/explorer/scroll/address/0x9BbEb2B2184B9313Cf5ed4a4DDFEa2ef62a2a03B)
[ReceiveUln302
(0x8363...)](https://layerzeroscan.com/api/explorer/scroll/address/0x8363302080e711E0CAb978C081b9e69308d49808)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/scroll/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x581b...)](https://layerzeroscan.com/api/explorer/scroll/address/0x581b26F362AD383f7B51eF8A165Efa13DDe398a4)
[LZ Dead DVN
(0xDB23...)](https://layerzeroscan.com/api/explorer/scroll/address/0xDB238D5196328b5623612C235062427F2F6792c0) |  |
| [### Scroll Sepolia Testnet](#scroll-testnet) | 534351 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/scroll-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40170 | [SendUln302
(0x21f1...)](https://layerzeroscan.com/api/explorer/scroll-testnet/address/0x21f1C2B131557c3AebA918D590815c47Dc4F20aa)
[ReceiveUln302
(0xf2dB...)](https://layerzeroscan.com/api/explorer/scroll-testnet/address/0xf2dB23f9eA1311E9ED44E742dbc4268de4dB0a88)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/scroll-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xD0D4...)](https://layerzeroscan.com/api/explorer/scroll-testnet/address/0xD0D47C34937DdbeBBe698267a6BbB1dacE51198D) |  |
| [### Sei Devnet](#sei-testnet) | 713715 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/sei-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40258 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/sei-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/sei-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/sei-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/sei-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Sei Mainnet](#sei) | 1329 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/sei/address/0x1a44076050125825900e736c501f859c50fE728c) | 30280 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/sei/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/sei/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/sei/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/sei/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0xf772...)](https://layerzeroscan.com/api/explorer/sei/address/0xf772581dcf3300914D6222C4e6FcF0ed5EF93142) |  |
| [### Shimmer Mainnet](#shimmer) | 148 | [EndpointV2
(0x148f...)](https://layerzeroscan.com/api/explorer/shimmer/address/0x148f693af10ddfaE81cDdb36F4c93B31A90076e1) | 30230 | [SendUln302
(0xD4a9...)](https://layerzeroscan.com/api/explorer/shimmer/address/0xD4a903930f2c9085586cda0b11D9681EECb20D2f)
[ReceiveUln302
(0xb21f...)](https://layerzeroscan.com/api/explorer/shimmer/address/0xb21f945e8917c6Cd69FcFE66ac6703B90f7fe004)
[BlockedMessageLib
(0x3091...)](https://layerzeroscan.com/api/explorer/shimmer/address/0x3091ca3aee13e16c051b03b282163e565296464c)
[LZ Executor
(0x868a...)](https://layerzeroscan.com/api/explorer/shimmer/address/0x868a44F9d9F09331da425539a174a2128b85D672)
[LZ Dead DVN
(0x7D71...)](https://layerzeroscan.com/api/explorer/shimmer/address/0x7D71242e93eD57455C017b92f980B01066E87D22) |  |
| caution
Shimmer, while being EVM-like, uses a different approach to gas token decimals, which could lead to specific implementations and adjustments in your gas calculations and transactions. | | | | | |
| [### Skale Mainnet](#skale) | 2046399126 | [EndpointV2
(0xe184...)](https://layerzeroscan.com/api/explorer/skale/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043) | 30273 | [SendUln302
(0x37aa...)](https://layerzeroscan.com/api/explorer/skale/address/0x37aaaf95887624a363effB7762D489E3C05c2a02)
[ReceiveUln302
(0x15e5...)](https://layerzeroscan.com/api/explorer/skale/address/0x15e51701F245F6D5bd0FEE87bCAf55B0841451B3)
[BlockedMessageLib
(0x0000...)](https://layerzeroscan.com/api/explorer/skale/address/0x0000000000000000000000000000000000000000)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/skale/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0xC8B0...)](https://layerzeroscan.com/api/explorer/skale/address/0xC8B0B3A95bc6AC3eDA97208556DC7A7820da5bf0) |  |
| caution
The Skale Endpoint uses an alternative ERC20 token instead of the native gas token for omnichain fees. You will need to modify your \_payNative() function in OApp to handle ERC20 fees (see OFTAlt). | | | | | |
| [### Skale Testnet](#skale-testnet) | 1444673419 | [EndpointV2
(0x82b7...)](https://layerzeroscan.com/api/explorer/skale-testnet/address/0x82b7dc04A4ABCF2b4aE570F317dcab49f5a10f24) | 40273 | [SendUln302
(0x4632...)](https://layerzeroscan.com/api/explorer/skale-testnet/address/0x4632b54146C45Cf31EE1d5A1191260Af7e9DB801)
[ReceiveUln302
(0x9D0A...)](https://layerzeroscan.com/api/explorer/skale-testnet/address/0x9D0A659cAC5F122e22bAaDD8769a3abc05C6bdAE)
[BlockedMessageLib
(0xae1b...)](https://layerzeroscan.com/api/explorer/skale-testnet/address/0xae1ba44dfab3b96cfdb8481960dd8e4c015c82b0)
[LZ Executor
(0x86d0...)](https://layerzeroscan.com/api/explorer/skale-testnet/address/0x86d08462EaA1559345d7F41f937B2C804209DB8A) |  |
| caution
The Skale Endpoint uses an alternative ERC20 token instead of the native gas token for omnichain fees. You will need to modify your \_payNative() function in OApp to handle ERC20 fees (see OFTAlt). | | | | | |
| [### Solana Devnet](#solana-testnet) | 103 | [EndpointV2
(76y77p...)](https://layerzeroscan.com/api/explorer/solana-testnet/address/76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6) | 40168 | [SendUln302
(7a4Wjy...)](https://layerzeroscan.com/api/explorer/solana-testnet/address/7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH)
[ReceiveUln302
(7a4Wjy...)](https://layerzeroscan.com/api/explorer/solana-testnet/address/7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH)
[LZ Executor
(6doghB...)](https://layerzeroscan.com/api/explorer/solana-testnet/address/6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn) | [](https://solana.com/docs/core/clusters "Open Solana page") |
| [### Solana Mainnet](#solana) | 101 | [EndpointV2
(76y77p...)](https://layerzeroscan.com/api/explorer/solana/address/76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6) | 30168 | [SendUln302
(7a4Wjy...)](https://layerzeroscan.com/api/explorer/solana/address/7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH)
[ReceiveUln302
(7a4Wjy...)](https://layerzeroscan.com/api/explorer/solana/address/7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH)
[LZ Executor
(6doghB...)](https://layerzeroscan.com/api/explorer/solana/address/6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn) | [](https://solana.com/docs/core/clusters "Open Solana page") |
| [### Soneium Mainnet](#soneium) | 1868 | [EndpointV2
(0x4bcb...)](https://layerzeroscan.com/api/explorer/soneium/address/0x4bcb6a963a9563c33569d7a512d35754221f3a19) | 30340 | [SendUln302
(0x5035...)](https://layerzeroscan.com/api/explorer/soneium/address/0x50351C9dA75CCC6d8Ea2464B26591Bb4bd616dD5)
[ReceiveUln302
(0x364B...)](https://layerzeroscan.com/api/explorer/soneium/address/0x364B548d8e6DB7CA84AaAFA54595919eCcF961eA)
[BlockedMessageLib
(0xe71e...)](https://layerzeroscan.com/api/explorer/soneium/address/0xe71ed82cb15850147e9886b770d251bf4c807da4)
[LZ Executor
(0xAE3C...)](https://layerzeroscan.com/api/explorer/soneium/address/0xAE3C661292bb4D0AEEe0588b4404778DF1799EE6)
[LZ Dead DVN
(0xf90b...)](https://layerzeroscan.com/api/explorer/soneium/address/0xf90b61aa892ba0433F54D2C1BF594d89d22ed7F6) |  |
| [### Soneium Minato Testnet](#minato-testnet) | 1946 | [EndpointV2
(0x6Ac7...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0x6Ac7bdc07A0583A362F1497252872AE6c0A5F5B8) | 40334 | [SendUln302
(0x00C5...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0x00C5C0B8e0f75aB862CbAaeCfff499dB555FBDD2)
[ReceiveUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[BlockedMessageLib
(0xa97f...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0xa97f783e717567ab8d0fc72110714f4fa7967373)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93)
[LZ Dead DVN
(0x55c1...)](https://layerzeroscan.com/api/explorer/minato-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Sonic Blaze Testnet](#sonic-testnet) | 57054 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40349 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/sonic-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Sonic Mainnet](#sonic) | 146 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/sonic/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30332 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/sonic/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/sonic/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/sonic/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/sonic/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/sonic/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Sophon Mainnet](#sophon) | 50104 | [EndpointV2
(0x5c6c...)](https://layerzeroscan.com/api/explorer/sophon/address/0x5c6cfF4b7C49805F8295Ff73C204ac83f3bC4AE7) | 30334 | [SendUln302
(0x0104...)](https://layerzeroscan.com/api/explorer/sophon/address/0x01047601DB5E63b1574aae317BAd9C684E3C9056)
[ReceiveUln302
(0x9AB6...)](https://layerzeroscan.com/api/explorer/sophon/address/0x9AB633555E460C01f8c7b8ab24C88dD4986dD5A1)
[BlockedMessageLib
(0x3258...)](https://layerzeroscan.com/api/explorer/sophon/address/0x3258287147fb7887d8a643006e26e19368057377)
[LZ Executor
(0x5533...)](https://layerzeroscan.com/api/explorer/sophon/address/0x553313dB58dEeFa3D55B1457D27EAB3Fe5EC87E8)
[LZ Dead DVN
(0x0483...)](https://layerzeroscan.com/api/explorer/sophon/address/0x04830f6deCF08Dec9eD6C3fCAD215245B78A59e1) |  |
| [### Sophon Testnet](#sophon-testnet) | 531050104 | [EndpointV2
(0x9EC2...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0x9EC2DB700a3c3D35888acCa134F3f860B4a0b41a) | 40341 | [SendUln302
(0xC996...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0xC9968d69bfaFCFC1a768B2e97D4020386A5e15b2)
[ReceiveUln302
(0x21bc...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0x21bc626E5e97FBF404Fda7e7D808E41A2fA56B60)
[ReadLib1002
(0xf1A4...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0xf1A4f4FA1643ACf9f867b635A6d66a1990A3C217)
[BlockedMessageLib
(0x25aa...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0x25aa15242c9d94526f2844e7c03c5a40e8a79256)
[LZ Executor
(0xaF86...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0xaF862837316E00d2708Bd648c5FE87EdC7093799)
[LZ Dead DVN
(0xE18A...)](https://layerzeroscan.com/api/explorer/sophon-testnet/address/0xE18A1F5942b31F075B00B5F5E944F384B15abF83) |  |
| [### Story Mainnet](#story)
Recently Added | 1514 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/story/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30364 | [SendUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/story/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[ReceiveUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/story/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/story/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0x41Bd...)](https://layerzeroscan.com/api/explorer/story/address/0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d)
[LZ Dead DVN
(0xce83...)](https://layerzeroscan.com/api/explorer/story/address/0xce8358bc28dd8296Ce8cAF1CD2b44787abd65887) |  |
| [### Superposition Mainnet](#superposition) | 55244 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/superposition/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30327 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/superposition/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/superposition/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/superposition/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/superposition/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/superposition/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Superposition Testnet](#superposition-testnet) | 98985 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40336 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/superposition-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Swell Mainnet](#swell) | 1923 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/swell/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30335 | [SendUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/swell/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[ReceiveUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/swell/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/swell/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/swell/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/swell/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Swell Testnet](#swell-testnet)
Recently Added | 1924 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40353 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x9dB9...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0x9dB9Ca3305B48F196D18082e91cB64663b13d014)
[LZ Dead DVN
(0xF49d...)](https://layerzeroscan.com/api/explorer/swell-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### TON Mainnet](#ton) | | [Controller
(0x1eb2...)](https://layerzeroscan.com/api/explorer/ton/address/0x1eb2bbea3d8c0d42ff7fd60f0264c866c934bbff727526ca759e7374cae0c166) | 30343 | [ExecutorProxy
(0x0f9a...)](https://layerzeroscan.com/api/explorer/ton/address/0x0f9a60ea29c5c9e4643601e8881e850498ee680a413e8ba01d5e55ce1c221024)
[DVN Proxy
(0x0d12...)](https://layerzeroscan.com/api/explorer/ton/address/0x0d122dec4ec8bd66c68344faf0dd471d727a7d57a21b62051705bbe2e4c272a7)
[ULN Manager
(0x1506...)](https://layerzeroscan.com/api/explorer/ton/address/0x150645746e25be5486eb3b2f5d98b44c6b324697c48d495d059f96fc9d3ec368)
[All Storages
(0xe19b...)](https://layerzeroscan.com/api/explorer/ton/address/0xe19b25ddb84b14844d5978b0e96a74189729d70cf43d94524d04391a683b8876) | |
| [### Taiko Mainnet](#taiko) | 167000 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/taiko/address/0x1a44076050125825900e736c501f859c50fE728c) | 30290 | [SendUln302
(0xc1B6...)](https://layerzeroscan.com/api/explorer/taiko/address/0xc1B621b18187F74c8F6D52a6F709Dd2780C09821)
[ReceiveUln302
(0x3775...)](https://layerzeroscan.com/api/explorer/taiko/address/0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/taiko/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/taiko/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0xc30F...)](https://layerzeroscan.com/api/explorer/taiko/address/0xc30Ff4F3182A75C0bE27493fA357886c06c384b6) |  |
| [### Taiko Testnet](#taiko-testnet) | 167009 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/taiko-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40274 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/taiko-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/taiko-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/taiko-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/taiko-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Tangible Testnet](#tangible-testnet) | | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/tangible-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40252 | [SendUln302
(0x6Ac7...)](https://layerzeroscan.com/api/explorer/tangible-testnet/address/0x6Ac7bdc07A0583A362F1497252872AE6c0A5F5B8)
[ReceiveUln302
(0x145C...)](https://layerzeroscan.com/api/explorer/tangible-testnet/address/0x145C041566B21Bec558B2A37F1a5Ff261aB55998)
[LZ Executor
(0xF49d...)](https://layerzeroscan.com/api/explorer/tangible-testnet/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6) |  |
| [### Telos Testnet](#telos-testnet) | 41 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/telos-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40199 | [SendUln302
(0x4628...)](https://layerzeroscan.com/api/explorer/telos-testnet/address/0x4628040135EF85759594290F0877aB93B660ac8b)
[ReceiveUln302
(0x9Fc5...)](https://layerzeroscan.com/api/explorer/telos-testnet/address/0x9Fc55169a8B47EDCE891942565De00DBd50B3C2E)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/telos-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x9Ed8...)](https://layerzeroscan.com/api/explorer/telos-testnet/address/0x9Ed8C430B96ae6FDdDb542DDa4eF6f53E919eBdD) |  |
| [### TelosEVM Mainnet](#telos) | 40 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/telos/address/0x1a44076050125825900e736c501f859c50fE728c) | 30199 | [SendUln302
(0x0BcA...)](https://layerzeroscan.com/api/explorer/telos/address/0x0BcAC336466ef7F1e0b5c184aAB2867C108331aF)
[ReceiveUln302
(0x8F76...)](https://layerzeroscan.com/api/explorer/telos/address/0x8F76bAcC52b5730c1f1A2413B8936D4df12aF4f6)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/telos/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x1785...)](https://layerzeroscan.com/api/explorer/telos/address/0x1785c94d31E3E3Ab1079e7ca8a9fbDf33EEf9dd5)
[LZ Dead DVN
(0x1DE7...)](https://layerzeroscan.com/api/explorer/telos/address/0x1DE78bEd411ad03e7f9B4c591D9C80499287Cb04) |  |
| [### Tenet Mainnet](#tenet) | 1559 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/tenet/address/0x1a44076050125825900e736c501f859c50fE728c) | 30173 | [SendUln302
(0x1785...)](https://layerzeroscan.com/api/explorer/tenet/address/0x1785c94d31E3E3Ab1079e7ca8a9fbDf33EEf9dd5)
[ReceiveUln302
(0x1690...)](https://layerzeroscan.com/api/explorer/tenet/address/0x16909F77E57CDaaB7BE0fbDF12b6A77d99541605)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/tenet/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xB125...)](https://layerzeroscan.com/api/explorer/tenet/address/0xB12514e226E50844E4655696c92c0c36B8A53141)
[LZ Dead DVN
(0x0e55...)](https://layerzeroscan.com/api/explorer/tenet/address/0x0e557f8F5BfeEDC6905594987Dccc2E10aF33E5C) |  |
| [### Tenet Testnet](#tenet-testnet) | 155 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/tenet-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40173 | [SendUln302
(0x2CAD...)](https://layerzeroscan.com/api/explorer/tenet-testnet/address/0x2CAD3483690a95d10eeADFb7A79C212050BF5a23)
[ReceiveUln302
(0xBaf9...)](https://layerzeroscan.com/api/explorer/tenet-testnet/address/0xBaf97EC0E26b7879850c8682AdB723670C6133AF)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/tenet-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0xdfF5...)](https://layerzeroscan.com/api/explorer/tenet-testnet/address/0xdfF535e7F030E4aA69CcC7E4a8404648e872E220) |  |
| [### Tiltyard Mainnet](#tiltyard) | 710420 | [EndpointV2
(0x3A73...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9) | 30238 | [SendUln302
(0x62d1...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0x62d142E186344C0a2445c822e356E87faF7b8288)
[ReceiveUln302
(0xd83B...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0xd83B25f4Ff6C596380c36C7eD10c225d6B17Dfd7)
[BlockedMessageLib
(0xf540...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0xf540d892bc671f08e0b1c5b61185c53c2211e8f7)
[LZ Executor
(0xEF77...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0xEF7781FC1C4F7B2Fd3Cf03f4d65b6835b27C1A0d)
[LZ Dead DVN
(0xa50d...)](https://layerzeroscan.com/api/explorer/tiltyard/address/0xa50d9C4aD49933f7bC0574D8c54427EC42C2B073) |  |
| [### Treasure Testnet](#treasure-testnet) | 978658 | [EndpointV2
(0x9EC2...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0x9EC2DB700a3c3D35888acCa134F3f860B4a0b41a) | 40348 | [SendUln302
(0x0e2c...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0x0e2c52BC2e119b1919e68f4F1874D4d30F6eF9fB)
[ReceiveUln302
(0x5f04...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0x5f04B588B9408d8d5F4ac250e8c71986683C35A5)
[BlockedMessageLib
(0x25aa...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0x25aa15242c9d94526f2844e7c03c5a40e8a79256)
[LZ Executor
(0xe2Ef...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0xe2Ef622A13e71D9Dd2BBd12cd4b27e1516FA8a09)
[LZ Dead DVN
(0x9c0B...)](https://layerzeroscan.com/api/explorer/treasure-testnet/address/0x9c0B5520e3eC0ccE919cEaA1fb5AaA7cdAb437D4) |  |
| [### Tron Mainnet](#tron) | 728126428 | [EndpointV2
(0x0Af5...)](https://layerzeroscan.com/api/explorer/tron/address/0x0Af59750D5dB5460E5d89E268C474d5F7407c061) | 30420 | [SendUln302
(0xE369...)](https://layerzeroscan.com/api/explorer/tron/address/0xE369D146219380B24Bb5D9B9E08a5b9936F9E719)
[ReceiveUln302
(0x6122...)](https://layerzeroscan.com/api/explorer/tron/address/0x612215D4dB0475a76dCAa36C7f9afD748c42ed2D)
[BlockedMessageLib
(0x3bdb...)](https://layerzeroscan.com/api/explorer/tron/address/0x3bdb0d09dc50852dcae920162b299b23c2bbe0e6)
[LZ Executor
(0x67DE...)](https://layerzeroscan.com/api/explorer/tron/address/0x67DE40af19C0C0a6D0278d96911889fAF4EBc1Bc)
[LZ Dead DVN
(0x73A3...)](https://layerzeroscan.com/api/explorer/tron/address/0x73A38738170aCa1B2ebCb55538ED9c7fB10ccd3B) |  |
| caution
TRX, the native token of Tron, uses 6 decimals, which affects how transactions and gas fees are calculated and can require specific handling in smart contracts and dApps. | | | | | |
| [### Tron Testnet](#tron-testnet) | 2494104990 | [EndpointV2
(0x1b35...)](https://layerzeroscan.com/api/explorer/tron-testnet/address/0x1b356f3030CE0c1eF9D3e1E250Bf0BB11D81b2d1) | 40420 | [SendUln302
(0xaef6...)](https://layerzeroscan.com/api/explorer/tron-testnet/address/0xaef63752785Ad2104cea1aa42b69b46f2530312F)
[ReceiveUln302
(0x8438...)](https://layerzeroscan.com/api/explorer/tron-testnet/address/0x843810EB9f002E940870a95B366cc59E623bF5f1)
[BlockedMessageLib
(0xc5db...)](https://layerzeroscan.com/api/explorer/tron-testnet/address/0xc5db86276f7a1294c836fea6fe88b20f980951a5)
[LZ Executor
(0xd9F0...)](https://layerzeroscan.com/api/explorer/tron-testnet/address/0xd9F0144AC7cED407a12dE2649b560b0a68a59A3D) |  |
| caution
TRX, the native token of Tron, uses 6 decimals, which affects how transactions and gas fees are calculated and can require specific handling in smart contracts and dApps. | | | | | |
| [### Unichain Mainnet](#unichain) | 130 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/unichain/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30320 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/unichain/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/unichain/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/unichain/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0x4208...)](https://layerzeroscan.com/api/explorer/unichain/address/0x4208D6E27538189bB48E603D6123A94b8Abe0A0b)
[LZ Dead DVN
(0x6788...)](https://layerzeroscan.com/api/explorer/unichain/address/0x6788f52439ACA6BFF597d3eeC2DC9a44B8FEE842) |  |
| [### Unichain Testnet](#unichain-testnet) | 1301 | [EndpointV2
(0xb881...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0xb8815f3f882614048CbE201a67eF9c6F10fe5035) | 40333 | [SendUln302
(0x72e3...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0x72e34F44Eb09058bdDaf1aeEebDEC062f1844b00)
[ReceiveUln302
(0xbEA3...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0xbEA34F26b6FBA63054e4eD86806adce594A62561)
[BlockedMessageLib
(0x5f6d...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0x5f6d188267cd4b175ad7430ddf3346906177dc69)
[LZ Executor
(0x8548...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0x8548b148800BB00C6E4039Aa9C20ebb36a36A600)
[LZ Dead DVN
(0x9fab...)](https://layerzeroscan.com/api/explorer/unichain-testnet/address/0x9fabEEcA47e03d4a37F43b5E476030ab1BB200Ad) |  |
| [### Unreal Testnet](#unreal-testnet) | 18233 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/unreal-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40262 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/unreal-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/unreal-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/unreal-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/unreal-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Vana Mainnet](#islander) | 1480 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/islander/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30330 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/islander/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/islander/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/islander/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/islander/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/islander/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Vanguard Testnet](#vanguard-testnet) | 78600 | [EndpointV2
(0x6C7A...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0x6C7Ab2202C98C4227C5c46f1417D81144DA716Ff) | 40298 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x9269...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0x926984a57b10a3a5c4cfdbac04daaa0309e78932)
[LZ Executor
(0x701f...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243)
[LZ Dead DVN
(0xC186...)](https://layerzeroscan.com/api/explorer/vanguard-testnet/address/0xC1868e054425D378095A003EcbA3823a5D0135C9) |  |
| [### Viction Mainnet](#tomo) | 88 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/tomo/address/0x1a44076050125825900e736c501f859c50fE728c) | 30196 | [SendUln302
(0x6f16...)](https://layerzeroscan.com/api/explorer/tomo/address/0x6f1686189f32e78f1D83e7c6Ed433FCeBc3A5B51)
[ReceiveUln302
(0x7004...)](https://layerzeroscan.com/api/explorer/tomo/address/0x7004396C99D5690da76A7C59057C5f3A53e01704)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/tomo/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x2d24...)](https://layerzeroscan.com/api/explorer/tomo/address/0x2d24207F9C1F77B2E08F2C3aD430da18e355CF66)
[LZ Dead DVN
(0x658F...)](https://layerzeroscan.com/api/explorer/tomo/address/0x658Ff096EE44e16564beA9E1161eCBC503aE6E75) |  |
| [### Viction Testnet](#tomo-testnet) | 89 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/tomo-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40196 | [SendUln302
(0xbB7e...)](https://layerzeroscan.com/api/explorer/tomo-testnet/address/0xbB7e6FE3fA954BF0e5Ea77d38736C56E8D09514B)
[ReceiveUln302
(0x8468...)](https://layerzeroscan.com/api/explorer/tomo-testnet/address/0x8468689ca62D8806614EEdb9F26a13e1Fddbd6BD)
[LZ Executor
(0xe4C9...)](https://layerzeroscan.com/api/explorer/tomo-testnet/address/0xe4C9F9Fa374273736199bdeB712592cE1a3B4B26) |  |
| [### Worldchain Mainnet](#worldchain) | 480 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/worldchain/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30319 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/worldchain/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/worldchain/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/worldchain/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/worldchain/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/worldchain/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Worldcoin Testnet](#worldcoin-testnet) | 4801 | [EndpointV2
(0x145C...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0x145C041566B21Bec558B2A37F1a5Ff261aB55998) | 40335 | [SendUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[ReceiveUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[BlockedMessageLib
(0x6f09...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0x6f09f1430c4c204c4b5433abe24c15f342b70699)
[LZ Executor
(0xe1a1...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0xe1a12515F9AB2764b887bF60B923Ca494EBbB2d6)
[LZ Dead DVN
(0x701f...)](https://layerzeroscan.com/api/explorer/worldcoin-testnet/address/0x701f3927871EfcEa1235dB722f9E608aE120d243) |  |
| [### X Layer Mainnet](#xlayer) | 196 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/xlayer/address/0x1a44076050125825900e736c501f859c50fE728c) | 30274 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/xlayer/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/xlayer/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/xlayer/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/xlayer/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0xAC9d...)](https://layerzeroscan.com/api/explorer/xlayer/address/0xAC9dc8415B2348d8135eC725e8E9B1F1DfAF8e53) |  |
| [### X Layer Testnet](#xlayer-testnet) | 195 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/xlayer-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40269 | [SendUln302
(0x1d18...)](https://layerzeroscan.com/api/explorer/xlayer-testnet/address/0x1d186C560281B8F1AF831957ED5047fD3AB902F9)
[ReceiveUln302
(0x53fd...)](https://layerzeroscan.com/api/explorer/xlayer-testnet/address/0x53fd4C4fBBd53F6bC58CaE6704b92dB1f360A648)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/xlayer-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/xlayer-testnet/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### XChain Mainnet](#xchain) | 94524 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/xchain/address/0x1a44076050125825900e736c501f859c50fE728c) | 30291 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/xchain/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/xchain/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/xchain/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/xchain/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x69fB...)](https://layerzeroscan.com/api/explorer/xchain/address/0x69fBED8561F829eFBB3c9785f1983641B27887e7) |  |
| [### XChain Testnet](#xchain-testnet) | 64002 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/xchain-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40282 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/xchain-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/xchain-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/xchain-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/xchain-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### XDC Mainnet](#xdc)
Recently Added | 50 | [EndpointV2
(0xcb56...)](https://layerzeroscan.com/api/explorer/xdc/address/0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa) | 30365 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/xdc/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/xdc/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x2646...)](https://layerzeroscan.com/api/explorer/xdc/address/0x26463a4af811c256f669524ec2dc1ba7e7a83c37)
[LZ Executor
(0xa20D...)](https://layerzeroscan.com/api/explorer/xdc/address/0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/xdc/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### XPLA Mainnet](#xpla) | 37 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/xpla/address/0x1a44076050125825900e736c501f859c50fE728c) | 30216 | [SendUln302
(0xF622...)](https://layerzeroscan.com/api/explorer/xpla/address/0xF622DFb40bf7340DBCf1e5147D6CFD95d7c5cF1F)
[ReceiveUln302
(0x6167...)](https://layerzeroscan.com/api/explorer/xpla/address/0x6167caAb5c3DA63311186db4D4E2596B20f557ec)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/xpla/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x148f...)](https://layerzeroscan.com/api/explorer/xpla/address/0x148f693af10ddfaE81cDdb36F4c93B31A90076e1)
[LZ Dead DVN
(0xADfA...)](https://layerzeroscan.com/api/explorer/xpla/address/0xADfAC55b334dE39B3eFBe88Bb1c992765e88Bb60) |  |
| [### XPLA Testnet](#xpla-testnet) | 47 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/xpla-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40216 | [SendUln302
(0x1a2f...)](https://layerzeroscan.com/api/explorer/xpla-testnet/address/0x1a2fd0712Ded46794022DdB16a282e798D22a7FB)
[ReceiveUln302
(0x13f7...)](https://layerzeroscan.com/api/explorer/xpla-testnet/address/0x13f78F780BB0ED02bC6df9FFA42fc2D8bB3F9aF5)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/xpla-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x43d2...)](https://layerzeroscan.com/api/explorer/xpla-testnet/address/0x43d28BEbaDF8B99C1aCF8c4961E4Fb895321EF23) |  |
| [### Xai Mainnet](#xai) | 660279 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/xai/address/0x1a44076050125825900e736c501f859c50fE728c) | 30236 | [SendUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/xai/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[ReceiveUln302
(0x2367...)](https://layerzeroscan.com/api/explorer/xai/address/0x2367325334447C5E1E0f1b3a6fB947b262F58312)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/xai/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/xai/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0xEFf2...)](https://layerzeroscan.com/api/explorer/xai/address/0xEFf272433131a0077592f58a16B702EE49B04312) |  |
| [### Xai Testnet](#xai-testnet) | 37714555429 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/xai-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40251 | [SendUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/xai-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[ReceiveUln302
(0xcF1B...)](https://layerzeroscan.com/api/explorer/xai-testnet/address/0xcF1B0F4106B0324F96fEfcC31bA9498caa80701C)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/xai-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x55c1...)](https://layerzeroscan.com/api/explorer/xai-testnet/address/0x55c175DD5b039331dB251424538169D8495C18d1) |  |
| [### Zircuit Mainnet](#zircuit) | 48900 | [EndpointV2
(0x6F47...)](https://layerzeroscan.com/api/explorer/zircuit/address/0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) | 30303 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/zircuit/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/zircuit/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0xc1ce...)](https://layerzeroscan.com/api/explorer/zircuit/address/0xc1ce56b2099ca68720592583c7984cab4b6d7e7a)
[LZ Executor
(0xcCE4...)](https://layerzeroscan.com/api/explorer/zircuit/address/0xcCE466a522984415bC91338c232d98869193D46e)
[LZ Dead DVN
(0x9C06...)](https://layerzeroscan.com/api/explorer/zircuit/address/0x9C061c9A4782294eeF65ef28Cb88233A987F4bdD) |  |
| [### Zircuit Testnet](#zircuit-testnet) | 48899 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/zircuit-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40275 | [SendUln302
(0x4584...)](https://layerzeroscan.com/api/explorer/zircuit-testnet/address/0x45841dd1ca50265Da7614fC43A361e526c0e6160)
[ReceiveUln302
(0xd682...)](https://layerzeroscan.com/api/explorer/zircuit-testnet/address/0xd682ECF100f6F4284138AA925348633B0611Ae21)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/zircuit-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x1252...)](https://layerzeroscan.com/api/explorer/zircuit-testnet/address/0x12523de19dc41c91F7d2093E0CFbB76b17012C8d) |  |
| [### Zora Mainnet](#zora) | 7777777 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/zora/address/0x1a44076050125825900e736c501f859c50fE728c) | 30195 | [SendUln302
(0xeDf9...)](https://layerzeroscan.com/api/explorer/zora/address/0xeDf930Cd8095548f97b21ec4E2dE5455a7382f04)
[ReceiveUln302
(0x57D9...)](https://layerzeroscan.com/api/explorer/zora/address/0x57D9775eE8feC31F1B612a06266f599dA167d211)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/zora/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0x4f8B...)](https://layerzeroscan.com/api/explorer/zora/address/0x4f8B7a7a346Da5c467085377796e91220d904c15)
[LZ Dead DVN
(0x08aB...)](https://layerzeroscan.com/api/explorer/zora/address/0x08aB92e05bA1dEC2C5bb876caa0Af60cAede1D17) |  |
| [### Zora Sepolia Testnet](#zora-sepolia) | 999999999 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/zora-sepolia/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40249 | [SendUln302
(0xF49d...)](https://layerzeroscan.com/api/explorer/zora-sepolia/address/0xF49d162484290EAeAd7bb8C2c7E3a6f8f52e32d6)
[ReceiveUln302
(0xC186...)](https://layerzeroscan.com/api/explorer/zora-sepolia/address/0xC1868e054425D378095A003EcbA3823a5D0135C9)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/zora-sepolia/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x4Cf1...)](https://layerzeroscan.com/api/explorer/zora-sepolia/address/0x4Cf1B3Fa61465c2c907f82fC488B43223BA0CF93) |  |
| [### inEVM Mainnet](#bb1) | 2525 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/bb1/address/0x1a44076050125825900e736c501f859c50fE728c) | 30234 | [SendUln302
(0x000C...)](https://layerzeroscan.com/api/explorer/bb1/address/0x000CC1A759bC3A15e664Ed5379E321Be5de1c9B6)
[ReceiveUln302
(0xe9AE...)](https://layerzeroscan.com/api/explorer/bb1/address/0xe9AE261D3aFf7d3fCCF38Fa2d612DD3897e07B2d)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/bb1/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xB041...)](https://layerzeroscan.com/api/explorer/bb1/address/0xB041cd355945627BDb7281f613B6E29623ab0110)
[LZ Dead DVN
(0x3aA7...)](https://layerzeroscan.com/api/explorer/bb1/address/0x3aA71d4C322eD650a78BC3A8BAB292e47a214A2c) |  |
| [### opBNB Mainnet](#opbnb) | 204 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/opbnb/address/0x1a44076050125825900e736c501f859c50fE728c) | 30202 | [SendUln302
(0x4428...)](https://layerzeroscan.com/api/explorer/opbnb/address/0x44289609cc6781fa2C665796b6c5AAbf9FFceDC5)
[ReceiveUln302
(0x9c9e...)](https://layerzeroscan.com/api/explorer/opbnb/address/0x9c9e25F9fC4e8134313C2a9f5c719f5c9F4fbD95)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/opbnb/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xACbD...)](https://layerzeroscan.com/api/explorer/opbnb/address/0xACbD57daAafb7D9798992A7b0382fc67d3E316f3)
[LZ Dead DVN
(0xd0a9...)](https://layerzeroscan.com/api/explorer/opbnb/address/0xd0a9ec7544106258c5836121fA032AE65c83f99B) |  |
| [### opBNB Testnet](#opbnb-testnet) | 5611 | [EndpointV2
(0x6EDC...)](https://layerzeroscan.com/api/explorer/opbnb-testnet/address/0x6EDCE65403992e310A62460808c4b910D972f10f) | 40202 | [SendUln302
(0xf514...)](https://layerzeroscan.com/api/explorer/opbnb-testnet/address/0xf514191C4a2D3b9A629fB658702015a5bCd570BC)
[ReceiveUln302
(0x4b21...)](https://layerzeroscan.com/api/explorer/opbnb-testnet/address/0x4b21Ad992A05Fb14e08df2cAF8d71A5c28b1f5E9)
[BlockedMessageLib
(0x0c77...)](https://layerzeroscan.com/api/explorer/opbnb-testnet/address/0x0c77d8d771ab35e2e184e7ce127f19ced31ff8c0)
[LZ Executor
(0x0F08...)](https://layerzeroscan.com/api/explorer/opbnb-testnet/address/0x0F0843fF71918B8Cb8e480BD8C581373BE3c1f9b) |  |
| [### re.al Mainnet](#real) | 111188 | [EndpointV2
(0x1a44...)](https://layerzeroscan.com/api/explorer/real/address/0x1a44076050125825900e736c501f859c50fE728c) | 30237 | [SendUln302
(0xC391...)](https://layerzeroscan.com/api/explorer/real/address/0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7)
[ReceiveUln302
(0xe184...)](https://layerzeroscan.com/api/explorer/real/address/0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043)
[BlockedMessageLib
(0x1ccb...)](https://layerzeroscan.com/api/explorer/real/address/0x1ccbf0db9c192d969de57e25b3ff09a25bb1d862)
[LZ Executor
(0xc097...)](https://layerzeroscan.com/api/explorer/real/address/0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f)
[LZ Dead DVN
(0xdA13...)](https://layerzeroscan.com/api/explorer/real/address/0xdA13808dBE60775e9B8B07a7cc9b98DFBd0f769f) |  |
| [### zkLink Mainnet](#zklink) | 810180 | [EndpointV2
(0x5c6c...)](https://layerzeroscan.com/api/explorer/zklink/address/0x5c6cfF4b7C49805F8295Ff73C204ac83f3bC4AE7) | 30301 | [SendUln302
(0x0104...)](https://layerzeroscan.com/api/explorer/zklink/address/0x01047601DB5E63b1574aae317BAd9C684E3C9056)
[ReceiveUln302
(0x9AB6...)](https://layerzeroscan.com/api/explorer/zklink/address/0x9AB633555E460C01f8c7b8ab24C88dD4986dD5A1)
[BlockedMessageLib
(0x3258...)](https://layerzeroscan.com/api/explorer/zklink/address/0x3258287147fb7887d8a643006e26e19368057377)
[LZ Executor
(0x06e5...)](https://layerzeroscan.com/api/explorer/zklink/address/0x06e56abD0cb3C88880644bA3C534A498cA18E5C8)
[LZ Dead DVN
(0x4c1a...)](https://layerzeroscan.com/api/explorer/zklink/address/0x4c1aC7b3C1223887dB9178d2437200B594EFFCf4) |  |
| caution
zkLink uses a unique compiler designed for zero-knowledge proof generation which generates different bytecode than the standard Solidity compiler (solc). | | | | | |
| [### zkLink Testnet](#zklink-testnet) | 810181 | [EndpointV2
(0xF3e3...)](https://layerzeroscan.com/api/explorer/zklink-testnet/address/0xF3e37ca248Ff739b8d0BebCcEAe1eeB199223dba) | 40283 | [SendUln302
(0xf1A4...)](https://layerzeroscan.com/api/explorer/zklink-testnet/address/0xf1A4f4FA1643ACf9f867b635A6d66a1990A3C217)
[ReceiveUln302
(0x0e2c...)](https://layerzeroscan.com/api/explorer/zklink-testnet/address/0x0e2c52BC2e119b1919e68f4F1874D4d30F6eF9fB)
[BlockedMessageLib
(0x110f...)](https://layerzeroscan.com/api/explorer/zklink-testnet/address/0x110f15766b4e6266b550a041ad5c7ec918adbe4a)
[LZ Executor
(0x0Cc6...)](https://layerzeroscan.com/api/explorer/zklink-testnet/address/0x0Cc6F5414996678Aa4763c3Bc66058B47813fa85) |  |
| caution
zkLink uses a unique compiler designed for zero-knowledge proof generation which generates different bytecode than the standard Solidity compiler (solc). | | | | | |
| [### zkSync Era Mainnet](#zksync) | 324 | [EndpointV2
(0xd07C...)](https://layerzeroscan.com/api/explorer/zksync/address/0xd07C30aF3Ff30D96BDc9c6044958230Eb797DDBF) | 30165 | [SendUln302
(0x07fD...)](https://layerzeroscan.com/api/explorer/zksync/address/0x07fD0e370B49919cA8dA0CE842B8177263c0E12c)
[ReceiveUln302
(0x0483...)](https://layerzeroscan.com/api/explorer/zksync/address/0x04830f6deCF08Dec9eD6C3fCAD215245B78A59e1)
[ReadLib1002
(0x2C6F...)](https://layerzeroscan.com/api/explorer/zksync/address/0x2C6FEDD430Be3c916292c3700144D4891c0FFedD)
[BlockedMessageLib
(0x0fdd...)](https://layerzeroscan.com/api/explorer/zksync/address/0x0fddfc529b5912e1cbe38ccedf8e226566e596d3)
[LZ Executor
(0x664e...)](https://layerzeroscan.com/api/explorer/zksync/address/0x664e390e672A811c12091db8426cBb7d68D5D8A6)
[LZ Dead DVN
(0x3F80...)](https://layerzeroscan.com/api/explorer/zksync/address/0x3F80157B0d0025C85f905b75b1Ee2386F6daf168) |  |
| caution
zkSync uses its own compiler called zkSync-solc, which generates different bytecode than the standard Solidity compiler (solc). | | | | | |
| [### zkSync Sepolia Testnet](#zksync-sepolia) | 300 | [EndpointV2
(0xe2Ef...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0xe2Ef622A13e71D9Dd2BBd12cd4b27e1516FA8a09) | 40305 | [SendUln302
(0xaF86...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0xaF862837316E00d2708Bd648c5FE87EdC7093799)
[ReceiveUln302
(0x5c12...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0x5c123dB6f87CC0d7e320C5CC9EaAfD336B5f6eF3)
[BlockedMessageLib
(0x9493...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0x9493a593f5ee4da6a18003783a71936243ebc04f)
[LZ Executor
(0x6E9b...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0x6E9bcFCbEdb7d1298E66cdE81ed9f39b1e12f935)
[LZ Dead DVN
(0x7750...)](https://layerzeroscan.com/api/explorer/zksync-sepolia/address/0x7750C0cBAE78Ddf514B9aEFeB2887143D3DBD203) |  |
| caution
zkSync uses its own compiler called zkSync-solc, which generates different bytecode than the standard Solidity compiler (solc). | | | | | |
info
**Endpoint Id** (`eid`) values have no relation to **Chain Id** (`chainId`) values. Since LayerZero spans both EVM and non-EVM chains, each Endpoint contract has a unique identifier known as the `eid` for determining which chain's `endpoint` to send to or receive messages from.
When using LayerZero contract methods, be sure to use the correct `eid` listed below:
* `30xxx`: refer to mainnet chains
* `40xxx`: refer to testnet chains
To see if a specific LayerZero contract supports another, use the `isSupportedEid()` method.
Contract Description[](#contract-description "Direct link to Contract Description")
-------------------------------------------------------------------------------------
| **Contract Name** | **Description** |
| --- | --- |
| **EndpointV2** | The primary entrypoint into LayerZero V2 responsible for managing cross-chain communications. It orchestrates message sending, receiving, and configuration management between various smart contract connections using message library contracts and internal mappings to track `OApp` specific settings. |
| **SendUln302** | A message library for sending cross-chain messages. It combines functionalities from `SendUlnBase` and `SendLibBaseE2` to ensure secure message dispatch. |
| **ReceiveUln302** | A message library for receiving and verifying cross-chain messages. It integrates `ReceiveUlnBase` and `ReceiveLibBaseE2` to maintain message integrity. |
| **SendUln301** | A version of the send message library compatible with `EndpointV1` for backwards compatibility with `EndpointV2`. |
| **ReceiveUln301** | A version of the receive message library compatible with `EndpointV1` for backwards compatibility with `EndpointV2`. |
| **LZ Executor** | A contract responsible for executing received cross-chain messages automatically with a specified `gas limit` and `msg.value` for a fee. |
| **LZ Dead DVN** | Represents a **Dead Decentralized Verifier Network (DVN)**. These contracts are placeholders used when the default LayerZero config is inactive and will require the OApp owner to manually configure the contract's config to use the pathway. |
Checking Default Configs[](#checking-default-configs "Direct link to Checking Default Configs")
-------------------------------------------------------------------------------------------------
To see the default configuration for a given pathway (i.e., from `Chain A` to `Chain B`), you can use [LayerZero Scan's Default Checker](https://layerzeroscan.com/tools/defaults?version=V2)
.

* [Contract Address Table](#contract-address-table)
* [Contract Description](#contract-description)
* [Checking Default Configs](#checking-default-configs)
---
# Tools Overview | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero Tools Overview
========================
This section of the documentation covers **three** key resources that help developers inspect and integrate with LayerZero’s cross-chain infrastructure.
Below is a summary of what each tool does and when you might want to use it.
LayerZero Scan (UI)[](#layerzero-scan-ui "Direct link to LayerZero Scan (UI)")
--------------------------------------------------------------------------------
The [**LayerZero Scan** overview page](/v2/tools/layerzeroscan/overview)
explains the **web-based block explorer** that showcases:
* **Cross-chain transactions** (messages) in a unified interface
* **Source/destination chain** details for any bridging operation
* **Individual transaction status** (delivered, pending, or failed)
* **Address search** to view bridging events associated with a particular user or contract
**Use it if**:
* You need a **visual** way to check cross-chain TX status.
* You want to see real-time bridging volume or debugging info for your messages.
LayerZero Scan Swagger API[](#layerzero-scan-swagger-api "Direct link to LayerZero Scan Swagger API")
-------------------------------------------------------------------------------------------------------
The [**API** page](/v2/tools/layerzeroscan/api)
documents the **Swagger-based** endpoints that expose the same cross-chain transaction data as the web UI, but in a programmatic manner:
* `GET /messages/{messageId}` to fetch message details
* `GET /transactions/{chainKey}/{txHash}` to see bridging logs for a particular chain TX
* Query-based endpoints to **filter** or **search** messages by chain, status, or time
**Use it if**:
* You want to **automate** cross-chain transaction queries (e.g., in a custom dashboard).
* You need to **poll or monitor** message statuses at scale (like for bridging analytics or notifications).
LayerZero Endpoint Metadata[](#layerzero-endpoint-metadata "Direct link to LayerZero Endpoint Metadata")
----------------------------------------------------------------------------------------------------------
The [**Endpoint Metadata** page](/v2/tools/endpoint-metadata)
details a **comprehensive JSON** file that maps:
* **All known LayerZero chain deployments** (bridging contract addresses, RPC endpoints, etc.)
* **Token metadata** on each chain (addresses, decimals, pegging info)
* **DVNs** (Decentralized Verifier Network addresses), chain explorers, environment flags, and more
It also explains how you can:
* Programmatically configure bridging by reading the `deployments` or `tokens` fields.
**Use it if**:
* You want to ensure you have the latest official addresses rather than manually hardcoding them.
Putting It All Together[](#putting-it-all-together "Direct link to Putting It All Together")
----------------------------------------------------------------------------------------------
* **LayerZero Scan** (web UI) → Quick visual debugging, real-time transaction lookup.
* **LayerZero Scan API** → Programmatic cross-chain transaction data retrieval and stats.
* **Endpoint Metadata** → Full listing of chain configs, bridging contracts, and tokens for advanced integrations or dynamic UIs.
Consider each tool a different piece of the puzzle:
* The **Scan** explorer helps confirm if a bridging transaction arrived safely.
* The **Scan API** helps you build your own custom dashboards or monitoring scripts.
* The **Endpoint Metadata** ensures your application always references the correct bridging addresses, token definitions, etc., across all LayerZero-supported networks.
For more context on how bridging works under the hood, see the rest of our [LayerZero documentation](/v2)
.
* [LayerZero Scan (UI)](#layerzero-scan-ui)
* [LayerZero Scan Swagger API](#layerzero-scan-swagger-api)
* [LayerZero Endpoint Metadata](#layerzero-endpoint-metadata)
* [Putting It All Together](#putting-it-all-together)
---
# Community Support | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
On this page
The LayerZero ecosystem is a fast-growing network of developers collaborating to build a better future across every blockchain network.
* Find the community on [Discord](https://layerzero.network/community)
.
* Engage with the [Telegram](https://t.me/joinchat/VcqxYkStIDsyN2Rh)
group.
* Find code on [GitHub](https://github.com/LayerZero-Labs/LayerZero)
.
Questions?[](#questions "Direct link to Questions?")
------------------------------------------------------
We love to answer questions! Questions help everyone learn together, so please don't hesitate to ask :)
Our [Discord](https://layerzero.network/community)
is for any real-time conversations, from brainstorming to debugging and troubleshooting. Participants often share interesting articles and other educational content. For in-depth considerations about important decisions and projects, we suggest contributing on GitHub. New issues, comments, and pull requests are welcome!
Feedback[](#feedback "Direct link to Feedback")
-------------------------------------------------
Your insights and experiences are vital to the continuous improvement of LayerZero's documentation and protocol. If you have suggestions, questions, or feedback, we strongly encourage you to share them with us.
If you come across anything in our documentation or protocol that you believe can be improved or if you have constructive feedback, please don't hesitate to let us know. Here's how you can do it:
### Leaving Feedback[](#leaving-feedback "Direct link to Leaving Feedback")
If your feedback is related to our documentation:
1. Depending on the issue, visit either our Documentation Repo or Protocol Repo.
2. Click on the **Issues** tab and then create a **New Issue**.
3. Provide a clear and detailed description of your suggestions, questions, or the issues you've encountered. This could include suggestions for additional content, clarifications, or corrections.
Once you've filled out the necessary details, submit the issue.
* [Questions?](#questions)
* [Feedback](#feedback)
* [Leaving Feedback](#leaving-feedback)
---
# Introduction to LayerZero V1 | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V1 Docs
On this page
[LayerZero](https://layerzero.network/)
is an open-source, immutable messaging protocol designed to facilitate the creation of omnichain, interoperable applications.
Developers can easily send arbitrary data, external function calls, and tokens with omnichain messaging while preserving full autonomy and control over their application.
info
[**LayerZero V2**](/v2)
is now live on both [**Mainnet and Testnet**](/v2/deployments/deployed-contracts)
, offering direct improvements for both existing, deployed applications on Endpoint V1, as well as new features that enhance the creation and scalability of omnichain applications deployed on the new Endpoint V2.
See the [**LayerZero V2 Documentation**](/v2)
.
Where can I find more information?[](#where-can-i-find-more-information "Direct link to Where can I find more information?")
------------------------------------------------------------------------------------------------------------------------------
For the message protocol design, check out the [V1 Whitepaper](https://layerzero.network/pdf/LayerZero_Whitepaper_Release.pdf)
found on the [website](https://layerzero.network/)
.
If you are looking for a detailed system architecture explanation, check out the architectures section on the [Endpoint](/v1/concepts/layerzero-endpoint)
and the Ultra-Light Node.
Code Examples[](#code-examples "Direct link to Code Examples")
----------------------------------------------------------------
Learn how to [integrate LayerZero](/v1/developers/evm/evm-guides/send-messages)
into your contracts and take a look at our deployed contracts for [Mainnet](/v1/developers/evm/technical-reference/deployed-contracts)
and [Testnet](/v1/developers/evm/technical-reference/deployed-contracts)
usage. If you want to see some examples to play around head over to [our Github](https://github.com/LayerZero-Labs/solidity-examples)
.
* See how to [send a LayerZero message](/v1/developers/evm/evm-guides/send-messages)
.
* Learn how to [move fungible tokens](/v1/developers/evm/evm-guides/contract-standards/oft-overview)
using the OFT Standard.
* Create [omnichain NFTs](/v1/developers/evm/evm-guides/contract-standards/onft-overview)
by inheriting the ONFT Standard.
More from LayerZero[](#more-from-layerzero "Direct link to More from LayerZero")
----------------------------------------------------------------------------------
### Questions?[](#questions "Direct link to Questions?")
Join the LayerZero community in our [Discord](https://layerzero.network/community)
to ask for help, as well as share your feedback or showcase what you have built with LayerZero!
### Careers[](#careers "Direct link to Careers")
LayerZero is growing. If you enjoy using our protocol and have a genuine interest in omnichain design, please check out [our current job openings](https://layerzero.network/careers)
.
* [Where can I find more information?](#where-can-i-find-more-information)
* [Code Examples](#code-examples)
* [More from LayerZero](#more-from-layerzero)
* [Questions?](#questions)
* [Careers](#careers)
---
# Solana Endpoint V1 Support | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V1 Docs
At the protocol level, Endpoint V2 is backwards compatible with Endpoint V1. This means, general message passing is supported out of the box between Endpoint V1 and Endpoint V2.
At the application level, customizations would be needed to support pathways between Endpoint V1 and Endpoint V2.
For example, to support OFT transfers between OFT deployments on Endpoint V1 and Solana (which is built on top of Endpoint V2), we have developed the [Solana OFT202 Program](/v1/developers/solana/oft)
. Note that Solana OFT202 is only compatible with Endpoint V1 OFT V2's. You can read more on this [here](/v1/developers/solana/oft#requirements)
.
---
# LayerZero Glossary | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero V2 Glossary
=====================
This glossary defines and explains key LayerZero concepts and terminology.
### Chain ID[](#chain-id "Direct link to Chain ID")
The native blockchain identifier assigned by the network itself (for example, `1` for Ethereum Mainnet, `42161` for Arbitrum Mainnet). This is distinct from LayerZero's Endpoint ID (EID), which is the protocol's internal identifier used to route messages between chains. When interacting with the LayerZero protocol, you'll primarily work with EIDs rather than chain IDs.
See [Endpoint](#endpoint)
for more details.
### Channel / Lossless Channel[](#channel--lossless-channel "Direct link to Channel / Lossless Channel")
A dedicated message pathway in LayerZero defined by four specific components: the sender OApp (source application contract), the source endpoint ID, the destination endpoint ID, and the receiver OApp (destination application contract).
The channel maintains message ordering through nonce tracking, ensuring messages are delivered exactly once and in the correct sequence. For example, if a token bridge on Ethereum (sender OApp) is communicating with its counterpart on Arbitrum (receiver OApp), their messages flow through a unique channel distinct from all other application pathways between these chains.
Each channel maintains its own independent message sequence, allowing multiple applications to communicate across the same chain pairs without interference.
### Compose / Composition[](#compose--composition "Direct link to Compose / Composition")
The ability to combine multiple cross-chain operations into a single transaction. Composition allows for complex cross-chain interactions while maintaining transaction integrity across multiple chains.
**Vertical Composability**
The traditional form of smart contract composability, where multiple function calls are stacked within a single transaction. In vertical composability, all operations must succeed together or the entire transaction reverts, providing atomic execution. For example, when a cross-chain token bridge receives tokens, it might atomically update balances, emit events, and trigger other contract functions. All these operations either complete successfully or fail together.
**Horizontal Composability**
LayerZero's unique approach to cross-chain composability using `endpoint.sendCompose` and `ILayerZeroComposer`. Unlike vertical composability, horizontal composability allows a receiving contract to split its execution into separate atomic pieces. Each piece can succeed or fail independently, removing the requirement for all-or-nothing execution. This enables more flexible cross-chain operations, as applications can handle partial successes and continue execution even if some components fail. For example, a cross-chain DEX might receive tokens in one atomic transaction, then initiate a separate composed transaction for performing the swap, allowing the token receipt to succeed even if the swap fails.
### Destination Chain[](#destination-chain "Direct link to Destination Chain")
The blockchain network that receives and processes a LayerZero message. The destination chain hosts the contract that will execute the received message's instructions through its `lzReceive` function.
### DVN (Decentralized Verifier Network)[](#dvn-decentralized-verifier-network "Direct link to DVN (Decentralized Verifier Network)")
A network of independent verifiers that validate message integrity between chains. DVNs are part of LayerZero's modular security model, allowing applications to configure multiple verification schemes for their messages.
### Endpoint[](#endpoint "Direct link to Endpoint")
The core, immutable smart contract deployed on each blockchain that serves as the entry and exit point for LayerZero messages. The Endpoint provides standardized interfaces for sending, receiving, and configuring messages. It's the primary interface through which applications interact with LayerZero.
### Executor[](#executor "Direct link to Executor")
Ensures the seamless execution of messages on the destination chain by following instructions set by the OApp owner on how to automatically deliver omnichain messages to the destination chain.
An off-chain service that monitors message verification status and executes verified messages on destination chains when all required DVNs have verified the message. Executors handle gas payments and message delivery.
It's a permissionless service that can be run by any party.
### GUID (Global Unique ID)[](#guid-global-unique-id "Direct link to GUID (Global Unique ID)")
A unique identifier generated for each LayerZero message that combines the message's nonce, source chain, destination chain, and participating contracts. GUIDs ensure messages can be tracked across the network and prevent replay attacks.
### Lazy nonce (lazy inbound nonce)[](#lazy-nonce-lazy-inbound-nonce "Direct link to Lazy nonce (lazy inbound nonce)")
A mechanism that tracks the highest consecutively delivered message number for a channel. Messages can be verified out of order, but they can only be executed sequentially starting from the lazy nonce. All messages before the message with lazyNonce have been verified. This ensures lossless message delivery while allowing parallel verification.
### LZ Config[](#lz-config "Direct link to LZ Config")
The file that declares the configuration for the OApp. Configuration refers to things such as the pathways (connections), DVN (Security Stack), and more. In our examples, this file has the default name of `layerzero.config.ts` but its name can be arbitrary. When needed, the LayerZero CLI expects the LZ config file via the `--oapp-config` flag. Check out the [LZ config in the OFT example](https://github.com/LayerZero-Labs/devtools/blob/main/examples/oft/layerzero.config.ts)
.
### `lzCompose`[](#lzcompose "Direct link to lzcompose")
_First, see [Compose](#compose)
to understand what composition is._
A function that enables horizontal composition by allowing a received message to trigger additional cross-chain messages. These composed messages are processed sequentially, creating chains of cross-chain operations.
### `lzRead`[](#lzread "Direct link to lzread")
Allows an OApp to request, receive and compute data from another blockchain by specifying the target chain and the block from which the state needs to be retrieved (including all historical data).
### `lzReceive`[](#lzreceive "Direct link to lzreceive")
The standard function implemented by LayerZero-compatible contracts to process incoming messages. When a message is delivered, the destination chain's Endpoint calls `lzReceive` on the target contract with the decoded message data.
### `lzSend`[](#lzsend "Direct link to lzsend")
The primary function used by the sender OApp to send messages through LayerZero. OApps call `endpoint.send()` on their local Endpoint, providing the destination details and message payload. The function initiates the cross-chain messaging process.
### Mesh Network[](#mesh-network "Direct link to Mesh Network")
LayerZero's network topology where every supported blockchain can directly communicate with every other supported blockchain. This creates a fully connected network without requiring intermediate chains or bridges.
### Message Library (MessageLib)[](#message-library-messagelib "Direct link to Message Library (MessageLib)")
Smart contracts that handle message payload packing on the source chain and verification on the destination chain.
MessageLibs are immutable and append-only, allowing protocols to add new verification methods while preserving existing ones. The Ultra Light Node (ULN) is the default MessageLib.
[Ultra-Light Node](#uln-ultra-light-node)
is an implementation of a Message Library.
### Message Options[](#message-options "Direct link to Message Options")
A required parameter in LayerZero transactions that specifies how messages should be handled on the destination chain. Message options must be provided either through enforced options configured at the application level or as explicit parameters in the transaction. These options control critical execution parameters like gas limits for `lzReceive` calls, composed message handling, and native token drops on the destination chain.
When calling functions like `quote()` or `send()`, the protocol will revert if no valid message options are present. This is a safety mechanism to ensure every cross-chain message has explicit instructions for its execution. Applications can enforce minimum gas requirements using `OAppOptionsType3`, which combines any user-provided options with the application's required settings. For example, an OFT contract might enforce minimum gas limits for token transfers while allowing users to specify additional gas for composed operations.
### Nonce[](#nonce "Direct link to Nonce")
A unique identifier for the message _within specific messaging channel_. Prevents replay attacks and censorship by defining a strong gapless ordering between all nonces in each channel. Each channel maintains its own independent nonce counter.
Difference between nonce and GUID:
* Nonce is unique within a channel (between two endpoints) and sequential.
* GUID is unique across all channels and is not sequential, allowing for tracking messages across the entire LayerZero network.
### OApp (Omnichain Application)[](#oapp-omnichain-application "Direct link to OApp (Omnichain Application)")
A smart contract that implements LayerZero's messaging interface for cross-chain communication. The base contract type for building omnichain applications.
### OFT (Omnichain Fungible Token)[](#oft-omnichain-fungible-token "Direct link to OFT (Omnichain Fungible Token)")
**Omnichain Fungible Token** - A token standard that extends fungible token standards such as the EVM's [ERC20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/)
, [Solana's SPL / Token-2022](https://solana.com/ja/docs/core/tokens)
, and [Aptos' Fungible Asset](https://aptos.dev/en/build/smart-contracts/fungible-asset)
, with LayerZero's messaging capabilities, enabling seamless token transfers across different blockchains. OFTs maintain a unified total supply across all chains while allowing tokens to be transferred between networks.
This standard works by debiting (burn / lock) tokens on the source chain whenever an omnichain transfer is initiated, sending a message via the protocol, and delivering a function call to the destination contract to credit (mint / unlock) the same number of tokens debited. This creates a unified supply across all networks LayerZero supports that the OFT is deployed on.
Vanilla OFTs will utilize burn and mint:
 
OFT Adapters will utilize lock and mint:
 
### OMP (Omnichain Messaging Protocol)[](#omp-omnichain-messaging-protocol "Direct link to OMP (Omnichain Messaging Protocol)")
The core protocol that enables secure cross-chain communication. An OMP provides the fundamental messaging capabilities that higher-level applications build upon.
### ONFT (Omnichain Non-Fungible Token)[](#onft-omnichain-non-fungible-token "Direct link to ONFT (Omnichain Non-Fungible Token)")
Omnichain Non-Fungible Token - A token standard that extends ERC721 with LayerZero's messaging capabilities, enabling NFT transfers across different blockchains while maintaining their unique properties and ownership history.
### Packet[](#packet "Direct link to Packet")
The standardized formatted data structure for messages in LayerZero, containing the message payload along with routing and verification information. Packets include fields like nonce, source chain, destination chain and the actual message data.
### Payload[](#payload "Direct link to Payload")
The actual data being sent in a cross-chain LayerZero message. This could be token transfer information, function calls, or any other data the application needs to transmit between chains.
### Security Stack[](#security-stack "Direct link to Security Stack")
The combination of MessageLib, DVNs, and other security parameters that an application configures for its cross-chain messages. Each application can (and should) customize its security stack to balance security, cost, and performance.
### Source Chain[](#source-chain "Direct link to Source Chain")
The blockchain from which a cross-chain message is being sent.
### ULN (Ultra Light Node)[](#uln-ultra-light-node "Direct link to ULN (Ultra Light Node)")
The default MessageLib in LayerZero that implements a flexible verification system using configurable DVN sets. ULN allows applications to specify required and optional verifiers along with confirmation thresholds.
`Ultra Light Node 302` is a MessageLib for Endpoint V2 applications. `Ultra Light Node 301` is a MessageLib for existing Endpoint V1 applications wanting to utilize the new Security Stack and Executor.
### Wire / Wiring[](#wire--wiring "Direct link to Wire / Wiring")
"Wiring" in LayerZero refers to the process of connecting [OApps](#oapp-omnichain-application)
across different blockchains to enable cross-chain communication. The process involves setting peer addresses between OApps, configuring [DVNs](#dvn-decentralized-verifier-network)
, and message execution settings. All these actions are done via submitting transactions to the relevant contracts (e.g. OApp, [Endpoint](#endpoint)
) on each chain. Once wired, contracts can send and receive messages between specific source and destination contracts.
### Worker[](#worker "Direct link to Worker")
A general term for offchain or onchain components that perform specific tasks in the LayerZero network, including executors and DVNs.
### X of Y of N[](#x-of-y-of-n "Direct link to X of Y of N")
A configurable security model pattern where:
* **X**: This is the number of **required DVNs** — each one is a specific, non-fungible verifier network that must always verify a message.
* **Y**: This is the total number of DVNs needed for a message to be considered verified. It includes the required DVNs (X) plus a set **threshold of optional DVNs**. Any of the optional DVNs can contribute toward this threshold since they are fungible; it doesn’t matter which optional DVNs verify, as long as the required number is met.
* **N**: This is the **total pool of DVNs** available for verification. It includes both the specific required DVNs (X) and all optional DVNs from which verification could be collected.
For example, consider a "1 of 3 of 5" setup:
* **X = 1**: One specific DVN must always sign (non-fungible).
* **Y = 3**: A total of three DVNs are required. Since one is the required DVN, you need 2 additional verifier networks from the optional group (which are fungible).
* **N = 5**: The application has configured five DVNs in total available for verification (1 required, plus a threshold of 2 out of a pool of 4 optional, which totals to 5 DVNs in the stack).
In summary, "X of Y of N" means that out of a total pool (N) of DVNs, you must always have some specific DVN(s) (X) verify, and then you need additional verifications from the remaining pool (with any optional DVN counting) until you hit the overall threshold (Y).
In pratice, this is done by setting an array of required DVN contract addresses, an array of optional DVN addresses, and a threshold for the optional DVNs.
### Delegate[](#delegate "Direct link to Delegate")
An address that an Omnichain Application (OApp) authorizes to act on its behalf within LayerZero’s protocol. Specifically:
* **Authorization:** The OApp calls `setDelegate(address _delegate)`, registering a delegate that can perform configuration changes.
* **Permissions:** Once set, both the OApp itself **and** its Delegate are the **only** parties allowed to update LayerZero settings (e.g., security thresholds, channel configurations). Any unauthorized caller will revert with `LZ_Unauthorized`.
This ensures that each application can securely delegate configuration rights.
* [Chain ID](#chain-id)
* [Channel / Lossless Channel](#channel--lossless-channel)
* [Compose / Composition](#compose--composition)
* [Destination Chain](#destination-chain)
* [DVN (Decentralized Verifier Network)](#dvn-decentralized-verifier-network)
* [Endpoint](#endpoint)
* [Executor](#executor)
* [GUID (Global Unique ID)](#guid-global-unique-id)
* [Lazy nonce (lazy inbound nonce)](#lazy-nonce-lazy-inbound-nonce)
* [LZ Config](#lz-config)
* [`lzCompose`](#lzcompose)
* [`lzRead`](#lzread)
* [`lzReceive`](#lzreceive)
* [`lzSend`](#lzsend)
* [Mesh Network](#mesh-network)
* [Message Library (MessageLib)](#message-library-messagelib)
* [Message Options](#message-options)
* [Nonce](#nonce)
* [OApp (Omnichain Application)](#oapp-omnichain-application)
* [OFT (Omnichain Fungible Token)](#oft-omnichain-fungible-token)
* [OMP (Omnichain Messaging Protocol)](#omp-omnichain-messaging-protocol)
* [ONFT (Omnichain Non-Fungible Token)](#onft-omnichain-non-fungible-token)
* [Packet](#packet)
* [Payload](#payload)
* [Security Stack](#security-stack)
* [Source Chain](#source-chain)
* [ULN (Ultra Light Node)](#uln-ultra-light-node)
* [Wire / Wiring](#wire--wiring)
* [Worker](#worker)
* [X of Y of N](#x-of-y-of-n)
* [Delegate](#delegate)
---
# Protocol Overview | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
To send a cross-chain message, a user must write a transaction on both the source and destination blockchains.
At its core, the LayerZero protocol defines a **channel** between a `sender` and a `receiver` smart contract by leveraging two key components:
* **Source and Destination Endpoints:**
Each supported blockchain deploys an immutable, permissionless Endpoint contract. On the source chain, a smart contract calls the Endpoint’s entry function (`endpoint.send()`) to send a message. On the destination chain, a smart contract authorizes the Endpoint to act as an exit point to receive and process that same message (`endpoint.lzReceive()`).
* **Channel Definition:**
A unique messaging channel in LayerZero is defined by four specific components:
1. **Sender Contract (Source OApp):** The contract initiating the cross-chain communication.
2. **Source Endpoint ID:** The identifier for the Endpoint on the source chain.
3. **Destination Endpoint ID:** The identifier for the Endpoint on the destination chain.
4. **Receiver Contract (Destination OApp):** The contract designated to receive and process the message on the destination chain.
Within each channel, message ordering is maintained through nonce tracking. This ensures that messages are delivered exactly once.
For example, if a token bridge on one chain sends a message to its counterpart on another chain, the messages flow through a dedicated channel — distinct from all other application pathways between those chains — preserving the integrity and sequence of communication.
How the Protocol Works[](#how-the-protocol-works "Direct link to How the Protocol Works")
-------------------------------------------------------------------------------------------
 
1. **Message Dispatch on the Source Chain:**
A smart contract on the source blockchain initiates the process by calling the Endpoint's entry function. This call includes an arbitrary message payload, details of the destination Endpoint, and the receiver's contract address. The Endpoint then uses a configurable Message Library to generate a standardized Message Packet based on the sender contract’s configuration.
2. **Establishing a Secure Channel:**
The generated Message Packet is emitted as an event by the source Endpoint. This packet contains critical information—including source and destination Endpoint IDs, the sender's and receiver’s addresses, and the message payload—which collectively define a unique messaging channel.
3. **Verification and Nonce Management:**
On the destination chain, the configured Security Stack (Decentralized Verifier Networks) deliver the corresponding payload hash to the receiver contract's configured Message Library. Once the threshold of DVN verifications satisfies the [X of Y of N](/v2/concepts/glossary#x-of-y-of-n)
configuration, the Message Packet can be marked as verified and committed to the destination channel, ensuring exactly-once delivery.
4. **Message Execution on the Destination Chain:**
Finally, a caller (typically an authorized smart contract like the Executor) calls the Endpoint’s exit function `lzReceive` to trigger the execution of the verified message. This call delivers the message payload to the receiver contract, which can then execute its defined logic based on the incoming data.
Security and Flexibility[](#security-and-flexibility "Direct link to Security and Flexibility")
-------------------------------------------------------------------------------------------------
* **Immutable and Permissionless Design:**
The core Endpoint contracts are immutable and permissionless. This ensures that the protocol remains secure and resistant to unauthorized changes, regardless of which virtual machine (VM) or blockchain environment is used.
* **VM-Agnostic Integration:**
The LayerZero protocol itself is designed to be VM agnostic. The same fundamental principles apply whether you’re working with Solidity on Ethereum, Rust on Solana, Move on Aptos, or any other supported environment.
* **Independent Channel Management:**
Each channel between a given pair of endpoints maintains its own independent message sequence. This means that multiple applications can communicate across the same chain pairs without interference, providing scalability and flexibility in designing cross-chain solutions.
Further Reading[](#further-reading "Direct link to Further Reading")
----------------------------------------------------------------------
For more detailed technical insights into the protocol contracts for each specific virtual machine, please refer to the following overviews:
* **EVM Technical Overview:**
Learn how LayerZero’s protocol contracts are implemented for EVM-based chains, covering the Endpoint architecture, Message Libraries, and Workers.
[Read the EVM Protocol Overview](/v2/developers/evm/developer-overview)
* **Solana Technical Overview:**
Discover the adaptations made for Solana’s runtime, including cross-chain messaging through the LayerZero Endpoint and integrations with Solana’s unique architecture.
[Read the Solana Protocol Overview](/v2/developers/solana/technical-overview)
* **Aptos Technical Overview:**
Explore how LayerZero leverages the Aptos Move language and framework to implement secure and efficient cross-chain messaging on Aptos-based networks.
[Read the Aptos Protocol Overview](/v2/developers/aptos-move/overview)
* [How the Protocol Works](#how-the-protocol-works)
* [Security and Flexibility](#security-and-flexibility)
* [Further Reading](#further-reading)
---
# Aptos Overview | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V1 Docs
On this page
Getting Started
===============
Developing on LayerZero is as simple as it gets - just implement the `register_ua()`, `send()` and `lz_receive()` interfaces and your app is ready to send messages across connected chains.
To get started, check `register_ua()`, `send()` and `lz_receive()`.
Or dive right in with a simple code example [here](/v1/developers/aptos-move/code-examples/omnicounter.move)
.
FAQ[](#faq "Direct link to FAQ")
----------------------------------
Learn answers to [Frequently Asked Questions](/v1/developers/evm/troubleshooting/faq)
.
Talk to the Team[](#talk-to-the-team "Direct link to Talk to the Team")
-------------------------------------------------------------------------
* [Twitter](https://twitter.com/LayerZero_Labs)
* [Telegram](https://t.me/joinchat/VcqxYkStIDsyN2Rh)
* [Discord](https://layerzero.network/community)
* [Medium](https://medium.com/layerzero-official)
Official Website[](#official-website "Direct link to Official Website")
-------------------------------------------------------------------------
[https://layerzero.network/](https://layerzero.network/)
* [FAQ](#faq)
* [Talk to the Team](#talk-to-the-team)
* [Official Website](#official-website)
---
# Getting Started with LayerZero V2 on Solana | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
caution
The Solana OFT, Endpoint, and ULN Programs are currently in **Mainnet Beta**!
Any data, whether it's a fungible token transfer, an NFT, or some other smart contract input can be encoded on-chain as a bytes array, and delivered to a destination chain to trigger some action using LayerZero.
Because of this, any blockchain that broadly supports state propagation and events can be connected to LayerZero, like **Solana**.
tip
If you're new to LayerZero, we recommend reviewing [**"What is LayerZero?"**](/v2/concepts/getting-started/what-is-layerzero)
before continuing.
LayerZero provides sister **Solana Programs** that can communicate with the equivalent [Solidity Contract Libraries](/v2/developers/evm/overview)
you deploy on the Ethereum Virtual Machine (EVM).
These programs, like their solidity counterparts, simplify calling the [LayerZero Endpoint](/v2/concepts/protocol/layerzero-endpoint)
, provide message handling, interfaces for protocol configurations, and other utilities for interoperability:
* [`Omnichain Fungible Token (OFT)`](/v2/developers/solana/oft/program)
: an extension of `OApp` built for handling and supporting omnichain fungible token transfers.
* [`Omnichain Application (OApp)`](/v2/developers/solana/oapp/overview)
: the base program utilities for omnichain messaging and configuration.
Each of these programs standards implement common functions for **sending** and **receiving** omnichain messages.
Differences from the Ethereum Virtual Machine[](#differences-from-the-ethereum-virtual-machine "Direct link to Differences from the Ethereum Virtual Machine")
----------------------------------------------------------------------------------------------------------------------------------------------------------------
The full differences between Solidity and Solana are outside the scope of this tutorial (e.g., see [A Complete Guide to Solana Development for Ethereum Developers](https://solana.com/developers/evm-to-svm/complete-guide)
or [60 Days of Solana by RareSkills](https://www.rareskills.io/solana-tutorial)
), however we will cover some major considerations.
info
Skip this section if you already feel comfortable working within the Solana Virtual Machine (SVM) and the Solana Account Model.
### Writing Smart Contracts on Solana[](#writing-smart-contracts-on-solana "Direct link to Writing Smart Contracts on Solana")
To create a new ERC20 tokens on an EVM-compatible blockchain, a developer will have to inherit and redeploy the ERC20 smart contract.
**Solana is different.** Direct translation of Solidity contract inheritance to Solana is not possible because Rust does not have classes like Solidity. Instead, the [Solana Account Model](https://solana.com/docs/core/accounts)
enables program reusability.
 
Rather than deploying a new ERC20 smart contract for every new token you want to issue, you will instead send an [instruction](https://solana.com/docs/terminology#instruction)
to the **Solana Token Program** and create a new account, known as the **Mint Account**, which defines a set of values based off the program's interface (e.g., the number of tokens in circulation, decimal points, who can mint more tokens, and who can freeze tokens).
 
An account on Solana either is an executable program (i.e. a smart contract) or holds data (e.g. how many tokens you have).
info
Sometimes you’ll see Solana tokens referred to as “**SPL tokens**.” SPL stands for Solana Program Library, which is a set of Solana programs that the Solana team has deployed on-chain. SPL tokens are similar to ERC20 tokens, since every SPL token has a standard set of functionality.
A [Program Derived Address (PDA)](https://solana.com/docs/core/pda#breadcrumbs)
can then be used as the address (unique identifier) for an on-chain account, providing a method to easily store, map, and fetch program state. For example, a user's wallet and the SPL Token Mint can be used to derive the [Token Account](https://solana.com/docs/core/tokens#token-account)
.
 
To be compatible with the Solana Account Model, the **Omnichain Fungible Token (OFT) Program** extends the existing SPL token standard to interact with the LayerZero Endpoint smart contract.
 
The typical path for Solana program development involves interacting with or deploying executable code that defines your specific implementation, and then having other developers mint accounts that want to use that interface (e.g., the **SPL Token Program** defines how tokens behave, and the **Mint Accounts** define the different brands of SPL tokens).
The OFT Program is different in this respect. Because every Solana Program has an [Upgrade Authority](https://solana.com/docs/programs/deploying#overview-of-the-upgradeable-bpf-loader)
, and this authority can change or modify the implementation of all child accounts, developers wishing to create cross-chain tokens on Solana will need to deploy their own instance of the **OFT Program** that will have their own **OFT Store** Account.
 
This decision was made so that tokens minted off of the OFT Program will own their OFT's **Upgrade Authority**, rather than depend on LayerZero Labs to maintain a single, mutable OFT Program for all OFT Stores.
See ["Why Auditing the Code is Not Enough: A Discussion on Solana Upgrade Authorities"](https://neodyme.io/en/blog/solana_upgrade_authority/#intro)
for more information on how Upgrade Authorities behave on Solana.
info
LayerZero Labs may eventually in the future maintain with another entity a version of the OFT Program which users can use to create OFT Store Accounts from, but for now developers should consider deploying their own version of the OFT Program.
### Writing Solana Programs[](#writing-solana-programs "Direct link to Writing Solana Programs")
Solana Programs are most commonly developed with Rust. LayerZero OApp Programs should also be written in Rust to take advantage of LayerZero Solana libraries. See an [Overview of Developing On-chain Programs](https://solana.com/docs/programs/overview)
to learn more about Solana.
caution
While some initiatives exist to enable developers to write Solana programs in Solidity, compiling LayerZero Solidity Libraries using compilers like [**Neon EVM**](https://neon-labs.org/)
or [**Solang**](https://solang.readthedocs.io/en/latest/)
will **NOT** work with the Solana LayerZero Endpoint, because the LayerZero Rust Endpoint does not match 1:1 the Solidity Endpoint interface.
* [Differences from the Ethereum Virtual Machine](#differences-from-the-ethereum-virtual-machine)
* [Writing Smart Contracts on Solana](#writing-smart-contracts-on-solana)
* [Writing Solana Programs](#writing-solana-programs)
---
# LayerZero Endpoint | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero Endpoint
==================
The LayerZero Endpoint is the immutable, permissionless protocol entrypoint for sending and receiving omnichain messages.
Every LayerZero message passes through the Endpoint. It not only ensures secure and exactly-once message processing, but also will be your home for managing messaging channels, configurations, and fees.
Below is an overview of the five core modules that comprise the Endpoint and the role each plays:
Endpoint Interface[](#endpoint-interface "Direct link to Endpoint Interface")
-------------------------------------------------------------------------------
The core interface defines the essential data structures and key functions used for transmitting messages between blockchains. It establishes:
| **Functionality** | **Description** |
| --- | --- |
| **Messaging Parameters** | Defines the destination endpoint identifier, receiver address, message payload, and worker options. |
| **Messaging Receipts** | Returns a unique global identifier (GUID) and a nonce with each send call to track messages. |
| **Key Methods** | Implements the core methods `quote`, `send`, `verify`, and `lzReceive` that all applications and workers routinely use. |
_This interface guarantees every message is uniquely identified, correctly routed, and has its fees and security checks properly handled._
Message Channel Management[](#message-channel-management "Direct link to Message Channel Management")
-------------------------------------------------------------------------------------------------------
This module tracks and manages messages along each distinct communication pathway.
| **Functionality** | **Description** |
| --- | --- |
| **Nonce Tracking** | Maintains gapless, monotonically increasing nonces per sender, receiver, and chain to enforce exactly‑once delivery. |
| **Payload Hash Recording** | Stores the verified hash of each message payload to ensure message integrity before execution. |
| **State Management** | Manages transitions (delivered, skipped, or burned) to maintain the channel’s integrity. |
_Together, these functions create a lossless communication pathway essential for reliable cross‑chain messaging._
Message Library Management[](#message-library-management "Direct link to Message Library Management")
-------------------------------------------------------------------------------------------------------
This module enables applications (OApps) to tailor the security threshold, finality, executor, and more.
| **Functionality** | **Description** |
| --- | --- |
| **Custom Library Selection** | Allows an application to choose a specific messaging library for different operations (e.g., [send](/v2/concepts/applications/oapp-standard#generic-message-passing)
versus [read](/v2/concepts/applications/read-standard#how-omnichain-queries-lzread-work)
); defaults to the standard library if not set. |
| **Worker Configuration** | Configures off‑chain workers (e.g, DVNs [X-of-Y-of-N](/v2/concepts/protocol/message-security#configurable-channellevel-security-xofyofn)
and Executor address) and finality settings on a per‑channel basis. |
This flexibility enables each application to customize its security and fee management settings rather than relying on a fixed validator set and standard.
Send Context and Reentrancy Protection[](#send-context-and-reentrancy-protection "Direct link to Send Context and Reentrancy Protection")
-------------------------------------------------------------------------------------------------------------------------------------------
The Messaging Context module ensures:
| **Functionality** | **Description** |
| --- | --- |
| **Unique Send Context** | Tags each outbound message with a combination of the destination endpoint and sender address, preventing reentrancy. |
| **Reentrancy Guard** | Implements a dedicated modifier to prevent overlapping message processing. |
_These features maintain the integrity of the messaging process, ensuring that each message is processed in isolation._
Message Composition[](#message-composition "Direct link to Message Composition")
----------------------------------------------------------------------------------
"Arbitrary runtime dispatch" refers to the ability of a virtual machine (like the EVM) to decide dynamically at runtime which function to call based on input data. Not every blockchain virtual machine supports this, which limits how dynamically contracts can interact.
The Messaging Composer provides a standardized way to compose and send follow‑up messages within multistep cross‑chain workflows.
| **Feature** | **Description** |
| --- | --- |
| **Standardized Composition** | Stores a composed message payload on-chain, which can later be retrieved and passed to a callback via `lzCompose`. |
| **Lossless, Exactly‑Once Delivery** | Inherits the same guarantees as the core messaging functions, ensuring that each composed message maintains integrity and finality. |
| **Fault Isolation** | Decouples composed messages from primary transactions so that errors remain isolated, simplifying troubleshooting. |
_This module enables advanced cross‑chain interactions without compromising security or finality._
Summary[](#summary "Direct link to Summary")
----------------------------------------------
The LayerZero Endpoint is the single, immutable entry and exit point for cross‑chain messaging, built on five core modules:
| **Module** | **Primary Role** |
| --- | --- |
| **Core Interface** | Defines foundational messaging structures and methods to ensure unique identification and proper routing. |
| **Messaging Channel** | Tracks nonces and payload hashes between senders and receivers, enforcing exactly‑once, lossless delivery. |
| **Message Library Manager** | Provides flexibility for applications to configure custom messaging libraries and worker settings. |
| **Messaging Context** | Supplies execution context and reentrancy protection to safeguard message processing. |
| **Messaging Composer** | Standardizes the composition and dispatch of follow‑up messages, enabling advanced cross‑chain workflows without compromising security. |
Together, these modules guarantee that every message sent and received via LayerZero is processed securely, efficiently, and reliably; no matter which blockchain the message originates from or is delivered to.
* [Endpoint Interface](#endpoint-interface)
* [Message Channel Management](#message-channel-management)
* [Message Library Management](#message-library-management)
* [Send Context and Reentrancy Protection](#send-context-and-reentrancy-protection)
* [Message Composition](#message-composition)
* [Summary](#summary)
---
# What is LayerZero V2? | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
What is LayerZero V2?
=====================
**LayerZero is a messaging protocol, not a blockchain.** Using smart contracts deployed on each chain, in combination with [Decentralized Verifier Networks (DVNs)](/v2/concepts/modular-security/security-stack-dvns)
and [Executors](/v2/concepts/permissionless-execution/executors)
, LayerZero enables different blockchains to seamlessly interact with one another.
 
In LayerZero V2, message verification and execution have been separated into two distinct phases, providing developers with more control over their application's [security configuration](#x-of-y-of-n-message-authentication)
and [independent execution](#independent-message-execution)
.
Combined with [improved handling](#improved-message-handling)
, [message throughput](#higher-message-throughput)
, [programmability](#improved-programmability)
, and other contract specific improvements, LayerZero V2 provides a more flexible, performant, and future-proof messaging protocol.
Start reading more about this design in the [Protocol Overview](/v2/concepts/protocol/protocol-overview)
.
New Security & Execution[](#new-security--execution "Direct link to New Security & Execution")
------------------------------------------------------------------------------------------------
[LayerZero V2](https://layerzero.network/)
offers direct improvements for both existing, deployed applications on Endpoint V1, as well as new features that enhance the creation and scalability of omnichain applications deployed on the new Endpoint V2.
Applications deployed on Endpoint V1 can receive two main overhauls to application security and execution by migrating their application's Message Library to **Ultra Light Node 301**. See the [Migration Guide](/v2/concepts/v2-migration)
to learn more.
### X of Y of N Message Authentication[](#x-of-y-of-n-message-authentication "Direct link to X of Y of N Message Authentication")
The new **Ultra Light Node 301 (V1)** and **Ultra Light Node 302 (V2)** allow application owners to configure a custom [Security Stack](/v2/concepts/modular-security/security-stack-dvns)
, choosing a set of different Decentralized Verifier Networks (DVNs) to verify the payload hash on the destination MessageLib. A subset of these DVNs are **all** required (`X`) to verify the payload hash, and a threshold (`Y`) of a set of optional DVNs (`N`) must also verify the same payload hash before the packet can be delivered.
OApp owners can now utilize multiple verification models to achieve a desired security and cost-efficiency outcome based on their application's needs.
You can select between the following DVNs at launch, or permissionlessly [Build DVNs](/v2/workers/off-chain/build-dvns)
Security Stack (DVNs) can be found [here](/v2/concepts/modular-security/security-stack-dvns)
.
### Independent Message Execution[](#independent-message-execution "Direct link to Independent Message Execution")
In LayerZero V1, the Relayer handled both the verification and execution of messages:
* **Oracle**: Handled the verification of message block headers.
* **Relayer**: Handled the verification of tx-proofs and the execution of messages.
In LayerZero V2, the verification of messages is now handled by the [Security Stack](/v2/developers/evm/configuration/dvn-executor-config#send-config-type-uln-security-stack)
, and execution by [Executors](/v2/concepts/permissionless-execution/executors)
:
* **Security Stack**: your application's selected (`X of Y of N`) DVNs.
* **Executor (Optional)**: your application's selected automated caller for receiving messages.
**For new applications deployed on Endpoint V2, this caller is completely permissionless.**
New Protocol Contracts[](#new-protocol-contracts "Direct link to New Protocol Contracts")
-------------------------------------------------------------------------------------------
In addition to [New Message Libraries](/v2/concepts/protocol/message-library#available-libraries)
, LayerZero V2 includes improvements to the core protocol architecture.
Developers can take advantage of higher message throughput on certain blockchains, improved programmability, smaller contract sizes, and more by deploying applications using the [Endpoint V2 Contract Standards](/v2/concepts/protocol/contract-standards)
.
### Improved Message Handling[](#improved-message-handling "Direct link to Improved Message Handling")
Because the V2 protocol splits the verification and execution of messages, message nonces can now be executed out of order while still maintaining censorship resistance:
* **`Verified`**: the nonce of the [Message Packet](/v2/concepts/protocol/packet)
has successfully been verified, and awaits execution.
* **`Delivered`**: the message has successfully been executed and received by the destination application.
In V1, by default, if a sent message failed to execute on destination, the relevant pathway would be blocked by a `storedPayload` event that would temporarily stop all subsequent messages from being executed.
Now by default, the subsequent flow of messages will continue to be delivered and executed even if a previous message failed to execute.
Ordered execution can still be enabled at the application level by configuring [Ordered Message Delivery](/v2/developers/evm/oapp/message-design-patterns#ordered-delivery)
.
### Higher Message Throughput[](#higher-message-throughput "Direct link to Higher Message Throughput")
This [Unordered Message Delivery](/v2/developers/evm/oapp/message-design-patterns#message-ordering)
offers the highest possible message throughput (i.e., the chain itself), by using improved on-chain nonce tracking via a **Lazy Inbound Nonce** and **Inbound Nonce** as pointers for where to try message execution.
* **Lazy Inbound Nonce**: the highest executed message nonce in the system.
* **Inbound Nonce**: the latest verified message nonce, where all preceding nonces have also been verified.
Since nonces must be verified before they can be executed, this system enables LayerZero V2 to verify and losslessly execute packets out-of-order, streamlining message execution without compromising censorship resistance.
### Improved Programmability[](#improved-programmability "Direct link to Improved Programmability")
LayerZero V2 has also significantly improved programmability in several ways:
* **Simplified Protocol Contract Interfaces**: The improved contract interfaces in LayerZero V2 simplify message routing and handling, reducing the complexity involved in sending and receiving messages via the protocol. Developers can work more confidently and efficiently.
* **Path-Specific Libraries**: Path-specific libraries in Endpoint V2 enable developers to configure different MessageLibs for specific pathways (source to destination), providing applications with more [flexibility and customization](/v2/developers/evm/configuration/dvn-executor-config)
.
* **Horizontal Composability**: The new `sendCompose` and `lzCompose` interfaces, where external calls can be containerized into new message packets, allows applications to maintain a clear separation between the logic that handles the receipt of a message (`lzReceive`) and the logic of the external call itself (`lzCompose`). This ensures that each step is executed correctly and independently of others, enabling powerful [cross-chain interactions](/v2/developers/evm/oapp/message-design-patterns)
.
### Smaller Contract Sizes[](#smaller-contract-sizes "Direct link to Smaller Contract Sizes")
LayerZero V2 introduces several improvements to enhance gas efficiency for developers and users interacting with LayerZero contracts. Let's break down these improvements:
* **Optimized Base Contracts**: All [LayerZero Contract Standards](/v2/developers/evm/overview)
have been restructured to reduce the inherited gas cost from base contracts.
* **Compiler Efficiency**: Improvements in the contracts lead to better compiler optimization, which in turn reduces the deployment and execution gas costs.
### Chain Compatibility[](#chain-compatibility "Direct link to Chain Compatibility")
V2 also significantly improves chain compatibility, further empowering developers to build versatile and efficient omnichain applications across a wider range of blockchains.
* **Chain-Agnostic Design**: The protocol defines isolation between composed contract calls (`composeSend` to store data followed by `lzCompose` to compose the contract). This enables developers to build more uniform application designs across blockchains with different environment assumptions (e.g., lack of runtime dispatch). This is important for achieving broad compatibility with non-EVM chains and unifying the OApp interface across every chain.
* **Improved Gas Payment Options**: The Endpoint can now specify an alternative gas token on a given chain during deployment. This flexibility accommodates blockchains that may have unique gas mechanisms or fee models.
* **Specific Library Defaults**: Endpoints now support a different default library per chain pathway. This feature allows for more streamlined and efficient message processing that is tailored to the specific characteristics and unique requirements of each chain pair.
These improvements offer a more chain-agnostic approach to message handling, helping OApp developers design a single application architecture that can be unified across EVM and non-EVM chains.
Consistent Security Standards[](#consistent-security-standards "Direct link to Consistent Security Standards")
----------------------------------------------------------------------------------------------------------------
* **Application Level Control**: While application contracts can opt into pre-defined curated defaults, LayerZero gives you the choice to [configure your application's settings](/v2/developers/evm/configuration/dvn-executor-config)
for every pathway, offering unparalleled flexibility and security.
* **Immutable Core Contracts**: LayerZero only uses immutable core contracts. This provides developers with a long-term stable and predictable interface to interact with, ensuring that security and reliability are never compromised by external updates.
* **Backwards Compatibility**: LayerZero's on-chain message libraries are immutable and can never be removed or deprecated. LayerZero will always be backwards-compatible with previous MessageLib versions.
Get Started[](#get-started "Direct link to Get Started")
----------------------------------------------------------
LayerZero offers a fully integrated suite of [Contract Standards](/v2/developers/evm/overview)
to help you quickly build, launch, and scale your omnichain applications.
Start learning about LayerZero's architecture by either reading the [Protocol Overview](/v2/concepts/protocol/protocol-overview)
or the [V2 Whitepaper](https://layerzero.network/publications/LayerZero_Whitepaper_V2.0.pdf)
.
Have questions? You can also ask for help or follow development in our [Discord](https://layerzero.network/community)
.
* [New Security & Execution](#new-security--execution)
* [X of Y of N Message Authentication](#x-of-y-of-n-message-authentication)
* [Independent Message Execution](#independent-message-execution)
* [New Protocol Contracts](#new-protocol-contracts)
* [Improved Message Handling](#improved-message-handling)
* [Higher Message Throughput](#higher-message-throughput)
* [Improved Programmability](#improved-programmability)
* [Smaller Contract Sizes](#smaller-contract-sizes)
* [Chain Compatibility](#chain-compatibility)
* [Consistent Security Standards](#consistent-security-standards)
* [Get Started](#get-started)
---
# Quickstart - Create Your First Omnichain App | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
This guide will walk you through the process of sending a simple cross-chain message using LayerZero. We cover both the traditional EVM setup as well as the Aptos (Move‑VM) approach. Choose the section that matches your target environment.
info
LayerZero enables seamless communication between different blockchain networks. In these examples, an action on one chain (e.g. **Ethereum**) triggers a reaction on another (e.g. **Aptos**) without a central relay.
 
Introduction[](#introduction "Direct link to Introduction")
-------------------------------------------------------------
LayerZero powers omnichain applications (OApps) by enabling cross‑chain messaging. These guides provide step‑by‑step instructions on deploying a simple OApp across chains—using an opinionated default configuration to ease the process.
We present two variants:
* **EVM-Based:** Using Hardhat (and Foundry) to deploy and wire Solidity contracts.
* **Aptos-Based:** Using the Aptos CLI and Move‑VM scripts to deploy and configure your omnichain app (OFT) on Aptos alongside your EVM deployments.
Disclaimer
The Aptos CLI is currently in **alpha**. While progress is being made toward a full build compatible with all create-lz-oapp examples, the CLI is not yet production-ready. For now, you can follow its progress in the LayerZero devtools repo and optionally try experimental builds. In the meantime, follow the examples for using the Aptos Typescript SDK to [**deploy and wire**](/v2/developers/aptos-move/configuration/dvn-executor-config)
or wait for the [**official create-lz-oapp Aptos release**](https://github.com/LayerZero-Labs/devtools/pull/1080)
.
* [Introduction](#introduction)
---
# Getting Started with Contract Standards | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
Use LayerZero's **Contract Standards** to easily start sending arbitrary data, tokens, and external calls using the protocol:
* [Omnichain Application (OApp)](/v2/developers/evm/oapp/overview)
: the base contract standard for omnichain messaging and configuration.
* [Omnichain Fungible Token (OFT)](/v2/developers/evm/oft/quickstart)
: an extension of `OApp` built for handling and supporting omnichain `ERC20` transfers.
* [Omnichain Non-Fungible Token (ONFT)](/v2/developers/evm/onft/quickstart)
: an extension built for handling and supporting omnichain `ERC721` transfers.
Each of these contract standards implement common functions for **sending**, **receiving**, and **configuring** omnichain messages via the protocol interface: the [LayerZero Endpoint](/v2/concepts/protocol/layerzero-endpoint)
contract.
* `OAppSender._lzSend`: internal function that calls `EndpointV2.send` to send a message as `bytes`.
* `OAppReceiver._lzReceive`: internal function that delivers the encoded message as `bytes` after the `Executor` calls `EndpointV2.lzReceive`.
This method of **encoding** send parameters and **decoding** them on the destination chain is the basis for how all OApps work.
Example Omnichain Application[](#example-omnichain-application "Direct link to Example Omnichain Application")
----------------------------------------------------------------------------------------------------------------
The `OApp` Standard contains both a **send** and **receive** interface.
info
This code snippet is already implemented in the Remix example below. Simply review this code to understand how it works internally.
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementersimport { OAppSender, MessagingFee, MessagingReceipt } from "./OAppSender.sol";// @dev Import the 'Origin' so it's exposed to OApp implementersimport { OAppReceiver, Origin } from "./OAppReceiver.sol";import { OAppCore } from "./OAppCore.sol";/** * @title OApp * @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality. */abstract contract OApp is OAppSender, OAppReceiver {}
You can use the **Remix IDE** to see how `OAppSender` and `OAppReceiver` work together for sending and receiving any arbitrary data to supported destination chains.
#### OAppSender.sol[](#oappsendersol "Direct link to OAppSender.sol")
[Open in Remix](https://remix.ethereum.org/#url=https://docs.layerzero.network/LayerZero/contracts/Source.sol&)
[What is Remix?](https://remix-ide.readthedocs.io/en/latest/index.html)
#### OAppReceiver.sol[](#oappreceiversol "Direct link to OAppReceiver.sol")
[Open in Remix](https://remix.ethereum.org/#url=https://docs.layerzero.network/LayerZero/contracts/Destination.sol)
[What is Remix?](https://remix-ide.readthedocs.io/en/latest/index.html)
### Prerequisites[](#prerequisites "Direct link to Prerequisites")
1. You should first be familiar with writing and deploying contracts to your desired blockchains. This involves understanding the specific smart contract language and the deployment process for those chains.
2. A wallet set up and funded for the chains you'll be working with.
### Deploying Your Contracts[](#deploying-your-contracts "Direct link to Deploying Your Contracts")
We'll deploy the **Source Contract** on `Sepolia`, and the **Destination Contract** on `Optimism Sepolia`:
info
This example can be used with any EVM-compatible blockchain that LayerZero supports.
1. Open MetaMask and select the `Ethereum Sepolia` network. Make sure you have native gas in the wallet connected.
2. In Remix under the **Deploy & Run Transactions** tab, select `Injected Provider - MetaMask` in the Environment list.
3. Under the Deploy section, fill in the [Endpoint Address](/v2/deployments/deployed-contracts)
for your current chain.
#### Sepolia Endpoint Address[](#sepolia-endpoint-address "Direct link to Sepolia Endpoint Address")
0x6edce65403992e310a62460808c4b910d972f10f
#### Optimism Sepolia Endpoint Address[](#optimism-sepolia-endpoint-address "Direct link to Optimism Sepolia Endpoint Address")
0x6edce65403992e310a62460808c4b910d972f10f
4. Click deploy, follow the MetaMask prompt to confirm the transaction, and wait for the contract address to appear under **Deployed Contracts**.
5. Repeat the above steps for any other chains you plan to deploy to and connect.
### Connecting Your Contracts[](#connecting-your-contracts "Direct link to Connecting Your Contracts")
To connect your OApp deployments together, you will need to call `setPeer` on both the Ethereum Sepolia and Optimism Sepolia OApp.
The function takes 2 arguments: `_eid`, the **destination** endpoint ID for the chain our other OApp contract lives on, and `_peer`, the destination OApp contract address in `bytes32` format.
// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol// @dev must-have configurations for standard OAppsfunction setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner { peers[_eid] = _peer; // Array of peer addresses by destination. emit PeerSet(_eid, _peer); // Event emitted each time a peer is set.}
To `setPeer` on `SourceOApp`, take the `DestinationOApp` address and call `OApp.addressToBytes32`. Use the returned output as the `_peer`.
Your `_peer` should look something like this: `0x0000000000000000000000000a3ecc421699e2eb7f53584d07165d95721a4ca7`.
By default, the `OApp` standard inherits `OAppReceiver` which uses this peer inside `lzReceive` to enforce that the sender is the expected origin address.
// LayerZero/V2/oapp/contracts/oapp/OAppCore.sol/** * @dev Entry point for receiving messages or packets from the endpoint. * @param _origin The origin information containing the source endpoint and sender address. * - srcEid: The source chain endpoint ID. * - sender: The sender address on the src chain. * - nonce: The nonce of the message. * @param _guid The unique identifier for the received LayerZero message. * @param _message The payload of the received message. * @param _executor The address of the executor for the received message. * @param _extraData Additional arbitrary data provided by the corresponding executor. * * @dev Entry point for receiving msg/packet from the LayerZero endpoint. */function lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) public payable virtual { // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp. if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender); // Ensure that the sender matches the expected peer for the source endpoint. if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender); // Call the internal OApp implementation of lzReceive. _lzReceive(_origin, _guid, _message, _executor, _extraData);}
tip
Remember, an EVM `address` is a `bytes20` value, so you must convert your address to `bytes32` when calling `setPeer`. This can also be easily be done by [**Zero Padding**](https://ethereum.stackexchange.com/questions/103901/can-you-convert-my-address-bytes20-type-to-a-bytes32-string)
the address until it is 32 bytes in length.
LayerZero uses `bytes32` for broad compatibility with non-EVM chains.
Pass the address of your destination contract as a `bytes32` value, as well as the destination endpoint ID.
* To send to Ethereum Sepolia, the Endpoint ID is: `40161`.
* To send to Optimism Sepolia, the Endpoint ID is: `40232`.
caution
You'll need to repeat this wiring on both contracts in order to send and receive messages. That means calling `setPeer` on both your `Ethereum Sepolia` and `Optimism Sepolia` contracts. **Remember to switch networks in MetaMask.**
If successful, you now should be setup to start sending cross-chain messages!
### Estimating Fees[](#estimating-fees "Direct link to Estimating Fees")
The LayerZero Protocol gas fees can vary based on your source chain, `DVNs`, `Executor`, and amount of native gas token you request in `_options`, so you should estimate fees before sending your first transaction.
The `OApp.quote` function invokes an internal `OAppSender._quote` to estimate the fees associated with a particular LayerZero transaction using four inputs:
* `_dstEid`: This is the identifier of the destination chain's endpoint where the transaction is intended to go.
* `_message`: This is the arbitrary message you intend to send to your destination chain and contract.
* `_options`: A bytes array that contains serialized execution options that tell the protocol the amount of gas to for the [Executor](/v2/concepts/permissionless-execution/executors)
to send when calling `lzReceive`, as well as other function call related settings.
* `_payInLzToken`: A boolean which determines whether to return the fee estimate in the native gas token or in ZRO token.
info
In this tutorial, you will deliver `50000` wei for the `lzReceive` call by passing `0x0003010011010000000000000000000000000000c350` as your `_options`. You will be quoted `50000` wei on the source chain, which the Executor will convert to the destination gas token and use in the call. See [**Message Execution Options**](/v2/developers/evm/configuration/options)
for all possible execution settings.
### Sending Your Message[](#sending-your-message "Direct link to Sending Your Message")
To use the `send` function, simply input a string into the `message` field that you wish to send to your destination chain.
#### Contract A[](#contract-a "Direct link to Contract A")
Remember to pass the `quote` in Remix under `VALUE` to pay the gas fees on the source and destination, as well as for the [Security Stack](/v2/concepts/modular-security/security-stack-dvns)
and Executor who verify and execute the messages. Then call `SourceOApp.send`!
#### Contract B[](#contract-b "Direct link to Contract B")
Your message may take a few minutes to appear in the destination block explorer, depending on which chains you deploy to.
### Tracking Your Message[](#tracking-your-message "Direct link to Tracking Your Message")
Finally, let's see what's happening in our transaction. Take your transaction hash and paste it into: [https://testnet.layerzeroscan.com/](https://testnet.layerzeroscan.com/)
You should see `Status: Delivered`, confirming your message has been delivered to its destination using LayerZero.
**Congrats, you just sent your first omnichain message!** 🥳
### Further Reading[](#further-reading "Direct link to Further Reading")
Now that you understand the basics for how OApps work, you should explore setting up your development environment and diving deeper into the omnichain contract standards!
* [Create LZ OApp Quickstart](/v2/developers/evm/create-lz-oapp/start)
* [OApp Quickstart](/v2/developers/evm/oapp/overview)
* [OFT Quickstart](/v2/developers/evm/oft/quickstart)
* [Example Omnichain Application](#example-omnichain-application)
* [OAppSender.sol](#oappsendersol)
* [OAppReceiver.sol](#oappreceiversol)
* [Prerequisites](#prerequisites)
* [Deploying Your Contracts](#deploying-your-contracts)
* [Sepolia Endpoint Address](#sepolia-endpoint-address)
* [Optimism Sepolia Endpoint Address](#optimism-sepolia-endpoint-address)
* [Connecting Your Contracts](#connecting-your-contracts)
* [Estimating Fees](#estimating-fees)
* [Sending Your Message](#sending-your-message)
* [Contract A](#contract-a)
* [Contract B](#contract-b)
* [Tracking Your Message](#tracking-your-message)
* [Further Reading](#further-reading)
---
# Sending Tokenized Assets | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
To transfer tokens to different blockchain networks using LayerZero, you have 3 options:
* [Build your own Omnichain Token](#building-your-own-omnichain-token)
using LayerZero contract standards.
* [Send native gas tokens](#sending-small-amounts-of-native-gas)
as part of your message's execution options.
* [Utilize a native bridge](#option-1-protocols-or-native-bridges-built-on-layerzero)
built on top of LayerZero (e.g., Stargate).
Building Your Own Omnichain Token[](#building-your-own-omnichain-token "Direct link to Building Your Own Omnichain Token")
----------------------------------------------------------------------------------------------------------------------------
The **Omnichain Fungible Token (OFT) Standard** and **Omnichain Non-Fungible Token (ONFT) Standard** are ideal for creating tokens that exist on multiple chains.
These standards allow tokens to be transferred across multiple blockchains without asset wrapping or middlechains, ensuring consistency and interoperability for holders.
For new tokens, inherit from `OFT` or `ONFT`.
For existing tokens, use `OFTAdapter` or `ONFTAdapter`.
To build a token using `OFT` or `ONFT`, you need to deploy the standard contracts on each chain where the token you own will or currently exists.
Read the [OFT Quickstart](/v2/developers/evm/oft/quickstart)
and the [ONFT Quickstart](/v2/developers/evm/onft/quickstart)
to learn more.
Sending Small Amounts of Native Gas[](#sending-small-amounts-of-native-gas "Direct link to Sending Small Amounts of Native Gas")
----------------------------------------------------------------------------------------------------------------------------------
Depending on your destination application's logic, you may want to transfer small amounts of native gas tokens for the destination chain's transaction fees or to help users onboard to the new blockchain.
LayerZero [Message Execution Options](/v2/developers/evm/configuration/options)
enable you to send small amounts of native gas as part of your cross-chain call or to a specific address on the destination chain:
* **`lzReceive`**: Send `gasLimit` AND / OR `msg.value` as part of the destination `EndpointV2.lzReceive` call.
* **`lzCompose`**: Send `gasLimit` AND / OR `msg.value` as part of the destination `EndpointV2.lzCompose` call.
* **`lzNativeDrop`**: Send an `_amount` of native gas in wei to a specific `_receiver` address.
These gas amounts will be paid for on the source chain by the caller of `EndpointV2.send` within your application, abstracting gas management from your users.
For more information, see [Transaction Pricing](/v2/developers/evm/technical-reference/tx-pricing)
.
Moving Native Assets (e.g., wETH, USDC, USDT)[](#moving-native-assets-eg-weth-usdc-usdt "Direct link to Moving Native Assets (e.g., wETH, USDC, USDT)")
---------------------------------------------------------------------------------------------------------------------------------------------------------
To move native assets that have already been deployed by another contract owner, two methods exist to help your development:
### Option 1: Protocols or Native Bridges Built on LayerZero[](#option-1-protocols-or-native-bridges-built-on-layerzero "Direct link to Option 1: Protocols or Native Bridges Built on LayerZero")
Utilize a protocol, decentralized exchange (DEX), or native asset bridge built on LayerZero (e.g., [Stargate](https://stargateprotocol.gitbook.io/stargate/developers/evm/how-to-swap)
) for transferring native assets between chains.
**Functionality:** Stargate and similar platforms handle the creation of asset pools, facilitating the easy movement of native assets across multiple chains.
**Advantages:** This option enables you to utilize existing liquidity and composability with your smart contracts without the need for deploying the OFT Standards directly.
Read the [Stargate Docs](https://stargateprotocol.gitbook.io/stargate/)
for how to transfer and swap cross-chain assets in your smart contracts.
### Option 2: Wrapped Asset Bridges[](#option-2-wrapped-asset-bridges "Direct link to Option 2: Wrapped Asset Bridges")
If you run your own blockchain, you can [Contact LayerZero Labs](https://layerzeronetwork.typeform.com/to/U9hMgxf1)
to deploy a [LayerZero Endpoint](/v2/concepts/protocol/layerzero-endpoint)
contract on your network. This enables the creation of a wrapped asset bridge to easily move existing assets to your chain.
**Wrapped Asset Bridge:** The bridge locks tokens on the source chain and mints equivalent tokens on the destination chain using the OFT Standard.
caution
This method is not advisable if this bridge will not be endorsed by the chain, as it requires acceptance and liquidity to be provided for the new token standard (e.g., "yourUSDC") by DeFi applications. Established tokens or those endorsed by the chain will have better composability and usability.
* [Building Your Own Omnichain Token](#building-your-own-omnichain-token)
* [Sending Small Amounts of Native Gas](#sending-small-amounts-of-native-gas)
* [Moving Native Assets (e.g., wETH, USDC, USDT)](#moving-native-assets-eg-weth-usdc-usdt)
* [Option 1: Protocols or Native Bridges Built on LayerZero](#option-1-protocols-or-native-bridges-built-on-layerzero)
* [Option 2: Wrapped Asset Bridges](#option-2-wrapped-asset-bridges)
---
# LayerZero V2 EVM Technical Overview | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
1. A user calls a smart contract `OApp` on the source chain and pays a fee to send a cross-chain message to the `Endpoint`.
2. The `Endpoint` check the validity of the cross-chain message and assigns each job to the `OApp` configured `DVNs` (Decentralized Verifier Networks) and `Executor` to execute the cross-chain message.
3. The `DVNs` verify the message on the destination chain. After the required and optional DVNs have verified the message, the message is to be inserted (committed) in the message channel of the `Endpoint` on the destination chain.
4. After the message has been inserted in the Endpoint's message channel, the `Executor` calls `Endpoint.lzReceive` to trigger the execution of the cross-chain message on the destination chain.
5. The `Endpoint` calls the payable `ReceiverOApp.lzReceive` to pass the message and execute the internal receive logic. You can modify the internal execution logic inside `ReceiverOApp._lzReceive` to trigger any intended outcome from the cross-chain message.
tip
You can find all of the above contracts by visiting [**Supported Chains**](/v2/deployments/deployed-contracts)
and [**Supported DVNs**](/v2/deployments/dvn-addresses)
.
### Send Overview[](#send-overview "Direct link to Send Overview")
The `OApp` calls `EndpointV2.send` to send the cross-chain message and pays a fee to each configured `DVN` and `Executor`.
#### EndpointV2.sol[](#endpointv2sol "Direct link to EndpointV2.sol")
Inside the `send` call:
* emit event to each `DVN` and `Executor` according to the `OApp` send configuration for the cross-chain message. Also calculate and record the fee that should be paid to each `DVN` and `Executor`.
* check whether the fees the user is willing to pay can cover the fees required by the `DVNs` and `Executor`.
* transfer fee to `_sendLibrary` (which records fee allocation).
// LayerZero/V2/protocol/contracts/EndpointV2.soladdress public lzToken;struct MessagingParams { uint32 dstEid; // destination chain endpoint id bytes32 receiver; // receiver on destination chain bytes message; // cross-chain message bytes options; // settings for executor and dvn bool payInLzToken; // whether to pay in ZRO token}struct MessagingReceipt { bytes32 guid; // unique identifier for the message uint64 nonce; // message nonce MessagingFee fee; // the message fee paid}/// @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 lzTokenfunction send( MessagingParams calldata _params, address _refundAddress) external payable sendContext(_params.dstEid, msg.sender) returns (MessagingReceipt memory) { if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LZ_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); // check fee sender has provided enough fee _assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken); // handle lz token fees to _sendLibrary _payToken(lzToken, receipt.fee.lzTokenFee, suppliedLzToken, _sendLibrary, _refundAddress); // handle native fees to _sendLibrary _payNative(receipt.fee.nativeFee, suppliedNative, _sendLibrary, _refundAddress); return receipt;}/// @dev Assert the required fees and the supplied fees are enoughfunction _assertMessagingFee( MessagingFee memory _required, uint256 _suppliedNativeFee, uint256 _suppliedLzTokenFee) internal pure { if (_required.nativeFee > _suppliedNativeFee || _required.lzTokenFee > _suppliedLzTokenFee) { revert Errors.LZ_InsufficientFee( _required.nativeFee, _suppliedNativeFee, _required.lzTokenFee, _suppliedLzTokenFee ); }}// pay lzTokenfunction _payToken( address _token, uint256 _required, uint256 _supplied, address _receiver, address _refundAddress) internal { if (_required > 0) { Transfer.token(_token, _receiver, _required); } if (_required < _supplied) { unchecked { // refund the excess Transfer.token(_token, _refundAddress, _supplied - _required); } }}// pay native tokenfunction _payNative( uint256 _required, uint256 _supplied, address _receiver, address _refundAddress) internal virtual { if (_required > 0) { Transfer.native(_receiver, _required); } if (_required < _supplied) { unchecked { // refund the excess Transfer.native(_refundAddress, _supplied - _required); } }}
Inside the internal `_send` call:
* get the `nonce` of this packet according to the path: **\[sender, destination chain, receiver\]**.
* generate `guid` of the packet (global unique identifier).
* get the `_sendLibrary` of the OApp (OApp can set their specific send library of each destination chain).
* call `_sendLibrary` to emit events to notify `Executor` and `DVNs`, also calculate and record the `fee` that should be paid to each.
// LayerZero/V2/protocol/contracts/EndpointV2.solmapping(address sender => mapping(uint32 dstEid => mapping(bytes32 receiver => uint64 nonce))) public outboundNonce;/// @dev increase and return the next outbound noncefunction _outbound(address _sender, uint32 _dstEid, bytes32 _receiver) internal returns (uint64 nonce) { unchecked { nonce = ++outboundNonce[_sender][_dstEid][_receiver]; }}address private constant DEFAULT_LIB = address(0);mapping(uint32 dstEid => address lib) public defaultSendLibrary;/// @notice The Send Library is the Oapp specified library that will be used to send the message to the destination/// endpoint. If the Oapp does not specify a Send Library, the default Send Library will be used./// @dev If the Oapp does not have a selected Send Library, this function will resolve to the default library/// configured by LayerZero/// @return lib address of the Send Library/// @param _sender The address of the Oapp that is sending the message/// @param _dstEid The destination endpoint idfunction getSendLibrary(address _sender, uint32 _dstEid) public view returns (address lib) { lib = sendLibrary[_sender][_dstEid]; if (lib == DEFAULT_LIB) { lib = defaultSendLibrary[_dstEid]; if (lib == address(0x0)) revert Errors.LZ_DefaultSendLibUnavailable(); }}struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee;}/// @dev internal function for sending the messages used by all external send methods/// @param _sender the address of the application sending the message to the destination chain/// @param _params the messaging parametersfunction _send( address _sender, MessagingParams calldata _params) internal returns (MessagingReceipt memory, address) { // get the correct outbound nonce uint64 latestNonce = _outbound(_sender, _params.dstEid, _params.receiver); // construct the packet with a GUID Packet memory packet = Packet({ nonce: latestNonce, srcEid: eid, sender: _sender, dstEid: _params.dstEid, receiver: _params.receiver, guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver), message: _params.message }); // get the send library by sender and dst eid address _sendLibrary = getSendLibrary(_sender, _params.dstEid); // messageLib always returns encodedPacket with guid (MessagingFee memory fee, bytes memory encodedPacket) = ISendLib(_sendLibrary).send( packet, _params.options, _params.payInLzToken ); // Emit packet information for DVNs, Executors, and any other offchain infrastructure to only listen // for this one event to perform their actions. emit PacketSent(encodedPacket, _params.options, _sendLibrary); return (MessagingReceipt(packet.guid, latestNonce, fee), _sendLibrary);}
The `guid` is generated using the following parameters:
// LayerZero/V2/protocol/contracts/libs/GUID.solfunction generate( uint64 _nonce, uint32 _srcEid, address _sender, uint32 _dstEid, bytes32 _receiver) internal pure returns (bytes32) { return keccak256(abi.encodePacked(_nonce, _srcEid, _sender.toBytes32(), _dstEid, _receiver));}
#### SendUln302.sol[](#senduln302sol "Direct link to SendUln302.sol")
Next, the message is handled by the `OApp` selected Send Library. For example, `SendUln302.send`:
* pay workers (`DVNs` and `Executor`) and treasury. In the send process, the `fee` is not directly paid to the workers, but recorded in the send library (`SendUln302.sol`) for workers to claim later.
* call `DVNs` and `Executor`'s contract to emit event to notify them to send cross-chain message.
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.solstruct Packet { uint64 nonce; uint32 srcEid; address sender; uint32 dstEid; bytes32 receiver; bytes32 guid; bytes message;}function send( Packet calldata _packet, bytes calldata _options, bool _payInLzToken) public virtual onlyEndpoint returns (MessagingFee memory, bytes memory) { // assign job to Executor and DVN, calculate fees (bytes memory encodedPacket, uint256 totalNativeFee) = _payWorkers(_packet, _options); // calculate and pay the treasury fee, if enabled (uint256 treasuryNativeFee, uint256 lzTokenFee) = _payTreasury( _packet.sender, _packet.dstEid, totalNativeFee, _payInLzToken ); totalNativeFee += treasuryNativeFee; return (MessagingFee(totalNativeFee, lzTokenFee), encodedPacket);}
Inside the `SendUln302._payWorkers`, the contract:
* splits options to get `executorOptions` (`Executor`) and `validationOptions` (`DVN`).
* get the `OApp` set `Executor` and corresponding `maxMessageSize` (If not set, then a default `maxMessageSize` of 10000 bytes is used), and checks that the size of the message to send is less than than the max.
* calls `_payExecutor` to assign job to corresponding `Executor` and record the fee paid.
* calls `_payVerifier` to assign job to specified `DVNs` and record fee paid.
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol/// 1/ handle executor/// 2/ handle other workersfunction _payWorkers( Packet calldata _packet, bytes calldata _options) internal returns (bytes memory encodedPacket, uint256 totalNativeFee) { // split workers options (bytes memory executorOptions, WorkerOptions[] memory validationOptions) = _splitOptions(_options); // handle executor ExecutorConfig memory config = getExecutorConfig(_packet.sender, _packet.dstEid); uint256 msgSize = _packet.message.length; _assertMessageSize(msgSize, config.maxMessageSize); totalNativeFee += _payExecutor(config.executor, _packet.dstEid, _packet.sender, msgSize, executorOptions); // handle other workers (uint256 verifierFee, bytes memory packetBytes) = _payVerifier(_packet, validationOptions); //for ULN, it will be dvns totalNativeFee += verifierFee; encodedPacket = packetBytes;}// @dev get the executor config and if not set, return the default configfunction getExecutorConfig(address _oapp, uint32 _remoteEid) public view returns (ExecutorConfig memory rtnConfig) { ExecutorConfig storage defaultConfig = executorConfigs[DEFAULT_CONFIG][_remoteEid]; ExecutorConfig storage customConfig = executorConfigs[_oapp][_remoteEid]; uint32 maxMessageSize = customConfig.maxMessageSize; rtnConfig.maxMessageSize = maxMessageSize != 0 ? maxMessageSize : defaultConfig.maxMessageSize; address executor = customConfig.executor; rtnConfig.executor = executor != address(0x0) ? executor : defaultConfig.executor;}function _assertMessageSize(uint256 _actual, uint256 _max) internal pure { if (_actual > _max) revert LZ_MessageLib_InvalidMessageSize(_actual, _max);}
Inside the `SendUln302._payExecutor`:
* calls `Executor` (default or set by OApp) to assign job and calculate the fee needed.
* record the `Executor`’s fee inside the send library.
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.solfunction _payExecutor( address _executor, uint32 _dstEid, address _sender, uint256 _msgSize, bytes memory _executorOptions) internal returns (uint256 executorFee) { executorFee = ILayerZeroExecutor(_executor).assignJob(_dstEid, _sender, _msgSize, _executorOptions); if (executorFee > 0) { fees[_executor] += executorFee; } emit ExecutorFeePaid(_executor, executorFee);}
Inside the `SendUln302._payVerifier`:
* calculate `payloadHash` and `payload`, which will be used to emit event to notify `DVN` to send the cross-chain message.
* `payloadHash` is a digest including information about the version and path of the cross-chain message;
* `payload` includes information of the `guid` and the body of the cross-chain message.
* get the sender `OApp` config about which `DVNs` to use.
* assign job for each `DVN`, including both required and optional.
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.solfunction _payVerifier( Packet calldata _packet, WorkerOptions[] memory _options) internal override returns (uint256 otherWorkerFees, bytes memory encodedPacket) { (otherWorkerFees, encodedPacket) = _payDVNs(fees, _packet, _options);}struct WorkerOptions { uint8 workerId; bytes options;}// accumulated fees for workers and treasurymapping(address worker => uint256) public fees;struct AssignJobParam { uint32 dstEid; bytes packetHeader; bytes32 payloadHash; uint64 confirmations; // source chain block confirmations before message being verified on the destination address sender;}struct UlnConfig { uint64 confirmations; // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNThreshold; // (0, optionalDVNCount] address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs}/// ---------- pay and assign jobs ----------function _payDVNs( mapping(address => uint256) storage _fees, Packet memory _packet, WorkerOptions[] memory _options) internal returns (uint256 totalFee, bytes memory encodedPacket) { // calculate packetHeader and payload bytes memory packetHeader = PacketV1Codec.encodePacketHeader(_packet); bytes memory payload = PacketV1Codec.encodePayload(_packet); bytes32 payloadHash = keccak256(payload); uint32 dstEid = _packet.dstEid; address sender = _packet.sender; // get user’s config about DVN UlnConfig memory config = getUlnConfig(sender, dstEid); // if options is not empty, it must be dvn options bytes memory dvnOptions = _options.length == 0 ? bytes("") : _options[0].options; uint256[] memory dvnFees; // assign job for each DVN includes those required and optional (totalFee, dvnFees) = _assignJobs( _fees, config, ILayerZeroDVN.AssignJobParam(dstEid, packetHeader, payloadHash, config.confirmations, sender), dvnOptions ); encodedPacket = abi.encodePacked(packetHeader, payload); emit DVNFeePaid(config.requiredDVNs, config.optionalDVNs, dvnFees);}
// LayerZero/V2/protocol/contracts/messagelib/libs/PacketV1Codec.solfunction encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) { return abi.encodePacked( PACKET_VERSION, _packet.nonce, _packet.srcEid, _packet.sender.toBytes32(), _packet.dstEid, _packet.receiver );}function encodePayload(Packet memory _packet) internal pure returns (bytes memory) { return abi.encodePacked(_packet.guid, _packet.message);}
Inside the `SendUln302._assignJobs`:
* call each required and optional `DVN` to notify them to verify the cross-chain message on the destination chain.
* update each `DVN`'s fee.
* return the `totalFee` used by all `DVNs`.
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.solfunction _assignJobs( mapping(address => uint256) storage _fees, UlnConfig memory _ulnConfig, ILayerZeroDVN.AssignJobParam memory _param, bytes memory dvnOptions) internal returns (uint256 totalFee, uint256[] memory dvnFees) { (bytes[] memory optionsArray, uint8[] memory dvnIds) = DVNOptions.groupDVNOptionsByIdx(dvnOptions); uint8 dvnsLength = _ulnConfig.requiredDVNCount + _ulnConfig.optionalDVNCount; dvnFees = new uint256[](dvnsLength); for (uint8 i = 0; i < dvnsLength; ++i) { address dvn = i < _ulnConfig.requiredDVNCount ? _ulnConfig.requiredDVNs[i] : _ulnConfig.optionalDVNs[i - _ulnConfig.requiredDVNCount]; bytes memory options = ""; for (uint256 j = 0; j < dvnIds.length; ++j) { if (dvnIds[j] == i) { options = optionsArray[j]; break; } } dvnFees[i] = ILayerZeroDVN(dvn).assignJob(_param, options); if (dvnFees[i] > 0) { _fees[dvn] += dvnFees[i]; totalFee += dvnFees[i]; } }}
#### Assign Job to Executor[](#assign-job-to-executor "Direct link to Assign Job to Executor")
`Executor.assignJob` calls `ExecutorFeeLib.getFeeOnSend` to calculate the fee that should be paid to the `Executor`, and emit an event to notify.
In the `ExecutorFeeLib.getFeeOnSend`, it will check the `msg.value` specified by the message sender and enforce that it should be smaller than the `DstConfig.nativeCap` of the destination chain. This is because the supply of native tokens (e.g., Ether) must be maintained by the `Executor`, and is not controlled by the OApp unless running a custom `Executor`.
// LayerZero/V2/messagelib/contracts/Executor.solstruct FeeParams { address priceFeed; uint32 dstEid; address sender; uint256 calldataSize; uint16 defaultMultiplierBps;}struct DstConfig { uint64 baseGas; // for verifying / fixed calldata overhead uint16 multiplierBps; uint128 floorMarginUSD; // uses priceFeed PRICE_RATIO_DENOMINATOR uint128 nativeCap; // maximum native gas token cap}function assignJob( uint32 _dstEid, address _sender, uint256 _calldataSize, bytes calldata _options) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 fee) { IExecutorFeeLib.FeeParams memory params = IExecutorFeeLib.FeeParams( priceFeed, _dstEid, _sender, _calldataSize, defaultMultiplierBps ); fee = IExecutorFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], _options);}
#### Assign Job to DVNs[](#assign-job-to-dvns "Direct link to Assign Job to DVNs")
`DVN.assignJob` calls `DVNFeeLib.getFeeOnSend` to calculate the fee that should be paid to the `DVNs`, and emit events to notify them.
// LayerZero/V2/messagelib/contracts/uln/dvn/DVN.sol/// @dev for ULN301, ULN302 and more to assign job/// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist/// @param _param assign job param/// @param _options dvn optionsfunction assignJob( AssignJobParam calldata _param, bytes calldata _options) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_param.sender) returns (uint256 totalFee) { IDVNFeeLib.FeeParams memory feeParams = IDVNFeeLib.FeeParams( priceFeed, _param.dstEid, _param.confirmations, _param.sender, quorum, defaultMultiplierBps ); totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[_param.dstEid], _options);}
### Send Limitations[](#send-limitations "Direct link to Send Limitations")
#### Max Message Bytes Size[](#max-message-bytes-size "Direct link to Max Message Bytes Size")
The `maxMessageSize` depends on the Send Library. In `SendUln302`, the default max is 10000 bytes, but this value can be configured per OApp.
#### Max Native Gas Token Requests[](#max-native-gas-token-requests "Direct link to Max Native Gas Token Requests")
In the `ExecutorFeeLib._decodeExecutorOptions`, it limits the maximum native gas token amount that can be requested from the `Executor` for the destination chain transaction.
This config is set in `Executor.dstConfig`:
// LayerZero/V2/messagelib/contracts/Executor.solstruct DstConfig { uint64 baseGas; // for verifying / fixed calldata overhead uint16 multiplierBps; uint128 floorMarginUSD; // uses priceFeed PRICE_RATIO_DENOMINATOR uint128 nativeCap; // maximum native gas token amount to request from Executor for destination chain transaction}
### Verification Workflow[](#verification-workflow "Direct link to Verification Workflow")
After the cross-chain message has been sent on the source chain (event has been emitted to notify `DVNs` and `Executor`), `DVN` will first verify the message on the destination chain, after which `Executor` will execute the message.
#### DVN Verification[](#dvn-verification "Direct link to DVN Verification")
`DVNs` call `ReceiveUln302.verify` to submit their witness of the source cross-chain message using the `_payloadHash`.
// LayerZero/V2/messagelib/contracts/uln/ReceiveUlnBase.solfunction verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external { _verify(_packetHeader, _payloadHash, _confirmations);}mapping(bytes32 headerHash => mapping(bytes32 payloadHash => mapping(address dvn => Verification))) public hashLookup;function _verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) internal { hashLookup[keccak256(_packetHeader)][_payloadHash][msg.sender] = Verification(true, _confirmations); emit PayloadVerified(msg.sender, _packetHeader, _confirmations, _payloadHash);}
#### Commit Verification[](#commit-verification "Direct link to Commit Verification")
After the `OApp`'s required `DVNs` have all verified, and the threshold of optional `DVNs` has been reached, `ReceiveUln302.commitVerification` can be called by any address to commit the verification to the `Endpoint`'s message channel.
// LayerZero/V2/messagelib/contracts/uln/uln302/ReceiveUln302.solstruct UlnConfig { uint64 confirmations; // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNThreshold; // (0, optionalDVNCount] address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs}/// @dev dont need to check endpoint verifiable here to save gas, as it will reverts if not verifiable.function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external { // check packet header validity _assertHeader(_packetHeader, localEid); // decode the receiver and source Endpoint Id address receiver = _packetHeader.receiverB20(); uint32 srcEid = _packetHeader.srcEid(); // get receiver's config UlnConfig memory config = getUlnConfig(receiver, srcEid); _verifyAndReclaimStorage(config, keccak256(_packetHeader), _payloadHash); Origin memory origin = Origin(srcEid, _packetHeader.sender(), _packetHeader.nonce()); // call endpoint to verify payload hash // endpoint will revert if nonce <= lazyInboundNonce ILayerZeroEndpointV2(endpoint).verify(origin, receiver, _payloadHash);}function _assertHeader(bytes calldata _packetHeader, uint32 _localEid) internal pure { // assert packet header is of right size 81 if (_packetHeader.length != 81) revert LZ_ULN_InvalidPacketHeader(); // assert packet header version is the same as ULN if (_packetHeader.version() != PacketV1Codec.PACKET_VERSION) revert LZ_ULN_InvalidPacketVersion(); // assert the packet is for this endpoint if (_packetHeader.dstEid() != _localEid) revert LZ_ULN_InvalidEid();}
`_verifyAndReclaimStorage` verifies that the required and optional `DVNs` have submitted witness.
function _verifyAndReclaimStorage(UlnConfig memory _config, bytes32 _headerHash, bytes32 _payloadHash) internal { if (!_checkVerifiable(_config, _headerHash, _payloadHash)) { revert LZ_ULN_Verifying(); } // iterate the required DVNs if (_config.requiredDVNCount > 0) { for (uint8 i = 0; i < _config.requiredDVNCount; ++i) { delete hashLookup[_headerHash][_payloadHash][_config.requiredDVNs[i]]; } } // iterate the optional DVNs if (_config.optionalDVNCount > 0) { for (uint8 i = 0; i < _config.optionalDVNCount; ++i) { delete hashLookup[_headerHash][_payloadHash][_config.optionalDVNs[i]]; } }}
#### Insert Hash to Endpoint's Message Channel[](#insert-hash-to-endpoints-message-channel "Direct link to Insert Hash to Endpoint's Message Channel")
Inside the `verify`:
* check `msg.sender` is valid `ReceiveLibrary` configured by the `OApp`.
* get the `lazyNonce` of the OApp.
* check the cross-chain message path is valid for the `receiver`.
* check the message represented by the `nonce` has not been executed before.
* insert the message into the `Endpoint`'s message channel.
`lazyNonce` is the latest executed message’s `nonce`. To execute a transaction, LayerZero requires all messages before the current message has been verified. So all messages before the message with `lazyNonce` has been verified.
// LayerZero/V2/protocol/contracts/EndpointV2.sol/// @dev configured receive library verifies a message/// @param _origin a struct holding the srcEid, nonce, and sender of the message/// @param _receiver the receiver of the message/// @param _payloadHash the payload hash of the messagefunction verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external { // check msg.sender is valid ReceiveLibrary configured by the OApp if (!isValidReceiveLibrary(_receiver, _origin.srcEid, msg.sender)) revert Errors.LZ_InvalidReceiveLibrary(); // get the lazynonce uint64 lazyNonce = lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]; // check whether path is valid if (!_initializable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotInitializable(); // check the nonce/msg hasn't been executed before if (!_verifiable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotVerifiable(); // insert the message into the message channel _inbound(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, _payloadHash); emit PacketVerified(_origin, _receiver, _payloadHash);}
`isValidReceiveLibrary` checks whether the `ReceiveLib` is the expected `ReceiveLib` of the `receiver`. If not, then check whether there has been a `Timeout` set for the current `ReceiveLib`.
`Timeout` is used to help improve the UX of updating a `ReceiveLib`. For example, if `OApp` decides to switch the `ReceiveLib`, it can update the address on the destination chain, but some cross-chain messages may already be in-flight and not inserted in the destination chain Endpoint's message channel before the switch. Those messages depend on the previous `ReceiveLib`, so `Timeout` provides a grace period to ensure already in-flight messages have successful execution.
// LayerZero/V2/protocol/contracts/EndpointV2.sol/// @dev called when the endpoint checks if the msgLib attempting to verify the msg is the configured msgLib of the Oapp/// @dev this check provides the ability for Oapp to lock in a trusted msgLib/// @dev it will fist check if the msgLib is the currently configured one. then check if the msgLib is the one in grace period of msgLib versioning upgradefunction isValidReceiveLibrary( address _receiver, uint32 _srcEid, address _actualReceiveLib) public view returns (bool) { // early return true if the _actualReceiveLib is the currently configured one (address expectedReceiveLib, bool isDefault) = getReceiveLibrary(_receiver, _srcEid); if (_actualReceiveLib == expectedReceiveLib) { return true; } // check the timeout condition otherwise // if the Oapp is using defaultReceiveLibrary, use the default Timeout config // otherwise, use the Timeout configured by the Oapp Timeout memory timeout = isDefault ? defaultReceiveLibraryTimeout[_srcEid] : receiveLibraryTimeout[_receiver][_srcEid]; // requires the _actualReceiveLib to be the same as the one in grace period and the grace period has not expired // block.number is uint256 so timeout.expiry must > 0, which implies a non-ZERO value if (timeout.lib == _actualReceiveLib && timeout.expiry > block.number) { // timeout lib set and has not expired return true; } // returns false by default return false;}/// @dev the receiveLibrary can be lazily resolved that if not set it will point to the default configured by LayerZerofunction getReceiveLibrary(address _receiver, uint32 _srcEid) public view returns (address lib, bool isDefault) { lib = receiveLibrary[_receiver][_srcEid]; if (lib == DEFAULT_LIB) { lib = defaultReceiveLibrary[_srcEid]; if (lib == address(0x0)) revert Errors.LZ_DefaultReceiveLibUnavailable(); isDefault = true; }}
`_initializable` is used to check whether the cross-chain message path is valid for the `receiver`. `_lazyInboundNonce` greater than 0 suggests a message has already been executed successfully, so no need to call `_receiver` to check the path again, which helps save gas.
Otherwise, call `_receiver.allowInitializePath` to check (the `OApp` standard inherits `OAppReceiver` which has already implemented `allowInitializePath`).
// LayerZero/V2/protocol/contracts/EndpointV2.solfunction _initializable( Origin calldata _origin, address _receiver, uint64 _lazyInboundNonce) internal view returns (bool) { return _lazyInboundNonce > 0 || // allowInitializePath already checked ILayerZeroReceiver(_receiver).allowInitializePath(_origin);}
// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol/** * @notice Checks if the path initialization is allowed based on the provided origin. * @param origin The origin information containing the source endpoint and sender address. * @return Whether the path has been initialized. * * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received. * @dev This defaults to assuming if a peer has been set, its initialized. * Can be overridden by the OApp if there is other logic to determine this. */function allowInitializePath(Origin calldata origin) public view virtual returns (bool) { return peers[origin.srcEid] == origin.sender;}
`_verifiable` checks that the nonce / message has not been executed before.
* If `_origin.nonce` > `_lazyInboundNonce`, then the nonce / message has not been executed before, otherwise `_lazyInboundNonce` ≥ `_origin.nonce`.
* If `_origin.nonce` ≤ `_lazyInboundNonce`, then the nonce / message has been verified. If the payload hash is empty, which means the nonce / message has been executed (because the `Endpoint` will clear the payload hash of the nonce after successful execution), it cannot be executed again.
// LayerZero/V2/protocol/contracts/EndpointV2.solfunction _verifiable( Origin calldata _origin, address _receiver, uint64 _lazyInboundNonce) internal view returns (bool) { return _origin.nonce > _lazyInboundNonce || // either initializing an empty slot or reverifying inboundPayloadHash[_receiver][_origin.srcEid][_origin.sender][_origin.nonce] != EMPTY_PAYLOAD_HASH; // only allow reverifying if it hasn't been executed}
`_inbound` inserts the message into the channel (`inboundPayloadHash`).
// LayerZero/V2/protocol/contracts/MessagingChannel.sol/// @dev inbound won't update the nonce eagerly to allow unordered verification/// @dev instead, it will update the nonce lazily when the message is received/// @dev messages can only be cleared in order to preserve censorship-resistancefunction _inbound( address _receiver, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes32 _payloadHash) internal { if (_payloadHash == EMPTY_PAYLOAD_HASH) revert Errors.LZ_InvalidPayloadHash(); inboundPayloadHash[_receiver][_srcEid][_sender][_nonce] = _payloadHash;}
### Receive Workflow[](#receive-workflow "Direct link to Receive Workflow")
#### Endpoint Execution[](#endpoint-execution "Direct link to Endpoint Execution")
After the cross-chain message has been inserted into the channel (`Endpoint.inboundPayloadHash`), `Executor` will try to call `Endpoint.lzReceive` to execute the message.
* clear the payload first to prevent reentrancy and double execution.
* call `ILayerZeroReceiver.lzReceive` to execute the message.
// LayerZero/V2/protocol/contracts/EndpointV2.solstruct Origin { uint32 srcEid; bytes32 sender; uint64 nonce;}/// @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);}
Inside the `_clearPayload`:
* update the `lazyInboundNonce`.
* verify payload provided by `Executor`.
* delete message in the channel to prevent double execution.
// LayerZero/V2/protocol/contracts/EndpointV2.sol/// @dev calling this function will clear the stored message and increment the lazyInboundNonce to the provided nonce/// @dev if a lot of messages are queued, the messages can be cleared with a smaller step size to prevent OOG/// @dev NOTE: this function does not change inboundNonce, it only changes the lazyInboundNonce up to the provided noncefunction _clearPayload( address _receiver, uint32 _srcEid, bytes32 _sender, uint64 _nonce, bytes memory _payload) internal returns (bytes32 actualHash) { uint64 currentNonce = lazyInboundNonce[_receiver][_srcEid][_sender]; if (_nonce > currentNonce) { unchecked { // try to lazily update the inboundNonce till the _nonce for (uint64 i = currentNonce + 1; i <= _nonce; ++i) { if (!_hasPayloadHash(_receiver, _srcEid, _sender, i)) revert Errors.LZ_InvalidNonce(i); } lazyInboundNonce[_receiver][_srcEid][_sender] = _nonce; } } // check the hash of the payload to verify the executor has given the proper payload that has been verified actualHash = keccak256(_payload); bytes32 expectedHash = inboundPayloadHash[_receiver][_srcEid][_sender][_nonce]; if (expectedHash != actualHash) revert Errors.LZ_PayloadHashNotFound(expectedHash, actualHash); // remove it from the storage delete inboundPayloadHash[_receiver][_srcEid][_sender][_nonce];}
#### OApp Execution[](#oapp-execution "Direct link to OApp Execution")
By default, the `OApp` standard inherits `OAppReceiver` which implements `lzReceive` called by `Endpoint` to execute message.
* check `msg.sender` is `Endpoint`.
* check the path is valid.
* call internal `_lzReceive` to execute logic (developer should override to add specific use).
// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol/** * @dev Entry point for receiving messages or packets from the endpoint. * @param _origin The origin information containing the source endpoint and sender address. * - srcEid: The source chain endpoint ID. * - sender: The sender address on the src chain. * - nonce: The nonce of the message. * @param _guid The unique identifier for the received LayerZero message. * @param _message The payload of the received message. * @param _executor The address of the executor for the received message. * @param _extraData Additional arbitrary data provided by the corresponding executor. * * @dev Entry point for receiving msg/packet from the LayerZero endpoint. */function lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) public payable virtual { // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp. if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender); // Ensure that the sender matches the expected peer for the source endpoint. if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender); // Call the internal OApp implementation of lzReceive. _lzReceive(_origin, _guid, _message, _executor, _extraData);}/** * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation. */function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) internal virtual;
In the original `_getPeerOrRevert` implementation, it can only assign one valid `sender` for each source chain, but developers can override this to allow multiple `senders` on one source chain.
// LayerZero/V2/oapp/contracts/oapp/OAppCore.sol/** * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set. * ie. the peer is set to bytes32(0). * @param _eid The endpoint ID. * @return peer The address of the peer associated with the specified endpoint. */function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) { bytes32 peer = peers[_eid]; if (peer == bytes32(0)) revert NoPeer(_eid); return peer;}
info
Developers should also override `OAppReceiver.allowInitializePath` so that the message can be successfully inserted into the `Endpoint`'s message channel (the Endpoint will call to check whether the path is valid).
tip
Special thanks to community member [**SennHanami**](https://x.com/HanamiSenn)
for their contribution to this documentation page. You can read their full deep-dive at: [**Decode LayerZero V2**](https://senn.fun/decode-layerzero-v2#af1b7e67e9ad48c5aae184928aa4d209)
.
* [Send Overview](#send-overview)
* [EndpointV2.sol](#endpointv2sol)
* [SendUln302.sol](#senduln302sol)
* [Assign Job to Executor](#assign-job-to-executor)
* [Assign Job to DVNs](#assign-job-to-dvns)
* [Send Limitations](#send-limitations)
* [Max Message Bytes Size](#max-message-bytes-size)
* [Max Native Gas Token Requests](#max-native-gas-token-requests)
* [Verification Workflow](#verification-workflow)
* [DVN Verification](#dvn-verification)
* [Commit Verification](#commit-verification)
* [Insert Hash to Endpoint's Message Channel](#insert-hash-to-endpoints-message-channel)
* [Receive Workflow](#receive-workflow)
* [Endpoint Execution](#endpoint-execution)
* [OApp Execution](#oapp-execution)
---
# Project Configuration | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
When working with a LayerZero project, it searches for the closest `hardhat.config.ts` and `layerzero.config.ts` files starting from the Current Working Directory. This file normally lives in the root of your project.
### Modifying Hardhat Config[](#modifying-hardhat-config "Direct link to Modifying Hardhat Config")
After initializing the repo, you will need to modify your `hardhat.config.ts` with the expected networks you will be working with:
// hardhat.config.tsnetworks: { sepolia: { // the LayerZero Endpoint ID eid: EndpointId.SEPOLIA_V2_TESTNET, url: 'https://rpc.sepolia.org/', accounts, }, ethereum: { // the LayerZero Endpoint ID eid: EndpointId.ETHEREUM_V2_MAINNET, url: 'https://eth.llamarpc.com', accounts, }, bsc_testnet: { // the LayerZero Endpoint ID eid: EndpointId.BSC_V2_TESTNET, url: 'https://bsc-testnet.publicnode.com', accounts, },},
info
The only notable change from a standard `hardhat.config.ts` setup is the inclusion of a [**LayerZero Endpoint ID**](/v2/deployments/deployed-contracts)
.
For hardhat specific questions, refer to the [**Hardhat Configuration**](https://hardhat.org/hardhat-runner/docs/config)
documentation.
tip
The npx package uses `@layerzerolabs/lz-definitions` to enable you to reference both V1 and V2 Endpoints. Make sure if your project uses LayerZero V2 to select the V2 Endpoint (i.e., `eid: EXAMPLE_V2_MAINNET`).
### Modifying LayerZero Config[](#modifying-layerzero-config "Direct link to Modifying LayerZero Config")
The `layerzero.config.ts` first defines what contracts you expect to deploy on each network, using the `@layerzerolabs/lz-definitions` package as a mapping for each network:
// layerzero.config.tsimport {EndpointId} from '@layerzerolabs/lz-definitions';// Define the Ethereum contract// eid specifies the network (LZ V2 Ethereum Sepolia Testnet)// contractName is the name of the contract.const sepoliaContract = { eid: EndpointId.SEPOLIA_V2_TESTNET, contractName: 'MyOFT',};// Define the Binance contract// eid specifies the network (LZ V2 Binance Smart Chain Testnet)// contractName is the name of the contract.const bscContract = { eid: EndpointId.BSC_V2_TESTNET, contractName: 'MyOFT',};// Define the Amoy (Polygon) contract// address can also be specified for deployed contracts.const amoyContract = { eid: EndpointId.AMOY_V2_TESTNET, address: '0x123...',};
After defining what contracts to use on each network, you can specify which contracts should be connected on a per pathway basis:
// layerzero.config.tsmodule.exports = { // Define the contracts to be deployed on each network // Each contract is associated with a specific blockchain. contracts: [ { contract: sepoliaContract, }, { contract: bscContract, }, { contract: amoyContract, }, ], // Define the pathway between each contract. // This allows for cross-chain communication using LayerZero. connections: [ { from: bscContract, to: sepoliaContract, }, { from: bscContract, to: amoyContract, }, { from: sepoliaContract, to: bscContract, }, { from: sepoliaContract, to: amoyContract, }, { from: amoyContract, to: sepoliaContract, }, { from: amoyContract, to: bscContract, }, ],};
### Checking Pathway Configurations[](#checking-pathway-configurations "Direct link to Checking Pathway Configurations")
To check your OApp's current configuration, you can run:
npx hardhat lz:oapp:config:get --oapp-config layerzero.config.ts
This command will output a table with 3 columns:
1. **Custom OApp Config**: your `layerzero.config.ts` configuration changes, with null values for unchanged parameters.
2. **Default OApp Config**: the default LayerZero configuration for the pathway.
3. **Active OApp Config**: the combination of your customized and default parameters, i.e., the active configuration.
┌────────────────────┬─────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐│ │ Custom OApp Config │ Default OApp Config │ Active OApp Config │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ localNetworkName │ bsc_testnet │ bsc_testnet │ bsc_testnet │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ remoteNetworkName │ sepolia │ sepolia │ sepolia │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ sendLibrary │ 0x0000000000000000000000000000000000000000 │ 0x55f16c442907e86D764AFdc2a07C2de3BdAc8BB7 │ 0x55f16c442907e86D764AFdc2a07C2de3BdAc8BB7 │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ receiveLibrary │ 0x0000000000000000000000000000000000000000 │ 0x188d4bbCeD671A7aA2b5055937F79510A32e9683 │ 0x188d4bbCeD671A7aA2b5055937F79510A32e9683 │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ sendUlnConfig │ ┌──────────────────────┬───┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ ││ │ │ confirmations │ 0 │ │ │ confirmations │ 5 │ │ │ confirmations │ 5 │ ││ │ ├──────────────────────┼───┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ requiredDVNs │ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ ││ │ ├──────────────────────┼───┤ │ │ │ │ 0 │ 0x0eE552262f7B562eFcED6DD4A7e2878AB897d405 │ │ │ │ │ │ 0 │ 0x0eE552262f7B562eFcED6DD4A7e2878AB897d405 │ │ ││ │ │ optionalDVNs │ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ ││ │ ├──────────────────────┼───┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ ││ │ └──────────────────────┴───┘ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ ││ │ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ sendExecutorConfig │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ ││ │ │ executor │ 0x0000000000000000000000000000000000000000 │ │ │ executor │ 0x31894b190a8bAbd9A067Ce59fde0BfCFD2B18470 │ │ │ executor │ 0x31894b190a8bAbd9A067Ce59fde0BfCFD2B18470 │ ││ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ ││ │ │ maxMessageSize │ 0 │ │ │ maxMessageSize │ 10000 │ │ │ maxMessageSize │ 10000 │ ││ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │├────────────────────┼─────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤│ receiveUlnConfig │ ┌──────────────────────┬───┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ ││ │ │ confirmations │ 0 │ │ │ confirmations │ 2 │ │ │ confirmations │ 2 │ ││ │ ├──────────────────────┼───┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ requiredDVNs │ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ ││ │ ├──────────────────────┼───┤ │ │ │ │ 0 │ 0x0eE552262f7B562eFcED6DD4A7e2878AB897d405 │ │ │ │ │ │ 0 │ 0x0eE552262f7B562eFcED6DD4A7e2878AB897d405 │ │ ││ │ │ optionalDVNs │ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ ││ │ ├──────────────────────┼───┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ ││ │ └──────────────────────┴───┘ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ ││ │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ ││ │ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │└────────────────────┴─────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘
### Adding Pathway Configurations[](#adding-pathway-configurations "Direct link to Adding Pathway Configurations")
To add specific configurations on a per pathway basis, review the [Configuring Pathways](/v2/developers/evm/create-lz-oapp/configuring-pathways)
section.
* [Modifying Hardhat Config](#modifying-hardhat-config)
* [Modifying LayerZero Config](#modifying-layerzero-config)
* [Checking Pathway Configurations](#checking-pathway-configurations)
* [Adding Pathway Configurations](#adding-pathway-configurations)
---
# Testing Contracts | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The LayerZero sample project supports unit testing using both the hardhat and foundry forge development framework, with specific test helpers for each:
* `EndpointV2Mock.sol`: a mock LayerZero V2 Endpoint contract, meant for local testing of LayerZero message passing.
* `TestHelper.sol`: an extensive LayerZero V2 testing framework, designed for simulating LayerZero state changes and contract interactions.
To run your unit tests for both Hardhat and Foundry, run the `test` command using your package manager:
pnpm test
### Example Hardhat Test[](#example-hardhat-test "Direct link to Example Hardhat Test")
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers';import {expect} from 'chai';import {Contract, ContractFactory} from 'ethers';import {deployments, ethers} from 'hardhat';import {Options} from '@layerzerolabs/lz-v2-utilities';describe('MyOFT Test', function () { // Constant representing a mock Endpoint ID for testing purposes const eidA = 1; const eidB = 2; // Declaration of variables to be used in the test suite let MyOFT: ContractFactory; let EndpointV2Mock: ContractFactory; let ownerA: SignerWithAddress; let ownerB: SignerWithAddress; let endpointOwner: SignerWithAddress; let myOFTA: Contract; let myOFTB: Contract; let mockEndpointA: Contract; let mockEndpointB: Contract; // Before hook for setup that runs once before all tests in the block before(async function () { // Contract factory for our tested contract MyOFT = await ethers.getContractFactory('MyOFT'); // Fetching the first three signers (accounts) from Hardhat's local Ethereum network const signers = await ethers.getSigners(); ownerA = signers.at(0)!; ownerB = signers.at(1)!; endpointOwner = signers.at(2)!; // The EndpointV2Mock contract comes from @layerzerolabs/test-devtools-evm-hardhat package // and its artifacts are connected as external artifacts to this project // // Unfortunately, hardhat itself does not yet provide a way of connecting external artifacts // so we rely on hardhat-deploy to create a ContractFactory for EndpointV2Mock // // See https://github.com/NomicFoundation/hardhat/issues/1040 const EndpointV2MockArtifact = await deployments.getArtifact('EndpointV2Mock'); EndpointV2Mock = new ContractFactory( EndpointV2MockArtifact.abi, EndpointV2MockArtifact.bytecode, endpointOwner, ); }); // beforeEach hook for setup that runs before each test in the block beforeEach(async function () { // Deploying a mock LZEndpoint with the given Endpoint ID mockEndpointA = await EndpointV2Mock.deploy(eidA); mockEndpointB = await EndpointV2Mock.deploy(eidB); // Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint myOFTA = await MyOFT.deploy('aOFT', 'aOFT', mockEndpointA.address, ownerA.address); myOFTB = await MyOFT.deploy('bOFT', 'bOFT', mockEndpointB.address, ownerB.address); // Setting destination endpoints in the LZEndpoint mock for each MyOFT instance await mockEndpointA.setDestLzEndpoint(myOFTB.address, mockEndpointB.address); await mockEndpointB.setDestLzEndpoint(myOFTA.address, mockEndpointA.address); // Setting each MyOFT instance as a peer of the other in the mock LZEndpoint await myOFTA.connect(ownerA).setPeer(eidB, ethers.utils.zeroPad(myOFTB.address, 32)); await myOFTB.connect(ownerB).setPeer(eidA, ethers.utils.zeroPad(myOFTA.address, 32)); }); // A test case to verify token transfer functionality it('should send a token from A address to B address via each OFT', async function () { // Minting an initial amount of tokens to ownerA's address in the myOFTA contract const initialAmount = ethers.utils.parseEther('100'); await myOFTA.mint(ownerA.address, initialAmount); // Defining the amount of tokens to send and constructing the parameters for the send operation const tokensToSend = ethers.utils.parseEther('1'); const sendParam = [eidB, ethers.utils.zeroPad(ownerB.address, 32), tokensToSend, tokensToSend]; // Defining extra message execution options for the send operation const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex().toString(); // Fetching the native fee for the token send operation const [nativeFee] = await myOFTA.quoteSend(sendParam, options, false, `0x`, `0x`); // Executing the send operation from myOFTA contract await myOFTA.send(sendParam, options, [nativeFee, 0], ownerA.address, '0x', '0x', { value: nativeFee, }); // Fetching the final token balances of ownerA and ownerB const finalBalanceA = await myOFTA.balanceOf(ownerA.address); const finalBalanceB = await myOFTB.balanceOf(ownerB.address); // Asserting that the final balances are as expected after the send operation expect(finalBalanceA.eq(initialAmount.sub(tokensToSend))).to.be.true; expect(finalBalanceB.eq(tokensToSend)).to.be.true; });});
### Example Foundry Test[](#example-foundry-test "Direct link to Example Foundry Test")
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.20;// Mock importsimport { OFTMock } from "../mocks/OFTMock.sol";import { ERC20Mock } from "../mocks/ERC20Mock.sol";import { OFTComposerMock } from "../mocks/OFTComposerMock.sol";// OApp importsimport { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";// OFT importsimport { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";import { MessagingFee, MessagingReceipt } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol";import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol";import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";// OZ importsimport { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";// Forge importsimport "forge-std/console.sol";// DevTools importsimport { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol";contract MyOFTTest is TestHelperOz5 { using OptionsBuilder for bytes; uint32 private aEid = 1; uint32 private bEid = 2; OFTMock private aOFT; OFTMock private bOFT; address private userA = address(0x1); address private userB = address(0x2); uint256 private initialBalance = 100 ether; function setUp() public virtual override { // Provide initial Ether balances to users for testing purposes vm.deal(userA, 1000 ether); vm.deal(userB, 1000 ether); // Call the base setup function from the TestHelperOz5 contract super.setUp(); // Initialize 2 endpoints, using UltraLightNode as the library type setUpEndpoints(2, LibraryType.UltraLightNode); // Deploy two instances of OFTMock for testing, associating them with respective endpoints aOFT = OFTMock( _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) ); bOFT = OFTMock( _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) ); // Configure and wire the OFTs together address[] memory ofts = new address[](2); ofts[0] = address(aOFT); ofts[1] = address(bOFT); this.wireOApps(ofts); // Mint initial tokens for userA and userB aOFT.mint(userA, initialBalance); bOFT.mint(userB, initialBalance); } // Test the constructor to ensure initial setup and state are correct function test_constructor() public { // Check that the contract owner is correctly set assertEq(aOFT.owner(), address(this)); assertEq(bOFT.owner(), address(this)); // Verify initial token balances for userA and userB assertEq(aOFT.balanceOf(userA), initialBalance); assertEq(bOFT.balanceOf(userB), initialBalance); // Verify that the token address is correctly set to the respective OFT instances assertEq(aOFT.token(), address(aOFT)); assertEq(bOFT.token(), address(bOFT)); } // Test sending OFT tokens from one user to another function test_send_oft() public { uint256 tokensToSend = 1 ether; // Build options for the send operation bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); // Set up parameters for the send operation SendParam memory sendParam = SendParam( bEid, addressToBytes32(userB), tokensToSend, tokensToSend, options, "", "" ); // Quote the fee for sending tokens MessagingFee memory fee = aOFT.quoteSend(sendParam, false); // Verify initial balances before the send operation assertEq(aOFT.balanceOf(userA), initialBalance); assertEq(bOFT.balanceOf(userB), initialBalance); // Perform the send operation vm.prank(userA); aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); // Verify that the packets were correctly sent to the destination chain. // @param _dstEid The endpoint ID of the destination chain. // @param _dstAddress The OApp address on the destination chain. verifyPackets(bEid, addressToBytes32(address(bOFT))); // Check balances after the send operation assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); } // Test sending OFT tokens with a composed message function test_send_oft_compose_msg() public { uint256 tokensToSend = 1 ether; // Create an instance of the OFTComposerMock contract OFTComposerMock composer = new OFTComposerMock(); // Build options for the send operation with a composed message bytes memory options = OptionsBuilder .newOptions() .addExecutorLzReceiveOption(200000, 0) .addExecutorLzComposeOption(0, 500000, 0); bytes memory composeMsg = hex"1234"; // Set up parameters for the send operation SendParam memory sendParam = SendParam( bEid, addressToBytes32(address(composer)), tokensToSend, tokensToSend, options, composeMsg, "" ); // Quote the fee for sending tokens MessagingFee memory fee = aOFT.quoteSend(sendParam, false); // Verify initial balances before the send operation assertEq(aOFT.balanceOf(userA), initialBalance); assertEq(bOFT.balanceOf(address(composer)), 0); // Perform the send operation vm.prank(userA); (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: fee.nativeFee }( sendParam, fee, payable(address(this)) ); // Verify that the packets were correctly sent to the destination chain. // @param _dstEid The endpoint ID of the destination chain. // @param _dstAddress The OApp address on the destination chain. verifyPackets(bEid, addressToBytes32(address(bOFT))); // Set up parameters for the composed message uint32 dstEid_ = bEid; address from_ = address(bOFT); bytes memory options_ = options; bytes32 guid_ = msgReceipt.guid; address to_ = address(composer); bytes memory composerMsg_ = OFTComposeMsgCodec.encode( msgReceipt.nonce, aEid, oftReceipt.amountReceivedLD, abi.encodePacked(addressToBytes32(userA), composeMsg) ); // Execute the composed message this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); // Check balances after the send operation assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); assertEq(bOFT.balanceOf(address(composer)), tokensToSend); // Verify the state of the composer contract assertEq(composer.from(), from_); assertEq(composer.guid(), guid_); assertEq(composer.message(), composerMsg_); assertEq(composer.executor(), address(this)); assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test }}
* [Example Hardhat Test](#example-hardhat-test)
* [Example Foundry Test](#example-foundry-test)
---
# Deploying Contracts | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The LayerZero CLI tool uses the [hardhat-deploy](https://www.npmjs.com/package/hardhat-deploy)
plugin to deploy contracts on multiple chains.
After adding your `MNEMONIC` or `PRIVATE_KEY` to your dotenv file and adding networks in your `hardhat.config.ts`, run the following command to deploy your LayerZero contracts:
npx hardhat lz:deploy
### Selecting Chains[](#selecting-chains "Direct link to Selecting Chains")
You will be prompted to select which chains to deploy to:
info: Compiling you hardhat projectNothing to compile? Which networks would you like to deploy? ›Instructions: ↑/↓: Highlight option ←/→/[space]: Toggle selection [a,b,c]/delete: Filter choices enter/return: Complete answerFiltered results for: Enter something to filter◉ fuji◉ amoy◉ sepolia
If you wish to deploy to all blockchain networks selected, simply hit enter to continue deployment.
To deselect a chain for deployment, highlight the chain and toggle the selection using the space bar or arrow keys:
Filtered results for: Enter something to filter◉ fuji◯ amoy◉ sepolia
### Adding Deploy Script Tags[](#adding-deploy-script-tags "Direct link to Adding Deploy Script Tags")
Afterwards you'll be prompted to choose which deploy script tags to use. By default, each CLI example contains a starter deploy script, with the deploy script tag being the contract name:
deploy.tags = [contractName];
* OApp
* OFT
* OFT Adapter
* ONFT
The generic message passing standard for creating [Omnichain Applications (OApps)](/v2/developers/evm/oapp/overview)
:
info: Compiling you hardhat projectNothing to compile✔ Which networks would you like to deploy? › bsc_testnet, amoy, sepolia? Which deploy script tags would you like to use? › MyOApp
An ERC20 extended with core bridging logic from OApp, creating an [Omnichain Fungible Token (OFT)](/v2/developers/evm/oft/quickstart)
:
info: Compiling you hardhat projectNothing to compile✔ Which networks would you like to deploy? › bsc_testnet, amoy, sepolia? Which deploy script tags would you like to use? › MyOFT
Variant of OFT for adapting deployed ERC20 tokens as Omnichain Fungible Tokens, creating an [OFT Adapter](/v2/developers/evm/oft/quickstart)
:
info: Compiling you hardhat projectNothing to compile✔ Which networks would you like to deploy? › bsc_testnet, amoy, sepolia? Which deploy script tags would you like to use? › MyOFTAdapter
An ERC721 extended with core bridging logic from OApp, creating an [Omnichain Non-Fungible Token (ONFT)](/v2/developers/evm/oft/quickstart)
:
info: Compiling you hardhat projectNothing to compile✔ Which networks would you like to deploy? › bsc_testnet, amoy, sepolia? Which deploy script tags would you like to use? › MyONFT
You will need to add a new deploy script for any new contracts added to the repo.
### Running the Deployer[](#running-the-deployer "Direct link to Running the Deployer")
After selecting either all or a specific deploy script, the deployer will those contracts on your specified chains.
warn: Will use all deployment scripts✔ Do you want to continue? … yesNetwork: amoyDeployer: 0x0000000000000000000000000000000000000000Network: fujiDeployer: 0x0000000000000000000000000000000000000000Network: sepoliaDeployer: 0x0000000000000000000000000000000000000000Deployed contract: MyOFT, network: amoy, address: 0x0000000000000000000000000000000000000000Deployed contract: MyOFT, network: fuji, address: 0x0000000000000000000000000000000000000000Deployed contract: MyOFT, network: sepolia, address: 0x0000000000000000000000000000000000000000info: ✓ Your contracts are now deployed
You should see an output in your `./deployments` folder, or have one generated, containing your contracts:
contracts / // your contracts folder deploy / // hardhat-deploy scripts deployments / // your hardhat-deploy deployments amoy / // network name defined in hardhat.config.ts MyOFT.json; // deployed-contract jsonfuji / MyOFT.json;sepolia / MyOFT.json;test / // unit-tests, both hardhat and foundry enabled foundry.toml; // normal foundry.toml for remappings and project configurationhardhat.config.ts; // standard hardhat.config.ts, with layerzero endpoint mappingslayerzero.config.ts; // special LayerZero config file (more on this later)
Your contract deployments can now be configured in your `layerzero.config.ts`!
* [Selecting Chains](#selecting-chains)
* [Adding Deploy Script Tags](#adding-deploy-script-tags)
* [Running the Deployer](#running-the-deployer)
---
# Configuring Contracts | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
For each contract in your config file, you can configure the following:
FromOApp.transferOwnership(newOwner)FromOApp.setPeer(dstEid, peer)FromOApp.setEnforcedOptions()EndpointV2.setSendLibrary(OApp, dstEid, newLib)EndpointV2.setReceiveLibrary(OApp, dstEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(OApp, dstEid, lib, gracePeriod)EndpointV2.setConfig(OApp, sendLibrary, sendConfig)EndpointV2.setConfig(OApp, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)
Adding Configurations[](#adding-configurations "Direct link to Adding Configurations")
----------------------------------------------------------------------------------------
To configure your OApp, you will need to change your `layerzero.config.ts` for your desired pathways.
### Initializing `Config`[](#initializing-config "Direct link to initializing-config")
You can initialize your OApp configurations by running:
npx hardhat lz:oapp:config:init --contract-name DEPLOYMENT_NAME --oapp-config CONFIG_FILE_NAME
This will auto-populate the provided config file, or create the file if the path does not exist, with the current LayerZero default configurations as a placeholder.
For example, running:
npx hardhat lz:oapp:config:init --contract-name MyOApp --oapp-config testnet.layerzero.config.ts
will create a new file in my directory with the correct type interface:
contracts/deploy/test/foundry.tomlhardhat.config.tslayerzero.config.tstestnet.layerzero.config.ts <-----
caution
Make sure the `DEPLOYMENT_NAME` exists in your `./deployments` folder, otherwise the task will fail.
Head to the `layerzero.config.ts` and scroll down to `module.exports`.
As explained previously, the CLI Toolkit organizes your configurations on a per-pathway basis:
module.exports = { // Define the contracts to be deployed on each network // Each contract is associated with a specific blockchain. contracts: [ { contract: sepoliaContract, }, { contract: bscContract, }, ], // Define the pathway between each contract. // This allows for cross-chain communication using LayerZero. connections: [ { from: bscContract, to: sepoliaContract, }, { from: sepoliaContract, to: bscContract, }, ],};
To add a specific pathway configuration, add a `config: {}` to your connection:
module.exports = { // Define the contracts to be deployed on each network // Each contract is associated with a specific blockchain. contracts: [ { contract: sepoliaContract, }, { contract: bscContract, }, ], // Define the pathway between each contract. // This allows for cross-chain communication using LayerZero. connections: [ { from: bscContract, to: sepoliaContract, config: {}, }, { from: sepoliaContract, to: bscContract, }, ],};
Each pathway contains a `config`, containing multiple configuration structs for changing how your OApp sends and receives messages, specifically for the chain your OApp is sending `from`:
| Name | Type | Description |
| --- | --- | --- |
| `sendLibrary` | Address | The message library used for configuring all sent messages `from` this chain. (e.g., `SendUln302.sol`) |
| `receiveLibraryConfig` | Struct | A struct containing the receive message library address (e.g., `ReceiveUln302.sol`), and an optional BigInt, `gracePeriod`, the time to wait before updating to a new MessageLib version during version migration. Controls how the `from` chain receives messages. |
| `receiveLibraryTimeoutConfig` | Struct | An optional param, defining when the old receive library (`lib`) will expire (`expiry`) during version migration. |
| `sendConfig` | Struct | Controls how the OApp sends `from` this pathway, containing two more structs: `executorConfig` and `ulnConfig` (DVNs). |
| `receiveConfig` | Struct | Controls how the OApp (`from`) receives messages, specifically the `ulnConfig` (DVNs). |
| `enforcedOptions` | Struct | Controls the minimum destination gas sent to the destination, per message type (e.g., `_lzReceive`, `lzCompose`, etc.) in your OApp. |
tip
When adding a `config`, consider that connections moves in a bidirectional, two-way path:
* The `sendConfig` applies to all message sent `from` **Chain A** and received by the `to` address, **Chain B**.
* The `receiveConfig` applies to all messages received by **Chain A** (`from`), sent from **Chain B** (the `to` contract).
For example, this `config: {}` applies only to how the `bscContract` sends messages to the `sepoliaContract`, and how the `bscContract` receives messages from the `sepoliaContract`.
### Adding `sendLibrary`[](#adding-sendlibrary "Direct link to adding-sendlibrary")
Every configuration should start by adding a `sendLibrary`.
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", }, },],
When running `lz:oapp:wire`, this will call `EndpointV2`:
// LayerZero/V2/protocol/contracts/interfaces/IMessageLibManager.solfunction setSendLibrary(address _oapp, uint32 _eid, address _newLib) external;
Each [MessageLib](/v2/concepts/protocol/message-library)
contains the available configuration options for the protocol, and so must be set by the application owner to prevent unintended updates.
info
You should use the `sendLibrary` address for the chain you're sending `from` (i.e., `SendUln302.sol` on BSC).
info
The MessageLib Registry is append only, meaning that old Message Libraries will always be available for OApps. Locking your Library is only necessary to prevent updates.
### Adding `receiveLibrary`[](#adding-receivelibrary "Direct link to adding-receivelibrary")
Every configuration should also add a `receiveLibrary`. Similar to the `sendLibrary`, the OApp owner must also set the Receive Library to ensure that your configured application settings will be locked.
To do this, add a `receiveLibraryConfig`:
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", receiveLibraryConfig: { // Required Receive Library Address on BSC receiveLibrary: "0x0000000000000000000000000000000000000000", // Optional Grace Period for Switching Receive Library Address on BSC gracePeriod: BigInt(0), }, // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC receiveLibraryTimeoutConfig: { lib: "0x0000000000000000000000000000000000000000", expiry: BigInt(0), }, }, },],
The Receive Library also provides two additional parameters to help future-proof OApp's for migrating MessageLib versions:
* `gracePeriod`: the time to wait before updating to a new MessageLib version during version migration. If the grace period is 0, it will delete the timeout configuration.
* `expiry`: the time at which messages in-flight from the old library will be considered invalid. This is mainly for handling messages that are in-flight during the migration.
In most cases, setting the `gracePeriod` to 0 will be sufficient.
When running `lz:oapp:wire`, this config will call `EndpointV2`:
// LayerZero/V2/protocol/contracts/interfaces/IMessageLibManager.solfunction setReceiveLibrary(address _oapp, uint32 _eid, address _newLib, uint256 _gracePeriod) external;function setReceiveLibraryTimeout(address _oapp, uint32 _eid, address _lib, uint256 _gracePeriod) external;
### Adding `sendConfig`[](#adding-sendconfig "Direct link to adding-sendconfig")
Your `sendConfig` controls what [DVN addresses](/v2/deployments/dvn-addresses)
and [Executor addresses](/v2/deployments/deployed-contracts)
should be paid to verify and execute when a message is sent.
info
Each DVN and Executor contains both on-chain and off-chain component. When sending a message, you pay the DVNs and Executors contracts on the source chain, and they relay the message to the equivalent contracts on the destination chain.
For your `sendConfig`, use the DVNs and Executor contract addresses on the same chain as your sending OApp.
tip
DVNs only need to be the same for a given pathway.
You can have one set of DVNs verifying transactions from `Arbitrum` to `Base` and `Base` to `Arbitrum`, and a separate set of DVNs verifying transactions from `Arbitrum` to `Avalanche` and `Avalanche` to `Arbitrum`.
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", // Required Receive Library Config receiveLibraryConfig: { // Required Receive Library Address on BSC receiveLibrary: "0x0000000000000000000000000000000000000000", // Optional Grace Period for Switching Receive Library Address on BSC gracePeriod: BigInt(0), }, // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC receiveLibraryTimeoutConfig: { lib: "0x0000000000000000000000000000000000000000", expiry: BigInt(0), }, // Optional Send Configuration // @dev Controls how the `from` chain sends messages to the `to` chain. sendConfig: { executorConfig: { maxMessageSize: 10000, // The configured Executor address on BSC executor: "0x0000000000000000000000000000000000000000", }, ulnConfig: { // The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC). confirmations: BigInt(0), // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until ALL `requiredDVNs` verify the message. requiredDVNs: [], // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, }, },],
This will call `EndpointV2.setConfig`:
// LayerZero/V2/protocol/contracts/interfaces/IMessageLibManager.solstruct SetConfigParam { uint32 eid; uint32 configType; bytes config;}function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external;
The Executor and ULN `configType` and `config`:
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.soluint32 internal constant CONFIG_TYPE_EXECUTOR = 1;uint32 internal constant CONFIG_TYPE_ULN = 2;
// LayerZero/V2/messagelib/contracts/uln/SendLibBase.solstruct ExecutorConfig { uint32 maxMessageSize; address executor;}
// LayerZero/V2/messagelib/contracts/uln/UlnBase.solstruct UlnConfig { uint64 confirmations; // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNThreshold; // (0, optionalDVNCount] address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs}
### Adding `receiveConfig`[](#adding-receiveconfig "Direct link to adding-receiveconfig")
The receive configuration controls what [DVN addresses](/v2/deployments/dvn-addresses)
your OApp expects to have verified the message in-flight.
tip
For example, if `BSC` is receiving messages from `Sepolia`, you should use the DVN contract addresses on `BSC` for each DVN provider you have in your `sendConfig`.
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", // Required Receive Library Config receiveLibraryConfig: { // Required Receive Library Address on BSC receiveLibrary: "0x0000000000000000000000000000000000000000", // Optional Grace Period for Switching Receive Library Address on BSC gracePeriod: BigInt(0), }, // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC receiveLibraryTimeoutConfig: { lib: "0x0000000000000000000000000000000000000000", expiry: BigInt(0), }, // Optional Send Configuration // @dev Controls how the `from` chain sends messages to the `to` chain. sendConfig: { executorConfig: { maxMessageSize: 99, // The configured Executor address on BSC executor: "0x0000000000000000000000000000000000000000", }, ulnConfig: { // The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC). confirmations: BigInt(42), // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until ALL `requiredDVNs` verify the message. requiredDVNs: [], // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, // Optional Receive Configuration // @dev Controls how the `from` chain receives messages from the `to` chain. receiveConfig: { ulnConfig: { // The number of block confirmations to expect from the `to` chain (Sepolia). confirmations: BigInt(42), // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC). // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message. requiredDVNs: [], // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, }, },],
This will set the `receiveConfig` in `EndpointV2.setConfig`:
// LayerZero/V2/messagelib/contracts/uln/UlnBase.solstruct UlnConfig { uint64 confirmations; // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default) uint8 optionalDVNThreshold; // (0, optionalDVNCount] address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs}
### Adding `enforcedOptions`[](#adding-enforcedoptions "Direct link to adding-enforcedoptions")
You can specify both a minimum destination gas and `msg.value` that users must pay for both your contract's `lzReceive` and \``lzCompose` logic to execute as intended.
The CLI Toolkit enables you to configure your message options in a human-readable format, provided that your OApp has added an [Enforced Option Message Type](/v2/developers/evm/oapp/overview#optional-enforced-options)
.
info
The **Omnichain Fungible Token (OFT) Standard** by default already has **Enforced Options** added to the contract, with two message types available:
// @dev execution types to handle different enforcedOptionsuint16 internal constant SEND = 1; // a standard token transfer via lzReceiveuint16 internal constant SEND_AND_CALL = 2; // a token transfer, followed by a composable call via lzCompose
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", receiveLibraryConfig: { // Required Receive Library Address on BSC receiveLibrary: "0x0000000000000000000000000000000000000000", // Optional Grace Period for Switching Receive Library Address on BSC gracePeriod: BigInt(0), }, // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC receiveLibraryTimeoutConfig: { lib: "0x0000000000000000000000000000000000000000", expiry: BigInt(0), }, // Optional Send Configuration // @dev Controls how the `from` chain sends messages to the `to` chain. sendConfig: { executorConfig: { maxMessageSize: 99, // The configured Executor address on BSC executor: "0x0000000000000000000000000000000000000000", }, ulnConfig: { // The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC). confirmations: BigInt(42), // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until ALL `requiredDVNs` verify the message. requiredDVNs: [], // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, // Optional Receive Configuration // @dev Controls how the `from` chain receives messages from the `to` chain. receiveConfig: { ulnConfig: { // The number of block confirmations to expect from the `to` chain (Sepolia). confirmations: BigInt(42), // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC). // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message. requiredDVNs: [], // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, // Optional Enforced Options Configuration // @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain. enforcedOptions: [ { msgType: 1, // depending on OAppOptionType3 optionType: ExecutorOptionType.LZ_RECEIVE, gas: 65000, // gas limit in wei for EndpointV2.lzReceive value: 0, // msg.value in wei for EndpointV2.lzReceive }, { msgType: 1, optionType: ExecutorOptionType.NATIVE_DROP, amount: 0, // amount of native gas token in wei to drop to receiver address receiver: "0x0000000000000000000000000000000000000000", }, { msgType: 2, optionType: ExecutorOptionType.LZ_RECEIVE, index: 0, gas: 65000, // gas limit in wei for EndpointV2.lzReceive value: 0, // msg.value in wei for EndpointV2.lzReceive }, { msgType: 2, optionType: ExecutorOptionType.COMPOSE, index: 0, // index of EndpointV2.lzCompose message gas: 50000, // gas limit in wei for EndpointV2.lzCompose value: 0, // msg.value in wei for EndpointV2.lzCompose }, ], }, },],
This will call `OApp.setEnforcedOptions` assuming your OApp has inherited from `OAppOptionsType3.sol`:
// LayerZero/V2/oapp/contracts/oapp/interfaces/IOAppOptionsType3.solstruct EnforcedOptionParam { uint32 eid; // Endpoint ID uint16 msgType; // Message Type bytes options; // Additional options}function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) external;
Review the [Transaction Pricing](/v2/developers/evm/technical-reference/tx-pricing)
section and the [Execution Options](/v2/developers/evm/configuration/options)
to better understand how you should add your execution gas settings.
### Adding `delegate`[](#adding-delegate "Direct link to adding-delegate")
// layerzero.config.tscontracts: [ { contract: sepolia, config: { delegate: '0x0000000000000000000000000000000000000000', }, }, { contract: bsc, config: { delegate: '0x0000000000000000000000000000000000000000', }, },];
### Adding `owner`[](#adding-owner "Direct link to adding-owner")
// layerzero.config.tscontracts: [ { contract: sepolia, config: { owner: '0x0000000000000000000000000000000000000000', }, }, { contract: bsc, config: { owner: '0x0000000000000000000000000000000000000000', }, },];
To transfer ownership, you will need to run a separate command:
npx hardhat lz:ownable:transfer-ownership --oapp-config layerzero.config.ts
caution
Once you transfer ownership, you can no longer call `OApp.setDelegate` and `OApp.setEnforcedOptions`. You should ensure all other configurations have been set to your liking before transferring ownership.
### Final Config[](#final-config "Direct link to Final Config")
Your final config may have different settings, but should define the following parameters:
connections: [ { // Sets the peer `from -> to`. Optional, you do not have to connect all pathways. from: bscContract, to: sepoliaContract, // Optional Configuration config: { // Required Send Library Address on BSC sendLibrary: "0x0000000000000000000000000000000000000000", receiveLibraryConfig: { // Required Receive Library Address on BSC receiveLibrary: "0x0000000000000000000000000000000000000000", // Optional Grace Period for Switching Receive Library Address on BSC gracePeriod: BigInt(0), }, // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC receiveLibraryTimeoutConfig: { lib: "0x0000000000000000000000000000000000000000", expiry: BigInt(0), }, // Optional Send Configuration // @dev Controls how the `from` chain sends messages to the `to` chain. sendConfig: { executorConfig: { maxMessageSize: 10000, // The configured Executor address on BSC executor: "0x0000000000000000000000000000000000000000", }, ulnConfig: { // The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC). confirmations: BigInt(0), // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until ALL `requiredDVNs` verify the message. requiredDVNs: [], // The address of the DVNs you will pay to verify a sent message on the source chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, // Optional Receive Configuration // @dev Controls how the `from` chain receives messages from the `to` chain. receiveConfig: { ulnConfig: { // The number of block confirmations to expect from the `to` chain (Sepolia). confirmations: BigInt(0), // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC). // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message. requiredDVNs: [], // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC). // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message. optionalDVNs: [ "0x_POLYHEDRA_DVN_ADDRESS_ON_BSC", "0x_LAYERZERO_DVN_ADDRESS_ON_BSC", ], // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified. optionalDVNThreshold: 2, }, }, // Optional Enforced Options Configuration // @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain. enforcedOptions: [ { msgType: 1, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 60000, value: 0, }, { msgType: 1, optionType: ExecutorOptionType.NATIVE_DROP, amount: 0, receiver: "0x0000000000000000000000000000000000000000", }, { msgType: 2, optionType: ExecutorOptionType.LZ_RECEIVE, index: 0, gas: 60000, value: 1, }, { msgType: 2, optionType: ExecutorOptionType.COMPOSE, index: 0, gas: 50000, value: 0, }, ], }, },],
Applying Changes[](#applying-changes "Direct link to Applying Changes")
-------------------------------------------------------------------------
Wiring your contracts will set the `peer` address for your OApp or OFT and initialize the desired configuration in your `layerzero.config.ts`. If unfamiliar with this concept, review the [OApp Quickstart](/v2/developers/evm/oapp/overview#setting-peer)
.
### Wiring Contracts[](#wiring-contracts "Direct link to Wiring Contracts")
The CLI Tool makes this one step easier by enabling you to wire and configure your contract pathways with a single command:
$ npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
Before wiring your contracts, you should review your `layerzero.config.ts` to ensure that you have specified accurately the configuration you want to set.
Wiring your contracts will set the `peer` address for your OApp or OFT and initialize the desired configuration in your `layerzero.config.ts`. If unfamiliar with this concept, review the [OApp Quickstart](/v2/developers/evm/oapp/overview#setting-peer)
.
The CLI Tool makes this one step easier by enabling you to wire and configure your contract pathways with a single command:
$ npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
Before wiring your contracts, you should review your `layerzero.config.ts` to ensure that you have specified accurately the configuration you want to set.
### Checking `setPeers`[](#checking-setpeers "Direct link to checking-setpeers")
To check if your contracts have correctly been set to communicate with one another, you can run:
npx hardhat lz:oapp:peers:get --oapp-config layerzero.config.ts
### Checking Pathway `config`[](#checking-pathway-config "Direct link to checking-pathway-config")
To confirm your OApp's configuration has been set as intended, you can run:
$ npx hardhat lz:oapp:config:get --oapp-config layerzero.config.ts
### Checking `executor`[](#checking-executor "Direct link to checking-executor")
To see your OApp's configured executor, you can run:
npx hardhat lz:oapp:config:get:executor
### Checking `enforcedOptions`[](#checking-enforcedoptions "Direct link to checking-enforcedoptions")
To see your OApp's configured execution gas has been set as intended, you can run:
npx hardhat lz:oapp:enforced-opts:get --oapp-config layerzero.config.ts
### Checking Pathway `defaults`[](#checking-pathway-defaults "Direct link to checking-pathway-defaults")
To see what the default configuration is for any pathway, run:
npx hardhat lz:oapp:config:get --oapp-config layerzero.config.ts
### Wiring via Safe multisig[](#wiring-via-safe-multisig "Direct link to Wiring via Safe multisig")
If your contracts are owned by a Safe multisig wallet, you must define the multisig's `safeUrl` and `safeAddress` per chain in your `hardhat.config.ts` file to enable the submission of wire transactions for multisig approval. `safeUrl` refers to the URL of the [Safe Transaction Service](https://docs.safe.global/core-api/api-safe-transaction-service)
for a given network. For the endpoints deployed by Safe themselves on popular networks, you can find the URLs in the [Safe Transaction Service API Reference](https://docs.safe.global/core-api/transaction-service-reference/mainnet)
.
#### Step 1: Configure your Safe multisig[](#step-1-configure-your-safe-multisig "Direct link to Step 1: Configure your Safe multisig")
In your hardhat config, add `safeConfig` to your networks, with your network specific `safeUrl` and `safeAddress` mapped accordingly:
// hardhat.config.tsnetworks: { // Include configurations for other networks as needed fuji: { /* ... */ // Network-specific settings safeConfig: { safeUrl: 'http://something', // URL of the Safe Transaction Service for the network safeAddress: 'address' // Address of the Safe wallet for the network } }}
#### Step 2: Use your safe config[](#step-2-use-your-safe-config "Direct link to Step 2: Use your safe config")
When wiring, pass the `--safe` flag in your wire command.
$ npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --safe
This command initiates the wiring process under the multisig setup, pushing transactions to the specified multisig wallet for necessary approvals.
note
Ensure your development tools are up to date to utilize this feature, as it relies on the latest versions of the required dependencies.
* [Adding Configurations](#adding-configurations)
* [Initializing `Config`](#initializing-config)
* [Adding `sendLibrary`](#adding-sendlibrary)
* [Adding `receiveLibrary`](#adding-receivelibrary)
* [Adding `sendConfig`](#adding-sendconfig)
* [Adding `receiveConfig`](#adding-receiveconfig)
* [Adding `enforcedOptions`](#adding-enforcedoptions)
* [Adding `delegate`](#adding-delegate)
* [Adding `owner`](#adding-owner)
* [Final Config](#final-config)
* [Applying Changes](#applying-changes)
* [Wiring Contracts](#wiring-contracts)
* [Checking `setPeers`](#checking-setpeers)
* [Checking Pathway `config`](#checking-pathway-config)
* [Checking `executor`](#checking-executor)
* [Checking `enforcedOptions`](#checking-enforcedoptions)
* [Checking Pathway `defaults`](#checking-pathway-defaults)
* [Wiring via Safe multisig](#wiring-via-safe-multisig)
---
# Debugging LayerZero Errors | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The LayerZero sample project provides powerful tools for listing and decoding custom errors from the protocol and your OApp.
Using the CLI tool, you can identify errors at the protocol level, debug, and resolve issues quickly during development and deployment.
### Commands[](#commands "Direct link to Commands")
To list all the custom errors defined in the LayerZero protocol and your project, run:
npx hardhat lz:errors:list
To decode custom error data based on the error selector, run:
npx hardhat lz:errors:decode
The output will provide information about the custom error name, which you can compare against the error list.
* [Commands](#commands)
---
# LayerZero V2 OApp Quickstart | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The OApp Standard provides developers with a _generic message passing interface_ to **send** and **receive** arbitrary pieces of data between contracts existing on different blockchain networks.
 
This interface can easily be extended to include anything from specific financial logic in a DeFi application, a voting mechanism in a DAO, and broadly any smart contract use case.
LayerZero provides `OApp.sol` for implementing generic message passing in your contracts:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { OAppSender } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol";// @dev import the origin so its exposed to OApp implementersimport { OAppReceiver, Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppReceiver.sol";import { OAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppCore.sol";abstract contract OApp is OAppSender, OAppReceiver { constructor(address _endpoint, address _owner) OAppCore(_endpoint, _owner) {} function oAppVersion() public pure virtual returns (uint64 senderVersion, uint64 receiverVersion) { senderVersion = SENDER_VERSION; receiverVersion = RECEIVER_VERSION; }}
`OApp.sol` provides the core interface logic for interacting with the LayerZero `EndpointV2.sol` contract interface, utilities for managing cross-chain applications, and an extendable send / receive interface for application business logic:
 
info
If you prefer reading the contract code, see the OApp package in the LayerZero Devtools [**OApp Package**](https://github.com/LayerZero-Labs/devtools/blob/main/packages/oapp-evm/contracts/oapp/OApp.sol)
.
tip
For developers interested in sending and receiving omnichain tokens, we recommend inheriting the [**OFT Standard**](/v2/developers/evm/oft/quickstart)
directly instead of OApp.
Installation[](#installation "Direct link to Installation")
-------------------------------------------------------------
To start using LayerZero contracts, you can install the [OApp package](https://github.com/LayerZero-Labs/devtools/tree/main/packages/oapp-evm)
to an existing project:
* npm
* yarn
* pnpm
* forge
npm install @layerzerolabs/oapp-evm
yarn add @layerzerolabs/oapp-evm
pnpm add @layerzerolabs/oapp-evm
forge init
forge install https://github.com/LayerZero-Labs/devtools
forge install https://github.com/LayerZero-Labs/layerzero-v2
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0
Then add to your `foundry.toml` under `[profile.default]`:
[profile.default]src = "src"out = "out"libs = ["lib"]remappings = [ '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',]
info
LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable)
and V4 contracts. Specify your desired version in your project's package.json:
"resolutions": { "@openzeppelin/contracts": "^5.0.1",}
tip
LayerZero also provides [**create-lz-oapp**](/v2/developers/evm/create-lz-oapp/start)
, an npx package that allows developers to create any omnichain application in <4 minutes! Get started by running the following from your command line:
npx create-lz-oapp@latest
Creating an OApp Contract[](#creating-an-oapp-contract "Direct link to Creating an OApp Contract")
----------------------------------------------------------------------------------------------------
Every OApp will need to set two arguments in the constructor:
1. **Endpoint Address:** The source chain’s [Endpoint Address](/v2/deployments/deployed-contracts)
for communicating with the protocol.
2. **Owner Address:** The address that will own the OApp contract.
And define the send and receive function:
* `_lzSend`: the internal function your application must call to send an omnichain message.
* `_lzReceive`: the function to receive an omnichain message. This internal method is called whenever the `EndpointV2.lzReceive()` is executed at the receiving OApp.
info
The OApp Contract Standard inherits directly from both `OAppSender.sol` and `OAppReceiver.sol`, so that your child contract has handling for both sending and receiving messages. You can inherit directly from either the [**Sender**](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OAppSender.sol)
or [**Receiver**](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OAppReceiver.sol)
contract if your child contract only needs one type of handling, as shown in [**Getting Started**](/v2/developers/evm/getting-started)
.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OApp, Origin, MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";contract MyOApp is OApp { constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {} // Some arbitrary data you want to deliver to the destination chain! string public data; /** * @notice Sends a message from the source to destination chain. * @param _dstEid Destination chain's endpoint ID. * @param _message The message to send. * @param _options Message execution options (e.g., for sending gas to destination). */ function send( uint32 _dstEid, string memory _message, bytes calldata _options ) external payable { // Encodes the message before invoking _lzSend. // Replace with whatever data you want to send! bytes memory _payload = abi.encode(_message); _lzSend( _dstEid, _payload, _options, // Fee in native gas and ZRO token. MessagingFee(msg.value, 0), // Refund address in case of failed source message. payable(msg.sender) ); } /** * @dev Called when data is received from the protocol. It overrides the equivalent function in the parent contract. * Protocol messages are defined as packets, comprised of the following parameters. * @param _origin A struct containing information about where the packet came from. * @param _guid A global unique identifier for tracking the packet. * @param payload Encoded message. */ function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata payload, address, // Executor address as specified by the OApp. bytes calldata // Any extra data or options to trigger on receipt. ) internal override { // Decode the payload to get the message // In this case, type is string, but depends on your encoding! data = abi.decode(payload, (string)); }}
Deployment Workflow[](#deployment-workflow "Direct link to Deployment Workflow")
----------------------------------------------------------------------------------
1. Deploy the `OApp` to all the chains you want to connect.
2. Call `MyOApp.setPeer` to whitelist each destination contract on every destination chain.
// The real endpoint ids will vary per chain, and can be found under "Supported Chains"uint32 aEid = 1;uint32 bEid = 2;MyOApp aOApp;MyOApp bOApp;function addressToBytes32(address _addr) public pure returns (bytes32) { return bytes32(uint256(uint160(_addr)));}// Call on both sides per pathwayaOApp.setPeer(bEid, addressToBytes32(address(bOApp)));bOApp.setPeer(aEid, addressToBytes32(address(aOApp)));
3. Set the DVN configuration, including optional settings such as block confirmations, security threshold, the Executor, max message size, and send/receive libraries.
EndpointV2.setSendLibrary(aOApp, bEid, newLib)EndpointV2.setReceiveLibrary(aOApp, bEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(aOApp, bEid, lib, gracePeriod)EndpointV2.setConfig(aOApp, sendLibrary, sendConfig)EndpointV2.setConfig(aOApp, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)
These custom configurations will be stored on-chain as part of EndpointV2 and your respective `SendLibrary` and `ReceiveLibrary`:
// LayerZero V2 MessageLibManager.sol (part of EndpointV2.sol)mapping(address sender => mapping(uint32 dstEid => address lib)) internal sendLibrary;mapping(address receiver => mapping(uint32 srcEid => address lib)) internal receiveLibrary;mapping(address receiver => mapping(uint32 srcEid => Timeout)) public receiveLibraryTimeout;// LayerZero V2 SendLibBase.sol (part of SendUln302.sol)mapping(address oapp => mapping(uint32 eid => ExecutorConfig)) public executorConfigs;// LayerZero V2 UlnBase.sol (both in SendUln302.sol and ReceiveUln302.sol)mapping(address oapp => mapping(uint32 eid => UlnConfig)) internal ulnConfigs;// LayerZero V2 EndpointV2.solmapping(address oapp => address delegate) public delegates;
You can find example scripts to make these calls under [Security and Executor Configuration](/v2/developers/evm/configuration/dvn-executor-config)
.
danger
These configurations control the verification mechanisms of messages sent between your OApps. You should review the above settings carefully.
If no configuration is set, the configuration will fallback to the default configurations set by LayerZero Labs. For example:
/// @notice The Send Library is the Oapp specified library that will be used to send the message to the destination/// endpoint. If the Oapp does not specify a Send Library, the default Send Library will be used./// @dev If the Oapp does not have a selected Send Library, this function will resolve to the default library/// configured by LayerZero/// @return lib address of the Send Library/// @param _sender The address of the Oapp that is sending the message/// @param _dstEid The destination endpoint idfunction getSendLibrary(address _sender, uint32 _dstEid) public view returns (address lib) { lib = sendLibrary[_sender][_dstEid]; if (lib == DEFAULT_LIB) { lib = defaultSendLibrary[_dstEid]; if (lib == address(0x0)) revert Errors.LZ_DefaultSendLibUnavailable(); }}
4. (**Recommended**) Optionally, if you inherit `OAppOptionsType3`, you can enforce specific gas settings when users call `aOApp.send`.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OApp, Origin, MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { OAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";contract MyOApp is OApp, OAppOptionsType3 { /// @notice Message types that are used to identify the various OApp operations. /// @dev These values are used in things like combineOptions() in OAppOptionsType3. uint16 public constant SEND = 1; constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {} // ... contract continues}
EnforcedOptionParam[] memory aEnforcedOptions = new EnforcedOptionParam[](1);// Send gas for lzReceive (A -> B).aEnforcedOptions[0] = EnforcedOptionParam({eid: bEid, msgType: SEND, options: OptionsBuilder.newOptions().addExecutorLzReceiveOption(50000, 0)}); // gas limit, msg.valueaOApp.setEnforcedOptions(aEnforcedOptions);
See more details about each setting below.
### Implementing `_lzSend`[](#implementing-_lzsend "Direct link to implementing-_lzsend")
To start sending messages from your OApp, you'll need to call `_lzSend` with your own contract logic.
Depending on your application, this might initiate token transfers, burn and mint NFTs, or just pass a simple string between chains.
#### Example: Sending a String[](#example-sending-a-string "Direct link to Example: Sending a String")
Consider the scenario where you want to send a simple string `_message` to store on a destination chain.
// Sends a message from the source to destination chain.function send(uint32 _dstEid, string memory _message, bytes calldata _options) external payable { bytes memory _payload = abi.encode(_message); // Encodes message as bytes. _lzSend( _dstEid, // Destination chain's endpoint ID. _payload, // Encoded message payload being sent. _options, // Message execution options (e.g., gas to use on destination). MessagingFee(msg.value, 0), // Fee struct containing native gas and ZRO token. payable(msg.sender) // The refund address in case the send call reverts. );}
You start by first encoding the `_message` as a bytes array and passing five arguments to `_lzSend`:
1. `_dstEid`: The destination Endpoint ID.
2. `_message`: The message to be sent.
3. `_options`: Message execution options for protocol handling _(see below)_.
4. `MessagingFee`: what token will be used to pay for the transaction?
struct MessagingFee { uint256 nativeFee; // Fee amount in native gas token. uint256 lzTokenFee; // Fee amount in ZRO token.}
5. `_refundAddress`: specifies the address to which any excess fees should be refunded.
payable(msg.sender) // The address of the user or contract that initiated the transaction.
info
If your refund address is a smart contract you will need to implement a fallback function in order for it to receive the refund.
### Message Execution Options[](#message-execution-options "Direct link to Message Execution Options")
You might be wondering, what are message execution `_options`?
`_options` are a generated bytes array with specific instructions for the [Security Stack](/v2/concepts/modular-security/security-stack-dvns)
and [Executor](/v2/concepts/permissionless-execution/executors)
to use when handling the authentication and execution of received messages.
You can find how to generate all the available `_options` in [Message Execution Options](/v2/developers/evm/configuration/options)
, but for this tutorial you'll focus on providing the Executor with a gas amount to use when executing our message:
* `ExecutorLzReceiveOption`: instructions for how much gas should be used when calling `lzReceive` on the destination Endpoint.
When generated correctly, the `_options` parameter will be used in the Endpoint `quote` to ensure enough `msg.value` is paid based to match the Executor amount.
For example, to send a vanilla OFT, you usually need `60000` wei in destination native gas during message execution:
_options = 0x0003010011010000000000000000000000000000ea60;
tip
`ExecutorLzReceiveOption` specifies a quote paid in advance on the source chain by the `msg.sender` for the equivalent amount of native gas to be used on the destination chain. If the actual cost to execute the message is less than what was set in `_options`, there is no default way to refund the sender the difference. Application developers need to thoroughly profile and test gas amounts to ensure consumed gas amounts are correct and not excessive.
#### Optional: Enforced Options[](#optional-enforced-options "Direct link to Optional: Enforced Options")
Once you determine ideal message `_options`, you will want to make sure users adhere to it. In the case of OApp, you mostly want to make sure the gas amount you have included in `_options` for the `lzReceive` call can be enforced for all callers of `_lzSend`, to prevent reverts.
To require a caller to use a specific `_options`, your OApp can inherit the enforced options interface `IOAppOptionsType3.sol`:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { OApp, Origin, MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { IOAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppOptionsType3.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";contract MyOApp is OApp, IOAppOptionsType3 { constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {}}
The `setEnforcedOptions` function allows the contract owner to specify mandatory execution options, making sure that the application behaves as expected when users interact with it.
Here is code snippet from `oapp/libs/OAppOptionsType3.sol`:
/** * @dev Sets the enforced options for specific endpoint and message type combinations. * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options. * * @dev Only the owner/admin of the OApp can call this function. * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc. * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose(). */function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner { _setEnforcedOptions(_enforcedOptions);}function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual { for (uint256 i = 0; i < _enforcedOptions.length; i++) { // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining. _assertOptionsType3(_enforcedOptions[i].options); enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options; } emit EnforcedOptionSet(_enforcedOptions);}
To use `setEnforcedOptions`, we only need to pass one parameter:
* `EnforcedOptionParam[]`: a struct specifying the execution options per message type and destination chain.
struct EnforcedOptionParam { uint32 eid; // destination endpoint id uint16 msgType; // the message type bytes options; // the execution option bytes array}
You will need to define your OApp's `msgType` and what those messaging types look like. For example, OFT Standard only has handling for 2 message types:
// @dev execution types to handle different enforcedOptionsuint16 internal constant SEND = 1; // a standard token transfer via send()uint16 internal constant SEND_AND_CALL = 2; // a composed token transfer via send()
You will pass these values in when specifying the `msgType` for your `_options`.
If you're looking for complete example how to set enforced options in Solidity this Foundry [test case](https://github.com/LayerZero-Labs/LayerZero-v2/blob/7aebbd7c79b2dc818f7bb054aed2405ca076b9d6/packages/layerzero-v2/evm/oapp/test/OFT.t.sol#L441)
might be helpful:
function test_combine_options() public { uint32 eid = 1; uint16 msgType = 1; bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1); enforcedOptionsArray[0] = EnforcedOptionParam(eid, msgType, enforcedOptions); aOFT.setEnforcedOptions(enforcedOptionsArray); bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption( 1.2345 ether, addressToBytes32(userA) ); bytes memory expectedOptions = OptionsBuilder .newOptions() .addExecutorLzReceiveOption(200000, 0) .addExecutorNativeDropOption(1.2345 ether, addressToBytes32(userA)); bytes memory combinedOptions = aOFT.combineOptions(eid, msgType, extraOptions); assertEq(combinedOptions, expectedOptions);}
### Estimating Gas Fees[](#estimating-gas-fees "Direct link to Estimating Gas Fees")
Often with the LayerZero protocol you'll want to know an estimate of how much gas a message will cost to be sent and received.
To do this you can implement a `quote()` function within the OApp contract to return an estimate from the Endpoint contract to use as a recommended `msg.value`.
/* @dev Quotes the gas needed to pay for the full omnichain transaction. * @return nativeFee Estimated gas fee in native gas. * @return lzTokenFee Estimated gas fee in ZRO token. */function quote( uint32 _dstEid, // Destination chain's endpoint ID. string memory _message, // The message to send. bytes calldata _options, // Message execution options bool _payInLzToken // boolean for which token to return fee in) public view returns (uint256 nativeFee, uint256 lzTokenFee) { bytes memory _payload = abi.encode(_message); MessagingFee memory fee = _quote(_dstEid, _payload, _options, _payInLzToken); return (fee.nativeFee, fee.lzTokenFee);}
The `_quote` can be returned in either the native gas token or in ZRO token, supporting both payment methods.
Because cross-chain gas fees are dynamic, this quote should be generated right before calling `_lzSend` to ensure accurate pricing.
tip
Make sure that the arguments passed into the `quote()` function identically match the parameters used in the `lzSend()` function. If parameters mismatch, you may run into errors as your `msg.value` will not match the actual gas quote.
info
Remember that when sending a message through LayerZero, the `msg.sender` will be paying for gas on the source chain, fees to the selected DVNs to validate the message, and for gas on the destination chain to execute the transaction. This results in a single bundled fee on the source chain, abstracting gas away on every other chain, leading to better composability.
### Implementing `_lzReceive`[](#implementing-_lzreceive "Direct link to implementing-_lzreceive")
To start receiving messages on a destination, your OApp needs to override the `_lzReceive` function.
function _lzReceive( Origin calldata _origin, // struct containing info about the message sender bytes32 _guid, // global packet identifier bytes calldata payload, // encoded message payload being received address _executor, // the Executor address. bytes calldata _extraData // arbitrary data appended by the Executor ) internal override { data = abi.decode(payload, (string)); // your logic here}
`_lzReceive` takes a few main inputs for message handling:
1. `_origin`: a struct generated by the protocol containing information about where the message came from.
struct Origin { uint32 srcEid; // The source chain's Endpoint ID. bytes32 sender; // The sending OApp address. uint64 nonce; // The message nonce for the pathway.}
2. `_guid`: a unique identifier for tracking the message.
3. `payload`: the message in encoded bytes format.
4. `_executor`: the address of the Executor calling the Endpoint's `lzReceive` function.
5. `_extraData`: Designed to carry arbitrary data appended by the Executor and passed along with the message payload. Cannot be modified by the OApp.
note
Even if your receiving OApp contract doesn't use every interface parameter, they must be included to match `_lzReceive`'s function signature.
What's great about an OApp is that you can define any arbitrary contract logic to trigger within `_lzReceive`.
That means that this function could store data, trigger other functions, or even invoke a nested `_lzSend` again to trigger an action back on the source chain. For advanced usage, LayerZero provides a full list of [Message Design Patterns](/v2/developers/evm/oapp/message-design-patterns)
to experiment with.
### Setting Delegates[](#setting-delegates "Direct link to Setting Delegates")
In a given OApp, a delegate is able to apply configurations on behalf of the OApp. This delegate gains the ability to handle various critical tasks such as setting configurations and MessageLibs, and skipping or clearing payloads.
By default, the contract owner is set as the delegate. The `setDelegate` function allows for changing this, but we recommend you always keep contract owner as delegate.
function setDelegate(address _delegate) public onlyOwner { endpoint.setDelegate(_delegate);}
For instructions on how to implement custom configurations after setting your delegate, refer to the [OApp Configuration](/v2/developers/evm/configuration/dvn-executor-config)
.
### Security and Governance[](#security-and-governance "Direct link to Security and Governance")
Given the impact associated with deployment, configuration, and debugging functions, OApp owners may want to add additional security measures in place to call core contract functions beyond just the `onlyOwner` requirement, such as:
* **Governance Controls**: Implementing a governance mechanism where decisions to clear messages are voted upon by stakeholders.
* **Multisig Deployment**: Deploying with a multisig wallet, preventing arbitrary actions by any one team member.
* **Timelocks**: Using a timelock to delay the execution of certain function, giving stakeholders time to react if the function is called inappropriately.
Usage[](#usage "Direct link to Usage")
----------------------------------------
That’s it. Once deployed, you just need to complete a few post-deployment requirements.
### Setting Peer[](#setting-peer "Direct link to Setting Peer")
Once you've finished your [OApp Configuration](/v2/developers/evm/configuration/dvn-executor-config)
, you can open the messaging channel and connect your OApp deployments by calling `setPeer`.
A peer is required to be set for each EID (or network). Ideally an OApp (or OFT) will have multiple peers set where one and only one peer exists for one EID.
The function takes 2 arguments: `_eid`, the destination endpoint ID for the chain our other OApp contract lives on, and `_peer`, the destination OApp contract address in `bytes32` format.
// @dev must-have configurations for standard OAppsfunction setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner { peers[_eid] = _peer; // Array of peer addresses by destination. emit PeerSet(_eid, _peer); // Event emitted each time a peer is set.}
caution
This function opens your OApp to start receiving messages from the messaging channel, meaning you should configure any application settings you intend on changing prior to calling `setPeer`.
danger
OApps need `setPeer` to be called correctly on both contracts to send messages. The peer address uses `bytes32` for handling non-EVM destination chains.
If the peer has been set to an incorrect destination address, your messages will not be delivered and handled properly. If not resolved, users can potentially pay gas on source without any corresponding action on destination. You can confirm the peer address is the expected destination OApp address by viewing the `peers` mapping directly.
The [LayerZero Endpoint](/v2/concepts/protocol/layerzero-endpoint)
will use this peer as the destination address for the cross-chain message:
// @dev the endpoint send method called by _lzSendendpoint.send{ value: messageValue }( MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0), _refundAddress);
To see if an address is the trusted peer you expect for a destination, you can read the `peers` mapping directly.
### Calling `send`[](#calling-send "Direct link to calling-send")
Once your source and destination chain contracts have successfully been deployed and peers set, you're ready to begin passing messages between them.
Remember to generate a fee estimate using `quote` first, and then pass the returned native gas amount as your `msg.value`.
> MyOApp.send{value: msg.value}(101, "My first omnichain message!", 0x0003010011010000000000000000000000000000c350)
### Tracing and Troubleshooting[](#tracing-and-troubleshooting "Direct link to Tracing and Troubleshooting")
You can follow your testnet and mainnet transaction statuses using [LayerZero Scan](https://layerzeroscan.com/)
.
Refer to [Debugging Messages](/v2/developers/evm/troubleshooting/debugging-messages)
for any unexpected complications when sending a message.
You can also ask for help or follow development in the [Discord](https://layerzero.network/community)
.
* [Installation](#installation)
* [Creating an OApp Contract](#creating-an-oapp-contract)
* [Deployment Workflow](#deployment-workflow)
* [Implementing `_lzSend`](#implementing-_lzsend)
* [Message Execution Options](#message-execution-options)
* [Estimating Gas Fees](#estimating-gas-fees)
* [Implementing `_lzReceive`](#implementing-_lzreceive)
* [Setting Delegates](#setting-delegates)
* [Security and Governance](#security-and-governance)
* [Usage](#usage)
* [Setting Peer](#setting-peer)
* [Calling `send`](#calling-send)
* [Tracing and Troubleshooting](#tracing-and-troubleshooting)
---
# LayerZero V2 ONFT Quickstart | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The **Omnichain Non-Fungible Token (ONFT) Standard** allows **non-fungible tokens (NFTs)** to be transferred across multiple blockchains without asset wrapping or middlechains.
ONFT Standard Overview[](#onft-standard-overview "Direct link to ONFT Standard Overview")
-------------------------------------------------------------------------------------------
* **ONFT Contract**: Uses a burn-and-mint mechanism. For a fluid NFT that can move directly between chains (e.g. Chain A and Chain B), you must deploy an ONFT contract on every chain. This creates a "mesh" of interconnected contracts.
* **ONFT Adapter**: Uses a lock-and-mint mechanism. If you already have an NFT collection on one chain and want to extend it omnichain, you deploy **a single ONFT Adapter on the source chain**. Then, you deploy ONFT contracts on any new chains where the collection will be transferred. Note that only one ONFT Adapter is allowed in the entire mesh.
This mesh concept is central to all LayerZero implementations: it represents the network of contracts that work together to enable omnichain NFT functionality.
### ONFT (Burn & Mint)[](#onft-burn--mint "Direct link to ONFT (Burn & Mint)")
 
When using **ONFT**, tokens are **burned** on the source chain whenever an omnichain transfer is initiated. LayerZero sends a message to the destination contract instructing it to **mint** the same number of tokens that were burned, ensuring the overall token supply remains consistent.
function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { if (_from != ERC721.ownerOf(_tokenId)) revert OnlyNFTOwner(_from, ERC721.ownerOf(_tokenId)); _burn(_tokenId);}function _credit(address _to, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { _mint(_to, _tokenId);}
**Key Points**
* Default pattern for **new NFT collections**.
* `ONFT721` extends [`ERC721`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#ERC721)
(OpenZeppelin) and adds cross-chain logic.
* Unified supply across chains is maintained by burning on source, minting on destination.
### ONFT Adapter (Lock & Mint)[](#onft-adapter-lock--mint "Direct link to ONFT Adapter (Lock & Mint)")
 
When using **ONFT Adapter**, tokens are **locked** in a contract on the source chain, while the destination contract **mints** or **unlocks** the token after receiving a message from LayerZero. When bridging back, the minted token is **burned** on the remote side, and the original is **unlocked** on the source side.
function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { // Lock the token by transferring it to this adapter contract innerToken.transferFrom(_from, address(this), _tokenId);}function _credit(address _toAddress, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { // Unlock the token by transferring it back to the user innerToken.transferFrom(address(this), _toAddress, _tokenId);}
**Key Points**
* Suitable for **existing NFT collections**.
* The adapter contract is effectively a “lockbox” for your existing ERC721 tokens.
* No changes to your original NFT contract are required. Instead, the adapter implements the cross-chain logic.
ONFT Standard Overview[](#onft-standard-overview-1 "Direct link to ONFT Standard Overview")
---------------------------------------------------------------------------------------------
* ONFT
* ONFT Adapter
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";import { ONFT721Core } from "./ONFT721Core.sol";/** * @title ONFT721 Contract * @dev ONFT721 is an ERC-721 token that extends the functionality of the ONFT721Core contract. */abstract contract ONFT721 is ONFT721Core, ERC721 { string internal baseTokenURI; event BaseURISet(string baseURI); /** * @dev Constructor for the ONFT721 contract. * @param _name The name of the ONFT. * @param _symbol The symbol of the ONFT. * @param _lzEndpoint The LayerZero endpoint address. * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. */ constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) ERC721(_name, _symbol) ONFT721Core(_lzEndpoint, _delegate) {} // @notice Retrieves the address of the underlying ERC721 implementation (ie. this contract). function token() external view returns (address) { return address(this); } function setBaseURI(string calldata _baseTokenURI) external onlyOwner { baseTokenURI = _baseTokenURI; emit BaseURISet(baseTokenURI); } function _baseURI() internal view override returns (string memory) { return baseTokenURI; } /** * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send. * @dev In the case of ONFT where the contract IS the token, approval is NOT required. * @return requiresApproval Needs approval of the underlying token implementation. */ function approvalRequired() external pure virtual returns (bool) { return false; } // @dev Key cross-chain overrides function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { if (_from != ERC721.ownerOf(_tokenId)) revert OnlyNFTOwner(_from, ERC721.ownerOf(_tokenId)); _burn(_tokenId); } function _credit(address _to, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { _mint(_to, _tokenId); }}
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";import { ONFT721Core } from "./ONFT721Core.sol";// @dev ONFT721Adapter is an adapter contract used to enable cross-chain transferring of an existing ERC721 token.abstract contract ONFT721Adapter is ONFT721Core { IERC721 internal immutable innerToken; /** * @dev Constructor for the ONFT721 contract. * @param _token The underlying ERC721 token address this adapts * @param _lzEndpoint The LayerZero endpoint address. * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. */ constructor(address _token, address _lzEndpoint, address _delegate) ONFT721Core(_lzEndpoint, _delegate) { innerToken = IERC721(_token); } // @notice Retrieves the address of the underlying ERC721 implementation (ie. external contract). function token() external view returns (address) { return address(innerToken); } /** * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send. * @dev In the case of ONFT where the contract IS the token, approval is NOT required. * @return requiresApproval Needs approval of the underlying token implementation. */ function approvalRequired() external pure virtual returns (bool) { return true; } // @dev Key cross-chain overrides function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { // @dev Dont need to check onERC721Received() when moving into this contract, ie. no 'safeTransferFrom' required innerToken.transferFrom(_from, address(this), _tokenId); } function _credit(address _toAddress, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { // @dev Do not need to check onERC721Received() when moving out of this contract, ie. no 'safeTransferFrom' // required // @dev The default implementation does not implement IERC721Receiver as 'safeTransferFrom' is not used. // @dev If IERC721Receiver is required, ensure proper re-entrancy protection is implemented. innerToken.transferFrom(address(this), _toAddress, _tokenId); }}
Installation[](#installation "Direct link to Installation")
-------------------------------------------------------------
To start using the `ONFT721` and `ONFT721Adapter` contracts, you can either create a new project via the LayerZero CLI or add the contract package to an existing project:
### New project[](#new-project "Direct link to New project")
If you're creating a new contract, LayerZero provides [`create-lz-oapp`](/v2/developers/evm/create-lz-oapp/start)
, an npx package that allows developers to create any omnichain application in **less than 4 minutes**. Get started by running the following from your command line and choose `ONFT721` when asked about a starting point. It will create both `ONFT721` and `ONFT721Adapter` contracts for your project.
npx create-lz-oapp@latest
### Existing project[](#existing-project "Direct link to Existing project")
To use ONFT in your existing project, install the [**@layerzerolabs/onft-evm**](https://www.npmjs.com/package/@layerzerolabs/onft-evm)
package. This library provides both `ONFT721` (burn-and-mint) and `ONFT721Adapter` (lock-and-mint) variants.
* npm
* yarn
* pnpm
* forge
npm install @layerzerolabs/onft-evm
yarn add @layerzerolabs/onft-evm
pnpm add @layerzerolabs/onft-evm
forge init
forge install https://github.com/LayerZero-Labs/devtools
forge install https://github.com/LayerZero-Labs/layerzero-v2
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0
Then add to your `foundry.toml` under `[profile.default]`:
[profile.default]src = "src"out = "out"libs = ["lib"]remappings = [ '@layerzerolabs/onft-evm/=lib/devtools/packages/onft-evm/', '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',]# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/erc721)
and V4 contracts. Specify your desired version in your project's package.json:
"resolutions": { "@openzeppelin/contracts": "^5.0.1",}
Constructing an ONFT Contract[](#constructing-an-onft-contract "Direct link to Constructing an ONFT Contract")
----------------------------------------------------------------------------------------------------------------
To create an ONFT, you should decide which implementation is appropriate for your use case:
1. Use `ONFT721` when you're creating a new NFT collection that will exist on multiple chains.
2. Use `ONFT721Adapter` when you need to make an existing NFT collection cross-chain compatible.
### ONFT721 Implementation[](#onft721-implementation "Direct link to ONFT721 Implementation")
Deploy an **ONFT** that inherits from `ONFT721`, which combines `ERC721` with the cross-chain functionality needed for omnichain transfers. The contract automatically handles token burning on the source chain and minting on the destination chain.
You can pass in your chosen contract name, symbol, the LayerZero Endpoint address, and the contract's delegate (owner or governance address). This contract becomes the "canonical" NFT on every chain.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { ONFT721 } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721.sol";contract MyONFT721 is ONFT721 { constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {}}
### ONFT721Adapter Implementation[](#onft721adapter-implementation "Direct link to ONFT721Adapter Implementation")
Deploy an **ONFT Adapter** that references your existing NFT contract address.
The `ONFT721Adapter` constructor takes an additional parameter `_token`, which is the address of the existing `ERC721` token that you want to make cross-chain compatible.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { ONFT721Adapter } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721Adapter.sol";contract MyONFT721Adapter is ONFT721Adapter { constructor( address _token, address _lzEndpoint, address _delegate ) ONFT721Adapter(_token, _lzEndpoint, _delegate) {}}
Warning
There can only be one ONFT Adapter used for a specific `ERC721` token, and it should be deployed on the chain where the original `ERC721` token is located. On all the other chains where you want to use the ONFT, you only need an `ONFT721` contract.
Deployment Workflow[](#deployment-workflow "Direct link to Deployment Workflow")
----------------------------------------------------------------------------------
The deployment process for ONFT contracts involves several steps, which we'll cover in detail:
1. **Deploy the ONFT** or ONFT Adapter contracts to all the chains you want to connect.
2. **Configure peer relationships** between contracts on different chains.
3. **Set security parameters** including Decentralized Validator Networks (DVNs).
4. **Configure message execution options**.
### 1\. Deploy ONFT Contracts[](#1-deploy-onft-contracts "Direct link to 1. Deploy ONFT Contracts")
First, deploy your ONFT contracts to all the chains you want to connect:
For new NFT collections:
* Deploy `MyONFT721` on all chains.
For existing NFT collections:
* Deploy `MyONFT721Adapter` on the chain where the original NFT exists.
* Deploy `MyONFT721` on all other chains you want to connect.
### 2\. Configure Security Parameters[](#2-configure-security-parameters "Direct link to 2. Configure Security Parameters")
Set the DVN configuration, including block confirmations, security thresholds, executor settings, and messaging libraries:
EndpointV2.setSendLibrary(aONFT, bEid, newLib)EndpointV2.setReceiveLibrary(aONFT, bEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(aONFT, bEid, lib, gracePeriod)EndpointV2.setConfig(aONFT, sendLibrary, sendConfig)EndpointV2.setConfig(aONFT, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)
These configurations are stored in the `EndpointV2` contract and control how messages are verified and executed. If you don't set custom configurations, the system will use default configurations set by LayerZero Labs.
**We strongly recommend reviewing these settings carefully and configuring your security stack according to your needs and preferences**.
You can find example scripts to make these calls in [Security and Executor Configuration](/v2/developers/evm/configuration/dvn-executor-config)
.
### 3\. Configure Peer Relationships[](#3-configure-peer-relationships "Direct link to 3. Configure Peer Relationships")
After deployment, you need to call `setPeer` on each contract to establish trust between ONFT contracts on different chains.
Set peers by calling `setPeer(dstEid, addressToBytes32(remoteONFT))` on every chain. This whitelists each destination as the trusted contract to receive your message.
uint32 aEid = 1; // Example endpoint id for Chain Auint32 bEid = 2; // Example endpoint id for Chain BMyONFT721 aONFT; // Contract deployed on Chain AMyONFT721 bONFT; // Contract deployed on Chain B// Call on both sides for each pathway// On chain AaONFT.setPeer(bEid, addressToBytes32(address(bONFT)));// On chain BbONFT.setPeer(aEid, addressToBytes32(address(aONFT)));
The actual endpoint ids will vary per chain, see [Supported Chains](/v2/deployments/deployed-contracts)
for endpoint id reference.
### 4\. Configure Message Execution Options[](#4-configure-message-execution-options "Direct link to 4. Configure Message Execution Options")
_\[Optional but recommended\]_
ONFT inherits `OAppOptionsType3` from the `OApp` standard. This means you can define:
1. **enforcedOptions**: A contract-wide default that every `send` must abide by (e.g. minimum gas for `lzReceive`, or a maximum message size).
2. **extraOptions**: A call-specific set of execution settings or advanced features, such as adding a “composed” message on the remote side.
// Recommended gas setting for ONFT transfersEnforcedOptionParam[] memory aEnforcedOptions = new EnforcedOptionParam[](1);// Force 65k gas on the remote (chain B) when bridging from chain AaEnforcedOptions[0] = EnforcedOptionParam({ eid: bEid, // Remote chain id (chain B) msgType: SEND, options: OptionsBuilder.newOptions().addExecutorLzReceiveOption(100_000, 0) // Gas limit, msg.value});aONFT.setEnforcedOptions(aEnforcedOptions);
This ensures every user who calls `myONFT.send(...)` must pay at least `100_000` gas on the remote chain for the bridging operation. This is useful for ensuring there's enough gas on the destination chain to execute the bridging operation and to receive the bridged tokens.
`enforcedOptions` should only be set for `msgType: SEND`, to make sure there's enough gas on the destination chain to execute the bridging operation and to receive the bridged tokens.
See [Message Execution Options](/v2/developers/evm/configuration/options)
for more details.
Using ONFT Contracts[](#using-onft-contracts "Direct link to Using ONFT Contracts")
-------------------------------------------------------------------------------------
### Estimating Gas Fees[](#estimating-gas-fees "Direct link to Estimating Gas Fees")
Before calling `send`, you'll typically want to estimate the fee using `quoteSend`.
Similar to OFT, you can call `quoteSend(...)` to get an estimate of how much `msg.value` you need to pass when bridging an NFT cross-chain. This function takes in the same parameters as `send` but does not actually initiate the transfer. Instead, it queries the Endpoint for an estimated cost in `nativeFee`.
Arguments of the estimate function:
1. `SendParam` _(struct)_: which parameters should be used for the `send` operation?
struct SendParam { uint32 dstEid; // Destination LayerZero EndpointV2 ID. bytes32 to; // Recipient address. uint256 tokenId; bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes onftCmd; // The ONFT command to be executed, unused in default ONFT implementations.}
2. `payInLzToken` _(bool)_: which token (native or LZ token) will be used to pay for the transaction? `true` for LZ token and `false` for native token.
This lets us construct the `quoteSend` function:
// @notice Provides a quote for the send() operation.// @param _sendParam The parameters for the send() operation.// @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.// @return msgFee The calculated LayerZero messaging fee from the send() operation.function quoteSend( SendParam calldata _sendParam, bool _payInLzToken) external view virtual returns (MessagingFee memory msgFee) { (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam); return _quote(_sendParam.dstEid, message, options, _payInLzToken);}
We now have everything we need to be able to send the NFT cross-chain:
* `SendParam` struct with all the parameters needed to send the NFT cross-chain
* `quoteSend` function to estimate the fee before sending the NFT cross-chain
* `refundAddress` parameter to specify the address to refund if the transaction fails on the source chain (default is the sender's address)
Let's send some NFTs across the chains!
### Sending NFTs Across Chains[](#sending-nfts-across-chains "Direct link to Sending NFTs Across Chains")
To transfer an NFT to another chain, users call the `send` function with appropriate parameters:
function send( SendParam calldata _sendParam, // Parameters for the send() operation. MessagingFee calldata _fee, // The calculated LayerZero messaging fee from the send() operation. address _refundAddress // The address to refund if the transaction fails on the source chain.) external payable virtual returns (MessagingReceipt memory msgReceipt) { _debit(msg.sender, _sendParam.tokenId, _sendParam.dstEid); // Debit the sender's balance. (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam); // @dev Sends the message to the LayerZero Endpoint, returning the MessagingReceipt. msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); emit ONFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, _sendParam.tokenId);}
You can override the `_debit` function with any additional logic you want to execute before the message is sent via the protocol, for example, taking custom fees.
### Example Client Code[](#example-client-code "Direct link to Example Client Code")
Here's how the `send` function can be called, as a Hardhat task for an ONFT Adapter contract:
* Hardhat Task
import {task} from 'hardhat/config';import { Options, addressToBytes32 } from '@layerzerolabs/lz-v2-utilities'import {BigNumberish, BytesLike} from 'ethers';interface SendParam { dstEid: BigNumberish // Destination LayerZero EndpointV2 ID. to: BytesLike // Recipient address. tokenId: BigNumberish // Token ID of the NFT to send. extraOptions: BytesLike // Additional options supplied by the caller to be used in the LayerZero message. composeMsg: BytesLike // The composed message for the send() operation. onftCmd: BytesLike // The ONFT command to be executed, unused in default ONFT implementations.}task('send-nft', 'Sends an NFT from chain A to chain B using MyONFTAdapter') .addParam('adapter', 'Address of MyONFTAdapter contract on source chain') .addParam('dstEndpointId', 'Destination chain endpoint ID') .addParam('recipient', 'Recipient on the destination chain') .addParam('tokenId', 'Token ID to send') .setAction(async (taskArgs, { ethers, deployments }) => { const { adapter, dstEndpointId, recipient, tokenId } = taskArgs const [signer] = await ethers.getSigners() const adapterDeployment = await deployments.get('MyONFT721Adapter') // Get adapter contract instance const adapterContract = new ethers.Contract(adapterDeployment.address, adapterDeployment.abi, signer) // Get the underlying ERC721 token address const tokenAddress = await adapterContract.token() const erc721Contract = await ethers.getContractAt('IERC721', tokenAddress) // Check and set approval for specific token ID const approved = await erc721Contract.getApproved(tokenId) if (approved.toLowerCase() !== adapterDeployment.address.toLowerCase()) { const approveTx = await erc721Contract.approve(adapterDeployment.address, tokenId) await approveTx.wait() // Grant approval for specific token ID } // Build the parameters const sendParam: SendParam = { dstEid: dstEndpointId, to: addressToBytes32(recipient), // convert to bytes32 tokenId: tokenId, extraOptions: '0x', // If you want to pass custom options composeMsg: '0x', // If you want additional logic on the remote chain onftCmd: '0x', } // Get quote for the transfer const quotedFee = await adapterContract.quoteSend(sendParam, false) // Send the NFT, using the returned quoted fee in msg.value const tx = await adapterContract.send( sendParam, quotedFee, signer.address, { value: quotedFee.nativeFee } ) const receipt = await tx.wait() console.log('🎉 NFT sent! Transaction hash:', receipt.transactionHash) })
You can put this task in `sendNFT.ts` in the `tasks` directory and run the command below to send the NFT.
This assumes that you have already deployed the adapter contract on Sepolia (testnet) and are sending the NFT to a recipient on Polygon Amoy (testnet).
npx hardhat send-nft \ --adapter 0x05EBb5dBefE45451Da5aA367CA0c39E715E85c99 \ # ONFTAdapter address on Sepolia --dst-endpoint-id 40267 \ # Destination chain endpoint ID (Amoy) --recipient 0x777A711938F0E40d8dd8cB457aE0AB3596Bd476d \ # Recipient address on Amoy --token-id 7 \ # Token ID of the NFT you want to send --network sepolia-testnet # Network you're sending from
When you call `send`:
* **ONFT** will `_burn` in the source chain contract, `_mint` in the destination chain contract.
* **ONFT Adapter** will `transferFrom(...)` tokens into itself on the source chain (locking them), then `_mint` or `_unlock` on the destination.
### Receiving the NFT (`_lzReceive`)[](#receiving-the-nft-_lzreceive "Direct link to receiving-the-nft-_lzreceive")
A successful `send` call will be delivered to the destination chain, invoking the `_lzReceive` method during execution on that chain:
function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address /*_executor*/, // @dev unused in the default implementation. bytes calldata /*_extraData*/ // @dev unused in the default implementation.) internal virtual override { address toAddress = _message.sendTo().bytes32ToAddress(); uint256 tokenId = _message.tokenId(); // Mint / unlock the NFT to the recipient _credit(toAddress, tokenId, _origin.srcEid); // If there's a "composeMsg" for extra logic, handle it here... if (_message.isComposed()) { // ... } emit ONFTReceived(_guid, _origin.srcEid, toAddress, tokenId);}
You can see each step in [ONFT721Core.sol](https://github.com/LayerZero-Labs/devtools/blob/main/packages/onft-evm/contracts/onft721/ONFT721Core.sol)
.
Advanced Features[](#advanced-features "Direct link to Advanced Features")
----------------------------------------------------------------------------
### Composed Messages[](#composed-messages "Direct link to Composed Messages")
ONFT supports composed messages, allowing you to execute additional logic on the destination chain as part of the NFT transfer. When the `composeMsg` parameter is not empty, after the NFT is minted on the destination chain, the composed message will be executed in a separate transaction.
For advanced use cases, you can leverage this feature to:
* Trigger additional actions when an NFT arrives
* Integrate with other protocols on the destination chain
* Implement cross-chain NFT marketplace functionality
### ONFT721Enumerable[](#onft721enumerable "Direct link to ONFT721Enumerable")
For collections that need enumeration capabilities, LayerZero provides an `ONFT721Enumerable` contract that extends `ONFT721` with the [ERC721Enumerable](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721#ERC721Enumerable)
functionality:
abstract contract ONFT721Enumerable is ONFT721Core, ERC721Enumerable { // Implementation details...}
This is useful for applications that need to enumerate or track all tokens within the collection.
Example: Complete End-to-End Deployment Flow[](#example-complete-end-to-end-deployment-flow "Direct link to Example: Complete End-to-End Deployment Flow")
------------------------------------------------------------------------------------------------------------------------------------------------------------
Here's a complete example showing how to deploy and configure an ONFT system with an existing NFT collection on Ethereum and bridging to Polygon:
1. **Create a new OApp with CLI**
npx create-lz-oapp@latest
Choose `ONFT721` as the starting point.
2. **Configure OApp**
* Modify `layerzero.config.ts` to configure the OApp and add all the chains you want your ONFT to be available on.
* Add private key to `.env` file
* Modify `hardhat.config.ts` to add the networks you want to deploy to
3. **Deploy Contracts**:
Adapt the contracts to your needs and deploy them using Hardhat:
npx hardhat lz:deploy
You'll be able to choose which chains you want to deploy to.
4. **Configure Peers**:
Now that everything is deployed, it's time to wire all the contracts together.
The fastest way is to use the CLI:
npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
5. **Verify Setup**
Verify that everything was wired up correctly:
npx hardhat lz:oapp:peers:get --oapp-config layerzero.config.ts
Verify configurations:
npx hardhat lz:oapp:config:get:default # Outputs the default OApp confignpx hardhat lz:oapp:config:get # Outputs Custom OApp Config, Default OApp Config, and Active OApp Config. Each config contains Send & Receive Libraries, Send Uln & Executor Configs, and Receive Executor Configs
In the output of the config command above:
* **Custom OApp config**: what you customized in your OApp
* **Default OApp config**: the defaults that are applied if you don't customize anything
* **Active OApp config**: the config that is currently active (essentially, default + your applied customizations)
And you are now ready to send the NFT across all your configured chains! 🎉
Security Considerations[](#security-considerations "Direct link to Security Considerations")
----------------------------------------------------------------------------------------------
When deploying ONFT contracts, consider the following security aspects:
1. **Peer Configuration**: Only set trusted contract addresses as peers to prevent unauthorized minting.
2. **DVN Settings**: Use multiple DVNs in production to ensure message verification is robust.
3. **Gas Limits**: Set appropriate gas limits in `enforceOptions` to prevent out-of-gas errors.
4. **Ownership Controls**: Implement proper access controls for administrative functions.
5. **Timeouts and Recovery**: Understand how message timeouts work and prepare recovery procedures.
Next Steps[](#next-steps "Direct link to Next Steps")
-------------------------------------------------------
The ONFT standard provides a powerful way to create truly cross-chain NFT collections. By understanding the core concepts and following the deployment guidelines outlined in this document, you can build robust omnichain NFT applications that leverage LayerZero's secure messaging protocol.
For more information, explore these related resources:
* [OApp Contract Standard](/v2/developers/evm/oapp/overview)
* [Security and Executor Configuration](/v2/developers/evm/configuration/dvn-executor-config)
* [Message Execution Options](/v2/developers/evm/configuration/options)
* [LayerZero Endpoint Addresses](/v2/deployments/deployed-contracts)
**You’re ready to build omnichain NFTs!**
* [ONFT Standard Overview](#onft-standard-overview)
* [ONFT (Burn & Mint)](#onft-burn--mint)
* [ONFT Adapter (Lock & Mint)](#onft-adapter-lock--mint)
* [ONFT Standard Overview](#onft-standard-overview-1)
* [Installation](#installation)
* [New project](#new-project)
* [Existing project](#existing-project)
* [Constructing an ONFT Contract](#constructing-an-onft-contract)
* [ONFT721 Implementation](#onft721-implementation)
* [ONFT721Adapter Implementation](#onft721adapter-implementation)
* [Deployment Workflow](#deployment-workflow)
* [1\. Deploy ONFT Contracts](#1-deploy-onft-contracts)
* [2\. Configure Security Parameters](#2-configure-security-parameters)
* [3\. Configure Peer Relationships](#3-configure-peer-relationships)
* [4\. Configure Message Execution Options](#4-configure-message-execution-options)
* [Using ONFT Contracts](#using-onft-contracts)
* [Estimating Gas Fees](#estimating-gas-fees)
* [Sending NFTs Across Chains](#sending-nfts-across-chains)
* [Example Client Code](#example-client-code)
* [Receiving the NFT (`_lzReceive`)](#receiving-the-nft-_lzreceive)
* [Advanced Features](#advanced-features)
* [Composed Messages](#composed-messages)
* [ONFT721Enumerable](#onft721enumerable)
* [Example: Complete End-to-End Deployment Flow](#example-complete-end-to-end-deployment-flow)
* [Security Considerations](#security-considerations)
* [Next Steps](#next-steps)
---
# LayerZero V2 OFT Quickstart | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The Omnichain Fungible Token (OFT) Standard allows **fungible tokens** to be transferred across multiple blockchains without asset wrapping or middlechains.
This standard works by either debiting (`burn` / `lock`) tokens on the source chain, sending a message via LayerZero, and delivering a function call to credit (`mint` / `unlock`) the same number of tokens on the destination chain.
This creates a **unified supply** across all networks that the OFT supports.
#### OFT.sol[](#oftsol "Direct link to OFT.sol")
`_burn` the spender's amount on the source chain (Chain A), triggering a new token to `_mint` on the target chain (Chain B), via the paired OFT contract.
 
`OFT.sol` extends the base `OApp.sol`'s bridging logic and inherits `ERC20`, meaning your OFT contract address supports `IERC20` directly:
 
#### OFTAdapter.sol[](#oftadaptersol "Direct link to OFTAdapter.sol")
`ERC20.safeTransferFrom` the spender to the OFT Adapter contract, triggering a `_mint` of the same amount on the selected destination chain (Chain B) via the paired OFT Contract.
To unlock the tokens in the source chain's OFT Adapter, you will call `OFT.send` (Chain B), triggering the token `_burn`, and sending a message via the protocol to `ERC20.safeTransfer` out of the Adapter to the receiving address (Chain A).
 
`OFTAdapter.sol` supports ERC20 tokens, but itself does not inherit the ERC20 contract. Instead, you can call `OFTAdapter.token()` to see the connected ERC20 token.
 
Using this design pattern, LayerZero can **extend** any fungible token to interoperate with other chains. The most widely used of these standards is `OFT.sol`, an extension of the [OApp Contract Standard](/v2/developers/evm/oapp/overview)
and the [ERC20 Token Standard](https://docs.openzeppelin.com/contracts/5.x/erc20)
.
info
If you prefer reading the contract code, see the OFT contract in the LayerZero Devtools [**OFT Package**](https://github.com/LayerZero-Labs/devtools/blob/main/packages/oft-evm/contracts/OFT.sol)
.
Installation[](#installation "Direct link to Installation")
-------------------------------------------------------------
To start using the `OFT` and `OFTAdapter` contracts, you can install the [OFT package](https://www.npmjs.com/package/@layerzerolabs/oapp-evm)
to an existing project:
* npm
* yarn
* pnpm
* forge
npm install @layerzerolabs/oft-evm
yarn add @layerzerolabs/oft-evm
pnpm add @layerzerolabs/oft-evm
forge init
forge install https://github.com/LayerZero-Labs/devtools
forge install https://github.com/LayerZero-Labs/layerzero-v2
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0
Then add to your `foundry.toml` under `[profile.default]`:
[profile.default]src = "src"out = "out"libs = ["lib"]remappings = [ '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/', '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',]# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
info
LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable)
and V4 contracts. Specify your desired version in your project's `package.json`:
"resolutions": { "@openzeppelin/contracts": "^5.0.1",}
tip
LayerZero also provides [**create-lz-oapp**](/v2/developers/evm/create-lz-oapp/start)
, an npx package that allows developers to create any omnichain application in <4 minutes! Get started by running the following from your command line:
npx create-lz-oapp@latest
Constructing an OFT Contract[](#constructing-an-oft-contract "Direct link to Constructing an OFT Contract")
-------------------------------------------------------------------------------------------------------------
To create an OFT, deploy the OFT contract on every chain you want the token to exist on.
If your token already exists on the chain you want to connect, you can deploy the OFT Adapter contract to act as an intermediary lockbox for the token.
* OFT
* OFT Adapter
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";/// @notice OFT is an ERC-20 token that extends the OFTCore contract.contract MyOFT is OFT { constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}}
tip
Remember to add the ERC20 `_mint` method either in the constructor or as a protected `mint` function before deploying.
This contract contains everything necessary to launch an omnichain ERC20 and can be deployed immediately! It also can be highly customized if you wish to add extra functionality.
Under the hood, `OFT.sol` extends `ERC20.sol`, by inheriting `OFTCore.sol`. OFT also overrides `_debit` and `_credit` to use the ERC20 `_mint` and `_burn` methods:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";import { IOFT, OFTCore } from "./OFTCore.sol";/** * @title OFT Contract * @dev OFT is an ERC-20 token that extends the functionality of the OFTCore contract. */abstract contract OFT is OFTCore, ERC20 { /** * @dev Constructor for the OFT contract. * @param _name The name of the OFT. * @param _symbol The symbol of the OFT. * @param _lzEndpoint The LayerZero endpoint address. * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. */ constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) ERC20(_name, _symbol) OFTCore(decimals(), _lzEndpoint, _delegate) {} /** * @dev Retrieves the address of the underlying ERC20 implementation. * @return The address of the OFT token. * * @dev In the case of OFT, address(this) and erc20 are the same contract. */ function token() public view returns (address) { return address(this); } /** * @notice Indicates whether the OFT contract requires approval of the 'token()' to send. * @return requiresApproval Needs approval of the underlying token implementation. * * @dev In the case of OFT where the contract IS the token, approval is NOT required. */ function approvalRequired() external pure virtual returns (bool) { return false; } /** * @dev Burns tokens from the sender's specified balance. * @param _from The address to debit the tokens from. * @param _amountLD The amount of tokens to send in local decimals. * @param _minAmountLD The minimum amount to send in local decimals. * @param _dstEid The destination chain ID. * @return amountSentLD The amount sent in local decimals. * @return amountReceivedLD The amount received in local decimals on the remote. */ function _debit( address _from, uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); // @dev In NON-default OFT, amountSentLD could be 100, with a 10% fee, the amountReceivedLD amount is 90, // therefore amountSentLD CAN differ from amountReceivedLD. // @dev Default OFT burns on src. _burn(_from, amountSentLD); } /** * @dev Credits tokens to the specified address. * @param _to The address to credit the tokens to. * @param _amountLD The amount of tokens to credit in local decimals. * @dev _srcEid The source chain ID. * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals. */ function _credit( address _to, uint256 _amountLD, uint32 /*_srcEid*/ ) internal virtual override returns (uint256 amountReceivedLD) { if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0) // @dev Default OFT mints on dst. _mint(_to, _amountLD); // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD. return _amountLD; }}
This design allows `OFT.sol` to facilitate cross-chain token transfers while maintaining compatibility with the ERC20 token standard and extensions. Any ERC20 compatible token library can be used with LayerZero's OFT Standard.
By default, the OFT follows ERC20 convention and uses a value of `18` for decimals. To use a different value, you will need to override the `decimals()` function in your contract.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OFTAdapter } from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";/// @notice OFTAdapter uses a deployed ERC-20 token and safeERC20 to interact with the OFTCore contract.contract MyOFTAdapter is OFTAdapter { constructor( address _token, address _lzEndpoint, address _owner ) OFTAdapter(_token, _lzEndpoint, _owner) Ownable(_owner) {}}
danger
**There can only be one OFT Adapter used in an OFT deployment.** Multiple OFT Adapters break omnichain unified liquidity by effectively creating token pools. If you create OFT Adapters on multiple chains, you have no way to guarantee finality for token transfers due to the fact that the source chain has no knowledge of the destination pool's supply (or lack of supply). This can create race conditions where if a sent amount exceeds the available supply on the destination chain, those sent tokens will be permanently lost.
This contract contains everything necessary to launch an omnichain ERC20 and can be deployed immediately! It also can be highly customized if you wish to add extra functionality.
Under the hood, `OFTAdapter.sol` uses the `SafeERC20.sol` library to handle transferring tokens to and from the Adapter contract by overriding OFTCore's `_debit` and `_credit` methods:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;import { IERC20Metadata, IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import { IOFT, OFTCore } from "./OFTCore.sol";/** * @title OFTAdapter Contract * @dev OFTAdapter is a contract that adapts an ERC-20 token to the OFT functionality. * * @dev For existing ERC20 tokens, this can be used to convert the token to crosschain compatibility. * @dev WARNING: ONLY 1 of these should exist for a given global mesh, * unless you make a NON-default implementation of OFT and needs to be done very carefully. * @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out. * IF the 'innerToken' applies something like a transfer fee, the default will NOT work... * a pre/post balance check will need to be done to calculate the amountSentLD/amountReceivedLD. */abstract contract OFTAdapter is OFTCore { using SafeERC20 for IERC20; IERC20 internal immutable innerToken; /** * @dev Constructor for the OFTAdapter contract. * @param _token The address of the ERC-20 token to be adapted. * @param _lzEndpoint The LayerZero endpoint address. * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. */ constructor( address _token, address _lzEndpoint, address _delegate ) OFTCore(IERC20Metadata(_token).decimals(), _lzEndpoint, _delegate) { innerToken = IERC20(_token); } /** * @dev Retrieves the address of the underlying ERC20 implementation. * @return The address of the adapted ERC-20 token. * * @dev In the case of OFTAdapter, address(this) and erc20 are NOT the same contract. */ function token() public view returns (address) { return address(innerToken); } /** * @notice Indicates whether the OFT contract requires approval of the 'token()' to send. * @return requiresApproval Needs approval of the underlying token implementation. * * @dev In the case of default OFTAdapter, approval is required. * @dev In non-default OFTAdapter contracts with something like mint and burn privileges, it would NOT need approval. */ function approvalRequired() external pure virtual returns (bool) { return true; } /** * @dev Locks tokens from the sender's specified balance in this contract. * @param _from The address to debit from. * @param _amountLD The amount of tokens to send in local decimals. * @param _minAmountLD The minimum amount to send in local decimals. * @param _dstEid The destination chain ID. * @return amountSentLD The amount sent in local decimals. * @return amountReceivedLD The amount received in local decimals on the remote. * * @dev msg.sender will need to approve this _amountLD of tokens to be locked inside of the contract. * @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out. * IF the 'innerToken' applies something like a transfer fee, the default will NOT work... * a pre/post balance check will need to be done to calculate the amountReceivedLD. */ function _debit( address _from, uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); // @dev Lock tokens by moving them into this contract from the caller. innerToken.safeTransferFrom(_from, address(this), amountSentLD); } /** * @dev Credits tokens to the specified address. * @param _to The address to credit the tokens to. * @param _amountLD The amount of tokens to credit in local decimals. * @dev _srcEid The source chain ID. * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals. * * @dev WARNING: The default OFTAdapter implementation assumes LOSSLESS transfers, ie. 1 token in, 1 token out. * IF the 'innerToken' applies something like a transfer fee, the default will NOT work... * a pre/post balance check will need to be done to calculate the amountReceivedLD. */ function _credit( address _to, uint256 _amountLD, uint32 /*_srcEid*/ ) internal virtual override returns (uint256 amountReceivedLD) { // @dev Unlock the tokens and transfer to the recipient. innerToken.safeTransfer(_to, _amountLD); // @dev In the case of NON-default OFTAdapter, the amountLD MIGHT not be == amountReceivedLD. return _amountLD; }}
Deployment Workflow[](#deployment-workflow "Direct link to Deployment Workflow")
----------------------------------------------------------------------------------
1. Deploy the `OFT` to all the chains you want to connect.
2. Since `OFT` extends `OApp`, call `OFT.setPeer` to whitelist each destination contract on every destination chain.
// The real endpoint ids will vary per chain, and can be found under "Supported Chains"uint32 aEid = 1;uint32 bEid = 2;MyOFT aOFT;MyOFT bOFT;function addressToBytes32(address _addr) public pure returns (bytes32) { return bytes32(uint256(uint160(_addr)));}// Call on both sides per pathwayaOFT.setPeer(bEid, addressToBytes32(address(bOFT)));bOFT.setPeer(aEid, addressToBytes32(address(aOFT)));
3. Set the DVN configuration, including optional settings such as block confirmations, security threshold, the Executor, max message size, and send/receive libraries.
EndpointV2.setSendLibrary(aOFT, bEid, newLib)EndpointV2.setReceiveLibrary(aOFT, bEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(aOFT, bEid, lib, gracePeriod)EndpointV2.setConfig(aOFT, sendLibrary, sendConfig)EndpointV2.setConfig(aOFT, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)
These custom configurations will be stored on-chain as part of `EndpointV2`, along with your respective `SendLibrary` and `ReceiveLibrary`:
// LayerZero V2 MessageLibManager.sol (part of EndpointV2.sol)mapping(address sender => mapping(uint32 dstEid => address lib)) internal sendLibrary;mapping(address receiver => mapping(uint32 srcEid => address lib)) internal receiveLibrary;mapping(address receiver => mapping(uint32 srcEid => Timeout)) public receiveLibraryTimeout;// LayerZero V2 SendLibBase.sol (part of SendUln302.sol)mapping(address oapp => mapping(uint32 eid => ExecutorConfig)) public executorConfigs;// LayerZero V2 UlnBase.sol (both in SendUln302.sol and ReceiveUln302.sol)mapping(address oapp => mapping(uint32 eid => UlnConfig)) internal ulnConfigs;// LayerZero V2 EndpointV2.solmapping(address oapp => address delegate) public delegates;
You can find example scripts to make these calls under [Security and Executor Configuration](/v2/developers/evm/configuration/dvn-executor-config)
.
danger
These configurations control the verification mechanisms of messages sent between your OApps. You should review the above settings carefully.
If no configuration is set, the configuration will fallback to the default configurations set by LayerZero Labs. For example:
/// @notice The Send Library is the Oapp specified library that will be used to send the message to the destination/// endpoint. If the Oapp does not specify a Send Library, the default Send Library will be used./// @dev If the Oapp does not have a selected Send Library, this function will resolve to the default library/// configured by LayerZero/// @return lib address of the Send Library/// @param _sender The address of the Oapp that is sending the message/// @param _dstEid The destination endpoint idfunction getSendLibrary(address _sender, uint32 _dstEid) public view returns (address lib) { lib = sendLibrary[_sender][_dstEid]; if (lib == DEFAULT_LIB) { lib = defaultSendLibrary[_dstEid]; if (lib == address(0x0)) revert Errors.LZ_DefaultSendLibUnavailable(); }}
4. (**Recommended**) The OFT inherits `OAppOptionsType3`, meaning you can enforce specific gas settings when users call `aOFT.send`.
EnforcedOptionParam[] memory aEnforcedOptions = new EnforcedOptionParam[](1);// Send gas for lzReceive (A -> B).aEnforcedOptions[0] = EnforcedOptionParam({eid: bEid, msgType: SEND, options: OptionsBuilder.newOptions().addExecutorLzReceiveOption(50000, 0)}); // gas limit, msg.valueaOFT.setEnforcedOptions(aEnforcedOptions);
5. Required only for `OFTAdapter`: Approve your `OFTAdapter` as a spender of your `ERC20` token for the token amount you want to transfer by calling `ERC20.approve`. This comes standard in the [`ERC20` interface](https://eips.ethereum.org/EIPS/eip-20#methods)
, and is required when using an intermediary contract to spend token amounts on behalf of the caller. See more details about each setting below.
### OFTCore[](#oftcore "Direct link to OFTCore")
Most of the LayerZero cross-chain messaging logic can be found within `OFTCore.sol`. This contract implements the `OApp` related functions like `_lzSend`, `_lzReceive`, and `sendCompose`, while also defining the core OFT interface that every OFT variant should adhere to.
`OFT.sol` overrides the [`_debit`](#adding-send-logic)
and [`_credit`](#adding-receive-logic)
methods found in `OFTCore.sol` to use the ERC20 internal `_burn` and `_mint` methods respectively during cross-chain token transfer.
Other OFT variants will override `_debit` and `_credit` differently depending on implementation (e.g., [`OFTAdapter.sol`](/v2/developers/evm/oft/quickstart)
overrides `_debit` and `_credit` to use `ERC20.safeTransferFrom` to lock / unlock tokens from the OFT Adapter contract itself).
You can also override these methods to add additional functionality to the base transfer logic, which will be explored below.
### Token Supply Cap[](#token-supply-cap "Direct link to Token Supply Cap")
When transferring tokens across different blockchain VMs, each chain may have a different level of decimal precision for the smallest unit of a token.
While EVM chains support `uint256` for token balances, many non-EVM environments use `uint64`. Because of this, the default OFT Standard has a max token supply `(2^64 - 1)/(10^6)`, or `18,446,744,073,709.551615`.
info
If your token's supply needs to exceed this limit, you'll need to override the **shared decimals value**.
#### Optional: Overriding `sharedDecimals`[](#optional-overriding-shareddecimals "Direct link to optional-overriding-shareddecimals")
This shared decimal precision is essentially the maximum number of decimal places that can be reliably represented and handled across different blockchain VMs when transferring tokens.
By default, an OFT has 6 `sharedDecimals`, which is optimal for most ERC20 use cases that use `18` decimals.
// @dev Sets an implicit cap on the amount of tokens, over uint64.max() will need some sort of outbound cap / totalSupply cap// Lowest common decimal denominator between chains.// Defaults to 6 decimal places to provide up to 18,446,744,073,709.551615 units (max uint64).// For tokens exceeding this totalSupply(), they will need to override the sharedDecimals function with something smaller.// ie. 4 sharedDecimals would be 1,844,674,407,370,955.1615function sharedDecimals() public view virtual returns (uint8) { return 6;}
To modify this default, simply override the `sharedDecimals` function to return another value.
caution
Shared decimals also control how token transfer precision is calculated.
### Token Transfer Precision[](#token-transfer-precision "Direct link to Token Transfer Precision")
The OFT Standard also handles differences in decimal precision before every cross-chain transfer by "**cleaning**" the amount from any decimal precision that cannot be represented in the shared system.
The OFT Standard defines these small token transfer amounts as "**dust**".
#### Example[](#example "Direct link to Example")
Vanilla OFTs use a local decimal value of `18` (the norm for ERC20 tokens), and a shared decimal value of `6`.
decimalConversionRate = 10^(localDecimals − sharedDecimals) = 10^(18−6) = 10^12
This means the conversion rate is `10^12`, which indicates the smallest unit that can be transferred is `10^-12` in terms of the token's local decimals.
For example, if you `send` a value of `1234567890123456789` (a token amount with 18 decimals), the OFT Standard will:
1. Divides by `decimalConversionRate`:
1234567890123456789 / 10^12 = 1234567.890123456789 = 1234567
tip
Remember that solidity performs integer arithmetic. This means when you divide two integers, the result is also an integer with the fractional part discarded.
2. Multiplies by `decimalConversionRate`:
1234567 * 10^12 = 1234567000000000000
This process removes the last 12 digits from the original amount, effectively "**cleaning**" the amount from any "**dust**" that cannot be represented in a system with 6 decimal places.
/** * @dev Internal function to remove dust from the given local decimal amount. * @param _amountLD The amount in local decimals. * @return amountLD The amount after removing dust. * * @dev Prevents the loss of dust when moving amounts between chains with different decimals. * @dev eg. uint(123) with a conversion rate of 100 becomes uint(100). */function _removeDust(uint256 _amountLD) internal view virtual returns (uint256 amountLD) { return (_amountLD / decimalConversionRate) * decimalConversionRate;}
tip
In summary, this adjustment via the **`_removeDust`** function prevents OFT transfers from a potential loss of value due to rounding errors between different VMs, and should be called after determining the actual transfer amount (e.g., after deducting fees).
### Adding Send Logic[](#adding-send-logic "Direct link to Adding Send Logic")
When calling the `send` function, `_debit` is invoked, triggering the OFT's internal ERC20 `_burn` method to be invoked.
/** * @dev Executes the send operation. * @param _sendParam The parameters for the send operation. * @param _fee The calculated fee for the send() operation. * - nativeFee: The native fee. * - lzTokenFee: The lzToken fee. * @param _refundAddress The address to receive any excess funds. * @return msgReceipt The receipt for the send operation. * @return oftReceipt The OFT receipt information. * * @dev MessagingReceipt: LayerZero msg receipt * - guid: The unique identifier for the sent message. * - nonce: The nonce of the sent message. * - fee: The LayerZero fee incurred for the message. */function send( SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { // @dev Applies the token transfers regarding this send() operation. // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender. // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance. (uint256 amountSentLD, uint256 amountReceivedLD) = _debit( msg.sender, _sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid ); // @dev Builds the options and OFT message to quote in the endpoint. (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD); // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt. msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); // @dev Formulate the OFT receipt. oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD); emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);}
You can override the `_debit` function with any additional logic you want to execute before the message is sent via the protocol, for example, taking custom fees.
All of the previous functions use the `_debitView` function to handle how many tokens should be debited on the source chain, versus credited on the destination.
This function can be overridden, allowing your OFT to implement custom fees by changing the `amountSentLD` and `amountReceivedLD` amounts:
/** * @dev Internal function to mock the amount mutation from a OFT debit() operation. * @param _amountLD The amount to send in local decimals. * @param _minAmountLD The minimum amount to send in local decimals. * @dev _dstEid The destination endpoint ID. * @return amountSentLD The amount sent, in local decimals. * @return amountReceivedLD The amount to be received on the remote chain, in local decimals. * * @dev This is where things like fees would be calculated and deducted from the amount to be received on the remote. */function _debitView( uint256 _amountLD, uint256 _minAmountLD, uint32 /*_dstEid*/) internal view virtual returns (uint256 amountSentLD, uint256 amountReceivedLD) { // @dev Remove the dust so nothing is lost on the conversion between chains with different decimals for the token. amountSentLD = _removeDust(_amountLD); // @dev The amount to send is the same as amount received in the default implementation. amountReceivedLD = amountSentLD; // @dev Check for slippage. if (amountReceivedLD < _minAmountLD) { revert SlippageExceeded(amountReceivedLD, _minAmountLD); }}
caution
The highlighted line above demonstrates how the OFT is safe from overflow because it reduces the size of `_amountLD` to a value that fits within the expected range of the destination chain's precision by calling `_removeDust`.
This method looks at the desired amount of tokens to transfer and only allows the sender to send values that meet the allowed decimal precision.
If you add fees to `_debitView`, make sure you implement the fee before calling `_removeDust`, so that the OFT can still maintain the correct level of decimal precision.
Review [**Token Transfer Precision**](#token-transfer-precision)
to learn more about removing dust values.
### Adding Receive Logic[](#adding-receive-logic "Direct link to Adding Receive Logic")
Similar to `send`, you can add custom logic when receiving an ERC20 token transfer on the destination chain by overriding the `_credit` function.
/** * @dev Credits tokens to the specified address. * @param _to The address to credit the tokens to. * @param _amountLD The amount of tokens to credit in local decimals. * @dev _srcEid The source chain ID. * @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals. */function _credit( address _to, uint256 _amountLD, uint32 /*_srcEid*/) internal virtual override returns (uint256 amountReceivedLD) { if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0) // @dev Default OFT mints on dst. _mint(_to, _amountLD); // @dev In the case of NON-default OFT, the _amountLD MIGHT not be == amountReceivedLD. return _amountLD;}
### Setting Delegates[](#setting-delegates "Direct link to Setting Delegates")
In an OFT, a delegate can be assigned to implement custom configurations on behalf of the contract owner. This delegate gains the ability to handle various critical tasks such as setting configurations and skipping inbound packets for the OFT.
By default, the contract owner is set as the delegate. The `setDelegate` function allows for changing this, but you should generally keep the contract owner as delegate.
function setDelegate(address _delegate) public onlyOwner { endpoint.setDelegate(_delegate);}
For instructions on how to implement custom configurations after setting your delegate, refer to the [OApp Configuration](/v2/developers/evm/configuration/dvn-executor-config)
.
### Security and Governance[](#security-and-governance "Direct link to Security and Governance")
Given the impact associated with deployment, configuration, and debugging functions, OFT owners may want to add additional security measures in place to call core contract functions instead of `onlyOwner`, such as:
* **Governance Controls**: Implementing a governance mechanism where decisions to clear messages are voted upon by stakeholders.
* **Multisig Deployment**: Deploying with a multisig wallet, preventing arbitrary actions by any one team member.
* **Timelocks**: Using a timelock to delay the execution of the clear function, giving stakeholders time to react if the function is called inappropriately.
info
Any normal access control library can be added to the base OFT Standard. The only relevant difference is that these access controls will need to coordinate across multiple contract implementations, since a deployed OFT typically consists of an OFT contract on every connected chain.
Deployment & Usage[](#deployment--usage "Direct link to Deployment & Usage")
------------------------------------------------------------------------------
You can now deploy your contracts and get one step closer to moving fungible tokens between chains.
### Setting Trusted Peers[](#setting-trusted-peers "Direct link to Setting Trusted Peers")
You should only connect your OFT deployments together after setting your DVN and Executor configuration (see the [Configuration Guide](/v2/developers/evm/configuration/dvn-executor-config)
or [`create-lz-oapp` CLI tool](/v2/developers/evm/create-lz-oapp/configuring-pathways)
).
Once you've finished configuring your OFT, you can connect your OFT deployment to different chains by calling `setPeer`.
The function takes 2 arguments: `_eid`, the endpoint ID for the destination chain that the other OFT contract lives on, and `_peer`, the destination OFT's contract address in `bytes32` format.
// @dev must-have configurations for standard OAppsfunction setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner { peers[_eid] = _peer; // Array of peer addresses by destination. emit PeerSet(_eid, _peer); // Event emitted each time a peer is set.}
caution
`setPeer` opens your OFT to start receiving messages from the messaging channel, meaning you should configure any application settings you intend on changing prior to calling `setPeer`.
danger
OFTs need `setPeer` to be called correctly on both contracts to send messages. The peer address uses `bytes32` for handling non-EVM destination chains.
If the peer has been set to an incorrect destination address, your messages will not be delivered and handled properly. If not resolved, users can burn source funds without a corresponding mint on destination. You can confirm the peer address is the expected destination OFT address by using the `isPeer` function.
The [LayerZero Endpoint](/v2/concepts/protocol/layerzero-endpoint)
will use this peer as the destination address when sending the cross-chain message:
// @dev the endpoint send method called by _lzSendendpoint.send{ value: messageValue }( MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0), _refundAddress);
The destination Endpoint will check if the `_receiver` matches the OFT contract's expected peer before delivering the message on the destination chain:
function _initializable( Origin calldata _origin, address _receiver, uint64 _lazyInboundNonce) internal view returns (bool) { return _lazyInboundNonce > 0 || // allowInitializePath already checked ILayerZeroReceiver(_receiver).allowInitializePath(_origin);}
To see if an address is the trusted peer you expect for a destination, you can read the `peers` mapping directly:
/** * @dev Internal function to check if peer is considered 'trusted' by the OApp. * @param _eid The endpoint ID to check. * @param _peer The peer to check. * @return Whether the peer passed is considered 'trusted' by the OApp. * * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source. */function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) { return peers[_eid] == _peer;}
This can be useful for confirming whether `setPeer` has been called correctly and as expected.
### Message Execution Options[](#message-execution-options "Direct link to Message Execution Options")
`_options` are a generated bytes array with specific instructions for the [DVNs](/v2/concepts/modular-security/security-stack-dvns)
and [Executor](/v2/concepts/permissionless-execution/executors)
to use when handling the authentication and execution of received messages.
You can find how to generate all the available `_options` in [Message Execution Options](/v2/developers/evm/configuration/options)
, but for this tutorial we'll focus on how options work with OFT.
* `ExecutorLzReceiveOption`: instructions for how much gas the Executor should use when calling `lzReceive` on the destination Endpoint.
For example, usually to send a vanilla OFT to a destination chain you will need `60000` wei in native gas on destination. The options will look like the following:
_options = 0x0003010011010000000000000000000000000000ea60;
tip
`ExecutorLzReceiveOption` specifies a quote paid in advance on the source chain by the `msg.sender` for the equivalent amount of native gas to be used on the destination chain. If the actual cost to execute the message is less than what was set in `_options`, there is no default way to refund the sender the difference. Application developers need to thoroughly profile and test gas amounts to ensure consumed gas amounts are correct and not excessive.
#### Setting Enforced Options[](#setting-enforced-options "Direct link to Setting Enforced Options")
Once you determine ideal message `_options`, you will want to make sure users adhere to it. In the case of OFT, you mostly want to make sure the gas is enough for transferring the ERC20 token, plus any additional logic.
A typical OFT's `lzReceive` call will use `60000` gas on most EVM chains, so you can enforce this option to require callers to pay a `60000` gas limit in the source chain transaction to prevent out of gas issues:
_options = 0x0003010011010000000000000000000000000000ea60;
tip
You can use the [**`create-lz-oapp`**](/v2/developers/evm/create-lz-oapp/configuring-pathways#adding-enforcedoptions)
npx package to set `enforcedOptions` in a human readable format by defining your settings in your `layerzero.config.ts`.
The `setEnforcedOptions` function allows the contract owner to specify mandatory execution options, making sure that the application behaves as expected when users interact with it.
// inherited from `oapp/libs/OAppOptionsType3.sol`:/** * @dev Sets the enforced options for specific endpoint and message type combinations. * @param _enforcedOptions An array of EnforcedOptionParam structures specifying enforced options. * * @dev Only the owner/admin of the OApp can call this function. * @dev Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc. * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the msgType. * eg. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you dont want to pay * if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose(). */function setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) public virtual onlyOwner { _setEnforcedOptions(_enforcedOptions);}function _setEnforcedOptions(EnforcedOptionParam[] memory _enforcedOptions) internal virtual { for (uint256 i = 0; i < _enforcedOptions.length; i++) { // @dev Enforced options are only available for optionType 3, as type 1 and 2 dont support combining. _assertOptionsType3(_enforcedOptions[i].options); enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options; } emit EnforcedOptionSet(_enforcedOptions);}
To use `setEnforcedOptions`, we only need to pass one parameter:
* `EnforcedOptionParam[]`: a struct specifying the execution options per message type and destination chain.
struct EnforcedOptionParam { uint32 eid; // destination endpoint id uint16 msgType; // the message type bytes options; // the execution option bytes array}
The OFT Standard only has handling for 2 message types:
// @dev execution types to handle different enforcedOptionsuint16 internal constant SEND = 1; // a standard token transfer via send()uint16 internal constant SEND_AND_CALL = 2; // a composed token transfer via send()
Pass these values in when specifying the `msgType` for your `_options`.
For best practice, generate this array off-chain and pass it as a parameter when configuring your OFT:
EnforcedOptionParam[] memory aEnforcedOptions = new EnforcedOptionParam[](1);// Send gas for lzReceive (A -> B).aEnforcedOptions[0] = EnforcedOptionParam({eid: bEid, msgType: SEND, options: OptionsBuilder.newOptions().addExecutorLzReceiveOption(65000, 0)});// Call the setEnforcedOptions functionaOFT.setEnforcedOptions(aEnforcedOptions);
caution
When setting `enforcedOptions`, try not to unintentionally pass a duplicate `_options` argument to `extraOptions`. Passing identical `_options` in both `enforcedOptions` and `extraOptions` will cause the protocol to charge the caller twice on the source chain, because LayerZero interprets duplicate `_options` as two separate requests for gas.
#### Setting Extra Options[](#setting-extra-options "Direct link to Setting Extra Options")
Any `_options` passed in the `send` call itself should be considered `_extraOptions`.
`_extraOptions` can specify additional handling within the same message type. These `_options` will then be combined with `enforcedOption` if set.
If not needed in your application, you should pass an empty bytes array `0x`.
if (enforced.length > 0) { // combine extra options with enforced options // remove the first 2 bytes (TYPE_3) of extra options // should pack executor options last in enforced options (assuming most extra options are executor options only) // to save gas on grouping by worker id in message library uint16 extraOptionsType = uint16(bytes2(_extraOptions[0:2])); uint16 enforcedOptionsType = (uint16(uint8(enforced[0])) << 8) + uint8(enforced[1]); if (extraOptionsType != enforcedOptionsType) revert InvalidOptions(); options = bytes.concat(enforced, _extraOptions[2:]);} else { // no enforced options, use extra options directly options = _extraOptions;}
caution
As outlined above, decide on whether you need an application wide option via `enforcedOptions` or a call specific option using `extraOptions`. Be specific in what `_options` you use for both parameters, as your transactions will reflect the exact settings you implement.
### Estimating Gas Fees[](#estimating-gas-fees "Direct link to Estimating Gas Fees")
Now let's get an estimate of how much gas a transfer will cost to be sent and received.
To do this we can call the `quoteSend` function to return an estimate from the Endpoint contract to use as a recommended `msg.value`.
Arguments of the estimate function:
1. `SendParam`: what parameters should be used for the send call?
/** * @dev Struct representing token parameters for the OFT send() operation. */ struct SendParam { uint32 dstEid; // Destination endpoint ID. bytes32 to; // Recipient address. uint256 amountLD; // Amount to send in local decimals. uint256 minAmountLD; // Minimum amount to send in local decimals. bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. }
note
Here is a link to further explain [Extra Message Options](#setting-extra-options)
that would be used besides [`enforcedOptions`](#setting-enforced-options)
.
2. `_payInLzToken`: what token will be used to pay for the transaction?
struct MessagingFee { uint nativeFee; // gas amount in native gas token uint lzTokenFee; // gas amount in ZRO token}
/** * @notice Provides a quote for the send() operation. * @param _sendParam The parameters for the send() operation. * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token. * @return msgFee The calculated LayerZero messaging fee from the send() operation. * * @dev MessagingFee: LayerZero msg fee * - nativeFee: The native fee. * - lzTokenFee: The lzToken fee. */ function quoteSend( SendParam calldata _sendParam, bool _payInLzToken ) external view virtual returns (MessagingFee memory msgFee) { // @dev mock the amount to receive, this is the same operation used in the send(). // The quote is as similar as possible to the actual send() operation. (, uint256 amountReceivedLD) = _debitView(_sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid); // @dev Builds the options and OFT message to quote in the endpoint. (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD); // @dev Calculates the LayerZero fee for the send() operation. return _quote(_sendParam.dstEid, message, options, _payInLzToken); }
### Calling `send`[](#calling-send "Direct link to calling-send")
Since the `send` logic has already been defined, we'll instead view how the function should be called.
* Hardhat Task
* Foundry Script
import {task} from 'hardhat/config';import {getNetworkNameForEid, types} from '@layerzerolabs/devtools-evm-hardhat';import {EndpointId} from '@layerzerolabs/lz-definitions';import {addressToBytes32} from '@layerzerolabs/lz-v2-utilities';import {Options} from '@layerzerolabs/lz-v2-utilities';import {BigNumberish, BytesLike} from 'ethers';interface Args { amount: string; to: string; toEid: EndpointId;}interface SendParam { dstEid: EndpointId; // Destination endpoint ID, represented as a number. to: BytesLike; // Recipient address, represented as bytes. amountLD: BigNumberish; // Amount to send in local decimals. minAmountLD: BigNumberish; // Minimum amount to send in local decimals. extraOptions: BytesLike; // Additional options supplied by the caller to be used in the LayerZero message. composeMsg: BytesLike; // The composed message for the send() operation. oftCmd: BytesLike; // The OFT command to be executed, unused in default OFT implementations.}// send tokens from a contract on one network to anothertask('lz:oft:send', 'Sends tokens from either OFT or OFTAdapter') .addParam('to', 'contract address on network B', undefined, types.string) .addParam('toEid', 'destination endpoint ID', undefined, types.eid) .addParam('amount', 'amount to transfer in token decimals', undefined, types.string) .setAction(async (taskArgs: Args, {ethers, deployments}) => { const toAddress = taskArgs.to; const eidB = taskArgs.toEid; // Get the contract factories const oftDeployment = await deployments.get('MyOFT'); const [signer] = await ethers.getSigners(); // Create contract instances const oftContract = new ethers.Contract(oftDeployment.address, oftDeployment.abi, signer); const decimals = await oftContract.decimals(); const amount = ethers.utils.parseUnits(taskArgs.amount, decimals); let options = Options.newOptions().addExecutorLzReceiveOption(65000, 0).toBytes(); // Now you can interact with the correct contract const oft = oftContract; const sendParam: SendParam = { dstEid: eidB, to: addressToBytes32(toAddress), amountLD: amount, minAmountLD: amount, extraOptions: options, composeMsg: ethers.utils.arrayify('0x'), // Assuming no composed message oftCmd: ethers.utils.arrayify('0x'), // Assuming no OFT command is needed }; // Get the quote for the send operation const feeQuote = await oft.quoteSend(sendParam, false); const nativeFee = feeQuote.nativeFee; console.log( `sending ${taskArgs.amount} token(s) to network ${getNetworkNameForEid(eidB)} (${eidB})`, ); const ERC20Factory = await ethers.getContractFactory('ERC20'); const innerTokenAddress = await oft.token(); // // If the token address !== address(this), then this is an OFT Adapter // if (innerTokenAddress !== oft.address) { // // If the contract is OFT Adapter, get decimals from the inner token // const innerToken = ERC20Factory.attach(innerTokenAddress); // // Approve the amount to be spent by the oft contract // await innerToken.approve(oftDeployment.address, amount); // } const r = await oft.send(sendParam, {nativeFee: nativeFee, lzTokenFee: 0}, signer.address, { value: nativeFee, }); console.log(`Send tx initiated. See: https://layerzeroscan.com/tx/${r.hash}`); });
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.13;import {Script, console} from "forge-std/Script.sol";import { IOAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol";import { SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";import { MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { MyOFT } from "../contracts/MyOFT.sol";contract SendOFT is Script { using OptionsBuilder for bytes; /** * @dev Converts an address to bytes32. * @param _addr The address to convert. * @return The bytes32 representation of the address. */ function addressToBytes32(address _addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(_addr))); } function run() public { // Fetching environment variables address oftAddress = vm.envAddress("OFT_ADDRESS"); address toAddress = vm.envAddress("TO_ADDRESS"); uint256 _tokensToSend = vm.envUint("TOKENS_TO_SEND"); // Fetch the private key from environment variable uint256 privateKey = vm.envUint("PRIVATE_KEY"); // Start broadcasting with the private key vm.startBroadcast(privateKey); MyOFT sourceOFT = MyOFT(oftAddress); bytes memory _extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(65000, 0); SendParam memory sendParam = SendParam( 30111, // You can also make this dynamic if needed addressToBytes32(toAddress), _tokensToSend, _tokensToSend * 9 / 10, _extraOptions, "", "" ); MessagingFee memory fee = sourceOFT.quoteSend(sendParam, false); console.log("Fee amount: ", fee.nativeFee); sourceOFT.send{value: fee.nativeFee}(sendParam, fee, msg.sender); // Stop broadcasting vm.stopBroadcast(); }}
Below you can find the send method itself.
// @dev executes a cross-chain OFT swap via layerZero Endpoint function send( SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { // @dev Applies the token transfers regarding this send() operation. // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender. // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance. (uint256 amountSentLD, uint256 amountReceivedLD) = _debit( msg.sender, _sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid ); // @dev Builds the options and OFT message to quote in the endpoint. (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD); // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt. msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); // @dev Formulate the OFT receipt. oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD); emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD);}
To do this, we only need to pass `send` a few inputs:
1. `SendParam`: what parameters should be used for the send call?
struct SendParam { uint32 dstEid; // Destination endpoint ID. bytes32 to; // Recipient address. uint256 amountLD; // Amount to send in local decimals. uint256 minAmountLD; // Minimum amount to send in local decimals. bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. }
info
`extraOptions` allow a caller to define an additional amount of `gas_limit` and `msg.value` to deliver to the destination chain along with the required amount set by the contract owner (`enforcedOptions`).
2. `_fee`: what token will be used to pay for the transaction?
struct MessagingFee { uint nativeFee; // gas amount in native gas token uint lzTokenFee; // gas amount in ZRO token}
3. `_refundAddress`: If the transaction fails on the source chain, where should funds be refunded?
#### Optional: `_composedMsg`[](#optional-_composedmsg "Direct link to optional-_composedmsg")
When sending an OFT, you can also include an optional `_composedMsg` parameter in the transaction to execute additional logic on the destination chain as part of the token transfer.
// @dev executes an omnichain OFT swap via layerZero Endpointif (_composeMsg.length > 0) { // @dev Remote chains will want to know the composed function caller. // ALSO, the presence of a composeFrom msg.sender inside of the bytes array indicates the payload should // be composed. ie. this allows users to compose with an empty payload, vs it must be length > 0 _composeMsg = abi.encodePacked(OFTMsgCodec.addressToBytes32(msg.sender), _composeMsg);}msgReceipt = _sendInternal( _send, combineOptions(_send.dstEid, SEND_AND_CALL, _extraOptions), _msgFee, // message fee _refundAddress, // refund address for failed source tx _composeMsg // composed message);
On the destination chain, the `_lzReceive` function will first process the token transfer, crediting the recipient's account with the specified amount, and then check if `_message.isComposed()`.
if (_message.isComposed()) { bytes memory composeMsg = OFTComposeMsgCodec.encode( _origin.nonce, // nonce of the origin transaction _origin.srcEid, // source endpoint id of the transaction amountLDReceive, // the token amount in local decimals to credit _message.composeMsg() // the composed message ); // @dev Stores the lzCompose payload that will be executed in a separate tx. // standardizes functionality for delivering/executing arbitrary contract invocation on some non evm chains. // @dev Composed toAddress is the same as the receiver of the oft/tokens endpoint.deliverComposedMessage(toAddress, _guid, composeMsg);}
If the message is composed, the contract retrieves and re-encodes the additional composed message information, then delivers the message to the endpoint, which will execute the additional logic as a separate transaction.
#### Optional: `_oftCmd`[](#optional-_oftcmd "Direct link to optional-_oftcmd")
The `_oftCmd` is a `bytes` array that can be used like a function selector on the destination chain that you can check for within `_lzReceive` similar to `lzCompose` for custom OFT implementations.
### `_lzReceive` tokens[](#_lzreceive-tokens "Direct link to _lzreceive-tokens")
A successful `send` call will be delivered to the destination chain, invoking the provided `_lzReceive` method during execution:
function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address /*_executor*/, bytes calldata /*_extraData*/) internal virtual override { // @dev sendTo is always a bytes32 as the remote chain initiating the call doesnt know remote chain address size address toAddress = _message.sendTo().bytes32ToAddress(); uint256 amountToCreditLD = _toLD(_message.amountSD()); uint256 amountReceivedLD = _credit(toAddress, amountToCreditLD, _origin.srcEid); if (_message.isComposed()) { bytes memory composeMsg = OFTComposeMsgCodec.encode( _origin.nonce, _origin.srcEid, amountReceivedLD, _message.composeMsg() ); // @dev Stores the lzCompose payload that will be executed in a separate tx. // standardizes functionality for executing arbitrary contract invocation on some non-evm chains. // @dev Composed toAddress is the same as the receiver of the oft/tokens // TODO need to document the index / understand how to use it properly endpoint.sendCompose(toAddress, _guid, 0, composeMsg); } emit OFTReceived(_guid, toAddress, amountToCreditLD, amountReceivedLD);}
#### `_credit`:[](#_credit "Direct link to _credit")
When receiving the message on your destination contract, `_credit` is invoked, triggering the final steps to mint an ERC20 token on the destination to the specified address.
function _credit( address _to, uint256 _amountToCreditLD, uint32 /*_srcEid*/) internal virtual override returns (uint256 amountReceivedLD) { _mint(_to, _amountToCreditLD); return _amountToCreditLD;}
* [Installation](#installation)
* [Constructing an OFT Contract](#constructing-an-oft-contract)
* [Deployment Workflow](#deployment-workflow)
* [OFTCore](#oftcore)
* [Token Supply Cap](#token-supply-cap)
* [Token Transfer Precision](#token-transfer-precision)
* [Adding Send Logic](#adding-send-logic)
* [Adding Receive Logic](#adding-receive-logic)
* [Setting Delegates](#setting-delegates)
* [Security and Governance](#security-and-governance)
* [Deployment & Usage](#deployment--usage)
* [Setting Trusted Peers](#setting-trusted-peers)
* [Message Execution Options](#message-execution-options)
* [Estimating Gas Fees](#estimating-gas-fees)
* [Calling `send`](#calling-send)
* [`_lzReceive` tokens](#_lzreceive-tokens)
---
# Omnichain Composability | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
Cross-chain composability has long been a goal for developers building advanced, interconnected decentralized applications.
LayerZero V2 introduces **horizontal composability** — a concept that empowers developers to spread out cross-chain calls into multiple, discrete steps.
Prerequisites[](#prerequisites "Direct link to Prerequisites")
----------------------------------------------------------------
Before diving into LayerZero V2 Horizontal Composability, it's essential to have a foundational understanding of the following concepts:
* **[Solidity Interfaces](https://blog.paulmcaviney.ca/solidity-interfaces)
**: Knowledge of defining and implementing interfaces in Solidity.
* **[Solidity Interface Composability](https://dev.to/shlok2740/interfaces-in-solidity-26m3#:~:text=Interfaces%20allow%20for%20composability%20between,any%20contract%20that%20implements%20it.)
**: Grasping how interfaces facilitate composability between contracts.
Having familiarity with these topics will enable a smoother comprehension of the concepts discussed.
Workflow[](#workflow "Direct link to Workflow")
-------------------------------------------------
LayerZero V2 supports both **Vertical and Horizontal Composability** within cross-chain calls.
### What is Vertical Composability?[](#what-is-vertical-composability "Direct link to What is Vertical Composability?")
**Vertical Composability** is the traditional model of composability in blockchain applications, where multiple function calls from different contracts are stacked within a single transaction.
// Example of vertical composability with atomicityfunction _lzReceive( Origin calldata /*_origin*/, bytes32 /*_guid*/, bytes calldata /*_message*/, address /*_executor*/, bytes calldata /*_extraData*/) internal override { contractA.functionA(); contractB.functionB(); contractC.functionC(); // If any of the above calls fail, the entire transaction reverts}
All function calls in the stack execute atomically. This means that either all operations succeed, or the entire transaction reverts if any single operation fails.
caution
Vertical composability can present potential **Atomicity Issues** in cross-chain interactions:
* If an operation on one contract fails, it can produce unintended reversions or inconsistencies across the entire stack. This limits the ability to have instant finality guarantees when receiving cross-chain messages.
In cross-chain contracts, you should minimize the impact of potential message failure by performing only one action per message.
### What is Horizontal Composability?[](#what-is-horizontal-composability "Direct link to What is Horizontal Composability?")
**Horizontal Composability** is an implementation in **LayerZero V2** to address the limitations of vertical composability in cross-chain interactions.
Unlike vertical composability, which relies on a single, linear stack of function calls, horizontal composability allows for multiple, sequential calls across different chains within a single overarching operation.
This facilitates the orchestration of complex, multi-step interactions across multiple chains without being constrained by the depth or complexity of a single call stack.
### How Horizontal Composability Works[](#how-horizontal-composability-works "Direct link to How Horizontal Composability Works")
LayerZero's horizontal composability leverages composed messages that are treated as separate, containerized message packets. These packets are processed independently, allowing for more flexible and controlled interactions across chains.
**Workflow Overview:**
1. **Sending Application Logic:** The sender application uses the `OApp._lzSend()` function to dispatch a cross-chain message.
2. **Receiving Application Logic:** A destination application receives the message from `EndpointV2.lzReceive()`, does some state change, and then calls `EndpointV2.sendCompose()` to send a new message to the target composer.
info
Crucially, either the `sender` or `receiver` should construct an additional message directed at a `composer`, which will handle subsequent operations in a new method, `EndpointV2.lzCompose()`.
This dual-message approach ensures that both the immediate and follow-up actions are clearly defined and routed appropriately.
3. **Composer Application Logic:** A composer application receives the composed message in `lzCompose()` and does a state change to follow up on the first state changes created in `lzReceive()`.
This workflow creates a way for delivering some critical state change information in separate steps, reducing the complexity of the call stack and enabling non-critical reverts on the destination chain.
### Horizontally Composing Supported Contracts[](#horizontally-composing-supported-contracts "Direct link to Horizontally Composing Supported Contracts")
Implementing horizontal composability involves crafting composed messages to expand on existing cross-chain contract workflows. By default, both the `OFT` and `ONFT` standards support horizontally composed calls out of the box.
This allows `OFT` or `ONFT` token holders to send tokens cross-chain to a trusted `composer` contract on the destination, and trigger some action on behalf of the token holders (e.g., token swaps, token staking, etc).
For more advanced implementations, you can design complex `OApp` contracts that have other cross-chain `composer` implications.
Installation[](#installation "Direct link to Installation")
-------------------------------------------------------------
To create a `composer` contract, you can install the [OApp package](https://www.npmjs.com/package/@layerzerolabs/oapp-evm)
to an existing project:
* npm
* yarn
* pnpm
* forge
npm install @layerzerolabs/oapp-evm
yarn add @layerzerolabs/oapp-evm
pnpm add @layerzerolabs/oapp-evm
forge install https://github.com/LayerZero-Labs/devtools
forge install https://github.com/LayerZero-Labs/layerzero-v2
forge install OpenZeppelin/openzeppelin-contracts@v5.1.0
Then add to your `foundry.toml` under `[profile.default]`:
[profile.default]src = "src"out = "out"libs = ["lib"]remappings = [ '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',]# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
info
LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable)
and V4 contracts. Specify your desired version in your project's `package.json`:
"resolutions": { "@openzeppelin/contracts": "^5.0.1",}
Usage[](#usage "Direct link to Usage")
----------------------------------------
To implement a `composer` contract, simply inherit the `IOAppComposer.sol` interface from the `oapp-evm` package:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";/** * @title Composer * @notice Demonstrates the minimum `IOAppComposer` interface necessary to receive composed messages via LayerZero. * @dev Implements the `lzCompose` function to process incoming composed messages. */contract Composer is IOAppComposer { /** * @notice Address of the LayerZero Endpoint. */ address public immutable endpoint; /** * @notice Address of the OApp that is sending the composed message. */ address public immutable oApp; /** * @notice Constructs the contract and initializes state variables. * @dev Stores the LayerZero Endpoint and OApp addresses. * * @param _endpoint The address of the LayerZero Endpoint. * @param _oApp The address of the OApp that is sending composed messages. */ constructor(address _endpoint, address _oApp) { endpoint = _endpoint; oApp = _oApp; } /** * @notice Handles incoming composed messages from LayerZero. * @dev Ensures the message comes from the correct OApp and is sent through the authorized endpoint. * * @param _oApp The address of the OApp that is sending the composed message. */ function lzCompose( address _oApp, bytes32 /* _guid */, bytes calldata /* _message */, address /* _executor */, bytes calldata /* _extraData */ ) external payable override { // Ensure the composed message comes from the correct OApp. require(_oApp == oApp, "ComposedReceiver: Invalid OApp"); require(msg.sender == endpoint, "ComposedReceiver: Unauthorized sender"); // ... execute logic for handling composed messages }}
### Composed Message Execution Options[](#composed-message-execution-options "Direct link to Composed Message Execution Options")
Longer `composer` messages, which contain more bytes encoded instructions, increase the cost of calling `EndpointV2.lzReceive()`.
Typically, the reason for the gas increase can be found in the additional length being added to your cross-chain message, as well as the cost of invoking `EndpointV2.sendCompose()` inside your `OApp._lzReceive()` function.
Ensure that when calling `OFT.send()` and `ONFT.send()` or your own custom OApp, that you correctly estimate the cost of calling `endpoint.sendCompose()` and add the additional `LzReceiveOption` gas limit to your `SendParam.extraOptions` or OApp specific `options` argument:
// addExecutorLzReceiveOption(uint128 _gas, uint128 _value)Options.newOptions().addExecutorLzReceiveOption(50000, 0);
Besides the increase cost of `EndpointV2.lzReceive()`, you should also take into account the cost of your actual `composer.lzCompose()`. Similar to lzReceive(), you can specify the `gas limit` and `msg.value` the Executor should use when calling the `composer` contract:
// addExecutorLzComposeOption(uint16 _index, uint128 _gas, uint128 _value)Options.newOptions().addExecutorLzReceiveOption(50000, 0).addExecutorLzComposeOption(0, 30000, 0);
* **`_index`:** Identifies the specific composed call within a batch of composed messages. This allows for distinct execution settings for each call.
* **`_gas`:** Specifies the gas limit allocated for the composed call's execution on the destination chain. Gas requirements may vary across chains due to different opcode costs and gas mechanisms.
* **`_value`:** Determines the amount of native currency (e.g., ETH) to be sent alongside the composed call, facilitating payable functions or covering additional costs.
Review the existing documentation on [Message Execution Options](/v2/developers/evm/configuration/options)
to learn more.
caution
If not enough `gas limit` or `msg.value` is provided, the `EndpointV2.lzReceive()` will not execute, and will need to be manually retried either via the LayerZero Scan explorer, or manual contract call.
### Composing an OFT / ONFT[](#composing-an-oft--onft "Direct link to Composing an OFT / ONFT")
Both the `OFT` and `ONFT` support sending a composed message along with the cross-chain token transfers.
* OFT
* ONFT
// IOFT.sol/** * @dev Struct representing token parameters for the OFT send() operation. */struct SendParam { uint32 dstEid; // Destination endpoint ID. bytes32 to; // Composer address. uint256 amountLD; // Amount to send in local decimals. uint256 minAmountLD; // Minimum amount to send in local decimals. bytes extraOptions; // Compose options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.}
// IONFT.sol/** * @dev Struct representing token parameters for the ONFT send() operation. */struct SendParam { uint32 dstEid; // Destination LayerZero EndpointV2 ID. bytes32 to; // Composer address. uint256 tokenId; // The ERC721 tokenId for the send() operation. bytes extraOptions; // Compose options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes onftCmd; // The ONFT command to be executed, unused in default ONFT implementations.}
When calling `send()`, specify the `composer` as the to address, encode a `composeMsg` based on the composer's specification, and add a `ComposeExecutionOption` gas limit and/or msg.value depending on the composer's needs.
When creating the `composeMsg`, the OFT / ONFT will already encode specific parameters along with your message for use in the composer.
* OFT
* ONFT
Below is how the `OFTCore` contract encodes the `composeMsg` and sends it to the `composer`:
// OFTCore.sol/** * @dev The `OFTMsgCodec` provides a helper function to extract the `composeMsg` from * the overall message. This ensures that the `composeMsg` is properly formed and can * be processed by the composer. * * @notice The `composeMsg` includes both: * - The `msg.sender` on the source chain (as bytes32). * - The actual `composeMsg` intended for the composer. * * @notice The final encoded message structure is: * abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg); */using OFTMsgCodec for bytes;/** * @dev When sending a message, the `composeMsg` is encoded alongside standard parameters. */(message, hasCompose) = OFTMsgCodec.encode(_sendParam.to, _toSD(_amountLD), _sendParam.composeMsg());/** * @dev If the message is composed (i.e., it contains a `composeMsg`), * we extract it and send it to the composer. */if (_message.isComposed()) { /** * @dev The `composeMsg` sent to the composer includes: * - `_origin.nonce` (to track the originating transaction). * - `_origin.srcEid` (the source chain endpoint ID). * - The actual `composeMsg` extracted from `_message`. */ bytes memory composeMsg = ONFTComposeMsgCodec.encode(_origin.nonce, _origin.srcEid, _message.composeMsg()); /** * @dev Sends the composed message to the specified `toAddress` (the composer). * * @notice The `composeIndex` is always `0` because batching is not implemented. * - If batching is added, the index will need to be properly tracked. */ endpoint.sendCompose(toAddress, _guid, 0 /* the index of composed message */, composeMsg);}
Below is how the `ONFT721Core` contract encodes the `composeMsg` and sends it to the `composer`:
// ONFT721Core.sol/** * @dev The `ONFT721MsgCodec` provides a helper function to extract the `composeMsg` from * the overall message. This ensures that the `composeMsg` is properly formed and can * be processed by the composer. * * @notice The `composeMsg` includes both: * - The `msg.sender` on the source chain (as bytes32). * - The actual `composeMsg` intended for the composer. * * @notice The final encoded message structure is: * abi.encodePacked(_sendTo, _tokenId, addressToBytes32(msg.sender), _composeMsg) */using ONFT721MsgCodec for bytes;/** * @dev When sending a message, the `composeMsg` is encoded alongside standard parameters. */(message, hasCompose) = ONFT721MsgCodec.encode(_sendParam.to, _sendParam.tokenId, _sendParam.composeMsg());/** * @dev If the message is composed (i.e., it contains a `composeMsg`), * we extract it and send it to the composer. */if (_message.isComposed()) { /** * @dev The `composeMsg` sent to the composer includes: * - `_origin.nonce` (to track the originating transaction). * - `_origin.srcEid` (the source chain endpoint ID). * - The actual `composeMsg` extracted from `_message`. */ bytes memory composeMsg = ONFTComposeMsgCodec.encode(_origin.nonce, _origin.srcEid, _message.composeMsg()); /** * @dev Sends the composed message to the specified `toAddress` (the composer). * * @notice The `composeIndex` is always `0` because batching is not implemented. * - If batching is added, the index will need to be properly tracked. */ endpoint.sendCompose(toAddress, _guid, 0 /* the index of composed message */, composeMsg);}
This means that in your composer application, you can decode the `msg.sender` for specific checks, along with the other composer encodings.
For example, see the following `composer` example which mocks an ERC20 token swap after receiving from an OFT:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";/** * @title SwapMock Contract * @notice Mocks an ERC20 token swap in response to receiving an OFT message via LayerZero. * @dev This contract interacts with LayerZero's Omnichain Fungible Token (OFT) Standard, * processing incoming OFT messages (`lzCompose`) and executing a token swap action. */contract SwapMock is IOAppComposer { using SafeERC20 for IERC20; /// @notice The ERC20 token used for swaps. IERC20 public erc20; /// @notice Address of the LayerZero Endpoint. address public immutable endpoint; /// @notice Address of the OApp that is sending the composed message. address public immutable oApp; /** * @notice Emitted when a token swap is executed. * @dev This event logs the swap details, including the recipient, token, and amount swapped. * * @param user The address of the user who receives the swapped tokens. * @param tokenOut The address of the ERC20 token being swapped. * @param amount The amount of tokens swapped. */ event Swapped(address indexed user, address tokenOut, uint256 amount); /** * @notice Constructs the `SwapMock` contract. * @dev Initializes the contract by setting the ERC20 token, LayerZero endpoint, and OApp address. * * @param _erc20 The address of the ERC20 token that will be used in swaps. * @param _endpoint The LayerZero Endpoint address. * @param _oApp The address of the OApp that is sending the composed message. */ constructor(address _erc20, address _endpoint, address _oApp) { erc20 = IERC20(_erc20); endpoint = _endpoint; oApp = _oApp; } /** * @notice Handles incoming composed messages from LayerZero and executes a token swap. * @dev Decodes the `composeMsg` from `_message`, extracts relevant parameters, and transfers * tokens to the intended recipient. * * The `message` is structured in the sender's contract and includes: * - `_nonce`: A unique identifier for tracking the message. * - `_srcEid`: The source endpoint ID, identifying the originating chain. * - `_amountLD`: The amount of tokens in local decimals being transferred. * - `_composeFrom`: The address of the original sender (encoded as `bytes32`). * - `_composeMsg`: The payload containing the recipient address. * * @param _oApp The address of the originating OApp. * @param _message The encoded message containing the `composeMsg`. */ function lzCompose( address _oApp, bytes32 /*_guid*/, bytes calldata _message, address /*_executor*/, bytes calldata /*_extraData*/ ) external payable override { require(_oApp == oApp, "SwapMock: Invalid OApp"); require(msg.sender == endpoint, "SwapMock: Unauthorized sender"); // Decode the nonce (unique identifier for the transaction) uint64 _nonce = OFTComposeMsgCodec.nonce(_message); // Decode the source endpoint ID (originating chain) uint32 _srcEid = OFTComposeMsgCodec.srcEid(_message); // Decode the amount in local decimals being transferred uint256 _amountLD = OFTComposeMsgCodec.amountLD(_message); // Decode the `composeFrom` address (original sender) from bytes32 to address bytes32 _composeFromBytes = OFTComposeMsgCodec.composeFrom(_message); address _composeFrom = OFTComposeMsgCodec.bytes32ToAddress(_composeFromBytes); // Decode the actual `composeMsg` payload to extract the recipient address bytes memory _actualComposeMsg = OFTComposeMsgCodec.composeMsg(_message); address _receiver = abi.decode(_actualComposeMsg, (address)); // Execute the token swap by transferring `_amountLD` to `_receiver` erc20.safeTransfer(_receiver, _amountLD); // Emit an event for logging the swap details emit Swapped(_receiver, address(erc20), _amountLD); }}
### Composing an OApp[](#composing-an-oapp "Direct link to Composing an OApp")
1. **Source OApp:** Sends a cross-chain message via `_lzSend()` to a destination chain.
2. **Destination OApp:** Receives the cross-chain message via `_lzReceive()` and initiates composed calls using `EndpointV2.sendCompose()`:
/** * @dev Handles incoming LayerZero messages and sends a composed message using `endpoint.sendCompose()`. * @notice This function processes received packets and relays them to a composed receiver. * * @param _guid A globally unique identifier for tracking the packet. * @param payload The encoded message payload. */function _lzReceive( Origin calldata /*_origin*/, bytes32 _guid, bytes calldata payload, address /*_executor*/, bytes calldata /*_extraData*/) internal override { /** * @dev Decode the payload based on the expected format from the sender application. * The structure of `payload` depends entirely on how the sender encoded it. * In this case, we assume the sender encoded a string message and a composer address. * If the sender encodes different types or a different order, this decoding must be updated accordingly. */ (string memory _message, address _composedAddress) = abi.decode(payload, (string, address)); // Store received data in the destination OApp data = _message; // Send a composed message to the composed receiver using the same GUID endpoint.sendCompose(_composedAddress, _guid, 0, payload);}
3. **Composer:** Contracts that implement business logic to handle incoming composed messages via `EndpointV2.lzCompose()`.
* [Prerequisites](#prerequisites)
* [Workflow](#workflow)
* [What is Vertical Composability?](#what-is-vertical-composability)
* [What is Horizontal Composability?](#what-is-horizontal-composability)
* [How Horizontal Composability Works](#how-horizontal-composability-works)
* [Horizontally Composing Supported Contracts](#horizontally-composing-supported-contracts)
* [Installation](#installation)
* [Usage](#usage)
* [Composed Message Execution Options](#composed-message-execution-options)
* [Composing an OFT / ONFT](#composing-an-oft--onft)
* [Composing an OApp](#composing-an-oapp)
---
# Design Patterns & Extensions | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
Each design pattern functions as a distinct omnichain building block, capable of being used independently or in conjunction with others.
| Message Pattern | Description |
| --- | --- |
| [ABA](#aba) | a nested send call from Chain A to Chain B that sends back again to the source chain (`A` -> `B` -> `A`) |
| [Batch Send](#batch-send) | a single send that calls multiple destination chains |
| [Composed](#composed) | a message that transfers from a source to destination chain and calls an external contract (`A` -> `B1` -> `B2`) |
| [Composed ABA](#composed-aba) | transfers data from a source to destination, calls an external contract, and then calls back to the source (`A` -> `B1` -> `B2` -> `A`) |
| [Message Ordering](#message-ordering) | enforce the ordered delivery of messages on execution post verification |
| [Rate Limit](#rate-limiting) | rate limit the number of `send` calls for a given amount of `messages` or `tokens` transferred |
This modularity allows for the seamless integration and combination of patterns to suit specific developer requirements.
ABA[](#aba "Direct link to ABA")
----------------------------------
**AB** messaging refers to a one way send call from a source to destination blockchain.
 
In the [Getting Started Guide](/v2/developers/evm/getting-started)
, we use this design pattern to send a string from Chain A to store on Chain B (`A` -> `B`).
The **ABA** message pattern extends this simple logic by nesting another `_lzSend` call within the destination contract's `_lzReceive` function. You can think of this as a ping-pong style call, pinging a destination chain and ponging back to the original source (`A` -> `B` -> `A`).
 
This is particularly useful when actions on one blockchain depend on the state or confirmation of another, such as:
* **Conditional Execution of Contracts**: A smart contract on chain A will only execute a function if a condition on chain B is met. It sends a message to chain B to check the condition and then receives a confirmation back before proceeding.
* **Omnichain Data Feeds**: A contract on Chain A can fetch data from the destination (Chain B) to complete a process back on the source.
* **Cross-chain Authentication**: A user or contract might authenticate on chain A, ping chain B to process something that requires this authentication, and then receive back a token or confirmation that the process was successful.
### Code Example[](#code-example "Direct link to Code Example")
This pattern demonstrates **vertical composability**, where the nested message contains both handling for the message receipt, as well as additional logic for a subsequent call that must all happen within one atomic transaction.
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { OApp, MessagingFee, Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { OAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";/** * @title ABA contract for demonstrating LayerZero messaging between blockchains. * @notice THIS IS AN EXAMPLE CONTRACT. DO NOT USE THIS CODE IN PRODUCTION. * @dev This contract showcases a PingPong style call (A -> B -> A) using LayerZero's OApp Standard. */contract ABA is OApp, OAppOptionsType3 { /// @notice Last received message data. string public data = "Nothing received yet"; /// @notice Message types that are used to identify the various OApp operations. /// @dev These values are used in things like combineOptions() in OAppOptionsType3. uint16 public constant SEND = 1; uint16 public constant SEND_ABA = 2; /// @notice Emitted when a return message is successfully sent (B -> A). event ReturnMessageSent(string message, uint32 dstEid); /// @notice Emitted when a message is received from another chain. event MessageReceived(string message, uint32 senderEid, bytes32 sender); /// @notice Emitted when a message is sent to another chain (A -> B). event MessageSent(string message, uint32 dstEid); /// @dev Revert with this error when an invalid message type is used. error InvalidMsgType(); /** * @dev Constructs a new PingPong contract instance. * @param _endpoint The LayerZero endpoint for this contract to interact with. * @param _owner The owner address that will be set as the owner of the contract. */ constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(msg.sender) {} function encodeMessage(string memory _message, uint16 _msgType, bytes memory _extraReturnOptions) public pure returns (bytes memory) { // Get the length of _extraReturnOptions uint256 extraOptionsLength = _extraReturnOptions.length; // Encode the entire message, prepend and append the length of extraReturnOptions return abi.encode(_message, _msgType, extraOptionsLength, _extraReturnOptions, extraOptionsLength); } /** * @notice Returns the estimated messaging fee for a given message. * @param _dstEid Destination endpoint ID where the message will be sent. * @param _msgType The type of message being sent. * @param _message The message content. * @param _extraSendOptions Gas options for receiving the send call (A -> B). * @param _extraReturnOptions Additional gas options for the return call (B -> A). * @param _payInLzToken Boolean flag indicating whether to pay in LZ token. * @return fee The estimated messaging fee. */ function quote( uint32 _dstEid, uint16 _msgType, string memory _message, bytes calldata _extraSendOptions, bytes calldata _extraReturnOptions, bool _payInLzToken ) public view returns (MessagingFee memory fee) { bytes memory payload = encodeMessage(_message, _msgType, _extraReturnOptions); bytes memory options = combineOptions(_dstEid, _msgType, _extraSendOptions); fee = _quote(_dstEid, payload, options, _payInLzToken); } /** * @notice Sends a message to a specified destination chain. * @param _dstEid Destination endpoint ID for the message. * @param _msgType The type of message to send. * @param _message The message content. * @param _extraSendOptions Options for sending the message, such as gas settings. * @param _extraReturnOptions Additional options for the return message. */ function send( uint32 _dstEid, uint16 _msgType, string memory _message, bytes calldata _extraSendOptions, // gas settings for A -> B bytes calldata _extraReturnOptions // gas settings for B -> A ) external payable { // Encodes the message before invoking _lzSend. require(bytes(_message).length <= 32, "String exceeds 32 bytes"); if (_msgType != SEND && _msgType != SEND_ABA) { revert InvalidMsgType(); } bytes memory options = combineOptions(_dstEid, _msgType, _extraSendOptions); _lzSend( _dstEid, encodeMessage(_message, _msgType, _extraReturnOptions), options, // Fee in native gas and ZRO token. MessagingFee(msg.value, 0), // Refund address in case of failed source message. payable(msg.sender) ); emit MessageSent(_message, _dstEid); } function decodeMessage(bytes calldata encodedMessage) public pure returns (string memory message, uint16 msgType, uint256 extraOptionsStart, uint256 extraOptionsLength) { extraOptionsStart = 256; // Starting offset after _message, _msgType, and extraOptionsLength string memory _message; uint16 _msgType; // Decode the first part of the message (_message, _msgType, extraOptionsLength) = abi.decode(encodedMessage, (string, uint16, uint256)); return (_message, _msgType, extraOptionsStart, extraOptionsLength); } /** * @notice Internal function to handle receiving messages from another chain. * @dev Decodes and processes the received message based on its type. * @param _origin Data about the origin of the received message. * @param message The received message content. */ function _lzReceive( Origin calldata _origin, bytes32 /*guid*/, bytes calldata message, address, // Executor address as specified by the OApp. bytes calldata // Any extra data or options to trigger on receipt. ) internal override { (string memory _data, uint16 _msgType, uint256 extraOptionsStart, uint256 extraOptionsLength) = decodeMessage(message); data = _data; if (_msgType == SEND_ABA) { string memory _newMessage = "Chain B says goodbye!"; bytes memory _options = combineOptions(_origin.srcEid, SEND, message[extraOptionsStart:extraOptionsStart + extraOptionsLength]); _lzSend( _origin.srcEid, abi.encode(_newMessage, SEND), // Future additions should make the data types static so that it is easier to find the array locations. _options, // Fee in native gas and ZRO token. MessagingFee(msg.value, 0), // Refund address in case of failed send call. // @dev Since the Executor makes the return call, this contract is the refund address. payable(address(this)) ); emit ReturnMessageSent(_newMessage, _origin.srcEid); } emit MessageReceived(data, _origin.srcEid, _origin.sender); } receive() external payable {}}
info
This message pattern can also be considered an ABC type call (`A` -> `B` -> `C`), as the nested `_lzSend` can send to any new destination chain.
Batch Send[](#batch-send "Direct link to Batch Send")
-------------------------------------------------------
The **Batch Send** design pattern, where a single transaction can initiate multiple `_lzSend` calls to various destination chains, is highly efficient for operations that need to propagate an action across several blockchains simultaneously.
 
This can significantly reduce the operational overhead associated with performing the same action multiple times on different blockchains. It streamlines omnichain interactions by bundling them into a single transaction, making processes more efficient and easier to manage for example:
* **Simultaneous Omnichain Updates**: When a system needs to update the same information across multiple chains (such as a change in governance parameters or updating oracle data), Batch Send can propagate the updates in one go.
* **DeFi Strategies**: For DeFi protocols that operate on multiple chains, rebalancing liquidity pools or executing yield farming strategies can be done in batch to maintain parity across ecosystems.
* **Aggregated Omnichain Data Posting**: Oracles or data providers that supply information to smart contracts on multiple chains can use Batch Send to post data such as price feeds, event outcomes, or other updates in a single transaction.
### Code Example[](#code-example-1 "Direct link to Code Example")
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { OApp, MessagingFee, Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { OAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";/** * @title BatchSend contract for demonstrating multiple outbound cross-chain calls using LayerZero. * @notice THIS IS AN EXAMPLE CONTRACT. DO NOT USE THIS CODE IN PRODUCTION. * @dev This contract showcases how to send multiple cross-chain calls with one source function call using LayerZero's OApp Standard. */contract BatchSend is OApp, OAppOptionsType3 { /// @notice Last received message data. string public data = "Nothing received yet"; /// @notice Message types that are used to identify the various OApp operations. /// @dev These values are used in things like combineOptions() in OAppOptionsType3 (enforcedOptions). uint16 public constant SEND = 1; /// @notice Emitted when a message is received from another chain. event MessageReceived(string message, uint32 senderEid, bytes32 sender); /// @notice Emitted when a message is sent to another chain (A -> B). event MessageSent(string message, uint32 dstEid); /// @dev Revert with this error when an invalid message type is used. error InvalidMsgType(); /** * @dev Constructs a new BatchSend contract instance. * @param _endpoint The LayerZero endpoint for this contract to interact with. * @param _owner The owner address that will be set as the owner of the contract. */ constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(msg.sender) {} function _payNative(uint256 _nativeFee) internal override returns (uint256 nativeFee) { if (msg.value < _nativeFee) revert NotEnoughNative(msg.value); return _nativeFee; } /** * @notice Returns the estimated messaging fee for a given message. * @param _dstEids Destination endpoint ID array where the message will be batch sent. * @param _msgType The type of message being sent. * @param _message The message content. * @param _extraSendOptions Extra gas options for receiving the send call (A -> B). * Will be summed with enforcedOptions, even if no enforcedOptions are set. * @param _payInLzToken Boolean flag indicating whether to pay in LZ token. * @return totalFee The estimated messaging fee for sending to all pathways. */ function quote( uint32[] memory _dstEids, uint16 _msgType, string memory _message, bytes calldata _extraSendOptions, bool _payInLzToken ) public view returns (MessagingFee memory totalFee) { bytes memory encodedMessage = abi.encode(_message); for (uint i = 0; i < _dstEids.length; i++) { bytes memory options = combineOptions(_dstEids[i], _msgType, _extraSendOptions); MessagingFee memory fee = _quote(_dstEids[i], encodedMessage, options, _payInLzToken); totalFee.nativeFee += fee.nativeFee; totalFee.lzTokenFee += fee.lzTokenFee; } } function send( uint32[] memory _dstEids, uint16 _msgType, string memory _message, bytes calldata _extraSendOptions // gas settings for A -> B ) external payable { if (_msgType != SEND) { revert InvalidMsgType(); } // Calculate the total messaging fee required. MessagingFee memory totalFee = quote(_dstEids, _msgType, _message, _extraSendOptions, false); require(msg.value >= totalFee.nativeFee, "Insufficient fee provided"); // Encodes the message before invoking _lzSend. bytes memory _encodedMessage = abi.encode(_message); uint256 totalNativeFeeUsed = 0; uint256 remainingValue = msg.value; for (uint i = 0; i < _dstEids.length; i++) { bytes memory options = combineOptions(_dstEids[i], _msgType, _extraSendOptions); MessagingFee memory fee = _quote(_dstEids[i], _encodedMessage, options, false); totalNativeFeeUsed += fee.nativeFee; remainingValue -= fee.nativeFee; // Ensure the current call has enough allocated fee from msg.value. require(remainingValue >= 0, "Insufficient fee for this destination"); _lzSend( _dstEids[i], _encodedMessage, options, fee, payable(msg.sender) ); emit MessageSent(_message, _dstEids[i]); } } /** * @notice Internal function to handle receiving messages from another chain. * @dev Decodes and processes the received message based on its type. * @param _origin Data about the origin of the received message. * @param message The received message content. */ function _lzReceive( Origin calldata _origin, bytes32 /*guid*/, bytes calldata message, address, // Executor address as specified by the OApp. bytes calldata // Any extra data or options to trigger on receipt. ) internal override { string memory _data = abi.decode(message, (string)); data = _data; emit MessageReceived(data, _origin.srcEid, _origin.sender); }}
Composed[](#composed "Direct link to Composed")
-------------------------------------------------
A composed message refers to an application that invokes the Endpoint method, `sendCompose`, to deliver a composed call to a destination contract via `lzCompose`.
 
This pattern demonstrates **horizontal composability**, which differs from vertical composability in that the external call is now containerized as a new message packet; enabling the application to ensure that certain receipt logic remains separate from the external call itself.
info
Since each composable call is created as a separate message packet via `lzCompose`, this pattern can be extended for as many steps as your application needs (`B1` -> `B2` -> `B3`, etc).
This pattern can be particularly powerful for orchestrating complex interactions and processes on the destination chain that need contract logic to be handled in independent steps, such as:
* **Omnichain DeFi Strategies**: A smart contract could trigger a token transfer on the destination chain and then automatically interact with a DeFi protocol to lend, borrow, provide liquidity, stake, etc. executing a series of financial strategies across chains.
* **NFT Interactions**: An NFT could be transferred to another chain, and upon arrival, it could trigger a contract to issue a license, register a domain, or initiate a subscription service linked to the NFT's ownership.
* **DAO Coordination**: A DAO could send funds to another chain's contract and compose a message to execute specific DAO-agreed upon investments or funding of public goods.
### Composing an OApp[](#composing-an-oapp "Direct link to Composing an OApp")
There are 3 relevant contract interactions when composing an OApp:
1. **Source OApp**: the OApp sending a cross-chain message via `_lzSend` to a destination.
2. **Destination OApp(s)**: the OApp receiving a cross-chain message via `_lzReceive` and calling `sendCompose`.
3. **Composed Receiver(s)**: the contract interface implementing business logic to handle receiving a composed message via `lzCompose`.
### Sending Message[](#sending-message "Direct link to Sending Message")
The sending OApp is **required** to pass specific [Composed Message Execution Options](#composed-message-execution-options)
(more on this below) for the `sendCompose` call, but is **not required** to pass any input parameters for the call itself (however this pattern may be useful depending on what arbitrary action you wish to trigger when composing).
For example, this `send` function packs the destination `_composedAddress` for the destination OApp to decode and use for the actual composed call.
/// @notice Sends a message from the source to destination chain./// @param _dstEid Destination chain's endpoint ID./// @param _message The message to send./// @param _composedAddress The contract you wish to deliver a composed call to./// @param _options Message execution options (e.g., for sending gas to destination).function send( uint32 _dstEid, string memory _message, address _composedAddress, // the destination contract implementing ILayerZeroComposer bytes calldata _options) external payable returns(MessagingReceipt memory receipt) { // Encodes the message before invoking _lzSend. bytes memory _payload = abi.encode(_message, _composedAddress); receipt = _lzSend( _dstEid, _payload, _options, // Fee in native gas and ZRO token. MessagingFee(msg.value, 0), // Refund address in case of failed source message. payable(msg.sender) );}
### Sending Compose[](#sending-compose "Direct link to Sending Compose")
The receiving OApp invokes the LayerZero Endpoint's `sendCompose` method as part of your OApp's `_lzReceive` business logic.
The `sendCompose` method takes the following inputs:
1. `_to`: the contract implementing the [`ILayerZeroComposer`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/protocol/contracts/interfaces/ILayerZeroComposer.sol)
receive interface.
2. `_guid`: the global unique identifier of the source message (provided standard by `lzReceive`).
3. `_index`: the index of the composed message (used for pricing different gas execution amounts along different composed legs of the transaction).
/// @dev the Oapp sends the lzCompose message to the endpoint/// @dev the composer MUST assert the sender because anyone can send compose msg with this function/// @dev with the same GUID, the Oapp can send compose to multiple _composer at the same time/// @dev authenticated by the msg.sender/// @param _to the address which will receive the composed message/// @param _guid the message guid/// @param _message the messagefunction sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external { // must have not been sent before if (composeQueue[msg.sender][_to][_guid][_index] != NO_MESSAGE_HASH) revert Errors.ComposeExists(); composeQueue[msg.sender][_to][_guid][_index] = keccak256(_message); emit ComposeSent(msg.sender, _to, _guid, _index, _message);}
This means that when a packet is received (`_lzReceive`) by the Destination OApp, it will send (`sendCompose`) a new composed packet via the destination LayerZero Endpoint.
/// @dev Called when data is received from the protocol. It overrides the equivalent function in the parent contract./// Protocol messages are defined as packets, comprised of the following parameters./// @param _origin A struct containing information about where the packet came from./// @param _guid A global unique identifier for tracking the packet./// @param payload Encoded message.function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata payload, address, // Executor address as specified by the OApp. bytes calldata // Any extra data or options to trigger on receipt.) internal override { // Decode the payload to get the message (string memory _message, address _composedAddress) = abi.decode(payload, (string, address)); // Storing data in the destination OApp data = _message; // Send a composed message[0] to a composed receiver endpoint.sendCompose(_composedAddress, _guid, 0, payload);}
info
The above `sendCompose` call hardcodes `_index` to `0` and simply forwards the same `payload` as `_lzReceive` to `lzCompose`, however these inputs can also be dynamically adjusted depending on the number and type of composed calls you wish to make.
#### Composed Message Execution Options[](#composed-message-execution-options "Direct link to Composed Message Execution Options")
You can decide both the `_gas` and `msg.value` that should be used for the composed call(s), depending on the type and quantity of messages you intend to send.
Your configured Executor will use the `_options` provided in the original `_lzSend` call to determine the gas limit and amount of `msg.value` to include per message `_index`:
// addExecutorLzComposeOption(uint16 _index, uint128 _gas, uint128 _value)Options.newOptions() .addExecutorLzReceiveOption(50000, 0) .addExecutorLzComposeOption(0, 30000, 0) .addExecutorLzComposeOption(1, 30000, 0);
It's important to remember that gas costs may vary depending on the destination chain. For example, all new Ethereum transactions cost `21000` wei, but other chains may have lower or higher opcode costs, or entirely different gas mechanisms.
You can read more about generating `_options` and the role of `_index` in [Message Execution Options](/v2/developers/evm/configuration/options#lzcompose-option)
.
### Receiving Compose[](#receiving-compose "Direct link to Receiving Compose")
The destination must implement the `ILayerZeroComposer` interface to handle receiving the composed message.
From there, you can decide any additional composed business logic to execute within `lzCompose`, as shown below:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";/// @title ComposedReceiver/// @dev A contract demonstrating the minimum ILayerZeroComposer interface necessary to receive composed messages via LayerZero.contract ComposedReceiver is ILayerZeroComposer { /// @notice Stores the last received message. string public data = "Nothing received yet"; /// @notice Store LayerZero addresses. address public immutable endpoint; address public immutable oApp; /// @notice Constructs the contract. /// @dev Initializes the contract. /// @param _endpoint LayerZero Endpoint address /// @param _oApp The address of the OApp that is sending the composed message. constructor(address _endpoint, address _oApp) { endpoint = _endpoint; oApp = _oApp; } /// @notice Handles incoming composed messages from LayerZero. /// @dev Decodes the message payload and updates the state. /// @param _oApp The address of the originating OApp. /// @param /*_guid*/ The globally unique identifier of the message. /// @param _message The encoded message content. function lzCompose( address _oApp, bytes32 /*_guid*/, bytes calldata _message, address, bytes calldata ) external payable override { // Perform checks to make sure composed message comes from correct OApp. require(_oApp == oApp, "!oApp"); require(msg.sender == endpoint, "!endpoint"); // Decode the payload to get the message (string memory message, ) = abi.decode(_message, (string, address)); data = message; }}
### Further Reading[](#further-reading "Direct link to Further Reading")
For more advanced implementations of `sendCompose` and `lzCompose`:
* Review the [`OmniCounter.sol`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/examples/OmniCounter.sol#L43)
for sending composed messages to the same OApp implementation.
* Read the [OFT Composing](/v2/developers/evm/oft/oft-patterns-extensions#composed-oft)
section to see how to implement composed business logic into your OFTs.
Composed ABA[](#composed-aba "Direct link to Composed ABA")
-------------------------------------------------------------
The **Composed ABA** design pattern enables sophisticated omnichain communication by allowing for an operation to be performed as part of the receive logic on the destination chain (`B1`), a follow-up action or call containerized as an independent step within `lzCompose` (`B2`), which then sends back to the source chain (`A`).
 
info
This message pattern can also be considered a Composed ABC type call (`A` -> `B1` -> `B2` -> `C`), as the nested `_lzSend` can send to any new destination chain.
This pattern demonstrates a complex, multi-step, process across blockchains where each step requires its own atomic logic to execute without depending on separate execution logic. Here are some use cases that could benefit from a Composed ABA design pattern:
* **Omnichain Data Verification**: Chain A sends a request to chain B to verify a set of data. Once verified, a contract on chain B executes an action based on this data and sends a signal back to chain A to either proceed with the next step or record the verification.
* **Omnichain Collateral Management**: When collateral on chain A is locked or released, a corresponding contract on chain B could be called to issue a loan or unlock additional funds. Confirmation of the action is then sent back to chain A to complete the process.
* **Multi-Step Contract Interaction for Games and Collectibles**: In a gaming scenario, an asset (like an NFT) could be sent from chain A to B, triggering a contract on B that could unlock a new level or feature in a game, with a confirmation or reward then sent back to chain A.
Message Ordering[](#message-ordering "Direct link to Message Ordering")
-------------------------------------------------------------------------
LayerZero offers both [**unordered delivery**](#unordered-delivery)
and [**ordered delivery**](#ordered-delivery)
, providing developers with the flexibility to choose the most appropriate transaction ordering mechanism based on the specific requirements of their application.
### Unordered Delivery[](#unordered-delivery "Direct link to Unordered Delivery")
By default, the LayerZero protocol uses **unordered delivery**, where transactions can be executed out of order if all transactions prior have been verified.
If transactions `1` and `2` have not been verified, then transaction `3` cannot be executed until the previous nonces have been verified.
Once nonces `1`, `2`, `3` have been verified:
* If nonce `2` failed to execute (due to some gas or user logic related issue), nonce `3` can still proceed and execute.
 
This is particularly useful in scenarios where transactions are not critically dependent on the execution of previous transactions.
### Ordered Delivery[](#ordered-delivery "Direct link to Ordered Delivery")
Developers can configure the OApp contract to use **ordered delivery**.
 
In this configuration, if you have a sequence of packets with nonces `1`, `2`, `3`, and so on, each packet must be executed in that exact, sequential order:
* If nonce `2` fails for any reason, it will block all subsequent transactions with higher nonces from being executed until nonce `2` is resolved.
 
Strict nonce enforcement can be important in scenarios where the order of transactions is critical to the integrity of the system, such as any multi-step process that needs to occur in a specific sequence to maintain consistency.
info
In these cases, strict nonce enforcement can be used to provide consistency, fairness, and censorship-resistance to maintain system integrity.
### Code Example[](#code-example-2 "Direct link to Code Example")
To implement strict nonce enforcement, you need to implement the following:
* a mapping to track the maximum received nonce.
* override `_acceptNonce` and `nextNonce`.
* add `ExecutorOrderedExecutionOption` in `_options` when calling `_lzSend`.
caution
If you do not pass an `ExecutorOrderedExecutionOption` in your `_lzSend` call, the Executor will attempt to execute the message despite your application-level nonce enforcement, leading to a message revert.
Append to your [Message Options](/v2/developers/evm/configuration/options)
an `ExecutorOrderedExecutionOption` in your `_lzSend` call:
// appends "01000104", the ExecutorOrderedExecutionOption, to your options bytes array_options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0).addExecutorOrderedExecutionOption();
Implement strict nonce enforcement via function override:
pragma solidity ^0.8.20;import { OApp } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; // Import OApp and other necessary contracts/interfaces/** * @title OmniChain Nonce Ordered Enforcement Example * @dev Implements nonce ordered enforcement for your OApp. */contract MyNonceEnforcementExample is OApp { // Mapping to track the maximum received nonce for each source endpoint and sender mapping(uint32 eid => mapping(bytes32 sender => uint64 nonce)) private receivedNonce; /** * @dev Constructor to initialize the omnichain contract. * @param _endpoint Address of the LayerZero endpoint. * @param _owner Address of the contract owner. */ constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {} /** * @dev Public function to get the next expected nonce for a given source endpoint and sender. * @param _srcEid Source endpoint ID. * @param _sender Sender's address in bytes32 format. * @return uint64 Next expected nonce. */ function nextNonce(uint32 _srcEid, bytes32 _sender) public view virtual override returns (uint64) { return receivedNonce[_srcEid][_sender] + 1; } /** * @dev Internal function to accept nonce from the specified source endpoint and sender. * @param _srcEid Source endpoint ID. * @param _sender Sender's address in bytes32 format. * @param _nonce The nonce to be accepted. */ function _acceptNonce(uint32 _srcEid, bytes32 _sender, uint64 _nonce) internal virtual override { receivedNonce[_srcEid][_sender] += 1; require(_nonce == receivedNonce[_srcEid][_sender], "OApp: invalid nonce"); } // @dev Override receive function to enforce strict nonce enforcement. function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData ) public payable virtual override { _acceptNonce(_origin.srcEid, _origin.sender, _origin.nonce); // Call the internal function with the correct parameters super._lzReceive(_origin, _guid, _message, _executor, _extraData); }}
Rate Limiting[](#rate-limiting "Direct link to Rate Limiting")
----------------------------------------------------------------
The `RateLimiter.sol` is used to control the number of cross-chain messages that can be sent within a certain time window, ensuring that the OApp is not spammed by too many transactions at once. It's particularly useful for:
* **Preventing Denial of Service Attacks**: By setting thresholds on the number of messages that can be processed within a given timeframe, the `RateLimiter` acts as a safeguard against DoS attacks, where malicious actors might attempt to overload an OApp with a flood of transactions. This protection ensures that the OApp remains accessible and functional for legitimate users, even under attempted attacks.
* **Regulatory Compliance**: Some applications may need to enforce limits to comply with legal or regulatory requirements.
The `RateLimiter` is only useful under specific application use cases. It will not be necessary for most OApps and can even be counterproductive for more generic applications:
* **Low Traffic Applications**: If your application doesn't expect high volumes of traffic, implementing a rate limiter might be unnecessary overhead.
* **Critical Systems Requiring Immediate Transactions**: For systems where transactions need to be processed immediately without delay, rate limiting could hinder performance.
### Installation[](#installation "Direct link to Installation")
To begin working with LayerZero contracts, you can install the [OApp npm package](https://www.npmjs.com/package/@layerzerolabs/oapp-evm?activeTab=code)
to an existing project:
npm install @layerzerolabs/oapp-evm
### Usage[](#usage "Direct link to Usage")
Import the `RateLimiter.sol` contract into your OApp contract file and inherit the contract:
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OApp } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { RateLimiter } from "@layerzerolabs/oapp-evm/contracts/oapp/utils/RateLimiter.sol";contract MyOApp is OApp, RateLimiter { // ...contract}
#### Initializing Rate Limits[](#initializing-rate-limits "Direct link to Initializing Rate Limits")
In the constructor of your contract, initialize the rate limits using `_setRateLimits` with an array of `RateLimitConfig` structs.
**Example:**
constructor( RateLimitConfig[] memory _rateLimitConfigs, address _lzEndpoint, address _delegate) OApp(_lzEndpoint, _delegate) { _setRateLimits(_rateLimitConfigs);}// ...Rest of contract code
**`RateLimitConfig` Struct:**
struct RateLimitConfig { uint32 dstEid; // destination endpoint ID uint256 limit; // arbitrary limit of messages/tokens to transfer uint256 window; // window of time before limit resets}
#### Setting Rate Limits[](#setting-rate-limits "Direct link to Setting Rate Limits")
Provide functions to set or update rate limits dynamically. This can include a function to adjust individual or multiple rate limits and a mechanism to authorize who can make these changes (typically restricted to the contract owner or a specific role).
/** * @dev Sets the rate limits based on RateLimitConfig array. Only callable by the owner or the rate limiter. * @param _rateLimitConfigs An array of RateLimitConfig structures defining the rate limits. */function setRateLimits( RateLimitConfig[] calldata _rateLimitConfigs) external { if (msg.sender != rateLimiter && msg.sender != owner()) revert OnlyRateLimiter(); _setRateLimits(_rateLimitConfigs);}
#### Checking Rate Limits During Send Calls[](#checking-rate-limits-during-send-calls "Direct link to Checking Rate Limits During Send Calls")
Before processing transactions, use `_checkAndUpdateRateLimit` to ensure the transaction doesn't exceed the set limits. This function should be called in any transactional functions, such as message passing or token transfers.
#### Message Passing[](#message-passing "Direct link to Message Passing")
function send( uint32 _dstEid, string memory _message, bytes calldata _options) external payable { _checkAndUpdateRateLimit(_dstEid, 1); // updating the rate limit per message sent bytes memory _payload = abi.encode(_message); // Encodes message as bytes. _lzSend( _dstEid, // Destination chain's endpoint ID. _payload, // Encoded message payload being sent. _options, // Message execution options (e.g., gas to use on destination). MessagingFee(msg.value, 0), // Fee struct containing native gas and ZRO token. payable(msg.sender) // The refund address in case the send call reverts. );}
#### Token Transfers[](#token-transfers "Direct link to Token Transfers")
/** * @dev Checks and updates the rate limit before initiating a token transfer. * @param _amountLD The amount of tokens to be transferred. * @param _minAmountLD The minimum amount of tokens expected to be received. * @param _dstEid The destination endpoint identifier. * @return amountSentLD The actual amount of tokens sent. * @return amountReceivedLD The actual amount of tokens received. */function _debit( uint256 _amountLD, uint256 _minAmountLD, uint32 _dstEid) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { _checkAndUpdateRateLimit(_dstEid, _amountLD); return super._debit(_amountLD, _minAmountLD, _dstEid);}
* [ABA](#aba)
* [Code Example](#code-example)
* [Batch Send](#batch-send)
* [Code Example](#code-example-1)
* [Composed](#composed)
* [Composing an OApp](#composing-an-oapp)
* [Sending Message](#sending-message)
* [Sending Compose](#sending-compose)
* [Receiving Compose](#receiving-compose)
* [Further Reading](#further-reading)
* [Composed ABA](#composed-aba)
* [Message Ordering](#message-ordering)
* [Unordered Delivery](#unordered-delivery)
* [Ordered Delivery](#ordered-delivery)
* [Code Example](#code-example-2)
* [Rate Limiting](#rate-limiting)
* [Installation](#installation)
* [Usage](#usage)
---
# OFT Patterns and Extensions | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The Omnichain Fungible Token (OFT) Standard can be extended to support several different use cases, similar to the ERC20 token standard.
In addition to the [OApp Design Patterns and Extensions](/v2/developers/evm/oapp/message-design-patterns)
, the following examples demonstrate how to modify your OFT contract for specific use cases.
| Message Pattern | Description |
| --- | --- |
| [Composed OFT](#composed-oft) | a composed call made after the OFT delivers the token transfer |
| [OFT Alt](#oft-alt) | a variant of the OFT standard that supports `EndpointV2Alt` for paying in an alternative ERC20 token |
Composed OFT[](#composed-oft "Direct link to Composed OFT")
-------------------------------------------------------------
A composed message refers to an OApp that invokes the LayerZero Endpoint method `sendCompose` to deliver a composed call to another contract on the destination chain via `lzCompose`.
Because OFT inherits the base OApp implementation, you can also send composed messages within your OFT receive logic.
 
If you are not familiar with how [OApp Composing](/v2/developers/evm/oapp/message-design-patterns)
works, review that section first before continuing here.
### Composing an OFT[](#composing-an-oft "Direct link to Composing an OFT")
The OFT Standard comes pre-packaged with methods for delivering composed calls to the destination OFT contract for handling.
1. **Source OFT**: The Source OFT specifies in the `send` call a composed message in `bytes` for delivering `to`. You can think of this the same as how `_lzSend` sends arbitrary bytes to a destination, which the destination contract uses in the `_lzReceive` business logic.
2. **Destination OFT(s)**: When the send call is received by the destination OFT, the internal `_lzReceive` function in `OFTCore.sol` handles the delivery of tokens along with the composed call.
3. **Composed Receiver(s)**: the contract interface implementing business logic to handle receiving a composed message via `lzCompose`.
### Sending Token[](#sending-token "Direct link to Sending Token")
When sending a token from source to destination, the caller has the option to specify an additional `composeMsg` in bytes.
/** * @dev Struct representing token parameters for the OFT send() operation. */struct SendParam { uint32 dstEid; // Destination endpoint ID. bytes32 to; // Recipient address. uint256 amountLD; // Amount to send in local decimals. uint256 minAmountLD; // Minimum amount to send in local decimals. bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. bytes composeMsg; // The composed message for the send() operation. bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations.}function send( SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {}
Depending on your implementation, this composed message field can be used to pass any arbitrary information as bytes along with your token to the destination address.
#### Composed Message Execution Options[](#composed-message-execution-options "Direct link to Composed Message Execution Options")
You will need to pass both an `lzReceiveOption` and `lzComposeOption` as either your [enforced](/v2/developers/evm/oft/quickstart#setting-enforced-options)
or extra options for this call to succeed.
You can decide both the `_gas` and `msg.value` that should be used for the composed call(s), depending on the type and quantity of messages you intend to send.
Your configured Executor will use the `_options` provided in the original `_lzSend` call to determine the gas limit and amount of `msg.value` to include per message `_index`:
// addExecutorLzComposeOption(uint16 _index, uint128 _gas, uint128 _value)Options.newOptions() .addExecutorLzReceiveOption(50000, 0) .addExecutorLzComposeOption(0, 30000, 0) .addExecutorLzComposeOption(1, 30000, 0);
It's important to remember that gas values may vary depending on the destination chain. For example, all new Ethereum transactions cost `21000` wei, but other chains may have lower or higher opcode costs, or entirely different gas mechanisms.
You can read more about generating `_options` and the role of `_index` in [Message Execution Options](/v2/developers/evm/configuration/options#lzcompose-option)
.
### Sending Compose[](#sending-compose "Direct link to Sending Compose")
By default, the destination OFT's `_lzReceive` method will check if the message is composed, and then deliver those arbitrary bytes to the specified `toAddress`:
// @dev Internal function to handle the receive on the LayerZero endpoint.if (_message.isComposed()) { // @dev Proprietary composeMsg format for the OFT. bytes memory composeMsg = OFTComposeMsgCodec.encode( _origin.nonce, _origin.srcEid, amountReceivedLD, _message.composeMsg() ); // @dev Stores the lzCompose payload that will be executed in a separate tx. // Standardizes functionality for executing arbitrary contract invocation on some non-evm chains. // @dev The off-chain executor will listen and process the msg based on the src-chain-callers compose options passed. // @dev The index is used when a OApp needs to compose multiple msgs on lzReceive. // For default OFT implementation there is only 1 compose msg per lzReceive, thus its always 0. endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);}
As shown in the `sendCompose` comments, the base OFT implementation only allows for 1 composed message per `lzReceive` call.
To add additional composed calls, you will need to override the `_lzReceive` method and add custom composed logic.
### Receiving Compose[](#receiving-compose "Direct link to Receiving Compose")
The receiving address of the cross-chain token transfer will need to implement custom business logic to handle the composed message, for example, consider this mock contract that swaps an inbound OFT for an ERC20:
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;import "@openzeppelin/contracts/token/ERC20/IERC20.sol";import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import { IOAppCore } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppCore.sol";import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";/// @title SwapMock Contract/// @dev This contract mocks an ERC20 token swap in response to an OFT being received (lzReceive) on the destination chain./// @notice The contract is designed to interact with LayerZero's Omnichain Fungible Token (OFT) Standard,/// allowing it to respond to cross-chain OFT mint events with a token swap action.contract SwapMock is IOAppComposer { using SafeERC20 for IERC20; IERC20 public erc20; address public immutable endpoint; address public immutable oApp; /// @notice Emitted when a token swap is executed. /// @param user The address of the user who receives the swapped tokens. /// @param tokenOut The address of the ERC20 token being swapped. /// @param amount The amount of tokens swapped. event Swapped(address indexed user, address tokenOut, uint256 amount); /// @notice Constructs the SwapMock contract. /// @dev Initializes the contract. /// @param _erc20 The address of the ERC20 token that will be used in swaps. /// @param _endpoint LayerZero Endpoint address /// @param _oApp The address of the OApp that is sending the composed message. constructor(address _erc20, address _endpoint, address _oApp) { erc20 = IERC20(_erc20); endpoint = _endpoint; oApp = _oApp; } /// @notice Handles incoming composed messages from LayerZero. /// @dev Decodes the message payload to perform a token swap. /// This method expects the encoded compose message to contain the swap amount and recipient address. /// @param _oApp The address of the originating OApp. /// @param /*_guid*/ The globally unique identifier of the message (unused in this mock). /// @param _message The encoded message content in the format of the OFTComposeMsgCodec. /// @param /*Executor*/ Executor address (unused in this mock). /// @param /*Executor Data*/ Additional data for checking for a specific executor (unused in this mock). function lzCompose( address _oApp, bytes32 /*_guid*/, bytes calldata _message, address /*Executor*/, bytes calldata /*Executor Data*/ ) external payable override { require(_oApp == oApp, "!oApp"); require(msg.sender == endpoint, "!endpoint"); // Extract the composed message from the delivered message using the MsgCodec address _receiver = abi.decode(OFTComposeMsgCodec.composeMsg(_message), (address)); uint256 _amountLD = OFTComposeMsgCodec.amountLD(_message); // Execute the token swap by transferring the specified amount to the receiver erc20.safeTransfer(_receiver, _amountLD); // Emit an event to log the token swap details emit Swapped(_receiver, address(erc20), _amountLD); }}
You will need to use the `OFTComposeMsgCodec` to extract the `composeMsg` and `_amountLD` from the overall message, before decoding it.
tip
The above example enforces that the `_amountLD` was deposited to this contract!
The OFT Standard will only credit tokens and call `sendCompose` to the `_toAddress` provided on the source chain:
// @dev Credit the amountLD to the recipient and return the ACTUAL amount the recipient received in local decimalsuint256 amountReceivedLD = _credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid);endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
### Further Reading[](#further-reading "Direct link to Further Reading")
* Review the [`OFT.sol`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFTCore.sol#L272)
implementation and [unit tests](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/test/OFT.t.sol#L112)
for handling composed messages.
OFT Alt[](#oft-alt "Direct link to OFT Alt")
----------------------------------------------
When deploying OApps, you might encounter scenarios where the native gas token cannot be used to pay the LayerZero Endpoint to send a message.
These Endpoints have been deployed using the [`EndpointV2Alt.sol`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/protocol/contracts/EndpointV2Alt.sol)
contract, so that they can use an alternative ERC20 token on the same chain to pay for cross-chain messages. Because these Endpoints do not use the native gas token, some changes must be made to your OApp contracts (including OFT).
For example, the `OFTAlt.sol` demonstrates this implementation fully, which you can reference when modifying your other OApp-based contracts:
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OFTAlt } from "../OFTAlt.sol";contract MyOFT is OFTAlt { constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) OFTAlt(_name, _symbol, _lzEndpoint, _delegate) { // constructor logic ... }}
### Contract Changes[](#contract-changes "Direct link to Contract Changes")
At a high level, only a few changes to your OApp are needed to interact with the `EndpointV2Alt.sol` contract:
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";import { MessagingParams } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";import { MessagingFee, MessagingReceipt } from "../interfaces/IOFT.sol";import { OFT } from "../OFT.sol";contract OFTAlt is OFT { using SafeERC20 for IERC20; error LzAltTokenUnavailable(); constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} /** * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message. * @param _dstEid The destination endpoint ID. * @param _message The message payload. * @param _options Additional options for the message. * @param _fee The calculated LayerZero fee for the message. * - nativeFee: The native fee. * - lzTokenFee: The lzToken fee. * @param _refundAddress The address to receive any excess fee values sent to the endpoint. * @return receipt The receipt for the sent message. * - guid: The unique identifier for the sent message. * - nonce: The nonce of the sent message. * - fee: The LayerZero fee incurred for the message. */ function _lzSend( uint32 _dstEid, bytes memory _message, bytes memory _options, MessagingFee memory _fee, address _refundAddress ) internal virtual override returns (MessagingReceipt memory receipt) { // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint. _payNative(_fee.nativeFee); if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee); return // solhint-disable-next-line check-send-result endpoint.send( MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0), _refundAddress ); } /** * @dev Internal function to pay the alt token fee associated with the message. * @param _nativeFee The alt token fee to be paid. * * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction, * this will need to be overridden because alt token would contain multiple lzFees. */ function _payNative(uint256 _nativeFee) internal virtual override returns (uint256 nativeFee) { address nativeErc20 = endpoint.nativeToken(); if (nativeErc20 == address(0)) revert LzAltTokenUnavailable(); // Pay Alt token fee by sending tokens to the endpoint. IERC20(nativeErc20).safeTransferFrom(msg.sender, address(endpoint), _nativeFee); }}
#### Pass OpenZeppelin Ownable[](#pass-openzeppelin-ownable "Direct link to Pass OpenZeppelin Ownable")
constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
Make sure to pass [OpenZeppelin's Ownable](https://docs.openzeppelin.com/contracts/5.x/api/access#Ownable)
modifier to the constructor. The access control has already been applied, but must be explicitly passed in the constructor to compile successfully.
#### Using SafeERC20[](#using-safeerc20 "Direct link to Using SafeERC20")
using SafeERC20 for IERC20;
You should include the [SafeERC20 library](https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#SafeERC20)
for safely interacting with ERC20 tokens. This is crucial for ensuring that token transfers handle potential errors like reverts or exceptions.
#### Error Handling[](#error-handling "Direct link to Error Handling")
error LzAltTokenUnavailable();
A custom error `LzAltTokenUnavailable` which is used to handle cases where the native ERC20 token for fee payment is not set in the `EndpointV2Alt` contract.
#### Override `_payNative`[](#override-_paynative "Direct link to override-_paynative")
/** * @dev Internal function to pay the alt token fee associated with the message. * @param _nativeFee The alt token fee to be paid. * * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction, * this will need to be overridden because alt token would contain multiple lzFees. */function _payNative(uint256 _nativeFee) internal virtual override returns (uint256 nativeFee) { address nativeErc20 = endpoint.nativeToken(); if (nativeErc20 == address(0)) revert LzAltTokenUnavailable(); // Pay Alt token fee by sending tokens to the endpoint. IERC20(nativeErc20).safeTransferFrom(msg.sender, address(endpoint), _nativeFee);}
You should override the `_payNative` function to handle paying using an ERC20 token. This function checks if the ERC20 token address is set (`nativeErc20`), reverts if not, and performs a `safeTransferFrom` to transfer the fee from the `sender` to the `endpoint`.
This ensures that the contract can handle fees in the specified ERC20 token by the `EndpointV2Alt`.
#### Override `_lzSend`[](#override-_lzsend "Direct link to override-_lzsend")
/** * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message. * @param _dstEid The destination endpoint ID. * @param _message The message payload. * @param _options Additional options for the message. * @param _fee The calculated LayerZero fee for the message. * - nativeFee: The native fee. * - lzTokenFee: The lzToken fee. * @param _refundAddress The address to receive any excess fee values sent to the endpoint. * @return receipt The receipt for the sent message. * - guid: The unique identifier for the sent message. * - nonce: The nonce of the sent message. * - fee: The LayerZero fee incurred for the message. */function _lzSend( uint32 _dstEid, bytes memory _message, bytes memory _options, MessagingFee memory _fee, address _refundAddress) internal virtual override returns (MessagingReceipt memory receipt) { // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint. _payNative(_fee.nativeFee); if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee); return // solhint-disable-next-line check-send-result endpoint.send( MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0), _refundAddress );}
To apply the changes made in `_payNative`, you should also override `_lzSend` to handle the ERC20 token fee.
info
Because `_lzSend` now uses an ERC20 token as payment, you must now [**approve**](https://docs.openzeppelin.com/contracts/2.x/api/token/erc20#IERC20-approve-address-uint256-)
the OFT as a spender of your ERC20 token.
* [Composed OFT](#composed-oft)
* [Composing an OFT](#composing-an-oft)
* [Sending Token](#sending-token)
* [Sending Compose](#sending-compose)
* [Receiving Compose](#receiving-compose)
* [Further Reading](#further-reading)
* [OFT Alt](#oft-alt)
* [Contract Changes](#contract-changes)
---
# Best Practices for Contract Ownership | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero’s Contract Standards inherit the [OpenZeppelin Ownable Standard](https://docs.openzeppelin.com/contracts/5.x/access-control)
by default. This allows for flexible and secure administration of deployed contracts, such as OApp or OFT. However, decisions around transferring or renouncing ownership must be made carefully, especially when dealing with critical contracts.
Why Ownership Matters[](#why-ownership-matters "Direct link to Why Ownership Matters")
----------------------------------------------------------------------------------------
When you deploy a contract, such as an OFT token, the deployer is set as the initial owner. As the owner, you have the ability to configure many administrative settings, including:
* **Peer Management:** Setting peers for cross-chain operations.
* **Delegate Controls:** Managing delegate addresses.
* **Enforced Options:** Configuring options that govern contract behavior.
* **Message Inspectors:** Overseeing message processing and security checks.
These controls are essential for ensuring the secure operation of your LayerZero contracts.
Recommended Best Practices[](#recommended-best-practices "Direct link to Recommended Best Practices")
-------------------------------------------------------------------------------------------------------
1. **Retain Ownership with a Secure Multisig:**
* **Do not renounce ownership** of critical contracts like the OFT. Instead, transfer ownership to a multisig wallet.
* A multisig setup requires multiple signatures (or approvals) for administrative actions, reducing the risk of a single point of failure.
* Use a high enough quorum to ensure that no single party can unilaterally change settings.
2. **Maintain Flexibility:**
* Retaining ownership allows you to adjust peers, delegates, and other settings as your cross-chain protocols evolve.
* This flexibility can be critical for adding new networks or responding to chain level disruptions.
3. **Document and Audit:**
* Clearly document the ownership and administration process for your contracts.
* Regularly audit the multisig wallet and its quorum settings to ensure they meet current security and governance standards.
Example: Transfer of Ownership[](#example-transfer-of-ownership "Direct link to Example: Transfer of Ownership")
------------------------------------------------------------------------------------------------------------------
LayerZero’s contracts follow the `Ownable` pattern. For example, here’s how you can transfer ownership of an OFT token contract:
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";contract MyOFT is OFT, Ownable { constructor( string memory _name, string memory _symbol, address _lzEndpoint, address _delegate ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}}
// Transferring ownership in your deployment script or via a web3 interface:tx = await(await oft.transferOwnership(newAddress)).wait();
By transferring ownership to a secure multisig wallet (or another trusted address), you ensure that the contract remains under strong administrative control even as you delegate responsibilities or make system-wide changes.
Summary[](#summary "Direct link to Summary")
----------------------------------------------
* **Retain Ownership:** Do not renounce ownership on critical LayerZero contracts (like the BNB OFT token).
* **Use Secure Multisig:** Always maintain ownership through a properly configured multisig wallet to allow for necessary administrative controls.
* **Stay Flexible:** Keeping control allows you to update settings such as peers, delegates, and message inspectors as needed.
This approach secures your contract administration while ensuring you can respond to any changes or issues that arise in a rapidly evolving cross-chain environment.
* [Why Ownership Matters](#why-ownership-matters)
* [Recommended Best Practices](#recommended-best-practices)
* [Example: Transfer of Ownership](#example-transfer-of-ownership)
* [Summary](#summary)
---
# Read CLI Setup Guide | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
To start leveraging LayerZero Read (`lzRead`), LayerZero offers CLI examples that streamline the setup and configuration process, similar to the standard CLI examples provided for other LayerZero functionalities.
Using the Read CLI[](#using-the-read-cli "Direct link to Using the Read CLI")
-------------------------------------------------------------------------------
To begin using `lzRead`, follow the steps below. These instructions utilize the same commands as the standard setup for OApps but include minor adjustments specific to configuring read capabilities.
### Create Your lzRead Repo[](#create-your-lzread-repo "Direct link to Create Your lzRead Repo")
Run the following command to create a new LayerZero OApp with read capabilities enabled:
LZ_ENABLE_READ_EXAMPLE=1 npx create-lz-oapp@latest
This command initializes a new project with the necessary configurations to support lzRead. The project creation wizard will guide you through selecting a template and setting up your development environment.
✔ Where do you want to start your project? … ./my-lz-read-oapp✔ Which example would you like to use as a starting point? › OApp Read✔ What package manager would you like to use in your project? › pnpm
This will set up a repository with example contracts, cross-chain unit tests for read operations, custom LayerZero read configuration files, deployment scripts, and more.
Follow the normal setup process defined above (adding networks to your `hardhat.config.ts`, adding your `MNEMONIC` or `PRIVATE_KEY` to `.env`, etc.)
// hardhat.config.tsimport { EndpointId } from '@layerzerolabs/lz-definitions';networks: { ethereum: { eid: EndpointId.ETHEREUM_V2_MAINNET, url: process.env.RPC_URL_ETHEREUM, accounts, }, arbitrum: { eid: EndpointId.ARBITRUM_V2_MAINNET, url: process.env.RPC_URL_ARBITRUM, accounts, }, polygon: { eid: EndpointId.POLYGON_V2_MAINNET, url: process.env.RPC_URL_POLYGON, accounts, },},
Refer to the [LayerZero Endpoint Addresses](/v2/deployments/deployed-contracts)
to ensure the networks you add have deployed endpoints.
To see a list of available commands, run `npx hardhat`:
lz:read:resolve-command Task for debugging read commandslz:oapp-read:wire Wire LayerZero Read OApplz:oapp-read:config:get Get Read OApp configurationlz:oapp-read:config:init Initialize Read OApp configurationlz:oapp-read:config:get:channel Get information of read channels for networks
Each command serves a specific purpose in managing and configuring your lzRead setup.
All of the standard CLI methods available in the [LayerZero CLI Setup Guide](/v2/developers/evm/create-lz-oapp/start)
can also be found in this newly initialized project. Take some time to familiarize yourself with the project commands and layout.
In general each of the `lzRead` CLI methods have been modified from the base CLI, so all `lz:oapp:` and `lz:oapp:read` methods should behave similarly.
### Configure LayerZero Config[](#configure-layerzero-config "Direct link to Configure LayerZero Config")
Unlike standard OApp configurations, `lzRead` requires specific settings in the `layerzero.config.ts` file. Instead of configuring `connections`, you'll focus solely on defining `contracts` and `read channels`:
// layerzero.config.tsimport {ChannelId, EndpointId} from '@layerzerolabs/lz-definitions';import {OAppReadOmniGraphHardhat} from '@layerzerolabs/oapp-evm';import {ethers} from 'ethers';const arbitrumContract: OmniPointHardhat = { eid: EndpointId.ARBITRUM_V2_MAINNET, contractName: 'UniswapV3QuoteDemo',};const config: OAppReadOmniGraphHardhat = { contracts: [ { contract: arbitrumContract, // Dummy contract address config: { readLibrary: '0xbcd4CADCac3F767C57c4F402932C4705DF62BEFf', readChannels: [ { channelId: ChannelId.READ_CHANNEL_1, active: true, }, ], readConfig: { ulnConfig: { requiredDVNs: ['0x1308151a7ebac14f435d3ad5ff95c34160d539a5'], executor: '0x31CAe3B7fB82d847621859fb1585353c5720660D', }, }, }, }, ], connections: [], // No connections needed for read-only setup};export default config;
You can generate this config file based on the networks specified in your `hardhat.config.ts` by running:
npx hardaht lz:oapp-read:config:init --contract-name --oapp-config
This will produce a new `layerzero.config.ts` file based on the `READ_CONTRACT_NAME` and available networks in your hardhat project.
Key configuration details include:
* `contracts`: Define the contracts you intend to interact with. In this example, the origin chain Arbitrum has the child `OAppRead` contract address.
* `readLibrary`: The address of the Read Library (`ReadLib1002`) contract deployed on your network.
* `readChannels`: Specify the read channels you want to activate.
* `readConfig`: Configure the Read Library settings, including required Decentralized Verifier Networks (DVNs) and the executor address.
### Wire Your Read OApp[](#wire-your-read-oapp "Direct link to Wire Your Read OApp")
After configuring `layerzero.config.ts`, execute the following command to wire your Read OApp:
npx hardhat lz:oapp-read:wire --oapp-config layerzero.config.ts
This command sets up the necessary connections and configurations based on your `layerzero.config.ts` file, enabling your OApp to perform read operations.
### Debugging Malformed or Unresolvable Commands[](#debugging-malformed-or-unresolvable-commands "Direct link to Debugging Malformed or Unresolvable Commands")
LayerZero provides a Hardhat task to assist in debugging and resolving read commands. This task helps identify and troubleshoot issues with your read commands.
To resolve a read command, run the following command in your terminal:
npx hardhat lz:read:resolve-command --command
If the command is correctly formed and resolvable, the task will provide the expected target data.
If the command is [Malformed](/v2/developers/evm/lzread/overview#debugging-read-commands)
or [Unresolvable](/v2/developers/evm/lzread/overview#debugging-read-commands)
, the task will output relevant error messages to help you pinpoint the issue.
Based on the feedback from the resolver task, make necessary adjustments to your command construction or target contract configurations.
### Testing Contracts[](#testing-contracts "Direct link to Testing Contracts")
Ensuring the reliability of your `lzRead` setup involves thorough testing. LayerZero provides a `TestHelper` tailored for Foundry unit tests, enabling you to simulate cross-chain reads in your tests.
For lzRead, this helper has been extended to support read-specific functionalities. Some limitations of this testing method should be understood:
* **Command Validation**: Does not simulate whether a command is `malformed` or `unresolvable`.
To fully ensure contract functionality, it's recommended to conduct tests on mainnet even after unit testing, to ensure that the mocked state in your unit tests matches the behaviour found on-chain.
* [Using the Read CLI](#using-the-read-cli)
* [Create Your lzRead Repo](#create-your-lzread-repo)
* [Configure LayerZero Config](#configure-layerzero-config)
* [Wire Your Read OApp](#wire-your-read-oapp)
* [Debugging Malformed or Unresolvable Commands](#debugging-malformed-or-unresolvable-commands)
* [Testing Contracts](#testing-contracts)
---
# Transaction Pricing | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
Every transaction using LayerZero has four main cost elements, one for each component that enables cross-chain messaging:
1. an initial source blockchain transaction.
2. the fee paid to the OApp's configured [Security Stack](/v2/concepts/modular-security/security-stack-dvns)
.
3. the configured [Executor](/v2/concepts/permissionless-execution/executors)
fee for executing the message on the destination chain.
4. the cost of purchasing the specified amount of destination gas token(s) for the Executor's destination transaction.
The source chain's native gas token quote for the messaging fee is calculated using following formula:
GAS×DESTINATION\_GAS\_PRICE×DESTINATION\_NATIVE\_TOKEN\_PRICESOURCE\_NATIVE\_TOKEN\_PRICE\\text{GAS} \\times \\text{DESTINATION\\\_GAS\\\_PRICE} \\times \\frac{\\text{DESTINATION\\\_NATIVE\\\_TOKEN\\\_PRICE}}{\\text{SOURCE\\\_NATIVE\\\_TOKEN\\\_PRICE}}GAS×DESTINATION\_GAS\_PRICE×SOURCE\_NATIVE\_TOKEN\_PRICEDESTINATION\_NATIVE\_TOKEN\_PRICE
### Gas Amount[](#gas-amount "Direct link to Gas Amount")
Because the source chain has no concept of the destination chain's state, you must specify the amount of gas in `wei` you anticipate will be necessary for executing your `_lzReceive` or `lzCompose` method on the destination smart contract.
LayerZero provides robust [Message Execution Options](/v2/developers/evm/configuration/options)
, which allow users to provide detailed instructions regarding the gas limit and `msg.value` the Executor uses for message delivery on the destination chain per function call:
// addExecutorLzReceiveOption(GAS_LIMIT, MSG_VALUE)// addExecutorLzComposeOption(INDEX, GAS_LIMIT, MSG_VALUE)bytes memory options = OptionsBuilder.newOptions() .addExecutorLzReceiveOption(60000, 0) .addExecutorLzComposeOption(0, 30000, 0)
caution
The amount of gas units (`wei`) that your contract's `_lzReceive` or `lzCompose` methods consume can be dynamic depending on the destination chain. Different blockchains have different opcode costs and gas mechanisms that can fluctuate (e.g., sequencer fees, proof fees, etc).
To mitigate the risk of transactions stalling due to `OUT-OF-GAS` issues on the destination, it is advisable to test gas costs for your `_lzReceive` or `lzCompose` contract logic, and incorporate a gas buffer by allocating additional gas upfront depending on the chain.
### Quote Mechanism[](#quote-mechanism "Direct link to Quote Mechanism")
The LayerZero Endpoint provides an on-chain quote mechanism, to determine the cost of sending a message to the destination chain:
// LayerZero/V2/protocol/contracts/interfaces/ILayerZeroEndpointV2.solstruct MessagingParams { uint32 dstEid; bytes32 receiver; bytes message; bytes options; bool payInLzToken;}struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee;}// LayerZero Endpoint's quote mechanismfunction quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory);
Both the OApp and OFT have implemented a [quote mechanism](/v2/developers/evm/configuration/gas-fees)
using this Endpoint method.
If a user wants to send a message from Chain A to Chain B, the gas quote returned on Chain A is:
* `the execution cost on Chain A` + `fees for the Security Stack and Executor` + `a quote for the gas to be executed on Chain B`
For example, if a user wants `200000` gas units on Chain B, then a quote for that gas token is obtained by multiplying the gas by the gas price on the destination chain. It also takes into account dollar prices of the source and destination native tokens.
The source chain's native token quote is calculated using following formula:
GAS×DESTINATION\_GAS\_PRICE×DESTINATION\_NATIVE\_TOKEN\_PRICESOURCE\_NATIVE\_TOKEN\_PRICE\\text{GAS} \\times \\text{DESTINATION\\\_GAS\\\_PRICE} \\times \\frac{\\text{DESTINATION\\\_NATIVE\\\_TOKEN\\\_PRICE}}{\\text{SOURCE\\\_NATIVE\\\_TOKEN\\\_PRICE}}GAS×DESTINATION\_GAS\_PRICE×SOURCE\_NATIVE\_TOKEN\_PRICEDESTINATION\_NATIVE\_TOKEN\_PRICE
info
For example, assume Chain A is **Astar** and Chain B is **Astar zkEVM**, **Astar** uses token `ASTR` as a native token, and **Astar zkEVM** uses `ETH` as its native token. Other assumptions are:
1. ASTR = ~$0.15
2. ETH = ~$3500
3. Destination gas price = 4 Gwei
The quote returned will be:
200000 * (4000000000 / 10**18) * $3500 / $0.15 = 18.7 ASTR quote
### Profiling[](#profiling "Direct link to Profiling")
These sample gas profiles were based on 15 OFT transfers across 3 EVM networks (`Sepolia`, `Fuji`, `Mumbai`):
| Metric | Value |
| --- | --- |
| Deployment gas | `2,903,879` |
| Send gas on source chain (average) | `226,541` |
| Send gas on source chain (range) | `221,261 - 241,095` |
| Receive gas on destination chain (average) | `62,000` |
| Receive gas on destination chain (range) | `56,970 - 78,882` |
Please note that the values provided above were measured for standard OFT transactions with minimal custom logic applied. You can explore the source code at the following links:
* [Sepolia Etherscan](https://sepolia.etherscan.io/address/0x67457db11bcf2d79be032d8cda7c696eb8142d98)
* [Snowtrace Testnet](https://testnet.snowtrace.io/address/0x205d4d615c73965467eeb9a113cef702095d9d05)
* [Mumbai PolygonScan](https://mumbai.polygonscan.com/address/0x82c404bdffcc7da1a3d5ee8aee5f0932a0f68a26)
Feel free to input these contract addresses into [LayerZero Scan](https://layerzeroscan.com/)
to discover all the transfers used for profiling, including both the source and destination transactions.
### Handling Errors[](#handling-errors "Direct link to Handling Errors")
Transactions in LayerZero may occasionally encounter delays in transit from the source chain to the destination chain. Common causes for these delays include:
* Failure to initiate a valid transaction from the source chain into the LayerZero protocol.
* Insufficient gas payments made by the user.
* Transaction reverts on the destination chain, either due to in-contract or configuration issues.
[LayerZero Scan](/v2/developers/evm/tooling/layerzeroscan)
offers a comprehensive tool for users to track their transactions. It provides detailed insights into where transactions may encounter delays, serving as a starting point for debugging.
Users can find detailed guidelines and support for debugging and recovering stalled transactions in [Debugging Messages](/v2/developers/evm/troubleshooting/debugging-messages)
.
* [Gas Amount](#gas-amount)
* [Quote Mechanism](#quote-mechanism)
* [Profiling](#profiling)
* [Handling Errors](#handling-errors)
---
# Supported Data Types | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero Read currently supports a variety of data types for reading external state via `calldata` from `EVMCallRequestV1`. The following types of `calldata` methods are supported:
1. **Public State Variables**: Direct access to `public` state variables on target contracts.
2. **View or Pure Functions**: Functions that do not modify the blockchain state and only return data.
3. **Non-View or Pure Functions Returning Data**: Functions that are not marked as `view` or `pure`, but do not alter the on-chain state and only return data.
These supported data types enable developers to fetch and utilize external state data efficiently without incurring unnecessary gas costs or affecting the target blockchain's state.
Function Types[](#function-types "Direct link to Function Types")
-------------------------------------------------------------------
See the simple data type examples below for reference on how to implement `EVMCallRequestV1` in your `OAppRead` application.
### State Variables[](#state-variables "Direct link to State Variables")
Public state variables in Solidity automatically generate `getter` functions, making them easily accessible for read operations. LayerZero Read can directly interact with these `getter` functions to retrieve the current state:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;/** * @title ExampleContract * @notice A simple contract with a public state variable. */contract ExampleContract { // a public data variable on the target data chain to read from uint256 public data; constructor(uint256 _data) { data = _data; }}
To read the data variable using `lzRead`, you can encode the getter function call as follows:
bytes memory callData = abi.encodeWithSelector(ExampleContract.data.selector);readRequests[i] = EVMCallRequestV1({ appRequestLabel: uint16(i + 1), // arbitrary request label, for OApp filtering purposes targetEid: targetEid, isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: 15, // Example set block confirmations to wait to 15 blocks to: 0x1234567890123456789012345678901234567890, // Dummy address where ExampleContract is deployed callData: callData});
### View or Pure Functions[](#view-or-pure-functions "Direct link to View or Pure Functions")
`view` and `pure` functions are ideal for read operations as they do not modify the blockchain state. LayerZero Read can seamlessly interact with these functions to retrieve necessary data.
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;/** * @title MathContract * @notice A contract with a pure function for mathematical operations. */contract MathContract { /** * @notice Adds two numbers. * @param a First number. * @param b Second number. * @return sum The sum of a and b. */ function add(uint256 a, uint256 b) external pure returns (uint256 sum) { return a + b; }}
To read the result of the `add` function using `lzRead`:
bytes memory callData = abi.encodeWithSelector(MathContract.add.selector, 5, 10);readRequests[i] = EVMCallRequestV1({ appRequestLabel: uint16(i + 1), targetEid: targetEid, isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: 15, // Set to 15 blocks to: 0x1234567890123456789012345678901234567890, // Dummy contract address callData: callData});
Non-View or Pure Functions Returning Data[](#non-view-or-pure-functions-returning-data "Direct link to Non-View or Pure Functions Returning Data")
----------------------------------------------------------------------------------------------------------------------------------------------------
Some functions are not marked as `view` or `pure`, but still do not modify the on-chain state. These functions can also be utilized with `lzRead` as long as they only return data without performing state changes.
For example, Uniswap V3's `IQuoterV2` relies on calling non-view functions and reverting to compute the result. This is not gas efficient and should not be called on-chain, making `lzRead` a great option for retrieving the state:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;/// @title QuoterV2 Interface/// @notice Supports quoting the calculated amounts from exact input or exact output swaps./// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap./// @dev These functions are not marked view because they rely on calling non-view functions and reverting/// to compute the result. They are also not gas efficient and should not be called on-chain.interface IQuoterV2 { /// @notice Returns the amount out received for a given exact input but for a swap of a single pool /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` /// tokenIn The token being swapped in /// tokenOut The token being swapped out /// fee The fee of the token pool to consider for the pair /// amountIn The desired input amount /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap /// @return amountOut The amount of `tokenOut` that would be received /// @return sqrtPriceX96After The sqrt price of the pool after the swap /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed /// @return gasEstimate The estimate of the gas that the swap consumes function quoteExactInputSingle(QuoteExactInputSingleParams memory params) external returns ( uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate );}
To read a quote for an `amountOut` for a specific token pair function using `lzRead`:
// Define the QuoteExactInputSingleParamsIQuoterV2.QuoteExactInputSingleParams memory params = IQuoterV2.QuoteExactInputSingleParams({ tokenIn: config.tokenInAddress, tokenOut: config.tokenOutAddress, amountIn: 1 ether, // amountIn: 1 WETH fee: config.fee, sqrtPriceLimitX96: 0 // No price limit});// @notice Encode the function call// @dev From Uniswap Docs, this function is not marked view because it relies on calling non-view// functions and reverting to compute the result. It is also not gas efficient and should not// be called on-chain. We take advantage of lzRead to call this function off-chain and get the result// returned back on-chain to the OApp's _lzReceive method.// https://docs.uniswap.org/contracts/v3/reference/periphery/interfaces/IQuoterV2bytes memory callData = abi.encodeWithSelector(IQuoterV2.quoteExactInputSingle.selector, params);readRequests[i] = EVMCallRequestV1({ appRequestLabel: uint16(i + 1), targetEid: targetEid, isBlockNum: false, blockNumOrTimestamp: uint64(block.timestamp), confirmations: config.confirmations, to: config.quoterAddress, callData: callData});
* [Function Types](#function-types)
* [State Variables](#state-variables)
* [View or Pure Functions](#view-or-pure-functions)
* [Non-View or Pure Functions Returning Data](#non-view-or-pure-functions-returning-data)
---
# LayerZero EVM Chain Compatibility | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
LayerZero V2 connects a diverse ecosystem of blockchain networks that support Ethereum’s Virtual Machine (EVM). Because different chains implement the EVM in various ways, it’s important for developers—especially those building omnichain applications (OApp), OFT, and ONFT—to understand whether a network is **EVM Compatible** or **EVM Equivalent**.
This documentation focuses on the practical impacts when integrating LayerZero:
* **Fee delivery:** LayerZero endpoints expect worker fees to be delivered via `payable` (`msg.value`) using the chain’s native token. Some chains (e.g. SKALE) use an alternative `ERC20` fee token, which requires alternative LayerZero contracts (e.g., `EndpointV2Alt`, `OAppAlt`, `OFTAlt`).
* **Token standards:** LayerZero EVM token standards rely on the normal `ERC20`/`ERC721` conventions.
* **Data queries (lzRead):** LayerZero Read functions may use `block.number` and `block.timestamp` to reference “latest” state. However, on some chains these values may drift or be unreliable (for example, Arbitrum’s `block.number` may return the L1 block number), potentially causing mismatches in state queries.
* **Gas estimation:** Accurate gas limits are critical to ensure successful cross-chain message delivery. Each chain may have a unique fee model, which impacts how gas estimates should be calculated for [`lzReceive`](/v2/concepts/glossary#lzreceive)
and [`lzCompose`](/v2/concepts/glossary#lzcompose)
.
Concept: Compatibility vs. Equivalence[](#concept-compatibility-vs-equivalence "Direct link to Concept: Compatibility vs. Equivalence")
-----------------------------------------------------------------------------------------------------------------------------------------
> **EVM compatibility:**
> While these chains run Ethereum smart contracts, they may require adjustments in deployment scripts, fee handling, gas estimation, and verification. For example, zkSync requires its own compiler (`zkSolc`), and SKALE’s “free gas” model requires an alternative fee token for cross-chain fees. These differences can affect how LayerZero contracts pay/receive fees and how developers should interact with the chain.
>
> **EVM equivalence:**
> These chains replicate Ethereum’s execution environment so closely that standard clients, deployment scripts, and tooling work without modification. Most LayerZero integrations (like OApp/OFT/ONFT and lzRead queries) work as on Ethereum—with only subtle differences.
These differences directly impact:
* **Fee payment:** Standard `msg.value` fee delivery is expected by LayerZero endpoints. Some chains, however, require alternative tokens or extra configuration.
* **On-chain data:** lzRead can depend on `block.number` and `block.timestamp`. Variability or drift in these values (for instance, Arbitrum may return L1 `block.number`) may result in inaccurate or outdated state queries.
* **Gas management:** Accurate gas limits must be set to ensure `lzReceive` and `lzCompose` execution succeeds across chains.
Detailed Chain-Specific Overviews[](#detailed-chain-specific-overviews "Direct link to Detailed Chain-Specific Overviews")
----------------------------------------------------------------------------------------------------------------------------
Below is a summary for each chain type with key impacts for LayerZero integrations and links to more documentation.
EVM Diff Checker
For a quick way to identify opcode differences between networks, check out the [**EVM Diff Checker**](https://www.evmdiff.com/)
. This tool is particularly useful if you're troubleshooting or optimizing across various EVM implementations.
### **Optimism (OP) Stack: EVM Equivalence**[](#optimism-op-stack-evm-equivalence "Direct link to optimism-op-stack-evm-equivalence")
OP Stack chains aim for out-of-the-box Ethereum compatibility. You can use standard Ethereum tools and wallets without modification.
**Examples:**
* Optimism, Base
**Key details for LayerZero:**
* **Toolchain & compilers:**
Use standard Ethereum tools and the regular Solidity compiler.
[Optimism Docs – Differences](https://docs.optimism.io/stack/differences)
* **Fee payment:**
Fees are paid in ETH via `msg.value` with no alternative fee token needed.
* **On-chain reads:**
`Block.number` and `block.timestamp` behave similarly to Ethereum, with a fixed ~2-second block time.
* **Further documentation:**
[Optimism Documentation](https://docs.optimism.io/)
### **Arbitrum Orbit: EVM Equivalence**[](#arbitrum-orbit-evm-equivalence "Direct link to arbitrum-orbit-evm-equivalence")
Arbitrum uses normal EVM bytecode (Arbitrum Nitro incorporates the Ethereum Yellow Paper spec), meaning you can compile with the same solc version you’d use on Ethereum mainnet.
**Examples:**
* Arbitrum One, Arbitrum Nova, ApeChain (Orbit)
**Key details for LayerZero:**
* **Toolchain & compilers:**
Standard Ethereum tools work; no special compiler is needed.
[Arbitrum Developer Portal](https://developer.arbitrum.io/)
* **Fee payment:**
On Arbitrum One/Nova, fees are paid in ETH (or bridged ArbETH). However, some Orbit chains may use a custom `ERC20` (e.g. APE on ApeChain).
* **On-chain reads:**
Arbitrum’s `block.number` may reflect L1’s block number, and its flexible sequencer-controlled `block.timestamp` can potentially drift by up to 24 hours in the past or 1 hour in the future. In the worst case scenario, this variability may cause lzRead to return historical or mismatched state. [Arbitrum Docs – Arbitrum vs Ethereum](https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/block-numbers-and-time)
* **Further documentation:**
[Arbitrum Developer Documentation](https://developer.arbitrum.io/)
### **Avalanche Subnet: EVM Equivalent**[](#avalanche-subnet-evm-equivalent "Direct link to avalanche-subnet-evm-equivalent")
Avalanche subnets that run the EVM (Subnet-EVM) allow you to use the same Ethereum development tools as expected. By default, Avalanche’s Subnet-EVM does not remove or alter EVM opcodes – it’s EVM-equivalent.
info
Avalanche subnets can have custom fee tokens and models. By default, when you create a subnet EVM, you specify the native token (it could be an existing ERC20 or a new token created as the native asset).
**Examples:**
* Avalanche, Dexalot, DeFi Kingdom
**Key details for LayerZero:**
* **Toolchain & compilers:**
Use standard Ethereum development tools with the subnet’s RPC and chain ID.
* **Fee payment:**
Fees are paid in the subnet’s native token (AVAX or a custom token). Make sure your `msg.value` fee delivery aligns with the chain’s requirements. You may need [OFTAlt](/v2/developers/evm/oft/oft-patterns-extensions#oft-alt)
if the Subnet requires a custom ERC20 token for fees.
* **On-chain reads:**
`block.number` and `block.timestamp` update more frequently (typically 1–2 seconds per block) compared to Ethereum. This faster cadence can affect lzRead if your contracts assume Ethereum-like intervals.
* **Further documentation:**
[Avalanche Subnets Docs](https://docs.avax.network/subnets)
### **zkSync Elastic Chains: EVM Compatible**[](#zksync-elastic-chains-evm-compatible "Direct link to zksync-elastic-chains-evm-compatible")
zkSync Era is a ZK-rollup that supports Solidity, but you should use zkSync’s provided tooling for the smoothest experience. Incorporate Matter Labs’ toolchain additions: use `zksolc` compiler, and the specialized Hardhat or Foundry integration for a frictionless dev experience.
**Examples:**
* zkSync Era, Abstract
**Key details for LayerZero:**
* **Toolchain & compilers:**
Use [zkSync’s Hardhat](https://docs.zksync.io/zksync-era/tooling/hardhat)
or [Foundry](https://docs.zksync.io/zksync-era/tooling/foundry/overview)
plugin with the `zksolc` compiler.
* **Fee payment:**
Fees are paid in `msg.value`, with no alternative fee token needed.
* **On-chain reads:**
Due to rollup batching, `block.number` and `block.timestamp` may jump in batches rather than update continuously. This requires careful handling in lzRead to ensure you query the intended state.
* **Further documentation:**
[zkSync Era Documentation](https://docs.zksync.io/)
### **SKALE: EVM Compatible**[](#skale-evm-compatible "Direct link to skale-evm-compatible")
SKALE is a multi-chain network where each chain is an EVM-compatible blockchain (often called an “Elastic Sidechain”). For deploying and interacting with contracts on a SKALE chain, you mostly use standard Ethereum tools – with a couple of caveats due to network specifics.
**Examples:**
* SKALE
**Key details for LayerZero:**
* **Toolchain & compilers:**
Standard Ethereum tools work, with configuration changes for SKALE’s RPC and chain ID.
[SKALE Network Differences](https://docs.skale.network/technology/differences)
* **Fee payment:**
SKALE uses a “free gas” model with a dummy token (sFUEL). However, LayerZero workers require an alternative ERC20 token to handle destination gas payments. The LayerZero Endpoint will expect fee delivery in this token rather than `msg.value`. For more information see [OFT Alt](/v2/developers/evm/oft/oft-patterns-extensions#oft-alt)
.
* **On-chain reads:**
`Block.number` and `block.timestamp` are generally reliable, but note that gas fees aren’t paid in ETH.
* **Further documentation:**
[SKALE Documentation](https://docs.skale.network/)
### **BTC L2 Chains: EVM Compatible**[](#btc-l2-chains-evm-compatible "Direct link to btc-l2-chains-evm-compatible")
Most BTC L2s are EVM‑compatible. You can generally use standard Ethereum development tools and can compile with standard solc.
**Examples:**
* GOAT, Rootstock, Bitlayer, Bouncebit, Citrea, and Corn
**Key details for LayerZero:**
* **Toolchain & compilers:**
Standard Ethereum tools work, but may vary from L2 to L2.
* **Fee payment:**
Fees are paid in RBTC on RSK or zBTC on GOAT. LayerZero endpoints must receive fees in the proper native token.
* **On-chain reads:**
`block.timestamp` and `block.number` may differ substantially from Ethereum (e.g., RSK’s ~30-second blocks). In GOAT, state finality depends on Bitcoin settlement; this could impact lzRead if using local chain data.
* **Further documentation:**
[Rootstock Documentation](https://dev.rootstock.io/)
| [GOAT Network Documentation](https://docs.goat.network/)
| [Bitlayer Documentation](https://docs.bitlayer.org/docs/Learn/Introduction/)
| [Corn Documentation](https://docs.usecorn.com/)
### **HyperEVM: EVM Equivalence**[](#hyperevm-evm-equivalence "Direct link to hyperevm-evm-equivalence")
**Key details for LayerZero:**
* **Toolchain & compilers:**
Use standard Ethereum tools (Hardhat, ethers.js) with HyperEVM’s RPC and chain ID.
[HyperLiquid Docs](https://hyperliquid.gitbook.io/hyperliquid-docs/)
* **Fee payment:**
Fees are paid in HYPE (HyperLiquid’s native token). Your LayerZero endpoints will expect msg.value in HYPE. Note the dual-block architecture may require higher gas limits for heavy transactions.
* **Contract Standards:** While the normal OApp/OFT/ONFT can be used out-of-the-box on the HyperEVM, you will want to deploy a custom HyperOFT to have automatic delivery to the Hyperliquid Spot. See the [HyperOFT README](/v2/developers/evm/evm-variants/)
for more information.
* **On-chain reads:**
While HyperEVM’s `block.timestamp` and `block.number` behave similarly to Ethereum’s, the dual-block design (small vs. big blocks) may introduce discrepancies—especially if a heavy transaction is scheduled in a “big” block.
* **Further documentation:**
[HyperLiquid HyperEVM Documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/)
Below are separate sections for Hedera and Tron, with additional detail on Hedera’s unique requirements. In Hedera’s case, many DeFi protocols use the Hedera Token Service (HTS) rather than a standard ERC20, which can necessitate custom contract changes when integrating with LayerZero.
### **Hedera: EVM Compatible**[](#hedera-evm-compatible "Direct link to hedera-evm-compatible")
Hedera is a public distributed ledger built on Hashgraph consensus that supports high-speed, fair, and secure transactions while also offering an EVM-compatible environment via the Hedera EVM.
**Key details for LayerZero:**
* **Toolchain & compilers:**
Hedera supports EVM-compatible smart contracts through the Hedera EVM. However, developers may need to use the Hedera Web3 SDK and adjust configurations to work with Hedera’s network.
[Hedera EVM Docs](https://hedera.com/technology/hedera-evm)
* **Fee payment:**
Fees are paid in HBAR, Hedera’s native token. Additionally, many DeFi applications on Hedera rely on the Hedera Token Service (HTS) for token issuance and transfers instead of standard ERC20 tokens. This means that a standard ERC20 OFT may not work as expected on Hedera.
* **On-chain reads:**
Not available.
* **Further documentation:**
[Hedera EVM Documentation](https://hedera.com/technology/hedera-evm)
| [Hedera Token Service Overview](https://hedera.com/technology/token-service)
### **Tron: EVM Compatible**[](#tron-evm-compatible "Direct link to tron-evm-compatible")
Tron is a blockchain platform focused on decentralizing the internet and digital entertainment, utilizing its native TRX token and offering an EVM-compatible environment through its Tron Virtual Machine (TVM).
**Key details for LayerZero:**
* **Toolchain & compilers:**
Tron supports an EVM-like environment (via Tron Virtual Machine, TVM), but many projects rely on Tron-specific libraries such as TronWeb. While you can deploy standard Solidity contracts, some adaptations may be needed to interface with Tron’s unique APIs.
[Tron Developer Hub](https://developers.tron.network/)
* **Fee payment:**
Fees are paid in TRX, Tron's native token. The Tron ecosystem uses standards such as TRC20 (similar to ERC20) for token contracts, so LayerZero integrations that rely on ERC20 conventions generally translate well.
* **On-chain reads:**
Not available.
* **Further documentation:**
[Tron Developer Documentation](https://developers.tron.network/)
Conclusion[](#conclusion "Direct link to Conclusion")
-------------------------------------------------------
**EVM Equivalent chains** generally allow you to deploy and operate with minimal changes. However, be sure to account for subtle differences that may impact your contract's logic (e.g., different behaviour in `block.number` or `block.timestamp`).
**EVM Compatible chains** may require adjustments in deployment, fee handling, and gas estimation.
**Developer checklist:**
* **Network configuration:** Update your deployment scripts with the correct RPC endpoints, chain IDs, and native token details.
* **Fee handling:** Verify that your payable functions deliver fees in the correct native token as required by the chain.
* **Gas estimation:** Test gas limits on your target chain to ensure that calls execute successfully.
* **On-chain data:** Validate that your logic correctly executes as expected, accounting for any drift or inconsistencies.
* **Toolchain adjustments:** Use chain-specific SDKs or compilers as needed (e.g., zkSync’s Hardhat plugin) to guarantee compatibility.
By understanding these nuances and consulting the chain-specific documentation linked above, you can adapt your LayerZero cross-chain messaging and token integrations to work reliably across all supported networks.
* [Concept: Compatibility vs. Equivalence](#concept-compatibility-vs-equivalence)
* [Detailed Chain-Specific Overviews](#detailed-chain-specific-overviews)
* [**Optimism (OP) Stack: EVM Equivalence**](#optimism-op-stack-evm-equivalence)
* [**Arbitrum Orbit: EVM Equivalence**](#arbitrum-orbit-evm-equivalence)
* [**Avalanche Subnet: EVM Equivalent**](#avalanche-subnet-evm-equivalent)
* [**zkSync Elastic Chains: EVM Compatible**](#zksync-elastic-chains-evm-compatible)
* [**SKALE: EVM Compatible**](#skale-evm-compatible)
* [**BTC L2 Chains: EVM Compatible**](#btc-l2-chains-evm-compatible)
* [**HyperEVM: EVM Equivalence**](#hyperevm-evm-equivalence)
* [**Hedera: EVM Compatible**](#hedera-evm-compatible)
* [**Tron: EVM Compatible**](#tron-evm-compatible)
* [Conclusion](#conclusion)
---
# LayerZero V2 Integration Checklist | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
On this page
The checklist below is designed to help prepare a project that integrates LayerZero V2 for an external audit or Mainnet deployment.
### Use the Latest Version of LayerZero Packages[](#use-the-latest-version-of-layerzero-packages "Direct link to Use the Latest Version of LayerZero Packages")
Always use the latest version of LayerZero packages. Avoid copying contracts directly from LayerZero repositories. You can find the latest packages on each contract's home page.
### Token Bridging Guidelines[](#token-bridging-guidelines "Direct link to Token Bridging Guidelines")
For new tokens, inherit from `OFT` or `ONFT`.
For existing tokens, use `OFTAdapter` or `ONFTAdapter`.
For non-EVM tokens, select the correct VM from the navbar and see the equivalent sections.
danger
**There can only be one OFT Adapter used in an OFT deployment.** Multiple OFT Adapters break omnichain unified liquidity by effectively creating token pools.
If you create OFT Adapters on multiple chains, you have no way to guarantee finality for token transfers due to the fact that the source chain has no knowledge of the destination pool's supply (or lack of supply). This can create race conditions where if a sent amount exceeds the available supply on the destination chain, those sent tokens will be permanently lost.
### Avoid Hardcoding LayerZero Endpoint IDs[](#avoid-hardcoding-layerzero-endpoint-ids "Direct link to Avoid Hardcoding LayerZero Endpoint IDs")
Use admin-restricted setters to configure endpoint IDs instead of hardcoding them.
### Call `setPeer` on every OApp Deployment[](#call-setpeer-on-every-oapp-deployment "Direct link to call-setpeer-on-every-oapp-deployment")
To ensure successful one-way messages between chains, it's essential to establish peer configurations on both the source and destination chains. Both chains' OApps perform peer verification before executing the message on the destination chain, ensuring secure and reliable cross-chain communication.
// The real endpoint ids will vary per chain, and can be found under "Supported Chains"uint32 aEid = 1;uint32 bEid = 2;MyOApp aOApp;MyOApp bOApp;// Call on both sides per pathwayaOApp.setPeer(bEid, addressToBytes32(address(bOApp)));bOApp.setPeer(aEid, addressToBytes32(address(aOApp)));
If using a custom OApp implementation that is not a child contract of the LayerZero OApp Standard, implement the receive side check for initializing the OApp's pathway. The Receive Library will call `allowInitializePath` when a message is received, and if true, it will initialize the pathway for message passing.
// LayerZero V2 OAppReceiver.sol (implements ILayerZeroReceiver.sol)/** * @notice Checks if the path initialization is allowed based on the provided origin. * @param origin The origin information containing the source endpoint and sender address. * @return Whether the path has been initialized. * * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received. * @dev This defaults to assuming if a peer has been set, its initialized. * Can be overridden by the OApp if there is other logic to determine this. */function allowInitializePath(Origin calldata origin) public view virtual returns (bool) { return peers[origin.srcEid] == origin.sender;}
### Set Security and Executor Configurations[](#set-security-and-executor-configurations "Direct link to Set Security and Executor Configurations")
You must configure Decentralized Validator Networks (DVNs) manually on all chain pathways for your OApp. LayerZero maintains a neutral stance and does not presuppose any security assumptions on behalf of deployed OApps. This approach requires you to define and implement security considerations that align with your application’s requirements.
EndpointV2.setSendLibrary(aOApp, bEid, newLib)EndpointV2.setReceiveLibrary(aOApp, bEid, newLib, gracePeriod)EndpointV2.setReceiveLibraryTimeout(aOApp, bEid, lib, gracePeriod)EndpointV2.setConfig(aOApp, sendLibrary, sendConfig)EndpointV2.setConfig(aOApp, receiveLibrary, receiveConfig)EndpointV2.setDelegate(delegate)
Follow the [Protocol Configuration](/v2/developers/evm/configuration/dvn-executor-config)
documentation to configure DVNs for each chain pathway.
caution
**If no configuration is set, the OApp will fallback to the default settings set by LayerZero Labs.**
/// @notice The Send Library is the Oapp specified library that will be used to send the message to the destination/// endpoint. If the Oapp does not specify a Send Library, the default Send Library will be used./// @dev If the Oapp does not have a selected Send Library, this function will resolve to the default library/// configured by LayerZero/// @return lib address of the Send Library/// @param _sender The address of the Oapp that is sending the message/// @param _dstEid The destination endpoint idfunction getSendLibrary(address _sender, uint32 _dstEid) public view returns (address lib) { lib = sendLibrary[_sender][_dstEid]; if (lib == DEFAULT_LIB) { lib = defaultSendLibrary[_dstEid]; if (lib == address(0x0)) revert Errors.LZ_DefaultSendLibUnavailable(); }}
### Implement Enforced Options[](#implement-enforced-options "Direct link to Implement Enforced Options")
Implement and set `enforcedOptions` to ensure users pay a predetermined amount of gas for delivery on the destination transaction. This setup guarantees that messages sent from a source have sufficient gas to be executed on the destination chain.
Test the gas required for execution on the destination chain to prevent failures due to insufficient gas.
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;import { OApp, Origin, MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";import { OAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";contract MyOApp is OApp, OAppOptionsType3 { /// @notice Message types that are used to identify the various OApp operations. /// @dev These values are used in things like combineOptions() in OAppOptionsType3. uint16 public constant SEND = 1; constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {} // ... contract continues}
EnforcedOptionParam[] memory aEnforcedOptions = new EnforcedOptionParam[](1);// Send gas for lzReceive (A -> B).aEnforcedOptions[0] = EnforcedOptionParam({eid: bEid, msgType: SEND, options: OptionsBuilder.newOptions().addExecutorLzReceiveOption(50000, 0)}); // gas limit, msg.valueaOApp.setEnforcedOptions(aEnforcedOptions);
### Avoid Redundant `require` Statements[](#avoid-redundant-require-statements "Direct link to avoid-redundant-require-statements")
Do not add `require` statements that repeat checks in parent contracts, such as those in `OAppReceiver.lzReceive`.
/** * @dev Entry point for receiving messages or packets from the endpoint. * @param _origin The origin information containing the source endpoint and sender address. * - srcEid: The source chain endpoint ID. * - sender: The sender address on the src chain. * - nonce: The nonce of the message. * @param _guid The unique identifier for the received LayerZero message. * @param _message The payload of the received message. * @param _executor The address of the executor for the received message. * @param _extraData Additional arbitrary data provided by the corresponding executor. * * @dev Entry point for receiving msg/packet from the LayerZero endpoint. */function lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) public payable virtual { // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp. if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender); // Ensure that the sender matches the expected peer for the source endpoint. if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender); // Call the internal OApp implementation of lzReceive. _lzReceive(_origin, _guid, _message, _executor, _extraData);}
### Add `require` Statements in `lzCompose`[](#add-require-statements-in-lzcompose "Direct link to add-require-statements-in-lzcompose")
Unlike child contracts with the `OAppReceiver.lzReceive` method, the `ILayerZeroComposer.lzCompose` does not have built-in checks.
Add these checks for the source `oApp` and `endpoint` before any custom state change logic:
// SPDX-License-Identifier: MITpragma solidity ^0.8.22;import { ILayerZeroComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroComposer.sol";/// @title ComposedReceiver/// @dev A contract demonstrating the minimum ILayerZeroComposer interface necessary to receive composed messages via LayerZero.contract ComposedReceiver is ILayerZeroComposer { /// @notice Stores the last received message. string public data = "Nothing received yet"; /// @notice Store LayerZero addresses. address public immutable endpoint; address public immutable oApp; /// @notice Constructs the contract. /// @dev Initializes the contract. /// @param _endpoint LayerZero Endpoint address /// @param _oApp The address of the OApp that is sending the composed message. constructor(address _endpoint, address _oApp) { endpoint = _endpoint; oApp = _oApp; } /// @notice Handles incoming composed messages from LayerZero. /// @dev Decodes the message payload and updates the state. /// @param _oApp The address of the originating OApp. /// @param /*_guid*/ The globally unique identifier of the message. /// @param _message The encoded message content. function lzCompose( address _oApp, bytes32 /*_guid*/, bytes calldata _message, address, bytes calldata ) external payable override { // Perform checks to make sure composed message comes from correct OApp. require(_oApp == oApp, "!oApp"); require(msg.sender == endpoint, "!endpoint"); // Decode the payload to get the message (string memory message, ) = abi.decode(_message, (string, address)); data = message; }}
### Enforce `msg.value` in `_lzReceive` and `lzCompose`[](#enforce-msgvalue-in-_lzreceive-and-lzcompose "Direct link to enforce-msgvalue-in-_lzreceive-and-lzcompose")
If you specify in the executor `_options` a certain `msg.value`, it is not guaranteed that the message will be executed with these exact parameters because any caller can execute a verified message.
In certain scenarios depending on the encoded message data, this can result in a successful message being delivered, but with a state change different than intended.
Encode the `msg.value` inside the message on the sending chain, and then decode it in the `lzReceive` or `lzCompose` and compare with the actual `msg.value`.
// LayerZero V2 OmniCounter.sol examplefunction value(bytes calldata _message) internal pure returns (uint256) { return uint256(bytes32(_message[VALUE_OFFSET:]));}function _lzReceive( Origin calldata _origin, bytes32 _guid, bytes calldata _message, address /*_executor*/, bytes calldata /*_extraData*/) internal override { _acceptNonce(_origin.srcEid, _origin.sender, _origin.nonce); uint8 messageType = _message.msgType(); if (messageType == MsgCodec.VANILLA_TYPE) { //////////////////////////////// IMPORTANT ////////////////////////////////// /// if you request for msg.value in the options, you should also encode it /// into your message and check the value received at destination (example below). /// if not, the executor could potentially provide less msg.value than you requested /// leading to unintended behavior. Another option is to assert the executor to be /// one that you trust. ///////////////////////////////////////////////////////////////////////////// require(msg.value >= _message.value(), "OmniCounter: insufficient value"); count++; }}
This requires encoding the `msg.value` as part of the `_message` on the source chain, and extracting it from the encoded message.
### Implement Instant Finality Guarantee (IFG)[](#implement-instant-finality-guarantee-ifg "Direct link to Implement Instant Finality Guarantee (IFG)")
Design your OApp with IFG to ensure that transactions accepted at the source will be accepted at the destination, minimizing state damage in case of message failure.
### Perform One Action Per Message[](#perform-one-action-per-message "Direct link to Perform One Action Per Message")
Minimize the impact of potential message failure by performing only one action per message.
### Message Encoding[](#message-encoding "Direct link to Message Encoding")
Use type-safe bytes codec for message encoding. Use custom codecs only if necessary and if your app requires deep optimization. For example, see the `OFTMsgCodec.sol`:
// SPDX-License-Identifier: MITpragma solidity ^0.8.20;library OFTMsgCodec { // Offset constants for encoding and decoding OFT messages uint8 private constant SEND_TO_OFFSET = 32; uint8 private constant SEND_AMOUNT_SD_OFFSET = 40; /** * @dev Encodes an OFT LayerZero message. * @param _sendTo The recipient address. * @param _amountShared The amount in shared decimals. * @param _composeMsg The composed message. * @return _msg The encoded message. * @return hasCompose A boolean indicating whether the message has a composed payload. */ function encode( bytes32 _sendTo, uint64 _amountShared, bytes memory _composeMsg ) internal view returns (bytes memory _msg, bool hasCompose) { hasCompose = _composeMsg.length > 0; // @dev Remote chains will want to know the composed function caller ie. msg.sender on the src. _msg = hasCompose ? abi.encodePacked(_sendTo, _amountShared, addressToBytes32(msg.sender), _composeMsg) : abi.encodePacked(_sendTo, _amountShared); } /** * @dev Checks if the OFT message is composed. * @param _msg The OFT message. * @return A boolean indicating whether the message is composed. */ function isComposed(bytes calldata _msg) internal pure returns (bool) { return _msg.length > SEND_AMOUNT_SD_OFFSET; } /** * @dev Retrieves the recipient address from the OFT message. * @param _msg The OFT message. * @return The recipient address. */ function sendTo(bytes calldata _msg) internal pure returns (bytes32) { return bytes32(_msg[:SEND_TO_OFFSET]); } /** * @dev Retrieves the amount in shared decimals from the OFT message. * @param _msg The OFT message. * @return The amount in shared decimals. */ function amountSD(bytes calldata _msg) internal pure returns (uint64) { return uint64(bytes8(_msg[SEND_TO_OFFSET:SEND_AMOUNT_SD_OFFSET])); } /** * @dev Retrieves the composed message from the OFT message. * @param _msg The OFT message. * @return The composed message. */ function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) { return _msg[SEND_AMOUNT_SD_OFFSET:]; } /** * @dev Converts an address to bytes32. * @param _addr The address to convert. * @return The bytes32 representation of the address. */ function addressToBytes32(address _addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(_addr))); } /** * @dev Converts bytes32 to an address. * @param _b The bytes32 value to convert. * @return The address representation of bytes32. */ function bytes32ToAddress(bytes32 _b) internal pure returns (address) { return address(uint160(uint256(_b))); }}
* [Use the Latest Version of LayerZero Packages](#use-the-latest-version-of-layerzero-packages)
* [Token Bridging Guidelines](#token-bridging-guidelines)
* [Avoid Hardcoding LayerZero Endpoint IDs](#avoid-hardcoding-layerzero-endpoint-ids)
* [Call `setPeer` on every OApp Deployment](#call-setpeer-on-every-oapp-deployment)
* [Set Security and Executor Configurations](#set-security-and-executor-configurations)
* [Implement Enforced Options](#implement-enforced-options)
* [Avoid Redundant `require` Statements](#avoid-redundant-require-statements)
* [Add `require` Statements in `lzCompose`](#add-require-statements-in-lzcompose)
* [Enforce `msg.value` in `_lzReceive` and `lzCompose`](#enforce-msgvalue-in-_lzreceive-and-lzcompose)
* [Implement Instant Finality Guarantee (IFG)](#implement-instant-finality-guarantee-ifg)
* [Perform One Action Per Message](#perform-one-action-per-message)
* [Message Encoding](#message-encoding)
---
# LayerZero Experimental Simple Config Generator | LayerZero
[Skip to main content](#__docusaurus_skipToContent_fallback)
LayerZero Read is live! Learn how to use LayerZero to read and compute external blockchain state by visiting [lzRead Overview](/v2/developers/evm/lzread/overview)
.
Version: Endpoint V2 Docs
We have developed a new simple config generator which makes use of the `@layerzerolabs/metadata-tools` package. It is currently in the experimental stage. It allows for a more simplified Layerzero config file.
Here's how to use it:
1. Install metadata-tools: `pnpm add -D @layerzerolabs/metadata-tools`
2. Create a new [LZ config](/v2/concepts/glossary#lz-config)
file named `layerzero.config.ts` (or edit your existing one) in the project root and use the examples below as a starting point:
* Example: EVM chains only
* Example: EVM chain and Solana
import {ExecutorOptionType} from '@layerzerolabs/lz-v2-utilities';import {OAppEnforcedOption, OmniPointHardhat} from '@layerzerolabs/toolbox-hardhat';import {EndpointId} from '@layerzerolabs/lz-definitions';import {generateConnectionsConfig} from '@layerzerolabs/metadata-tools';const avalancheContract: OmniPointHardhat = { eid: EndpointId.AVALANCHE_V2_TESTNET, contractName: 'MyOFT',};const polygonContract: OmniPointHardhat = { eid: EndpointId.AMOY_V2_TESTNET, contractName: 'MyOFT',};const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ { msgType: 1, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 80000, value: 0, }, { msgType: 2, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 80000, value: 0, }, { msgType: 2, optionType: ExecutorOptionType.COMPOSE, index: 0, gas: 80000, value: 0, },];export default async function () { // note: pathways declared here are automatically bidirectional // if you declare A,B there's no need to declare B,A const connections = await generateConnectionsConfig([ [ avalancheContract, // Chain A contract polygonContract, // Chain B contract [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] [1, 1], // [A to B confirmations, B to A confirmations] [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions ], ]); return { contracts: [{contract: avalancheContract}, {contract: polygonContract}], connections, };}
import {ExecutorOptionType} from '@layerzerolabs/lz-v2-utilities';import {OAppEnforcedOption, OmniPointHardhat} from '@layerzerolabs/toolbox-hardhat';import {EndpointId} from '@layerzerolabs/lz-definitions';import {generateConnectionsConfig} from '@layerzerolabs/metadata-tools';export const avalancheContract: OmniPointHardhat = { eid: EndpointId.AVALANCHE_V2_TESTNET, contractName: 'MyOFT',};export const solanaContract: OmniPointHardhat = { eid: EndpointId.SOLANA_V2_TESTNET, address: 'HBTWw2VKNLuDBjg9e5dArxo5axJRX8csCEBcCo3CFdAy', // your OFT Store address};const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ { msgType: 1, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 80000, value: 0, }, { msgType: 2, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 80000, value: 0, }, { msgType: 2, optionType: ExecutorOptionType.COMPOSE, index: 0, gas: 80000, value: 0, },];const SOLANA_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ { msgType: 1, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 200000, value: 2500000, }, { msgType: 2, optionType: ExecutorOptionType.LZ_RECEIVE, gas: 200000, value: 2500000, }, { // Solana options use (gas == compute units, value == lamports) msgType: 2, optionType: ExecutorOptionType.COMPOSE, index: 0, gas: 0, value: 0, },];export default async function () { // note: pathways declared here are automatically bidirectional // if you declare A,B there's no need to declare B,A const connections = await generateConnectionsConfig([ [ avalancheContract, // Chain A contract solanaContract, // Chain B contract [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] [1, 1], // [A to B confirmations, B to A confirmations] [SOLANA_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions ], ]); return { contracts: [{contract: avalancheContract}, {contract: solanaContract}], connections, };}
* Note that only the Solana contract object requires `address` to be specified. Do not specify `address` for non-Solana contract objects.
* The above examples contains a minimal mesh with only one pathway (two chains) for demonstration purposes. You are able to add as many pathways as you need into the `connections` param, via `generateConnectionsConfig`.
3. If your pathways include Solana, run the Solana init config command:
npx hardhat lz:oft:solana:init-config --oapp-config layerzero.config.ts --solana-eid
4. Run the wire command:
* EVM Chains only
* EVM Chain and Solana
npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
The wire command would process all the transactions required to connect all pathways specified in the LZ Config file. You need to only run this once regardless of how many pathways there are. If you change anything in the LZ Config file, then it should be run again.
npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --solana-eid
---