Configuring Pathways
The LayerZero Protocol allows your OApp contract owner to configure various different decentralized verifier networks (DVNs) to verify your cross-chain messages using unique validation techniques, Executors to automatically deliver messages, and direct control over the amount of destination gas units used for different calls within your destination OApp.
To configure your OApp, you will need to change your layerzero.config.ts
for your desired pathways.
Adding Configurations
Inside your create-lz-oapp
repo, head to the layerzero.config.ts
and scroll down to module.exports
.
This tutorial assumes you've already added your desired OApp contracts to your layerzero.config.ts
.
If not, review the Project Configuration section before continuing.
As explained previously, the CLI Toolkit organizes your OApp's configurations on a per-pathway basis:
module.exports = {
// Define the contracts to be deployed on each network
// Each contract is associated with a specific blockchain.
contracts: [
{
contract: sepoliaContract,
},
{
contract: bscContract,
},
],
// Define the pathway between each contract.
// This allows for cross-chain communication using LayerZero.
connections: [
{
from: bscContract,
to: sepoliaContract,
},
{
from: sepoliaContract,
to: bscContract,
},
],
};
To add a specific pathway configuration, add a config: {}
to your connection:
module.exports = {
// Define the contracts to be deployed on each network
// Each contract is associated with a specific blockchain.
contracts: [
{
contract: sepoliaContract,
},
{
contract: bscContract,
},
],
// Define the pathway between each contract.
// This allows for cross-chain communication using LayerZero.
connections: [
{
from: bscContract,
to: sepoliaContract,
config: {},
},
{
from: sepoliaContract,
to: bscContract,
},
],
};
Each pathway contains a config
, containing multiple configuration structs for changing how your OApp sends and receives messages, specifically for the chain your OApp is sending from
:
Name | Type | Description |
---|---|---|
sendLibrary | Address | The message library used for configuring all sent messages from this chain. (e.g., SendUln302.sol ) |
receiveLibraryConfig | Struct | A struct containing the receive message library address (e.g., ReceiveUln302.sol ), and an optional BigInt, gracePeriod , the time to wait before updating to a new MessageLib version during version migration. Controls how the from chain receives messages. |
receiveLibraryTimeoutConfig | Struct | An optional param, defining when the old receive library (lib ) will expire (expiry ) during version migration. |
sendConfig | Struct | Controls how the OApp sends from this pathway, containing two more structs: executorConfig and ulnConfig (DVNs). |
receiveConfig | Struct | Controls how the OApp (from ) receives messages, specifically the ulnConfig (DVNs). |
enforcedOptions | Struct | Controls the minimum destination gas sent to the destination, per message type (e.g., _lzReceive , lzCompose , etc.) in your OApp. |
When adding a config
, consider that connections moves in a bidirectional, two-way path:
The
sendConfig
applies to all message sentfrom
Chain A and received by theto
address, Chain B.The
receiveConfig
applies to all messages received by Chain A (from
), sent from Chain B (theto
contract).
For example, this config: {}
applies only to how the bscContract
sends messages to the sepoliaContract
, and how the bscContract
receives messages from the sepoliaContract
.
Adding sendLibrary
Every configuration should start by adding a sendLibrary
.
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
},
},
],
Each MessageLib contains the available configuration options for the protocol, and so must be set by the application owner to prevent unintended updates.
You should use the sendLibrary
address for the chain you're sending from
(i.e., SendUln302.sol
on BSC).
The MessageLib Registry is append only, meaning that old Message Libraries will always be available for OApps. Locking your Library is only necessary to prevent updates.
Adding receiveLibrary
Every configuration should also add a receiveLibrary
. Similar to the sendLibrary
, the OApp owner must also set the Receive Library to ensure that your configured application settings will be locked.
To do this, add a receiveLibraryConfig
:
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
receiveLibraryConfig: {
// Required Receive Library Address on BSC
receiveLibrary: "0x0000000000000000000000000000000000000000",
// Optional Grace Period for Switching Receive Library Address on BSC
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC
receiveLibraryTimeoutConfig: {
lib: "0x0000000000000000000000000000000000000000",
expiry: BigInt(0),
},
},
},
],
The Receive Library also provides two additional parameters to help future-proof OApp's for migrating MessageLib versions:
gracePeriod
: the time to wait before updating to a new MessageLib version during version migration. If the grace period is 0, it will delete the timeout configuration.expiry
: the time at which messages in-flight from the old library will be considered invalid. This is mainly for handling messages that are in-flight during the migration.
In most cases, setting the gracePeriod
to 0 will be sufficient.
Adding sendConfig
Your sendConfig
controls what DVN addresses and Executor addresses should be paid to verify and execute when a message is sent.
Each DVN and Executor contains both on-chain and off-chain component. When sending a message, you pay the DVNs and Executors contracts on the source chain, and they relay the message to the equivalent contracts on the destination chain.
For your sendConfig
, use the DVNs and Executor contract addresses on the same chain as your sending OApp.
The DVNs you use do not need to be the same on all pathways of an OApp. The DVNs just need to be the same for a given chain pathway. You could technically have one set of DVNs verifying transactions from Arbitrum to Base and Base to Arbitrum, and a separate set of DVNs verifying transactions from Arbitrum to Avalanche and Avalanche to Arbitrum.
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
// Required Receive Library Config
receiveLibraryConfig: {
// Required Receive Library Address on BSC
receiveLibrary: "0x0000000000000000000000000000000000000000",
// Optional Grace Period for Switching Receive Library Address on BSC
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC
receiveLibraryTimeoutConfig: {
lib: "0x0000000000000000000000000000000000000000",
expiry: BigInt(0),
},
// Optional Send Configuration
// @dev Controls how the `from` chain sends messages to the `to` chain.
sendConfig: {
executorConfig: {
maxMessageSize: 99,
// The configured Executor address on BSC
executor: "0x0000000000000000000000000000000000000000",
},
ulnConfig: {
// The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC).
confirmations: BigInt(42),
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until ALL `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
},
},
],
Adding receiveConfig
The receive configuration controls what DVN addresses your OApp expects to have verified the message in-flight.
For example, if BSC
is receiving messages from Sepolia
, you should use the DVN contract addresses on BSC
for each DVN provider you have in your sendConfig
.
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
// Required Receive Library Config
receiveLibraryConfig: {
// Required Receive Library Address on BSC
receiveLibrary: "0x0000000000000000000000000000000000000000",
// Optional Grace Period for Switching Receive Library Address on BSC
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC
receiveLibraryTimeoutConfig: {
lib: "0x0000000000000000000000000000000000000000",
expiry: BigInt(0),
},
// Optional Send Configuration
// @dev Controls how the `from` chain sends messages to the `to` chain.
sendConfig: {
executorConfig: {
maxMessageSize: 99,
// The configured Executor address on BSC
executor: "0x0000000000000000000000000000000000000000",
},
ulnConfig: {
// The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC).
confirmations: BigInt(42),
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until ALL `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
// Optional Receive Configuration
// @dev Controls how the `from` chain receives messages from the `to` chain.
receiveConfig: {
ulnConfig: {
// The number of block confirmations to expect from the `to` chain (Sepolia).
confirmations: BigInt(42),
// The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC).
// The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
},
},
],
Adding enforcedOptions
You can specify both a minimum destination gas and msg.value
that users must pay for both your contract's lzReceive
and `lzCompose
logic to execute as intended.
The CLI Toolkit enables you to configure your message options in a human-readable format, provided that your OApp has added an Enforced Option Message Type.
The Omnichain Fungible Token (OFT) Standard by default already has Enforced Options added to the contract, with two message types available:
// @dev execution types to handle different enforcedOptions
uint16 internal constant SEND = 1; // a standard token transfer via lzReceive
uint16 internal constant SEND_AND_CALL = 2; // a token transfer, followed by a composable call via lzCompose
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
receiveLibraryConfig: {
// Required Receive Library Address on BSC
receiveLibrary: "0x0000000000000000000000000000000000000000",
// Optional Grace Period for Switching Receive Library Address on BSC
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC
receiveLibraryTimeoutConfig: {
lib: "0x0000000000000000000000000000000000000000",
expiry: BigInt(0),
},
// Optional Send Configuration
// @dev Controls how the `from` chain sends messages to the `to` chain.
sendConfig: {
executorConfig: {
maxMessageSize: 99,
// The configured Executor address on BSC
executor: "0x0000000000000000000000000000000000000000",
},
ulnConfig: {
// The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC).
confirmations: BigInt(42),
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until ALL `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
// Optional Receive Configuration
// @dev Controls how the `from` chain receives messages from the `to` chain.
receiveConfig: {
ulnConfig: {
// The number of block confirmations to expect from the `to` chain (Sepolia).
confirmations: BigInt(42),
// The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC).
// The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
// Optional Enforced Options Configuration
// @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain.
enforcedOptions: [
{
msgType: 1,
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 60000,
value: 1,
},
{
msgType: 1,
optionType: ExecutorOptionType.NATIVE_DROP,
amount: 1,
receiver: "0x0000000000000000000000000000000000000000",
},
{
msgType: 2,
optionType: ExecutorOptionType.LZ_RECEIVE,
index: 0,
gas: 60000,
value: 1,
},
{
msgType: 2,
optionType: ExecutorOptionType.COMPOSE,
index: 1,
gas: 50000,
value: 1,
},
],
},
},
],
Review the Transaction Pricing section and the Execution Options to better understand how you should add your execution gas settings.
Final Config
Your final config should look like:
connections: [
{
// Sets the peer `from -> to`. Optional, you do not have to connect all pathways.
from: bscContract,
to: sepoliaContract,
// Optional Configuration
config: {
// Required Send Library Address on BSC
sendLibrary: "0x0000000000000000000000000000000000000000",
receiveLibraryConfig: {
// Required Receive Library Address on BSC
receiveLibrary: "0x0000000000000000000000000000000000000000",
// Optional Grace Period for Switching Receive Library Address on BSC
gracePeriod: BigInt(0),
},
// Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid on BSC
receiveLibraryTimeoutConfig: {
lib: "0x0000000000000000000000000000000000000000",
expiry: BigInt(0),
},
// Optional Send Configuration
// @dev Controls how the `from` chain sends messages to the `to` chain.
sendConfig: {
executorConfig: {
maxMessageSize: 99,
// The configured Executor address on BSC
executor: "0x0000000000000000000000000000000000000000",
},
ulnConfig: {
// The number of block confirmations to wait on BSC before emitting the message from the source chain (BSC).
confirmations: BigInt(42),
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until ALL `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the DVNs you will pay to verify a sent message on the source chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
// Optional Receive Configuration
// @dev Controls how the `from` chain receives messages from the `to` chain.
receiveConfig: {
ulnConfig: {
// The number of block confirmations to expect from the `to` chain (Sepolia).
confirmations: BigInt(42),
// The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain (BSC).
// The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
requiredDVNs: [],
// The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain (BSC).
// The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
optionalDVNs: [
"0x_POLYHEDRA_DVN_ADDRESS_ON_BSC",
"0x_LAYERZERO_DVN_ADDRESS_ON_BSC",
],
// The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
optionalDVNThreshold: 2,
},
},
// Optional Enforced Options Configuration
// @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain.
enforcedOptions: [
{
msgType: 1,
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 60000,
value: 1,
},
{
msgType: 1,
optionType: ExecutorOptionType.NATIVE_DROP,
amount: 1,
receiver: "0x0000000000000000000000000000000000000000",
},
{
msgType: 2,
optionType: ExecutorOptionType.LZ_RECEIVE,
index: 0,
gas: 60000,
value: 1,
},
{
msgType: 2,
optionType: ExecutorOptionType.COMPOSE,
index: 1,
gas: 50000,
value: 1,
},
],
},
},
],
Applying Changes
Wiring your contracts will set the peer
address for your OApp or OFT and initialize the desired configuration in your layerzero.config.ts
. If unfamiliar with this concept, review the OApp Quickstart.
The CLI Tool makes this one step easier by enabling you to wire and configure your contract pathways with a single command:
$ npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
Before wiring your contracts, you should review your layerzero.config.ts
to ensure that you have specified accurately the configuration you want to set.