Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/violet-parents-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@layerzerolabs/oft-alt-example": minor
---

Feat: add better support for simple config and the latest tooling found in OFT and OFT-Solana
1 change: 1 addition & 0 deletions examples/oft-alt/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require('@rushstack/eslint-patch/modern-module-resolution');

module.exports = {
root: true,
extends: ['@layerzerolabs/eslint-config-next/recommended'],
rules: {
// @layerzerolabs/eslint-config-next defines rules for turborepo-based projects
Expand Down
662 changes: 314 additions & 348 deletions examples/oft-alt/README.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions examples/oft-alt/contracts/MyOFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";

/// @notice Standard OFT for regular EVM chains (uses native gas for fees).
/// @dev This contract is deployed on chains with standard EndpointV2 (not Alt).
/// It connects to OFTAlt contracts on chains with Alt Endpoints.
contract MyOFT is OFT {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
}

53 changes: 53 additions & 0 deletions examples/oft-alt/deploy/MyOFT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import assert from 'assert'

import { type DeployFunction } from 'hardhat-deploy/types'

const contractName = 'MyOFT'

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
15 changes: 12 additions & 3 deletions examples/oft-alt/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types'
import { EndpointId } from '@layerzerolabs/lz-definitions'

import './type-extensions'
import './tasks/index'

// Set your preferred authentication method
//
Expand Down Expand Up @@ -54,16 +55,24 @@ const config: HardhatUserConfig = {
],
},
networks: {
// ========== STANDARD EVM CHAINS ==========
// These chains use standard EndpointV2 with native gas fee payment.
// Deploy MyOFT (standard OFT) on these chains.
'arbitrum-sepolia': {
eid: EndpointId.ARBSEP_V2_TESTNET,
url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co',
accounts,
},
'base-sepolia': {
eid: EndpointId.BASESEP_V2_TESTNET,
url: process.env.RPC_URL_BASE_SEPOLIA || 'https://base-sepolia.gateway.tenderly.co',

// ========== ALT ENDPOINT CHAINS ==========
// Chains with Alt Endpoints use ERC-20 tokens for fee payment instead of native gas.
// Deploy MyOFTAlt on these chains.
'tempo-testnet': {
eid: EndpointId.TEMPO_V2_TESTNET,
url: process.env.RPC_URL_TEMPO_TESTNET || 'https://rpc.testnet.tempo.xyz',
accounts,
},

hardhat: {
// Need this for testing because TestHelperOz5.sol is exceeding the compiled contract size limit
allowUnlimitedContractSize: true,
Expand Down
134 changes: 84 additions & 50 deletions examples/oft-alt/layerzero.config.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,94 @@
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: 'MyOFTAltAdapter',
}
// ======================================
// SECTION 1: CONTRACT DEFINITIONS
// ======================================
// This example demonstrates the primary use case for OFTAlt:
// Connecting an OFTAlt on a chain with an Alt Endpoint (ERC20 fee payment)
// to standard OFT contracts on regular EVM chains (native gas fee payment).
//
// Primary scenario:
// - OFTAlt deployed on Tempo (Alt Endpoint - fees paid in stablecoins)
// - Standard OFT deployed on Arbitrum (regular EndpointV2 - fees paid in native gas)

const fujiContract: OmniPointHardhat = {
eid: EndpointId.AVALANCHE_V2_TESTNET,
contractName: 'MyOFTAlt',
// ========== ALT ENDPOINT CHAINS ==========
// OFTAlt on chains with EndpointV2Alt (ERC20 fee payment)
// Tempo is a payments-focused blockchain where fees are paid in TIP-20 stablecoins
const tempoContract: OmniPointHardhat = {
eid: EndpointId.TEMPO_V2_TESTNET,
contractName: 'MyOFTAlt', // OFTAlt for Alt Endpoint chains (ERC20 fee payment)
}

const amoyContract: OmniPointHardhat = {
eid: EndpointId.AMOY_V2_TESTNET,
contractName: 'MyOFTAlt',
// ========== STANDARD EVM CHAINS ==========
// Standard OFT on chains with regular EndpointV2 (native gas fee payment)
const arbitrumContract: OmniPointHardhat = {
eid: EndpointId.ARBSEP_V2_TESTNET,
contractName: 'MyOFT', // Standard OFT for regular EVM chains
}

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,
},
// ======================================
// SECTION 2: ENFORCED OPTIONS
// ======================================
// Define the gas options for destination chain execution.
// These options ensure a minimum gas amount is provided for lzReceive execution.
//
// For production, profile your contract's lzReceive gas usage on each destination
// chain and set appropriate values. The values below are examples.
//
// Learn more: https://docs.layerzero.network/v2/concepts/applications/oapp-standard#execution-options-and-enforced-settings

const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [
{
msgType: 1, // SEND message type
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 80000, // Gas limit for lzReceive on destination
value: 0, // Native value to send (usually 0 for OFT)
},
]

// ======================================
// SECTION 3: PATHWAY CONFIGURATION
// ======================================
// Define bidirectional pathways between contracts.
// The Simple Config Generator automatically creates both directions (A→B and B→A).
//
// Each pathway specifies:
// - Contract pair (source, destination)
// - DVN configuration: [ [requiredDVNs], [optionalDVNs, threshold] ]
// - Block confirmations: [A→B confirmations, B→A confirmations]
// - Enforced options: [B's enforcedOptions, A's enforcedOptions]
//
// For production, configure appropriate DVNs for your security requirements.
// See: https://docs.layerzero.network/v2/developers/evm/configuration/dvn-executor-config

const pathways: TwoWayConfig[] = [
// Tempo (Alt Endpoint) <-> Arbitrum (Standard Endpoint)
// This demonstrates the primary use case: OFTAlt on Alt chain ↔ OFT on standard chain
[
tempoContract, // Alt Endpoint chain (OFTAlt with ERC20 fees)
arbitrumContract, // Standard chain (OFT with native gas fees)
[['LayerZero Labs'], []], // DVNs: Required=[LayerZero Labs], Optional=[]
[1, 1], // Block confirmations (increase for mainnet)
[EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Enforced options
],
}
]

export default config
// ======================================
// SECTION 4: EXPORT CONFIGURATION
// ======================================
// The generateConnectionsConfig function creates the full connection configuration
// from the pathway definitions, including DVN addresses and executor settings
// resolved from LayerZero metadata.

export default async function () {
const connections = await generateConnectionsConfig(pathways)
return {
contracts: [{ contract: tempoContract }, { contract: arbitrumContract }],
connections,
}
}
18 changes: 12 additions & 6 deletions examples/oft-alt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"compile": "concurrently -c auto --names forge,hardhat '$npm_execpath run compile:forge' '$npm_execpath run compile:hardhat'",
"compile:forge": "forge build",
"compile:hardhat": "hardhat compile",
"gas:lzCompose": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzCompose(string,address,uint32,address,uint32,address,address,bytes[],uint256,uint256)'",
"gas:lzReceive": "forge script scripts/GasProfiler.s.sol:GasProfilerScript --via-ir --sig 'run_lzReceive(string,address,uint32,address,uint32,address,bytes[],uint256,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 .",
Expand All @@ -22,22 +24,26 @@
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@layerzerolabs/devtools": "~2.0.4",
"@layerzerolabs/devtools-evm-hardhat": "^4.0.4",
"@layerzerolabs/eslint-config-next": "~2.3.39",
"@layerzerolabs/lz-definitions": "^3.0.12",
"@layerzerolabs/lz-evm-messagelib-v2": "^3.0.12",
"@layerzerolabs/lz-evm-protocol-v2": "^3.0.12",
"@layerzerolabs/lz-evm-v1-0.7": "^3.0.12",
"@layerzerolabs/lz-v2-utilities": "^3.0.12",
"@layerzerolabs/io-devtools": "~0.3.2",
"@layerzerolabs/lz-definitions": "^3.0.151",
"@layerzerolabs/lz-evm-messagelib-v2": "^3.0.148",
"@layerzerolabs/lz-evm-protocol-v2": "^3.0.148",
"@layerzerolabs/lz-evm-v1-0.7": "^3.0.148",
"@layerzerolabs/lz-v2-utilities": "^3.0.148",
"@layerzerolabs/metadata-tools": "^3.0.0",
"@layerzerolabs/oapp-alt-evm": "^0.0.5",
"@layerzerolabs/oapp-evm": "^0.4.1",
"@layerzerolabs/oapp-evm-upgradeable": "^0.1.3",
"@layerzerolabs/oft-alt-evm": "^0.0.5",
"@layerzerolabs/oft-evm": "^4.0.1",
"@layerzerolabs/prettier-config-next": "^2.3.39",
"@layerzerolabs/protocol-devtools-evm": "^5.0.2",
"@layerzerolabs/solhint-config": "^3.0.12",
"@layerzerolabs/test-devtools-evm-foundry": "~8.0.1",
"@layerzerolabs/toolbox-foundry": "~0.1.9",
"@layerzerolabs/toolbox-foundry": "~0.1.13",
"@layerzerolabs/toolbox-hardhat": "~0.6.13",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@nomiclabs/hardhat-ethers": "^2.2.3",
Expand Down
1 change: 1 addition & 0 deletions examples/oft-alt/tasks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './sendOFT'
Loading
Loading