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
Address of the local LayerZero V2 endpoint contract
Address authorized to configure LayerZero settings (configs, paths, etc.)
Address of the Portal contract that manages rewards
Array of trusted prover addresses on other chains (as bytes32 for cross-VM compatibility)
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
);
}
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:
- Validates call is from the LayerZero endpoint
- Validates sender address is non-zero
- Checks sender is whitelisted
- Extracts chain ID from message prefix
- 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
Common V2 endpoint IDs (verify before use):
| Chain | Chain ID | LayerZero V2 EID |
|---|
| Ethereum | 1 | 30101 |
| Optimism | 10 | 30111 |
| Polygon | 137 | 30109 |
| Arbitrum | 42161 | 30110 |
| Base | 8453 | 30184 |
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;
}