Create LZ OApp Quickstart
LayerZero provides create-lz-oapp
, a CLI Toolkit designed to streamline the process of building, testing, deploying, and configuring omnichain applications (OApps).
create-lz-oapp
is an npx package that creates a Node.js
project with both the Hardhat and Foundry development frameworks installed, allowing developers to build from any LayerZero Contract Standards.
Start a simple, barebones dApp, to a feature-rich cross-chain platform, in <4 minutes!
Setting Up Your LayerZero Project
Run the following from your command line to bootstrap your initial LayerZero project:
npx create-lz-oapp@latest
Following this, a simple project creation wizard will guide you through setting up a project template. Choose from a variety of examples to match your project needs:
- OApp
- OFT
- OFT Adapter
- ONFT
The generic message passing standard for creating Omnichain Applications (OApps):
✔ Where do you want to start your project? … ./my-lz-oapp
✔ Which example would you like to use as a starting point? › OApp
✔ What package manager would you like to use in your project? › pnpm
An ERC20 extended with core bridging logic from OApp, creating an Omnichain Fungible Token (OFT):
✔ Where do you want to start your project? … ./my-lz-oapp
✔ Which example would you like to use as a starting point? › OFT
✔ What package manager would you like to use in your project? › pnpm
Variant of OFT for adapting deployed ERC20 tokens as Omnichain Fungible Tokens, creating an OFT Adapter:
✔ Where do you want to start your project? … ./my-lz-oapp
✔ Which example would you like to use as a starting point? › OFT Adapter
✔ What package manager would you like to use in your project? › pnpm
An ERC721 extended with core bridging logic from OApp, creating an Omnichain Non-Fungible Token (ONFT):
✔ Where do you want to start your project? … ./my-lz-oapp
✔ Which example would you like to use as a starting point? › ONFT
✔ What package manager would you like to use in your project? › pnpm
This will initialize a repo with example contracts, cross-chain unit tests for sample contracts, custom LayerZero configuration files, deployment scripts, and more.
Sample Project Overview
The initialized project has the following structure:
📁 contracts // Your contracts folder
📁 deploy // hardhat-deploy scripts
📁 test // Unit tests, both Hardhat and Foundry enabled
foundry.toml // Normal foundry.toml for remappings and project configuration
hardhat.config.ts // Standard hardhat.config.ts, with layerzero endpoint mappings
layerzero.config.ts // Special LayerZero config file (more on this later)
If you need to change these paths, refer to the hardhat paths configuration documentation.
To see all of the available LayerZero hardhat tasks, you can run:
npx hardhat
In addition to default Hardhat tasks, you will see a list of available LayerZero tasks that you can use to set up, configure, and wire your LayerZero contracts:
// ... Hardhat defaults
lz:deploy Deploy LayerZero contracts
lz:errors:decode Decodes custom error data based
lz:errors:list List all custom errors from your project
lz:export:deployments:typescript Export deployments as TypeScript files
lz:healthcheck:validate:rpcs Validate RPC URLs in hardhat.config.ts. RPCs are only considered valid if they use the https or wss protocol and respond within the specified timeout.
lz:healthcheck:validate:safe-configs Validate safe configs in hardhat.config.ts
lz:oapp-read: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
lz:oapp-read:config:get:channel Outputs the Default OApp Read Channel Config. Each config contains read channels, default libraries, and Uln configs
lz:oapp-read:config:init Initialize an OApp configuration file
lz:oapp-read:wire Wire LayerZero OApp
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
lz:oapp:config:get:default Outputs the Default OApp Config. Each config contains Send & Receive Libraries, Send Uln & Executor Configs, and Receive Executor Configs
lz:oapp:config:get:executor Outputs the Executors destination configurations including the native max cap amount
lz:oapp:config:init Initialize an OApp configuration file
lz:oapp:enforced-opts:get Outputs OApp enforced options
lz:oapp:peers:get Outputs OApp peer connections
lz:oapp:wire Wire LayerZero OApp
lz:ownable:transfer-ownership Transfer ownable contract ownership
lz:read:resolve-command Resolve an LzRead command
Adding External Networks
You will need to modify project configurations to add any new networks you plan to deploy on.
Follow the standard Hardhat process for adding external networks to your hardhat.config.ts
, with the only additional requirement being that a LayerZero Endpoint has been deployed to the chain.
- Deploying on Testnets
- Deploying on Mainnets
The default hardhat.config.ts
file has three networks configured but we can start with just two, for simple setup and in case you don't have enough test tokens on all networks.
// hardhat.config.ts
networks: {
'sepolia-testnet': {
// You can find specific chain constants in @layerzerolabs/lz-definitions package
eid: EndpointId.SEPOLIA_V2_TESTNET,
url: process.env.RPC_URL_SEPOLIA || 'https://ethereum-sepolia-rpc.publicnode.com',
accounts,
},
'avalanche-testnet': {
// You can find specific chain constants in @layerzerolabs/lz-definitions package
eid: EndpointId.AVALANCHE_V2_TESTNET,
url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji',
accounts,
},
hardhat: {
// Need this for testing because TestHelperOz5.sol is exceeding the compiled contract size limit
allowUnlimitedContractSize: true,
},
},
Note the different constants for EndpointId
for Mainnets vs Testnets, and the different RPC URLs for each network.
// hardhat.config.ts
networks: {
ethereum: {
// You can find specific chain constants in @layerzerolabs/lz-definitions package
eid: EndpointId.ETHEREUM_V2_MAINNET,
url: process.env.RPC_URL_ETHEREUM || 'https://eth.llamarpc.com',
accounts,
},
arbitrum: {
// You can find specific chain constants in @layerzerolabs/lz-definitions package
eid: EndpointId.ARBITRUM_V2_MAINNET,
url: process.env.RPC_URL_ARBITRUM || 'https://arbitrum.llamarpc.com',
accounts,
},
},
Review the list of LayerZero Endpoint Addresses to see which networks you can set.
Testing LayerZero Contracts
To begin testing your sample LayerZero contracts locally, you can use Hardhat and / or Foundry unit tests, with specific local testing utilities for each:
Hardhat:
EndpointV2Mock.sol
, a mock LayerZero V2 Endpoint contract, meant for local testing of LayerZero message passing in Hardhat.Foundry:
TestHelper.sol
, an extensive LayerZero V2 testing framework, designed for simulating LayerZero state changes and contract interactions in Foundry.
To run your unit tests for both Hardhat and Foundry, run the test command using your package manager:
pnpm test
All tests should pass successfully.
You can learn more in Testing Contracts.
Deploying LayerZero Contracts
To deploy your LayerZero contracts, run this command and follow the prompts:
npx hardhat lz:deploy
The script will use the network names defined in your hardhat.config.ts
and the contract defined in your layerzero.config.ts
. If you haven't changed
See Deploying Contracts to learn more.
Initializing LayerZero Configs
To setup an initial layerzero.config.ts
file, run:
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, run this command and follow the prompts:
npx hardhat lz:oapp:config:init --contract-name MyOApp --oapp-config testnet.layerzero.config.ts
This will create a new config file with the correct type interface:
📁 contracts
📁 deploy
📁 test
foundry.toml
hardhat.config.ts
layerzero.config.ts
testnet.layerzero.config.ts // ← New config file
To show the default configuration for all possible network pathways from your hardhat.config.ts
in your terminal, run:
npx hardhat lz:oapp:config:get:default
Configuring LayerZero Contracts
LayerZero contracts have unique configurations on a per pathway basis (i.e., from A to B
has different properties than from B to A
).
To configure your LayerZero contracts, simply modify the config
of the pathway in your testnet.layerzero.config.ts
(or layerzero.config.ts
, if deploying on Mainnet).
Once you have configured your contract pathways, you can wire them together using:
npx hardhat lz:oapp:wire --oapp-config testnet.layerzero.config.ts
This script will check all the configurations for each pathway, ask you if you would like to preview the transactions, show the transaction details before execution, and execute the transactions when you confirm.
This is what the script does behind the scenes:
For each pathway in your config file, this task will call the following:
fromContract.OApp.setPeer
fromContract.EndpointV2.setSendLibrary
fromContract.EndpointV2.setReceiveLibrary
fromContract.EndpointV2.setConfig(OApp, sendLibrary, sendConfig)
fromContract.EndpointV2.setConfig(OApp, receiveLibrary, receiveConfig)
If you have manually added enforcedOptions
and receiveLibraryTimeoutConfig
(you can see more options in Configuring Contracts), the following will also be called for each contract instance, applying the custom configurations added:
fromContract.OApp.setEnforcedOptions
fromContract.EndpointV2.setReceiveLibraryTimeout
This means that the pathway configurations should mirror, for example:
// connections[] in layerzero.config.ts
{
from: ethereumContract,
to: arbitrumContract,
// The config below is SET on Ethereum
config: {
// ...
sendConfig: {
ulnConfig: {
confirmations: 15, // Block confirmations to wait for finality
requiredDVNs: [ // DVNs to pay to verify
'0x589dEDbD617e0CBcB916A9223F4d1300c294236b', // LayerZero DVN
'0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc', // Google Cloud DVN
],
optionalDVNs: [],
optionalDVNThreshold: 0,
},
},
},
},
{
from: arbitrumContract,
to: ethereumContract,
// The config below is SET on Arbitrum
config: {
// ...
receiveConfig: {
ulnConfig: {
confirmations: 20, // Enforces DVNs waited for finality
requiredDVNs: [ // Enforces specific DVNs have verified
'0x589dEDbD617e0CBcB916A9223F4d1300c294236b', // LayerZero DVN
'0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc', // Google Cloud DVN
],
optionalDVNs: [],
optionalDVNThreshold: 0,
},
},
},
},
Remember to apply the opposite direction if the pathway is bi-directional (i.e., adding a sendConfig
to from: arbitrumContract
, and a receiveConfig
to from: ethereumContract
).
For a detailed overview of all possible configuration commands, see Configuring Contracts.
Debugging Contract Errors
To list all the custom errors defined in the LayerZero protocol and your project, run:
npx hardhat lz:errors:list
To help decode unknown error signatures when testing, run:
npx hardhat lz:errors:decode <error selector>