Skip to content

Latest commit

 

History

History
730 lines (567 loc) · 52 KB

README.md

File metadata and controls

730 lines (567 loc) · 52 KB

LayerZero

Homepage | Docs | Developers

Omnichain Fungible Token (OFT) Solana Example

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

A boilerplate for streamlining the process of building, testing, deploying, and configuring Omnichain Fungible Tokens (OFTs) on Solana

Warning

The OFT Solana Example is currently an experimental build that is subject to changes.

You can enable this build by running: LZ_ENABLE_EXPERIMENTAL_SOLANA_OFT_EXAMPLE=1 npx create-lz-oapp@latest

So What is an Omnichain Fungible Token?

  • OFT Solana Program (Beta)
  • Installation
  • Available Helpers in this Repo
  • Omnichain Fungible Token

    The Omnichain Fungible Token (OFT) Standard allows fungible tokens to be transferred across multiple blockchains without asset wrapping or middlechains.

    This standard works by burning tokens on the source chain whenever an omnichain transfer is initiated, sending a message via the protocol, and delivering a function call to the destination contract to mint the same number of tokens burned. This creates a unified supply across all networks LayerZero supports that the OFT is deployed on.

    Read more about what you can do with OFTs by reading the OFT Quickstart in the LayerZero Documentation.

    title

    OFT Solana Program (Beta)

    To be compatible with the Solana Account Model and composable with existing Solana DeFi applications, the Omnichain Fungible Token (OFT) Program extends the existing Solana Program Library (SPL) fungible token to interact with the LayerZero Endpoint, Message Library, DVN(s), and Executor programs.

    LayerZero

    Audit Reports

    Installation

    Installing dependencies

    We recommend using pnpm as a package manager (but you can of course use a package manager of your choice):

    pnpm install

    Compiling your program

    pnpm compile

    Running tests

    pnpm test

    Preparing OFT Program ID

    Create programId keypair files by running:

    solana-keygen new -o target/deploy/endpoint-keypair.json
    solana-keygen new -o target/deploy/oft-keypair.json
    
    anchor keys sync

    ⚠️ You will want to use the --force flag to generate your own keypair if these keys already exist.

    Deploying OFT Program

    Using anchor

    anchor build -v
    solana program deploy --program-id target/deploy/oft-keypair.json target/verifiable/oft.so -u devnet

    Using solana-verify

    solana-verify build
    solana program deploy --program-id target/deploy/oft-keypair.json target/deploy/oft.so -u mainnet-beta

    please visit Solana Verify CLI and Deploy a Solana Program with the CLI for more detail.

    Notice

    If you encounter issues during compilation and testing, it might be due to the versions of Solana and Anchor. You can switch to Solana version 1.17.31 and Anchor version 0.29.0, as these are the versions we have tested and verified to be working.

    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

    Deploys your contract to any of the available networks in your hardhat.config.ts when given a deploy tag (by default contract name) and returns a list of available networks to select for the deployment. For specifics around all deployment options, please refer to the Deploying Contracts section of the documentation. LayerZero's lz:deploy utilizes hardhat-deploy.

    'arbitrum-sepolia': {
        eid: EndpointId.ARBSEP_V2_TESTNET,
        url: process.env.RPC_URL_ARBSEP_TESTNET,
        accounts,
    },
    'base-sepolia': {
        eid: EndpointId.BASESEP_V2_TESTNET,
        url: process.env.RPC_URL_BASE_TESTNET,
        accounts,
    },

    To add an existing deployment, refer to the hardhat-deploy instructions here.

    npx hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME

    Initializes a layerzero.config.ts file for all available pathways between your hardhat networks with the current LayerZero default placeholder settings. This task can be incredibly useful for correctly formatting your config file.

    You can run this task by providing the contract-name you want to set for the config and file-name you want to generate:

    npx 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:

    import { EndpointId } from '@layerzerolabs/lz-definitions'
    
    const arbsepContract = {
        eid: EndpointId.ARBSEP_V2_TESTNET,
        contractName: 'MyOFT',
    }
    const sepoliaContract = {
        eid: EndpointId.SEPOLIA_V2_TESTNET,
        contractName: 'MyOFT',
    }
    
    export default {
        contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }],
        connections: [
            {
                from: arbsepContract,
                to: sepoliaContract,
                config: {
                    sendLibrary: '0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E',
                    receiveLibraryConfig: { receiveLibrary: '0x75Db67CDab2824970131D5aa9CECfC9F69c69636', gracePeriod: 0 },
                    sendConfig: {
                        executorConfig: { maxMessageSize: 10000, executor: '0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897' },
                        ulnConfig: {
                            confirmations: 1,
                            requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'],
                            optionalDVNs: [],
                            optionalDVNThreshold: 0,
                        },
                    },
                    // receiveConfig: {
                    //     ulnConfig: {
                    //         confirmations: 2,
                    //         requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'],
                    //         optionalDVNs: [],
                    //         optionalDVNThreshold: 0,
                    //     },
                    // },
                },
            },
            {
                from: sepoliaContract,
                to: arbsepContract,
                config: {
                    sendLibrary: '0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE',
                    receiveLibraryConfig: { receiveLibrary: '0xdAf00F5eE2158dD58E0d3857851c432E34A3A851', gracePeriod: 0 },
                    // sendConfig: {
                    //     executorConfig: { maxMessageSize: 10000, executor: '0x718B92b5CB0a5552039B593faF724D182A881eDA' },
                    //     ulnConfig: {
                    //         confirmations: 2,
                    //         requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'],
                    //         optionalDVNs: [],
                    //         optionalDVNThreshold: 0,
                    //     },
                    // },
                    receiveConfig: {
                        ulnConfig: {
                            confirmations: 1,
                            requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'],
                            optionalDVNs: [],
                            optionalDVNThreshold: 0,
                        },
                    },
                },
            },
        ],
    }
    npx hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG

    Calls the configuration functions between your deployed OApp contracts on every chain based on the provided layerzero.config.ts.

    Running lz:oapp:wire will make the following function calls per pathway connection for a fully defined config file using your specified settings and your environment variables (Private Keys and RPCs):

    To use this task, run:

    npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE

    Whenever you make changes to the configuration, run lz:oapp:wire again. The task will check your current configuration, and only apply NEW changes.

    To use a Gnosis Safe multisig as the signer for these transactions, add the following to each network in your hardhat.config.ts and add the --safe flag to lz:oapp:wire --safe:

    // 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

    Returns your current OApp's configuration for each chain and pathway in 3 columns:

    • Custom Configuration: the changes that your layerzero.config.ts currently has set

    • Default Configuration: the default placeholder configuration that LayerZero provides

    • Active Configuration: the active configuration that applies to the message pathway (Defaults + Custom Values)

    If you do NOT explicitly set each configuration parameter, your OApp will fallback to the placeholder parameters in the default config.

    ┌────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐
    │                    │ Custom OApp Config                                                            │ Default OApp Config                                                           │ Active OApp Config                                                            │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ localNetworkName   │ arbsep                                                                        │ arbsep                                                                        │ arbsep                                                                        │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ remoteNetworkName  │ sepolia                                                                       │ sepolia                                                                       │ sepolia                                                                       │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ sendLibrary        │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E                                    │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E                                    │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E                                    │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ receiveLibrary     │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636                                    │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636                                    │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636                                    │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ sendUlnConfig      │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
    │                    │ │ confirmations        │ 1                                                  │ │ │ confirmations        │ 1                                                  │ │ │ confirmations        │ 1                                                  │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │
    │                    │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │
    │                    │ │                      │ └───┴────────────────────────────────────────────┘ │ │ │                      │ └───┴────────────────────────────────────────────┘ │ │ │                      │ └───┴────────────────────────────────────────────┘ │ │
    │                    │ │                      │                                                    │ │ │                      │                                                    │ │ │                      │                                                    │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ optionalDVNs         │                                                    │ │ │ optionalDVNs         │                                                    │ │ │ optionalDVNs         │                                                    │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ optionalDVNThreshold │ 0                                                  │ │ │ optionalDVNThreshold │ 0                                                  │ │ │ optionalDVNThreshold │ 0                                                  │ │
    │                    │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │
    │                    │                                                                               │                                                                               │                                                                               │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ sendExecutorConfig │ ┌────────────────┬────────────────────────────────────────────┐               │ ┌────────────────┬────────────────────────────────────────────┐               │ ┌────────────────┬────────────────────────────────────────────┐               │
    │                    │ │ executor       │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │               │ │ executor       │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │               │ │ executor       │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │               │
    │                    │ ├────────────────┼────────────────────────────────────────────┤               │ ├────────────────┼────────────────────────────────────────────┤               │ ├────────────────┼────────────────────────────────────────────┤               │
    │                    │ │ maxMessageSize │ 10000                                      │               │ │ maxMessageSize │ 10000                                      │               │ │ maxMessageSize │ 10000                                      │               │
    │                    │ └────────────────┴────────────────────────────────────────────┘               │ └────────────────┴────────────────────────────────────────────┘               │ └────────────────┴────────────────────────────────────────────┘               │
    │                    │                                                                               │                                                                               │                                                                               │
    ├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
    │ receiveUlnConfig   │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
    │                    │ │ confirmations        │ 2                                                  │ │ │ confirmations        │ 2                                                  │ │ │ confirmations        │ 2                                                  │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs         │ ┌───┬────────────────────────────────────────────┐ │ │
    │                    │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │                      │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │
    │                    │ │                      │ └───┴────────────────────────────────────────────┘ │ │ │                      │ └───┴────────────────────────────────────────────┘ │ │ │                      │ └───┴────────────────────────────────────────────┘ │ │
    │                    │ │                      │                                                    │ │ │                      │                                                    │ │ │                      │                                                    │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ optionalDVNs         │                                                    │ │ │ optionalDVNs         │                                                    │ │ │ optionalDVNs         │                                                    │ │
    │                    │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
    │                    │ │ optionalDVNThreshold │ 0                                                  │ │ │ optionalDVNThreshold │ 0                                                  │ │ │ optionalDVNThreshold │ 0                                                  │ │
    │                    │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │
    │                    │                                                                               │                                                                               │                                                                               │
    └────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘
    npx hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG

    Returns the LayerZero Executor config for each network in your hardhat.config.ts. You can use this method to see the max destination gas in wei (nativeCap) you can request in your execution options.

    ┌───────────────────┬────────────────────────────────────────────┐
    │ localNetworkName  │ mantle                                     │
    ├───────────────────┼────────────────────────────────────────────┤
    │ remoteNetworkName │ polygon                                    │
    ├───────────────────┼────────────────────────────────────────────┤
    │ executorDstConfig │ ┌────────────────┬───────────────────────┐ │
    │                   │ │ baseGas        │ 85000                 │ │
    │                   │ ├────────────────┼───────────────────────┤ │
    │                   │ │ multiplierBps  │ 12000                 │ │
    │                   │ ├────────────────┼───────────────────────┤ │
    │                   │ │ floorMarginUSD │ 5000000000000000000   │ │
    │                   │ ├────────────────┼───────────────────────┤ │
    │                   │ │ nativeCap      │ 681000000000000000000 │ │
    │                   │ └────────────────┴───────────────────────┘ │
    │                   │                                            │
    └───────────────────┴────────────────────────────────────────────┘

    LayerZero Solana Helper Tasks

    This repo also comes with several Solana helpers in the form of Hardhat tasks to easily deploy, verify, configure, connect, and send OFTs on Solana.

    These helpers use the @metaplex-foundation/umi Typescript framework to interact with the Solana blockchain. For more information, refer to the Umi documentation.

    npx hardhat lz:oft:solana:create --program YOUR_OFT_PROGRAM_ID --staging mainnet

    In addition to deploying your OFT Program, you will need to mint a new SPL Token and OFT Config Account to create your deployment. The lz:oft:solana:create task will create a new SPL Token Mint, and optionally mint an amount of tokens if given an amount.

    task('lz:oft:solana:create', 'Mints new SPL Token and creates new OFT Config account')
        .addParam('program', 'The OFT Program ID')
        .addParam('staging', 'Solana mainnet or testnet (devnet)')
        .addOptionalParam('amount', 'The initial supply to mint on solana')
        .setAction(async (taskArgs: TaskArguments) => {
          // ... task continues
        }

    A successful mint will display the following output in your terminal:

    ✅ Token Mint Complete! View the transaction here: https://explorer.solana.com/tx/5EeWgwuH52T1YMSs1vNJZdpGRd1o8K8HYn3T45vv17Z9rXwgYq2LWfAweJYCTXDxpjJTzmJXC3P3oRKLinhsBtZn
    Your token account is: 2w9iFgRJzdSmuoNH4WiLiD1jtEr7t2YtgiGmWNwNgrgo
    OFT Config: PublicKey [PublicKey(AuRJrMHgGwVVeby68cA7nu4twpwYedvJJfVJqCjt4J3y)] {
      _bn: <BN: 9326de8a307c402b6233474c34123061491c1830a6a773c1cf7d0b66497f6ab8>
    }
    ✅ You created an OFT, view the transaction here: https://explorer.solana.com/tx/5nikt8nzmfQTtCWCA93wpBDSiaGJJpzKzetGgq2RPYGyF1Hekup88CXKTzBCEhPsSpUFW7kuT8VD5G3sgRNjrY4J
    Accounts have been saved to ./deployments/solana-mainnet/OFT.json
    npx hardhat lz:oapp:init:solana --oapp-config YOUR_OAPP_CONFIG --solana-secret-key YOUR_SOLANA_SECRET_KEY

    After creating your Solana Config Account, you will need to initialize each account for every pathways you will be connecting. The lz:oapp:init:solana task will make these calls and initialize per the pathways defined in your layerzero.config.ts.

    // ./layerzero.config.ts
    const arbitrumContract: OmniPointHardhat = {
      eid: EndpointId.ARBITRUM_V2_MAINNET,
      contractName: "MyOFT",
    };
    
    const solanaContract: OmniPointHardhat = {
      eid: EndpointId.SOLANA_V2_MAINNET,
      address: "YOUR_OFT_CONFIG_ACCOUNT",
    };
    
    // ... rest of LayerZero config
    npx hardhat lz:oapp:wire --oapp-config YOUR_OAPP_CONFIG --solana-program-id YOUR_OFT_PROGRAM_ID --solana-secret-key YOUR_SOLANA_SECRET_KEY

    After initializing your accounts, run the standard lz:oapp:wire with the new flags (--solana-program-id & --solana-secret-key).

    npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --solana-program-id YOUR_OFT_PROGRAM_ID --solana-secret-key YOUR_SOLANA_SECRET_KEY
    
    
    
    
    
    
                  **********
                **************
              ******************
             ********************
             *********  *********
             *********  *********          ****                                                 ***********
             *********  *********          ****                                                 ***********
                ******  *********          ****        *************    ****  ******   *******       ****    ******    *** **   ******
              ********  *********          ****      *********** ****  **** ********** *******      ****   *********** ****** **********
             *********  ********           ****     ****    ****  ******** ***   ***** ****        ****   ****   ***** ****  ****    ****
             *********  ******             ****     ****    ****   ******* ********    ****      *****    *********    ***   ****    ****
             *********  *********          ********* ***********   ******   ********** ****     ********************** ***    **********  ****
             *********  *********          *********  **********    ****     ********  ****     ***********  ********  ***      *******   ****
             *********  *********                                 *****
             ********************                                 ****
              ******************
               ****************
                  **********
    
    
    
    
    
    
    
    info:    [OApp] Checking OApp configuration
    info:    [OApp] Checking OApp delegates configuration
    info:    [OApp] ✓ Checked OApp delegates
    info:    [OApp] Checking OApp peers configuration
    info:    [OApp] ✓ Checked OApp peers configuration
    info:    [OApp] Checking send libraries configuration
    info:    [OApp] ✓ Checked send libraries configuration
    info:    [OApp] Checking receive libraries configuration
    info:    [OApp] ✓ Checked receive libraries configuration
    info:    [OApp] Checking receive library timeout configuration
    info:    [OApp] ✓ Checked receive library timeout configuration
    info:    [OApp] Checking send configuration
    info:    [OApp] ✓ Checked send configuration
    info:    [OApp] Checking receive configuration
    info:    [OApp] ✓ Checked receive configuration
    info:    [OApp] Checking enforced options
    info:    [OApp] ✓ Checked enforced options
    info:    [OApp] Checking OApp callerBpsCap configuration
    info:    [OApp] ✓ Checked OApp callerBpsCap configuration
    info:    [OApp] ✓ Checked OApp configuration
    info:    The OApp is wired, no action is necessary

    Developing Contracts

    Installing dependencies

    We recommend using pnpm as a package manager (but you can of course use a package manager of your choice):

    pnpm install

    Compiling your contracts

    This project supports both hardhat and forge compilation. By default, the compile command will execute both:

    pnpm compile

    If you prefer one over the other, you can use the tooling-specific commands:

    pnpm compile:forge
    pnpm compile:hardhat

    Or adjust the package.json to for example remove forge build:

    - "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:

    pnpm test

    If you prefer one over the other, you can use the tooling-specific commands:

    pnpm test:forge
    pnpm test:hardhat
    pnpm test:anchor

    Or adjust the package.json to for example remove hardhat tests:

    - "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:

    npx hardhat lz:deploy

    More information about available CLI arguments can be found using the --help flag:

    npx hardhat lz:deploy --help

    By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication.



    Connecting Contracts

    Ethereum Configurations

    Fill out your layerzero.config.ts with the contracts you want to connect. You can generate the default config file for your declared hardhat networks by running:

    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).


    const ethereumContract: OmniPointHardhat = {
      eid: EndpointId.ETHEREUM_V2_MAINNET,
      contractName: "MyOFTAdapter",
    };
    
    const arbitrumContract: OmniPointHardhat = {
      eid: EndpointId.ARBITRUM_V2_MAINNET,
      contractName: "MyOFT",
    };

    Then define the pathway you want to create from and to each contract:

    connections: [
      // ETH <--> ARB PATHWAY: START
      {
        from: ethereumContract,
        to: arbitrumContract,
      },
      {
        from: arbitrumContract,
        to: ethereumContract,
      },
      // ETH <--> ARB PATHWAY: END
    ];

    Finally, define the config settings for each direction of the pathway:

    connections: [
      // ETH <--> ARB PATHWAY: START
      {
        from: ethereumContract,
        to: arbitrumContract,
        config: {
          sendLibrary: contractsConfig.ethereum.sendLib302,
          receiveLibraryConfig: {
            receiveLibrary: contractsConfig.ethereum.receiveLib302,
            gracePeriod: BigInt(0),
          },
          // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid
          receiveLibraryTimeoutConfig: {
            lib: "0x0000000000000000000000000000000000000000",
            expiry: BigInt(0),
          },
          // Optional Send Configuration
          // @dev Controls how the `from` chain sends messages to the `to` chain.
          sendConfig: {
            executorConfig: {
              maxMessageSize: 10000,
              // The configured Executor address
              executor: contractsConfig.ethereum.executor,
            },
            ulnConfig: {
              // The number of block confirmations to wait on BSC before emitting the message from the source chain.
              confirmations: BigInt(15),
              // The address of the DVNs you will pay to verify a sent message on the source chain ).
              // The destination tx will wait until ALL `requiredDVNs` verify the message.
              requiredDVNs: [
                contractsConfig.ethereum.horizenDVN, // Horizen
                contractsConfig.ethereum.polyhedraDVN, // Polyhedra
                contractsConfig.ethereum.animocaBlockdaemonDVN, // Animoca-Blockdaemon (only available on ETH <-> Arbitrum One)
                contractsConfig.ethereum.lzDVN, // LayerZero Labs
              ],
              // The address of the DVNs you will pay to verify a sent message on the source chain ).
              // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
              optionalDVNs: [],
              // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
              optionalDVNThreshold: 0,
            },
          },
          // Optional Receive Configuration
          // @dev Controls how the `from` chain receives messages from the `to` chain.
          receiveConfig: {
            ulnConfig: {
              // The number of block confirmations to expect from the `to` chain.
              confirmations: BigInt(20),
              // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain ).
              // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
              requiredDVNs: [
                contractsConfig.ethereum.lzDVN, // LayerZero Labs DVN
                contractsConfig.ethereum.animocaBlockdaemonDVN, // Blockdaemon-Animoca
                contractsConfig.ethereum.horizenDVN, // Horizen Labs
                contractsConfig.ethereum.polyhedraDVN, // Polyhedra
              ],
              // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain ).
              // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
              optionalDVNs: [],
              // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
              optionalDVNThreshold: 0,
            },
          },
          // Optional Enforced Options Configuration
          // @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain.
          enforcedOptions: [
            {
              msgType: 1,
              optionType: ExecutorOptionType.LZ_RECEIVE,
              gas: 65000,
              value: 0,
            },
            {
              msgType: 2,
              optionType: ExecutorOptionType.LZ_RECEIVE,
              gas: 65000,
              value: 0,
            },
            {
              msgType: 2,
              optionType: ExecutorOptionType.COMPOSE,
              index: 0,
              gas: 50000,
              value: 0,
            },
          ],
        },
      },
      {
        from: arbitrumContract,
        to: ethereumContract,
      },
      // ETH <--> ARB PATHWAY: END
    ];

    To set these config settings, run:

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

    Join our community on Discord | Follow us on Twitter