From 374885ea7d9d4e76b6d4d96a7b70b380f88a292b Mon Sep 17 00:00:00 2001 From: nazreen Date: Tue, 1 Jul 2025 01:24:43 +0200 Subject: [PATCH 01/57] structure doc + OFT README revamp --- docs/EXAMPLES_DEVELOPMENT.md | 90 ++++++ examples/oft/README.md | 566 +++++++++++++++++++---------------- 2 files changed, 395 insertions(+), 261 deletions(-) create mode 100644 docs/EXAMPLES_DEVELOPMENT.md diff --git a/docs/EXAMPLES_DEVELOPMENT.md b/docs/EXAMPLES_DEVELOPMENT.md new file mode 100644 index 0000000000..acdc7971cf --- /dev/null +++ b/docs/EXAMPLES_DEVELOPMENT.md @@ -0,0 +1,90 @@ +This document is intended for the maintainers of the examples that are in `/examples` in this repo. It is also meant as a guide for coding agents for the purposes of reviewing or editing. + +Currently, this document will only detail the structure for the READMEs of the examples. + +## Structure for examples' READMEs + +1. **Header** + - Goal: Branding + promote docs site + entrypoint + - Contents: LayerZero logo + links to docs and dev site + +2. **Example Title** + - Goal: What the example will teach + - Contents: Title and 1–2 sentence description (possibly goal-oriented) + +3. **Prerequisite Knowledge** + - Goal: What to understand before running the example + - Contents: Short list (≤3 items) like OApp, OFT + +4. **Requirements** + - Goal: What needs to be installed + - Contents: Tools + exact versions + +5. **Scaffold this example** + - Goal: How to init the example + - Contents: `npx create-lz-oapp@latest --example ` + +6. **Helper Tasks (inline notice)** + - Goal: Know that helper tasks exist + - Contents: Statement + link to helper section + +7. **Setup** + - Goal: What to configure before running + - Contents: .env instructions, deployer account setup + +8. **Build** + - Goal: How to build contracts/programs/modules + - Contents: Build command(s) + +9. **Deploy** + - Goal: How to deploy contracts/programs/modules + - Contents: Deploy command + minting instructions (if needed) + +10. **Wiring / Configuring OApps** + - Goal: How to wire OApps for cross-chain use + - Contents: LZ config, init step, wiring step + +11. **Sending Message/OFT/ONFT** + - Goal: How to trigger cross-chain action + - Contents: Command to send message/OFT/ONFT, both/all directions + +12. **Next Steps** + - Goal: What to know after initial deployment + - Contents: Links to Production Checklist, Security Stack, Message Options + +13. **Production Deployment Checklist** + - Goal: What’s needed for production readiness + - Contents: Gas profiling, DVN config, confirmation count + +14. **Appendix** + - Goal: Mark end of main build steps + - Contents: Supplementary instructions and optional configurations + + 14.1. **Running tests** + - Goal: How to test the contracts/programs + - Contents: Test commands + + 14.2. **Adding other chains** + - Goal: How to add additional networks + - Contents: How to add chains + example config (e.g. modify `hardhat.config.ts`) + + 14.3. **Using Multisigs** + - Goal: How to deploy if using a multisig + - Contents: Command param diffs + multi-VM notes + + 14.4. **LayerZero Hardhat Helper Tasks (detailed)** + - Goal: Know all available helpers + - Contents: Link to docs + list of built-in and local helper tasks + + 14.5. **Contract/Program Verification** + - Goal: How to verify + - Contents: Links to verification docs (per VM) + + 14.6. **Troubleshooting** + - Goal: How to debug errors/issues + - Contents: Link to global page + example-specific fixes + + + + +Any sections that don't appear in the above list should be considered for removal. \ No newline at end of file diff --git a/examples/oft/README.md b/examples/oft/README.md index 0504b12757..ca960f483d 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -1,38 +1,305 @@

- LayerZero + LayerZero

- Homepage | Docs | Developers + LayerZero Docs

-

Omnichain Fungible Token (OFT) Example

+

EVM-to-EVM Omnichain Fungible Token (OFT) Example

+ +

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 + +- [What is an OFT (Omnichain Fungible Token) ?](https://docs.layerzero.network/v2/concepts/applications/oft-standard) +- [What is an OApp (Omnichain Application) ?](https://docs.layerzero.network/v2/concepts/applications/oapp-standard) + +## Requirements + +- `Node.js` - ` >=18.16.0` +- `pnpm` (recommended) - or another package manager of your choice (npm, yarn) +- `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation + +## Scaffold this example + +Create your local copy of this example: + +```bash +pnpm dlx create-lz-oapp@latest --example oft +``` + +Note that `create-lz-oapp` will also automatically run the dependencies install step for you. + + +## Helper Tasks + +Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the [LayerZero Hardhat Helper Tasks section](#layerzero-hardhat-helper-tasks). All commands can be run at the project root. + +## Setup + +- Copy `.env.example` into a new `.env` +- Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_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: **Optimism**, **Avalanche**, and **Arbitrum**. + +## Build + +#### Compiling your contracts + +This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: + +```bash +pnpm compile +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm compile:forge +pnpm compile:hardhat +``` + +## Deploy + + +To deploy your contracts to your desired blockchains, run the following command: + +```bash +pnpm hardhat lz:deploy --tags MyOFTMock +``` + +> :information_source: 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. + +## Enable Messaging + +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](https://docs.layerzero.network/v2/concepts/glossary#wire--wiring) task. + +> :information_source: This example uses the [Simple Config Generator](https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config), which is recommended over manual configuration. + +Run the wiring task: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. + + +## Sending OFTs + +With your OFTs wired, you can now send them cross chain. + +First, via the mock contract, let's mint on **Optimism Sepolia**: + +``` +cast send "mint(address,uint256)" --private-key --rpc-url + +``` + +Send 1 OFT from **Optimism Sepolia** to **Avalanche Fuji**: + +```bash +npx hardhat lz:oft:send --src-eid 40232 --dst-eid 40106 --amount 1 --to +``` + +> :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Avalanche Fuji respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) 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](#troubleshooting). + +## Next Steps + +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](#production-deployment-checklist). +- Read on [DVNs / Security Stack](https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns) +- Read on [Message Execution Options](https://docs.layerzero.network/v2/concepts/technical-reference/options-reference) + + +## Production Deployment Checklist + + + +Before deploying, ensure the following: + +- (required) you are not using `MyOFTMock`, which has a public `_mint` function +- (recommended) you have profiled the gas usage of `lzReceive` on your destination chains + + +### Profiling `lzReceive` and `lzCompose` Gas Usage + +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. + +### Available Commands + +1. **`gas:lzReceive`** + + This command profiles the `lzReceive` function for estimating gas usage across multiple runs. + + ```json + "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes,uint256,uint256)'" + ``` + +2. **`gas:lzCompose`** + + This command profiles the `lzCompose` function for estimating gas usage across multiple runs. + + ```json + "gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes,uint256,uint256)'" + ``` + +### Usage Examples + +#### `lzReceive` + +To estimate the gas for the `lzReceive` function: + +```bash +pnpm gas:lzReceive + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msg.value`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +#### `lzCompose` + +To estimate the gas for the `lzCompose` function: + +```bash +pnpm gas:lzCompose + \ + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msgValue`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +### Notes + +- Modify `numOfRuns` based 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.

- Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses | DVN Addresses + Join our community! | Follow us on X (formerly Twitter)

-

Template project for getting started with LayerZero's OFT contract standard.

-

+# Appendix -- [So What is an Omnichain Fungible Token?](#so-what-is-an-omnichain-fungible-token) -- [Available Helpers in this Repo](#layerzero-hardhat-helper-tasks) +## Running Tests -

+Similar to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: + +```bash +pnpm test +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm test:forge +pnpm test:hardhat +``` + +## Adding other chains -## So what is an Omnichain Fungible Token? + -The Omnichain Fungible Token (OFT) Standard is an ERC20 token that can be transferred across multiple blockchains without asset wrapping or middlechains. +If 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`. -LayerZero + -This standard works by combining the LayerZero OApp Contract Standard with the ERC20 [`_burn`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L80) method, to initiate omnichain send transfers on the source chain, sending a message via the LayerZero protocol, and delivering a function call to the destination contract to [`_mint`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L96) the same number of tokens burned, creating a unified supply across all networks connected. +Then, modify `layerzero.config.ts` with the following changes: + + - declare a new contract object (specifying the `eid` and `contractName`) + - decide whether to use an existing EVM enforced options variable or declare a new one + - create a new entry in the `pathways` variable + - add the new contract into the `contracts` key of the `return` of the `export default` function + +After applying the desired changes, make sure you re-run the wiring task: + +```bash +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +## Using Multisigs + +The 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`: + +```typescript +// 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' + } + } +} +``` -Read more about what you can do with OFTs by reading the [OFT Quickstart](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) in the LayerZero Documentation. ## LayerZero Hardhat Helper Tasks @@ -45,7 +312,7 @@ LayerZero Devtools provides several helper hardhat tasks to easily deploy, verif Deploys your contract to any of the available networks in your [`hardhat.config.ts`](./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](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`. -```yml +```typescript 'arbitrum-sepolia': { eid: EndpointId.ARBSEP_V2_TESTNET, url: process.env.RPC_URL_ARBSEP_TESTNET, @@ -58,6 +325,12 @@ Deploys your contract to any of the available networks in your [`hardhat.config. }, ``` +More information about available CLI arguments can be found using the `--help` flag: + +```bash +npx hardhat lz:deploy --help +``` +
@@ -75,7 +348,7 @@ npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE This 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: -```yml +```typescript import { EndpointId } from '@layerzerolabs/lz-definitions' const arbsepContract = { @@ -173,23 +446,6 @@ npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes. -To use a Gnosis 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`: - -```yml -// 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' - } - } -} -```
@@ -285,231 +541,10 @@ Returns the LayerZero Executor config for each network in your `hardhat.config.t
-## Developing Contracts - -#### Installing dependencies - -We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): - -```bash -pnpm install -``` - -#### Compiling your contracts - -This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: - -```bash -pnpm compile -``` - -If you prefer one over the other, you can use the tooling-specific commands: - -```bash -pnpm compile:forge -pnpm compile:hardhat -``` - -Or adjust the `package.json` to for example remove `forge` build: - -```diff -- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", -- "compile:forge": "forge build", -- "compile:hardhat": "hardhat compile", -+ "compile": "hardhat compile" -``` - -#### Running tests - -Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: - -```bash -pnpm test -``` - -If you prefer one over the other, you can use the tooling-specific commands: - -```bash -pnpm test:forge -pnpm test:hardhat -``` - -Or adjust the `package.json` to for example remove `hardhat` tests: - -```diff -- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", -- "test:forge": "forge test", -- "test:hardhat": "$npm_execpath hardhat test" -+ "test": "forge test" -``` - -## Deploying Contracts - -Set up deployer wallet/account: - -- Rename `.env.example` -> `.env` -- Choose your preferred means of setting up your deployer wallet/account: - -``` -MNEMONIC="test test test test test test test test test test test junk" -or... -PRIVATE_KEY="0xabc...def" -``` - -- Fund this address with the corresponding chain's native tokens you want to deploy to. - -To deploy your contracts to your desired blockchains, run the following command in your project's folder: - -```bash -npx hardhat lz:deploy -``` - -More information about available CLI arguments can be found using the `--help` flag: - -```bash -npx hardhat lz:deploy --help -``` - -By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. - -

- -## Estimating `lzReceive` and `lzCompose` Gas Usage - -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. - -### Available Commands - -1. **`gas:lzReceive`** - - This command profiles the `lzReceive` function for estimating gas usage across multiple runs. - - ```json - "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes,uint256,uint256)'" - ``` - -2. **`gas:lzCompose`** - - This command profiles the `lzCompose` function for estimating gas usage across multiple runs. - - ```json - "gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes,uint256,uint256)'" - ``` - -### Usage Examples - -#### `lzReceive` - -To estimate the gas for the `lzReceive` function: - -```bash -pnpm gas:lzReceive - \ - \ - \ - \ - \ - \ - \ - \ - - -pnpm gas:lzCompose -``` - -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 a `bytes` array. -- `msg.value`: The amount of Ether sent with the message (in wei). -- `numOfRuns`: The number of test runs to execute. - -#### `lzCompose` - -To estimate the gas for the `lzCompose` function: - -```bash -pnpm gas:lzCompose - \ - \ - \ - \ - \ - \ - \ - \ - \ - -``` - -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 a `bytes` array. -- `msgValue`: The amount of Ether sent with the message (in wei). -- `numOfRuns`: The number of test runs to execute. - -### Notes - -- Modify `numOfRuns` based 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. - -## Connecting Contracts - -This example uses the [Simple Config Generator](https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config), which is recommended over manual configuration. - -### Generate [LZ Config](https://docs.layerzero.network/v2/concepts/glossary#lz-config) file based on hardhat.config.ts - -Fill out your `layerzero.config.ts` with the contracts you want to connect. You can generate the default [LZ Config](https://docs.layerzero.network/v2/concepts/glossary#lz-config) file for your declared hardhat networks (in `hardhat.config.ts`) by running: - -```bash -npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME] -``` - -### Customize values in the LZ Config - -> [!NOTE] -> You may need to change the contract name if you're deploying multiple OApp contracts on different chains (e.g., OFT and OFT Adapter). - -
- -```typescript -const optimismContract: OmniPointHardhat = { - eid: EndpointId.OPTSEP_V2_TESTNET, - contractName: "MyOFTAdapter", -}; - -const avalancheContract: OmniPointHardhat = { - eid: EndpointId.AVALANCHE_V2_TESTNET, - contractName: "MyOFT", -}; -``` - -### Apply configurations - -After applying the desired settings, run: - -```bash -npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts -``` - -Congratulations! Your contracts are now wired and can begin sending messages to each other. - ### Manual Configuration + + 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: @@ -557,7 +592,7 @@ connections: [ executor: contractsConfig.ethereum.executor, }, ulnConfig: { - // The number of block confirmations to wait on BSC before emitting the message from the source chain. + // 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. @@ -628,6 +663,15 @@ connections: [ ]; ``` -

- Join our community! | Follow us on X (formerly Twitter) -

+### Contract Verification + +You can verify EVM chain contracts using the LayerZero helper package: + +```bash +pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts +``` + + +### Troubleshooting + +Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). From cba33e13aa6a40ab75c05473f59bd05421cd4625 Mon Sep 17 00:00:00 2001 From: nazreen Date: Tue, 1 Jul 2025 01:29:41 +0200 Subject: [PATCH 02/57] lint --- examples/oft/README.md | 149 +++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index ca960f483d..3d5671171a 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -21,7 +21,7 @@ - `Node.js` - ` >=18.16.0` - `pnpm` (recommended) - or another package manager of your choice (npm, yarn) -- `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation +- `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation ## Scaffold this example @@ -33,7 +33,6 @@ pnpm dlx create-lz-oapp@latest --example oft Note that `create-lz-oapp` will also automatically run the dependencies install step for you. - ## Helper Tasks Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the [LayerZero Hardhat Helper Tasks section](#layerzero-hardhat-helper-tasks). All commands can be run at the project root. @@ -42,6 +41,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` @@ -49,6 +49,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava 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: **Optimism**, **Avalanche**, and **Arbitrum**. ## Build @@ -70,7 +71,6 @@ pnpm compile:hardhat ## Deploy - To deploy your contracts to your desired blockchains, run the following command: ```bash @@ -95,7 +95,6 @@ pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. - ## Sending OFTs With your OFTs wired, you can now send them cross chain. @@ -131,7 +130,6 @@ Now that you've gone through a simplified walkthrough, here are what you can do - Read on [DVNs / Security Stack](https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns) - Read on [Message Execution Options](https://docs.layerzero.network/v2/concepts/technical-reference/options-reference) - ## Production Deployment Checklist @@ -239,7 +237,6 @@ This approach simplifies repetitive tasks and ensures consistent testing across Join our community! | Follow us on X (formerly Twitter)

- # Appendix ## Running Tests @@ -267,10 +264,10 @@ If you're adding another EVM chain, first, add it to the `hardhat.config.ts`. Ad Then, modify `layerzero.config.ts` with the following changes: - - declare a new contract object (specifying the `eid` and `contractName`) - - decide whether to use an existing EVM enforced options variable or declare a new one - - create a new entry in the `pathways` variable - - add the new contract into the `contracts` key of the `return` of the `export default` function +- declare a new contract object (specifying the `eid` and `contractName`) +- decide whether to use an existing EVM enforced options variable or declare a new one +- create a new entry in the `pathways` variable +- add the new contract into the `contracts` key of the `return` of the `export default` function After applying the desired changes, make sure you re-run the wiring task: @@ -300,7 +297,6 @@ networks: { } ``` - ## LayerZero Hardhat Helper Tasks LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain. @@ -349,72 +345,81 @@ npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE This 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: ```typescript -import { EndpointId } from '@layerzerolabs/lz-definitions' +import { EndpointId } from "@layerzerolabs/lz-definitions"; const arbsepContract = { - eid: EndpointId.ARBSEP_V2_TESTNET, - contractName: 'MyOFT', -} + eid: EndpointId.ARBSEP_V2_TESTNET, + contractName: "MyOFT", +}; const sepoliaContract = { - eid: EndpointId.SEPOLIA_V2_TESTNET, - contractName: 'MyOFT', -} + 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, - // }, - // }, - }, + contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }], + connections: [ + { + from: arbsepContract, + to: sepoliaContract, + config: { + sendLibrary: "0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E", + receiveLibraryConfig: { + receiveLibrary: "0x75Db67CDab2824970131D5aa9CECfC9F69c69636", + gracePeriod: 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, - }, - }, - }, + 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, + }, + }, + }, + }, + ], +}; ``` @@ -446,7 +451,6 @@ npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes. -
npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG @@ -668,10 +672,9 @@ connections: [ You can verify EVM chain contracts using the LayerZero helper package: ```bash -pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts +pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts ``` - ### Troubleshooting Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). From 58c69c64bfc4419fe39f40213c9dd1198bb22497 Mon Sep 17 00:00:00 2001 From: nazreen Date: Tue, 1 Jul 2025 23:35:25 +0200 Subject: [PATCH 03/57] amend --- examples/oft/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/oft/README.md b/examples/oft/README.md index 3d5671171a..d906d263b7 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -31,6 +31,8 @@ Create your local copy of this example: pnpm dlx create-lz-oapp@latest --example oft ``` +Specify the directory, select `OFT` and proceed with the installation. + Note that `create-lz-oapp` will also automatically run the dependencies install step for you. ## Helper Tasks From b55242e62bc7891e6f6cd3df1e8ee7538cf59d50 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 01:17:02 +0200 Subject: [PATCH 04/57] add deploy script for MyOFTMock --- examples/oft/deploy/MyOFTMock.ts | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/oft/deploy/MyOFTMock.ts diff --git a/examples/oft/deploy/MyOFTMock.ts b/examples/oft/deploy/MyOFTMock.ts new file mode 100644 index 0000000000..dd5ebb0e1c --- /dev/null +++ b/examples/oft/deploy/MyOFTMock.ts @@ -0,0 +1,53 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'MyOFTMock' + +const deploy: DeployFunction = async (hre) => { + const { getNamedAccounts, deployments } = hre + + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + assert(deployer, 'Missing named deployer account') + + console.log(`Network: ${hre.network.name}`) + console.log(`Deployer: ${deployer}`) + + // This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2 + // + // @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments + // from @layerzerolabs packages based on the configuration in your hardhat config + // + // For this to work correctly, your network config must define an eid property + // set to `EndpointId` as defined in @layerzerolabs/lz-definitions + // + // For example: + // + // networks: { + // fuji: { + // ... + // eid: EndpointId.AVALANCHE_V2_TESTNET + // } + // } + const endpointV2Deployment = await hre.deployments.get('EndpointV2') + + const { address } = await deploy(contractName, { + from: deployer, + args: [ + 'MyOFT', // name + 'MOFT', // symbol + endpointV2Deployment.address, // LayerZero's EndpointV2 address + deployer, // owner + ], + log: true, + skipIfAlreadyDeployed: false, + }) + + console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`) +} + +deploy.tags = [contractName] + +export default deploy From 8cbf9607bbbdd60932b0d6f7f98d93ff480424d4 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 01:24:43 +0200 Subject: [PATCH 05/57] reduce mesh to 2 chains --- examples/oft/README.md | 10 ++++++---- examples/oft/deploy/MyOFT.ts | 4 ++-- examples/oft/deploy/MyOFTMock.ts | 4 ++-- examples/oft/hardhat.config.ts | 5 ----- examples/oft/layerzero.config.ts | 23 +---------------------- 5 files changed, 11 insertions(+), 35 deletions(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index d906d263b7..10da22328c 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -52,12 +52,14 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava 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: **Optimism**, **Avalanche**, and **Arbitrum**. +- 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: **Optimism** and **Arbitrum**. ## Build #### Compiling your contracts + + This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: ```bash @@ -108,13 +110,13 @@ cast send "mint(address,uint256)" --p ``` -Send 1 OFT from **Optimism Sepolia** to **Avalanche Fuji**: +Send 1 OFT from **Optimism Sepolia** to **Arbitrum Sepolia**: ```bash -npx hardhat lz:oft:send --src-eid 40232 --dst-eid 40106 --amount 1 --to +npx hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to ``` -> :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Avalanche Fuji respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. +> :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. Upon a successful send, the script will provide you with the link to the message on LayerZero Scan. diff --git a/examples/oft/deploy/MyOFT.ts b/examples/oft/deploy/MyOFT.ts index bcbdeb19b1..8970e45917 100644 --- a/examples/oft/deploy/MyOFT.ts +++ b/examples/oft/deploy/MyOFT.ts @@ -26,9 +26,9 @@ const deploy: DeployFunction = async (hre) => { // For example: // // networks: { - // fuji: { + // 'optimism-testnet': { // ... - // eid: EndpointId.AVALANCHE_V2_TESTNET + // eid: EndpointId.OPTSEP_V2_TESTNET // } // } const endpointV2Deployment = await hre.deployments.get('EndpointV2') diff --git a/examples/oft/deploy/MyOFTMock.ts b/examples/oft/deploy/MyOFTMock.ts index dd5ebb0e1c..cf4b6bb159 100644 --- a/examples/oft/deploy/MyOFTMock.ts +++ b/examples/oft/deploy/MyOFTMock.ts @@ -26,9 +26,9 @@ const deploy: DeployFunction = async (hre) => { // For example: // // networks: { - // fuji: { + // 'optimism-testnet': { // ... - // eid: EndpointId.AVALANCHE_V2_TESTNET + // eid: EndpointId.OPTSEP_V2_TESTNET // } // } const endpointV2Deployment = await hre.deployments.get('EndpointV2') diff --git a/examples/oft/hardhat.config.ts b/examples/oft/hardhat.config.ts index 45ab695a6e..4ee703b0ae 100644 --- a/examples/oft/hardhat.config.ts +++ b/examples/oft/hardhat.config.ts @@ -57,11 +57,6 @@ const config: HardhatUserConfig = { url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co', accounts, }, - 'avalanche-testnet': { - eid: EndpointId.AVALANCHE_V2_TESTNET, - url: process.env.RPC_URL_FUJI || 'https://avalanche-fuji.drpc.org', - accounts, - }, 'arbitrum-testnet': { eid: EndpointId.ARBSEP_V2_TESTNET, url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co', diff --git a/examples/oft/layerzero.config.ts b/examples/oft/layerzero.config.ts index 1b924c79ce..d7ccd27462 100644 --- a/examples/oft/layerzero.config.ts +++ b/examples/oft/layerzero.config.ts @@ -10,20 +10,13 @@ const optimismContract: OmniPointHardhat = { contractName: 'MyOFT', } -const avalancheContract: OmniPointHardhat = { - eid: EndpointId.AVALANCHE_V2_TESTNET, - contractName: 'MyOFT', -} - const arbitrumContract: OmniPointHardhat = { eid: EndpointId.ARBSEP_V2_TESTNET, contractName: 'MyOFT', } // To connect all the above chains to each other, we need the following pathways: -// Optimism <-> Avalanche // Optimism <-> Arbitrum -// Avalanche <-> Arbitrum // For this example's simplicity, we will use the same enforced options values for sending to all chains // For production, you should ensure `gas` is set to the correct value through profiling the gas usage of calling OFT._lzReceive(...) on the destination chain @@ -40,13 +33,6 @@ const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ // With the config generator, pathways declared are automatically bidirectional // i.e. if you declare A,B there's no need to declare B,A const pathways: TwoWayConfig[] = [ - [ - optimismContract, // Chain A contract - avalancheContract, // 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 - ], [ optimismContract, // Chain A contract arbitrumContract, // Chain C contract @@ -54,20 +40,13 @@ const pathways: TwoWayConfig[] = [ [1, 1], // [A to B confirmations, B to A confirmations] [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain A enforcedOptions ], - [ - avalancheContract, // Chain B contract - arbitrumContract, // Chain C contract - [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] - [1, 1], // [A to B confirmations, B to A confirmations] - [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain B enforcedOptions - ], ] export default async function () { // Generate the connections config based on the pathways const connections = await generateConnectionsConfig(pathways) return { - contracts: [{ contract: optimismContract }, { contract: avalancheContract }, { contract: arbitrumContract }], + contracts: [{ contract: optimismContract }, { contract: arbitrumContract }], connections, } } From bbcfc176d04c6ed828918bcb8cc9daea995df5ee Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 01:28:41 +0200 Subject: [PATCH 06/57] fix typos, use pnpm --- examples/oft/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index 10da22328c..20398fe9b8 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -56,7 +56,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava ## Build -#### Compiling your contracts +### Compiling your contracts @@ -113,7 +113,7 @@ cast send "mint(address,uint256)" --p Send 1 OFT from **Optimism Sepolia** to **Arbitrum Sepolia**: ```bash -npx hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to +pnpm hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to ``` > :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. @@ -230,7 +230,7 @@ Where: - `msgValue`: The amount of Ether sent with the message (in wei). - `numOfRuns`: The number of test runs to execute. -### Notes +#### Notes - Modify `numOfRuns` based 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. @@ -276,7 +276,7 @@ Then, modify `layerzero.config.ts` with the following changes: After applying the desired changes, make sure you re-run the wiring task: ```bash -npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts ``` ## Using Multisigs @@ -306,7 +306,7 @@ networks: { LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain.
- npx hardhat lz:deploy + pnpm hardhat lz:deploy
@@ -328,13 +328,13 @@ Deploys your contract to any of the available networks in your [`hardhat.config. More information about available CLI arguments can be found using the `--help` flag: ```bash -npx hardhat lz:deploy --help +pnpm hardhat lz:deploy --help ```
- npx hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME + pnpm hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME
@@ -343,7 +343,7 @@ Initializes a `layerzero.config.ts` file for all available pathways between your You can run this task by providing the `contract-name` you want to set for the config and `file-name` you want to generate: ```bash -npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME +pnpm hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME ``` This 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: @@ -429,7 +429,7 @@ export default {
- npx hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG
@@ -450,14 +450,14 @@ Running `lz:oapp:wire` will make the following function calls per pathway connec To use this task, run: ```bash -npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE +pnpm hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE ``` Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes.
- npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG
@@ -522,7 +522,7 @@ If you do NOT explicitly set each configuration parameter, your OApp will fallba
- npx hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG
From 8f4abe42059ea63737d35115699cb79bd1b117d8 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 01:33:29 +0200 Subject: [PATCH 07/57] ToC --- examples/oft/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/examples/oft/README.md b/examples/oft/README.md index 20398fe9b8..635404d01e 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -12,6 +12,31 @@

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.

+## Table of Contents + +- [Prerequisite Knowledge](#prerequisite-knowledge) +- [Requirements](#requirements) +- [Scaffold this example](#scaffold-this-example) +- [Helper Tasks](#helper-tasks) +- [Setup](#setup) +- [Build](#build) + - [Compiling your contracts](#compiling-your-contracts) +- [Deploy](#deploy) +- [Enable Messaging](#enable-messaging) +- [Sending OFTs](#sending-ofts) +- [Next Steps](#next-steps) +- [Production Deployment Checklist](#production-deployment-checklist) + - [Profiling `lzReceive` and `lzCompose` Gas Usage](#profiling-lzreceive-and-lzcompose-gas-usage) + - [Available Commands](#available-commands) + - [`lzReceive`](#lzreceive) + - [`lzCompose`](#lzcompose) + - [Notes](#notes) +- [Appendix](#appendix) + - [Running Tests](#running-tests) + - [Adding other chains](#adding-other-chains) + - [Using Multisigs](#using-multisigs) + - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + ## Prerequisite Knowledge - [What is an OFT (Omnichain Fungible Token) ?](https://docs.layerzero.network/v2/concepts/applications/oft-standard) From 1531986158ff29cef7e59718e61eac34e6784427 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 01:54:54 +0200 Subject: [PATCH 08/57] readme --- examples/oft/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/oft/README.md b/examples/oft/README.md index 635404d01e..7c9cdb74cd 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -135,6 +135,8 @@ cast send "mint(address,uint256)" --p ``` +> You can get the address of your OFT on Optimism Sepolia from the file at `./deployments/optimism-testnet/MyOFTMock.json` + Send 1 OFT from **Optimism Sepolia** to **Arbitrum Sepolia**: ```bash From 8c0ebbf5fcfc571c53c4aeacebd6a2ad25ae425f Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 02:02:56 +0200 Subject: [PATCH 09/57] oft send task --- examples/oft/README.md | 3 +- examples/oft/hardhat.config.ts | 2 + examples/oft/tasks/sendEvm.ts | 123 +++++++++++++++++++++++++++++++++ examples/oft/tasks/sendOFT.ts | 88 +++++++++++++++++++++++ examples/oft/tasks/types.ts | 4 ++ examples/oft/tasks/utils.ts | 51 ++++++++++++++ 6 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 examples/oft/tasks/sendEvm.ts create mode 100644 examples/oft/tasks/sendOFT.ts create mode 100644 examples/oft/tasks/types.ts create mode 100644 examples/oft/tasks/utils.ts diff --git a/examples/oft/README.md b/examples/oft/README.md index 7c9cdb74cd..24a3ba48a3 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -167,7 +167,8 @@ Now that you've gone through a simplified walkthrough, here are what you can do Before deploying, ensure the following: -- (required) you are not using `MyOFTMock`, which has a public `_mint` function +- (required) you are not using `MyOFTMock`, which has a public `mint` function + - ensure there is no mention of `MyOFTMock` in `layerzero.config.ts` - (recommended) you have profiled the gas usage of `lzReceive` on your destination chains diff --git a/examples/oft/hardhat.config.ts b/examples/oft/hardhat.config.ts index 4ee703b0ae..979e85c14d 100644 --- a/examples/oft/hardhat.config.ts +++ b/examples/oft/hardhat.config.ts @@ -13,6 +13,8 @@ import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' import { EndpointId } from '@layerzerolabs/lz-definitions' +import './tasks/sendOFT' + // Set your preferred authentication method // // If you prefer using a mnemonic, set a MNEMONIC environment variable diff --git a/examples/oft/tasks/sendEvm.ts b/examples/oft/tasks/sendEvm.ts new file mode 100644 index 0000000000..4c9118d645 --- /dev/null +++ b/examples/oft/tasks/sendEvm.ts @@ -0,0 +1,123 @@ +import { BigNumber, ContractTransaction } from 'ethers' +import { parseUnits } from 'ethers/lib/utils' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat' +import { createLogger } from '@layerzerolabs/io-devtools' +import { ChainType, endpointIdToChainType, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' + +import layerzeroConfig from '../layerzero.config' + +import { SendResult } from './types' +import { DebugLogger, KnownErrors, getLayerZeroScanLink } from './utils' + +const logger = createLogger() + +export interface EvmArgs { + srcEid: number + dstEid: number + amount: string + to: string + minAmount?: string + extraOptions?: string + composeMsg?: string + oftAddress?: string +} + +export async function sendEvm( + { srcEid, dstEid, amount, to, minAmount, extraOptions, composeMsg, oftAddress }: EvmArgs, + hre: HardhatRuntimeEnvironment +): Promise { + if (endpointIdToChainType(srcEid) !== ChainType.EVM) { + throw new Error(`non-EVM srcEid (${srcEid}) not supported here`) + } + + const getHreByEid = createGetHreByEid(hre) + let srcEidHre: HardhatRuntimeEnvironment + try { + srcEidHre = await getHreByEid(srcEid) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_GETTING_HRE, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + const signer = (await srcEidHre.ethers.getSigners())[0] + + // 1️⃣ resolve the OFT wrapper address + let wrapperAddress: string + if (oftAddress) { + wrapperAddress = oftAddress + } else { + const { contracts } = typeof layerzeroConfig === 'function' ? await layerzeroConfig() : layerzeroConfig + const wrapper = contracts.find((c) => c.contract.eid === srcEid) + if (!wrapper) throw new Error(`No config for EID ${srcEid}`) + wrapperAddress = wrapper.contract.contractName + ? (await srcEidHre.deployments.get(wrapper.contract.contractName)).address + : wrapper.contract.address! + } + + // 2️⃣ load IOFT ABI, extend it with token() + const ioftArtifact = await srcEidHre.artifacts.readArtifact('IOFT') + + // now attach + const oft = await srcEidHre.ethers.getContractAt(ioftArtifact.abi, wrapperAddress, signer) + + // 3️⃣ fetch the underlying ERC-20 + const underlying = await oft.token() + + // 4️⃣ fetch decimals from the underlying token + const erc20 = await srcEidHre.ethers.getContractAt('ERC20', underlying, signer) + const decimals: number = await erc20.decimals() + + // 5️⃣ normalize the user-supplied amount + const amountUnits: BigNumber = parseUnits(amount, decimals) + + // hex string → Uint8Array → zero-pad to 32 bytes + const toBytes = addressToBytes32(to) + + // 6️⃣ build sendParam and dispatch + const sendParam = { + dstEid, + to: toBytes, + amountLD: amountUnits.toString(), + minAmountLD: minAmount ? parseUnits(minAmount, decimals).toString() : amountUnits.toString(), + extraOptions: extraOptions ? extraOptions.toString() : '0x', + composeMsg: composeMsg ? composeMsg.toString() : '0x', + oftCmd: '0x', + } + + // 6️⃣ Quote (MessagingFee = { nativeFee, lzTokenFee }) + logger.info('Quoting the native gas cost for the send transaction...') + let msgFee: { nativeFee: BigNumber; lzTokenFee: BigNumber } + try { + msgFee = await oft.quoteSend(sendParam, false) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_QUOTING_NATIVE_GAS_COST, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + logger.info('Sending the transaction...') + let tx: ContractTransaction + try { + tx = await oft.send(sendParam, msgFee, signer.address, { + value: msgFee.nativeFee, + }) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_SENDING_TRANSACTION, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + const receipt = await tx.wait() + + const txHash = receipt.transactionHash + const scanLink = getLayerZeroScanLink(txHash, srcEid >= 40_000 && srcEid < 50_000) + + return { txHash, scanLink } +} diff --git a/examples/oft/tasks/sendOFT.ts b/examples/oft/tasks/sendOFT.ts new file mode 100644 index 0000000000..6875e2783b --- /dev/null +++ b/examples/oft/tasks/sendOFT.ts @@ -0,0 +1,88 @@ +import { task, types } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { ChainType, endpointIdToChainType, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' + +import { EvmArgs, sendEvm } from './sendEvm' +import { SendResult } from './types' +import { DebugLogger, KnownOutputs, KnownWarnings, getBlockExplorerLink } from './utils' + +interface MasterArgs { + srcEid: number + dstEid: number + amount: string + to: string + /** Minimum amount to receive in case of custom slippage or fees (human readable units, e.g. "1.5") */ + minAmount?: string + /** Extra options for sending additional gas units to lzReceive, lzCompose, or receiver address */ + extraOptions?: string + /** Arbitrary bytes message to deliver alongside the OFT */ + composeMsg?: string + /** EVM: 20-byte hex address */ + oftAddress?: string +} + +task('lz:oft:send', 'Sends OFT tokens cross‐chain from EVM chains') + .addParam('srcEid', 'Source endpoint ID', undefined, types.int) + .addParam('dstEid', 'Destination endpoint ID', undefined, types.int) + .addParam('amount', 'Amount to send (human readable units, e.g. "1.5")', undefined, types.string) + .addParam('to', 'Recipient address (20-byte hex for EVM)', undefined, types.string) + .addOptionalParam( + 'minAmount', + 'Minimum amount to receive in case of custom slippage or fees (human readable units, e.g. "1.5")', + undefined, + types.string + ) + .addOptionalParam( + 'extraOptions', + 'Extra options for sending additional gas units to lzReceive, lzCompose, or receiver address', + undefined, + types.string + ) + .addOptionalParam( + 'oftAddress', + 'Override the source local deployment OFT address (20-byte hex for EVM)', + undefined, + types.string + ) + .setAction(async (args: MasterArgs, hre: HardhatRuntimeEnvironment) => { + const chainType = endpointIdToChainType(args.srcEid) + let result: SendResult + + if (args.oftAddress) { + DebugLogger.printWarning( + KnownWarnings.USING_OVERRIDE_OFT, + `For network: ${endpointIdToNetwork(args.srcEid)}, OFT: ${args.oftAddress}` + ) + } + + // Only support EVM chains in this example + if (chainType === ChainType.EVM) { + result = await sendEvm(args as EvmArgs, hre) + } else { + throw new Error( + `The chain type ${chainType} is not supported in this OFT example. Only EVM chains are supported.` + ) + } + + DebugLogger.printLayerZeroOutput( + KnownOutputs.SENT_VIA_OFT, + `Successfully sent ${args.amount} tokens from ${endpointIdToNetwork(args.srcEid)} to ${endpointIdToNetwork(args.dstEid)}` + ) + + // print the explorer link for the srcEid from metadata + const explorerLink = await getBlockExplorerLink(args.srcEid, result.txHash) + // if explorer link is available, print the tx hash link + if (explorerLink) { + DebugLogger.printLayerZeroOutput( + KnownOutputs.TX_HASH, + `Explorer link for source chain ${endpointIdToNetwork(args.srcEid)}: ${explorerLink}` + ) + } + + // print the LayerZero Scan link from metadata + DebugLogger.printLayerZeroOutput( + KnownOutputs.EXPLORER_LINK, + `LayerZero Scan link for tracking all cross-chain transaction details: ${result.scanLink}` + ) + }) diff --git a/examples/oft/tasks/types.ts b/examples/oft/tasks/types.ts new file mode 100644 index 0000000000..8d2b681692 --- /dev/null +++ b/examples/oft/tasks/types.ts @@ -0,0 +1,4 @@ +export interface SendResult { + txHash: string // EVM: receipt.transactionHash + scanLink: string // LayerZeroScan link for cross-chain tracking +} diff --git a/examples/oft/tasks/utils.ts b/examples/oft/tasks/utils.ts new file mode 100644 index 0000000000..934c9a046d --- /dev/null +++ b/examples/oft/tasks/utils.ts @@ -0,0 +1,51 @@ +import { createLogger } from '@layerzerolabs/io-devtools' +import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { Options } from '@layerzerolabs/lz-v2-utilities' + +const logger = createLogger() + +export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' + +/** + * Given a srcEid and on-chain tx hash, return + * `https://…blockExplorers[0].url/tx/`, or undefined. + */ +export async function getBlockExplorerLink(srcEid: number, txHash: string): Promise { + const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" + const res = await fetch(deploymentMetadataUrl) + if (!res.ok) return + const all = (await res.json()) as Record + const meta = all[network] + const explorer = meta?.blockExplorers?.[0]?.url + if (explorer) { + // many explorers use `/tx/` + return `${explorer.replace(/\/+$/, '')}/tx/${txHash}` + } + return +} + +function formatBigIntForDisplay(n: bigint) { + return n.toLocaleString().replace(/,/g, '_') +} + +export function decodeLzReceiveOptions(hex: string): string { + try { + // Handle empty/undefined values first + if (!hex || hex === '0x') return 'No options set' + const options = Options.fromOptions(hex) + const lzReceiveOpt = options.decodeExecutorLzReceiveOption() + return lzReceiveOpt + ? `gas: ${formatBigIntForDisplay(lzReceiveOpt.gas)} , value: ${formatBigIntForDisplay(lzReceiveOpt.value)} wei` + : 'No executor options' + } catch (e) { + return `Invalid options (${hex.slice(0, 12)}...)` + } +} + +// Get LayerZero scan link +export function getLayerZeroScanLink(txHash: string, isTestnet = false): string { + const baseUrl = isTestnet ? 'https://testnet.layerzeroscan.com' : 'https://layerzeroscan.com' + return `${baseUrl}/tx/${txHash}` +} + +export { DebugLogger, KnownErrors, KnownOutputs, KnownWarnings } from '@layerzerolabs/io-devtools' From 679f5fb30022dab64d0d695dab8237a1bfb19c2c Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 02:03:10 +0200 Subject: [PATCH 10/57] use mock --- examples/oft/layerzero.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft/layerzero.config.ts b/examples/oft/layerzero.config.ts index d7ccd27462..5c8d9853ed 100644 --- a/examples/oft/layerzero.config.ts +++ b/examples/oft/layerzero.config.ts @@ -7,7 +7,7 @@ import type { OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' const optimismContract: OmniPointHardhat = { eid: EndpointId.OPTSEP_V2_TESTNET, - contractName: 'MyOFT', + contractName: 'MyOFTMock', // Note: change this to your production version } const arbitrumContract: OmniPointHardhat = { From 0fb0258bc1492044c1992c8596e7d3f46d052e1f Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 02:05:49 +0200 Subject: [PATCH 11/57] mock --- examples/oft/README.md | 2 +- examples/oft/layerzero.config.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index 24a3ba48a3..928e3018ad 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -168,7 +168,7 @@ Now that you've gone through a simplified walkthrough, here are what you can do Before deploying, ensure the following: - (required) you are not using `MyOFTMock`, which has a public `mint` function - - ensure there is no mention of `MyOFTMock` in `layerzero.config.ts` + - In `layerzero.config.ts`, ensure you are not using `MyOFTMock` as the `contractName` for any of the contract objects. - (recommended) you have profiled the gas usage of `lzReceive` on your destination chains diff --git a/examples/oft/layerzero.config.ts b/examples/oft/layerzero.config.ts index 5c8d9853ed..34fd8de61e 100644 --- a/examples/oft/layerzero.config.ts +++ b/examples/oft/layerzero.config.ts @@ -7,12 +7,12 @@ import type { OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' const optimismContract: OmniPointHardhat = { eid: EndpointId.OPTSEP_V2_TESTNET, - contractName: 'MyOFTMock', // Note: change this to your production version + contractName: 'MyOFTMock', // Note: change this to 'MyOFT' or your production contract name } const arbitrumContract: OmniPointHardhat = { eid: EndpointId.ARBSEP_V2_TESTNET, - contractName: 'MyOFT', + contractName: 'MyOFTMock', // Note: change this to 'MyOFT' or your production contract name } // To connect all the above chains to each other, we need the following pathways: From 07af20c1e3b05de4a589fde9a1d510453f0a9a35 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 13:40:27 +0200 Subject: [PATCH 12/57] update to include optional introduction --- docs/EXAMPLES_DEVELOPMENT.md | 105 +++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/docs/EXAMPLES_DEVELOPMENT.md b/docs/EXAMPLES_DEVELOPMENT.md index acdc7971cf..5675b14a1c 100644 --- a/docs/EXAMPLES_DEVELOPMENT.md +++ b/docs/EXAMPLES_DEVELOPMENT.md @@ -6,83 +6,92 @@ Currently, this document will only detail the structure for the READMEs of the e 1. **Header** - Goal: Branding + promote docs site + entrypoint - - Contents: LayerZero logo + links to docs and dev site + - Contents: LayerZero logo + links to docs and dev portal 2. **Example Title** - Goal: What the example will teach - - Contents: Title and 1–2 sentence description (possibly goal-oriented) + - Contents: Title + 1–2 sentence description (goal-oriented preferred) -3. **Prerequisite Knowledge** +3. **Table of Contents** + - Goal: Allow user to easily navigate the README + - Contents: TOC of all headings + +4. **Prerequisite Knowledge** - Goal: What to understand before running the example - - Contents: Short list (≤3 items) like OApp, OFT + - Contents: e.g., What is an OApp? What is an OFT? + +5. **Introduction** _(optional)_ + - Goal: What is this example about + - Contents: Brief explanation if needed; otherwise rely on title + prerequisites -4. **Requirements** +6. **Requirements** - Goal: What needs to be installed - - Contents: Tools + exact versions + - Contents: List tools + version numbers; consider noting testnet token needs -5. **Scaffold this example** +7. **Scaffold this example** - Goal: How to init the example - - Contents: `npx create-lz-oapp@latest --example ` + - Contents: `pnpm dlx create-lz-oapp@latest --example ` -6. **Helper Tasks (inline notice)** +8. **Helper Tasks (inline notice)** - Goal: Know that helper tasks exist - - Contents: Statement + link to helper section + - Contents: Single line pointing to the detailed section -7. **Setup** +9. **Setup** - Goal: What to configure before running - - Contents: .env instructions, deployer account setup + - Contents: `.env` instructions, deployer account setup -8. **Build** - - Goal: How to build contracts/programs/modules - - Contents: Build command(s) +10. **Build** + - Goal: How to build contracts/programs/modules + - Contents: Build command(s) -9. **Deploy** - - Goal: How to deploy contracts/programs/modules - - Contents: Deploy command + minting instructions (if needed) +11. **Deploy** + - Goal: How to deploy contracts/programs/modules + - Contents: Deploy command + minting instructions (if applicable) -10. **Wiring / Configuring OApps** - - Goal: How to wire OApps for cross-chain use +12. **Wiring / Configuring OApps** + - Goal: How to set up OApps for use - Contents: LZ config, init step, wiring step -11. **Sending Message/OFT/ONFT** - - Goal: How to trigger cross-chain action - - Contents: Command to send message/OFT/ONFT, both/all directions +13. **Sending Message/OFT/ONFT** + - Goal: How to trigger cross-chain actions + - Contents: Send command(s) for both/all directions -12. **Next Steps** - - Goal: What to know after initial deployment - - Contents: Links to Production Checklist, Security Stack, Message Options +14. **Next Steps** + - Goal: What to know after basic deployment + - Contents: Production Checklist + links to Security Stack, Message Options -13. **Production Deployment Checklist** - - Goal: What’s needed for production readiness - - Contents: Gas profiling, DVN config, confirmation count +15. **Production Deployment Checklist** + - Goal: Prepare for production + - Contents: Gas profiling, DVNs, confirmations -14. **Appendix** +16. **Appendix** - Goal: Mark end of main build steps - - Contents: Supplementary instructions and optional configurations + - Contents: Additional configuration, testing, and advanced info - 14.1. **Running tests** - - Goal: How to test the contracts/programs - - Contents: Test commands + 16.1. **Running tests** + - Goal: How to test contracts/programs + - Contents: Test command(s) - 14.2. **Adding other chains** - - Goal: How to add additional networks - - Contents: How to add chains + example config (e.g. modify `hardhat.config.ts`) + 16.2. **Adding other chains** + - Goal: Support more networks + - Contents: Add chain logic; update `hardhat.config.ts` - 14.3. **Using Multisigs** - - Goal: How to deploy if using a multisig - - Contents: Command param diffs + multi-VM notes + 16.3. **Using Multisigs** + - Goal: Deploy using multisig + - Contents: Command param diffs; multi-VM notes - 14.4. **LayerZero Hardhat Helper Tasks (detailed)** + 16.4. **LayerZero Hardhat Helper Tasks (detailed)** - Goal: Know all available helpers - - Contents: Link to docs + list of built-in and local helper tasks + - Contents: Link to docs + built-in + local tasks + + 16.5. **Contract/Program Verification** + - Goal: How to verify deployments + - Contents: Per-VM verification doc links - 14.5. **Contract/Program Verification** - - Goal: How to verify - - Contents: Links to verification docs (per VM) + 16.6. **Troubleshooting** + - Goal: Solve common issues + - Contents: Link to global troubleshooting + local fixes - 14.6. **Troubleshooting** - - Goal: How to debug errors/issues - - Contents: Link to global page + example-specific fixes From 9e6691150b483efe3779fe8033a4522af7e037c2 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 13:40:38 +0200 Subject: [PATCH 13/57] wording tweak --- examples/oft/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index 928e3018ad..a661b623d2 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -100,7 +100,7 @@ pnpm compile:hardhat ## Deploy -To deploy your contracts to your desired blockchains, run the following command: +To deploy the OFT contracts to your desired blockchains, run the following command: ```bash pnpm hardhat lz:deploy --tags MyOFTMock From a3532d4ac525b443aa519794f49cd9e41ac228f4 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 13:40:47 +0200 Subject: [PATCH 14/57] simplify to 2 --- examples/oft-adapter/hardhat.config.ts | 17 +++----- examples/oft-adapter/layerzero.config.ts | 52 +++++++----------------- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/examples/oft-adapter/hardhat.config.ts b/examples/oft-adapter/hardhat.config.ts index b1fbe86f84..853ff62796 100644 --- a/examples/oft-adapter/hardhat.config.ts +++ b/examples/oft-adapter/hardhat.config.ts @@ -54,22 +54,17 @@ const config: HardhatUserConfig = { ], }, networks: { - 'sepolia-testnet': { - eid: EndpointId.SEPOLIA_V2_TESTNET, - url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/', + 'optimism-testnet': { + eid: EndpointId.OPTSEP_V2_TESTNET, + url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co', accounts, oftAdapter: { tokenAddress: '0x0', // Set the token address for the OFT adapter }, }, - 'avalanche-testnet': { - eid: EndpointId.AVALANCHE_V2_TESTNET, - url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji', - accounts, - }, - 'amoy-testnet': { - eid: EndpointId.AMOY_V2_TESTNET, - url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com', + 'arbitrum-testnet': { + eid: EndpointId.ARBSEP_V2_TESTNET, + url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co', accounts, }, hardhat: { diff --git a/examples/oft-adapter/layerzero.config.ts b/examples/oft-adapter/layerzero.config.ts index 01f77dd4b3..f899110da1 100644 --- a/examples/oft-adapter/layerzero.config.ts +++ b/examples/oft-adapter/layerzero.config.ts @@ -8,66 +8,42 @@ import type { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/tool * * for example: * - * sepolia: { - * eid: EndpointId.SEPOLIA_V2_TESTNET, - * url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/', - * accounts, + * 'optimism-testnet': { + * eid: EndpointId.OPTSEP_V2_TESTNET, + * url: process.env.RPC_URL_OP_SEPOLIA || 'https://* optimism-sepolia.gateway.tenderly.co', + * accounts, * oftAdapter: { * tokenAddress: '0x0', // Set the token address for the OFT adapter * }, * }, */ -const sepoliaContract: OmniPointHardhat = { - eid: EndpointId.SEPOLIA_V2_TESTNET, +const optimismContract: OmniPointHardhat = { + eid: EndpointId.OPTSEP_V2_TESTNET, contractName: 'MyOFTAdapter', } -const fujiContract: OmniPointHardhat = { - eid: EndpointId.AVALANCHE_V2_TESTNET, - contractName: 'MyOFT', -} - -const amoyContract: OmniPointHardhat = { - eid: EndpointId.AMOY_V2_TESTNET, +const arbitrumContract: OmniPointHardhat = { + eid: EndpointId.ARBSEP_V2_TESTNET, contractName: 'MyOFT', } const config: OAppOmniGraphHardhat = { contracts: [ { - contract: fujiContract, + contract: optimismContract, }, { - contract: sepoliaContract, - }, - { - contract: amoyContract, + contract: arbitrumContract, }, ], connections: [ { - from: fujiContract, - to: sepoliaContract, - }, - { - from: fujiContract, - to: amoyContract, - }, - { - from: sepoliaContract, - to: fujiContract, - }, - { - from: sepoliaContract, - to: amoyContract, - }, - { - from: amoyContract, - to: sepoliaContract, + from: optimismContract, + to: arbitrumContract, }, { - from: amoyContract, - to: fujiContract, + from: optimismContract, + to: arbitrumContract, }, ], } From 0607d8cb7b8ce0400586a947c6deee785e6918e1 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 13:40:57 +0200 Subject: [PATCH 15/57] add deploy script --- examples/oft-adapter/deploy/MyERC20Mock.ts | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/oft-adapter/deploy/MyERC20Mock.ts diff --git a/examples/oft-adapter/deploy/MyERC20Mock.ts b/examples/oft-adapter/deploy/MyERC20Mock.ts new file mode 100644 index 0000000000..b25cc83045 --- /dev/null +++ b/examples/oft-adapter/deploy/MyERC20Mock.ts @@ -0,0 +1,37 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'MyERC20Mock' + +const deploy: DeployFunction = async (hre) => { + const { getNamedAccounts, deployments } = hre + + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + assert(deployer, 'Missing named deployer account') + + console.log(`Network: ${hre.network.name}`) + console.log(`Deployer: ${deployer}`) + + // Deploy the mock ERC20 token + // This is typically used as the "inner token" for OFT Adapter testing + const { address } = await deploy(contractName, { + from: deployer, + args: [ + 'MyERC20Mock', // name + 'MERC20', // symbol + ], + log: true, + skipIfAlreadyDeployed: false, + }) + + console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`) + console.log(`Use this address as the tokenAddress in your oftAdapter network configuration:`) + console.log(`oftAdapter: { tokenAddress: '${address}' }`) +} + +deploy.tags = [contractName] + +export default deploy From d60399fe32f03954aea6ef61d79a7b260d207c5a Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 13:41:03 +0200 Subject: [PATCH 16/57] revamp readme --- examples/oft-adapter/README.md | 164 ++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 65 deletions(-) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 15f4be147d..1776abf1bd 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -1,42 +1,98 @@

- LayerZero + LayerZero

- Homepage | Docs | Developers + LayerZero Docs

-

OFTAdapter Example

+

OFT Adapter Example

-

- Quickstart | Configuration | Message Execution Options | Endpoint Addresses -

+

Template project for converting an existing token into a cross-chain token (OFT) using 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.

+ +## Table of Contents + +- [Prerequisite Knowledge](#prerequisite-knowledge) +- [Requirements](#requirements) +- [Scaffold this example](#scaffold-this-example) +- [Helper Tasks](#helper-tasks) +- [Setup](#setup) +- [Build](#build) + - [Compiling your contracts](#compiling-your-contracts) +- [Deploy](#deploy) +- [Enable Messaging](#enable-messaging) +- [Sending OFTs](#sending-ofts) +- [Next Steps](#next-steps) +- [Production Deployment Checklist](#production-deployment-checklist) + - [Profiling `lzReceive` and `lzCompose` Gas Usage](#profiling-lzreceive-and-lzcompose-gas-usage) + - [Available Commands](#available-commands) + - [`lzReceive`](#lzreceive) + - [`lzCompose`](#lzcompose) + - [Notes](#notes) +- [Appendix](#appendix) + - [Running Tests](#running-tests) + - [Adding other chains](#adding-other-chains) + - [Using Multisigs](#using-multisigs) + - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + +## Prerequisite Knowledge -

Template project for getting started with LayerZero's OFTAdapter contract development.

+- [What is an OFT (Omnichain Fungible Token) ?](https://docs.layerzero.network/v2/concepts/applications/oft-standard) +- [What is an OApp (Omnichain Application) ?](https://docs.layerzero.network/v2/concepts/applications/oapp-standard) -### OFTAdapter additional setup: -- In your `hardhat.config.ts` file, add the following configuration to the network you want to deploy the OFTAdapter to: - ```typescript - // Replace `0x0` with the address of the ERC20 token you want to adapt to the OFT functionality. - oftAdapter: { - tokenAddress: '0x0', - } - ``` +## Introduction +**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. -## 1) Developing Contracts + -#### Installing dependencies +## Requirements -We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): +- `Node.js` - ` >=18.16.0` +- `pnpm` (recommended) - or another package manager of your choice (npm, yarn) +- `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation + + +## Scaffold this example + +Create your local copy of this example: ```bash -pnpm install +pnpm dlx create-lz-oapp@latest --example oft-adapter ``` -#### Compiling your contracts +Specify the directory, select `OFTAdapter` and proceed with the installation. + +Note that `create-lz-oapp` will also automatically run the dependencies install step for you. + + +## Helper Tasks + +Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the [LayerZero Hardhat Helper Tasks section](#layerzero-hardhat-helper-tasks). All commands can be run at the project root. + +## Setup + +- Copy `.env.example` into a new `.env` +- Set up your deployer address/account via the `.env` + + - You can specify either `MNEMONIC` or `PRIVATE_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: **Optimism** and **Arbitrum**. + + +## Build + +### Compiling your contracts + + This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: @@ -51,70 +107,48 @@ pnpm compile:forge pnpm compile:hardhat ``` -Or adjust the `package.json` to for example remove `forge` build: - -```diff -- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", -- "compile:forge": "forge build", -- "compile:hardhat": "hardhat compile", -+ "compile": "hardhat compile" -``` - -#### Running tests +## Deploy -Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: +First, deploy the inner token to (only) **Optimism Sepolia**. ```bash -pnpm test +pnpm hardhat lz:deploy --tags MyERC20Mock --networks optimism-testnet ``` -If you prefer one over the other, you can use the tooling-specific commands: +Note the address logged (inner token's address) upon successful deployment as you need it for the next step. Else, you can also refer to `./deployments/optimism-testnet/MyERC20Mock.json`. -```bash -pnpm test:forge -pnpm test:hardhat -``` +> :information_source: MyERC20Mock will be used as it provides a public mint function which we require for testing. Ensure you do not use this for production. -Or adjust the `package.json` to for example remove `hardhat` tests: +In the `hardhat.config.ts` file, add the inner token's address to the network you want to deploy the OFTAdapter to: -```diff -- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", -- "test:forge": "forge test", -- "test:hardhat": "$npm_execpath hardhat test" -+ "test": "forge test" +```typescript +// Replace `0x0` with the address of the ERC20 token you want to adapt to the OFT functionality. +oftAdapter: { + tokenAddress: '', +} ``` -## 2) Deploying Contracts +Deploy an OFTAdapter to Optimism Sepolia: -Set up deployer wallet/account: +```bash +pnpm hardhat lz:deploy --tags MyOFTAdapter --networks optimism-testnet +``` -- Rename `.env.example` -> `.env` -- Choose your preferred means of setting up your deployer wallet/account: +Deploy the OFT to Arbitrum Sepolia: -``` -MNEMONIC="test test test test test test test test test test test junk" -or... -PRIVATE_KEY="0xabc...def" +```bash +pnpm hardhat lz:deploy --tags MyOFT --networks arbitrum-testnet ``` -- Fund this address with the corresponding chain's native tokens you want to deploy to. +## Enable Messaging -To deploy your contracts to your desired blockchains, run the following command in your project's folder: +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](https://docs.layerzero.network/v2/concepts/glossary#wire--wiring) task. -```bash -npx hardhat lz:deploy -``` -More information about available CLI arguments can be found using the `--help` flag: +Run the wiring task: ```bash -npx hardhat lz:deploy --help +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts ``` -By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. - -

- -

- Join our community! | Follow us on X (formerly Twitter) -

+Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. From a30e0784ff52cf05817754d4e4c30a3b46dea750 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 14:30:11 +0200 Subject: [PATCH 17/57] amount --- examples/oft/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft/README.md b/examples/oft/README.md index a661b623d2..aaf979bcdf 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -131,7 +131,7 @@ With your OFTs wired, you can now send them cross chain. First, via the mock contract, let's mint on **Optimism Sepolia**: ``` -cast send "mint(address,uint256)" --private-key --rpc-url +cast send "mint(address,uint256)" 1000000000000000000 --private-key --rpc-url ``` From 57cac467cfddc9a1deebde1f55e8e8d9b875c9f0 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 14:40:23 +0200 Subject: [PATCH 18/57] fix variable name --- examples/oft-adapter/deploy/MyERC20Mock.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/oft-adapter/deploy/MyERC20Mock.ts b/examples/oft-adapter/deploy/MyERC20Mock.ts index bbb93605e0..916d453393 100644 --- a/examples/oft-adapter/deploy/MyERC20Mock.ts +++ b/examples/oft-adapter/deploy/MyERC20Mock.ts @@ -35,12 +35,12 @@ const deploy: DeployFunction = async (hre) => { // Mint initial tokens to the deployer const [signer] = await hre.ethers.getSigners() - const mintBurnToken = await hre.ethers.getContractAt(contractName, address, signer) + const innerToken = await hre.ethers.getContractAt(contractName, address, signer) - const mintTx = await mintBurnToken.mint(deployer, initialMintAmount) + const mintTx = await innerToken.mint(deployer, initialMintAmount) await mintTx.wait() - const balance = await mintBurnToken.balanceOf(deployer) + const balance = await innerToken.balanceOf(deployer) console.log(`Minted ${hre.ethers.utils.formatEther(balance)} ${tokenSymbol} tokens to deployer: ${deployer}`) } From 3c8ae8895481273a54c17fe668fb9e11736e17f3 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 14:44:33 +0200 Subject: [PATCH 19/57] rm avalanche --- examples/oft-adapter/layerzero.config.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/examples/oft-adapter/layerzero.config.ts b/examples/oft-adapter/layerzero.config.ts index 5950720b20..46f8ff8af3 100644 --- a/examples/oft-adapter/layerzero.config.ts +++ b/examples/oft-adapter/layerzero.config.ts @@ -48,13 +48,6 @@ const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ // With the config generator, pathways declared are automatically bidirectional // i.e. if you declare A,B there's no need to declare B,A const pathways: TwoWayConfig[] = [ - [ - optimismContract, // Chain A contract - avalancheContract, // 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 - ], [ optimismContract, // Chain A contract arbitrumContract, // Chain C contract @@ -62,20 +55,13 @@ const pathways: TwoWayConfig[] = [ [1, 1], // [A to B confirmations, B to A confirmations] [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain A enforcedOptions ], - [ - avalancheContract, // Chain B contract - arbitrumContract, // Chain C contract - [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] - [1, 1], // [A to B confirmations, B to A confirmations] - [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain B enforcedOptions - ], ] export default async function () { // Generate the connections config based on the pathways const connections = await generateConnectionsConfig(pathways) return { - contracts: [{ contract: optimismContract }, { contract: avalancheContract }, { contract: arbitrumContract }], + contracts: [{ contract: optimismContract }, { contract: arbitrumContract }], connections, } } From 67161ce9dbb500672bc453e882791a77e5d75d28 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 14:44:50 +0200 Subject: [PATCH 20/57] note on minting --- examples/oft-adapter/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 1776abf1bd..0acdbb8a5e 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -115,10 +115,13 @@ First, deploy the inner token to (only) **Optimism Sepolia**. pnpm hardhat lz:deploy --tags MyERC20Mock --networks optimism-testnet ``` -Note the address logged (inner token's address) upon successful deployment as you need it for the next step. Else, you can also refer to `./deployments/optimism-testnet/MyERC20Mock.json`. +The deploy script for **MyERC20Mock** will also mint 10 tokens to the deployer address. + +On the `Deployed Contract` line, note the `address` logged (inner token's address) upon successful deployment as you need it for the next step. Else, you can also refer to `./deployments/optimism-testnet/MyERC20Mock.json`. > :information_source: MyERC20Mock will be used as it provides a public mint function which we require for testing. Ensure you do not use this for production. + In the `hardhat.config.ts` file, add the inner token's address to the network you want to deploy the OFTAdapter to: ```typescript From a3f8f844824f794641d4a673b0b6666ac5dbbe2c Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 14:50:42 +0200 Subject: [PATCH 21/57] sending --- examples/oft-adapter/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 0acdbb8a5e..5f1570412d 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -155,3 +155,21 @@ pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts ``` Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. + +## Sending OFTs + +With your OFTs wired, you can now send them cross chain. + +Send 1 OFT from **Optimism Sepolia** to **Arbitrum Sepolia**: + +```bash +pnpm hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to +``` + +> :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) 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! \ No newline at end of file From ef0606887d924227d3e7384bd3822dafe9c41378 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 15:58:01 +0200 Subject: [PATCH 22/57] oft adapter readme complete --- examples/oft-adapter/README.md | 564 ++++++++++++++++++++++++++++++++- 1 file changed, 563 insertions(+), 1 deletion(-) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 5f1570412d..699b63a94c 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -36,6 +36,8 @@ - [Adding other chains](#adding-other-chains) - [Using Multisigs](#using-multisigs) - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + - [Contract Verification](#contract-verification) + - [Troubleshooting](#troubleshooting) ## Prerequisite Knowledge @@ -172,4 +174,564 @@ Upon a successful send, the script will provide you with the link to the message 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! \ No newline at end of file +Congratulations, you have now sent an OFT cross-chain! + +> If you run into any issues, refer to [Troubleshooting](#troubleshooting). + +## Next Steps + +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](#production-deployment-checklist). +- Read on [DVNs / Security Stack](https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns) +- Read on [Message Execution Options](https://docs.layerzero.network/v2/concepts/technical-reference/options-reference) + +## Production Deployment Checklist + + + +Before deploying, ensure the following: + +- (required) you are not using `MyOFTMock`, which has a public `mint` function + - In `layerzero.config.ts`, ensure you are not using `MyOFTMock` as the `contractName` for any of the contract objects. +- (recommended) you have profiled the gas usage of `lzReceive` on your destination chains + + +### Profiling `lzReceive` and `lzCompose` Gas Usage + +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. + +### Available Commands + +1. **`gas:lzReceive`** + + This command profiles the `lzReceive` function for estimating gas usage across multiple runs. + + ```json + "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes,uint256,uint256)'" + ``` + +2. **`gas:lzCompose`** + + This command profiles the `lzCompose` function for estimating gas usage across multiple runs. + + ```json + "gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes,uint256,uint256)'" + ``` + +### Usage Examples + +#### `lzReceive` + +To estimate the gas for the `lzReceive` function: + +```bash +pnpm gas:lzReceive + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msg.value`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +#### `lzCompose` + +To estimate the gas for the `lzCompose` function: + +```bash +pnpm gas:lzCompose + \ + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msgValue`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +#### Notes + +- Modify `numOfRuns` based 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) +

+ +# Appendix + +## Running Tests + +Similar to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: + +```bash +pnpm test +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm test:forge +pnpm test:hardhat +``` + +## Adding other chains + + + +If 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 `eid` and `contractName`) +- decide whether to use an existing EVM enforced options variable or declare a new one +- create a new entry in the `pathways` variable +- add the new contract into the `contracts` key of the `return` of the `export default` function + +After applying the desired changes, make sure you re-run the wiring task: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +## Using Multisigs + +The 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`: + +```typescript +// 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 Hardhat Helper Tasks + +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`](./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](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`. + +```typescript +'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: + +```bash +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: + +```bash +pnpm hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME +``` + +This 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: + +```typescript +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): + +- function setPeer(uint32 \_eid, bytes32 \_peer) public virtual onlyOwner {} + +- function setConfig(address \_oapp, address \_lib, SetConfigParam[] calldata \_params) external onlyRegistered(\_lib) {} + +- function setEnforcedOptions(EnforcedOptionParam[] calldata \_enforcedOptions) public virtual onlyOwner {} + +- function setSendLibrary(address \_oapp, uint32 \_eid, address \_newLib) external onlyRegisteredOrDefault(\_newLib) isSendLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +- function setReceiveLibrary(address \_oapp, uint32 \_eid, address \_newLib, uint256 \_gracePeriod) external onlyRegisteredOrDefault(\_newLib) isReceiveLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +To use this task, run: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE +``` + +Whenever 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.ts` currently 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. + +```bash +┌────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐ +│ │ 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`](https://docs.layerzero.network/v2/developers/evm/gas-settings/options). + +```bash +┌───────────────────┬────────────────────────────────────────────┐ +│ localNetworkName │ mantle │ +├───────────────────┼────────────────────────────────────────────┤ +│ remoteNetworkName │ polygon │ +├───────────────────┼────────────────────────────────────────────┤ +│ executorDstConfig │ ┌────────────────┬───────────────────────┐ │ +│ │ │ baseGas │ 85000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ multiplierBps │ 12000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ floorMarginUSD │ 5000000000000000000 │ │ +│ │ ├────────────────┼───────────────────────┤ │ +│ │ │ nativeCap │ 681000000000000000000 │ │ +│ │ └────────────────┴───────────────────────┘ │ +│ │ │ +└───────────────────┴────────────────────────────────────────────┘ +``` + +
+ +### Manual Configuration + + + +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: + +```typescript +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: + +```typescript +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 +]; +``` + +### Contract Verification + +You can verify EVM chain contracts using the LayerZero helper package: + +```bash +pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts +``` + +### Troubleshooting + +Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). \ No newline at end of file From 2807cfd685eb9f7692bf3d1f39a4a8d328f9df8a Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 19:02:42 +0200 Subject: [PATCH 23/57] update ToC --- examples/oft/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/oft/README.md b/examples/oft/README.md index aaf979bcdf..9cde874f27 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -30,12 +30,16 @@ - [Available Commands](#available-commands) - [`lzReceive`](#lzreceive) - [`lzCompose`](#lzcompose) + - [Usage Examples](#usage-examples) - [Notes](#notes) - [Appendix](#appendix) - [Running Tests](#running-tests) - [Adding other chains](#adding-other-chains) - [Using Multisigs](#using-multisigs) - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + - [Manual Configuration](#manual-configuration) + - [Contract Verification](#contract-verification) + - [Troubleshooting](#troubleshooting) ## Prerequisite Knowledge From 1165dc8c00a99bf6361057d26b3bc9e62e6ab520 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 19:04:02 +0200 Subject: [PATCH 24/57] update ToC --- examples/oft-adapter/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 699b63a94c..709389af92 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -15,6 +15,7 @@ ## Table of Contents - [Prerequisite Knowledge](#prerequisite-knowledge) +- [Introduction](#introduction) - [Requirements](#requirements) - [Scaffold this example](#scaffold-this-example) - [Helper Tasks](#helper-tasks) @@ -30,12 +31,14 @@ - [Available Commands](#available-commands) - [`lzReceive`](#lzreceive) - [`lzCompose`](#lzcompose) + - [Usage Examples](#usage-examples) - [Notes](#notes) - [Appendix](#appendix) - [Running Tests](#running-tests) - [Adding other chains](#adding-other-chains) - [Using Multisigs](#using-multisigs) - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + - [Manual Configuration](#manual-configuration) - [Contract Verification](#contract-verification) - [Troubleshooting](#troubleshooting) From 24467505c470f941167d99575ca06411765e2e74 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 20:04:35 +0200 Subject: [PATCH 25/57] update term to Enable Messsaging --- docs/EXAMPLES_DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/EXAMPLES_DEVELOPMENT.md b/docs/EXAMPLES_DEVELOPMENT.md index 5675b14a1c..c4d03dce00 100644 --- a/docs/EXAMPLES_DEVELOPMENT.md +++ b/docs/EXAMPLES_DEVELOPMENT.md @@ -48,7 +48,7 @@ Currently, this document will only detail the structure for the READMEs of the e - Goal: How to deploy contracts/programs/modules - Contents: Deploy command + minting instructions (if applicable) -12. **Wiring / Configuring OApps** +12. **Enable Messaging** - Goal: How to set up OApps for use - Contents: LZ config, init step, wiring step From d9ab6e9663609b4d7a70d262cb8e9cda0e29e237 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 21:04:17 +0200 Subject: [PATCH 26/57] README --- examples/oft-upgradeable/README.md | 645 +++++++++++++++++++---------- 1 file changed, 422 insertions(+), 223 deletions(-) diff --git a/examples/oft-upgradeable/README.md b/examples/oft-upgradeable/README.md index 9778e80bae..0a81490dbb 100644 --- a/examples/oft-upgradeable/README.md +++ b/examples/oft-upgradeable/README.md @@ -1,21 +1,61 @@

- LayerZero + LayerZero

- Homepage | Docs | Developers + LayerZero Docs

-

Omnichain Fungible Token (OFT) Upgradeable Example

+

EVM-to-EVM Omnichain Fungible Token (OFT) Upgradeable Example

+ +

Template project for an upgradeable 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.

+ +## Table of Contents + +- [Prerequisite Knowledge](#prerequisite-knowledge) +- [Requirements](#requirements) +- [Scaffold this example](#scaffold-this-example) +- [Helper Tasks](#helper-tasks) +- [Setup](#setup) +- [Build](#build) + - [Compiling your contracts](#compiling-your-contracts) +- [Deploy](#deploy) +- [Enable Messaging](#enable-messaging) +- [Sending OFTs](#sending-ofts) +- [Next Steps](#next-steps) +- [Production Deployment Checklist](#production-deployment-checklist) + - [Profiling `lzReceive` and `lzCompose` Gas Usage](#profiling-lzreceive-and-lzcompose-gas-usage) + - [Available Commands](#available-commands) + - [`lzReceive`](#lzreceive) + - [`lzCompose`](#lzcompose) + - [Usage Examples](#usage-examples) + - [Notes](#notes) +- [Appendix](#appendix) + - [Running Tests](#running-tests) + - [Adding other chains](#adding-other-chains) + - [Using Multisigs](#using-multisigs) + - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + - [Manual Configuration](#manual-configuration) + - [Contract Verification](#contract-verification) + - [Troubleshooting](#troubleshooting) + +## Prerequisite Knowledge + +- [What is an OFT (Omnichain Fungible Token) ?](https://docs.layerzero.network/v2/concepts/applications/oft-standard) +- [What is an OApp (Omnichain Application) ?](https://docs.layerzero.network/v2/concepts/applications/oapp-standard) + +## Introduction + +This example contains 4 OFT variations: +- `MyOFTAdapterFeeUpgradeable` +- `MyOFTAdapterUpgradeable` +- `MyOFTFeeUpgradeable` +- `MyOFTUpgradeable` + +The walkthrough will use `MyOFTUpgradeable` but you can as easily swap out the `contractName` (in the [Deploy](#deploy) step onwards). -

- Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses | DVN Addresses -

- -

Template project for getting started with LayerZero's OFT contract standard.

:warning: With great power comes great responsibility. Upgradeable contracts are powerful, but they also come with risks. Please ensure you understand the risks before deploying an upgradeable contract. For more information on the @@ -23,35 +63,305 @@ limitations of upgradeable contracts, please see the [OpenZeppelin documentation](https://docs.openzeppelin.com/contracts/5.x/upgradeable). Further, consider fully testing any and all upgrades thoroughly before deploying to production. -

+ +This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: + +```bash +pnpm compile +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm compile:forge +pnpm compile:hardhat +``` + +## Deploy + +To deploy the OFT contracts to your desired blockchains, run the following command: + +```bash +pnpm hardhat lz:deploy --tags MyOFTUpgradeableMock +``` + +> :information_source: MyOFTUpgradeableMock 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. + +## Enable Messaging + +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](https://docs.layerzero.network/v2/concepts/glossary#wire--wiring) task. + +> :information_source: This example uses the [Simple Config Generator](https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config), which is recommended over manual configuration. + +Run the wiring task: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config +``` + +Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. + +## Sending OFTs + +With your OFTs wired, you can now send them cross chain. + +First, via the mock contract, let's mint on **Optimism Sepolia**: + +``` +cast send "mint(address,uint256)" 1000000000000000000 --private-key --rpc-url + +``` + +> You can get the address of your OFT on Optimism Sepolia from the file at `./deployments/optimism-testnet/MyOFTUpgradeableMock.json` + +Send 1 OFT from **Optimism Sepolia** to **Arbitrum Sepolia**: + +```bash +pnpm hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to +``` + +> :information_source: `40232` and `40106` are the Endpoint IDs of Optimism Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) 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. -- [So What is an Omnichain Fungible Token?](#so-what-is-an-omnichain-fungible-token) -- [Available Helpers in this Repo](#layerzero-hardhat-helper-tasks) +Congratulations, you have now sent an OFT cross-chain! +> If you run into any issues, refer to [Troubleshooting](#troubleshooting). + +## Next Steps + +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](#production-deployment-checklist). +- Read on [DVNs / Security Stack](https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns) +- Read on [Message Execution Options](https://docs.layerzero.network/v2/concepts/technical-reference/options-reference) + +## Production Deployment Checklist + + + +Before deploying, ensure the following: + +- (required) you are not using `MyOFTUpgradeableMock`, which has a public `mint` function + - In `layerzero.config.ts`, ensure you are not using `MyOFTUpgradeableMock` as the `contractName` for any of the contract objects. +- (recommended) you have profiled the gas usage of `lzReceive` on your destination chains + + +### Profiling `lzReceive` and `lzCompose` Gas Usage + +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. + +### Available Commands + +1. **`gas:lzReceive`** + + This command profiles the `lzReceive` function for estimating gas usage across multiple runs. + + ```json + "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes,uint256,uint256)'" + ``` + +2. **`gas:lzCompose`** + + This command profiles the `lzCompose` function for estimating gas usage across multiple runs. + + ```json + "gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes,uint256,uint256)'" + ``` + +### Usage Examples + +#### `lzReceive` + +To estimate the gas for the `lzReceive` function: + +```bash +pnpm gas:lzReceive + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msg.value`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +#### `lzCompose` + +To estimate the gas for the `lzCompose` function: + +```bash +pnpm gas:lzCompose + \ + \ + \ + \ + \ + \ + \ + \ + \ + +``` + +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 a `bytes` array. +- `msgValue`: The amount of Ether sent with the message (in wei). +- `numOfRuns`: The number of test runs to execute. + +#### Notes + +- Modify `numOfRuns` based 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)

-## So what is an Omnichain Fungible Token? +# Appendix + +## Running Tests + +Similar to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: -The Omnichain Fungible Token (OFT) Standard is an ERC20 token that can be transferred across multiple blockchains without asset wrapping or middlechains. +```bash +pnpm test +``` -LayerZero +If you prefer one over the other, you can use the tooling-specific commands: -This standard works by combining the LayerZero OApp Contract Standard with the ERC20 [`_burn`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L80) method, to initiate omnichain send transfers on the source chain, sending a message via the LayerZero protocol, and delivering a function call to the destination contract to [`_mint`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L96) the same number of tokens burned, creating a unified supply across all networks connected. +```bash +pnpm test:forge +pnpm test:hardhat +``` + +## Adding other chains + + + +If 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 `eid` and `contractName`) +- decide whether to use an existing EVM enforced options variable or declare a new one +- create a new entry in the `pathways` variable +- add the new contract into the `contracts` key of the `return` of the `export default` function + +After applying the desired changes, make sure you re-run the wiring task: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +## Using Multisigs + +The 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`: + +```typescript +// hardhat.config.ts -Read more about what you can do with OFTs by reading the [OFT Quickstart](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) in the LayerZero Documentation. +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 Hardhat Helper Tasks LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain.
- npx hardhat lz:deploy + pnpm hardhat lz:deploy
Deploys your contract to any of the available networks in your [`hardhat.config.ts`](./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](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`. -```yml +```typescript 'arbitrum-sepolia': { eid: EndpointId.ARBSEP_V2_TESTNET, url: process.env.RPC_URL_ARBSEP_TESTNET, @@ -64,10 +374,16 @@ Deploys your contract to any of the available networks in your [`hardhat.config. }, ``` +More information about available CLI arguments can be found using the `--help` flag: + +```bash +pnpm hardhat lz:deploy --help +``` +
- npx hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME + pnpm hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME
@@ -76,84 +392,93 @@ Initializes a `layerzero.config.ts` file for all available pathways between your You can run this task by providing the `contract-name` you want to set for the config and `file-name` you want to generate: ```bash -npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME +pnpm hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME ``` This 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: -```yml -import { EndpointId } from '@layerzerolabs/lz-definitions' +```typescript +import { EndpointId } from "@layerzerolabs/lz-definitions"; const arbsepContract = { - eid: EndpointId.ARBSEP_V2_TESTNET, - contractName: 'MyOFT', -} + eid: EndpointId.ARBSEP_V2_TESTNET, + contractName: "MyOFT", +}; const sepoliaContract = { - eid: EndpointId.SEPOLIA_V2_TESTNET, - contractName: 'MyOFT', -} + 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, - // }, - // }, - }, + contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }], + connections: [ + { + from: arbsepContract, + to: sepoliaContract, + config: { + sendLibrary: "0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E", + receiveLibraryConfig: { + receiveLibrary: "0x75Db67CDab2824970131D5aa9CECfC9F69c69636", + gracePeriod: 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, - }, - }, - }, + 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, + }, + }, + }, + }, + ], +}; ```
- npx hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG
@@ -174,32 +499,14 @@ Running `lz:oapp:wire` will make the following function calls per pathway connec To use this task, run: ```bash -npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE +pnpm hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE ``` Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes. -To use a Gnosis 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`: - -```yml -// 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' - } - } -} -``` -
- npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG
@@ -264,7 +571,7 @@ If you do NOT explicitly set each configuration parameter, your OApp will fallba
- npx hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG + pnpm hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG
@@ -291,123 +598,13 @@ Returns the LayerZero Executor config for each network in your `hardhat.config.t
-## Developing Contracts - -#### Installing dependencies - -We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): - -```bash -pnpm install -``` +### Manual Configuration -#### Compiling your contracts - -This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: + -```bash -pnpm compile -``` +This section only applies if you would like to configure manually instead of using the Simple Config Generator. -If you prefer one over the other, you can use the tooling-specific commands: - -```bash -pnpm compile:forge -pnpm compile:hardhat -``` - -Or adjust the `package.json` to for example remove `forge` build: - -```diff -- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", -- "compile:forge": "forge build", -- "compile:hardhat": "hardhat compile", -+ "compile": "hardhat compile" -``` - -#### Running tests - -Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: - -```bash -pnpm test -``` - -If you prefer one over the other, you can use the tooling-specific commands: - -```bash -pnpm test:forge -pnpm test:hardhat -``` - -Or adjust the `package.json` to for example remove `hardhat` tests: - -```diff -- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", -- "test:forge": "forge test", -- "test:hardhat": "$npm_execpath hardhat test" -+ "test": "forge test" -``` - -## Deploying Contracts - -Set up deployer wallet/account: - -- Rename `.env.example` -> `.env` -- Choose your preferred means of setting up your deployer wallet/account: - -``` -MNEMONIC="test test test test test test test test test test test junk" -or... -PRIVATE_KEY="0xabc...def" -``` - -- Fund this address with the corresponding chain's native tokens you want to deploy to. - -To deploy your contracts to your desired blockchains, run the following command in your project's folder: - -```bash -npx hardhat lz:deploy -``` - -More information about available CLI arguments can be found using the `--help` flag: - -```bash -npx hardhat lz:deploy --help -``` - -By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. - -

- -## Connecting Contracts - -### Ethereum Configurations - -Fill out your `layerzero.config.ts` with the contracts you want to connect. You can generate the default config file for your declared hardhat networks by running: - -```bash -npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME] -``` - -> [!NOTE] -> You may need to change the contract name if you're deploying multiple OApp contracts on different chains (e.g., OFT and OFT Adapter). - -
- -```typescript -const ethereumContract: OmniPointHardhat = { - eid: EndpointId.ETHEREUM_V2_MAINNET, - contractName: "MyOFTAdapter", -}; - -const arbitrumContract: OmniPointHardhat = { - eid: EndpointId.ARBITRUM_V2_MAINNET, - contractName: "MyOFT", -}; -``` - -Then define the pathway you want to create from and to each contract: +Define the pathway you want to create from and to each contract: ```typescript connections: [ @@ -452,7 +649,7 @@ connections: [ executor: contractsConfig.ethereum.executor, }, ulnConfig: { - // The number of block confirmations to wait on BSC before emitting the message from the source chain. + // 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. @@ -523,12 +720,14 @@ connections: [ ]; ``` -To set these config settings, run: +### Contract Verification + +You can verify EVM chain contracts using the LayerZero helper package: ```bash -npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts ``` -

- Join our community! | Follow us on X (formerly Twitter) -

+### Troubleshooting + +Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). From 27028846df227dd7859894ea00ea909318c4d9d2 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 21:33:31 +0200 Subject: [PATCH 27/57] oft-upgradeable send tasks and simple config generator --- examples/oft-upgradeable/README.md | 2 +- .../deploy/MyOFTUpgradeableMock.ts | 39 +++ examples/oft-upgradeable/hardhat.config.ts | 19 +- examples/oft-upgradeable/layerzero.config.ts | 92 +++---- examples/oft-upgradeable/package.json | 2 + examples/oft-upgradeable/tasks/sendEvm.ts | 242 ++++++++++++++++++ examples/oft-upgradeable/tasks/sendOFT.ts | 108 ++++++++ examples/oft-upgradeable/tasks/types.ts | 4 + examples/oft-upgradeable/tasks/utils.ts | 51 ++++ pnpm-lock.yaml | 83 +++++- 10 files changed, 566 insertions(+), 76 deletions(-) create mode 100644 examples/oft-upgradeable/deploy/MyOFTUpgradeableMock.ts create mode 100644 examples/oft-upgradeable/tasks/sendEvm.ts create mode 100644 examples/oft-upgradeable/tasks/sendOFT.ts create mode 100644 examples/oft-upgradeable/tasks/types.ts create mode 100644 examples/oft-upgradeable/tasks/utils.ts diff --git a/examples/oft-upgradeable/README.md b/examples/oft-upgradeable/README.md index 0a81490dbb..323f0a6c95 100644 --- a/examples/oft-upgradeable/README.md +++ b/examples/oft-upgradeable/README.md @@ -49,6 +49,7 @@ ## Introduction This example contains 4 OFT variations: + - `MyOFTAdapterFeeUpgradeable` - `MyOFTAdapterUpgradeable` - `MyOFTFeeUpgradeable` @@ -56,7 +57,6 @@ This example contains 4 OFT variations: The walkthrough will use `MyOFTUpgradeable` but you can as easily swap out the `contractName` (in the [Deploy](#deploy) step onwards). - :warning: With great power comes great responsibility. Upgradeable contracts are powerful, but they also come with risks. Please ensure you understand the risks before deploying an upgradeable contract. For more information on the limitations of upgradeable contracts, please see the diff --git a/examples/oft-upgradeable/deploy/MyOFTUpgradeableMock.ts b/examples/oft-upgradeable/deploy/MyOFTUpgradeableMock.ts new file mode 100644 index 0000000000..2da2943f83 --- /dev/null +++ b/examples/oft-upgradeable/deploy/MyOFTUpgradeableMock.ts @@ -0,0 +1,39 @@ +import { type DeployFunction } from 'hardhat-deploy/types' + +import { EndpointId, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { getDeploymentAddressAndAbi } from '@layerzerolabs/lz-evm-sdk-v2' + +const contractName = 'MyOFTUpgradeableMock' + +const deploy: DeployFunction = async (hre) => { + const { deploy } = hre.deployments + const signer = (await hre.ethers.getSigners())[0] + console.log(`deploying ${contractName} on network: ${hre.network.name} with ${signer.address}`) + + const eid = hre.network.config.eid as EndpointId + const lzNetworkName = endpointIdToNetwork(eid) + + const { address } = getDeploymentAddressAndAbi(lzNetworkName, 'EndpointV2') + + await deploy(contractName, { + from: signer.address, + args: [address], + log: true, + waitConfirmations: 1, + skipIfAlreadyDeployed: false, + proxy: { + proxyContract: 'OpenZeppelinTransparentProxy', + owner: signer.address, + execute: { + init: { + methodName: 'initialize', + args: ['MyOFT', 'MOFT', signer.address], // TODO: add name/symbol + }, + }, + }, + }) +} + +deploy.tags = [contractName] + +export default deploy diff --git a/examples/oft-upgradeable/hardhat.config.ts b/examples/oft-upgradeable/hardhat.config.ts index 9c2972f3bd..8191c4cfa1 100644 --- a/examples/oft-upgradeable/hardhat.config.ts +++ b/examples/oft-upgradeable/hardhat.config.ts @@ -17,6 +17,8 @@ import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' import { EndpointId } from '@layerzerolabs/lz-definitions' +import './tasks/sendOFT' + // Set your preferred authentication method // // If you prefer using a mnemonic, set a MNEMONIC environment variable @@ -56,19 +58,14 @@ const config: HardhatUserConfig = { ], }, networks: { - 'sepolia-testnet': { - eid: EndpointId.SEPOLIA_V2_TESTNET, - url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/', - accounts, - }, - 'avalanche-testnet': { - eid: EndpointId.AVALANCHE_V2_TESTNET, - url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji', + 'optimism-testnet': { + eid: EndpointId.OPTSEP_V2_TESTNET, + url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co', accounts, }, - 'amoy-testnet': { - eid: EndpointId.AMOY_V2_TESTNET, - url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com', + 'arbitrum-testnet': { + eid: EndpointId.ARBSEP_V2_TESTNET, + url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co', accounts, }, hardhat: { diff --git a/examples/oft-upgradeable/layerzero.config.ts b/examples/oft-upgradeable/layerzero.config.ts index 077fe6d4b3..baa1c7a4a3 100644 --- a/examples/oft-upgradeable/layerzero.config.ts +++ b/examples/oft-upgradeable/layerzero.config.ts @@ -1,60 +1,52 @@ import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' +import { TwoWayConfig, generateConnectionsConfig } from '@layerzerolabs/metadata-tools' +import { OAppEnforcedOption } from '@layerzerolabs/toolbox-hardhat' -import type { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' +import type { OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' -const sepoliaContract: OmniPointHardhat = { - eid: EndpointId.SEPOLIA_V2_TESTNET, - contractName: 'MyOFTUpgradeable', +const optimismContract: OmniPointHardhat = { + eid: EndpointId.OPTSEP_V2_TESTNET, + contractName: 'MyOFTUpgradeableMock', // Note: change this to 'MyOFTUpgradeable' or your production contract name } -const fujiContract: OmniPointHardhat = { - eid: EndpointId.AVALANCHE_V2_TESTNET, - contractName: 'MyOFTUpgradeable', +const arbitrumContract: OmniPointHardhat = { + eid: EndpointId.ARBSEP_V2_TESTNET, + contractName: 'MyOFTUpgradeableMock', // Note: change this to 'MyOFTUpgradeable' or your production contract name } -const amoyContract: OmniPointHardhat = { - eid: EndpointId.AMOY_V2_TESTNET, - contractName: 'MyOFTUpgradeable', -} +// To connect all the above chains to each other, we need the following pathways: +// Optimism <-> Arbitrum -const config: OAppOmniGraphHardhat = { - contracts: [ - { - contract: fujiContract, - }, - { - contract: sepoliaContract, - }, - { - contract: amoyContract, - }, - ], - connections: [ - { - from: fujiContract, - to: sepoliaContract, - }, - { - from: fujiContract, - to: amoyContract, - }, - { - from: sepoliaContract, - to: fujiContract, - }, - { - from: sepoliaContract, - to: amoyContract, - }, - { - from: amoyContract, - to: sepoliaContract, - }, - { - from: amoyContract, - to: fujiContract, - }, +// For this example's simplicity, we will use the same enforced options values for sending to all chains +// For production, you should ensure `gas` is set to the correct value through profiling the gas usage of calling OFT._lzReceive(...) on the destination chain +// To learn more, read https://docs.layerzero.network/v2/concepts/applications/oapp-standard#execution-options-and-enforced-settings +const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 80000, + value: 0, + }, +] +// With the config generator, pathways declared are automatically bidirectional +// i.e. if you declare A,B there's no need to declare B,A +const pathways: TwoWayConfig[] = [ + [ + optimismContract, // Chain A contract + arbitrumContract, // Chain C contract + [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] + [1, 1], // [A to B confirmations, B to A confirmations] + [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain A enforcedOptions ], -} +] +// Note: you should not use the values 1, 1 for confirmations. Choose the right number of confirmations based on the finalization that you require from the source/destination chains. -export default config +export default async function () { + // Generate the connections config based on the pathways + const connections = await generateConnectionsConfig(pathways) + return { + contracts: [{ contract: optimismContract }, { contract: arbitrumContract }], + connections, + } +} diff --git a/examples/oft-upgradeable/package.json b/examples/oft-upgradeable/package.json index a63a412c3f..4dcb885ac1 100644 --- a/examples/oft-upgradeable/package.json +++ b/examples/oft-upgradeable/package.json @@ -24,12 +24,14 @@ "@babel/core": "^7.23.9", "@layerzerolabs/devtools-evm-hardhat": "^3.0.0", "@layerzerolabs/eslint-config-next": "~2.3.39", + "@layerzerolabs/io-devtools": "~0.2.0", "@layerzerolabs/lz-definitions": "^3.0.75", "@layerzerolabs/lz-evm-messagelib-v2": "^3.0.75", "@layerzerolabs/lz-evm-protocol-v2": "^3.0.75", "@layerzerolabs/lz-evm-sdk-v2": "^3.0.75", "@layerzerolabs/lz-evm-v1-0.7": "^3.0.75", "@layerzerolabs/lz-v2-utilities": "^3.0.75", + "@layerzerolabs/metadata-tools": "^2.0.0", "@layerzerolabs/oapp-evm": "^0.3.2", "@layerzerolabs/oapp-evm-upgradeable": "^0.1.2", "@layerzerolabs/oft-evm": "^3.1.3", diff --git a/examples/oft-upgradeable/tasks/sendEvm.ts b/examples/oft-upgradeable/tasks/sendEvm.ts new file mode 100644 index 0000000000..7a9cf98959 --- /dev/null +++ b/examples/oft-upgradeable/tasks/sendEvm.ts @@ -0,0 +1,242 @@ +import path from 'path' + +import { BigNumber, ContractTransaction } from 'ethers' +import { parseUnits } from 'ethers/lib/utils' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { OmniPointHardhat, createGetHreByEid } from '@layerzerolabs/devtools-evm-hardhat' +import { createLogger } from '@layerzerolabs/io-devtools' +import { ChainType, endpointIdToChainType, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { Options, addressToBytes32 } from '@layerzerolabs/lz-v2-utilities' + +import { SendResult } from './types' +import { DebugLogger, KnownErrors, getLayerZeroScanLink } from './utils' + +const logger = createLogger() + +export interface EvmArgs { + srcEid: number + dstEid: number + amount: string + to: string + oappConfig: string + minAmount?: string + extraLzReceiveOptions?: string[] + extraLzComposeOptions?: string[] + extraNativeDropOptions?: string[] + composeMsg?: string + oftAddress?: string +} + +export async function sendEvm( + { + srcEid, + dstEid, + amount, + to, + oappConfig, + minAmount, + extraLzReceiveOptions, + extraLzComposeOptions, + extraNativeDropOptions, + composeMsg, + oftAddress, + }: EvmArgs, + hre: HardhatRuntimeEnvironment +): Promise { + if (endpointIdToChainType(srcEid) !== ChainType.EVM) { + throw new Error(`non-EVM srcEid (${srcEid}) not supported here`) + } + + const getHreByEid = createGetHreByEid(hre) + let srcEidHre: HardhatRuntimeEnvironment + try { + srcEidHre = await getHreByEid(srcEid) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_GETTING_HRE, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + const signer = (await srcEidHre.ethers.getSigners())[0] + + // 1️⃣ resolve the OFT wrapper address + let wrapperAddress: string + if (oftAddress) { + wrapperAddress = oftAddress + } else { + const layerZeroConfig = (await import(path.resolve('./', oappConfig))).default + const { contracts } = typeof layerZeroConfig === 'function' ? await layerZeroConfig() : layerZeroConfig + const wrapper = contracts.find((c: { contract: OmniPointHardhat }) => c.contract.eid === srcEid) + if (!wrapper) throw new Error(`No config for EID ${srcEid}`) + wrapperAddress = wrapper.contract.contractName + ? (await srcEidHre.deployments.get(wrapper.contract.contractName)).address + : wrapper.contract.address || '' + } + + // 2️⃣ load IOFT ABI, extend it with token() + const ioftArtifact = await srcEidHre.artifacts.readArtifact('IOFT') + + // now attach + const oft = await srcEidHre.ethers.getContractAt(ioftArtifact.abi, wrapperAddress, signer) + + // 3️⃣ fetch the underlying ERC-20 + const underlying = await oft.token() + + // 4️⃣ fetch decimals from the underlying token + const erc20 = await srcEidHre.ethers.getContractAt('ERC20', underlying, signer) + const decimals: number = await erc20.decimals() + + // 5️⃣ normalize the user-supplied amount + const amountUnits: BigNumber = parseUnits(amount, decimals) + + // 6️⃣ Check if approval is required (for OFT Adapters) and handle approval + try { + const approvalRequired = await oft.approvalRequired() + if (approvalRequired) { + logger.info('OFT Adapter detected - checking ERC20 allowance...') + + // Check current allowance + const currentAllowance = await erc20.allowance(signer.address, wrapperAddress) + logger.info(`Current allowance: ${currentAllowance.toString()}`) + logger.info(`Required amount: ${amountUnits.toString()}`) + + if (currentAllowance.lt(amountUnits)) { + logger.info('Insufficient allowance - approving ERC20 tokens...') + const approveTx = await erc20.approve(wrapperAddress, amountUnits) + logger.info(`Approval transaction hash: ${approveTx.hash}`) + await approveTx.wait() + logger.info('ERC20 approval confirmed') + } else { + logger.info('Sufficient allowance already exists') + } + } + } catch (error) { + // If approvalRequired() doesn't exist or fails, assume it's a regular OFT (not an adapter) + logger.info('No approval required (regular OFT detected)') + } + + // 7️⃣ hex string → Uint8Array → zero-pad to 32 bytes + const toBytes = addressToBytes32(to) + + // 8️⃣ Build options dynamically using Options.newOptions() + let options = Options.newOptions() + + // Add lzReceive options + if (extraLzReceiveOptions && extraLzReceiveOptions.length > 0) { + // Handle case where Hardhat's CSV parsing splits "gas,value" into separate elements + if (extraLzReceiveOptions.length % 2 !== 0) { + throw new Error( + `Invalid lzReceive options: received ${extraLzReceiveOptions.length} values, but expected pairs of gas,value` + ) + } + + for (let i = 0; i < extraLzReceiveOptions.length; i += 2) { + const gas = Number(extraLzReceiveOptions[i]) + const value = Number(extraLzReceiveOptions[i + 1]) || 0 + options = options.addExecutorLzReceiveOption(gas, value) + logger.info(`Added lzReceive option: ${gas} gas, ${value} value`) + } + } + + // Add lzCompose options + if (extraLzComposeOptions && extraLzComposeOptions.length > 0) { + // Handle case where Hardhat's CSV parsing splits "index,gas,value" into separate elements + if (extraLzComposeOptions.length % 3 !== 0) { + throw new Error( + `Invalid lzCompose options: received ${extraLzComposeOptions.length} values, but expected triplets of index,gas,value` + ) + } + + for (let i = 0; i < extraLzComposeOptions.length; i += 3) { + const index = Number(extraLzComposeOptions[i]) + const gas = Number(extraLzComposeOptions[i + 1]) + const value = Number(extraLzComposeOptions[i + 2]) || 0 + options = options.addExecutorComposeOption(index, gas, value) + logger.info(`Added lzCompose option: index ${index}, ${gas} gas, ${value} value`) + } + } + + // Add native drop options + if (extraNativeDropOptions && extraNativeDropOptions.length > 0) { + // Handle case where Hardhat's CSV parsing splits "amount,recipient" into separate elements + if (extraNativeDropOptions.length % 2 !== 0) { + throw new Error( + `Invalid native drop options: received ${extraNativeDropOptions.length} values, but expected pairs of amount,recipient` + ) + } + + for (let i = 0; i < extraNativeDropOptions.length; i += 2) { + const amountStr = extraNativeDropOptions[i] + const recipient = extraNativeDropOptions[i + 1] + + if (!amountStr || !recipient) { + throw new Error( + `Invalid native drop option: Both amount and recipient must be provided. Got amount="${amountStr}", recipient="${recipient}"` + ) + } + + try { + options = options.addExecutorNativeDropOption(amountStr.trim(), recipient.trim()) + logger.info(`Added native drop option: ${amountStr.trim()} wei to ${recipient.trim()}`) + } catch (error) { + // Provide helpful context if the amount exceeds protocol limits + const maxUint128 = BigInt('340282366920938463463374607431768211455') // 2^128 - 1 + const maxUint128Ether = Number(maxUint128) / 1e18 // Convert to ETH for readability + + throw new Error( + `Failed to add native drop option with amount ${amountStr.trim()} wei. ` + + `LayerZero protocol constrains native drop amounts to uint128 maximum ` + + `(${maxUint128.toString()} wei ≈ ${maxUint128Ether.toFixed(2)} ETH). ` + + `Original error: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + } + + const extraOptions = options.toHex() + + // 9️⃣ build sendParam and dispatch + const sendParam = { + dstEid, + to: toBytes, + amountLD: amountUnits.toString(), + minAmountLD: minAmount ? parseUnits(minAmount, decimals).toString() : amountUnits.toString(), + extraOptions: extraOptions, + composeMsg: composeMsg ? composeMsg.toString() : '0x', + oftCmd: '0x', + } + + // 10️⃣ Quote (MessagingFee = { nativeFee, lzTokenFee }) + logger.info('Quoting the native gas cost for the send transaction...') + let msgFee: { nativeFee: BigNumber; lzTokenFee: BigNumber } + try { + msgFee = await oft.quoteSend(sendParam, false) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_QUOTING_NATIVE_GAS_COST, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + logger.info('Sending the transaction...') + let tx: ContractTransaction + try { + tx = await oft.send(sendParam, msgFee, signer.address, { + value: msgFee.nativeFee, + }) + } catch (error) { + DebugLogger.printErrorAndFixSuggestion( + KnownErrors.ERROR_SENDING_TRANSACTION, + `For network: ${endpointIdToNetwork(srcEid)}, OFT: ${oftAddress}` + ) + throw error + } + const receipt = await tx.wait() + + const txHash = receipt.transactionHash + const scanLink = getLayerZeroScanLink(txHash, srcEid >= 40_000 && srcEid < 50_000) + + return { txHash, scanLink } +} diff --git a/examples/oft-upgradeable/tasks/sendOFT.ts b/examples/oft-upgradeable/tasks/sendOFT.ts new file mode 100644 index 0000000000..32101a05af --- /dev/null +++ b/examples/oft-upgradeable/tasks/sendOFT.ts @@ -0,0 +1,108 @@ +import { task, types } from 'hardhat/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +import { types as cliTypes } from '@layerzerolabs/devtools-evm-hardhat' +import { ChainType, endpointIdToChainType, endpointIdToNetwork } from '@layerzerolabs/lz-definitions' + +import { EvmArgs, sendEvm } from './sendEvm' +import { SendResult } from './types' +import { DebugLogger, KnownOutputs, KnownWarnings, getBlockExplorerLink } from './utils' + +interface MasterArgs { + srcEid: number + dstEid: number + amount: string + to: string + oappConfig: string + /** Minimum amount to receive in case of custom slippage or fees (human readable units, e.g. "1.5") */ + minAmount?: string + /** Array of lzReceive options as comma-separated values "gas,value" - e.g. --extra-lz-receive-options "200000,0" */ + extraLzReceiveOptions?: string[] + /** Array of lzCompose options as comma-separated values "index,gas,value" - e.g. --extra-lz-compose-options "0,500000,0" */ + extraLzComposeOptions?: string[] + /** Array of native drop options as comma-separated values "amount,recipient" - e.g. --extra-native-drop-options "1000000000000000000,0x1234..." */ + extraNativeDropOptions?: string[] + /** Arbitrary bytes message to deliver alongside the OFT */ + composeMsg?: string + /** EVM: 20-byte hex address */ + oftAddress?: string +} + +task('lz:oft:send', 'Sends OFT tokens cross‐chain from EVM chains') + .addParam('srcEid', 'Source endpoint ID', undefined, types.int) + .addParam('dstEid', 'Destination endpoint ID', undefined, types.int) + .addParam('amount', 'Amount to send (human readable units, e.g. "1.5")', undefined, types.string) + .addParam('to', 'Recipient address (20-byte hex for EVM)', undefined, types.string) + .addOptionalParam('oappConfig', 'Path to the LayerZero config file', 'layerzero.config.ts', types.string) + .addOptionalParam( + 'minAmount', + 'Minimum amount to receive in case of custom slippage or fees (human readable units, e.g. "1.5")', + undefined, + types.string + ) + .addOptionalParam( + 'extraLzReceiveOptions', + 'Array of extra lzReceive options in format "gas,value" (e.g. ["200000,0", "100000,1000000000000000000"])', + undefined, + cliTypes.csv + ) + .addOptionalParam( + 'extraLzComposeOptions', + 'Array of extra lzCompose options in format "index,gas,value" (e.g. ["0,500000,0", "1,300000,1000000000000000000"])', + undefined, + cliTypes.csv + ) + .addOptionalParam( + 'extraNativeDropOptions', + 'Array of extra native drop options in format "amount,recipient" (e.g. ["1000000000000000000,0x1234..."])', + undefined, + cliTypes.csv + ) + .addOptionalParam('composeMsg', 'Arbitrary bytes message to deliver alongside the OFT', undefined, types.string) + .addOptionalParam( + 'oftAddress', + 'Override the source local deployment OFT address (20-byte hex for EVM)', + undefined, + types.string + ) + .setAction(async (args: MasterArgs, hre: HardhatRuntimeEnvironment) => { + const chainType = endpointIdToChainType(args.srcEid) + let result: SendResult + + if (args.oftAddress) { + DebugLogger.printWarning( + KnownWarnings.USING_OVERRIDE_OFT, + `For network: ${endpointIdToNetwork(args.srcEid)}, OFT: ${args.oftAddress}` + ) + } + + // Only support EVM chains in this example + if (chainType === ChainType.EVM) { + result = await sendEvm(args as EvmArgs, hre) + } else { + throw new Error( + `The chain type ${chainType} is not supported in this OFT example. Only EVM chains are supported.` + ) + } + + DebugLogger.printLayerZeroOutput( + KnownOutputs.SENT_VIA_OFT, + `Successfully sent ${args.amount} tokens from ${endpointIdToNetwork(args.srcEid)} to ${endpointIdToNetwork(args.dstEid)}` + ) + + // print the explorer link for the srcEid from metadata + const explorerLink = await getBlockExplorerLink(args.srcEid, result.txHash) + // if explorer link is available, print the tx hash link + if (explorerLink) { + DebugLogger.printLayerZeroOutput( + KnownOutputs.TX_HASH, + `Explorer link for source chain ${endpointIdToNetwork(args.srcEid)}: ${explorerLink}` + ) + } + + // print the LayerZero Scan link from metadata + DebugLogger.printLayerZeroOutput( + KnownOutputs.EXPLORER_LINK, + `LayerZero Scan link for tracking all cross-chain transaction details: ${result.scanLink}` + ) + }) diff --git a/examples/oft-upgradeable/tasks/types.ts b/examples/oft-upgradeable/tasks/types.ts new file mode 100644 index 0000000000..8d2b681692 --- /dev/null +++ b/examples/oft-upgradeable/tasks/types.ts @@ -0,0 +1,4 @@ +export interface SendResult { + txHash: string // EVM: receipt.transactionHash + scanLink: string // LayerZeroScan link for cross-chain tracking +} diff --git a/examples/oft-upgradeable/tasks/utils.ts b/examples/oft-upgradeable/tasks/utils.ts new file mode 100644 index 0000000000..934c9a046d --- /dev/null +++ b/examples/oft-upgradeable/tasks/utils.ts @@ -0,0 +1,51 @@ +import { createLogger } from '@layerzerolabs/io-devtools' +import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' +import { Options } from '@layerzerolabs/lz-v2-utilities' + +const logger = createLogger() + +export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' + +/** + * Given a srcEid and on-chain tx hash, return + * `https://…blockExplorers[0].url/tx/`, or undefined. + */ +export async function getBlockExplorerLink(srcEid: number, txHash: string): Promise { + const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" + const res = await fetch(deploymentMetadataUrl) + if (!res.ok) return + const all = (await res.json()) as Record + const meta = all[network] + const explorer = meta?.blockExplorers?.[0]?.url + if (explorer) { + // many explorers use `/tx/` + return `${explorer.replace(/\/+$/, '')}/tx/${txHash}` + } + return +} + +function formatBigIntForDisplay(n: bigint) { + return n.toLocaleString().replace(/,/g, '_') +} + +export function decodeLzReceiveOptions(hex: string): string { + try { + // Handle empty/undefined values first + if (!hex || hex === '0x') return 'No options set' + const options = Options.fromOptions(hex) + const lzReceiveOpt = options.decodeExecutorLzReceiveOption() + return lzReceiveOpt + ? `gas: ${formatBigIntForDisplay(lzReceiveOpt.gas)} , value: ${formatBigIntForDisplay(lzReceiveOpt.value)} wei` + : 'No executor options' + } catch (e) { + return `Invalid options (${hex.slice(0, 12)}...)` + } +} + +// Get LayerZero scan link +export function getLayerZeroScanLink(txHash: string, isTestnet = false): string { + const baseUrl = isTestnet ? 'https://testnet.layerzeroscan.com' : 'https://layerzeroscan.com' + return `${baseUrl}/tx/${txHash}` +} + +export { DebugLogger, KnownErrors, KnownOutputs, KnownWarnings } from '@layerzerolabs/io-devtools' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bab7cd994..85a852ae4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2533,6 +2533,9 @@ importers: '@layerzerolabs/eslint-config-next': specifier: ~2.3.39 version: 2.3.44(typescript@5.5.3) + '@layerzerolabs/io-devtools': + specifier: ~0.2.0 + version: link:../../packages/io-devtools '@layerzerolabs/lz-definitions': specifier: ^3.0.75 version: 3.0.75 @@ -2551,6 +2554,9 @@ importers: '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 + '@layerzerolabs/metadata-tools': + specifier: ^2.0.0 + version: link:../../packages/metadata-tools '@layerzerolabs/oapp-evm': specifier: ^0.3.2 version: link:../../packages/oapp-evm @@ -4034,7 +4040,7 @@ importers: version: 2.16.2 jest: specifier: ^29.6.2 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tsup: specifier: ~8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -4219,7 +4225,7 @@ importers: version: 29.5.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tslib: specifier: ~2.6.2 version: 2.6.3 @@ -4747,13 +4753,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.0 - version: 3.0.0(typescript@5.5.3) + version: 3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.38 - version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~2.0.0 version: link:../protocol-devtools @@ -5374,13 +5380,13 @@ importers: version: 3.0.75 '@layerzerolabs/lz-solana-sdk-v2': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': specifier: ^3.0.75 version: 3.0.75 '@layerzerolabs/oft-v2-solana-sdk': specifier: ^3.0.59 - version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + version: 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/protocol-devtools': specifier: ~2.0.0 version: link:../protocol-devtools @@ -5485,7 +5491,7 @@ importers: version: 12.6.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + version: 29.7.0(@types/node@18.18.14) tsup: specifier: ^8.0.1 version: 8.0.1(@swc/core@1.4.0)(typescript@5.5.3) @@ -10315,7 +10321,7 @@ packages: - typescript - utf-8-validate - /@layerzerolabs/lz-solana-sdk-v2@3.0.0(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-sPvLXeQUO9QLpjOuWE7V+V8yfoI4E/NBYsH9lO2aPx0LYkQa+88ACgPq43B/zFROUD8238WuSb+doGrn3PKtJQ==} dependencies: '@ethersproject/abi': 5.7.0 @@ -10346,7 +10352,7 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-zyuqBYxaVtSa+STdcbO/uzzQV2kyxmBX3flNbvOq7kS2QHBivuaYd/XDbNLE54/egQ63yMtFcilqwy3VzNpclw==} dependencies: '@layerzerolabs/lz-corekit-solana': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) @@ -10454,7 +10460,7 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/lz-solana-sdk-v2@3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/lz-solana-sdk-v2@3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-FfZLkFHIOPIFLkevQD0QSrfVR2UgREG6YF6oSR92XPViuVpAq0LAyCTXQi915bdiSdQ/Mwd+9eU/9ZPlk9f6sA==} dependencies: '@layerzerolabs/lz-corekit-solana': 3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) @@ -10781,12 +10787,12 @@ packages: '@layerzerolabs/lz-definitions': 3.0.75 dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-P06/a5+ixph0u1AQkDZ0P0oFaIAdfGPl/UezMfWXUpiWLth428RT0rrMR6qI7z6X1uxqlUFNIotz2ET1fyFcpQ==} dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.38(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.86 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -10812,12 +10818,12 @@ packages: - utf-8-validate dev: true - /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3): + /@layerzerolabs/oft-v2-solana-sdk@3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3): resolution: {integrity: sha512-ijbvj6/Gc4O4WLHfqnrBuKUtIhpaYws/ORj2apZFT1RKSgAX8CCJ9aZmn0ClamEG98i+PpXoroPPU46LMOMZyA==} dependencies: '@ethersproject/bytes': 5.7.0 '@layerzerolabs/lz-foundation': 3.0.66(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) - '@layerzerolabs/lz-solana-sdk-v2': 3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(typescript@5.5.3) + '@layerzerolabs/lz-solana-sdk-v2': 3.0.86(@swc/core@1.4.0)(@types/node@18.18.14)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.3) '@layerzerolabs/lz-v2-utilities': 3.0.86 '@metaplex-foundation/beet': 0.7.2 '@metaplex-foundation/beet-solana': 0.4.1 @@ -19744,6 +19750,34 @@ packages: - babel-plugin-macros - supports-color + /jest-cli@29.7.0(@types/node@18.18.14): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@18.18.14)(ts-node@10.9.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-cli@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -20156,6 +20190,27 @@ packages: merge-stream: 2.0.0 supports-color: 8.1.1 + /jest@29.7.0(@types/node@18.18.14): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@18.18.14) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest@29.7.0(@types/node@18.18.14)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} From 4cc8d3cf3630c4df526fbfa35d5750655dd04100 Mon Sep 17 00:00:00 2001 From: nazreen Date: Wed, 2 Jul 2025 21:36:47 +0200 Subject: [PATCH 28/57] update --- examples/oft-upgradeable/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/oft-upgradeable/README.md b/examples/oft-upgradeable/README.md index 323f0a6c95..994253ec7a 100644 --- a/examples/oft-upgradeable/README.md +++ b/examples/oft-upgradeable/README.md @@ -129,6 +129,8 @@ pnpm hardhat lz:deploy --tags MyOFTUpgradeableMock > :information_source: MyOFTUpgradeableMock will be used as it provides a public mint function which we require for testing +> If you would like to try any of the other 3 variants (`MyOFTAdapterFeeUpgradeable`, `MyOFTAdapterUpgradeable`, `MyOFTFeeUpgradeable`), replace `MyOFTUpgradeableMock` with the name of the variant you'd like to deploy. + Select all the chains you want to deploy the OFT to. ## Enable Messaging @@ -137,6 +139,8 @@ The OFT standard builds on top of the OApp standard, which enables generic messa > :information_source: This example uses the [Simple Config Generator](https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config), which is recommended over manual configuration. +> If you are deploying a variant other than `MyOFTUpgradeableMock`, you would need to update the `layerzero.config.ts` so that the contract object uses the correct `contractName` value. + Run the wiring task: ```bash From d9ab744468c567b7ea2dab86b7940c5e8b945178 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 00:05:38 +0200 Subject: [PATCH 29/57] rm unneeded --- examples/oft-upgradeable/tasks/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/oft-upgradeable/tasks/utils.ts b/examples/oft-upgradeable/tasks/utils.ts index 934c9a046d..1a322be4d9 100644 --- a/examples/oft-upgradeable/tasks/utils.ts +++ b/examples/oft-upgradeable/tasks/utils.ts @@ -1,9 +1,6 @@ -import { createLogger } from '@layerzerolabs/io-devtools' import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' import { Options } from '@layerzerolabs/lz-v2-utilities' -const logger = createLogger() - export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' /** From 0df58e9a9716fb51f4853e9977c91b7f45244b1a Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 00:24:35 +0200 Subject: [PATCH 30/57] lint --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 644cdd5f85..c529d3e099 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Welcome to the **LayerZero Developer Tools Hub**. This repository houses everyth Visit our developer docs to get started building omnichain applications. ## Repository Structure + The primary folders that smart contract developers will find most useful are: `examples/`: Contains various example projects demonstrating how to build with `OApp.sol` (Omnichain App Standard), `OFT.sol` (Omnichain Fungible Tokens), `ONFT.sol` (Omnichain Non-Fungible Tokens), and more. These examples serve as templates and learning resources. @@ -88,7 +89,7 @@ pnpm build This will build all the packages and examples in the repository. -Review the README for each individual `examples/` project to learn how to interact with and use each sample project. +Review the README for each individual `examples/` project to learn how to interact with and use each sample project. ## Contributing From ba6cc3bf0a6e4bb781c236ae2441985cb464038e Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 00:34:55 +0200 Subject: [PATCH 31/57] lint --- examples/oft-adapter/README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 709389af92..6d70af5423 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -47,9 +47,9 @@ - [What is an OFT (Omnichain Fungible Token) ?](https://docs.layerzero.network/v2/concepts/applications/oft-standard) - [What is an OApp (Omnichain Application) ?](https://docs.layerzero.network/v2/concepts/applications/oapp-standard) - ## Introduction -**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. + +**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. @@ -59,7 +59,6 @@ - `pnpm` (recommended) - or another package manager of your choice (npm, yarn) - `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation - ## Scaffold this example Create your local copy of this example: @@ -72,7 +71,6 @@ Specify the directory, select `OFTAdapter` and proceed with the installation. Note that `create-lz-oapp` will also automatically run the dependencies install step for you. - ## Helper Tasks Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the [LayerZero Hardhat Helper Tasks section](#layerzero-hardhat-helper-tasks). All commands can be run at the project root. @@ -92,7 +90,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - 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: **Optimism** and **Arbitrum**. - ## Build ### Compiling your contracts @@ -126,7 +123,6 @@ On the `Deployed Contract` line, note the `address` logged (inner token's addres > :information_source: MyERC20Mock will be used as it provides a public mint function which we require for testing. Ensure you do not use this for production. - In the `hardhat.config.ts` file, add the inner token's address to the network you want to deploy the OFTAdapter to: ```typescript @@ -152,7 +148,6 @@ pnpm hardhat lz:deploy --tags MyOFT --networks arbitrum-testnet 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](https://docs.layerzero.network/v2/concepts/glossary#wire--wiring) task. - Run the wiring task: ```bash @@ -737,4 +732,4 @@ pnpm dlx @layerzerolabs/verify-contract -n -u -k Date: Thu, 3 Jul 2025 12:09:52 +0200 Subject: [PATCH 32/57] eslint error --- examples/oft-upgradeable/.eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/oft-upgradeable/.eslintrc.js b/examples/oft-upgradeable/.eslintrc.js index 8ef2c6dbe9..32352e104c 100644 --- a/examples/oft-upgradeable/.eslintrc.js +++ b/examples/oft-upgradeable/.eslintrc.js @@ -7,5 +7,6 @@ module.exports = { // @layerzerolabs/eslint-config-next defines rules for turborepo-based projects // that are not relevant for this particular project 'turbo/no-undeclared-env-vars': 'off', + 'import/no-unresolved': 'warn', // lint runs before workspace packages are built; missing dist/ folders cause false unresolved errors }, }; From a8f72a07eb8b04fcfe1ebced1fc2128969b693be Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 13:06:56 +0200 Subject: [PATCH 33/57] rename to SPECS --- docs/{EXAMPLES_DEVELOPMENT.md => EXAMPLES_SPECS.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{EXAMPLES_DEVELOPMENT.md => EXAMPLES_SPECS.md} (100%) diff --git a/docs/EXAMPLES_DEVELOPMENT.md b/docs/EXAMPLES_SPECS.md similarity index 100% rename from docs/EXAMPLES_DEVELOPMENT.md rename to docs/EXAMPLES_SPECS.md From 61518a807f830e5582ac5aa0ff0487c8e980e3ab Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 13:07:19 +0200 Subject: [PATCH 34/57] fix lint --- examples/mint-burn-oft-adapter/tasks/utils.ts | 5 +- examples/oapp/README.md | 282 +++++++++++++++--- examples/oapp/package.json | 1 + examples/oft-adapter/tasks/utils.ts | 5 +- examples/oft-upgradeable/tasks/utils.ts | 2 +- examples/oft/tasks/utils.ts | 5 +- 6 files changed, 241 insertions(+), 59 deletions(-) diff --git a/examples/mint-burn-oft-adapter/tasks/utils.ts b/examples/mint-burn-oft-adapter/tasks/utils.ts index 934c9a046d..5e2fd9b3df 100644 --- a/examples/mint-burn-oft-adapter/tasks/utils.ts +++ b/examples/mint-burn-oft-adapter/tasks/utils.ts @@ -1,9 +1,6 @@ -import { createLogger } from '@layerzerolabs/io-devtools' import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' import { Options } from '@layerzerolabs/lz-v2-utilities' -const logger = createLogger() - export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' /** @@ -14,7 +11,7 @@ export async function getBlockExplorerLink(srcEid: number, txHash: string): Prom const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" const res = await fetch(deploymentMetadataUrl) if (!res.ok) return - const all = (await res.json()) as Record + const all = (await res.json()) as Record const meta = all[network] const explorer = meta?.blockExplorers?.[0]?.url if (explorer) { diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 33386e81ca..749ac33450 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -1,32 +1,84 @@

- LayerZero + LayerZero

- Homepage | Docs | Developers + LayerZero Docs

-

OApp Example

+

Omnichain Application (OApp) Example

-

- Quickstart | Configuration | Message Execution Options | Endpoint Addresses -

+

Template project for creating custom omnichain applications (OApp) powered by the LayerZero protocol. This example demonstrates how to build applications that can send and receive arbitrary messages across different blockchains.

+ +## Table of Contents + +- [Prerequisite Knowledge](#prerequisite-knowledge) +- [Requirements](#requirements) +- [Scaffold this example](#scaffold-this-example) +- [Helper Tasks](#helper-tasks) +- [Setup](#setup) +- [Build](#build) + - [Compiling your contracts](#compiling-your-contracts) +- [Deploy](#deploy) +- [Enable Messaging](#enable-messaging) +- [Sending Messages](#sending-messages) +- [Next Steps](#next-steps) +- [Production Deployment Checklist](#production-deployment-checklist) +- [Appendix](#appendix) + - [Running Tests](#running-tests) + - [Adding other chains](#adding-other-chains) + - [Using Multisigs](#using-multisigs) + - [LayerZero Hardhat Helper Tasks](#layerzero-hardhat-helper-tasks) + - [Contract Verification](#contract-verification) + - [Troubleshooting](#troubleshooting) + +## Prerequisite Knowledge + +- [What is an OApp (Omnichain Application)?](https://docs.layerzero.network/v2/concepts/applications/oapp-standard) +- [How does LayerZero work?](https://docs.layerzero.network/v2/concepts/protocol/core-concepts) -

Template project for getting started with LayerZero's OApp contract development.

+## Requirements -## 1) Developing Contracts +- `Node.js` - `>=18.16.0` +- `pnpm` (recommended) - or another package manager of your choice (npm, yarn) +- `forge` (optional) - `>=0.2.0` for testing, and if not using Hardhat for compilation -#### Installing dependencies +## Scaffold this example -We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): +Create your local copy of this example: ```bash -pnpm install +pnpm dlx create-lz-oapp@latest --example oapp ``` -#### Compiling your contracts +Specify the directory, select `OApp` and proceed with the installation. + +Note that `create-lz-oapp` will also automatically run the dependencies install step for you. + +## Helper Tasks + +Throughout this walkthrough, helper tasks will be used. For the full list of available helper tasks, refer to the [LayerZero Hardhat Helper Tasks section](#layerzero-hardhat-helper-tasks). All commands can be run at the project root. + +## Setup + +- Copy `.env.example` into a new `.env` +- Set up your deployer address/account via the `.env` + + - You can specify either `MNEMONIC` or `PRIVATE_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: **Ethereum Sepolia** and **Arbitrum Sepolia**. + +## Build + +### Compiling your contracts This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: @@ -41,18 +93,75 @@ pnpm compile:forge pnpm compile:hardhat ``` -Or adjust the `package.json` to for example remove `forge` build: +## Deploy + +To deploy the OApp contracts to your desired blockchains, run the following command: -```diff -- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", -- "compile:forge": "forge build", -- "compile:hardhat": "hardhat compile", -+ "compile": "hardhat compile" +```bash +pnpm hardhat lz:deploy --tags MyOApp ``` -#### Running tests +Select all the chains you want to deploy the OApp to. -Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: +## Enable Messaging + +After deploying the OApp on the respective chains, you enable messaging by running the [wiring](https://docs.layerzero.network/v2/concepts/glossary#wire--wiring) task. + +> :information_source: This example uses the [Simple Config Generator](https://docs.layerzero.network/v2/developers/evm/technical-reference/simple-config), which is recommended over manual configuration. + +Run the wiring task: + +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts +``` + +Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. + +## Sending Messages + +With your OApps wired, you can now send messages cross-chain. + +Send a message from **Ethereum Sepolia** to **Arbitrum Sepolia**: + +```bash +pnpm hardhat lz:oapp:send --src-eid 40161 --dst-eid 40231 --msg "Hello from Ethereum!" +``` + +> :information_source: `40161` and `40231` are the Endpoint IDs of Ethereum Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) 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 message was received. + +Congratulations, you have now sent a message cross-chain! + +> If you run into any issues, refer to [Troubleshooting](#troubleshooting). + +## Next Steps + +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](#production-deployment-checklist). +- Read on [DVNs / Security Stack](https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns) +- Read on [Message Execution Options](https://docs.layerzero.network/v2/concepts/technical-reference/options-reference) + +## Production Deployment Checklist + +Before deploying, ensure the following: + +- (recommended) you have profiled the gas usage of `lzReceive` on your destination chains +- (recommended) you have configured appropriate DVNs for your security requirements +- (recommended) you have tested your application thoroughly on testnets + +

+ Join our community! | Follow us on X (formerly Twitter) +

+ +# Appendix + +## Running Tests + +Similar to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: ```bash pnpm test @@ -65,52 +174,133 @@ pnpm test:forge pnpm test:hardhat ``` -Or adjust the `package.json` to for example remove `hardhat` tests: +## Adding other chains -```diff -- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", -- "test:forge": "forge test", -- "test:hardhat": "$npm_execpath hardhat test" -+ "test": "forge test" -``` +If 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`. -## 2) Deploying Contracts +Then, modify `layerzero.config.ts` with the following changes: -Set up deployer wallet/account: +- declare a new contract object (specifying the `eid` and `contractName`) +- decide whether to use an existing EVM enforced options variable or declare a new one +- create a new entry in the `connections` array +- add the new contract into the `contracts` array of the `export default` function -- Rename `.env.example` -> `.env` -- Choose your preferred means of setting up your deployer wallet/account: +After applying the desired changes, make sure you re-run the wiring task: +```bash +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts ``` -MNEMONIC="test test test test test test test test test test test junk" -or... -PRIVATE_KEY="0xabc...def" + +## Using Multisigs + +The 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`: + +```typescript +// 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' + } + } +} ``` -To deploy your contracts to your desired blockchains, run the following command in your project's folder: +## LayerZero Hardhat Helper Tasks + +LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and interact with OApps cross-chain. + +
+ pnpm hardhat lz:deploy + +
+ +Deploys your contract to any of the available networks in your [`hardhat.config.ts`](./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](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`. + +More information about available CLI arguments can be found using the `--help` flag: ```bash -npx hardhat lz:deploy +pnpm hardhat lz:deploy --help ``` -More information about available CLI arguments can be found using the `--help` flag: +
+ +
+ 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: ```bash -npx hardhat lz:deploy --help +pnpm hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME ``` -## 3) Connecting Contracts +
+ +
+ 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`. -Wire your deployed contracts by running: +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): + +- function setPeer(uint32 \_eid, bytes32 \_peer) public virtual onlyOwner {} + +- function setConfig(address \_oapp, address \_lib, SetConfigParam[] calldata \_params) external onlyRegistered(\_lib) {} + +- function setEnforcedOptions(EnforcedOptionParam[] calldata \_enforcedOptions) public virtual onlyOwner {} + +- function setSendLibrary(address \_oapp, uint32 \_eid, address \_newLib) external onlyRegisteredOrDefault(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +- function setReceiveLibrary(address \_oapp, uint32 \_eid, address \_newLib, uint256 \_gracePeriod) external onlyRegisteredOrDefault(\_newLib) isReceiveLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {} + +To use this task, run: ```bash -npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts +pnpm hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE ``` -By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. +Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes. -

+
-

- Join our community! | Follow us on X (formerly Twitter) -

+
+ 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.ts` currently 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. + +
+ +### Contract Verification + +You can verify EVM chain contracts using the LayerZero helper package: + +```bash +pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts +``` + +### Troubleshooting + +Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). diff --git a/examples/oapp/package.json b/examples/oapp/package.json index baf749f054..b02ca964db 100644 --- a/examples/oapp/package.json +++ b/examples/oapp/package.json @@ -23,6 +23,7 @@ "devDependencies": { "@babel/core": "^7.23.9", "@layerzerolabs/eslint-config-next": "~2.3.39", + "@layerzerolabs/io-devtools": "~0.2.0", "@layerzerolabs/lz-definitions": "^3.0.75", "@layerzerolabs/lz-evm-messagelib-v2": "^3.0.75", "@layerzerolabs/lz-evm-protocol-v2": "^3.0.75", diff --git a/examples/oft-adapter/tasks/utils.ts b/examples/oft-adapter/tasks/utils.ts index 934c9a046d..5e2fd9b3df 100644 --- a/examples/oft-adapter/tasks/utils.ts +++ b/examples/oft-adapter/tasks/utils.ts @@ -1,9 +1,6 @@ -import { createLogger } from '@layerzerolabs/io-devtools' import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' import { Options } from '@layerzerolabs/lz-v2-utilities' -const logger = createLogger() - export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' /** @@ -14,7 +11,7 @@ export async function getBlockExplorerLink(srcEid: number, txHash: string): Prom const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" const res = await fetch(deploymentMetadataUrl) if (!res.ok) return - const all = (await res.json()) as Record + const all = (await res.json()) as Record const meta = all[network] const explorer = meta?.blockExplorers?.[0]?.url if (explorer) { diff --git a/examples/oft-upgradeable/tasks/utils.ts b/examples/oft-upgradeable/tasks/utils.ts index 1a322be4d9..5e2fd9b3df 100644 --- a/examples/oft-upgradeable/tasks/utils.ts +++ b/examples/oft-upgradeable/tasks/utils.ts @@ -11,7 +11,7 @@ export async function getBlockExplorerLink(srcEid: number, txHash: string): Prom const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" const res = await fetch(deploymentMetadataUrl) if (!res.ok) return - const all = (await res.json()) as Record + const all = (await res.json()) as Record const meta = all[network] const explorer = meta?.blockExplorers?.[0]?.url if (explorer) { diff --git a/examples/oft/tasks/utils.ts b/examples/oft/tasks/utils.ts index 934c9a046d..5e2fd9b3df 100644 --- a/examples/oft/tasks/utils.ts +++ b/examples/oft/tasks/utils.ts @@ -1,9 +1,6 @@ -import { createLogger } from '@layerzerolabs/io-devtools' import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions' import { Options } from '@layerzerolabs/lz-v2-utilities' -const logger = createLogger() - export const deploymentMetadataUrl = 'https://metadata.layerzero-api.com/v1/metadata/deployments' /** @@ -14,7 +11,7 @@ export async function getBlockExplorerLink(srcEid: number, txHash: string): Prom const network = endpointIdToNetwork(srcEid) // e.g. "ethereum-mainnet" const res = await fetch(deploymentMetadataUrl) if (!res.ok) return - const all = (await res.json()) as Record + const all = (await res.json()) as Record const meta = all[network] const explorer = meta?.blockExplorers?.[0]?.url if (explorer) { From 42f18f4b2507b8502a35f016273104fd9cb5d77e Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 13:14:47 +0200 Subject: [PATCH 35/57] lockfile --- pnpm-lock.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 85a852ae4c..a65fd2ff80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,8 @@ importers: specifier: 1.11.0 version: 1.11.0 + docs: {} + examples/lzapp-migration: dependencies: '@layerzerolabs/devtools': @@ -515,6 +517,9 @@ importers: '@layerzerolabs/eslint-config-next': specifier: ~2.3.39 version: 2.3.44(typescript@5.5.3) + '@layerzerolabs/io-devtools': + specifier: ~0.2.0 + version: link:../../packages/io-devtools '@layerzerolabs/lz-definitions': specifier: ^3.0.75 version: 3.0.75 From 941b7653fd747a6a749205304447322910fa091f Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 13:18:11 +0200 Subject: [PATCH 36/57] update specs --- docs/EXAMPLES_SPECS.md | 82 +++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/docs/EXAMPLES_SPECS.md b/docs/EXAMPLES_SPECS.md index c4d03dce00..31f91fe2d6 100644 --- a/docs/EXAMPLES_SPECS.md +++ b/docs/EXAMPLES_SPECS.md @@ -2,7 +2,7 @@ This document is intended for the maintainers of the examples that are in `/exam Currently, this document will only detail the structure for the READMEs of the examples. -## Structure for examples' READMEs +## 1. README Structure 1. **Header** - Goal: Branding + promote docs site + entrypoint @@ -10,7 +10,7 @@ Currently, this document will only detail the structure for the READMEs of the e 2. **Example Title** - Goal: What the example will teach - - Contents: Title + 1–2 sentence description (goal-oriented preferred) + - Contents: Title + 1–2 sentence goal-style description 3. **Table of Contents** - Goal: Allow user to easily navigate the README @@ -21,76 +21,92 @@ Currently, this document will only detail the structure for the READMEs of the e - Contents: e.g., What is an OApp? What is an OFT? 5. **Introduction** _(optional)_ - - Goal: What is this example about - - Contents: Brief explanation if needed; otherwise rely on title + prerequisites + - Goal: High-level context on what this example covers + - Contents: Brief explanation; skip if title + prerequisites suffice 6. **Requirements** - Goal: What needs to be installed - - Contents: List tools + version numbers; consider noting testnet token needs + - Contents: Tools + versions; optionally call out testnet funding needs 7. **Scaffold this example** - - Goal: How to init the example + - Goal: How to initialize the example - Contents: `pnpm dlx create-lz-oapp@latest --example ` 8. **Helper Tasks (inline notice)** - - Goal: Know that helper tasks exist - - Contents: Single line pointing to the detailed section + - Goal: Let users know helpers exist + - Contents: Single-line pointer to helper tasks section 9. **Setup** - - Goal: What to configure before running - - Contents: `.env` instructions, deployer account setup + - Goal: What to configure before building + - Contents: `.env` setup, deployer account prep 10. **Build** - - Goal: How to build contracts/programs/modules - - Contents: Build command(s) + - Goal: How to compile contracts/programs/modules + - Contents: Build commands 11. **Deploy** - Goal: How to deploy contracts/programs/modules - Contents: Deploy command + minting instructions (if applicable) 12. **Enable Messaging** - - Goal: How to set up OApps for use - - Contents: LZ config, init step, wiring step + - Goal: How to wire/configure OApps for messaging + - Contents: LZ config, init, and wiring steps 13. **Sending Message/OFT/ONFT** - - Goal: How to trigger cross-chain actions - - Contents: Send command(s) for both/all directions + - Goal: Trigger a cross-chain action + - Contents: Send command(s), both/all directions 14. **Next Steps** - - Goal: What to know after basic deployment - - Contents: Production Checklist + links to Security Stack, Message Options + - Goal: What to know after completing the deployment + - Contents: Production Deployment Checklist + links (Security Stack, Message Options) 15. **Production Deployment Checklist** - - Goal: Prepare for production - - Contents: Gas profiling, DVNs, confirmations + - Goal: Prep for production usage + - Contents: Gas profiling, DVN config, confirmation settings 16. **Appendix** - - Goal: Mark end of main build steps - - Contents: Additional configuration, testing, and advanced info + - Goal: Mark end of core deployment steps + - Contents: Additional topics and configuration 16.1. **Running tests** - Goal: How to test contracts/programs - - Contents: Test command(s) + - Contents: Test commands 16.2. **Adding other chains** - - Goal: Support more networks - - Contents: Add chain logic; update `hardhat.config.ts` + - Goal: Expand the example to more networks + - Contents: Add logic, update `hardhat.config.ts` 16.3. **Using Multisigs** - - Goal: Deploy using multisig - - Contents: Command param diffs; multi-VM notes + - Goal: Deploy using a multisig wallet + - Contents: Command param diffs, multi-VM notes 16.4. **LayerZero Hardhat Helper Tasks (detailed)** - - Goal: Know all available helpers - - Contents: Link to docs + built-in + local tasks + - Goal: Understand all helper tasks + - Contents: Link to docs + list of built-in and local tasks 16.5. **Contract/Program Verification** - - Goal: How to verify deployments - - Contents: Per-VM verification doc links + - Goal: Verify deployments + - Contents: VM-specific verification docs 16.6. **Troubleshooting** - - Goal: Solve common issues - - Contents: Link to global troubleshooting + local fixes + - Goal: Resolve errors and setup issues + - Contents: Link to general troubleshooting + local fixes + +--- + +## 2. README Principles + +1. Example READMEs should focus on required commands, with elaborations linked to docs. +2. Avoid duplicating explanations of general concepts (e.g., OFTs)—link to docs instead. +3. The first mention of concepts like Endpoint IDs, Wiring, etc. should link to the glossary: https://docs.layerzero.network/v2/home/glossary +4. TODO: Every README should invite partners to provide feedback to drive improvements. + +--- + +## 3. Example Code Principles + +1. **Options-first**: Enforced Options implementation and instructions should be included by default (e.g. in `layerzero.config.ts`). +2. **Two chains only**: Examples should use only 2 chains by default to reduce testnet setup friction; use “Add other chains” section to scale up if needed. From 8c6a47f2b70cf8f754a8e70b816dfe0c8efc5df7 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 13:19:52 +0200 Subject: [PATCH 37/57] fix --- examples/oapp/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 749ac33450..0e6a207056 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -293,7 +293,7 @@ If you do NOT explicitly set each configuration parameter, your OApp will fallba
-### Contract Verification +## Contract Verification You can verify EVM chain contracts using the LayerZero helper package: @@ -301,6 +301,6 @@ You can verify EVM chain contracts using the LayerZero helper package: pnpm dlx @layerzerolabs/verify-contract -n -u -k --contracts ``` -### Troubleshooting +## Troubleshooting Refer to [Debugging Messages](https://docs.layerzero.network/v2/developers/evm/troubleshooting/debugging-messages) or [Error Codes & Handling](https://docs.layerzero.network/v2/developers/evm/troubleshooting/error-messages). From d15d8466f79aa6238eeef3353293fe26b301eac7 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 14:03:23 +0200 Subject: [PATCH 38/57] try docs as workspace package --- docs/package.json | 12 ++++++++++++ pnpm-workspace.yaml | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/package.json diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000000..9f7fdf2e51 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,12 @@ +{ + "name": "@layerzerolabs/devtools-docs", + "version": "0.0.1", + "private": true, + "description": "Documentation for LayerZero devtools", + "scripts": { + "build": "echo 'No build needed for docs'", + "clean": "echo 'No clean needed for docs'", + "lint": "echo 'No lint needed for docs'", + "test": "echo 'No tests for docs'" + } +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ff6d4059f6..5589d5e8ee 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ packages: + - "docs" - "examples/*" - "packages/*" - "tests/*" From 3c454e1cfb3aaf02e57792a412bed8a97f592daf Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 16:36:47 +0200 Subject: [PATCH 39/57] fix --- examples/oft-adapter/README.md | 1 - examples/oft/README.md | 1 - 2 files changed, 2 deletions(-) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 6d70af5423..b6e0d41063 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,7 +79,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft/README.md b/examples/oft/README.md index 9cde874f27..033ee0b968 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,7 +72,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From cfa757974cbf924f65c4ed96e91856a7eb9c3e01 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 17:54:14 +0200 Subject: [PATCH 40/57] fix line --- examples/oft-adapter/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index b6e0d41063..6d70af5423 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,6 +79,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From befb36b1e18e008e1e8899aabf167b25802dc644 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 18:29:57 +0200 Subject: [PATCH 41/57] fix --- examples/oft/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/oft/README.md b/examples/oft/README.md index 033ee0b968..9cde874f27 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,6 +72,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From 1748b13af241895e78be48706f5b41d603cb2e64 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 22:41:20 +0200 Subject: [PATCH 42/57] typo --- examples/oft-adapter/layerzero.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft-adapter/layerzero.config.ts b/examples/oft-adapter/layerzero.config.ts index 46f8ff8af3..03ae1446e1 100644 --- a/examples/oft-adapter/layerzero.config.ts +++ b/examples/oft-adapter/layerzero.config.ts @@ -13,7 +13,7 @@ import type { OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' * * 'optimism-testnet': { * eid: EndpointId.OPTSEP_V2_TESTNET, - * url: process.env.RPC_URL_OP_SEPOLIA || 'https://* optimism-sepolia.gateway.tenderly.co', + * url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co', * accounts, * oftAdapter: { * tokenAddress: '0x0', // Set the token address for the OFT adapter From 87a5b0f713a433ea843f60a79941f1bece5cd705 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 23:09:49 +0200 Subject: [PATCH 43/57] fix README lints --- examples/oapp/README.md | 1 - examples/oft-adapter/README.md | 1 - examples/oft/README.md | 1 - 3 files changed, 3 deletions(-) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 0e6a207056..7281bcf431 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -65,7 +65,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 6d70af5423..b6e0d41063 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,7 +79,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft/README.md b/examples/oft/README.md index 9cde874f27..033ee0b968 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,7 +72,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From 5f9252734024b56375bced6dc42268c4272abae6 Mon Sep 17 00:00:00 2001 From: nazreen Date: Thu, 3 Jul 2025 23:58:19 +0200 Subject: [PATCH 44/57] use pnpm exec for prettier --- examples/oapp/package.json | 4 ++-- examples/oft-adapter/package.json | 4 ++-- examples/oft/package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/oapp/package.json b/examples/oapp/package.json index b02ca964db..446a18b262 100644 --- a/examples/oapp/package.json +++ b/examples/oapp/package.json @@ -9,8 +9,8 @@ "compile:forge": "forge build", "compile:hardhat": "hardhat compile", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", diff --git a/examples/oft-adapter/package.json b/examples/oft-adapter/package.json index 6ef0ae2a9b..4eca3b2756 100644 --- a/examples/oft-adapter/package.json +++ b/examples/oft-adapter/package.json @@ -9,8 +9,8 @@ "compile:forge": "forge build", "compile:hardhat": "hardhat compile", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", diff --git a/examples/oft/package.json b/examples/oft/package.json index 3cf56a12af..185026bb8f 100644 --- a/examples/oft/package.json +++ b/examples/oft/package.json @@ -12,8 +12,8 @@ "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes[],uint256,uint256)'", "gas:run": "forge script scripts/OFTProfilerExample.s.sol:OFTProfilerExample --via-ir --sig 'run(uint256)'", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", From 91070c0e2a0689ce587a57f5ec537fe35425bec9 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 00:25:53 +0200 Subject: [PATCH 45/57] lint fixes --- examples/oapp/README.md | 1 + examples/oft-adapter/README.md | 1 + examples/oft/README.md | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 7281bcf431..0e6a207056 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -65,6 +65,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index b6e0d41063..6d70af5423 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,6 +79,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft/README.md b/examples/oft/README.md index 033ee0b968..9cde874f27 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,6 +72,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From a6298f544cc4d224996d740d9b8a14c608311649 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 00:52:55 +0200 Subject: [PATCH 46/57] Revert "lint fixes" This reverts commit 91070c0e2a0689ce587a57f5ec537fe35425bec9. --- examples/oapp/README.md | 1 - examples/oft-adapter/README.md | 1 - examples/oft/README.md | 1 - 3 files changed, 3 deletions(-) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 0e6a207056..7281bcf431 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -65,7 +65,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index 6d70af5423..b6e0d41063 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,7 +79,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft/README.md b/examples/oft/README.md index 9cde874f27..033ee0b968 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,7 +72,6 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` - - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From 7c2964a0781b62984bca2019a0347a311da46c85 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 00:56:23 +0200 Subject: [PATCH 47/57] apply exec at top level lint-staged --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f30d8a8413..69f7e97155 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "lint-staged": { "**/*.{js,ts,tsx,json}": [ - "pnpm prettier --write --ignore-unknown", + "pnpm exec prettier --write --ignore-unknown", "pnpm eslint --fix" ] }, From a442b09bae8cce52103d2ed6123e5d9bb5902937 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 01:02:46 +0200 Subject: [PATCH 48/57] lint fix --- examples/oapp/README.md | 1 + examples/oft-adapter/README.md | 1 + examples/oft/README.md | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 7281bcf431..0e6a207056 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -65,6 +65,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft-adapter/README.md b/examples/oft-adapter/README.md index b6e0d41063..6d70af5423 100644 --- a/examples/oft-adapter/README.md +++ b/examples/oft-adapter/README.md @@ -79,6 +79,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` diff --git a/examples/oft/README.md b/examples/oft/README.md index 033ee0b968..9cde874f27 100644 --- a/examples/oft/README.md +++ b/examples/oft/README.md @@ -72,6 +72,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava - Copy `.env.example` into a new `.env` - Set up your deployer address/account via the `.env` + - You can specify either `MNEMONIC` or `PRIVATE_KEY`: ``` From 2b2930c078d056a0d09de9495d9c24dbbddbd434 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 01:21:33 +0200 Subject: [PATCH 49/57] reorder --- tests-user/tests/create-lz-oapp.bats | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests-user/tests/create-lz-oapp.bats b/tests-user/tests/create-lz-oapp.bats index 29694a915a..0a33b3cc41 100644 --- a/tests-user/tests/create-lz-oapp.bats +++ b/tests-user/tests/create-lz-oapp.bats @@ -121,8 +121,8 @@ teardown() { cd "$DESTINATION" pnpm compile pnpm test - pnpm lint pnpm lint:fix + pnpm lint } @test "should work with pnpm & oft example in CI mode" { @@ -132,8 +132,8 @@ teardown() { cd "$DESTINATION" pnpm compile pnpm test - pnpm lint pnpm lint:fix + pnpm lint } @test "should work with pnpm & onft721 example in CI mode" { @@ -154,8 +154,8 @@ teardown() { cd "$DESTINATION" pnpm compile pnpm test - pnpm lint pnpm lint:fix + pnpm lint } @test "should work with pnpm & native-oft-adapter example in CI mode" { From ca285d8a6e97dfb340d1ff82d8c81ba88c20a82a Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 10:44:09 +0200 Subject: [PATCH 50/57] try removing pnpm exec --- examples/oapp/package.json | 4 ++-- examples/oft-adapter/package.json | 4 ++-- examples/oft/package.json | 4 ++-- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/oapp/package.json b/examples/oapp/package.json index 446a18b262..b02ca964db 100644 --- a/examples/oapp/package.json +++ b/examples/oapp/package.json @@ -9,8 +9,8 @@ "compile:forge": "forge build", "compile:hardhat": "hardhat compile", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", diff --git a/examples/oft-adapter/package.json b/examples/oft-adapter/package.json index 4eca3b2756..6ef0ae2a9b 100644 --- a/examples/oft-adapter/package.json +++ b/examples/oft-adapter/package.json @@ -9,8 +9,8 @@ "compile:forge": "forge build", "compile:hardhat": "hardhat compile", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", diff --git a/examples/oft/package.json b/examples/oft/package.json index 185026bb8f..3cf56a12af 100644 --- a/examples/oft/package.json +++ b/examples/oft/package.json @@ -12,8 +12,8 @@ "gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes[],uint256,uint256)'", "gas:run": "forge script scripts/OFTProfilerExample.s.sol:OFTProfilerExample --via-ir --sig 'run(uint256)'", "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", - "lint:fix": "eslint --fix '**/*.{js,ts,json}' && pnpm exec prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", - "lint:js": "eslint '**/*.{js,ts,json}' && pnpm exec prettier --check .", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", "lint:sol": "solhint 'contracts/**/*.sol'", "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", "test:forge": "forge test", diff --git a/package.json b/package.json index 69f7e97155..5f379c4855 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "lint-staged": { "**/*.{js,ts,tsx,json}": [ - "pnpm exec prettier --write --ignore-unknown", + "prettier --write --ignore-unknown", "pnpm eslint --fix" ] }, From 66f0cde2132b97f615b6e85af0565707b4493bde Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 12:20:45 +0200 Subject: [PATCH 51/57] reduce to 2 chains --- examples/oapp/hardhat.config.ts | 7 ++----- examples/oapp/layerzero.config.ts | 25 ++----------------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/examples/oapp/hardhat.config.ts b/examples/oapp/hardhat.config.ts index 45ab695a6e..6750b79054 100644 --- a/examples/oapp/hardhat.config.ts +++ b/examples/oapp/hardhat.config.ts @@ -13,6 +13,8 @@ import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' import { EndpointId } from '@layerzerolabs/lz-definitions' +import './tasks/sendString' + // Set your preferred authentication method // // If you prefer using a mnemonic, set a MNEMONIC environment variable @@ -57,11 +59,6 @@ const config: HardhatUserConfig = { url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co', accounts, }, - 'avalanche-testnet': { - eid: EndpointId.AVALANCHE_V2_TESTNET, - url: process.env.RPC_URL_FUJI || 'https://avalanche-fuji.drpc.org', - accounts, - }, 'arbitrum-testnet': { eid: EndpointId.ARBSEP_V2_TESTNET, url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co', diff --git a/examples/oapp/layerzero.config.ts b/examples/oapp/layerzero.config.ts index 3700145155..844542b809 100644 --- a/examples/oapp/layerzero.config.ts +++ b/examples/oapp/layerzero.config.ts @@ -8,11 +8,6 @@ const optimismContract: OmniPointHardhat = { contractName: 'MyOApp', } -const avalancheContract: OmniPointHardhat = { - eid: EndpointId.AVALANCHE_V2_TESTNET, - contractName: 'MyOApp', -} - const arbitrumContract: OmniPointHardhat = { eid: EndpointId.ARBSEP_V2_TESTNET, contractName: 'MyOApp', @@ -31,41 +26,25 @@ const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ ] // To connect all the above chains to each other, we need the following pathways: -// Optimism <-> Avalanche // Optimism <-> Arbitrum -// Avalanche <-> Arbitrum // With the config generator, pathways declared are automatically bidirectional // i.e. if you declare A,B there's no need to declare B,A const pathways: TwoWayConfig[] = [ [ optimismContract, // Chain A contract - avalancheContract, // Chain B contract + arbitrumContract, // 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 ], - [ - optimismContract, // Chain A contract - arbitrumContract, // Chain C contract - [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] - [1, 1], // [A to B confirmations, B to A confirmations] - [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain A enforcedOptions - ], - [ - avalancheContract, // Chain B contract - arbitrumContract, // Chain C contract - [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] - [1, 1], // [A to B confirmations, B to A confirmations] - [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain C enforcedOptions, Chain B enforcedOptions - ], ] export default async function () { // Generate the connections config based on the pathways const connections = await generateConnectionsConfig(pathways) return { - contracts: [{ contract: optimismContract }, { contract: avalancheContract }, { contract: arbitrumContract }], + contracts: [{ contract: optimismContract }, { contract: arbitrumContract }], connections, } } From 9b76d41a9670eeeea2b8df4c22cfe029ed26ed1b Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 12:40:56 +0200 Subject: [PATCH 52/57] single quotes --- examples/oapp/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 0e6a207056..2aee91bda5 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -124,7 +124,7 @@ With your OApps wired, you can now send messages cross-chain. Send a message from **Ethereum Sepolia** to **Arbitrum Sepolia**: ```bash -pnpm hardhat lz:oapp:send --src-eid 40161 --dst-eid 40231 --msg "Hello from Ethereum!" +pnpm hardhat lz:oapp:send --src-eid 40161 --dst-eid 40231 --msg 'Hello from Ethereum!' ``` > :information_source: `40161` and `40231` are the Endpoint IDs of Ethereum Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. From c15dbe82b12c7340afdc83c0d3f70c7061ba3c95 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 12:46:36 +0200 Subject: [PATCH 53/57] update instructions --- examples/oapp/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/oapp/README.md b/examples/oapp/README.md index 2aee91bda5..5a3ed942d6 100644 --- a/examples/oapp/README.md +++ b/examples/oapp/README.md @@ -74,7 +74,7 @@ Throughout this walkthrough, helper tasks will be used. For the full list of ava 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: **Ethereum Sepolia** and **Arbitrum Sepolia**. +- 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: **Optimism Sepolia** and **Arbitrum Sepolia**. ## Build @@ -121,13 +121,19 @@ Submit all the transactions to complete wiring. After all transactions confirm, With your OApps wired, you can now send messages cross-chain. -Send a message from **Ethereum Sepolia** to **Arbitrum Sepolia**: +Send a message from **Optimism Sepolia** to **Arbitrum Sepolia**: ```bash -pnpm hardhat lz:oapp:send --src-eid 40161 --dst-eid 40231 --msg 'Hello from Ethereum!' +pnpm hardhat lz:oapp:send --dst-eid 40231 --string 'Hello from Ethereum!' --network optimism-testnet ``` -> :information_source: `40161` and `40231` are the Endpoint IDs of Ethereum Sepolia and Arbitrum Sepolia respectively. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. +Send a message from **Arbitrum Sepolia** to **Optimism Sepolia**: + +```bash +pnpm hardhat lz:oapp:send --dst-eid 40161 --string 'Hello from Arbitrum!' --network arbitrum-testnet +``` + +> :information_source: `40161` and `40231` are the Endpoint IDs of Optimism Sepolia and Arbitrum Sepolia respectively. The source network is determined by the `--network` flag, not a separate `--src-eid` parameter. View the list of chains and their Endpoint IDs on the [Deployed Endpoints](https://docs.layerzero.network/v2/deployments/deployed-contracts) page. Upon a successful send, the script will provide you with the link to the message on LayerZero Scan. From ee0d30db8cea07224ea4ce08eb8fb55f7bb9cc57 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 13:18:27 +0200 Subject: [PATCH 54/57] typo --- examples/oft-upgradeable/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft-upgradeable/README.md b/examples/oft-upgradeable/README.md index 994253ec7a..f220031929 100644 --- a/examples/oft-upgradeable/README.md +++ b/examples/oft-upgradeable/README.md @@ -144,7 +144,7 @@ The OFT standard builds on top of the OApp standard, which enables generic messa Run the wiring task: ```bash -pnpm hardhat lz:oapp:wire --oapp-config layerzero.config +pnpm hardhat lz:oapp:wire --oapp-config layerzero.config.ts ``` Submit all the transactions to complete wiring. After all transactions confirm, your OApps are wired and can send messages to each other. From 2830f6f94679702d3e8973fb21698066afb4cb73 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 16:07:10 +0200 Subject: [PATCH 55/57] changesets --- .changeset/odd-carrots-stare.md | 5 +++++ .changeset/perfect-bobcats-complain.md | 5 +++++ .changeset/serious-beds-fetch.md | 5 +++++ .changeset/tiny-books-drive.md | 5 +++++ .changeset/wise-numbers-hammer.md | 5 +++++ 5 files changed, 25 insertions(+) create mode 100644 .changeset/odd-carrots-stare.md create mode 100644 .changeset/perfect-bobcats-complain.md create mode 100644 .changeset/serious-beds-fetch.md create mode 100644 .changeset/tiny-books-drive.md create mode 100644 .changeset/wise-numbers-hammer.md diff --git a/.changeset/odd-carrots-stare.md b/.changeset/odd-carrots-stare.md new file mode 100644 index 0000000000..f0f8f13678 --- /dev/null +++ b/.changeset/odd-carrots-stare.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oapp-example": minor +--- + +reduce to 2 chains and revamp README diff --git a/.changeset/perfect-bobcats-complain.md b/.changeset/perfect-bobcats-complain.md new file mode 100644 index 0000000000..6ad46e4dca --- /dev/null +++ b/.changeset/perfect-bobcats-complain.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/mint-burn-oft-adapter-example": patch +--- + +fix any type usage diff --git a/.changeset/serious-beds-fetch.md b/.changeset/serious-beds-fetch.md new file mode 100644 index 0000000000..c1d9a81e38 --- /dev/null +++ b/.changeset/serious-beds-fetch.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-example": minor +--- + +revamp README, add mock deploy script, reduce to 2 chains diff --git a/.changeset/tiny-books-drive.md b/.changeset/tiny-books-drive.md new file mode 100644 index 0000000000..6366dcacdd --- /dev/null +++ b/.changeset/tiny-books-drive.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-adapter-example": minor +--- + +revamp README, add mock deploy script, reduce to 2 chains diff --git a/.changeset/wise-numbers-hammer.md b/.changeset/wise-numbers-hammer.md new file mode 100644 index 0000000000..c84046aae6 --- /dev/null +++ b/.changeset/wise-numbers-hammer.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-upgradeable-example": minor +--- + +revamp README, add mock deploy script, reduce to 2 chains, add send scripts From b2176f732a9b6ce522b3d5c88dbeded6a2148dcc Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 16:07:23 +0200 Subject: [PATCH 56/57] revert --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f379c4855..f30d8a8413 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "lint-staged": { "**/*.{js,ts,tsx,json}": [ - "prettier --write --ignore-unknown", + "pnpm prettier --write --ignore-unknown", "pnpm eslint --fix" ] }, From 22f4b435d03e9d38f52864d0b75b0671e3f1d636 Mon Sep 17 00:00:00 2001 From: nazreen Date: Fri, 4 Jul 2025 16:54:05 +0200 Subject: [PATCH 57/57] reformat --- docs/EXAMPLES_SPECS.md | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/EXAMPLES_SPECS.md b/docs/EXAMPLES_SPECS.md index 31f91fe2d6..5067f23874 100644 --- a/docs/EXAMPLES_SPECS.md +++ b/docs/EXAMPLES_SPECS.md @@ -1,8 +1,23 @@ -This document is intended for the maintainers of the examples that are in `/examples` in this repo. It is also meant as a guide for coding agents for the purposes of reviewing or editing. +# Example Specs + +## Table of Contents + +- [Audience](#audience) +- [README Structure](#readme-structure) +- [Example README Principles](#example-readme-principles) +- [Example Code Principles](#example-code-principles) + +## Audience + +This guide is intended for both: +- **Developers** maintaining or contributing to `/examples` +- **Coding agents** (e.g. Cursor, Copilot, GPT) that assist with editing, reviewing, or scaffolding examples + +AI agents should be pointed to this file via `AGENTS.md` or `.cursor/rules`. Currently, this document will only detail the structure for the READMEs of the examples. -## 1. README Structure +## README Structure 1. **Header** - Goal: Branding + promote docs site + entrypoint @@ -30,7 +45,7 @@ Currently, this document will only detail the structure for the READMEs of the e 7. **Scaffold this example** - Goal: How to initialize the example - - Contents: `pnpm dlx create-lz-oapp@latest --example ` + - Contents: `pnpm dlx create-lz-oapp@latest --example ` (Some examples require a feature flag. Refer to `packages/create-lz-oapp/src/config.ts` to verify) 8. **Helper Tasks (inline notice)** - Goal: Let users know helpers exist @@ -54,7 +69,7 @@ Currently, this document will only detail the structure for the READMEs of the e 13. **Sending Message/OFT/ONFT** - Goal: Trigger a cross-chain action - - Contents: Send command(s), both/all directions + - Contents: CLI command to triffer send, both/all directions. E.g. for examples/oft, it is `pnpm hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1 --to ` 14. **Next Steps** - Goal: What to know after completing the deployment @@ -92,9 +107,12 @@ Currently, this document will only detail the structure for the READMEs of the e - Goal: Resolve errors and setup issues - Contents: Link to general troubleshooting + local fixes +Any sections that don't appear in the above list should be considered for removal. + + --- -## 2. README Principles +## Example README Principles 1. Example READMEs should focus on required commands, with elaborations linked to docs. 2. Avoid duplicating explanations of general concepts (e.g., OFTs)—link to docs instead. @@ -103,13 +121,7 @@ Currently, this document will only detail the structure for the READMEs of the e --- -## 3. Example Code Principles +## Example Code Principles 1. **Options-first**: Enforced Options implementation and instructions should be included by default (e.g. in `layerzero.config.ts`). 2. **Two chains only**: Examples should use only 2 chains by default to reduce testnet setup friction; use “Add other chains” section to scale up if needed. - - - - - -Any sections that don't appear in the above list should be considered for removal. \ No newline at end of file