Skip to main content
This document contains a high level overview of how to implement and integrate a basic third party DVN into the LayerZero V2 protocol.

Fee Quoting, Collection, and Withdrawal

DVN owners should implement and deploy a DVN contract on every chain they want to support. The contract must implement the ILayerZeroDVN interface, which specifies two core functions:
  • assignJob - Called by the Message Library when a packet is sent, paying the DVN for verification
  • getFee - Returns the fee for verifying a message to a specific destination
For the complete interface specification, data structures, and method signatures, see the DVN Technical Reference. If your DVN is responsible for a packet, the LayerZero Endpoint will call your DVN contract’s assignJob function.

Building a DVN

The DVN has one off-chain workflow:
  1. The DVN first listens for the PacketSent event:
    PacketSent(
    bytes encodedPacket,
    bytes options,
    address sendLibrary)
    
    The packet has the following structure:
    struct Packet {
        uint64 nonce; // the nonce of the message in the pathway
        uint32 srcEid; // the source endpoint ID
        address sender; // the sender address
        uint32 dstEid; // the destination endpoint ID
        bytes32 receiver; // the receiving address
        bytes32 guid; // a global unique identifier
        bytes message; // the message payload
    }
    
    The encoded packet can be deserialized with the PacketSerializer and the option can be deserialized with the OptionSerializer.
  2. After the PacketSent event, the DVNFeePaid event is how you know your DVN has been assigned to verify the packet’s payloadHash.
    DVNFeePaid(
        address[] requiredDVNs,
        address[] optionalDVNs,
        uint256[] fees
    );
    
    The DVNFeePaid event returns a list of all of the OApp’s configured DVNs, so your workflow should filter your specific DVN address from the array to make sure your DVN has been paid.
  3. After receiving the fee, your DVN should query the address of the MessageLib on the destination chain:
    getReceiveLibrary(
        _receiver,
        _dstEid
    );
    
  4. After your DVN has retrieved the receive MessageLib, you should read the MessageLib configuration from it. In the configuration is the required block confirmations to wait before calling verify on the destination chain.
    function getUlnConfig(address _oapp, uint32 _remoteEid) public view returns (UlnConfig memory rtnConfig);
    
    This will return the UlnConfig, which you can use to read the number of confirmations:
    struct UlnConfig {
        uint64 confirmations;
        // ...
    
  5. Your DVN should next do an idempotency check:
    ULN._verified(
        _dvn,
        _headerHash,
        _payloadHash,
        _requiredConfirmation
    );
    
    This returns a boolean value:
    • If the state is true, then your idempotency check indicates that you already verified this packet. You can terminate your DVN workflow.
    • If the state is false, then you must call ULN.verify:
    ULN._verify(
        _packetHeader,
        _payloadHash,
        _confirmations
    );
    
    To know your workflow has successfully fulfilled its obligation, your DVN should perform an idempotency check at the end of the DVN workflow.