Skip to main content

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.

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.
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 for the full security rationale and target configuration tiers.

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). 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 (or the equivalent VM-specific page).
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).
  • Wait until a third-party provider deploys (track on DVN Addresses).
  • Defer the chain until multi-DVN coverage exists.

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).
  • 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.
DVN addresses in requiredDVNs must be sorted in ascending address order. The contract reverts on unsorted or duplicate arrays. Sort before encoding.

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 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 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 sendConfigs on Chain A in one transaction; wait for the drain window; then update all receiveConfigs 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.
  • You have repeated the above for the reverse direction.

See also