Template project for a cross-chain token (OFT) powered by the LayerZero protocol. This example's config involves EVM chains, but the same OFT can be extended to involve other VM chains such as Solana, Aptos and Hyperliquid.
- Prerequisite Knowledge
- Requirements
- Scaffold this example
- Helper Tasks
- Setup
- Build
- Deploy
- Simple Workers (For Testnets Without Default Workers)
- Enable Messaging
- Sending OFTs
- Next Steps
- Production Deployment Checklist
- Appendix
Node.js->=18.16.0pnpm(recommended) - or another package manager of your choice (npm, yarn)forge(optional) ->=0.2.0for testing, and if not using Hardhat for compilation
Create your local copy of this example:
pnpm dlx create-lz-oapp@latest --example oftSpecify the directory, select OFT and proceed with the installation.
Note that create-lz-oapp will also automatically run the dependencies install step for you.
Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the LayerZero Hardhat Helper Tasks section. All commands can be run at the project root.
-
Copy
.env.exampleinto a new.env -
Set up your deployer address/account via the
.env-
You can specify either
MNEMONICorPRIVATE_KEY:MNEMONIC="test test test test test test test test test test test junk" or... PRIVATE_KEY="0xabc...def"
-
-
Fund this deployer address/account with the native tokens of the chains you want to deploy to. This example by default will deploy to the following chains' testnets: Base and Arbitrum.
This project supports both hardhat and forge compilation. By default, the compile command will execute both:
pnpm compileIf you prefer one over the other, you can use the tooling-specific commands:
pnpm compile:forge
pnpm compile:hardhatTo deploy the OFT contracts to your desired blockchains, run the following command:
pnpm hardhat lz:deploy --tags MyOFTMockβΉοΈ MyOFTMock will be used as it provides a public mint function which we require for testing
Select all the chains you want to deploy the OFT to.
β οΈ Development Only: Simple Workers are mock implementations for testing on testnets that lack DVNs and Executors. They should NEVER be used in production as they provide no security or service guarantees.
Some LayerZero testnets have default configurations, but no working DVNs (Decentralized Verifier Networks) or Executors. In these cases, you need to deploy and configure Simple Workers to enable message verification and execution.
Simple Workers consist of:
- SimpleDVNMock: A minimal DVN that allows manual message verification
- SimpleExecutorMock: A mock executor that charges zero fees and enables manual message execution
Deploy the Simple Workers on all chains where you need them:
# Deploy SimpleDVNMock
pnpm hardhat lz:deploy --tags SimpleDVNMock
# Deploy SimpleExecutorMock
pnpm hardhat lz:deploy --tags SimpleExecutorMockNote: If you are NOT using simple workers then use
layerzero.config.tsand you can skip this step
You can now use custom DVNs and Executors with the standard lz:oapp:wire command by adding them to your metadata configuration.
-
Get your deployed addresses from the deployment files:
- SimpleDVNMock:
./deployments/<network-name>/SimpleDVNMock.json - SimpleExecutorMock:
./deployments/<network-name>/SimpleExecutorMock.json
- SimpleDVNMock:
-
Update your
layerzero.simple-worker.config.tsto include your deployed Simple Workers:- SECTION 1: Add your contract definitions with the correct endpoint IDs
- SECTION 4: Add your Simple Worker addresses:
// In layerzero.simple-worker.config.ts, SECTION 4: CUSTOM EXECUTOR AND DVN ADDRESSES
const customExecutorsByEid: Record<number, { address: string }> = {
[EndpointId.BASESEP_V2_TESTNET]: { address: "0x..." }, // From deployments/base-sepolia/SimpleExecutorMock.json
[EndpointId.ARBSEP_V2_TESTNET]: { address: "0x..." }, // From deployments/arbitrum-sepolia/SimpleExecutorMock.json
// Add for each chain where you deployed SimpleExecutorMock
};
const customDVNsByEid: Record<number, { address: string }> = {
[EndpointId.BASESEP_V2_TESTNET]: { address: "0x..." }, // From deployments/base-sepolia/SimpleDVNMock.json
[EndpointId.ARBSEP_V2_TESTNET]: { address: "0x..." }, // From deployments/arbitrum-sepolia/SimpleDVNMock.json
// Add for each chain where you deployed SimpleDVNMock
};- Use them in your pathways (SECTION 5) by their canonical names:
// In layerzero.simple-worker.config.ts, SECTION 5: PATHWAY CONFIGURATION
const pathways: TwoWayConfig[] = [
[
sourceContract,
destContract,
[["SimpleDVNMock"], []], // Use the DVN by name
[1, 1],
[EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS],
"SimpleExecutorMock", // Use the executor by name
],
];- Wire normally using the custom configuration:
pnpm hardhat lz:oapp:wire --oapp-config layerzero.simple-worker.config.tsThis command will automatically:
- Detect pathways without DVN configurations in your LayerZero config
- Configure SimpleDVNMock and SimpleExecutorMock for those pathways
- Set both send and receive configurations on the appropriate chains
- Skip pathways that already have DVN configurations
βΉοΈ The command only configures pathways with empty DVN arrays, preserving any existing configurations.
When sending OFTs with Simple Workers, add the --simple-workers flag to enable the manual verification and execution flow:
pnpm hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to <EVM_ADDRESS> --simple-workersWith the --simple-workers flag, the task will:
- Send the OFT transaction as normal
- Automatically trigger the manual verification process on the destination chain
- Execute the message delivery through the Simple Workers
The manual verification flow involves three steps on the destination chain:
- Verify: SimpleDVNMock verifies the message payload
- Commit: SimpleDVNMock commits the verification to the ULN
- Execute: SimpleExecutorMock executes the message delivery
Without the --simple-workers flag, you would need to manually call these steps using the provided tasks:
lz:oapp:wire:simple-workers- Configure Simple Workers for all pathways without DVN configurationslz:simple-dvn:verify- Verify the message with SimpleDVNMocklz:simple-dvn:commit- Commit the verification to ULNlz:simple-workers:commit-and-execute- Execute the message deliverylz:simple-workers:skip- Skip a stuck message (permanent action!)
- Zero Fees: Simple Workers charge no fees, breaking the economic security model
- No Real Verification: Messages are manually verified without actual validation
- Testnet Only: These mocks provide no security and must never be used on mainnet
- Manual Process: Requires manual intervention or the
--simple-workersflag for automation
LayerZero enforces ordered message delivery per channel (source β destination). Messages must be processed in the exact order they were sent. If a message fails or is skipped, all subsequent messages on that channel will be blocked.
Common Error: "InvalidNonce"
warn: Lazy inbound nonce is not equal to inboundNonce + 1. You will run into an InvalidNonce error.
This means there are pending messages that must be processed first.
When a message is stuck, you have two options:
Option 1: Process the Pending Message
# Find the pending nonce from the error message, then:
npx hardhat lz:simple-dvn:verify --src-eid <SRC_EID> --dst-eid <DST_EID> --nonce <PENDING_NONCE> --src-oapp <SRC_OAPP> --to-address <RECIPIENT> --amount <AMOUNT>
npx hardhat lz:simple-workers:commit-and-execute --src-eid <SRC_EID> --dst-eid <DST_EID> --nonce <PENDING_NONCE> ...Option 2: Skip the Message (Cannot be undone!)
# Skip a stuck message on the destination chain
npx hardhat lz:simple-workers:skip --src-eid <SRC_EID> --src-oapp <SRC_OAPP> --nonce <NONCE_TO_SKIP> --receiver <RECEIVER_OAPP>
β οΈ Skipping is permanent! Once skipped, the message cannot be recovered. The tokens/value in that message will be permanently lost.
If your RPC connection fails during --simple-workers processing:
- The outbound message may already be sent but not verified/executed
- You'll see detailed recovery information in the error output
- You must handle this nonce before sending new messages
- Either wait for RPC limits to reset and complete processing, or skip the message
If nonce 6 fails because nonce 4 is pending:
- First process or skip nonce 4
- Then process or skip nonce 5
- Finally, you can process nonce 6
Remember: All messages must be handled in order!
The OFT standard builds on top of the OApp standard, which enables generic message-passing between chains. After deploying the OFT on the respective chains, you enable messaging by running the wiring task.
βΉοΈ This example uses the Simple Config Generator, which is recommended over manual configuration.
This example provides two configuration files:
layerzero.config.ts- The standard configuration using LayerZero's default DVNs and Executors (recommended for most deployments)layerzero.simple-worker.config.ts- A template for using custom DVNs and Executors (useful for testnets without default workers or advanced custom setups)
For most deployments, use the standard configuration:
pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.tsThe layerzero.config.ts file is organized into clear sections:
- Contract definitions
- Gas options
- Pathway configuration using LayerZero's default workers
If you need custom DVNs and Executors (e.g., for testnets without default workers or custom security requirements), use:
pnpm hardhat lz:oapp:wire --oapp-config layerzero.simple-worker.config.tsThe layerzero.simple-worker.config.ts file is organized into clear sections:
- SECTION 1: Contract definitions (YOU MUST EDIT)
- SECTION 2: Gas options (YOU MAY NEED TO EDIT)
- SECTION 3: Metadata configuration (MOSTLY BOILERPLATE)
- SECTION 4: Custom executor/DVN addresses (YOU MUST EDIT if using custom workers)
- SECTION 5: Pathway configuration (YOU MUST EDIT)
- SECTION 6: Export configuration
Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other.
βΉοΈ For testnets without default workers, see the Simple Workers section above.
For production deployments or advanced use cases, you can deploy and configure your own custom Executors and DVNs. This is useful when:
- You need specific fee structures or execution logic
- You want full control over message verification and execution
- You're building a custom security stack
To use custom executors and DVNs:
- Deploy your custom contracts on each chain
- Use the
layerzero.simple-worker.config.tstemplate:- SECTION 1: Define your contracts
- SECTION 4: Add your custom executor/DVN addresses
- SECTION 5: Reference them by name in pathways
- Wire normally with
pnpm hardhat lz:oapp:wire --oapp-config layerzero.simple-worker.config.ts
β οΈ Important: Custom executors and DVNs must be deployed on each chain where they're needed. The same canonical name can resolve to different addresses on different chains.
π For detailed instructions, see the Custom Workers Configuration Guide which shows exactly what to modify in your configuration.
βΉοΈ Note: For production, review SECTION 2 in
layerzero.simple-worker.config.tsto adjust gas limits based on your contract's actual usage.
With your OFTs wired, you can now send them cross chain.
First, via the mock contract, let's mint on Base Sepolia:
cast send <OFT_ADDRESS> "mint(address,uint256)" <RECIPIENT_ADDRESS> 1000000000000000000000 --private-key <PRIVATE_KEY> --rpc-url <BASE_SEPOLIA_RPC_URL>
You can get the address of your OFT on Base Sepolia from the file at
./deployments/base-sepolia/MyOFTMock.json
Send 1 OFT from Base Sepolia to Arbitrum Sepolia:
pnpm hardhat lz:oft:send --src-eid 40245 --dst-eid 40231 --amount 1 --to <EVM_ADDRESS>βΉοΈ
40245and40231are the Endpoint IDs of Base Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the Deployed Endpoints page.
Upon a successful send, the script will provide you with the link to the message on LayerZero Scan.
Once the message is delivered, you will be able to click on the destination transaction hash to verify that the OFT was sent.
Congratulations, you have now sent an OFT cross-chain!
If you run into any issues, refer to Troubleshooting.
Now that you've gone through a simplified walkthrough, here are what you can do next.
- If you are planning to deploy to production, go through the Production Deployment Checklist.
- Read on DVNs / Security Stack
- Read on Message Execution Options
Before deploying, ensure the following:
- (required) you are not using
MyOFTMock, which has a publicmintfunction- In
layerzero.config.ts, ensure you are not usingMyOFTMockas thecontractNamefor any of the contract objects.
- In
- (recommended) you have profiled the gas usage of
lzReceiveon your destination chains
The optimal values you should specify for the gas parameter in the LZ Config depends on the destination chain, and requires profiling. This section walks through how to estimate the optimal gas value.
This guide explains how to use the pnpm commands to estimate gas usage for LayerZero's lzReceive and lzCompose functions. These commands wrap Foundry scripts for easier invocation and allow you to pass the required arguments dynamically.
-
gas:lzReceiveThis command profiles the
lzReceivefunction for estimating gas usage across multiple runs."gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes,uint256,uint256)'"
-
gas:lzComposeThis command profiles the
lzComposefunction for estimating gas usage across multiple runs."gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes,uint256,uint256)'"
To estimate the gas for the lzReceive function:
pnpm gas:lzReceive
<rpcUrl> \
<endpointAddress> \
<srcEid> \
<sender> \
<dstEid> \
<receiver> \
<message> \
<msg.value> \
<numOfRuns>Where:
rpcUrl: The RPC URL for the target blockchain (e.g., Optimism, Arbitrum, etc.).endpointAddress: The deployed LayerZero EndpointV2 contract address.srcEid: The source endpoint ID (uint32).sender: The sender's address (OApp).dstEid: The destination endpoint ID (uint32).receiver: The address intended to receive the message (OApp).message: The message payload as abytesarray.msg.value: The amount of Ether sent with the message (in wei).numOfRuns: The number of test runs to execute.
To estimate the gas for the lzCompose function:
pnpm gas:lzCompose
<rpcUrl> \
<endpointAddress> \
<srcEid> \
<sender> \
<dstEid> \
<receiver> \
<composer> \
<composeMsg> \
<msg.value> \
<numOfRuns>Where:
rpcUrl: The RPC URL for the target blockchain (e.g., Optimism, Arbitrum, etc.).endpointAddress: The deployed LayerZero EndpointV2 contract address.srcEid: The source endpoint ID (uint32).sender: The originating OApp address.dstEid: The destination endpoint ID (uint32).receiver: The address intended to receive the message (OApp).composer: The LayerZero Composer contract address.composeMsg: The compose message payload as abytesarray.msgValue: The amount of Ether sent with the message (in wei).numOfRuns: The number of test runs to execute.
- Modify
numOfRunsbased on the level of accuracy or performance you require for gas profiling. - Log outputs will provide metrics such as the average, median, minimum, and maximum gas usage across all successful runs.
This approach simplifies repetitive tasks and ensures consistent testing across various configurations.
Join our community! | Follow us on X (formerly Twitter)
Similar to the contract compilation, we support both hardhat and forge tests. By default, the test command will execute both:
pnpm testIf you prefer one over the other, you can use the tooling-specific commands:
pnpm test:forge
pnpm test:hardhatIf you're adding another EVM chain, first, add it to the hardhat.config.ts. Adding non-EVM chains do not require modifying the hardhat.config.ts.
Then, modify layerzero.config.ts with the following changes:
- declare a new contract object (specifying the
eidandcontractName) - decide whether to use an existing EVM enforced options variable or declare a new one
- create a new entry in the
pathwaysvariable - add the new contract into the
contractskey of thereturnof theexport defaultfunction
After applying the desired changes, make sure you re-run the wiring task:
pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.tsThe wiring task supports the usage of Safe Multisigs.
To use a Safe multisig as the signer for these transactions, add the following to each network in your hardhat.config.ts and add the --safe flag to lz:oapp:wire --safe:
// hardhat.config.ts
networks: {
// Include configurations for other networks as needed
fuji: {
/* ... */
// Network-specific settings
safeConfig: {
safeUrl: 'http://something', // URL of the Safe API, not the Safe itself
safeAddress: 'address'
}
}
}LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain.
pnpm hardhat lz:deploy
Deploys your contract to any of the available networks in your hardhat.config.ts when given a deploy tag (by default contract name) and returns a list of available networks to select for the deployment. For specifics around all deployment options, please refer to the Deploying Contracts section of the documentation. LayerZero's lz:deploy utilizes hardhat-deploy.
'arbitrum-sepolia': {
eid: EndpointId.ARBSEP_V2_TESTNET,
url: process.env.RPC_URL_ARBSEP_TESTNET,
accounts,
},
'base-sepolia': {
eid: EndpointId.BASESEP_V2_TESTNET,
url: process.env.RPC_URL_BASE_TESTNET,
accounts,
},More information about available CLI arguments can be found using the --help flag:
pnpm hardhat lz:deploy --help pnpm hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME
Initializes a layerzero.config.ts file for all available pathways between your hardhat networks with the current LayerZero default placeholder settings. This task can be incredibly useful for correctly formatting your config file.
You can run this task by providing the contract-name you want to set for the config and file-name you want to generate:
pnpm hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAMEThis will create a layerzero.config.ts in your working directory populated with your contract name and connections for every pathway possible between your hardhat networks:
import { EndpointId } from "@layerzerolabs/lz-definitions";
const arbsepContract = {
eid: EndpointId.ARBSEP_V2_TESTNET,
contractName: "MyOFT",
};
const sepoliaContract = {
eid: EndpointId.SEPOLIA_V2_TESTNET,
contractName: "MyOFT",
};
export default {
contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }],
connections: [
{
from: arbsepContract,
to: sepoliaContract,
config: {
sendLibrary: "0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E",
receiveLibraryConfig: {
receiveLibrary: "0x75Db67CDab2824970131D5aa9CECfC9F69c69636",
gracePeriod: 0,
},
sendConfig: {
executorConfig: {
maxMessageSize: 10000,
executor: "0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897",
},
ulnConfig: {
confirmations: 1,
requiredDVNs: ["0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8"],
optionalDVNs: [],
optionalDVNThreshold: 0,
},
},
// receiveConfig: {
// ulnConfig: {
// confirmations: 2,
// requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'],
// optionalDVNs: [],
// optionalDVNThreshold: 0,
// },
// },
},
},
{
from: sepoliaContract,
to: arbsepContract,
config: {
sendLibrary: "0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE",
receiveLibraryConfig: {
receiveLibrary: "0xdAf00F5eE2158dD58E0d3857851c432E34A3A851",
gracePeriod: 0,
},
// sendConfig: {
// executorConfig: { maxMessageSize: 10000, executor: '0x718B92b5CB0a5552039B593faF724D182A881eDA' },
// ulnConfig: {
// confirmations: 2,
// requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'],
// optionalDVNs: [],
// optionalDVNThreshold: 0,
// },
// },
receiveConfig: {
ulnConfig: {
confirmations: 1,
requiredDVNs: ["0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193"],
optionalDVNs: [],
optionalDVNThreshold: 0,
},
},
},
},
],
}; pnpm hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG
Calls the configuration functions between your deployed OApp contracts on every chain based on the provided layerzero.config.ts.
Running lz:oapp:wire will make the following function calls per pathway connection for a fully defined config file using your specified settings and your environment variables (Private Keys and RPCs):
To use this task, run:
pnpm hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILEWhenever you make changes to the configuration, run lz:oapp:wire again. The task will check your current configuration, and only apply NEW changes.
pnpm hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG
Returns your current OApp's configuration for each chain and pathway in 3 columns:
-
Custom Configuration: the changes that your
layerzero.config.tscurrently has set -
Default Configuration: the default placeholder configuration that LayerZero provides
-
Active Configuration: the active configuration that applies to the message pathway (Defaults + Custom Values)
If you do NOT explicitly set each configuration parameter, your OApp will fallback to the placeholder parameters in the default config.
ββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Custom OApp Config β Default OApp Config β Active OApp Config β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β localNetworkName β arbsep β arbsep β arbsep β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β remoteNetworkName β sepolia β sepolia β sepolia β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β sendLibrary β 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E β 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E β 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β receiveLibrary β 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 β 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 β 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β sendUlnConfig β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β confirmations β 1 β β β confirmations β 1 β β β confirmations β 1 β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β
β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β β β β β β β β β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β optionalDVNs β β β β optionalDVNs β β β β optionalDVNs β β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β optionalDVNThreshold β 0 β β β optionalDVNThreshold β 0 β β β optionalDVNThreshold β 0 β β
β β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β sendExecutorConfig β ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β
β β β executor β 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 β β β executor β 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 β β β executor β 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 β β
β β ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β maxMessageSize β 10000 β β β maxMessageSize β 10000 β β β maxMessageSize β 10000 β β
β β ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β
β β β β β
ββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β receiveUlnConfig β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β confirmations β 2 β β β confirmations β 2 β β β confirmations β 2 β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β β requiredDVNs β βββββ¬βββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β β β β 0 β 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 β β β
β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β β β βββββ΄βββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β β β β β β β β β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β optionalDVNs β β β β optionalDVNs β β β β optionalDVNs β β β
β β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β β optionalDVNThreshold β 0 β β β optionalDVNThreshold β 0 β β β optionalDVNThreshold β 0 β β
β β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β ββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β β β
ββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ pnpm hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG
Returns the LayerZero Executor config for each network in your hardhat.config.ts. You can use this method to see the max destination gas in wei (nativeCap) you can request in your execution options.
βββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ
β localNetworkName β mantle β
βββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€
β remoteNetworkName β polygon β
βββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€
β executorDstConfig β ββββββββββββββββββ¬ββββββββββββββββββββββββ β
β β β baseGas β 85000 β β
β β ββββββββββββββββββΌββββββββββββββββββββββββ€ β
β β β multiplierBps β 12000 β β
β β ββββββββββββββββββΌββββββββββββββββββββββββ€ β
β β β floorMarginUSD β 5000000000000000000 β β
β β ββββββββββββββββββΌββββββββββββββββββββββββ€ β
β β β nativeCap β 681000000000000000000 β β
β β ββββββββββββββββββ΄ββββββββββββββββββββββββ β
β β β
βββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββThis section only applies if you would like to configure manually instead of using the Simple Config Generator.
Define the pathway you want to create from and to each contract:
connections: [
// ETH <--> ARB PATHWAY: START
{
from: ethereumContract,
to: arbitrumContract,
},
{
from: arbitrumContract,
to: ethereumContract,
},
// ETH <--> ARB PATHWAY: END
];Finally, define the config settings for each direction of the pathway:
connections: [
// ETH <--> ARB PATHWAY: START
{
from: ethereumContract,
to: arbitrumContract,
config: {
sendLibrary: contractsConfig.ethereum.sendLib302,
receiveLibraryConfig: {
receiveLibrary: contractsConfig.ethereum.receiveLib302,
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid
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
executor: contractsConfig.ethereum.executor,
},
ulnConfig: {
// The number of block confirmations to wait on Ethereum before emitting the message from the source chain.
confirmations: BigInt(15),
// The address of the DVNs you will pay to verify a sent message on the source chain ).
// The destination tx will wait until ALL `requiredDVNs` verify the message.
requiredDVNs: [
contractsConfig.ethereum.horizenDVN, // Horizen
contractsConfig.ethereum.polyhedraDVN, // Polyhedra
contractsConfig.ethereum.animocaBlockdaemonDVN, // Animoca-Blockdaemon (only available on ETH <-> Arbitrum One)
contractsConfig.ethereum.lzDVN, // LayerZero Labs
],
// The address of the DVNs you will pay to verify a sent message on the source chain ).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
optionalDVNs: [],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 0,
},
},
// 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.
confirmations: BigInt(20),
// The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain ).
// The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
requiredDVNs: [
contractsConfig.ethereum.lzDVN, // LayerZero Labs DVN
contractsConfig.ethereum.animocaBlockdaemonDVN, // Blockdaemon-Animoca
contractsConfig.ethereum.horizenDVN, // Horizen Labs
contractsConfig.ethereum.polyhedraDVN, // Polyhedra
],
// The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain ).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
optionalDVNs: [],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 0,
},
},
// 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: 65000,
value: 0,
},
{
msgType: 2,
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 65000,
value: 0,
},
{
msgType: 2,
optionType: ExecutorOptionType.COMPOSE,
index: 0,
gas: 50000,
value: 0,
},
],
},
},
{
from: arbitrumContract,
to: ethereumContract,
},
// ETH <--> ARB PATHWAY: END
];You can verify EVM chain contracts using the LayerZero helper package:
pnpm dlx @layerzerolabs/verify-contract -n <NETWORK_NAME> -u <API_URL> -k <API_KEY> --contracts <CONTRACT_NAME>Refer to Debugging Messages or Error Codes & Handling.