Skip to main content

Build Decentralized Verifier Networks (DVNs)

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 functions: assignJob and getFee.

interface ILayerZeroDVN {
struct AssignJobParam {
uint32 dstEid;
bytes packetHeader;
bytes32 payloadHash;
uint64 confirmations;
address sender;
}

function assignJob(AssignJobParam calldata _param, bytes calldata _options) external payable returns (uint256 fee);

function getFee(
uint32 _dstEid,
uint64 _confirmations,
address _sender,
bytes calldata _options
) external view returns (uint256 fee);
}
Function NameTypeDescription
assignJobPayableCalled as part of _lzSend.
getFeeViewTypically called by applications before sending the packet to estimate fees.

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 is how you know your DVN has been assigned to verify the packet's payloadHash.

    DVNFeePaid(
    address[] requiredDVNs,
    address[] optionalDVNs,
    uint256[] fees
    );
    tip

    The DVNFeePaid 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
    );
    tip

    To know your workflow has successfully fulfilled its obligation, your DVN should perform an idempotency check at the end of the DVN workflow.