Skip to main content
The LayerZero Prover enables cross-chain intent verification using LayerZero’s omnichain messaging infrastructure. It receives messages via ILayerZeroReceiver and extends MessageBridgeProver for common proving functionality.

Architecture

Destination Chain                Source Chain
━━━━━━━━━━━━━━━━                ━━━━━━━━━━━━━━
Portal                           Portal
    ↓                                ↑
LayerZeroProver                  LayerZeroProver
    ↓                                ↑
LayerZero Endpoint ──────────→ LayerZero Endpoint

Configuration

constructor(
    address endpoint,         // LayerZero V2 endpoint on this chain
    address delegate,         // Address authorized to configure LayerZero
    address portal,           // Portal contract address
    bytes32[] memory provers, // Trusted prover addresses on other chains
    uint256 minGasLimit       // Minimum gas limit (defaults to 200k if zero)
)
Key Points:
  • endpoint: Chain-specific LayerZero V2 endpoint address
  • delegate: Set via ILayerZeroEndpointV2(endpoint).setDelegate(delegate) - can configure paths and settings
  • provers: Whitelist of trusted provers on source chains (bytes32 format for cross-VM compatibility)
  • minGasLimit: Enforced minimum is 200,000 gas if zero or lower value provided

Proving Flow

Sending Proofs (Destination → Source)

Called by Portal after intent fulfillment:
portal.prove{value: fee}(
    address(prover),
    sourceChainDomainID,
    intentHashes,
    data
);
Data Parameter:
abi.encode(
    LayerZeroProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))),
        options: bytes(""),      // Empty for default, or custom LayerZero options
        gasLimit: 250000         // Minimum MIN_GAS_LIMIT enforced
    })
)

Receiving Proofs (Source Chain)

LayerZero endpoint calls lzReceive() with message containing:
  • 8 bytes: destination chain ID
  • 64 bytes per intent: [intentHash (32 bytes)][claimant (32 bytes)]
Only whitelisted senders accepted via isWhitelisted(origin.sender).

Fee Calculation

function fetchFee(
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data
) public view returns (uint256)
Returns native token amount required for LayerZero message dispatch. Always call before proving to avoid reverts.

Domain IDs

Critical: LayerZero uses Endpoint IDs (eids), not chain IDs.
params.dstEid = uint32(domainID);
The sourceChainDomainID parameter must be LayerZero’s endpoint ID. Consult LayerZero V2 Documentation for mappings. Example:
  • Ethereum: Chain ID 1 → Endpoint ID 30101
  • Arbitrum: Chain ID 42161 → Endpoint ID 30110

Message Options

Default (Empty Options)

options: ""
Creates basic options with gas limit:
abi.encodePacked(uint16(3), gasLimit)

Custom Options

Pass encoded LayerZero options for advanced configurations:
options: customEncodedOptions
See LayerZero documentation for option encoding.

Security

Whitelisting

Only provers in the whitelist can send messages:
function isWhitelisted(bytes32 sender) internal view returns (bool)

Access Control

  • Only LayerZero endpoint can call lzReceive()
  • Only Portal can call prove()
  • Path initialization allowed only for whitelisted senders

Gas Limits

Minimum gas enforced during unpacking:
if (unpacked.gasLimit < MIN_GAS_LIMIT) {
    unpacked.gasLimit = MIN_GAS_LIMIT;
}

Integration Example

// Deploy prover
bytes32[] memory trustedProvers = new bytes32[](1);
trustedProvers[0] = bytes32(uint256(uint160(ethereumProverAddress)));

LayerZeroProver prover = new LayerZeroProver(
    layerZeroEndpoint,  // Chain-specific endpoint
    delegateAddress,    // Governance/admin address
    portalAddress,
    trustedProvers,
    250_000            // 250k gas minimum
);

// Fulfill and prove
bytes memory proverData = abi.encode(
    LayerZeroProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(ethereumProver))),
        options: "",
        gasLimit: 250_000
    })
);

uint256 fee = prover.fetchFee(30101, encodedProofs, proverData);

portal.fulfillAndProve{value: route.nativeAmount + fee}(
    intentHash,
    route,
    rewardHash,
    claimant,
    address(prover),
    30101,  // Ethereum endpoint ID
    proverData
);

Advantages

  • Wide Chain Support: 50+ chains including EVM and non-EVM
  • Mature Infrastructure: Battle-tested with significant TVL
  • Flexible Configuration: Delegate model allows operational control
  • Good Tooling: LayerZero Scan for message tracking

Limitations

  • Higher Costs: More expensive than native bridges ($5-50 per message)
  • Domain ID Complexity: Endpoint IDs differ from chain IDs, requires mapping
  • Gas Estimation: Must specify gas limit upfront, failures if underestimated
  • Bridge Dependency: Relies on LayerZero infrastructure availability

Errors

  • EndpointCannotBeZeroAddress(): Invalid endpoint in constructor
  • DelegateCannotBeZeroAddress(): Invalid delegate in constructor
  • SenderCannotBeZeroAddress(): Zero sender in lzReceive
  • DomainIdTooLarge(uint64): Domain ID exceeds uint32.max
  • InvalidExecutor(address): Unexpected executor address
  • NonPortalCaller(address): Unauthorized prove() caller
  • NotWhitelisted(bytes32): Sender not in whitelist

Constant

string public constant PROOF_TYPE = "LayerZero";
Identifies this prover’s mechanism type.
I