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

# Aptos DVN and Executor Configuration

> Configure Aptos DVN and Executor Configuration for your LayerZero application. Set up DVNs, executors, and pathway settings for crosschain messaging.

Before setting your DVN and Executor Configuration, you should review the [Security Stack Core Concepts](../../../concepts/modular-security/security-stack-dvns).

<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](../../../tools/integration-checklist#set-security-and-executor-configurations-on-every-pathway) for production DVN guidance.
</Warning>

You can manually configure your Aptos Move 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](../../../concepts/glossary#channel--lossless-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.

<Tip>
  ### Use the LayerZero CLI

  The LayerZero CLI has abstracted these calls for every supported chain. See the [**CLI Setup Guide**](../../../get-started/create-lz-oapp/start) to easily deploy, configure, and send messages using LayerZero.
</Tip>

## Setting Send / Receive Libraries

In Aptos, you call the [**`endpoint_v2::endpoint`** module’s](#endpointv2endpoint-key-functions) **entry** or **friend** functions to pick the library you want for **sending** or **receiving** messages.

A typical library in current Aptos V2 is the ULN 302 library (`uln_302::msglib`). If you do **not** call `set_send_library` or `set_receive_library`, your OApp falls back to the **default** library for that remote EID.

**Note**: The Endpoint has built-in constraints:

1. **`dst_eid`** in `set_send_library(...)` must be valid for that library.

2. **`src_eid`** in `set_receive_library(...)` must be valid for that library (i.e., the library says it supports receiving from that chain).

When you set a new library, the old library is replaced. You can optionally specify a **grace\_period** on the receive side so the old library can continue verifying messages for a set time. This is how you “roll over” from one library version to another.

### Typescript

Below is an example of how you might call the `endpoint_v2::endpoint::set_send_library` or `endpoint_v2::endpoint::set_receive_library` function using the Aptos JS SDK.

```typescript wrap theme={null}
import {
  Account,
  Aptos,
  Ed25519PrivateKey,
  PrivateKey,
  PrivateKeyVariants,
  SimpleTransaction,
  InputEntryFunctionData,
  AptosConfig,
} from '@aptos-labs/ts-sdk';

const NODE_URL = 'https://fullnode.testnet.aptoslabs.com/v1';

// Replace with your actual private key or create from a local mnemonic
const ADMIN_PRIVATE_KEY_HEX = '0x...';
const ADMIN_ACCOUNT_ADDRESS = '0x...';

// OApp data
const OAPP_ADDRESS = '0xMyOApp'; // your OApp’s address on Aptos
const REMOTE_EID = 30101; // e.g. the remote chain’s EID
const MSGLIB_ADDRESS = '0xULN302'; // The “Send” library you want
const NETWORK = 'testnet'; // "testnet" or "mainnet"

// Create the private key
const aptos_private_key = PrivateKey.formatPrivateKey(
  ADMIN_PRIVATE_KEY_HEX,
  PrivateKeyVariants.Ed25519,
);

// Create the signer account
const signer_account = Account.fromPrivateKey({
  privateKey: new Ed25519PrivateKey(aptos_private_key),
  address: ADMIN_ACCOUNT_ADDRESS,
});

// Create the Aptos client
const aptos = new Aptos(new AptosConfig({network: NETWORK}));
```

The function signature in your OApp might look like:

```rust wrap theme={null}
public entry fun set_send_library(
    account: &signer,
    remote_eid: u32,
    msglib: address,
) { ... }
```

Which can be invoked like:

```typescript wrap theme={null}
async function setSendLibrary() {
  // 1. Build the transaction payload and transaction
  const payload: InputEntryFunctionData = {
    function: `${OAPP_ADDRESS}::oapp_core::set_send_library`,
    functionArguments: [REMOTE_EID, MSGLIB_ADDRESS],
  };

  const transaction: SimpleTransaction = await aptos.transaction.build.simple({
    sender: ADMIN_ACCOUNT_ADDRESS,
    data: payload,
    options: {
      maxGasAmount: 30000,
    },
  });

  // 2. Generate and sign transaction
  const signedTransaction = await aptos.signAndSubmitTransaction({
    signer: signer_account,
    transaction: transaction,
  });

  // 3. Wait for confirmation
  const executedTransaction = await aptos.waitForTransaction({
    transactionHash: signedTransaction.hash,
  });

  console.log('set_send_library transaction completed:', executedTransaction.hash);
}

setSendLibrary()
  .then(() => {
    console.log('Done setting send library');
  })
  .catch(console.error);
```

## Setting Security & Executor Configuration

A similar approach to EVM’s `setConfig` is available in Aptos. You can call:

```rust wrap theme={null}
public entry fun set_config(
    account: &signer,
    msglib: address,
    eid: u32,
    config_type: u32,
    config: vector<u8>,
) {
    assert_authorized(address_of(account));
    endpoint::set_config(&oapp_store::call_ref(), msglib, eid, config_type, config);
}
```

* **`msglib`** is the library you are configuring (e.g. `@uln_302`).

* **`eid`** is the remote endpoint ID you are targeting (e.g. `30101` if referencing “Chain B’s ID”).

* **`config_type`** is typically 1 for **Executor** and 2 or 3 for ULN-based “send” or “receive” config.

* **`config`** is a serialized bytes array containing your DVN addresses, confirmations, or max message size, etc.

### Typical ULN & Executor Structures

The `uln_302::configuration` module references these data structures:

#### ULN Config (Security Stack)

```rust wrap theme={null}
struct UlnConfig has copy, drop {
    confirmations: u64,
    optional_dvn_threshold: u8,
    required_dvns: vector<address>,
    optional_dvns: vector<address>,
    use_default_for_confirmations: bool,
    use_default_for_required_dvns: bool,
    use_default_for_optional_dvns: bool,
}
```

* `confirmations`: how many blocks to wait on the source chain for finality.

* `required_dvns`: the DVNs that **must** sign your message.

* `optional_dvns`: the DVNs that **may** sign your message if they reach the threshold.

* `optional_dvn_threshold`: how many optional DVNs are needed if you have optional DVNs.

* `use_default_for_*`: determines if we fallback to a default config for certain fields.

In EVM you’d see fields like `requiredDVNCount`, `requiredDVNs`, `optionalDVNCount`, etc. In Aptos, it’s stored as a single struct with arrays for addresses.

#### Executor Config

```rust wrap theme={null}
struct ExecutorConfig has copy, drop {
    max_message_size: u32,
    executor_address: address,
}
```

* `max_message_size`: max size of crosschain messages, in bytes.

* `executor_address`: which executor is authorized/paid to `lz_receive` your message.

#### Distinction vs. EVM

Where EVM calls `setConfigParam[]`, on Aptos, we pass a single `(config_type, config)` each time. If you want to set both Executor and ULN in one go, call `set_config` with each config type. Some developers write a convenience function to do both in a single transaction.

The `uln_302::configuration` module handles the actual decode:

* **`CONFIG_TYPE_EXECUTOR = 1`**
* **`CONFIG_TYPE_SEND_ULN = 2`**
* **`CONFIG_TYPE_RECV_ULN = 3`**

It extracts your config bytes, e.g. `extract_uln_config` for a ULN struct or `extract_executor_config` for an executor struct.

**Example**: Setting a “send side” ULN config might look like:

```ts wrap theme={null}
async function setUlnConfig(sendLibrary: string, remoteEid: number, serializedConfig: Uint8Array) {
  // config_type = 2 for "send side" or 3 for "receive side"
  const CONFIG_TYPE_SEND_ULN = 2;

  // Suppose your OApp entry function is:
  // public entry fun set_config(account: &signer, msglib: address, eid: u32, config_type: u32, config: vector<u8>)
  const payload: InputEntryFunctionData = {
    function: `${OAPP_ADDRESS}::oapp_core::set_config`,
    functionArguments: [sendLibrary, remoteEid, CONFIG_TYPE_SEND_ULN, serializedConfig],
  };

  const rawTransaction = await aptos.transaction.build.simple({
    sender: ADMIN_ACCOUNT_ADDRESS,
    data: payload,
    options: {
      maxGasAmount: 30000,
    },
  });

  const signedTransaction = await aptos.signAndSubmitTransaction({
    signer: signer_account,
    transaction: rawTransaction,
  });

  const executedTransaction = await aptos.waitForTransaction({
    transactionHash: signedTransaction.hash,
  });

  console.log(`set_config ULN success: ${signedTransaction.hash}`);
}
```

The Executor config is `CONFIG_TYPE_EXECUTOR = 1`. You pass a serialized `(max_message_size, executor_address)` structure.

```ts wrap theme={null}
async function setExecutorConfig(
  sendLibrary: string,
  remoteEid: number,
  execConfigBytes: Uint8Array,
) {
  // config_type = 1 for "executor"
  const CONFIG_TYPE_EXECUTOR = 1;

  const payload: InputEntryFunctionData = {
    function: `${OAPP_ADDRESS}::oapp_core::set_config`,
    functionArguments: [sendLibrary, remoteEid, CONFIG_TYPE_EXECUTOR, execConfigBytes],
  };

  const rawTransaction = await aptos.transaction.build.simple({
    sender: ADMIN_ACCOUNT_ADDRESS,
    data: payload,
    options: {
      maxGasAmount: 30000,
    },
  });

  const signedTransaction = await aptos.signAndSubmitTransaction({
    signer: signer_account,
    transaction: rawTransaction,
  });

  const executedTransaction = await aptos.waitForTransaction({
    transactionHash: signedTransaction.hash,
  });

  console.log(`set_config Executor success: ${signedTransaction.hash}`);
}
```

## Resetting to Default

If you pass a config that sets fields like `confirmations = 0`, `required_dvns = []`, and sets `use_default_for_confirmations = true`, then the OApp will fallback to whatever the default is on that chain.

Similarly, if you pass an `ExecutorConfig` with `max_message_size = 0` and `executor_address = @0x0`, you revert to default. The `uln_302::configuration` module merges your OApp’s config with the chain’s default config if you set `use_default_for_* = true`.

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

<Tip>
  The sending OApp's **SendLibConfig** (OApp on Chain A) and the receiving OApp's **ReceiveLibConfig** (OApp on Chain B) match!
</Tip>

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

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

#### 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)** |

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

#### [Dead DVN](../../../concepts/glossary#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)** |

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

## Key Functions in `endpoint_v2::endpoint`

Below are the main wiring functions used for configuration.

They typically are invoked in your OApp’s admin or delegate entry function.

* **`register_receive_pathway(call_ref, src_eid, sender_bytes32)`**: Inform the endpoint that you accept messages from `(src_eid, sender)`.

* **`set_send_library(call_ref, remote_eid, msglib)`**: Tells the endpoint which library to use for sending messages to `remote_eid`.

* **`set_receive_library(call_ref, remote_eid, msglib, grace_period)`**: Tells the endpoint which library to use for receiving messages from `remote_eid`. Optionally specify a `grace_period` in blocks.

* **`set_config(call_ref, msglib, eid, config_type, config_bytes)`**: Instruct the chosen library to store or merge your OApp’s custom config for that EID.

## Conclusion

The **Aptos V2** Endpoint wiring parallels the approach on EVM:

* **Choose your libraries** for sending and receiving (`set_send_library`, `set_receive_library`).

* **Set your ULN or Executor configs** via `set_config` on the chosen library’s address, specifying the remote EID.

* Ensure your sending chain’s config aligns with the receiving chain’s config (DVNs, block confirmations, etc.), or your messages may be blocked
  .

* If you want to revert to defaults, pass a config that indicates `use_default_for_* = true` or sets addresses to `@0x0`.

By following these steps, you can precisely control the **LayerZero V2** security stack (DVNs), block confirmations, and executor settings on Aptos—just as you would with the EVM-based `setSendLibrary`, `setReceiveLibrary`, and `setConfig` flow.
