Skip to main content
Version: Endpoint V2 Docs

Solana DVN and Executor Configuration

Before setting your DVN and Executor Configuration, you should review the Security Stack Core Concepts.

You can manually configure your Solana OApp’s Send and Receive settings by:

  • Reading Defaults: Use the get_config method to see default configurations.

  • Setting Libraries: Call set_send_library and set_receive_library to choose the correct Message Library version.

  • Setting Configs: Use the set_config instruction 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.

Use the LayerZero CLI

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

If you had set up your project using the LayerZero CLI, run the following to view the default configs:

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

Alternatively, you can also retrieve it via the following script.

import {UlnProgram} from '@layerzerolabs/lz-solana-sdk-v2';
import {Connection} from '@solana/web3.js';

const connection = new Connection('https://api.devnet.solana.com'); // replace with the desired Solana cluster's RPC URL

const uln: UlnProgram.Uln = new UlnProgram.Uln(UlnProgram.PROGRAM_ID);
const defaultSendConfig = await uln.getDefaultSendConfigState(connection, dstEid);
const defaultReceiveConfig = await uln.getDefaultReceiveConfigState(connection, dstEid);
console.log({
defaultSendConfig,
defaultReceiveConfig,
});

The script will return both the default SendLib and ReceiveLib configurations. In the SendLib is also the executor address.

{
defaultSendConfig: _SendConfig {
bump: 255,
uln: {
confirmations: <BN: a>,
requiredDvnCount: 1,
optionalDvnCount: 0,
optionalDvnThreshold: 0,
requiredDvns: [Array],
optionalDvns: []
},
executor: {
maxMessageSize: 10000,
executor: [PublicKey [PublicKey(AwrbHeCyniXaQhiJZkLhgWdUCteeWSGaSN1sTfLiY7xK)]]
}
},
defaultReceiveConfig: _ReceiveConfig {
bump: 255,
uln: {
confirmations: <BN: 2>,
requiredDvnCount: 1,
optionalDvnCount: 0,
optionalDvnThreshold: 0,
requiredDvns: [Array],
optionalDvns: []
}
}
}
info

The important takeaway is that every LayerZero Endpoint can be used to send and receive messages. Because of that, each Endpoint has a separate Send and Receive Configuration, which an OApp can configure by the target destination Endpoint.

In the above example, the default Send Library configurations control how messages emit from the Solana Endpoint to the BNB Endpoint.

The default Receive Library configurations control how the Solana Endpoint filters received messages from the BNB Endpoint.

For a configuration to be considered correct, the Send Library configurations on Chain A must match Chain B's Receive Library configurations for filtering messages.

Challenge: Confirm that the Solana Endpoint's Send Library ULN configuration matches the Ethereum Endpoint's Receive Library ULN Configuration using the methods above.

Custom Configuration

LayerZero CLI

tip

The create-lz-oapp (LayerZero CLI) npx package is the recommended way to start and maintain your project. For EVM and Solana projects, you will not need to write any custom scripting in order to view or set your OApp's configs.

For projects created using the LayerZero CLI, all custom configurations are managed via the LZ Config file (typically named layerzero.config.ts). You would modify the values in the LZ Config file and then run the wire command:

npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --solana-eid <SOLANA_ENDPOINT_ID>

The wire command would take care of preparing and submitting all transactions required to apply your configurations. It goes through each pathway and will submit transactions to each chain in your mesh. Regardless of how many pathways you have, you will only need to run the wire command once.

We recommmend you to use the LayerZero CLI unless you have a custom use case that is not supported by it.

Debugging Configurations

A correct OApp configuration example:

SendUlnConfig (A to B)ReceiveUlnConfig (B to A)
confirmations: 15confirmations: 15
optionalDVNCount: 0optionalDVNCount: 0
optionalDVNThreshold: 0optionalDVNThreshold: 0
optionalDVNs: Array(0)optionalDVNs: Array(0)
requiredDVNCount: 2requiredDVNCount: 2
requiredDVNs: Array(DVN1_Address_A, DVN2_Address_A)requiredDVNs: Array(DVN1_Address_B, DVN2_Address_B)
tip

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: 5confirmations: 15
optionalDVNCount: 0optionalDVNCount: 0
optionalDVNThreshold: 0optionalDVNThreshold: 0
optionalDVNs: Array(0)optionalDVNs: Array(0)
requiredDVNCount: 2requiredDVNCount: 2
requiredDVNs: Array(DVN1, DVN2)requiredDVNs: Array(DVN1, DVN2)
danger

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: 15confirmations: 15
optionalDVNCount: 0optionalDVNCount: 0
optionalDVNThreshold: 0optionalDVNThreshold: 0
optionalDVNs: Array(0)optionalDVNs: Array(0)
requiredDVNCount: 1requiredDVNCount: 2
requiredDVNs: Array(DVN1)requiredDVNs: Array(DVN1, DVN2)
danger

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: 15confirmations: 15
optionalDVNCount: 0optionalDVNCount: 0
optionalDVNThreshold: 0optionalDVNThreshold: 0
optionalDVNs: Array(0)optionalDVNs: Array(0)
requiredDVNCount: 2requiredDVNCount: 2
requiredDVNs: Array(DVN1, DVN2)requiredDVNs: Array(DVN1, DVN_DEAD)
danger

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.