const meta = all[network]
const explorer = meta?.blockExplorers?.[0]?.url
if (explorer) {
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
},
};
diff --git a/examples/oft-upgradeable/README.md b/examples/oft-upgradeable/README.md
index 9778e80bae..f220031929 100644
--- a/examples/oft-upgradeable/README.md
+++ b/examples/oft-upgradeable/README.md
@@ -1,21 +1,61 @@
-
+
- Homepage | Docs | Developers
+ LayerZero Docs
-Omnichain Fungible Token (OFT) Upgradeable Example
-
-
- Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses | DVN Addresses
-
-
-Template project for getting started with LayerZero's OFT contract standard.
+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).
: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,309 @@ 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
+
+> 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
+
+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.
+
+> 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
+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)" 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.
-- [So What is an Omnichain Fungible Token?](#so-what-is-an-omnichain-fungible-token)
-- [Available Helpers in this Repo](#layerzero-hardhat-helper-tasks)
+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 `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:
+
+```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`.
-The Omnichain Fungible Token (OFT) Standard is an ERC20 token that can be transferred across multiple blockchains without asset wrapping or middlechains.
+
-
+Then, modify `layerzero.config.ts` with the following changes:
-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.
+- 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
-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.
+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.
- 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 +378,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 +396,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 +503,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 +575,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 +602,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
-```
-
-#### 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.
-
-
-
-## Connecting Contracts
-
-### Ethereum Configurations
+### Manual Configuration
-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",
-};
-```
+This section only applies if you would like to configure manually instead of using the Simple Config Generator.
-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 +653,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 +724,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).
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..5e2fd9b3df
--- /dev/null
+++ b/examples/oft-upgradeable/tasks/utils.ts
@@ -0,0 +1,48 @@
+import { endpointIdToNetwork } from '@layerzerolabs/lz-definitions'
+import { Options } from '@layerzerolabs/lz-v2-utilities'
+
+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/examples/oft/README.md b/examples/oft/README.md
index 0504b12757..9cde874f27 100644
--- a/examples/oft/README.md
+++ b/examples/oft/README.md
@@ -1,51 +1,350 @@
-
+
- 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.
+
+## 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)
+
+## 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
+```
+
+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
+
+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:
+
+```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 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)" 1000000000000000000 --private-key --rpc-url
+
+```
+
+> 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
+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!
+
+> 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.
- 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
-
+## Running Tests
-- [So What is an Omnichain Fungible Token?](#so-what-is-an-omnichain-fungible-token)
-- [Available Helpers in this Repo](#layerzero-hardhat-helper-tasks)
+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
+```
-## So what is an Omnichain Fungible Token?
+## Adding other chains
-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`.
-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.
+
-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.
+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.
- 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,
@@ -58,10 +357,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
@@ -70,84 +375,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
@@ -168,32 +482,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
@@ -258,7 +554,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
@@ -285,231 +581,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 +632,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 +703,14 @@ 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).
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
new file mode 100644
index 0000000000..cf4b6bb159
--- /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: {
+ // 'optimism-testnet': {
+ // ...
+ // eid: EndpointId.OPTSEP_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
diff --git a/examples/oft/hardhat.config.ts b/examples/oft/hardhat.config.ts
index 44e95bde00..979e85c14d 100644
--- a/examples/oft/hardhat.config.ts
+++ b/examples/oft/hardhat.config.ts
@@ -59,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/oft/layerzero.config.ts b/examples/oft/layerzero.config.ts
index 1b924c79ce..34fd8de61e 100644
--- a/examples/oft/layerzero.config.ts
+++ b/examples/oft/layerzero.config.ts
@@ -7,23 +7,16 @@ import type { OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat'
const optimismContract: OmniPointHardhat = {
eid: EndpointId.OPTSEP_V2_TESTNET,
- contractName: 'MyOFT',
-}
-
-const avalancheContract: OmniPointHardhat = {
- eid: EndpointId.AVALANCHE_V2_TESTNET,
- contractName: 'MyOFT',
+ 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:
-// 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,
}
}
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) {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0bab7cd994..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
@@ -2533,6 +2538,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 +2559,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 +4045,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 +4230,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 +4758,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 +5385,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 +5496,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 +10326,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 +10357,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 +10465,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 +10792,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 +10823,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 +19755,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 +20195,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}
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/*"
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" {