Skip to content

Instantly share code, notes, and snippets.

@vivekascoder
Last active January 20, 2026 19:42
Show Gist options
  • Select an option

  • Save vivekascoder/81935274a9b84d9c1fd0b15d67210e5b to your computer and use it in GitHub Desktop.

Select an option

Save vivekascoder/81935274a9b84d9c1fd0b15d67210e5b to your computer and use it in GitHub Desktop.

Layerzero integration doc

Layer zero offers the following thing

OFT: For new ERC20 cross chain tokens.

OFTAdapter: For existing ERC20 deployed tokens (works by locking tokens in the contract)

There can only be one OFT Adapter lockbox in your omnichain deployment. Multiple adapters break unified liquidity and can cause permanent token loss due to insufficient destination supply.

OVault:

OVault uses ERC4626 contract + a OFTAdapter for the shares tokens + a OFT base asset or OFTAdapter for the base asset if base asset isn’t OFT.

So for existing shares token we can put a OFTAdapter followed by OFT on other chains.

If we go with OVault in new Vaults, it will allow cross chain vault interaction, the ERC4626 vault still stays only on the one chain, but you can call ERC4626 operations cross chain.

To support OFT for existing assets

we deploy a OFTAdapter for that asset on original chain, and OFT for that token on other chains.

For stargate integration

https://docs.stargate.finance/ecosystem/list-on-stargate

As mentioned in their doc they list any OFT and charge a 2bps (0.02%) fee on stargate transfer fee.

When deploying for an existing vault, the ERC-4626 cross-chain functionality isn't supported, correct?

As long as the asset vault uses is an OFT, we can deploy ShareOFTAdapter for existing ERC4626 vault thus enabling cross chain vault interaction via ShareOFTAdapter.

So, for the existing share token, we can deploy an OFTAdapter and then deploy OFTs on other chains. This means the vault share is handled as a plain ERC-20 token.

Yes exactly.

OFTAdapter + OFT L2 deployment process

srUSDe deployment so we’ll have a OFTAdapter contract for srUSDe like

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

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

/// @notice OFTAdapter uses a deployed ERC-20 token and SafeERC20 to interact with the OFTCore contract.
contract SrUSDeAdapter is OFTAdapter {
    constructor(
        address _token,
        address _lzEndpoint,
        address _owner
    ) OFTAdapter(_token, _lzEndpoint, _owner) Ownable(_owner) {}
}
contract SrUSDeOFT is OFT {
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpoint,
        address _owner
    ) OFT(_name, _symbol, _lzEndpoint, _owner) Ownable(_owner) {}
}

Layerzero config will look like the following, where you link ur OFTAdapter deployment + All deployments of OFT for srUSDe on other chains, and create path between them so they can communicate cross chain (this is a reference config).

import {EndpointId} from '@layerzerolabs/lz-definitions';
import {ExecutorOptionType} from '@layerzerolabs/lz-v2-utilities';
import {TwoWayConfig, generateConnectionsConfig} from '@layerzerolabs/metadata-tools';
import {OAppEnforcedOption, OmniPointHardhat} from '@layerzerolabs/toolbox-hardhat';

// This contract object defines the OApp deployment on Optimism Sepolia testnet
// The config references the contract deployment from your ./deployments folder
const optimismContract: OmniPointHardhat = {
  eid: EndpointId.OPTSEP_V2_TESTNET,
  contractName: 'MyOFT',
};

const arbitrumContract: OmniPointHardhat = {
  eid: EndpointId.ARBSEP_V2_TESTNET,
  contractName: 'MyOFT',
};

// 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 OApp._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,
  },
];

// To connect all the above chains to each other, we need the following pathways:
// Optimism <-> Arbitrum

// With the config generator, pathways declared are automatically bidirectional
// i.e. if you declare A,B there's no need to declare B,A
const pathways: TwoWayConfig[] = [
  [
    optimismContract, // Chain A contract
    arbitrumContract, // Chain B contract
    [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ]
    [1, 1], // [A to B confirmations, B to A confirmations]
    [EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions
  ],
];

export default async function () {
  // Generate the connections config based on the pathways
  const connections = await generateConnectionsConfig(pathways);
  return {
    contracts: [{contract: optimismContract}, {contract: arbitrumContract}],
    connections,
  };
}

Then you config these deployments using layerzero wire command

npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts

If we can upgrade our existing vaults (Junior, Senior), what changes should we apply to make the cross-chain functionality work?

Basically OVault is ERC4626 + OFTAdapter/OFT for base asset + OFAdapter for share token + A composer contract

The composer contract handles the the deposits/redeem post sending assets from hub to spoke chain, think of composer contract in OVault like a post contract hook that gets triggered from the OApp (https://github.com/LayerZero-Labs/devtools/blob/ba9762f99a6995101d7098c43ed8608dd125faab/packages/ovault-evm/contracts/VaultComposerSync.sol#L118)

So, the cross chain deposit/redeem happens using compose msg and composer contract https://github.com/LayerZero-Labs/devtools/blob/36c2cb8c926b6dcc5a1cee9bb1e8579c9b9ae9cc/examples/ovault-evm/tasks/sendOVaultComposer.ts#L406

as long as the address of ERC4626 is same considering the base asset is always constant, we wont need any change in the OVault to adapt new changes, it’ll require redeployment if the Vault address changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment