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

# Migrating from a Single-DVN Configuration

> Operational guide for OApps that currently use a single required DVN and need to migrate to a multi-DVN production configuration.

This guide walks an OApp that currently ships a single required DVN through the operational steps of adding a second required DVN. It is written for owners of live production deployments and assumes you cannot afford to lose in-flight messages during the migration.

<Warning>
  **Single-DVN production configurations are not safe.** A compromise of the one verifier results in unrestricted forged messages on the pathway. Plan to migrate every production pathway you operate. See [Production DVN Configuration](/v2/concepts/modular-security/production-dvn-configuration) for the full security rationale and target configuration tiers.
</Warning>

## Before you start

You need to know, for **every pathway** in your mesh:

1. **Both endpoint IDs** — source and destination chain.
2. **Your OApp address** on each chain.
3. **The Send Library** address used on the source chain (`getSendLibrary`).
4. **The Receive Library** address used on the destination chain (`getReceiveLibrary`).
5. **The current DVN configuration** on each side (`getConfig` for both `sendConfig` and `receiveConfig`). Capture the exact `requiredDVNCount`, `requiredDVNs`, `optionalDVNCount`, `optionalDVNThreshold`, `optionalDVNs`, and `confirmations`.
6. **Available DVNs on each chain** (see [DVN Addresses](/v2/deployments/dvn-addresses)). The DVN you choose as your secondary must be deployed on **both** the source chain and the destination chain.

You can capture (5) with the script in [EVM DVN and Executor Configuration → Getting the Default Config](/v2/developers/evm/configuration/dvn-executor-config) (or the equivalent VM-specific page).

<Info>
  If a pathway you operate has only one DVN currently deployed on one of the two chains, you cannot migrate to multi-DVN yet on that pathway. Your options are:

  * Run your own DVN (see [Build a DVN](/v2/workers/off-chain/build-dvns)).
  * Wait until a third-party provider deploys (track on [DVN Addresses](/v2/deployments/dvn-addresses)).
  * Defer the chain until multi-DVN coverage exists.
</Info>

## How DVN configuration applies to in-flight messages

This is the most-misunderstood part of a migration. Read it twice.

A pathway from Chain A → Chain B has two configurations that must agree:

* **Chain A `sendConfig`** controls which DVNs the source library *pays* to verify each outbound message.
* **Chain B `receiveConfig`** controls which DVNs the destination library *requires* to have verified before delivery.

When a message is sent, only the DVNs in Chain A's `sendConfig` at that moment are notified and paid. They produce attestations that flow to Chain B. Delivery succeeds only if the DVNs in Chain B's `receiveConfig` at the moment of delivery have all attested.

Implications for migration:

* If you change Chain B's `receiveConfig` to require both `[LZLabs, DVN2]` **before** Chain A's `sendConfig` includes `DVN2`, every in-flight message and every new message stalls. `DVN2` never witnessed those send events, so `DVN2`'s attestation never appears.
* If you change Chain A's `sendConfig` to `[LZLabs, DVN2]` **first**, then Chain A pays both DVNs to attest from that moment forward. Chain B's `receiveConfig` still only requires `LZLabs`, so messages still deliver. `DVN2` builds up attestations in the background.
* Once you can confirm `DVN2` is attesting reliably for the pathway, update Chain B's `receiveConfig` to require both. New messages from that point will require both attestations; in-flight messages sent before the `sendConfig` update will fail (because `DVN2` did not see them) and need to be re-sent or recovered manually.

The safe migration is therefore **send-side first, drain in-flight, then receive-side**.

## Migration sequence

The sequence below assumes a single bidirectional pathway A↔B. For larger meshes, repeat the entire sequence for each direction of each pathway. Update both directions of a pathway in a single maintenance window where possible.

### Step 1 — Choose your secondary DVN

Pick a DVN that is:

* Deployed on **both** Chain A and Chain B (verify in [DVN Addresses](/v2/deployments/dvn-addresses)).
* **Actively attesting the specific source-chain → destination-chain pathway**, not just present at the contract level. Provider deployment ≠ active pathway coverage; confirm with the provider directly or check the LayerZero metadata API for the pathway pair before committing.
* Using a **different verification method** where possible (different node infrastructure, different RPC providers, ideally a different verification proof type).

If only LayerZero Labs and one other provider are available on a pathway, that pair is your only multi-DVN option until a third provider deploys.

Record the DVN's address on each chain (they will differ — DVN providers deploy a separate contract per chain).

### Step 2 — Pause sends if you can

If your OApp supports a governance pause, **pause sends on Chain A** at the start of the migration window. This stops adding to the in-flight queue and lets the existing queue drain cleanly during Step 4, preventing the post-migration in-flight failure case. If you do not have a pause primitive, proceed to Step 3 and accept that any messages still in flight when Step 5 lands may need manual recovery — specifically, messages sent before Step 3 that have not yet been delivered when Chain B's `receiveConfig` is updated.

### Step 3 — Update Chain A's `sendConfig`

Submit a `setConfig` transaction on Chain A to set the new `UlnConfig` with `requiredDVNCount: 2`, `requiredDVNs: [LZLabs, DVN2]` (sorted in ascending address order, as required by the contract).

Verify with `getConfig` that the new value is on-chain.

<Warning>
  DVN addresses in `requiredDVNs` must be **sorted in ascending address order**. The contract reverts on unsorted or duplicate arrays. Sort before encoding.
</Warning>

### Step 4 — Drain in-flight

Wait for at least `confirmations` × source-chain block time + a buffer (recommend 2× the typical end-to-end delivery time). The relevant boundary is **Step 3** (when `sendConfig` changed), not this step:

* Messages sent **before Step 3** are attested only by DVN1, so they must complete delivery before Step 5 lands. The wait in this step ensures pre-Step-3 stragglers are not orphaned by the receive-side update.
* Messages sent **after Step 3** are attested by both DVNs and will pass the new `receiveConfig` requirement once it lands.

### Step 5 — Update Chain B's `receiveConfig`

Submit a `setConfig` transaction on Chain B to set the new `UlnConfig` with the matching `requiredDVNCount: 2` and matching DVNs (each chain has its own DVN address — make sure you use Chain B's deployment of the secondary DVN, not Chain A's).

Verify with `getConfig` that the new value is on-chain.

### Step 6 — Verify end-to-end

Send a small, low-value test message through the pathway. Confirm via [LayerZero Scan](https://layerzeroscan.com) that:

* Both DVNs attested.
* The message was committed and executed on Chain B.
* The committed config matches the new `requiredDVNs` set.

### Step 7 — Resume sends and post-confirm

Unpause sends if you paused in Step 2. Watch [LayerZero Scan](https://layerzeroscan.com) for delivery failures on the migrated pathway for at least 24 hours after the last pathway in your mesh completes Step 6.

### Step 8 — Repeat for the reverse direction

Apply Steps 3–7 with A and B swapped. A pathway is only as secure as its weakest direction; an attacker who can forge B → A while A → B is hardened still wins.

## Mesh-wide considerations

If you operate `N` chains in a mesh, you have `N × (N-1)` directional pathways. Three strategies, in order of operational complexity:

### Round-robin per pathway

Migrate one pathway at a time. Lowest-risk operationally — each pathway's migration is fully completed and verified before the next begins. High overhead for large meshes.

### Per-source-chain batch

Update all `sendConfig`s on Chain A in one transaction; wait for the drain window; then update all `receiveConfig`s on the partner chains. Lower overhead than round-robin, but requires careful coordination across the partner chains.

### Two-phase mesh

Set every `sendConfig` mesh-wide first; wait one drain window; then set every `receiveConfig` mesh-wide. Lowest user disruption if your governance can sequence the transactions reliably. For a contiguous mesh of more than 5 chains, this approach is generally preferred.

## Coordinating with peer-chain owners

If your OApp is jointly operated with another team (for example, an OFT bridged to a chain whose OApp deployment is owned by a partner), **both teams must update both sides**. You cannot unilaterally migrate a pathway whose receive side is owned by another team. Contact peer-chain owners with the new `UlnConfig` you intend to set and the maintenance window before you submit Step 3.

## Rollback

If you discover an issue **before Step 5 lands** (only `sendConfig` has been updated), roll back Chain A's `sendConfig` to the original value. The pathway returns to its original 1-of-1 state. Messages sent between Step 3 and the rollback are over-attested by DVN2 but Chain B's `receiveConfig` only requires DVN1, so they deliver normally — no manual recovery needed.

If you discover an issue **after Step 5 lands** (both `sendConfig` and `receiveConfig` updated, e.g. Step 6 verification fails), roll back **receive-side first**: drop Chain B's `receiveConfig` back to require only DVN1, then roll back Chain A's `sendConfig`. The intermediate state — Chain A producing both attestations while Chain B requires only one — works normally; outbound messages continue to deliver. The reverse order (rolling back `sendConfig` first while `receiveConfig` still requires both DVNs) leaves Chain A producing only DVN1 attestation, which Chain B will reject, blocking all messages until you complete the receive-side rollback.

## Verification checklist (after every pathway migration)

* [ ] `getConfig(oApp, sendLib, dstEid, configType=2)` on the source chain returns `requiredDVNCount: 2` (or higher) with the intended DVN addresses.
* [ ] `getConfig(oApp, receiveLib, srcEid, configType=2)` on the destination chain returns the matching configuration.
* [ ] `confirmations` matches both sides.
* [ ] DVN addresses are sorted in ascending order on both sides.
* [ ] A test message has been sent and delivered with both DVN attestations visible on LayerZero Scan.
* [ ] On-chain `getConfig` returns a configuration distinct from the protocol default shown by the [Default Config Checker](https://layerzeroscan.com/tools/defaults).
* [ ] You have repeated the above for the reverse direction.

## See also

* [Production DVN Configuration](/v2/concepts/modular-security/production-dvn-configuration) — risk tiers and target configurations
* [Integration Checklist](/v2/tools/integration-checklist) — pre-launch gate
* [Security Stack (DVNs)](/v2/concepts/modular-security/security-stack-dvns) — concept reference
* [DVN Addresses](/v2/deployments/dvn-addresses) — available providers per chain
* [Default Config Checker](https://layerzeroscan.com/tools/defaults) — verify resolved config per pathway
