> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# LayerZero V2 OFT Quickstart

> Get started with OFT Quickstart. Step-by-step tutorial for building omnichain applications on LayerZero V2. LayerZero enables secure crosschain messaging.

export const InteractiveContract = ({contractName, functionName, abi: abiProp, description = null, showDeployments = true, disableChainSelection = true}) => {
  const {useState, useEffect, useRef, useMemo} = React;
  const EMBEDDED_ABIS = {
    EndpointV2: [{
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }, {
        "internalType": "uint64",
        "name": "_nonce",
        "type": "uint64"
      }, {
        "internalType": "bytes32",
        "name": "_payloadHash",
        "type": "bytes32"
      }],
      "name": "burn",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }],
      "name": "clear",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_from",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "_to",
        "type": "address"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "uint16",
        "name": "_index",
        "type": "uint16"
      }],
      "name": "composeQueue",
      "outputs": [{
        "internalType": "bytes32",
        "name": "messageHash",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "defaultReceiveLibrary",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "defaultReceiveLibraryTimeout",
      "outputs": [{
        "internalType": "address",
        "name": "lib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "expiry",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "defaultSendLibrary",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "oapp",
        "type": "address"
      }],
      "name": "delegates",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "eid",
      "outputs": [{
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "uint32",
        "name": "_configType",
        "type": "uint32"
      }],
      "name": "getConfig",
      "outputs": [{
        "internalType": "bytes",
        "name": "config",
        "type": "bytes"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "getReceiveLibrary",
      "outputs": [{
        "internalType": "address",
        "name": "lib",
        "type": "address"
      }, {
        "internalType": "bool",
        "name": "isDefault",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "getRegisteredLibraries",
      "outputs": [{
        "internalType": "address[]",
        "name": "",
        "type": "address[]"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "getSendContext",
      "outputs": [{
        "internalType": "uint32",
        "name": "dstEid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "getSendLibrary",
      "outputs": [{
        "internalType": "address",
        "name": "lib",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }],
      "name": "inboundNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }, {
        "internalType": "uint64",
        "name": "_nonce",
        "type": "uint64"
      }],
      "name": "inboundPayloadHash",
      "outputs": [{
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }],
      "name": "initializable",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "isDefaultSendLibrary",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }],
      "name": "isRegisteredLibrary",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "isSendingMessage",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "isSupportedEid",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }],
      "name": "isValidReceiveLibrary",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }],
      "name": "lazyInboundNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_from",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "_to",
        "type": "address"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "uint16",
        "name": "_index",
        "type": "uint16"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzCompose",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceive",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [],
      "name": "lzToken",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "nativeToken",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_dstEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_receiver",
        "type": "bytes32"
      }],
      "name": "nextGuid",
      "outputs": [{
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }, {
        "internalType": "uint64",
        "name": "_nonce",
        "type": "uint64"
      }, {
        "internalType": "bytes32",
        "name": "_payloadHash",
        "type": "bytes32"
      }],
      "name": "nilify",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_dstEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_receiver",
        "type": "bytes32"
      }],
      "name": "outboundNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "receiver",
          "type": "bytes32"
        }, {
          "internalType": "bytes",
          "name": "message",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "options",
          "type": "bytes"
        }, {
          "internalType": "bool",
          "name": "payInLzToken",
          "type": "bool"
        }],
        "internalType": "struct MessagingParams",
        "name": "_params",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }],
      "name": "quote",
      "outputs": [{
        "components": [{
          "internalType": "uint256",
          "name": "nativeFee",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "lzTokenFee",
          "type": "uint256"
        }],
        "internalType": "struct MessagingFee",
        "name": "",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }],
      "name": "receiveLibraryTimeout",
      "outputs": [{
        "internalType": "address",
        "name": "lib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "expiry",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }],
      "name": "registerLibrary",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "receiver",
          "type": "bytes32"
        }, {
          "internalType": "bytes",
          "name": "message",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "options",
          "type": "bytes"
        }, {
          "internalType": "bool",
          "name": "payInLzToken",
          "type": "bool"
        }],
        "internalType": "struct MessagingParams",
        "name": "_params",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_refundAddress",
        "type": "address"
      }],
      "name": "send",
      "outputs": [{
        "components": [{
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }, {
          "components": [{
            "internalType": "uint256",
            "name": "nativeFee",
            "type": "uint256"
          }, {
            "internalType": "uint256",
            "name": "lzTokenFee",
            "type": "uint256"
          }],
          "internalType": "struct MessagingFee",
          "name": "fee",
          "type": "tuple"
        }],
        "internalType": "struct MessagingReceipt",
        "name": "",
        "type": "tuple"
      }],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_to",
        "type": "address"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "uint16",
        "name": "_index",
        "type": "uint16"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }],
      "name": "sendCompose",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }, {
        "components": [{
          "internalType": "uint32",
          "name": "eid",
          "type": "uint32"
        }, {
          "internalType": "uint32",
          "name": "configType",
          "type": "uint32"
        }, {
          "internalType": "bytes",
          "name": "config",
          "type": "bytes"
        }],
        "internalType": "struct SetConfigParam[]",
        "name": "_params",
        "type": "tuple[]"
      }],
      "name": "setConfig",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_newLib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "_gracePeriod",
        "type": "uint256"
      }],
      "name": "setDefaultReceiveLibrary",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "_expiry",
        "type": "uint256"
      }],
      "name": "setDefaultReceiveLibraryTimeout",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_newLib",
        "type": "address"
      }],
      "name": "setDefaultSendLibrary",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_delegate",
        "type": "address"
      }],
      "name": "setDelegate",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_lzToken",
        "type": "address"
      }],
      "name": "setLzToken",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_newLib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "_gracePeriod",
        "type": "uint256"
      }],
      "name": "setReceiveLibrary",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_lib",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "_expiry",
        "type": "uint256"
      }],
      "name": "setReceiveLibraryTimeout",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_newLib",
        "type": "address"
      }],
      "name": "setSendLibrary",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_oapp",
        "type": "address"
      }, {
        "internalType": "uint32",
        "name": "_srcEid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_sender",
        "type": "bytes32"
      }, {
        "internalType": "uint64",
        "name": "_nonce",
        "type": "uint64"
      }],
      "name": "skip",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }],
      "name": "verifiable",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_receiver",
        "type": "address"
      }, {
        "internalType": "bytes32",
        "name": "_payloadHash",
        "type": "bytes32"
      }],
      "name": "verify",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }],
    OApp: [{
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "origin",
        "type": "tuple"
      }],
      "name": "allowInitializePath",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "endpoint",
      "outputs": [{
        "internalType": "contract ILayerZeroEndpointV2",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "",
        "type": "tuple"
      }, {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }],
      "name": "isComposeMsgSender",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceive",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "name": "nextNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "nonce",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oAppVersion",
      "outputs": [{
        "internalType": "uint64",
        "name": "senderVersion",
        "type": "uint64"
      }, {
        "internalType": "uint64",
        "name": "receiverVersion",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "owner",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }],
      "name": "peers",
      "outputs": [{
        "internalType": "bytes32",
        "name": "peer",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_delegate",
        "type": "address"
      }],
      "name": "setDelegate",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "setPeer",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }],
    OAppRead: [{
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "origin",
        "type": "tuple"
      }],
      "name": "allowInitializePath",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "endpoint",
      "outputs": [{
        "internalType": "contract ILayerZeroEndpointV2",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "",
        "type": "tuple"
      }, {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }],
      "name": "isComposeMsgSender",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceive",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "name": "nextNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "nonce",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oAppVersion",
      "outputs": [{
        "internalType": "uint64",
        "name": "senderVersion",
        "type": "uint64"
      }, {
        "internalType": "uint64",
        "name": "receiverVersion",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "owner",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }],
      "name": "peers",
      "outputs": [{
        "internalType": "bytes32",
        "name": "peer",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_delegate",
        "type": "address"
      }],
      "name": "setDelegate",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "setPeer",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_channelId",
        "type": "uint32"
      }, {
        "internalType": "bool",
        "name": "_active",
        "type": "bool"
      }],
      "name": "setReadChannel",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }],
    OFT: [{
      "inputs": [],
      "name": "SEND",
      "outputs": [{
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "SEND_AND_CALL",
      "outputs": [{
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "origin",
        "type": "tuple"
      }],
      "name": "allowInitializePath",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }],
      "name": "allowance",
      "outputs": [{
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "approvalRequired",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }],
      "name": "approve",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "account",
        "type": "address"
      }],
      "name": "balanceOf",
      "outputs": [{
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "uint16",
        "name": "_msgType",
        "type": "uint16"
      }, {
        "internalType": "bytes",
        "name": "_extraOptions",
        "type": "bytes"
      }],
      "name": "combineOptions",
      "outputs": [{
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "decimalConversionRate",
      "outputs": [{
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "decimals",
      "outputs": [{
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "endpoint",
      "outputs": [{
        "internalType": "contract ILayerZeroEndpointV2",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }, {
        "internalType": "uint16",
        "name": "msgType",
        "type": "uint16"
      }],
      "name": "enforcedOptions",
      "outputs": [{
        "internalType": "bytes",
        "name": "enforcedOption",
        "type": "bytes"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "",
        "type": "tuple"
      }, {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }],
      "name": "isComposeMsgSender",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "isPeer",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceive",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "components": [{
            "internalType": "uint32",
            "name": "srcEid",
            "type": "uint32"
          }, {
            "internalType": "bytes32",
            "name": "sender",
            "type": "bytes32"
          }, {
            "internalType": "uint64",
            "name": "nonce",
            "type": "uint64"
          }],
          "internalType": "struct Origin",
          "name": "origin",
          "type": "tuple"
        }, {
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "address",
          "name": "receiver",
          "type": "address"
        }, {
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }, {
          "internalType": "address",
          "name": "executor",
          "type": "address"
        }, {
          "internalType": "bytes",
          "name": "message",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "extraData",
          "type": "bytes"
        }],
        "internalType": "struct InboundPacket[]",
        "name": "_packets",
        "type": "tuple[]"
      }],
      "name": "lzReceiveAndRevert",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceiveSimulate",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [],
      "name": "msgInspector",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "name",
      "outputs": [{
        "internalType": "string",
        "name": "",
        "type": "string"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "name": "nextNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "nonce",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oApp",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oAppVersion",
      "outputs": [{
        "internalType": "uint64",
        "name": "senderVersion",
        "type": "uint64"
      }, {
        "internalType": "uint64",
        "name": "receiverVersion",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oftVersion",
      "outputs": [{
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }, {
        "internalType": "uint64",
        "name": "version",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "owner",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }],
      "name": "peers",
      "outputs": [{
        "internalType": "bytes32",
        "name": "peer",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "preCrime",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }],
      "name": "quoteOFT",
      "outputs": [{
        "components": [{
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "maxAmountLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTLimit",
        "name": "oftLimit",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "int256",
          "name": "feeAmountLD",
          "type": "int256"
        }, {
          "internalType": "string",
          "name": "description",
          "type": "string"
        }],
        "internalType": "struct OFTFeeDetail[]",
        "name": "oftFeeDetails",
        "type": "tuple[]"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "amountSentLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "amountReceivedLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTReceipt",
        "name": "oftReceipt",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }, {
        "internalType": "bool",
        "name": "_payInLzToken",
        "type": "bool"
      }],
      "name": "quoteSend",
      "outputs": [{
        "components": [{
          "internalType": "uint256",
          "name": "nativeFee",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "lzTokenFee",
          "type": "uint256"
        }],
        "internalType": "struct MessagingFee",
        "name": "msgFee",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "nativeFee",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "lzTokenFee",
          "type": "uint256"
        }],
        "internalType": "struct MessagingFee",
        "name": "_fee",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_refundAddress",
        "type": "address"
      }],
      "name": "send",
      "outputs": [{
        "components": [{
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }, {
          "components": [{
            "internalType": "uint256",
            "name": "nativeFee",
            "type": "uint256"
          }, {
            "internalType": "uint256",
            "name": "lzTokenFee",
            "type": "uint256"
          }],
          "internalType": "struct MessagingFee",
          "name": "fee",
          "type": "tuple"
        }],
        "internalType": "struct MessagingReceipt",
        "name": "msgReceipt",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "amountSentLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "amountReceivedLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTReceipt",
        "name": "oftReceipt",
        "type": "tuple"
      }],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_delegate",
        "type": "address"
      }],
      "name": "setDelegate",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "eid",
          "type": "uint32"
        }, {
          "internalType": "uint16",
          "name": "msgType",
          "type": "uint16"
        }, {
          "internalType": "bytes",
          "name": "options",
          "type": "bytes"
        }],
        "internalType": "struct EnforcedOptionParam[]",
        "name": "_enforcedOptions",
        "type": "tuple[]"
      }],
      "name": "setEnforcedOptions",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_msgInspector",
        "type": "address"
      }],
      "name": "setMsgInspector",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "setPeer",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_preCrime",
        "type": "address"
      }],
      "name": "setPreCrime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [],
      "name": "sharedDecimals",
      "outputs": [{
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "symbol",
      "outputs": [{
        "internalType": "string",
        "name": "",
        "type": "string"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "token",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [{
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "to",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }],
      "name": "transfer",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "from",
        "type": "address"
      }, {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }],
      "name": "transferFrom",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }],
    OFTAdapter: [{
      "inputs": [],
      "name": "SEND",
      "outputs": [{
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "SEND_AND_CALL",
      "outputs": [{
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "origin",
        "type": "tuple"
      }],
      "name": "allowInitializePath",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "approvalRequired",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "uint16",
        "name": "_msgType",
        "type": "uint16"
      }, {
        "internalType": "bytes",
        "name": "_extraOptions",
        "type": "bytes"
      }],
      "name": "combineOptions",
      "outputs": [{
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "decimalConversionRate",
      "outputs": [{
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "endpoint",
      "outputs": [{
        "internalType": "contract ILayerZeroEndpointV2",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }, {
        "internalType": "uint16",
        "name": "msgType",
        "type": "uint16"
      }],
      "name": "enforcedOptions",
      "outputs": [{
        "internalType": "bytes",
        "name": "enforcedOption",
        "type": "bytes"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "",
        "type": "tuple"
      }, {
        "internalType": "bytes",
        "name": "",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }],
      "name": "isComposeMsgSender",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "isPeer",
      "outputs": [{
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceive",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "components": [{
            "internalType": "uint32",
            "name": "srcEid",
            "type": "uint32"
          }, {
            "internalType": "bytes32",
            "name": "sender",
            "type": "bytes32"
          }, {
            "internalType": "uint64",
            "name": "nonce",
            "type": "uint64"
          }],
          "internalType": "struct Origin",
          "name": "origin",
          "type": "tuple"
        }, {
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "address",
          "name": "receiver",
          "type": "address"
        }, {
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }, {
          "internalType": "address",
          "name": "executor",
          "type": "address"
        }, {
          "internalType": "bytes",
          "name": "message",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "extraData",
          "type": "bytes"
        }],
        "internalType": "struct InboundPacket[]",
        "name": "_packets",
        "type": "tuple[]"
      }],
      "name": "lzReceiveAndRevert",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "srcEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "sender",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }],
        "internalType": "struct Origin",
        "name": "_origin",
        "type": "tuple"
      }, {
        "internalType": "bytes32",
        "name": "_guid",
        "type": "bytes32"
      }, {
        "internalType": "bytes",
        "name": "_message",
        "type": "bytes"
      }, {
        "internalType": "address",
        "name": "_executor",
        "type": "address"
      }, {
        "internalType": "bytes",
        "name": "_extraData",
        "type": "bytes"
      }],
      "name": "lzReceiveSimulate",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [],
      "name": "msgInspector",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }],
      "name": "nextNonce",
      "outputs": [{
        "internalType": "uint64",
        "name": "nonce",
        "type": "uint64"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oApp",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oAppVersion",
      "outputs": [{
        "internalType": "uint64",
        "name": "senderVersion",
        "type": "uint64"
      }, {
        "internalType": "uint64",
        "name": "receiverVersion",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "oftVersion",
      "outputs": [{
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }, {
        "internalType": "uint64",
        "name": "version",
        "type": "uint64"
      }],
      "stateMutability": "pure",
      "type": "function"
    }, {
      "inputs": [],
      "name": "owner",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "eid",
        "type": "uint32"
      }],
      "name": "peers",
      "outputs": [{
        "internalType": "bytes32",
        "name": "peer",
        "type": "bytes32"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "preCrime",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }],
      "name": "quoteOFT",
      "outputs": [{
        "components": [{
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "maxAmountLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTLimit",
        "name": "oftLimit",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "int256",
          "name": "feeAmountLD",
          "type": "int256"
        }, {
          "internalType": "string",
          "name": "description",
          "type": "string"
        }],
        "internalType": "struct OFTFeeDetail[]",
        "name": "oftFeeDetails",
        "type": "tuple[]"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "amountSentLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "amountReceivedLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTReceipt",
        "name": "oftReceipt",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }, {
        "internalType": "bool",
        "name": "_payInLzToken",
        "type": "bool"
      }],
      "name": "quoteSend",
      "outputs": [{
        "components": [{
          "internalType": "uint256",
          "name": "nativeFee",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "lzTokenFee",
          "type": "uint256"
        }],
        "internalType": "struct MessagingFee",
        "name": "msgFee",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "dstEid",
          "type": "uint32"
        }, {
          "internalType": "bytes32",
          "name": "to",
          "type": "bytes32"
        }, {
          "internalType": "uint256",
          "name": "amountLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "minAmountLD",
          "type": "uint256"
        }, {
          "internalType": "bytes",
          "name": "extraOptions",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "composeMsg",
          "type": "bytes"
        }, {
          "internalType": "bytes",
          "name": "oftCmd",
          "type": "bytes"
        }],
        "internalType": "struct SendParam",
        "name": "_sendParam",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "nativeFee",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "lzTokenFee",
          "type": "uint256"
        }],
        "internalType": "struct MessagingFee",
        "name": "_fee",
        "type": "tuple"
      }, {
        "internalType": "address",
        "name": "_refundAddress",
        "type": "address"
      }],
      "name": "send",
      "outputs": [{
        "components": [{
          "internalType": "bytes32",
          "name": "guid",
          "type": "bytes32"
        }, {
          "internalType": "uint64",
          "name": "nonce",
          "type": "uint64"
        }, {
          "components": [{
            "internalType": "uint256",
            "name": "nativeFee",
            "type": "uint256"
          }, {
            "internalType": "uint256",
            "name": "lzTokenFee",
            "type": "uint256"
          }],
          "internalType": "struct MessagingFee",
          "name": "fee",
          "type": "tuple"
        }],
        "internalType": "struct MessagingReceipt",
        "name": "msgReceipt",
        "type": "tuple"
      }, {
        "components": [{
          "internalType": "uint256",
          "name": "amountSentLD",
          "type": "uint256"
        }, {
          "internalType": "uint256",
          "name": "amountReceivedLD",
          "type": "uint256"
        }],
        "internalType": "struct OFTReceipt",
        "name": "oftReceipt",
        "type": "tuple"
      }],
      "stateMutability": "payable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_delegate",
        "type": "address"
      }],
      "name": "setDelegate",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "components": [{
          "internalType": "uint32",
          "name": "eid",
          "type": "uint32"
        }, {
          "internalType": "uint16",
          "name": "msgType",
          "type": "uint16"
        }, {
          "internalType": "bytes",
          "name": "options",
          "type": "bytes"
        }],
        "internalType": "struct EnforcedOptionParam[]",
        "name": "_enforcedOptions",
        "type": "tuple[]"
      }],
      "name": "setEnforcedOptions",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_msgInspector",
        "type": "address"
      }],
      "name": "setMsgInspector",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_eid",
        "type": "uint32"
      }, {
        "internalType": "bytes32",
        "name": "_peer",
        "type": "bytes32"
      }],
      "name": "setPeer",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "_preCrime",
        "type": "address"
      }],
      "name": "setPreCrime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }, {
      "inputs": [],
      "name": "sharedDecimals",
      "outputs": [{
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [],
      "name": "token",
      "outputs": [{
        "internalType": "address",
        "name": "",
        "type": "address"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }],
    Executor: [{
      "inputs": [{
        "internalType": "uint32",
        "name": "_dstEid",
        "type": "uint32"
      }, {
        "internalType": "address",
        "name": "_sender",
        "type": "address"
      }, {
        "internalType": "uint256",
        "name": "_calldataSize",
        "type": "uint256"
      }, {
        "internalType": "bytes",
        "name": "_options",
        "type": "bytes"
      }],
      "name": "getFee",
      "outputs": [{
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      }],
      "stateMutability": "view",
      "type": "function"
    }, {
      "inputs": [{
        "internalType": "uint32",
        "name": "_dstEid",
        "type": "uint32"
      }],
      "name": "dstConfig",
      "outputs": [{
        "components": [{
          "internalType": "uint64",
          "name": "baseGas",
          "type": "uint64"
        }, {
          "internalType": "uint16",
          "name": "multiplierBps",
          "type": "uint16"
        }, {
          "internalType": "uint128",
          "name": "floorMarginUSD",
          "type": "uint128"
        }, {
          "internalType": "uint128",
          "name": "nativeCap",
          "type": "uint128"
        }],
        "internalType": "struct IExecutor.DstConfig",
        "name": "",
        "type": "tuple"
      }],
      "stateMutability": "view",
      "type": "function"
    }]
  };
  const EMBEDDED_CHAINS = [{
    chainId: 1,
    name: 'Ethereum',
    chainKey: 'ethereum',
    rpc: 'https://eth.llamarpc.com',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 56,
    name: 'BNB Chain',
    chainKey: 'bsc',
    rpc: 'https://bsc-dataseed.binance.org',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 137,
    name: 'Polygon',
    chainKey: 'polygon',
    rpc: 'https://polygon-rpc.com',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 42161,
    name: 'Arbitrum One',
    chainKey: 'arbitrum',
    rpc: 'https://arb1.arbitrum.io/rpc',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 10,
    name: 'Optimism',
    chainKey: 'optimism',
    rpc: 'https://mainnet.optimism.io',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 8453,
    name: 'Base',
    chainKey: 'base',
    rpc: 'https://mainnet.base.org',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 43114,
    name: 'Avalanche',
    chainKey: 'avalanche',
    rpc: 'https://api.avax.network/ext/bc/C/rpc',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 250,
    name: 'Fantom',
    chainKey: 'fantom',
    rpc: 'https://rpc.ftm.tools',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 324,
    name: 'zkSync Era',
    chainKey: 'zksync',
    rpc: 'https://mainnet.era.zksync.io',
    endpointV2: '0xd07C30aF3Ff30D96BDc9c6044958230Eb797DDBF'
  }, {
    chainId: 59144,
    name: 'Linea',
    chainKey: 'linea',
    rpc: 'https://rpc.linea.build',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 5000,
    name: 'Mantle',
    chainKey: 'mantle',
    rpc: 'https://rpc.mantle.xyz',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 534352,
    name: 'Scroll',
    chainKey: 'scroll',
    rpc: 'https://rpc.scroll.io',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 1101,
    name: 'Polygon zkEVM',
    chainKey: 'polygon-zkevm',
    rpc: 'https://zkevm-rpc.com',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 81457,
    name: 'Blast',
    chainKey: 'blast',
    rpc: 'https://rpc.blast.io',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }, {
    chainId: 34443,
    name: 'Mode',
    chainKey: 'mode',
    rpc: 'https://mainnet.mode.network',
    endpointV2: '0x1a44076050125825900e736c501f859c50fE728c'
  }];
  const contractDeployments = {
    OApp: [{
      chainId: 1,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 10,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 56,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 137,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 8453,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 42161,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 43114,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }],
    OAppRead: [],
    OFT: [{
      chainId: 1,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 10,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 56,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 137,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 8453,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 42161,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }, {
      chainId: 43114,
      address: '0x6985884c4392d348587b19cb9eaaf157f13271cd',
      name: 'ZRO Token'
    }],
    OFTAdapter: [],
    EndpointV2: [{
      chainId: 1,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }, {
      chainId: 56,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }, {
      chainId: 42161,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }, {
      chainId: 10,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }, {
      chainId: 137,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }, {
      chainId: 8453,
      address: '0x1a44076050125825900e736c501f859c50fE728c',
      name: 'Endpoint V2'
    }],
    Executor: [{
      chainId: 1,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }, {
      chainId: 56,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }, {
      chainId: 42161,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }, {
      chainId: 10,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }, {
      chainId: 137,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }, {
      chainId: 8453,
      address: '0x173272739Bd7Aa6e4e214714048a9fE699453059',
      name: 'LZ Executor'
    }]
  };
  const getPlaceholderForType = type => {
    if (type.includes('address')) return '0x...';
    if (type.includes('uint')) return '0';
    if (type.includes('int')) return '0';
    if (type.includes('bool')) return 'true or false';
    if (type.includes('bytes32')) return '0x... (32 bytes)';
    if (type.includes('bytes')) return '0x...';
    if (type.includes('string')) return 'text';
    return 'value';
  };
  const [isDark, setIsDark] = useState(true);
  const [selectedChain, setSelectedChain] = useState(disableChainSelection ? null : 1);
  const [contract, setContract] = useState(null);
  const [inputs, setInputs] = useState({});
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [walletConnected, setWalletConnected] = useState(false);
  const [isExpanded, setIsExpanded] = useState(false);
  const [isChainDropdownOpen, setIsChainDropdownOpen] = useState(false);
  const [chainSearchTerm, setChainSearchTerm] = useState('');
  const [customAddress, setCustomAddress] = useState('');
  const [isTruncated, setIsTruncated] = useState(false);
  const [ethersLib, setEthersLib] = useState(null);
  const [isLoadingData, setIsLoadingData] = useState(false);
  const [iconErrors, setIconErrors] = useState({});
  const [loadedAbi, setLoadedAbi] = useState(null);
  const [signer, setSigner] = useState(null);
  const abi = abiProp || loadedAbi;
  const chainDropdownRef = useRef(null);
  const codeRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const checkTheme = () => setIsDark(document.documentElement.classList.contains('dark'));
    checkTheme();
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    if (window.ethers) {
      setEthersLib(window.ethers);
      return;
    }
    const script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/ethers/5.7.2/ethers.umd.min.js';
    script.async = true;
    script.onload = () => {
      if (window.ethers) {
        setEthersLib(window.ethers);
      }
    };
    document.head.appendChild(script);
  }, []);
  useEffect(() => {
    if (abiProp) return;
    const embeddedAbi = EMBEDDED_ABIS[contractName];
    if (embeddedAbi) {
      setLoadedAbi(embeddedAbi);
    } else {
      setError('No ABI available for ' + contractName);
    }
  }, [contractName, abiProp]);
  const availableChains = useMemo(() => {
    if (disableChainSelection) return [];
    return EMBEDDED_CHAINS.slice().sort((a, b) => a.name.localeCompare(b.name));
  }, [disableChainSelection]);
  const currentChain = availableChains.find(chain => chain.chainId === selectedChain);
  const chainDisplayName = currentChain?.name || 'Select Chain';
  const chainKey = currentChain?.chainKey || 'default';
  const functionAbi = abi?.find(item => item.type === 'function' && item.name === functionName);
  const getDeploymentAddress = () => {
    if (contractName === 'EndpointV2') {
      const chain = EMBEDDED_CHAINS.find(c => c.chainId === selectedChain);
      return chain?.endpointV2;
    }
    const deployments = contractDeployments[contractName] || [];
    const dep = deployments.find(d => d.chainId === selectedChain);
    return dep?.address;
  };
  const defaultDeploymentAddress = getDeploymentAddress();
  const deploymentAddress = defaultDeploymentAddress || customAddress;
  useEffect(() => {
    if (availableChains.length > 0 && !currentChain) {
      setSelectedChain(availableChains[0].chainId);
    }
  }, [availableChains, currentChain]);
  useEffect(() => {
    if (defaultDeploymentAddress) setCustomAddress('');
  }, [selectedChain, defaultDeploymentAddress]);
  useEffect(() => {
    const checkTruncation = () => {
      if (codeRef.current) {
        setIsTruncated(codeRef.current.scrollWidth > codeRef.current.clientWidth);
      }
    };
    checkTruncation();
    const timeout = setTimeout(checkTruncation, 100);
    return () => clearTimeout(timeout);
  }, [functionAbi, isExpanded]);
  const isReadOnly = functionAbi && (functionAbi.stateMutability === 'view' || functionAbi.stateMutability === 'pure');
  const NetworkIcon = ({chainKey, size = 20}) => {
    const hasError = iconErrors['network-' + chainKey];
    const iconUrl = hasError ? 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg' : 'https://icons-ckg.pages.dev/lz-scan/networks/' + chainKey + '.svg';
    return <span style={{
      display: 'inline-block',
      width: size,
      height: size,
      flexShrink: 0,
      backgroundImage: 'url(' + iconUrl + ')',
      backgroundSize: 'contain',
      backgroundRepeat: 'no-repeat',
      backgroundPosition: 'center',
      borderRadius: 4
    }} className={isDark ? '' : 'invert brightness-110 contrast-100'} />;
  };
  const connectWallet = async () => {
    if (!ethersLib) {
      setError('Ethers library not loaded yet');
      return;
    }
    try {
      if (!window.ethereum) {
        setError('Please install MetaMask or another Web3 wallet');
        return;
      }
      const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
      });
      if (accounts.length > 0) {
        try {
          await window.ethereum.request({
            method: 'wallet_switchEthereumChain',
            params: [{
              chainId: '0x' + selectedChain.toString(16)
            }]
          });
        } catch (switchError) {
          if (switchError.code === 4902 && currentChain) {
            const chainRpcData = chainlistRPCs.find(c => c.chainId === selectedChain);
            await window.ethereum.request({
              method: 'wallet_addEthereumChain',
              params: [{
                chainId: '0x' + selectedChain.toString(16),
                chainName: currentChain.name,
                rpcUrls: [currentChain.rpc],
                nativeCurrency: chainRpcData?.nativeCurrency,
                blockExplorerUrls: chainRpcData?.explorers?.map(e => e.url) || []
              }]
            });
          }
        }
        const web3Provider = new ethersLib.providers.Web3Provider(window.ethereum);
        const web3Signer = web3Provider.getSigner();
        setSigner(web3Signer);
        setWalletConnected(true);
        setError(null);
      }
    } catch (err) {
      setError(err.message);
    }
  };
  useEffect(() => {
    if (!ethersLib || !deploymentAddress || !currentChain || !currentChain.rpc || !abi) return;
    try {
      if (signer) {
        const contractInstance = new ethersLib.Contract(deploymentAddress, abi, signer);
        setContract(contractInstance);
      } else if (isReadOnly) {
        const readProvider = new ethersLib.providers.JsonRpcProvider(currentChain.rpc);
        const contractInstance = new ethersLib.Contract(deploymentAddress, abi, readProvider);
        setContract(contractInstance);
      }
    } catch (err) {
      console.error('Error initializing contract:', err);
    }
  }, [ethersLib, signer, deploymentAddress, abi, currentChain, isReadOnly]);
  useEffect(() => {
    const handleClickOutside = event => {
      if (chainDropdownRef.current && !chainDropdownRef.current.contains(event.target)) {
        setIsChainDropdownOpen(false);
        setChainSearchTerm('');
      }
    };
    if (isChainDropdownOpen) {
      document.addEventListener('mousedown', handleClickOutside);
    }
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [isChainDropdownOpen]);
  const handleInputChange = (paramPath, value) => {
    setInputs(prev => ({
      ...prev,
      [paramPath]: value
    }));
  };
  const parseValue = (value, type) => {
    const val = (value || '').trim();
    if (type.endsWith('[]')) {
      if (!val || val === '[]') return [];
      try {
        return JSON.parse(val);
      } catch {
        return val.split(',').map(v => parseValue(v.trim(), type.slice(0, -2)));
      }
    }
    if (type === 'bool') {
      return val.toLowerCase() === 'true' || val === '1';
    }
    if (type.startsWith('uint') || type.startsWith('int')) {
      return val || '0';
    }
    if (type === 'address') {
      return val || '0x0000000000000000000000000000000000000000';
    }
    if (type === 'bytes32') {
      return val || '0x0000000000000000000000000000000000000000000000000000000000000000';
    }
    if (type.match(/^bytes\d+$/)) {
      const size = parseInt(type.slice(5));
      return val || '0x' + ('00').repeat(size);
    }
    if (type === 'bytes') {
      return val || '0x';
    }
    return val;
  };
  const prepareArguments = () => {
    const args = [];
    functionAbi.inputs.forEach((input, index) => {
      if (input.type.startsWith('tuple')) {
        const tupleValues = [];
        if (input.components) {
          input.components.forEach((component, compIndex) => {
            const path = (input.name || index) + '.' + (component.name || compIndex);
            tupleValues.push(parseValue(inputs[path], component.type));
          });
        }
        args.push(tupleValues);
      } else {
        const path = input.name || index.toString();
        args.push(parseValue(inputs[path], input.type));
      }
    });
    return args;
  };
  const executeFunction = async () => {
    if (!functionAbi) return;
    if (!isReadOnly && !walletConnected) {
      setError('Please connect your wallet to send transactions');
      return;
    }
    if (!contract) {
      setError('Contract not initialized. Please check the network connection.');
      return;
    }
    setLoading(true);
    setError(null);
    setResult(null);
    try {
      const args = prepareArguments();
      let tx;
      if (isReadOnly) {
        tx = await contract[functionName](...args);
        setResult({
          type: 'read',
          data: tx,
          formatted: formatResult(tx)
        });
      } else {
        tx = await contract[functionName](...args);
        setResult({
          type: 'write',
          hash: tx.hash,
          status: 'pending'
        });
        const receipt = await tx.wait();
        setResult({
          type: 'write',
          hash: tx.hash,
          status: 'confirmed',
          blockNumber: receipt.blockNumber
        });
      }
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
  const formatResult = data => {
    if (data === null || data === undefined) return 'null';
    if (typeof data === 'object' && data._isBigNumber) return data.toString();
    if (Array.isArray(data)) return data.map(formatResult);
    if (typeof data === 'object') {
      const result = {};
      Object.keys(data).forEach(key => {
        if (isNaN(parseInt(key))) result[key] = formatResult(data[key]);
      });
      return result;
    }
    return data;
  };
  const renderOutputType = (output, depth = 0) => {
    if (output.type.startsWith('tuple') && output.components) {
      return <div key={output.name || 'tuple-' + depth} style={{
        marginLeft: depth * 16
      }}>
          <span className={isDark ? 'text-purple-400' : 'text-purple-600'}>
            {output.name && output.name + ': '}{'struct {'}
          </span>
          {output.components.map((comp, idx) => renderOutputType(comp, depth + 1))}
          <div style={{
        marginLeft: depth * 16
      }}>
            <span className={isDark ? 'text-purple-400' : 'text-purple-600'}>{'}'}</span>
            {output.type.endsWith('[]') && <span className={isDark ? 'text-zinc-400' : 'text-zinc-500'}>[]</span>}
          </div>
        </div>;
    }
    return <div key={output.name || 'field-' + depth} style={{
      marginLeft: depth * 16
    }}>
        {output.name && <span className={isDark ? 'text-zinc-300' : 'text-zinc-600'}>{output.name}: </span>}
        <span style={{
      color: isDark ? '#f1df38' : '#e36209'
    }}>{output.type}</span>
      </div>;
  };
  if (typeof window === 'undefined') {
    return <div className="p-4 text-center text-zinc-500">Loading...</div>;
  }
  if (error && !abi) {
    return <div className="p-4 rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 mb-4">
        <div className="font-medium mb-1">Error loading {contractName}</div>
        <div className="text-sm">{error}</div>
      </div>;
  }
  if (isLoadingData) {
    return <div className="p-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 mb-4">
        <div className="text-center text-zinc-500">Loading chain data...</div>
      </div>;
  }
  if (!abi) {
    return <div className="p-4 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 mb-4">
        <div className="text-center text-zinc-500">Loading {contractName} ABI...</div>
      </div>;
  }
  if (!functionAbi) {
    return <div className="p-4 rounded-lg border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 mb-4">
        Function {functionName} not found in {contractName} ABI
      </div>;
  }
  const renderInputFields = () => {
    const fields = [];
    functionAbi.inputs.forEach((input, index) => {
      if (input.type.startsWith('tuple') && input.components) {
        fields.push(<div key={'tuple-' + index} className="mb-4">
            <div className={'text-xs font-semibold uppercase tracking-wide mb-2 ' + (isDark ? 'text-purple-400' : 'text-purple-600')}>
              {input.name || 'Param ' + index} (tuple)
            </div>
            <div className={'pl-4 border-l-2 ' + (isDark ? 'border-purple-500/30' : 'border-purple-300')}>
              {input.components.map((component, compIndex) => {
          const path = (input.name || index) + '.' + (component.name || compIndex);
          return <div key={path} className="mb-3">
                    <label className={'flex items-center gap-2 text-sm mb-1 ' + (isDark ? 'text-zinc-300' : 'text-zinc-700')}>
                      {component.name || 'field' + compIndex}
                      <span className={'text-xs ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>({component.type})</span>
                    </label>
                    <input type="text" placeholder={getPlaceholderForType(component.type)} value={inputs[path] || ''} onChange={e => handleInputChange(path, e.target.value)} className={'w-full px-3 py-2 rounded-lg border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-blue-500 ' + (isDark ? 'bg-zinc-800 border-zinc-700 text-white placeholder-zinc-500' : 'bg-white border-zinc-300 text-zinc-900 placeholder-zinc-400')} />
                  </div>;
        })}
            </div>
          </div>);
      } else {
        const path = input.name || index.toString();
        fields.push(<div key={path} className="mb-3">
            <label className={'flex items-center gap-2 text-sm mb-1 ' + (isDark ? 'text-zinc-300' : 'text-zinc-700')}>
              {input.name || 'param' + index}
              <span className={'text-xs ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>({input.type})</span>
            </label>
            <input type="text" placeholder={getPlaceholderForType(input.type)} value={inputs[path] || ''} onChange={e => handleInputChange(path, e.target.value)} className={'w-full px-3 py-2 rounded-lg border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-blue-500 ' + (isDark ? 'bg-zinc-800 border-zinc-700 text-white placeholder-zinc-500' : 'bg-white border-zinc-300 text-zinc-900 placeholder-zinc-400')} />
          </div>);
      }
    });
    return fields;
  };
  return <div className="rounded-xl border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 overflow-hidden mb-4">
      {}
      <div className={'flex items-center justify-between p-3 cursor-pointer transition-colors ' + (isDark ? 'bg-zinc-800 hover:bg-zinc-700' : 'bg-zinc-100 hover:bg-zinc-200')} onClick={() => setIsExpanded(!isExpanded)}>
        <div className="flex items-center gap-3 flex-1 min-w-0">
          <span className="px-2 py-1 rounded text-xs font-semibold text-white" style={{
    backgroundColor: isReadOnly ? '#6366f1' : '#22c55e'
  }}>
            {isReadOnly ? 'CALL' : 'SEND'}
          </span>
          <code ref={codeRef} className={'text-sm truncate ' + (isDark ? 'text-zinc-200' : 'text-zinc-800')} title={functionName + '(' + functionAbi.inputs.map(i => i.name + ': ' + i.type).join(', ') + ')'}>
            <span style={{
    color: '#6cadf5'
  }}>{functionName}</span>
            <span className={isDark ? 'text-zinc-500' : 'text-zinc-400'}>(</span>
            {functionAbi.inputs.map((input, idx) => <span key={idx}>
                <span className={isDark ? 'text-zinc-300' : 'text-zinc-600'}>{input.name}</span>
                <span className={isDark ? 'text-zinc-500' : 'text-zinc-400'}>: </span>
                <span style={{
    color: isDark ? '#f1df38' : '#e36209'
  }}>{input.type}</span>
                {idx < functionAbi.inputs.length - 1 && <span className={isDark ? 'text-zinc-500' : 'text-zinc-400'}>, </span>}
              </span>)}
            <span className={isDark ? 'text-zinc-500' : 'text-zinc-400'}>)</span>
          </code>
        </div>
        <div className={'transition-transform ' + (isExpanded ? 'rotate-180' : '')} style={{
    color: isDark ? '#71717a' : '#a1a1aa'
  }}>
          <svg viewBox="0 0 24 24" fill="none" width="20" height="20">
            <path d="M6 9L12 15L18 9" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </div>
      </div>

      {}
      {isExpanded && <div className="p-4">
          {description && <div className={'text-sm mb-4 pb-4 border-b ' + (isDark ? 'text-zinc-400 border-zinc-800' : 'text-zinc-600 border-zinc-200')}>
              {description}
            </div>}

          {}
          <div className="mb-4">
            <label className={'text-xs font-semibold uppercase tracking-wide mb-2 block ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>CHAIN</label>
            <div ref={chainDropdownRef} className="relative">
              <div onClick={() => setIsChainDropdownOpen(!isChainDropdownOpen)} className={'flex items-center justify-between px-3 py-2 rounded-lg border cursor-pointer ' + (isDark ? 'bg-zinc-800 border-zinc-700 hover:border-zinc-600' : 'bg-white border-zinc-300 hover:border-zinc-400')}>
                <div className="flex items-center gap-2">
                  {currentChain && <NetworkIcon chainKey={chainKey} size={20} />}
                  <span className={isDark ? 'text-white' : 'text-zinc-900'}>{chainDisplayName}</span>
                </div>
                <svg width="10" height="6" viewBox="0 0 10 6" fill="none" className={isDark ? 'text-zinc-400' : 'text-zinc-500'}>
                  <path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              </div>

              {isChainDropdownOpen && <div className="absolute z-50 w-full mt-1 rounded-lg border shadow-lg" style={{
    backgroundColor: isDark ? '#27272a' : '#ffffff',
    borderColor: isDark ? '#3f3f46' : '#d4d4d8'
  }}>
                  <input type="text" placeholder="Search chains..." value={chainSearchTerm} onChange={e => setChainSearchTerm(e.target.value)} onClick={e => e.stopPropagation()} autoFocus className={'w-full px-3 py-2 text-sm border-b focus:outline-none ' + (isDark ? 'bg-transparent border-zinc-700 text-white placeholder-zinc-500' : 'bg-transparent border-zinc-200 text-zinc-900 placeholder-zinc-400')} />
                  <div style={{
    maxHeight: '240px',
    overflowY: 'auto',
    backgroundColor: isDark ? '#27272a' : '#ffffff'
  }}>
                    {availableChains.length === 0 ? <div className={'px-3 py-4 text-sm text-center ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>
                        No chains available
                      </div> : availableChains.filter(chain => chain.name.toLowerCase().includes(chainSearchTerm.toLowerCase())).map(chain => <div key={chain.chainId} onClick={() => {
    setSelectedChain(chain.chainId);
    setIsChainDropdownOpen(false);
    setChainSearchTerm('');
  }} className={'flex items-center gap-2 px-3 py-2 cursor-pointer ' + (selectedChain === chain.chainId ? isDark ? 'bg-blue-600/20 text-blue-400' : 'bg-blue-50 text-blue-600' : isDark ? 'hover:bg-zinc-700 text-white' : 'hover:bg-zinc-100 text-zinc-900')}>
                            <NetworkIcon chainKey={chain.chainKey} size={16} />
                            <span className="text-sm">{chain.name}</span>
                          </div>)}
                  </div>
                </div>}
            </div>
          </div>

          {}
          {showDeployments && <div className="mb-4">
              <label className={'text-xs font-semibold uppercase tracking-wide mb-2 block ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>
                {defaultDeploymentAddress ? 'CONTRACT' : 'CONTRACT ADDRESS'}
              </label>
              {defaultDeploymentAddress ? <div className={'px-3 py-2 rounded-lg text-sm font-mono ' + (isDark ? 'bg-zinc-800 text-zinc-300' : 'bg-zinc-100 text-zinc-700')}>
                  {defaultDeploymentAddress}
                </div> : <>
                  <input type="text" placeholder="0x..." value={customAddress} onChange={e => setCustomAddress(e.target.value)} className={'w-full px-3 py-2 rounded-lg border text-sm font-mono focus:outline-none focus:ring-1 focus:ring-blue-500 ' + (isDark ? 'bg-zinc-800 border-zinc-700 text-white placeholder-zinc-500' : 'bg-white border-zinc-300 text-zinc-900 placeholder-zinc-400')} />
                  <div className={'text-xs mt-1 ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>
                    Enter your {contractName} contract address
                  </div>
                </>}
            </div>}

          {}
          {functionAbi.inputs.length > 0 && <div className="mb-4">
              <div className={'text-xs font-semibold uppercase tracking-wide mb-3 ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>PARAMETERS</div>
              {renderInputFields()}
            </div>}

          {}
          {functionAbi.outputs && functionAbi.outputs.length > 0 && <div className="mb-4">
              <div className={'text-xs font-semibold uppercase tracking-wide mb-2 ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>RETURNS</div>
              <div className={'p-3 rounded-lg text-sm font-mono ' + (isDark ? 'bg-zinc-800' : 'bg-zinc-100')}>
                {functionAbi.outputs.map((output, idx) => renderOutputType(output, 0))}
              </div>
            </div>}

          {}
          <div className="mb-4">
            {!walletConnected && !isReadOnly ? <button onClick={connectWallet} className="w-full px-4 py-2 rounded-lg font-medium text-sm bg-blue-600 hover:bg-blue-700 text-white transition-colors">
                Connect Wallet
              </button> : <button onClick={executeFunction} disabled={loading || !deploymentAddress} className="w-full px-4 py-2 rounded-lg font-medium text-sm transition-colors text-white disabled:opacity-50 disabled:cursor-not-allowed" style={{
    backgroundColor: loading || !deploymentAddress ? '#52525b' : '#6cadf5'
  }}>
                {loading ? 'Processing...' : isReadOnly ? 'Send Request' : 'Send Transaction'}
              </button>}
          </div>

          {}
          {error && <div className={'p-3 rounded-lg text-sm mb-4 ' + (isDark ? 'bg-red-900/20 text-red-400' : 'bg-red-50 text-red-600')}>
              {error}
            </div>}

          {}
          {result && <div className="mb-4">
              <div className={'text-xs font-semibold uppercase tracking-wide mb-2 ' + (isDark ? 'text-zinc-500' : 'text-zinc-400')}>RESPONSE</div>
              <div className={'p-3 rounded-lg text-sm font-mono overflow-auto ' + (isDark ? 'bg-zinc-800' : 'bg-zinc-100')}>
                {result.type === 'read' ? <pre style={{
    margin: 0,
    whiteSpace: 'pre-wrap',
    color: isDark ? '#9EFC7E' : '#166534'
  }}>
                    {JSON.stringify(result.formatted, null, 2)}
                  </pre> : <div className={isDark ? 'text-zinc-300' : 'text-zinc-700'}>
                    <div>Tx: {result.hash}</div>
                    <div>Status: <span className={result.status === 'confirmed' ? 'text-green-500' : 'text-yellow-500'}>{result.status}</span></div>
                    {result.blockNumber && <div>Block: {result.blockNumber}</div>}
                  </div>}
              </div>
            </div>}

          {}
          {!deploymentAddress && <div className={'p-3 rounded-lg text-sm ' + (isDark ? 'bg-yellow-900/20 text-yellow-400' : 'bg-yellow-50 text-yellow-700')}>
              No deployment found for {contractName} on {chainDisplayName}
            </div>}
        </div>}
    </div>;
};

The **Omnichain Fungible Token (OFT) Standard** enables fungible tokens to exist across multiple blockchains while maintaining a unified supply. The OFT standard works by **debiting** an amount of tokens from a sender on the source chain and **crediting** the same amount of tokens to a receiver on the destination chain.

### OFT

The `_debit` function in `OFT.sol` burns an amount of an ERC20 token, while `_credit` mints ERC20 tokens on the destination chain.

<img src="https://mintcdn.com/layerzero/l5FYciYAmKUwmOFF/images/learn/oft_mechanism_light.jpg?fit=max&auto=format&n=l5FYciYAmKUwmOFF&q=85&s=f1789e8bc96aac6d6a4949e10798c85d" alt="Diagram showing OFT burn-and-mint mechanism: tokens are burned (subtracted) on Network A and minted (added) on Network B, connected by an arrow representing the crosschain transfer" className="block dark:hidden" width="3840" height="1034" data-path="images/learn/oft_mechanism_light.jpg" />

<img src="https://mintcdn.com/layerzero/l5FYciYAmKUwmOFF/images/learn/oft_mechanism.jpg?fit=max&auto=format&n=l5FYciYAmKUwmOFF&q=85&s=3d32bc3d05cf1241b84ca9b4426c42d2" alt="Diagram showing OFT burn-and-mint mechanism: tokens are burned (subtracted) on Network A and minted (added) on Network B, connected by an arrow representing the crosschain transfer" className="hidden dark:block" width="3840" height="1034" data-path="images/learn/oft_mechanism.jpg" />

`OFT.sol` extends the base `OApp.sol` and inherits `ERC20`, providing both crosschain messaging and standard token functionality:

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/oft-inheritance-light.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=964c35ef9ed09176100cb3746e10c65e" alt="Class inheritance diagram showing OFT.sol extending OApp.sol for crosschain messaging and inheriting ERC20 for standard token functionality" className="block dark:hidden" width="2051" height="1040" data-path="images/oft-inheritance-light.svg" />

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/oft-inheritance-dark.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=032d47a15c9b7a717880e0a6d214d177" alt="Class inheritance diagram showing OFT.sol extending OApp.sol for crosschain messaging and inheriting ERC20 for standard token functionality" className="hidden dark:block" width="2051" height="1040" data-path="images/oft-inheritance-dark.svg" />

### OFT Adapter

`OFTAdapter.sol` can be used for already deployed ERC20 tokens who lack mint capabilities, so that the `_debit` function calls `safeERC20.transferFrom` from a sender, while `_credit` calls `safeERC20.transfer` to a receiver.

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/learn/oft-adapter-light.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=bdff2990c335cf923c28c1e22c97d617" alt="Diagram showing OFT Adapter lock-and-mint mechanism: ERC20 tokens are locked in an escrow contract on Network A, and equivalent OFT tokens are minted on Network B" className="block dark:hidden" width="1920" height="517" data-path="images/learn/oft-adapter-light.svg" />

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/learn/oft-adapter-dark.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=b03d1a2fe2bab25f7896e27537e2d77d" alt="Diagram showing OFT Adapter lock-and-mint mechanism: ERC20 tokens are locked in an escrow contract on Network A, and equivalent OFT tokens are minted on Network B" className="hidden dark:block" width="1920" height="517" data-path="images/learn/oft-adapter-dark.svg" />

`OFTAdapter.sol` provides token bridging without modifying the original ERC20 token contract:

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/oft-adapter-inheritance-light.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=431b87b5c5d1367165518748eb65502b" alt="Class inheritance diagram showing OFTAdapter.sol extending OApp.sol for crosschain messaging while wrapping an existing ERC20 token contract" className="block dark:hidden" width="2051" height="1040" data-path="images/oft-adapter-inheritance-light.svg" />

<img src="https://mintcdn.com/layerzero/VohHXGobQ14zkw24/images/oft-adapter-inheritance-dark.svg?fit=max&auto=format&n=VohHXGobQ14zkw24&q=85&s=52f57be4e30f6b88652280c936bb599f" alt="Class inheritance diagram showing OFTAdapter.sol extending OApp.sol for crosschain messaging while wrapping an existing ERC20 token contract" className="hidden dark:block" width="2051" height="1040" data-path="images/oft-adapter-inheritance-dark.svg" />

<Tip>
  If your use case involves crosschain messaging beyond token transfers, consider using the [**OApp Standard**](../oapp/overview) for maximum flexibility.
</Tip>

<Info>
  For detailed technical information about transfer flows, decimal handling, and architecture patterns, see the [**OFT Technical Reference**](../../../concepts/technical-reference/oft-reference).
</Info>

<Tip>
  ### Explore Deployed OFTs

  Browse production OFT deployments from various asset issuers, including Stargate-managed assets, on the [**OFT Ecosystem & Stargate Assets**](../../../deployments/oft-ecosystem-stargate-assets) page. See which tokens are available for crosschain transfers across LayerZero-supported chains.
</Tip>

## Installation

Below, you can find instructions for installing the OFT contract:

### OFT in a new project

To start using LayerZero OFT contracts in a new project, use the LayerZero CLI tool, [**create-lz-oapp**](../../../get-started/create-lz-oapp/start). The CLI tool allows developers to create any omnichain application in \<4 minutes! Get started by running the following from your command line:

```bash wrap theme={null}
npx create-lz-oapp@latest --example oft
```

This will create an example repository containing both the Hardhat and Foundry frameworks, LayerZero development utilities, as well as the **OFT contract package** pre-installed.

### OFT in an existing project

To use LayerZero contracts in an existing project, you can install the **OFT package** directly:

<Tabs>
  <Tab title="npm">
    ```bash wrap theme={null}
    npm install @layerzerolabs/oft-evm
    ```
  </Tab>

  <Tab title="yarn">
    ```bash wrap theme={null}
    yarn add @layerzerolabs/oft-evm
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash wrap theme={null}
    pnpm add @layerzerolabs/oft-evm
    ```
  </Tab>

  <Tab title="forge">
    ```bash wrap theme={null}
    forge init
    ```

    ```bash wrap theme={null}
    forge install layerzero-labs/devtools
    forge install layerzero-labs/LayerZero-v2
    forge install OpenZeppelin/openzeppelin-contracts
    git submodule add https://github.com/GNSPS/solidity-bytes-utils.git lib/solidity-bytes-utils
    ```

    Then add to your `foundry.toml` under `[profile.default]`:

    ```toml wrap theme={null}
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]

    remappings = [
        '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/',
        '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/',
        '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol',
        '@layerzerolabs/lz-evm-messagelib-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/messagelib',
        '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
        'solidity-bytes-utils/=lib/solidity-bytes-utils/',
    ]

    # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
    ```
  </Tab>
</Tabs>

<Info>
  LayerZero contracts work with both [**OpenZeppelin V5**](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable) and V4 contracts. Specify your desired version in your project's `package.json`:

  ```typescript wrap theme={null}
  "resolutions": {
      "@openzeppelin/contracts": "^5.0.1",
  }
  ```
</Info>

## Custom OFT Contract

To build your own omnichain token contract, inherit from `OFT.sol` or `OFTAdapter.sol` depending on whether you're creating a new token or bridging an existing one.

Below is a complete example showing the key pieces you need to implement:

<Tabs>
  <Tab title="OFT (New token)">
    ```solidity wrap theme={null}
    // 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 OFT is an ERC-20 token that extends the OFTCore contract.
    contract MyOFT is OFT {
        constructor(
            string memory _name,
            string memory _symbol,
            address _lzEndpoint,
            address _owner
        ) OFT(_name, _symbol, _lzEndpoint, _owner) Ownable(_owner) {}
    }
    ```

    <Tip>
      Remember to add the ERC20 `_mint` method either in the constructor or as a protected `mint` function before deploying.
    </Tip>

    This contract provides a complete omnichain ERC20 implementation. The OFT automatically handles:

    * **Burning tokens** on the source chain when sending
    * **Minting tokens** on the destination chain when receiving
    * **Decimal precision** conversion between different chains
    * **Unified supply** management across all networks
  </Tab>

  <Tab title="OFT Adapter (Existing token)">
    ```solidity wrap theme={null}
    // 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 MyOFTAdapter is OFTAdapter {
        constructor(
            address _token,
            address _lzEndpoint,
            address _owner
        ) OFTAdapter(_token, _lzEndpoint, _owner) Ownable(_owner) {}
    }
    ```

    <Warning>
      **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.
    </Warning>

    The OFT Adapter enables existing ERC20 tokens to become omnichain without code changes. The adapter:

    * **Locks tokens** in the adapter contract when sending
    * **Unlocks tokens** from the adapter when receiving
    * **Requires approval** of the underlying token for transfers
    * **Maintains the original token** contract unchanged

    <Warning>
      **Fee-on-transfer and rebasing tokens are not supported.** The OFT `_debit` / `_credit` accounting assumes lossless ERC20 transfers (debit amount equals credit amount). To support such tokens, override `_debit` and `_credit` to reconcile against actual balance changes.
    </Warning>
  </Tab>
</Tabs>

### Constructor

* Pass the Endpoint V2 address and owner address into the base contracts.
  * `OFT(_name, _symbol, _lzEndpoint, _owner)` binds your contract to the local LayerZero Endpoint V2 and registers the delegate
  * `Ownable(_owner)` makes `_owner` the only address that can change configurations (such as peers, enforced options, and delegate)
* After deployment, the owner can call:
  * `setConfig(...)` to adjust library or DVN parameters
  * `setSendLibrary(...)` and `setReceiveLibrary(...)` to override default libraries
  * `setPeer(...)` to whitelist remote OFT addresses
  * `setDelegate(...)` to assign a different delegate address
  * `setEnforcedOptions(...)` to set mandatory execution options

## Deployment and Wiring

After you finish writing and testing your `MyOFT` contract, follow these steps to deploy it on each network and wire up the messaging stack.

<Tip>
  We **strongly recommend** using the LayerZero CLI tool to manage your configurations. Our config generator simplifies access to all available deployments across networks and is the preferred method for crosschain messaging. See the [**CLI Guide**](../../../get-started/create-lz-oapp/start) for examples and how to use it in your project.
</Tip>

### 1. Deploy Your OFT Contract

Deploy `MyOFT` on each chain using either the LayerZero CLI (recommended) or manual deployment scripts.

<Tabs>
  <Tab title="LayerZero CLI">
    After running `pnpm compile` at the root level of your example repo, you can deploy your contracts.

    #### Network Configuration

    Before using the CLI, you'll need to configure your networks in `hardhat.config.ts` with LayerZero [Endpoint IDs (EIDs)](/v2/concepts/glossary#endpoint-id) and declare an RPC URL in your `.env` or directly in the config file:

    ```typescript wrap theme={null}
    // hardhat.config.ts
    import { EndpointId } from '@layerzerolabs/lz-definitions'

    // ... rest of hardhat config omitted for brevity
    networks: {
        'optimism-sepolia-testnet': {
            // highlight-next-line
            eid: EndpointId.OPTSEP_V2_TESTNET,
            url: process.env.RPC_URL_OP_SEPOLIA || 'https://optimism-sepolia.gateway.tenderly.co',
            accounts,
        },
        'arbitrum-sepolia-testnet': {
            // highlight-next-line
            eid: EndpointId.ARBSEP_V2_TESTNET,
            url: process.env.RPC_URL_ARB_SEPOLIA || 'https://arbitrum-sepolia.gateway.tenderly.co',
            accounts,
        },
    }
    ```

    <Info>
      The key addition to a standard `hardhat.config.ts` is the inclusion of LayerZero Endpoint IDs (`eid`) for each network. Check the [Deployments](../../../deployments/deployed-contracts) section for all available endpoint IDs.
    </Info>

    The LayerZero CLI provides automated deployment with built-in endpoint detection based on your `hardhat.config.ts` networks object:

    ```bash wrap theme={null}
    # Deploy using interactive prompts
    npx hardhat lz:deploy
    ```

    The CLI will prompt you to:

    1. **Select chains to deploy to:**

    ```bash wrap theme={null}
    ? Which networks would you like to deploy? ›
    ◉  fuji
    ◉  amoy
    ◉  sepolia
    ```

    2. **Choose deploy script tags:**

    ```bash wrap theme={null}
    ? Which deploy script tags would you like to use? › MyOFT
    ```

    3. **Confirm deployment:**

    ```bash wrap theme={null}
    ✔ Do you want to continue? … yes
    Network: amoy
    Deployer: 0x0000000000000000000000000000000000000000
    Network: sepolia
    Deployer: 0x0000000000000000000000000000000000000000
    Deployed contract: MyOApp, network: amoy, address: 0x0000000000000000000000000000000000000000
    Deployed contract: MyOApp, network: sepolia, address: 0x0000000000000000000000000000000000000000
    ```

    The CLI automatically:

    * Detects the correct LayerZero Endpoint V2 address for each chain
    * Deploys your OApp contract with proper constructor arguments
    * Generates deployment artifacts in `./deployments/` folder
    * Creates network-specific deployment files (e.g., `deployments/sepolia/MyOApp.json`)
  </Tab>

  <Tab title="Manual Foundry">
    For manual deployment using Foundry, create a deployment script that handles endpoint addresses:

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { MyOApp } from "../contracts/MyOApp.sol";

    contract DeployOApp is Script {
        function run() external {
            // Replace these env vars with your own values
            address endpoint = vm.envAddress("ENDPOINT_ADDRESS");
            address owner    = vm.envAddress("OWNER_ADDRESS");

            vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
            MyOApp oapp = new MyOApp(endpoint, owner);
            vm.stopBroadcast();

            console.log("MyOApp deployed to:", address(oapp));
        }
    }
    ```

    Run the deployment script:

    ```bash wrap theme={null}
    # Deploy to testnet
    forge script script/DeployOApp.s.sol --rpc-url $RPC_URL --broadcast --verify

    # Deploy to multiple chains
    forge script script/DeployOApp.s.sol --rpc-url $ETHEREUM_RPC --broadcast --verify
    forge script script/DeployOApp.s.sol --rpc-url $POLYGON_RPC --broadcast --verify
    ```

    You'll need to set the correct LayerZero Endpoint V2 addresses for each chain in your environment variables. Check the [Deployments](../../../deployments/deployed-contracts) section for endpoint addresses.
  </Tab>
</Tabs>

### 2. Wire Messaging Libraries and Configurations

Once your contracts are onchain, you must set up send/receive libraries and DVN/Executor settings so crosschain messages flow correctly.

<Warning>
  **Production deployments should use multiple required DVNs from independent operators.** A single-DVN configuration means a compromise of that one verifier results in unrestricted forged messages on the pathway. See the [Integration Checklist](/v2/tools/integration-checklist#set-security-and-executor-configurations-on-every-pathway) for production DVN guidance.
</Warning>

<Tabs>
  <Tab title="LayerZero CLI">
    The LayerZero CLI automatically handles all wiring via a single configuration file and command:

    #### Configuration File

    In your project root, you can find a `layerzero.config.ts` file:

    ```typescript wrap theme={null}
    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
        // Replace <SECONDARY_DVN> with a non-LayerZero-Labs DVN provider for this pathway.
        // See /v2/deployments/dvn-addresses for the providers available on each chain.
        [['LayerZero Labs', '<SECONDARY_DVN>'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ]
        [1, 1], // [A to B confirmations, B to A confirmations] — adjust per pathway; production deployments typically use larger values
        [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,
      };
    }
    ```

    Make sure your contract object's `contractName` matches the named deployment file for the network under `./deployments/`.

    #### Wire Everything

    Run a single command to configure all pathways:

    ```bash wrap theme={null}
    npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
    ```

    This automatically handles:

    * Fetching the necessary contract addresses for each network from metadata
    * Setting send and receive libraries
    * Configuring DVNs and Executors
    * Setting up peers between contracts
    * Applying enforced options
    * All bidirectional pathways in your config
  </Tab>

  <Tab title="Manual Foundry">
    For manual configuration using Foundry scripts, follow these steps:

    #### Environment Setup

    Here's a comprehensive `.env.example` file showing all the environment variables needed for the different configuration scripts:

    ```bash wrap theme={null}
    # Common variables used across scripts
    ENDPOINT_ADDRESS=0x...        # LayerZero Endpoint V2 address
    OAPP_ADDRESS=0x...           # Your OApp contract address
    SIGNER=0x...                 # Address with permissions to configure/send

    # Library Configuration (SetLibraries.s.sol)
    SEND_LIB_ADDRESS=0x...       # SendUln302 address
    RECEIVE_LIB_ADDRESS=0x...    # ReceiveUln302 address
    DST_EID=30101               # Destination chain EID
    SRC_EID=30110               # Source chain EID
    GRACE_PERIOD=0              # Grace period for library switch (0 for immediate)

    # Send Config (SetSendConfig.s.sol)
    SOURCE_ENDPOINT_ADDRESS=0x... # Chain A Endpoint address
    SENDER_OAPP_ADDRESS=0x...    # OApp on Chain A
    REMOTE_EID=30101            # Endpoint ID for Chain B

    # Peer Configuration (SetPeers.s.sol)
    CHAIN1_EID=30101            # First chain EID
    CHAIN1_PEER=0x...           # OApp address on first chain
    CHAIN2_EID=30110            # Second chain EID
    CHAIN2_PEER=0x...           # OApp address on second chain
    CHAIN3_EID=30111            # Third chain EID
    CHAIN3_PEER=0x...           # OApp address on third chain

    # Message Sending (SendMessage.s.sol)
    MESSAGE="Hello World"        # Message to send crosschain
    ```

    #### 2.1 Set Send and Receive Libraries

    1. **Choose your libraries** (addresses of deployed MessageLib contracts). For standard crosschain messaging, you should use `SendUln302.sol` for `setSendLibrary(...)` and `ReceiveUln302.sol` for `setReceiveLibrary(...)`. You can find the deployments for these contracts under the [Deployments](../../../deployments/deployed-contracts) section.
    2. Call `setSendLibrary(oappAddress, dstEid, sendLibAddress)` on the Endpoint.
    3. Call `setReceiveLibrary(oappAddress, srcEid, receiveLibAddress, gracePeriod)` on the Endpoint.

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { ILayerZeroEndpointV2 } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";

    /// @title LayerZero Library Configuration Script
    /// @notice Sets up send and receive libraries for OApp messaging
    contract SetLibraries is Script {
        function run() external {
            // Load environment variables
            address endpoint = vm.envAddress("ENDPOINT_ADDRESS");    // LayerZero Endpoint address
            address oapp = vm.envAddress("OAPP_ADDRESS");           // Your OApp contract address
            address signer = vm.envAddress("SIGNER");               // Address with permissions to configure

            // Library addresses
            address sendLib = vm.envAddress("SEND_LIB_ADDRESS");    // SendUln302 address
            address receiveLib = vm.envAddress("RECEIVE_LIB_ADDRESS"); // ReceiveUln302 address

            // Chain configurations
            uint32 dstEid = uint32(vm.envUint("DST_EID"));         // Destination chain EID
            uint32 srcEid = uint32(vm.envUint("SRC_EID"));         // Source chain EID
            uint32 gracePeriod = uint32(vm.envUint("GRACE_PERIOD")); // Grace period for library switch

            vm.startBroadcast(signer);

            // Set send library for outbound messages
            ILayerZeroEndpointV2(endpoint).setSendLibrary(
                oapp,    // OApp address
                dstEid,  // Destination chain EID
                sendLib  // SendUln302 address
            );

            // Set receive library for inbound messages
            ILayerZeroEndpointV2(endpoint).setReceiveLibrary(
                oapp,        // OApp address
                srcEid,      // Source chain EID
                receiveLib,  // ReceiveUln302 address
                gracePeriod  // Grace period for library switch
            );

            vm.stopBroadcast();
        }
    }
    ```

    You would need to set up your `.env` file with the appropriate values:

    ```env wrap theme={null}
    ENDPOINT_ADDRESS=0x...
    OAPP_ADDRESS=0x...
    SIGNER=0x...
    SEND_LIB_ADDRESS=0x...    # SendUln302 address
    RECEIVE_LIB_ADDRESS=0x... # ReceiveUln302 address
    DST_EID=30101
    SRC_EID=30110
    GRACE_PERIOD=0           # Set to 0 for immediate switch, or block number for gradual migration
    ```

    #### 2.2 Set Send Config and Receive Config

    If you need non-default DVN or Executor settings (block confirmations, required DVNs, max message size, etc.), call `setConfig(...)` next. To see defaults, use `getConfig(...)`.

    **Send Config (A → B):**

    The send config is set on the source chain (Chain A) and applies to messages being sent from Chain A to Chain B. This config determines the DVN and Executor settings for outbound messages leaving Chain A and destined for Chain B. You must call `setConfig` on the Endpoint contract on Chain A, specifying the remote Endpoint ID for Chain B and the appropriate SendLib address for the A → B pathway.

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { ILayerZeroEndpointV2, SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
    import { UlnConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol";
    import { ExecutorConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBase.sol";

    /// @title LayerZero Send Configuration Script (A → B)
    /// @notice Defines and applies ULN (DVN) + Executor configs for cross‑chain messages sent from Chain A to Chain B via LayerZero Endpoint V2.
    contract SetSendConfig is Script {
        uint32 constant EXECUTOR_CONFIG_TYPE = 1;
        uint32 constant ULN_CONFIG_TYPE = 2;

         /// @notice Broadcasts transactions to set both Send ULN and Executor configurations for messages sent from Chain A to Chain B
        function run() external {
            address endpoint = vm.envAddress("SOURCE_ENDPOINT_ADDRESS"); // Chain A Endpoint
            address oapp      = vm.envAddress("SENDER_OAPP_ADDRESS");    // OApp on Chain A
            uint32 eid        = uint32(vm.envUint("REMOTE_EID"));        // Endpoint ID for Chain B
            address sendLib   = vm.envAddress("SEND_LIB_ADDRESS");      // SendLib for A → B
            address signer    = vm.envAddress("SIGNER");

            /// @notice ULNConfig defines security parameters (DVNs + confirmation threshold) for A → B
            /// @notice Send config requests these settings to be applied to the DVNs and Executor for messages sent from A to B
            /// @dev 0 values will be interpretted as defaults, so to apply NIL settings, use:
            /// @dev uint8 internal constant NIL_DVN_COUNT = type(uint8).max;
            /// @dev uint64 internal constant NIL_CONFIRMATIONS = type(uint64).max;
            UlnConfig memory uln = UlnConfig({
                confirmations:        15,                                      // minimum block confirmations required on A before sending to B
                requiredDVNCount:     2,                                       // number of DVNs required
                optionalDVNCount:     type(uint8).max,                         // optional DVNs count, uint8
                optionalDVNThreshold: 0,                                       // optional DVN threshold
                requiredDVNs:        [address(0x1111...), address(0x2222...)], // sorted list of required DVN addresses
                optionalDVNs:        []                                        // sorted list of optional DVNs
            });

            /// @notice ExecutorConfig sets message size limit + fee‑paying executor for A → B
            ExecutorConfig memory exec = ExecutorConfig({
                maxMessageSize: 10000,                                       // max bytes per crosschain message
                executor:       address(0x3333...)                           // address that pays destination execution fees on B
            });

            bytes memory encodedUln  = abi.encode(uln);
            bytes memory encodedExec = abi.encode(exec);

            SetConfigParam[] memory params = new SetConfigParam[](2);
            params[0] = SetConfigParam(eid, EXECUTOR_CONFIG_TYPE, encodedExec);
            params[1] = SetConfigParam(eid, ULN_CONFIG_TYPE, encodedUln);

            vm.startBroadcast(signer);
            ILayerZeroEndpointV2(endpoint).setConfig(oapp, sendLib, params); // Set config for messages sent from A to B
            vm.stopBroadcast();
        }
    }
    ```

    **Receive Config (B ← A):**

    The receive config is set on the destination chain (Chain B) and applies to messages being received on Chain B from Chain A. This config determines the DVN settings for inbound messages arriving from Chain A. You must call `setConfig` on the Endpoint contract on Chain B, specifying the remote Endpoint ID for Chain A and the appropriate ReceiveLib address for the B ← A pathway.

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { ILayerZeroEndpointV2, SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
    import { UlnConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol";

    /// @title LayerZero Receive Configuration Script (B ← A)
    /// @notice Defines and applies ULN (DVN) config for inbound message verification on Chain B for messages received from Chain A via LayerZero Endpoint V2.
    contract SetReceiveConfig is Script {
        uint32 constant RECEIVE_CONFIG_TYPE = 2;

        function run() external {
            address endpoint = vm.envAddress("ENDPOINT_ADDRESS");      // Chain B Endpoint
            address oapp      = vm.envAddress("OAPP_ADDRESS");         // OApp on Chain B
            uint32 eid        = uint32(vm.envUint("REMOTE_EID"));      // Endpoint ID for Chain A
            address receiveLib= vm.envAddress("RECEIVE_LIB_ADDRESS");  // ReceiveLib for B ← A
            address signer    = vm.envAddress("SIGNER");

            /// @notice UlnConfig controls verification threshold for incoming messages from A to B
            /// @notice Receive config enforces these settings have been applied to the DVNs for messages received from A
            /// @dev 0 values will be interpretted as defaults, so to apply NIL settings, use:
            /// @dev uint8 internal constant NIL_DVN_COUNT = type(uint8).max;
            /// @dev uint64 internal constant NIL_CONFIRMATIONS = type(uint64).max;
            UlnConfig memory uln = UlnConfig({
                confirmations:      15,                                       // min block confirmations from source (A)
                requiredDVNCount:   2,                                        // required DVNs for message acceptance
                optionalDVNCount:   type(uint8).max,                          // optional DVNs count
                optionalDVNThreshold: 0,                                      // optional DVN threshold
                requiredDVNs:       [address(0x1111...), address(0x2222...)], // sorted required DVNs
                optionalDVNs:       []                                        // no optional DVNs
            });

            bytes memory encodedUln = abi.encode(uln);

            SetConfigParam[] memory params = new SetConfigParam[](1);
            params[0] = SetConfigParam(eid, RECEIVE_CONFIG_TYPE, encodedUln);

            vm.startBroadcast(signer);
            ILayerZeroEndpointV2(endpoint).setConfig(oapp, receiveLib, params); // Set config for messages received on B from A
            vm.stopBroadcast();
        }
    }
    ```

    #### 2.3 Set Peers

    Once you've finished your **OApp Configuration** you can open the messaging channel and connect your OApp deployments by calling `setPeer`.

    A peer is required to be set for each EID (or network). Ideally an OApp (or OFT) will have multiple peers set where one and only one peer exists for one EID.

    The function takes 2 arguments: `_eid`, the destination endpoint ID for the chain our other OApp contract lives on, and `_peer`, the destination OApp contract address in `bytes32` format.

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { MyOApp } from "../contracts/MyOApp.sol";

    /// @title LayerZero OApp Peer Configuration Script
    /// @notice Sets up peer connections between OApp deployments on different chains
    contract SetPeers is Script {
        function run() external {
            // Load environment variables
            address oapp = vm.envAddress("OAPP_ADDRESS");         // Your OApp contract address
            address signer = vm.envAddress("SIGNER");            // Address with owner permissions

            // Example: Set peers for different chains
            // Format: (chain EID, peer address in bytes32)
            (uint32 eid1, bytes32 peer1) = (uint32(vm.envUint("CHAIN1_EID")), bytes32(uint256(uint160(vm.envAddress("CHAIN1_PEER")))));
            (uint32 eid2, bytes32 peer2) = (uint32(vm.envUint("CHAIN2_EID")), bytes32(uint256(uint160(vm.envAddress("CHAIN2_PEER")))));
            (uint32 eid3, bytes32 peer3) = (uint32(vm.envUint("CHAIN3_EID")), bytes32(uint256(uint160(vm.envAddress("CHAIN3_PEER")))));

            vm.startBroadcast(signer);

            // Set peers for each chain
            MyOApp(oapp).setPeer(eid1, peer1);
            MyOApp(oapp).setPeer(eid2, peer2);
            MyOApp(oapp).setPeer(eid3, peer3);

            vm.stopBroadcast();
        }
    }
    ```

    <Warning>
      This function opens your OApp to start receiving messages from the messaging channel, meaning you should configure any application settings you intend on changing prior to calling `setPeer`.
    </Warning>

    <Warning>
      OApps need `setPeer` to be called correctly on both contracts to send messages. The peer address uses `bytes32` for handling non-EVM destination chains.

      If the peer has been set to an incorrect destination address, your messages will not be delivered and handled properly. If not resolved, users can potentially pay gas on source without any corresponding action on destination. You can confirm the peer address is the expected destination OApp address by viewing the `peers` mapping directly.
    </Warning>

    #### 2.4 Set Enforced Options

    Enforced options allow the OApp owner to set mandatory execution parameters that will be applied to all messages of a specific type sent to a destination chain. These options are automatically combined with any caller-provided options when using `OAppOptionsType3`.

    **Why use enforced options?**

    * Ensure sufficient gas is always allocated for message execution on the destination
    * Enforce payment for additional services like PreCrime verification
    * Set consistent execution parameters across all users of your OApp
    * Prevent failed deliveries due to insufficient gas

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { MyOApp } from "../contracts/MyOApp.sol";
    import { EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
    import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";

    /// @title LayerZero OApp Enforced Options Configuration Script
    /// @notice Sets enforced execution options for specific message types and destinations
    contract SetEnforcedOptions is Script {
        using OptionsBuilder for bytes;

        function run() external {
            // Load environment variables
            address oapp = vm.envAddress("OAPP_ADDRESS");         // Your OApp contract address
            address signer = vm.envAddress("SIGNER");            // Address with owner permissions

            // Destination chain configurations
            uint32 dstEid1 = uint32(vm.envUint("DST_EID_1"));    // First destination EID
            uint32 dstEid2 = uint32(vm.envUint("DST_EID_2"));    // Second destination EID

            // Message type (should match your contract's constant)
            uint16 SEND = 1;  // Message type for sendString function

            // Build options using OptionsBuilder
            bytes memory options1 = OptionsBuilder.newOptions().addExecutorLzReceiveOption(80000, 0);
            bytes memory options2 = OptionsBuilder.newOptions().addExecutorLzReceiveOption(100000, 0);

            // Create enforced options array
            EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](2);

            // Set enforced options for first destination
            enforcedOptions[0] = EnforcedOptionParam({
                eid: dstEid1,
                msgType: SEND,
                options: options1
            });

            // Set enforced options for second destination
            enforcedOptions[1] = EnforcedOptionParam({
                eid: dstEid2,
                msgType: SEND,
                options: options2
            });

            vm.startBroadcast(signer);

            // Set enforced options on the OApp
            MyOApp(oapp).setEnforcedOptions(enforcedOptions);

            vm.stopBroadcast();

            console.log("Enforced options set successfully!");
            console.log("Destination 1 EID:", dstEid1, "Gas:", 80000);
            console.log("Destination 2 EID:", dstEid2, "Gas:", 100000);
        }
    }
    ```

    **Environment variables needed:**

    ```env wrap theme={null}
    OAPP_ADDRESS=0x...           # Your deployed MyOApp address
    SIGNER=0x...                 # Address with owner permissions
    DST_EID_1=30101             # First destination endpoint ID
    DST_EID_2=30110             # Second destination endpoint ID
    ```

    **Run the script:**

    ```bash wrap theme={null}
    forge script script/SetEnforcedOptions.s.sol --rpc-url $RPC_URL --broadcast
    ```

    Once set, these enforced options will be automatically applied when using `combineOptions()` in your send functions, ensuring consistent execution parameters across all messages.
  </Tab>
</Tabs>

<br />

## Usage

Once deployed and wired, you can begin sending tokens across chains.

### Send tokens

The OFT standard provides methods for quoting and sending tokens crosschain via the [IOFT interface](https://github.com/LayerZero-Labs/devtools/blob/main/packages/oft-evm/contracts/interfaces/IOFT.sol).

#### quoteSend() - Get Transfer Fees

<InteractiveContract contractName="OFT" functionName="quoteSend" description="Provides a quote for the send() operation. Returns the messaging fees required to transfer tokens crosschain, including both native and LayerZero token fee options." />

#### quoteOFT() - Get Detailed Transfer Quote

<InteractiveContract contractName="OFT" functionName="quoteOFT" description="Provides comprehensive quote information including transfer limits, fee breakdowns, and expected receive amounts. Unused in the default implementation." />

#### send() - Transfer Tokens

<InteractiveContract contractName="OFT" functionName="send" description="Executes the crosschain token transfer. Debits tokens from the sender on the source chain and credits them to the recipient on the destination chain." />

#### How send() Works Under the Hood

When you call `send()`, it triggers a chain of calls through the LayerZero protocol:

1. **OFT Contract** → Debits tokens
2. **LayerZero Endpoint** → Routes the message to your configured MessageLib
3. **Message Library (SendUln302)** → Requests verification/execution from configured DVNs/Executor
4. **Workers (DVNs + Executor)** → Quote their fees for verification and execution services
5. **Fee Aggregation** → Returns total `nativeFee` needed for the transfer

When deploying an OFT, you choose your own trust assumptions for verification and execution:

* **Send/Receive libraries** - Set the MessageLibs for your contract
* **DVNs** - Select which DVNs verify your crosschain messages (at least one required)
* **Enforced options OR caller options** - Provide gas settings (globally or per call), or transactions fail with `LZ_ULN_InvalidWorkerOptions`
* **Peers** - Register destination OFT addresses for crosschain transfers

**These requirements must be satisfied** before `send()` will work. They are configured during the "Deployment and Wiring" step above.

<Info>
  ### Trust Decisions

  **Using Managed Applications** (e.g., Stargate): You trust the application team's selected DVNs and Executor configurations.

  **Deploying Your Own OFT**: You select your trusted DVNs and Executors, giving you full control over your security assumptions.
</Info>

<Tip>
  ### Quote Freshness

  Call `quoteSend()` as close as possible to `send()` execution to avoid stale fee quotes. Fees can change due to:

  * Gas price fluctuations on source/destination chains
  * Price feed updates for crosschain gas estimation
  * DVN fee adjustments

  In production applications, quote and send in the same transaction or block when possible.
</Tip>

<Tabs>
  <Tab title="LayerZero CLI">
    The LayerZero CLI provides a convenient task for sending OFT tokens that automatically handles fee estimation and transaction execution.

    #### Using the Send Task

    The CLI includes a built-in `lz:oft:send` task that:

    1. Finds your deployed OFT contract automatically
    2. Quotes the gas cost using your OFT's `quoteSend()` function
    3. Sends the tokens with the correct fee
    4. Provides tracking links for the transaction

    **Basic usage:**

    ```bash wrap theme={null}
    npx hardhat lz:oft:send --src-eid 40232 --dst-eid 40231 --amount 1.5 --to 0x1234567890123456789012345678901234567890
    ```

    **Required Parameters:**

    * `--src-eid`: Source endpoint ID (e.g., 40232 for Optimism Sepolia)
    * `--dst-eid`: Destination endpoint ID (e.g., 40231 for Arbitrum Sepolia)
    * `--amount`: Amount to send in human readable units (e.g., "1.5")
    * `--to`: Recipient address (20-byte hex for EVM)

    **Optional Parameters:**

    * `--min-amount`: Minimum amount to receive for slippage protection (e.g., "1.4")
    * `--extra-options`: Additional gas units for lzReceive, lzCompose, or receiver address
    * `--compose-msg`: Arbitrary bytes message to deliver alongside the OFT
    * `--oft-address`: Override the source OFT address (if not using deployment artifacts)

    **Example with optional parameters:**

    ```bash wrap theme={null}
    npx hardhat lz:oft:send \
      --src-eid 40232 \
      --dst-eid 40231 \
      --amount 10.0 \
      --to 0x1234567890123456789012345678901234567890 \
      --min-amount 9.5 \
      --extra-options 0x00030100110100000000000000000000000000030d40
    ```

    The task automatically:

    * Finds your deployed OFT contract from deployment artifacts
    * Handles token approvals (for OFTAdapter)
    * Quotes the exact gas fee needed
    * Provides block explorer and LayerZero Scan links for tracking
  </Tab>

  <Tab title="Manual Foundry">
    Remember to generate a fee estimate using `quoteSend` first, then pass the returned native gas amount as your `msg.value`

    If using the base `OFTAdapter.sol`, you will want to approve the adapter contract to spend your ERC20 tokens:

    ```solidity wrap theme={null}
    ERC20(tokenAddress).approve(adapterAddress, amount);
    ```

    For manual token sending using Foundry, create a script that handles fee estimation and token transfer:

    ```solidity wrap theme={null}
    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.22;

    import "forge-std/Script.sol";
    import { MyOFT } from "../contracts/MyOFT.sol";
    import { SendParam } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
    import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
    import { MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";

    contract SendOFT is Script {
        using OptionsBuilder for bytes;

        function addressToBytes32(address _addr) internal pure returns (bytes32) {
            return bytes32(uint256(uint160(_addr)));
        }

        function run() external {
            // Load environment variables
            address oftAddress = vm.envAddress("OFT_ADDRESS");
            address toAddress = vm.envAddress("TO_ADDRESS");
            uint256 tokensToSend = vm.envUint("TOKENS_TO_SEND");
            uint32 dstEid = uint32(vm.envUint("DST_EID"));
            uint256 privateKey = vm.envUint("PRIVATE_KEY");

            vm.startBroadcast(privateKey);

            MyOFT oft = MyOFT(oftAddress);

            // Build send parameters
            bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(65000, 0);
            SendParam memory sendParam = SendParam({
                dstEid: dstEid,
                to: addressToBytes32(toAddress),
                amountLD: tokensToSend,
                minAmountLD: tokensToSend * 95 / 100, // 5% slippage tolerance
                extraOptions: extraOptions,
                composeMsg: "",
                oftCmd: ""
            });

            // Get fee quote
            MessagingFee memory fee = oft.quoteSend(sendParam, false);

            console.log("Sending tokens...");
            console.log("Fee amount:", fee.nativeFee);

            // Send tokens
            oft.send{value: fee.nativeFee}(sendParam, fee, msg.sender);

            vm.stopBroadcast();
        }
    }
    ```

    **Environment variables needed:**

    ```env wrap theme={null}
    OFT_ADDRESS=0x...           # Your deployed OFT address
    TO_ADDRESS=0x...            # Recipient address
    TOKENS_TO_SEND=1000000000000000000  # Amount in wei (18 decimals)
    DST_EID=30101              # Destination endpoint ID
    PRIVATE_KEY=0x...          # Private key for sending
    ```

    **Run the script:**

    ```bash wrap theme={null}
    forge script script/SendOFT.s.sol --rpc-url $RPC_URL --broadcast
    ```
  </Tab>
</Tabs>

### Send tokens + call composer

**Horizontal composability** allows your OFT to trigger additional actions on the destination chain through separate, containerized message packets. Unlike vertical composability (multiple calls in a single transaction), horizontal composability processes operations independently, providing better fault isolation and gas efficiency.

<img src="https://mintcdn.com/layerzero/MzSiOdXt8xlDlEr4/images/learn/Composed-Light.svg?fit=max&auto=format&n=MzSiOdXt8xlDlEr4&q=85&s=3917355397da56862d784598420f6e0c" alt="Diagram showing horizontal composability flow: OFT processes token transfer in lzReceive, then calls endpoint.sendCompose to queue a separate composed message that the Composer contract receives via lzCompose for custom logic execution" className="block dark:hidden" width="1920" height="517" data-path="images/learn/Composed-Light.svg" />

<img src="https://mintcdn.com/layerzero/MzSiOdXt8xlDlEr4/images/learn/Composed-Dark.svg?fit=max&auto=format&n=MzSiOdXt8xlDlEr4&q=85&s=38ab6dbf2d791462d654d55dd9846c7a" alt="Diagram showing horizontal composability flow: OFT processes token transfer in lzReceive, then calls endpoint.sendCompose to queue a separate composed message that the Composer contract receives via lzCompose for custom logic execution" className="hidden dark:block" width="1920" height="517" data-path="images/learn/Composed-Dark.svg" />

#### Benefits of Horizontal Composability

* **Fault Isolation**: If a composed call fails, it doesn't revert the main token transfer
* **Gas Efficiency**: Each step can have independent gas limits and execution options
* **Flexible Workflows**: Complex multi-step operations can be broken into manageable pieces
* **Non-Critical Operations**: Secondary actions (like swaps or staking) can fail without affecting token delivery

#### Workflow Overview

1. **Token Transfer**: OFT processes the token transfer in `_lzReceive()` and credits tokens to the recipient
2. **Compose Message**: OFT calls `endpoint.sendCompose()` to queue a separate composed message
3. **Composer Execution**: The composer contract receives the message via `lzCompose()` and executes custom logic

#### Sending with ComposeMsg

When sending tokens with composed actions, set the `to` address to your composer contract and include your custom `composeMsg`:

```solidity wrap theme={null}
SendParam memory sendParam = SendParam({
    dstEid: dstEid,
    to: addressToBytes32(composerAddress), // Composer contract address, NOT end recipient
    amountLD: tokensToSend,
    minAmountLD: tokensToSend * 95 / 100,
    // highlight-start
    extraOptions: extraOptions,
    composeMsg: abi.encode(finalRecipient, swapParams), // Data for composer logic
    // highlight-end
    oftCmd: ""
});
```

#### Understanding the Message Encoding

When using composed messages, the OFT encodes your `composeMsg` along with token transfer data. After processing the transfer, the destination OFT re-encodes this data and delivers it to your composer contract via `endpoint.sendCompose()`.

For the complete message structures and codec functions, see the [Message Encoding Reference](../composer/overview#message-encoding-reference) documentation.

#### Execution Options for Composed Messages

Composed messages require gas for **two separate executions**:

1. **Token Transfer (`lzReceive`)**: Credits tokens and queues the composed message
2. **Composer Call (`lzCompose`)**: Executes your custom logic in the composer contract

```solidity wrap theme={null}
bytes memory options = OptionsBuilder.newOptions()
    .addExecutorLzReceiveOption(65000, 0)        // Token transfer + compose queuing
    .addExecutorLzComposeOption(0, 50000, 0);    // Composer contract execution
```

<Warning>
  **Two-Phase Gas Requirements**:

  * **`lzReceiveOption`**: Gas for token crediting + `endpoint.sendCompose()` call (varies with `composeMsg` size)
  * **`lzComposeOption`**: Gas for your composer contract's business logic (depends on complexity)

  Always test your composed implementation to determine adequate gas limits for both phases. If either phase runs out of gas, you'll need to manually retry the failed execution.
</Warning>

#### Using the CLI with Composed Messages

The `lz:oft:send` task supports composed messages via the `--compose-msg` and `--extra-options` parameters:

```bash wrap theme={null}
npx hardhat lz:oft:send \
  --src-eid 40232 \
  --dst-eid 40231 \
  --amount 5 \
  --to 0x1234567890123456789012345678901234567890 \
  --compose-msg 0x000000000000000000000000abcdefabcdefabcdefabcdefabcdefabcdefabcd \
  --extra-options 0x00030100110100000000000000000000000000fdfe00030200010000000000000000000000000000c350
```

<Tip>
  **Encoding Compose Messages**: The `--compose-msg` parameter expects hex-encoded bytes. You can encode data using:

  * **Online tools**: Use ethers.js playground or similar tools to encode your data
  * **Cast command**: `cast abi-encode "function_signature" param1 param2`
  * **Hardhat console**: `ethers.utils.defaultAbiCoder.encode(['address'], ['0x...'])`

  **Extra Options**: The `--extra-options` above includes both `lzReceiveOption` (gas: 65534) and `lzComposeOption` (index: 0, gas: 50000) for composed messages.
</Tip>

#### Implementing a Composer Contract

The composer contract must implement `IOAppComposer` to handle composed messages. Here's a comprehensive example:

```solidity wrap theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title TokenSwapper
 * @notice Receives OFT tokens and automatically swaps them for another token
 */
contract TokenSwapper is IOAppComposer {
    using SafeERC20 for IERC20;

    /// @notice LayerZero endpoint address
    address public immutable endpoint;

    /// @notice Trusted OFT that can send composed messages
    address public immutable trustedOFT;

    /// @notice Token to swap to
    IERC20 public immutable targetToken;

    event TokenSwapped(
        address indexed originalSender,
        address indexed recipient,
        uint256 amountIn,
        uint256 amountOut
    );

    constructor(address _endpoint, address _trustedOFT, address _targetToken) {
        endpoint = _endpoint;
        trustedOFT = _trustedOFT;
        targetToken = IERC20(_targetToken);
    }

    /**
     * @notice Handles composed messages from the OFT
     * @param _oApp Address of the originating OApp (must be trusted OFT)
     * @param _guid Unique identifier for this message
     * @param _message Encoded message containing compose data
     */
    function lzCompose(
        address _oApp,
        bytes32 _guid,
        bytes calldata _message,
        address /*_executor*/,
        bytes calldata /*_extraData*/
    ) external payable override {
        // Security: Verify the message source
        require(msg.sender == endpoint, "TokenSwapper: unauthorized sender");
        require(_oApp == trustedOFT, "TokenSwapper: untrusted OApp");

        // Decode the full composed message context
        uint64 nonce = OFTComposeMsgCodec.nonce(_message);
        uint32 srcEid = OFTComposeMsgCodec.srcEid(_message);
        uint256 amountLD = OFTComposeMsgCodec.amountLD(_message);

        // Get original sender (who initiated the OFT transfer)
        bytes32 composeFromBytes = OFTComposeMsgCodec.composeFrom(_message);
        address originalSender = OFTComposeMsgCodec.bytes32ToAddress(composeFromBytes);

        // Decode your custom compose message
        bytes memory composeMsg = OFTComposeMsgCodec.composeMsg(_message);
        (address recipient, uint256 minAmountOut) = abi.decode(composeMsg, (address, uint256));

        // Execute the swap logic
        uint256 amountOut = _performSwap(amountLD, minAmountOut);

        // Transfer swapped tokens to recipient
        targetToken.safeTransfer(recipient, amountOut);

        emit TokenSwapped(originalSender, recipient, amountLD, amountOut);
    }

    function _performSwap(uint256 amountIn, uint256 minAmountOut) internal returns (uint256 amountOut) {
        // Your swap logic here (DEX integration, etc.)
        // This is a simplified example
        amountOut = amountIn * 95 / 100; // Simulate 5% slippage
        require(amountOut >= minAmountOut, "TokenSwapper: insufficient output");
    }
}
```

#### Key Security Considerations

* **Endpoint Verification**: Always verify `msg.sender == endpoint`
* **OApp Authentication**: Only accept messages from trusted OApps
* **Message Validation**: Validate all decoded parameters before execution
* **Reentrancy Protection**: Consider using `ReentrancyGuard` for complex operations

<Tip>
  **Token Availability**: The OFT automatically credits tokens to the composer address before calling `lzCompose`, so your composer can immediately use the received tokens. The tokens are already available in the composer's balance when `lzCompose` executes.
</Tip>

## Extensions

The OFT Standard can be extended to support several different use cases, similar to the ERC20 token standard. Since OFT inherits from the base OApp contract, all OApp extensions and patterns are also available to OFT implementations, providing maximum flexibility for crosschain token applications.

Below you can find relevant patterns and extensions:

### Rate Limiting

The `RateLimiter` pattern controls the number of tokens that can be transferred crosschain within a specific time window. This is particularly valuable for OFTs to prevent abuse and ensure controlled token flow across chains.

#### Why Use Rate Limiting for OFTs?

* **Prevent Token Drain Attacks**: Protects against malicious actors attempting to rapidly drain tokens from a chain
* **Regulatory Compliance**: Helps meet compliance requirements for controlled cross-blockchain token transfers
* **Supply Management**: Maintains balanced token distribution across chains by limiting transfer velocity
* **Risk Management**: Reduces exposure to smart contract vulnerabilities or bridge exploits

#### Implementation

Inherit from both `OFT` and `RateLimiter` in your contract:

```solidity wrap theme={null}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

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

contract MyRateLimitedOFT is OFT, RateLimiter {
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpoint,
        address _owner,
        RateLimitConfig[] memory _rateLimitConfigs
    ) OFT(_name, _symbol, _lzEndpoint, _owner) Ownable(_owner) {
        _setRateLimits(_rateLimitConfigs);
    }

    // Override _debit to enforce rate limits on token transfers
    function _debit(
        address _from,
        uint256 _amountLD,
        uint256 _minAmountLD,
        uint32 _dstEid
    ) internal override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
        // Check rate limit before allowing the transfer
        _outflow(_dstEid, _amountLD);

        // Proceed with normal OFT debit logic
        return super._debit(_amountLD, _minAmountLD, _dstEid);
    }
}
```

#### Configuration

Set up rate limits per destination chain during deployment:

```solidity wrap theme={null}
// Example: Allow max 1000 tokens per hour to Ethereum, 500 per hour to Polygon
RateLimitConfig[] memory configs = new RateLimitConfig[](2);

configs[0] = RateLimitConfig({
    dstEid: 30101,     // Ethereum endpoint ID
    limit: 1000 ether, // 1000 tokens (18 decimals)
    window: 3600       // 1 hour window
});

configs[1] = RateLimitConfig({
    dstEid: 30109,     // Polygon endpoint ID
    limit: 500 ether,  // 500 tokens (18 decimals)
    window: 3600       // 1 hour window
});
```

#### Dynamic Rate Limit Management

Add functions to update rate limits post-deployment:

```solidity wrap theme={null}
function setRateLimits(
RateLimitConfig[] calldata _rateLimitConfigs
) external onlyOwner {
_setRateLimits(_rateLimitConfigs);
}

function getRateLimit(uint32 _dstEid) external view returns (RateLimit memory) {
return rateLimits[_dstEid];
}
```

#### Rate Limit Behavior

When a transfer exceeds the rate limit:

* The transaction reverts with a rate limit error
* Users must wait for the time window to reset
* The limit resets based on a sliding window mechanism

<Tip>
  Consider implementing different rate limits for different user tiers (e.g., higher limits for verified institutions) by overriding the rate limit check logic.
</Tip>

<Warning>
  Rate limiting may not be suitable for all OFT applications. High-frequency trading or time-sensitive applications might be negatively impacted by rate limits.
</Warning>

### Mint & Burn OFT Adapter

The `MintBurnOFTAdapter` is a specialized adapter for existing ERC20 tokens that have exposed mint and burn functions. Unlike the standard `OFTAdapter` which locks/unlocks tokens, this adapter burns tokens on the source chain and mints them on the destination chain.

#### Key Differences from Standard OFTAdapter

| Feature                  | Standard OFTAdapter                 | MintBurnOFTAdapter             |
| ------------------------ | ----------------------------------- | ------------------------------ |
| **Token Supply**         | Locks/unlocks existing tokens       | Burns/mints tokens dynamically |
| **Multiple Deployments** | Only one adapter per token globally | Multiple adapters can exist    |
| **Approval Required**    | Yes, users must approve adapter     | No, uses mint/burn privileges  |
| **Token Mechanism**      | Escrow (locks tokens)               | Non-escrow (burns/mints)       |

#### When to Use MintBurnOFTAdapter

* **Tokens with mint/burn capabilities**: Your ERC20 already has `mint()` and `burn()` functions
* **Dynamic supply management**: You prefer burning/minting over locking mechanisms
* **Reduced custody risk**: Eliminate the risk of locked token supply running dry when using multiple adapters

#### Installation

To get started with a MintBurnOFTAdapter example, use the LayerZero CLI tool to create a new project:

```bash wrap theme={null}
LZ_ENABLE_MINTBURN_EXAMPLE=1 npx create-lz-oapp@latest --example mint-burn-oft-adapter
```

This creates a complete project with:

* Example `MintBurnOFTAdapter` contracts
* Sample `ElevatedMinterBurner` implementation
* Deployment and configuration scripts
* Crosschain unit tests

The example includes both the adapter contract and the underlying token with mint/burn capabilities, showing the complete integration pattern.

#### Implementation

Create your mint/burn adapter by inheriting from `MintBurnOFTAdapter`:

```solidity wrap theme={null}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

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

contract MyMintBurnOFTAdapter is MintBurnOFTAdapter {
constructor(
  address _token,                   // Your existing ERC20 token with mint/burn exposed
  IMintableBurnable _minterBurner,  // Contract with mint/burn privileges
  address _lzEndpoint,              // Local LayerZero endpoint
  address _owner                    // Contract owner
) MintBurnOFTAdapter(_token, _minterBurner, _lzEndpoint, _owner) Ownable(_owner) {}
}
```

#### Token Requirements

You need a contract that implements the `IMintableBurnable` interface. This can be either:

**Option 1: Token directly implements the interface**

```solidity wrap theme={null}
interface IMintableBurnable {
    function burn(address _from, uint256 _amount) external returns (bool success);
    function mint(address _to, uint256 _amount) external returns (bool success);
}
```

**Option 2: Elevated minter/burner contract (Recommended)**

For existing tokens that already have mint/burn capabilities but don't implement `IMintableBurnable`, use an intermediary contract:

```solidity wrap theme={null}
contract ElevatedMinterBurner is IMintableBurnable, Ownable {
    IMintableBurnable public immutable token;
    mapping(address => bool) public operators;

    modifier onlyOperators() {
        require(operators[msg.sender] || msg.sender == owner(), "Not authorized");
        _;
    }

    constructor(IMintableBurnable _token, address _owner) Ownable(_owner) {
        token = _token;
    }

    function setOperator(address _operator, bool _status) external onlyOwner {
        operators[_operator] = _status;
    }

    function burn(address _from, uint256 _amount) external override onlyOperators returns (bool) {
        return token.burn(_from, _amount);
    }

    function mint(address _to, uint256 _amount) external override onlyOperators returns (bool) {
        return token.mint(_to, _amount);
    }
}
```

The elevated contract approach allows you to:

* Use existing tokens without modification
* Control which contracts can mint/burn through operator management
* Maintain existing token governance while adding bridge functionality

#### Usage Flow

1. **Sending tokens**:

   * User calls `send()` on the MintBurnOFTAdapter
   * Adapter burns tokens from user's balance
   * LayerZero message sent to destination

2. **Receiving tokens**:
   * Destination adapter receives LayerZero message
   * Adapter mints new tokens to recipient's address

#### Security Considerations

The `MintBurnOFTAdapter` requires careful access control since it can mint tokens:

```solidity wrap theme={null}
// Example: Ensure only the adapter can mint/burn
contract SecureMintBurner is IMintableBurnable, Ownable {
    IERC20Mintable public token;
    address public adapter;

    modifier onlyAdapter() {
        require(msg.sender == adapter, "Only adapter can mint/burn");
        _;
    }

    function mint(address _to, uint256 _amount) external onlyAdapter returns (bool) {
        token.mint(_to, _amount);
        return true;
    }

    function burn(address _from, uint256 _amount) external onlyAdapter returns (bool) {
        token.burnFrom(_from, _amount);
        return true;
    }
}
```

<Info>
  Unlike standard OFTAdapter, you can deploy multiple MintBurnOFTAdapters for the same omnichain mesh.
</Info>

### OFT Alt

When the native gas token cannot be used to pay LayerZero fees, you can use `OFTAlt` which supports payment in an alternative ERC20 token.

#### Installation

To get started with an OFTAlt example, use the LayerZero CLI tool to create a new project:

```bash wrap theme={null}
LZ_ENABLE_ALT_EXAMPLE=1 npx create-lz-oapp@latest --example oft-alt
```

This creates a complete project with:

* Example `OFTAlt` contracts with alternative fee payment
* [`EndpointV2Alt`](/v2/concepts/protocol/layerzero-endpoint-alt) integration setup
* Alternative fee token configuration
* Deployment and configuration scripts
* Crosschain unit tests with ERC20 fee payments

The example includes both the OFT Alt contract and the necessary setup for using alternative fee tokens, showing the complete integration pattern.

#### Implementation

```solidity wrap theme={null}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;

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

contract MyOFTAlt is OFTAlt {
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpointAlt,
        address _owner
    ) OFTAlt(_name, _symbol, _lzEndpointAlt, _owner) Ownable(_owner) {}
}
```

#### Key Differences

1. **Fee Payment**: Uses ERC20 tokens instead of native gas
2. **Approval Required**: You must approve the OFT contract to spend your fee tokens
3. **Endpoint**: Must use `EndpointV2Alt` instead of standard `EndpointV2`

#### Using OFT Alt

Before sending messages, approve the fee token:

```solidity wrap theme={null}
// Approve the OFT to spend fee tokens
IERC20(feeToken).approve(oftAltAddress, feeAmount);

// Then send normally
oft.send{value: 0}(sendParam, fee, refundAddress); // No native value needed
```

<Info>
  OFT Alt is designed for chains where native gas tokens are not suitable for LayerZero fees, such as certain L2s or sidechains with alternative fee mechanisms.
</Info>

### Further Reading

For more advanced patterns and detailed implementations, see:

* [OApp Design Patterns](../oapp/message-design-patterns) - Additional messaging patterns
* [Message Execution Options](../configuration/options) - Detailed options configuration
* [OFT Technical Reference](../../../concepts/technical-reference/oft-reference) - Deep dive into OFT mechanics

### Tracing and Troubleshooting

You can follow your testnet and mainnet transaction statuses using [LayerZero Scan](https://layerzeroscan.com/).

Refer to [Debugging Messages](../troubleshooting/debugging-messages) for any unexpected complications when sending a message.

You can also ask for help or follow development in the [Discord](https://discord.com/invite/ktbvm8Nkcr).
