Project for converting existing Billions Network Token into a cross-chain token (OFT) using the LayerZero protocol. Default for EVM chains, but the same OFT can be extended to involve other VM chains such as Solana, Aptos and Hyperliquid.
- Prerequisite Knowledge
- Introduction
- Requirements
- Scaffold this example
- Helper Tasks
- Setup
- Build
- Deploy
- Enable Messaging
- Sending OFTs
- Generate Safe Multisig Batch Files
- Transfer Ownership
- Next Steps
- Production Deployment Checklist
- Appendix
OFT Adapter - while a regular OFT uses the mint/burn mechanism, an OFT adapter uses lock/unlock. The OFT Adapter contract functions as a lockbox for the existing token (referred to as the inner token). Given the inner token's chain, transfers to outside the inner token's chain will require locking and transfers to the inner token's chain will result in unlocking.
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 oft-adapterSpecify the directory, select OFTAdapter 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
MNEMONIC,PRIVATE_KEYor `LEDGER_ACCOUNT:MNEMONIC="test test test test test test test test test test test junk" or... PRIVATE_KEY="0xabc...def" or... LEDGER_ACOUNT="0x..."
-
-
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 Sepolia and Arbitrum Sepolia.
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:hardhat- In the
hardhat.config.tsfile, add the inner token's address to the network you want to deploy the OFTAdapter to:
// Replace `0x0` with the address of the ERC20 token you want to adapt to the OFT functionality.
oftAdapter: {
tokenAddress: '<INNER_TOKEN_ADDRESS>',
}- Uncomment the networks that you don't want to deploy.
- Update
layerzero.testnet.config.tswith desired networks to deploy and wire. - Deploy OFCAdapter and OFC contracts in the different networks
pnpm run deploy- After deploying the OFT on the respective chains, you enable messaging by running the wiring
pnpm run wire:testnet- In the
hardhat.config.tsfile, add the inner token's address to the network you want to deploy the OFTAdapter to:
// Replace `0x0` with the address of the ERC20 token you want to adapt to the OFT functionality.
oftAdapter: {
tokenAddress: '<INNER_TOKEN_ADDRESS>',
}- Uncomment the networks that you don't want to deploy.
- Update
layerzero.mainnet.config.tswith desired networks to deploy and wire adjusting number of confirmations for every chain. - Deploy OFCAdapter and OFC contracts in the different networks
pnpm run deploy- After deploying the OFT on the respective chains, you enable messaging by running the wiring
pnpm run wire:mainnet- Alternatively, you can export wiring transactions without broadcasting and submit them manually with a Ledger signer (see Send Wiring Transactions with Ledger).
pnpm hardhat lz:oapp:wire --oapp-config layerzero.mainnet.config.ts --output-filename wiring-txns.json
If you already have a generated wiring-txns.json manifest and want to submit those transactions manually with a Ledger-backed signer, use the helper task and wrapper script added in this repository.
- Ensure your
.envcontainsLEDGER_ACCOUNT="0x..." - Optional: override Ledger timeouts in
.envwithLEDGER_OPEN_TIMEOUT=20000andLEDGER_CONNECTION_TIMEOUT=20000 - Open the Ethereum app on the Ledger device
- Fund the Ledger address on every destination chain you plan to submit to
Preview all transactions without broadcasting:
pnpm wire:ledger:previewBroadcast all transactions across the configured mainnets:
pnpm wire:ledger:sendTarget a single network or a subset of the manifest:
bash ./scripts/send-wiring-ledger.sh --network ethereum-mainnet --execute
pnpm hardhat lz:wiring:send --network ethereum-mainnet --omni-address 0x1a44076050125825900e736c501f859c50fE728c --count 2The task defaults to a dry run, prints the selected transactions and gas estimates, and only broadcasts when --execute is supplied.
If your contracts are owned by a Safe multisig, you can convert a wiring transactions manifest into Safe Transaction Builder batch files and import them directly into the Safe Wallet UI instead of broadcasting transactions yourself.
First, generate the wiring manifest without broadcasting:
pnpm hardhat lz:oapp:wire --oapp-config layerzero.mainnet.config.ts --output-filename wiring-txns.jsonThen run the generator:
bash ./scripts/generate-safe-txns.sh --input wiring-txns.jsonThis produces one file per network under safe-txns/:
safe-txns/
ethereum-sepolia/safe-batch.json
bnb-testnet/safe-batch.json
base-sepolia/safe-batch.json
polygon-amoy/safe-batch.json
arbitrum-sepolia/safe-batch.json
optimism-sepolia/safe-batch.json
mantle-sepolia/safe-batch.json
To import a batch into Safe Wallet:
- Open Safe Wallet and navigate to your Safe on the target network
- Go to Transaction Builder β New Batch β Import
- Upload the corresponding
safe-batch.jsonfile - Review and submit
Works with both testnet and mainnet manifests. To use a custom output directory:
bash ./scripts/generate-safe-txns.sh --input wiring-txns-update.json --output /tmp/safe-batchesUse this script to transfer ownership of all deployed Proxy and ProxyAdmin contracts, and set the LayerZero delegate on each Proxy, across all mainnet networks in a single command.
For each network the task sends three transactions per deployed contract pair (BillOFT and BillOFTAdapter where applicable):
Proxy.setDelegate(newOwner)ProxyAdmin.transferOwnership(newOwner)Proxy.transferOwnership(newOwner)
Prerequisites:
- Uncomment the mainnet network entries in
hardhat.config.ts - Ensure your
.envcontainsLEDGER_ACCOUNT="0x..."(orPRIVATE_KEY/MNEMONIC) - Open the Ethereum app on the Ledger device before running with
--execute - Fund the signer address on every target network
Dry-run β preview all transactions without broadcasting:
bash ./scripts/transfer-ownership.sh --new-owner 0xABC...Execute β broadcast across all 7 mainnets:
bash ./scripts/transfer-ownership.sh --new-owner 0xABC... --executeTarget a single network:
bash ./scripts/transfer-ownership.sh --network ethereum-mainnet --new-owner 0xABC... --executeThe task defaults to a dry run, prints the addresses and calls for each contract pair, and only broadcasts when --execute is supplied. Gas is estimated per transaction and bumped by 20% by default (configurable with --gas-bump-bps).
To verify deployments you can execute script to verify for the desired network
npx hardhat run verify/verify.ts --network <network>Example for Ethereum Sepolia
npx hardhat run verify/verify.ts --network ethereum-sepoliaWith your OFTs wired, you can now send them cross chain.
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> --oapp-config layerzero.testnet.config.tsβΉοΈ
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.
The script scripts/send-oft-all-paths.sh automates sending tokens across every pathway defined in layerzero.mainnet.config.ts bidirectionally. Starting with a single OFT token on Ethereum, it covers all 15 bidirectional pairs (30 sends total) across the six mainnets: Ethereum, BSC, Base, Polygon, Arbitrum, and Optimism.
Execution is split into three sequential phases so that tokens are always available on the source chain before the next send:
| Phase | Sends | Description |
|---|---|---|
| 1 | 5 | Ethereum β BSC, Base, Polygon, Arbitrum, Optimism |
| 2 | 20 | All non-ETH bidirectional combinations |
| 3 | 5 | BSC, Base, Polygon, Arbitrum, Optimism β Ethereum |
Dry run β print all 30 commands without submitting any transactions:
bash ./scripts/send-oft-all-paths.sh --to <EVM_ADDRESS>Execute Phase 1 β send from Ethereum to all other networks:
bash ./scripts/send-oft-all-paths.sh --to <EVM_ADDRESS> --amount 0.1 --phase 1 --executeWait for Phase 1 transactions to be delivered (verify on LayerZero Scan), then run Phase 2:
bash ./scripts/send-oft-all-paths.sh --to <EVM_ADDRESS> --amount 0.1 --phase 2 --executeAfter Phase 2 is delivered, consolidate all tokens back to Ethereum with Phase 3:
bash ./scripts/send-oft-all-paths.sh --to <EVM_ADDRESS> --amount 0.1 --phase 3 --executeThe
--amountflag accepts human-readable units (e.g.0.1). The--toaddress receives tokens on every destination chain.
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) if you uncommented the testnet mint line in
contracts/BillOFT.sol, ensure you remove this line for production - (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: "BillOFT",
};
const sepoliaContract = {
eid: EndpointId.SEPOLIA_V2_TESTNET,
contractName: "BillOFT",
};
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.