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.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.
Before you start
You need to know, for every pathway in your mesh:- Both endpoint IDs — source and destination chain.
- Your OApp address on each chain.
- The Send Library address used on the source chain (
getSendLibrary). - The Receive Library address used on the destination chain (
getReceiveLibrary). - The current DVN configuration on each side (
getConfigfor bothsendConfigandreceiveConfig). Capture the exactrequiredDVNCount,requiredDVNs,optionalDVNCount,optionalDVNThreshold,optionalDVNs, andconfirmations. - 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.
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
sendConfigcontrols which DVNs the source library pays to verify each outbound message. - Chain B
receiveConfigcontrols which DVNs the destination library requires to have verified before delivery.
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
receiveConfigto require both[LZLabs, DVN2]before Chain A’ssendConfigincludesDVN2, every in-flight message and every new message stalls.DVN2never witnessed those send events, soDVN2’s attestation never appears. - If you change Chain A’s
sendConfigto[LZLabs, DVN2]first, then Chain A pays both DVNs to attest from that moment forward. Chain B’sreceiveConfigstill only requiresLZLabs, so messages still deliver.DVN2builds up attestations in the background. - Once you can confirm
DVN2is attesting reliably for the pathway, update Chain B’sreceiveConfigto require both. New messages from that point will require both attestations; in-flight messages sent before thesendConfigupdate will fail (becauseDVN2did not see them) and need to be re-sent or recovered manually.
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).
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’sreceiveConfig 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.
Step 4 — Drain in-flight
Wait for at leastconfirmations × 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
receiveConfigrequirement 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
requiredDVNsset.
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 operateN 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 allsendConfigs 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 everysendConfig 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 newUlnConfig you intend to set and the maintenance window before you submit Step 3.
Rollback
If you discover an issue before Step 5 lands (onlysendConfig 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 returnsrequiredDVNCount: 2(or higher) with the intended DVN addresses. -
getConfig(oApp, receiveLib, srcEid, configType=2)on the destination chain returns the matching configuration. -
confirmationsmatches 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
getConfigreturns a configuration distinct from the protocol default shown by the Default Config Checker. - You have repeated the above for the reverse direction.
See also
- Production DVN Configuration — risk tiers and target configurations
- Integration Checklist — pre-launch gate
- Security Stack (DVNs) — concept reference
- DVN Addresses — available providers per chain
- Default Config Checker — verify resolved config per pathway