EVM DVN and Executor Configuration
Before setting your DVN and Executor Configuration, you should review the Security Stack Core Concepts.
You can manually configure your EVM OApp’s Send and Receive settings by:
Reading Defaults: Use the
getConfig
method to see default configurations.Setting Libraries: Call
setSendLibrary
andsetReceiveLibrary
to choose the correct Message Library version.Setting Configs: Use the
setConfig
function to update your custom DVN and Executor settings.
For both Send and Receive configurations, make sure that for a given channel:
Send (Chain A) settings match the Receive (Chain B) settings.
DVN addresses are provided in alphabetical order.
Block confirmations are correctly set to avoid mismatches.
The LayerZero CLI has abstracted these calls for every supported chain. See the CLI Setup Guide to easily deploy, configure, and send messages using LayerZero.
Getting the Default Config
You can easily fetch and decode your OApp’s current Send/Receive settings via endpoint.getConfig(...)
. Below are two options:
- Foundry
- Ethers V5
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { ILayerZeroEndpointV2 } 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 GetConfigScript
/// @notice Retrieves and logs the current configuration for the OApp.
contract GetConfigScript is Script {
/// @notice Calls getConfig on the specified LayerZero Endpoint.
/// @dev Decodes the returned bytes as a UlnConfig. Logs some of its fields.
/// @param rpcUrl The RPC URL for the target chain.
/// @param endpoint The LayerZero Endpoint address.
/// @param oapp The address of your OApp.
/// @param lib The address of the Message Library (send or receive).
/// @param eid The remote endpoint identifier.
/// @param configType The configuration type (1 = Executor, 2 = ULN).
function getConfig(
string memory _rpcUrl,
address _endpoint,
address _oapp,
address _lib,
uint32 _eid,
uint32 _configType
) external {
// Create a fork from the specified RPC URL.
vm.createSelectFork(_rpcUrl);
vm.startBroadcast();
// Instantiate the LayerZero endpoint.
ILayerZeroEndpointV2 endpoint = ILayerZeroEndpointV2(_endpoint);
// Retrieve the raw configuration bytes.
bytes memory config = endpoint.getConfig(_oapp, _lib, _eid, _configType);
if (_configType == 1) {
// Decode the Executor config (configType = 1)
ExecutorConfig memory execConfig = abi.decode(config, (ExecutorConfig));
// Log some key configuration parameters.
console.log("Executor Type:", execConfig.maxMessageSize);
console.log("Executor Address:", execConfig.executor);
}
if (_configType == 2) {
// Decode the ULN config (configType = 2)
UlnConfig memory decodedConfig = abi.decode(config, (UlnConfig));
// Log some key configuration parameters.
console.log("Confirmations:", decodedConfig.confirmations);
console.log("Required DVN Count:", decodedConfig.requiredDVNCount);
for (uint i = 0; i < decodedConfig.requiredDVNs.length; i++) {
console.logAddress(decodedConfig.requiredDVNs[i]);
}
console.log("Optional DVN Count:", decodedConfig.optionalDVNCount);
for (uint i = 0; i < decodedConfig.optionalDVNs.length; i++) {
console.logAddress(decodedConfig.optionalDVNs[i]);
}
console.log("Optional DVN Threshold:", decodedConfig.optionalDVNThreshold);
}
vm.stopBroadcast();
}
}
import * as ethers from 'ethers';
// Define provider
const provider = new ethers.providers.JsonRpcProvider('YOUR_RPC_PROVIDER_HERE');
// Define the smart contract address and ABI
const ethereumLzEndpointAddress = '0x1a44076050125825900e736c501f859c50fE728c';
const ethereumLzEndpointABI = [
'function getConfig(address _oapp, address _lib, uint32 _eid, uint32 _configType) external view returns (bytes memory config)',
];
// Create a contract instance
const contract = new ethers.Contract(ethereumLzEndpointAddress, ethereumLzEndpointABI, provider);
// Define the addresses and parameters
const oappAddress = '0xEB6671c152C88E76fdAaBC804Bf973e3270f4c78';
const sendLibAddress = '0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1';
const receiveLibAddress = '0xc02Ab410f0734EFa3F14628780e6e695156024C2';
const remoteEid = 30102; // Example target endpoint ID, Binance Smart Chain
const executorConfigType = 1; // 1 for executor
const ulnConfigType = 2; // 2 for UlnConfig
async function getConfigAndDecode() {
try {
// Fetch and decode for sendLib (both Executor and ULN Config)
const sendExecutorConfigBytes = await contract.getConfig(
oappAddress,
sendLibAddress,
remoteEid,
executorConfigType,
);
const executorConfigAbi = ['tuple(uint32 maxMessageSize, address executorAddress)'];
const executorConfigArray = ethers.utils.defaultAbiCoder.decode(
executorConfigAbi,
sendExecutorConfigBytes,
);
console.log('Send Library Executor Config:', executorConfigArray);
const sendUlnConfigBytes = await contract.getConfig(
oappAddress,
sendLibAddress,
remoteEid,
ulnConfigType,
);
const ulnConfigStructType = [
'tuple(uint64 confirmations, uint8 requiredDVNCount, uint8 optionalDVNCount, uint8 optionalDVNThreshold, address[] requiredDVNs, address[] optionalDVNs)',
];
const sendUlnConfigArray = ethers.utils.defaultAbiCoder.decode(
ulnConfigStructType,
sendUlnConfigBytes,
);
console.log('Send Library ULN Config:', sendUlnConfigArray);
// Fetch and decode for receiveLib (only ULN Config)
const receiveUlnConfigBytes = await contract.getConfig(
oappAddress,
receiveLibAddress,
remoteEid,
ulnConfigType,
);
const receiveUlnConfigArray = ethers.utils.defaultAbiCoder.decode(
ulnConfigStructType,
receiveUlnConfigBytes,
);
console.log('Receive Library ULN Config:', receiveUlnConfigArray);
} catch (error) {
console.error('Error fetching or decoding config:', error);
}
}
// Execute the function
getConfigAndDecode();
Setting the Send and Receive Libraries
- Foundry
- Ethers V5
// 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";
contract SetLibraries is Script {
function run(
address _endpoint,
address _oapp,
uint32 _eid,
address _sendLib,
address _receiveLib,
address _signer
) external {
ILayerZeroEndpointV2 endpoint = ILayerZeroEndpointV2(_endpoint);
vm.startBroadcast(_signer);
endpoint.setSendLibrary(_oapp, _eid, _sendLib);
console.log("Send library set successfully.");
endpoint.setReceiveLibrary(_oapp, _eid, _receiveLib);
console.log("Receive library set successfully.");
vm.stopBroadcast();
}
}
const {ethers} = require('ethers');
// Replace with your actual values
const YOUR_OAPP_ADDRESS = '0xYourOAppAddress';
const YOUR_SEND_LIB_ADDRESS = '0xYourSendLibAddress';
const YOUR_RECEIVE_LIB_ADDRESS = '0xYourReceiveLibAddress';
const YOUR_ENDPOINT_CONTRACT_ADDRESS = '0xYourEndpointContractAddress';
const YOUR_RPC_URL = 'YOUR_RPC_URL';
const YOUR_PRIVATE_KEY = 'YOUR_PRIVATE_KEY';
// Define the remote EID
const remoteEid = 30101; // Replace with your actual EID
// Set up the provider and signer
const provider = new ethers.providers.JsonRpcProvider(YOUR_RPC_URL);
const signer = new ethers.Wallet(YOUR_PRIVATE_KEY, provider);
// Set up the endpoint contract
const endpointAbi = [
'function setSendLibrary(address oapp, uint32 eid, address sendLib) external',
'function setReceiveLibrary(address oapp, uint32 eid, address receiveLib) external',
];
const endpointContract = new ethers.Contract(YOUR_ENDPOINT_CONTRACT_ADDRESS, endpointAbi, signer);
async function setLibraries() {
try {
// Set the send library
const sendTx = await endpointContract.setSendLibrary(
YOUR_OAPP_ADDRESS,
remoteEid,
YOUR_SEND_LIB_ADDRESS,
);
console.log('Send library transaction sent:', sendTx.hash);
await sendTx.wait();
console.log('Send library set successfully.');
// Set the receive library
const receiveTx = await endpointContract.setReceiveLibrary(
YOUR_OAPP_ADDRESS,
remoteEid,
YOUR_RECEIVE_LIB_ADDRESS,
);
console.log('Receive library transaction sent:', receiveTx.hash);
await receiveTx.wait();
console.log('Receive library set successfully.');
} catch (error) {
console.error('Transaction failed:', error);
}
}
setLibraries();
Setting Custom Send Config (DVN & Executor)
In this example, we configure both the ULN (DVN settings) and Executor settings on the sending chain.
- Foundry
- Ethers V5
// 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
/// @notice Defines and applies ULN (DVN) + Executor configs for cross‑chain messaging 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
function run() external {
address endpoint = vm.envAddress("SOURCE_ENDPOINT_ADDRESS");
address oapp = vm.envAddress("SENDER_OAPP_ADDRESS");
uint32 eid = uint32(vm.envUint("REMOTE_EID"));
address sendLib = vm.envAddress("SEND_LIB_ADDRESS");
address signer = vm.envAddress("SIGNER");
/// @notice ULNConfig defines security parameters (DVNs + confirmation threshold)
/// @notice Send config requests these settings to be applied to the DVNs and Executor
/// @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
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
ExecutorConfig memory exec = ExecutorConfig({
maxMessageSize: 10000, // max bytes per cross-chain message
executor: address(0x3333...) // address that pays destination execution fees
});
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);
vm.stopBroadcast();
}
}
const {ethers} = require('ethers');
// Addresses
const oappAddress = 'YOUR_OAPP_ADDRESS'; // Replace with your OApp address
const sendLibAddress = 'YOUR_SEND_LIB_ADDRESS'; // Replace with your send message library address
// Configuration
// UlnConfig controls verification threshold for incoming messages
// Receive config enforces these settings have been applied to the DVNs and Executor
// 0 values will be interpretted as defaults, so to apply NIL settings, use:
// uint8 internal constant NIL_DVN_COUNT = type(uint8).max;
// uint64 internal constant NIL_CONFIRMATIONS = type(uint64).max;
const remoteEid = 30101; // Example EID, replace with the actual value
const ulnConfig = {
confirmations: 99, // Example value, replace with actual
requiredDVNCount: 2, // Example value, replace with actual
optionalDVNCount: 0, // Example value, replace with actual
optionalDVNThreshold: 0, // Example value, replace with actual
requiredDVNs: ['0xDvnAddress1', '0xDvnAddress2'], // Replace with actual addresses, must be in alphabetical order
optionalDVNs: [], // Replace with actual addresses, must be in alphabetical order
};
const executorConfig = {
maxMessageSize: 10000, // Example value, replace with actual
executorAddress: '0xExecutorAddress', // Replace with the actual executor address
};
// Provider and Signer
const provider = new ethers.providers.JsonRpcProvider(YOUR_RPC_URL);
const signer = new ethers.Wallet(YOUR_PRIVATE_KEY, provider);
// ABI and Contract
const endpointAbi = [
'function setConfig(address oappAddress, address sendLibAddress, tuple(uint32 eid, uint32 configType, bytes config)[] setConfigParams) external',
];
const endpointContract = new ethers.Contract(YOUR_ENDPOINT_CONTRACT_ADDRESS, endpointAbi, signer);
// Encode UlnConfig using defaultAbiCoder
const configTypeUlnStruct =
'tuple(uint64 confirmations, uint8 requiredDVNCount, uint8 optionalDVNCount, uint8 optionalDVNThreshold, address[] requiredDVNs, address[] optionalDVNs)';
const encodedUlnConfig = ethers.utils.defaultAbiCoder.encode([configTypeUlnStruct], [ulnConfig]);
// Encode ExecutorConfig using defaultAbiCoder
const configTypeExecutorStruct = 'tuple(uint32 maxMessageSize, address executorAddress)';
const encodedExecutorConfig = ethers.utils.defaultAbiCoder.encode(
[configTypeExecutorStruct],
[executorConfig],
);
// Define the SetConfigParam structs
const setConfigParamUln = {
eid: remoteEid,
configType: 2, // ULN_CONFIG_TYPE
config: encodedUlnConfig,
};
const setConfigParamExecutor = {
eid: remoteEid,
configType: 1, // EXECUTOR_CONFIG_TYPE
config: encodedExecutorConfig,
};
// Send the transaction
async function sendTransaction() {
try {
const tx = await endpointContract.setConfig(
oappAddress,
sendLibAddress,
[setConfigParamUln, setConfigParamExecutor], // Array of SetConfigParam structs
);
console.log('Transaction sent:', tx.hash);
const receipt = await tx.wait();
console.log('Transaction confirmed:', receipt.transactionHash);
} catch (error) {
console.error('Transaction failed:', error);
}
}
sendTransaction();
Setting Custom Receive Config (DVN Only)
On the receiving chain, only the ULN (DVN) configuration is needed since the Executor is not enforced on destination (i.e., the call can be made by anyone without permission).
This config enforces all of the configuration settings from the source chain. Ensure that the DVNs in this config object match the sender side of the channel, otherwise messages will be blocked.
Blocked messages can be caused by:
Mismatch of block confirmations: if source block confirmations are less than the destination
Mismatch of DVNs: the source DVNs do not match the threshold requirements of the destination
A mismatch will result in a config error, and in some cases can result in a loss of funds if not caught.
Since anyone can call endpoint.lzReceive(...)
for a verified LayerZero message, if you require specific execution requirements you will need to enforce them in your child contract's internal _lzReceive(...)
. See the Integration Checklist for more details.
- Foundry
- Ethers V5
// 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
/// @notice Defines and applies ULN (DVN) config for inbound message verification via LayerZero Endpoint V2.
contract SetReceiveConfig is Script {
uint32 constant RECEIVE_CONFIG_TYPE = 2;
function run() external {
address endpoint = vm.envAddress("ENDPOINT_ADDRESS");
address oapp = vm.envAddress("OAPP_ADDRESS");
uint32 eid = uint32(vm.envUint("REMOTE_EID"));
address receiveLib= vm.envAddress("RECEIVE_LIB_ADDRESS");
address signer = vm.envAddress("SIGNER");
/// @notice UlnConfig controls verification threshold for incoming messages
/// @notice Receive config enforces these settings have been applied to the DVNs and Executor
/// @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
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);
vm.stopBroadcast();
}
}
const {ethers} = require('ethers');
// Addresses
const oappAddress = 'YOUR_OAPP_ADDRESS'; // Replace with your OApp address
const receiveLibAddress = 'YOUR_RECEIVE_LIB_ADDRESS'; // Replace with your receive message library address
// Configuration
const remoteEid = 30101; // Example EID, replace with the actual value
const ulnConfig = {
confirmations: 99, // Example value, replace with actual
requiredDVNCount: 2, // Example value, replace with actual
optionalDVNCount: 0, // Example value, replace with actual
optionalDVNThreshold: 0, // Example value, replace with actual
requiredDVNs: ['0xDvnAddress1', '0xDvnAddress2'], // Replace with actual addresses, must be in alphabetical order
optionalDVNs: [], // Replace with actual addresses, must be in alphabetical order
};
// Provider and Signer
const provider = new ethers.providers.JsonRpcProvider(YOUR_RPC_URL);
const signer = new ethers.Wallet(YOUR_PRIVATE_KEY, provider);
// ABI and Contract
const endpointAbi = [
'function setConfig(address oappAddress, address receiveLibAddress, tuple(uint32 eid, uint32 configType, bytes config)[] setConfigParams) external',
];
const endpointContract = new ethers.Contract(YOUR_ENDPOINT_CONTRACT_ADDRESS, endpointAbi, signer);
// Encode UlnConfig using defaultAbiCoder
const configTypeUlnStruct =
'tuple(uint64 confirmations, uint8 requiredDVNCount, uint8 optionalDVNCount, uint8 optionalDVNThreshold, address[] requiredDVNs, address[] optionalDVNs)';
const encodedUlnConfig = ethers.utils.defaultAbiCoder.encode([configTypeUlnStruct], [ulnConfig]);
// Define the SetConfigParam struct
const setConfigParam = {
eid: remoteEid,
configType: 2, // RECEIVE_CONFIG_TYPE
config: encodedUlnConfig,
};
// Send the transaction
async function sendTransaction() {
try {
const tx = await endpointContract.setConfig(
oappAddress,
receiveLibAddress,
[setConfigParam], // This should be an array of SetConfigParam structs
);
console.log('Transaction sent:', tx.hash);
const receipt = await tx.wait();
console.log('Transaction confirmed:', receipt.transactionHash);
} catch (error) {
console.error('Transaction failed:', error);
}
}
sendTransaction();
Debugging Configurations
A correct OApp configuration example:
SendUlnConfig (A to B) | ReceiveUlnConfig (B to A) |
---|---|
confirmations: 15 | confirmations: 15 |
optionalDVNCount: 0 | optionalDVNCount: 0 |
optionalDVNThreshold: 0 | optionalDVNThreshold: 0 |
optionalDVNs: Array(0) | optionalDVNs: Array(0) |
requiredDVNCount: 2 | requiredDVNCount: 2 |
requiredDVNs: Array(DVN1_Address_A, DVN2_Address_A) | requiredDVNs: Array(DVN1_Address_B, DVN2_Address_B) |
The sending OApp's SendLibConfig (OApp on Chain A) and the receiving OApp's ReceiveLibConfig (OApp on Chain B) match!
Block Confirmation Mismatch
An example of an incorrect OApp configuration:
SendUlnConfig (A to B) | ReceiveUlnConfig (B to A) |
---|---|
confirmations: 5 | confirmations: 15 |
optionalDVNCount: 0 | optionalDVNCount: 0 |
optionalDVNThreshold: 0 | optionalDVNThreshold: 0 |
optionalDVNs: Array(0) | optionalDVNs: Array(0) |
requiredDVNCount: 2 | requiredDVNCount: 2 |
requiredDVNs: Array(DVN1, DVN2) | requiredDVNs: Array(DVN1, DVN2) |
The above configuration has a block confirmation mismatch. The sending OApp (Chain A) will only wait 5 block confirmations, but the receiving OApp (Chain B) will not accept any message with less than 15 block confirmations.
Messages will be blocked until either the sending OApp has increased the outbound block confirmations, or the receiving OApp decreases the inbound block confirmation threshold.
DVN Mismatch
Another example of an incorrect OApp configuration:
SendUlnConfig (A to B) | ReceiveUlnConfig (B to A) |
---|---|
confirmations: 15 | confirmations: 15 |
optionalDVNCount: 0 | optionalDVNCount: 0 |
optionalDVNThreshold: 0 | optionalDVNThreshold: 0 |
optionalDVNs: Array(0) | optionalDVNs: Array(0) |
requiredDVNCount: 1 | requiredDVNCount: 2 |
requiredDVNs: Array(DVN1) | requiredDVNs: Array(DVN1, DVN2) |
The above configuration has a DVN mismatch. The sending OApp (Chain A) only pays DVN 1 to listen and verify the packet, but the receiving OApp (Chain B) requires both DVN 1 and DVN 2 to mark the packet as verified.
Messages will be blocked until either the sending OApp has added DVN 2's address on Chain A to the SendUlnConfig, or the receiving OApp removes DVN 2's address on Chain B from the ReceiveUlnConfig.
Dead DVN
This configuration includes a Dead DVN:
SendUlnConfig (A to B) | ReceiveUlnConfig (B to A) |
---|---|
confirmations: 15 | confirmations: 15 |
optionalDVNCount: 0 | optionalDVNCount: 0 |
optionalDVNThreshold: 0 | optionalDVNThreshold: 0 |
optionalDVNs: Array(0) | optionalDVNs: Array(0) |
requiredDVNCount: 2 | requiredDVNCount: 2 |
requiredDVNs: Array(DVN1, DVN2) | requiredDVNs: Array(DVN1, DVN_DEAD) |
The above configuration has a Dead DVN. Similar to a DVN Mismatch, the sending OApp (Chain A) pays DVN 1 and DVN 2 to listen and verify the packet, but the receiving OApp (Chain B) has currently set DVN 1 and a Dead DVN to mark the packet as verified.
Since a Dead DVN for all practical purposes should be considered a null address, no verification will ever match the dead address.
Messages will be blocked until the receiving OApp removes or replaces the Dead DVN from the ReceiveUlnConfig.
Summary
Retrieve defaults: Use
getConfig
if you need to review existing settings.Set Libraries: Choose your Message Library version by calling
setSendLibrary
andsetReceiveLibrary
.Set Configurations: Update your DVN (ULN) and Executor settings with
setConfig
.Ensure matching configurations: The Send settings on one chain must match the Receive settings on the other chain.