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

# DVN and Executor Configuration

> Step-by-step guide to dvn and executor configuration using LayerZero V2. Build and deploy omnichain applications with crosschain messaging. Follow step-by-s...

This guide explains how to configure Decentralized Verifier Networks (DVNs), Executors, and message libraries for your IOTA OApp or OFT using the SDK.

<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. The examples on this page show the LayerZero Labs DVN with a `<SECONDARY_PROVIDER>` placeholder so the snippet does not silently model a single-DVN production setup. Replace `<SECONDARY_PROVIDER>` with a real non-LayerZero-Labs provider — see [DVN Addresses](/v2/deployments/dvn-addresses) for available providers per chain. See the [Integration Checklist](/v2/tools/integration-checklist#set-security-and-executor-configurations-on-every-pathway) for production DVN guidance.
</Warning>

## Overview

Configuration is done through the OApp SDK instance. All examples use `sdk.getOApp(packageId)` to get the OApp instance, then call SDK methods for configuration.

**Configuration flow**:

1. Set message libraries (optional)
2. Configure DVNs for send/receive (optional but recommended)
3. Set enforced options (optional)
4. Set peer addresses (required - opens pathway, call last!)

## SDK Setup

```typescript wrap theme={null}
import {SDK} from '@layerzerolabs/lz-iotal1-sdk-v2';
import {Stage} from '@layerzerolabs/lz-definitions';

const sdk = new SDK({client, stage: Stage.MAINNET});
const oapp = sdk.getOApp(yourPackageId); // Always use SDK factory
```

## Configuration Methods

### Set Peer

```typescript wrap theme={null}
// Configure peer for destination chain
await oapp.setPeerMoveCall(tx, dstEid, peerBytes32);
```

**Address format**: Use package ID for IOTA peers, 32-byte address for other chains.

### Set Message Libraries

```typescript wrap theme={null}
// Set custom send library
await oapp.setSendLibraryMoveCall(tx, dstEid, libraryAddress);

// Set custom receive library
await oapp.setReceiveLibraryMoveCall(tx, srcEid, libraryAddress, gracePeriod);
```

**Default**: Uses Endpoint defaults if not configured.

## DVN Configuration

### Configure Receive DVN (Inbound)

```typescript wrap theme={null}
import {
  SDK,
  OAppUlnConfigBcs,
  PACKAGE_ULN_302_ADDRESS,
  OBJECT_ULN_302_ADDRESS,
  PACKAGE_DVN_LAYERZERO_ADDRESS,
} from '@layerzerolabs/lz-iotal1-sdk-v2';
import {Stage} from '@layerzerolabs/lz-definitions';

const sdk = new SDK({client, stage: Stage.MAINNET});
const oapp = sdk.getOApp(yourPackageId);

// Encode configuration
const config = OAppUlnConfigBcs.serialize({
  use_default_confirmations: false,
  use_default_required_dvns: false,
  use_default_optional_dvns: true,
  uln_config: {
    confirmations: 15,
    // Replace <SECONDARY_PROVIDER> with a non-LayerZero-Labs DVN; see /v2/deployments/dvn-addresses
    required_dvns: [
      PACKAGE_DVN_LAYERZERO_ADDRESS[Stage.MAINNET],
      PACKAGE_DVN_<SECONDARY_PROVIDER>_ADDRESS[Stage.MAINNET],
    ],
    optional_dvns: [],
    optional_dvn_threshold: 0,
  },
}).toBytes();

// Two-step Call pattern
const tx = new Transaction();
const configCall = await oapp.setConfigMoveCall(
  tx,
  PACKAGE_ULN_302_ADDRESS[Stage.MAINNET],
  30184, // Remote EID
  3, // CONFIG_TYPE_RECEIVE_ULN
  config,
);

tx.moveCall({
  target: `${PACKAGE_ULN_302_ADDRESS[Stage.MAINNET]}::uln_302::set_config`,
  arguments: [tx.object(OBJECT_ULN_302_ADDRESS[Stage.MAINNET]), configCall],
});

await client.signAndExecuteTransaction({transaction: tx, signer: keypair});
```

### Configure Send DVN (Outbound)

```typescript wrap theme={null}
// Same pattern, use CONFIG_TYPE_SEND_ULN = 2
const configCall = await oapp.setConfigMoveCall(
  tx,
  PACKAGE_ULN_302_ADDRESS[Stage.MAINNET],
  30184,
  2, // CONFIG_TYPE_SEND_ULN
  config,
);
```

**Config types**: `1` = Executor, `2` = Send ULN, `3` = Receive ULN

**DVN addresses**: Use `PACKAGE_DVN_LAYERZERO_ADDRESS[Stage.MAINNET]` or see [Deployed Contracts](/v2/deployments/chains/iota).

### Set Enforced Options

```typescript wrap theme={null}
import {Options} from '@layerzerolabs/lz-v2-utilities';

const options = Options.newOptions()
  .addExecutorLzReceiveOption(60000, 0) // Gas for destination
  .toBytes();

await oapp.setEnforcedOptionsMoveCall(tx, dstEid, msgType, options);
```

## Gas Limit Recommendations

Based on gas profiling:

### OApp/OFT Operations

| Operation    | Gas Used (IOTA) | Recommended Budget | Notes                                                                 |
| ------------ | --------------- | ------------------ | --------------------------------------------------------------------- |
| `lz_receive` | 2,000-4,172     | 3,500-5,000        | For OApps and custom business logic, this needs independent profiling |
| `oft_send`   | 4,728,620       | 6,700,000          | Includes endpoint + ULN                                               |
| `dvn_verify` | 5,684,108       | 7,700,000          | Verification submission                                               |
| `dvn_commit` | 517,248         | 2,500,000          | Commit verification                                                   |

### Enforced Options Examples

For EVM destinations:

```typescript wrap theme={null}
import {Options} from '@layerzerolabs/lz-v2-utilities';

// Standard OApp message
const options = Options.newOptions()
  .addExecutorLzReceiveOption(60000, 0) // 60k gas, no msg.value
  .toBytes();

// OFT with compose
const optionsCompose = Options.newOptions()
  .addExecutorLzReceiveOption(200000, 0) // Higher for compose
  .toBytes();
```

For IOTA destinations:

```typescript wrap theme={null}
const optionsForIOTA = Options.newOptions()
  .addExecutorLzReceiveOption(5000, 0) // 5k gas units, no msg.value
  .toBytes();
```

**Note**: Based on gas profiling, IOTA `lz_receive` uses 2,000-5,000 gas units. No msg.value needed - IOTA handles storage internally.

## Common Issues

**Errors**:

* `InvalidBCSBytes` → Use `OAppUlnConfigBcs.serialize()` for DVN config
* `oapp_registry::get_messaging_channel abort code: 1` → Used object ID instead of package ID as peer
* Channel not initialized → Registration creates MessagingChannel automatically (no manual init needed)

## Next Steps

* [OApp Overview](/v2/developers/iota/oapp/overview) - Base messaging standard
* [OFT Overview](/v2/developers/iota/oft/overview) - Token standard and deployment
* [OFT SDK](/v2/developers/iota/oft/sdk) - Complete SDK methods and examples
* [Technical Overview](/v2/developers/iota/technical-overview) - IOTA fundamentals and architecture
* [Protocol Overview](/v2/developers/iota/protocol-overview) - Complete message workflows
* [Troubleshooting](/v2/developers/iota/troubleshooting/common-errors) - Common configuration issues
