Skip to main content
LayerZeroProver integrates with LayerZero V2 endpoints to prove intent fulfillment across chains. It implements the ILayerZeroReceiver interface to receive cross-chain messages.

Contract Overview

Location: contracts/prover/LayerZeroProver.sol
contracts/prover/LayerZeroProver.sol
contract LayerZeroProver is ILayerZeroReceiver, MessageBridgeProver, Semver {
    string public constant PROOF_TYPE = "LayerZero";
    address public immutable ENDPOINT;
}

Constructor

From LayerZeroProver.sol:57-74:
contracts/prover/LayerZeroProver.sol
constructor(
    address endpoint,
    address delegate,
    address portal,
    bytes32[] memory provers,
    uint256 minGasLimit
) MessageBridgeProver(portal, provers, minGasLimit)

Parameters

endpoint
address
Address of the local LayerZero V2 endpoint contract
delegate
address
Address authorized to configure LayerZero settings (configs, paths, etc.)
portal
address
Address of the Portal contract that manages rewards
provers
bytes32[]
Array of trusted prover addresses on other chains (as bytes32 for cross-VM compatibility)
minGasLimit
uint256
Minimum gas limit for cross-chain messages. Defaults to 200,000 if zero.
The constructor automatically sets the delegate on the LayerZero endpoint (line 73):
ILayerZeroEndpointV2(endpoint).setDelegate(delegate);

Prove Function

Inherited from MessageBridgeProver.sol:112-117:
contracts/prover/MessageBridgeProver.sol
function prove(
    address sender,
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data
) external payable only(PORTAL);
The domainID parameter uses LayerZero Endpoint IDs (EIDs), which are NOT the same as chain IDs. Consult LayerZero V2 documentation for endpoint ID mappings.

Data Parameter Structure

The data parameter must be ABI-encoded as an UnpackedData struct (from LayerZeroProver.sol:19-24):
contracts/prover/LayerZeroProver.sol
struct UnpackedData {
    bytes32 sourceChainProver; // Address of prover on source chain
    bytes options;             // LayerZero message options
    uint256 gasLimit;          // Gas limit for execution
}
Encoding example:
bytes memory data = abi.encode(
    UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))),
        options: "",  // Or custom LayerZero options
        gasLimit: 250000
    })
);
Gas limits below MIN_GAS_LIMIT (200,000) are automatically increased to the minimum (from LayerZeroProver.sol:190-192).

Message Dispatch

Internal dispatch implementation from LayerZeroProver.sol:133-156:
contracts/prover/LayerZeroProver.sol
function _dispatchMessage(
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data,
    uint256 fee
) internal override {
    UnpackedData memory unpacked = _unpackData(data);
    
    ILayerZeroEndpointV2.MessagingParams memory params = _formatLayerZeroMessage(
        domainID,
        encodedProofs,
        unpacked
    );
    
    ILayerZeroEndpointV2(ENDPOINT).send{value: fee}(
        params,
        msg.sender  // refund address
    );
}

Message Formatting

From LayerZeroProver.sol:239-267:
contracts/prover/LayerZeroProver.sol
function _formatLayerZeroMessage(
    uint64 domainID,
    bytes calldata encodedProofs,
    UnpackedData memory unpacked
) internal pure returns (ILayerZeroEndpointV2.MessagingParams memory params) {
    // Validate domain ID fits in uint32
    if (domainID > type(uint32).max) {
        revert DomainIdTooLarge(domainID);
    }
    params.dstEid = uint32(domainID);
    
    // Use source chain prover as recipient
    params.receiver = unpacked.sourceChainProver;
    params.message = encodedProofs;
    
    // Use provided options or create default with gas limit
    params.options = unpacked.options.length > 0
        ? unpacked.options
        : abi.encodePacked(
            uint16(3),              // option type for gas limit
            unpacked.gasLimit       // gas amount
        );
    params.payInLzToken = false;
}

Message Reception

From LayerZeroProver.sol:85-98:
contracts/prover/LayerZeroProver.sol
function lzReceive(
    Origin calldata origin,
    bytes32 /* guid */,
    bytes calldata message,
    address /* executor */,
    bytes calldata /* extraData */
) external payable override only(ENDPOINT) {
    if (origin.sender == bytes32(0)) {
        revert MessageSenderCannotBeZeroAddress();
    }
    
    _handleCrossChainMessage(origin.sender, message);
}
The lzReceive() function:
  1. Validates call is from the LayerZero endpoint
  2. Validates sender address is non-zero
  3. Checks sender is whitelisted
  4. Extracts chain ID from message prefix
  5. Processes intent proofs

Additional LayerZero Methods

Allow Initialize Path

From LayerZeroProver.sol:105-110:
contracts/prover/LayerZeroProver.sol
function allowInitializePath(
    Origin calldata origin
) external view override returns (bool) {
    return isWhitelisted(origin.sender);
}
Checks if a cross-chain path should be allowed based on sender whitelist.

Next Nonce

From LayerZeroProver.sol:117-123:
contracts/prover/LayerZeroProver.sol
function nextNonce(
    uint32 /* srcEid */,
    bytes32 /* sender */
) external pure override returns (uint64) {
    return 0;  // We don't track nonces
}

Fee Calculation

From LayerZeroProver.sol:166-176:
contracts/prover/LayerZeroProver.sol
function fetchFee(
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data
) public view override returns (uint256) {
    UnpackedData memory unpacked = _unpackData(data);
    return _fetchFee(domainID, encodedProofs, unpacked);
}
Internal implementation (from LayerZeroProver.sol:202-221):
contracts/prover/LayerZeroProver.sol
function _fetchFee(
    uint64 domainID,
    bytes calldata encodedProofs,
    UnpackedData memory unpacked
) internal view returns (uint256) {
    ILayerZeroEndpointV2.MessagingParams memory params = _formatLayerZeroMessage(
        domainID,
        encodedProofs,
        unpacked
    );
    
    ILayerZeroEndpointV2.MessagingFee memory fee = ILayerZeroEndpointV2(
        ENDPOINT
    ).quote(params, address(this));
    
    return fee.nativeFee;
}

Usage Example

// On destination chain (e.g., Optimism)
address lzProver = 0x...;  // LayerZeroProver on Optimism
uint64 sourceEid = 30101;  // LayerZero EID for Ethereum

// Prepare proof data
bytes32[] memory intentHashes = new bytes32[](1);
intentHashes[0] = intentHash;

// Encode additional data
bytes memory data = abi.encode(
    ILayerZeroProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(ethereumProverAddress))),
        options: "",  // Use default options
        gasLimit: 250000
    })
);

// Calculate fee
uint256 fee = IProver(lzProver).fetchFee(
    sourceEid,
    encodedProofs,
    data
);

// Send proof (called by Inbox contract)
IProver(lzProver).prove{value: fee}(
    msg.sender,
    sourceEid,
    encodedProofs,
    data
);

Endpoint ID Reference

Always check LayerZero’s official endpoint ID registry for accurate endpoint IDs. These are NOT chain IDs.
Common V2 endpoint IDs (verify before use):
ChainChain IDLayerZero V2 EID
Ethereum130101
Optimism1030111
Polygon13730109
Arbitrum4216130110
Base845330184

Security Considerations

Whitelist Validation

Only whitelisted prover addresses can send proof messages. This is validated in _handleCrossChainMessage() from MessageBridgeProver.sol:81-82:
contracts/prover/MessageBridgeProver.sol
if (!isWhitelisted(messageSender)) {
    revert UnauthorizedIncomingProof(messageSender);
}

Delegate Configuration

The delegate address has full control over LayerZero configurations. Set this carefully during deployment.

Gas Limit Enforcement

The contract enforces a minimum gas limit (from LayerZeroProver.sol:190-192):
contracts/prover/LayerZeroProver.sol
if (unpacked.gasLimit < MIN_GAS_LIMIT) {
    unpacked.gasLimit = MIN_GAS_LIMIT;
}

Build docs developers (and LLMs) love