MetaProver integrates with Caldera Metalayer’s cross-chain messaging system to prove intent fulfillment. It implements the IMetalayerRecipient interface to receive messages from the Metalayer router.
Contract Overview
Location: contracts/prover/MetaProver.sol
contracts/prover/MetaProver.sol
contract MetaProver is IMetalayerRecipient, MessageBridgeProver, Semver {
string public constant PROOF_TYPE = "Meta";
IMetalayerRouterExt public immutable ROUTER;
}
Constructor
From MetaProver.sol:58-67:
contracts/prover/MetaProver.sol
constructor(
address router,
address portal,
bytes32[] memory provers,
uint256 minGasLimit
) MessageBridgeProver(portal, provers, minGasLimit)
Parameters
Address of the local Metalayer router contract
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.
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 Metalayer domain IDs, which are specific to their routing system and NOT the same as chain IDs. Consult Metalayer documentation for domain ID mappings.
Data Parameter Structure
The data parameter must be ABI-encoded as an UnpackedData struct (from MetaProver.sol:29-32):
contracts/prover/MetaProver.sol
struct UnpackedData {
bytes32 sourceChainProver; // Address of prover on source chain
uint256 gasLimit; // Gas limit for execution
}
Encoding example:
bytes memory data = abi.encode(
UnpackedData({
sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))),
gasLimit: 250000
})
);
Gas limits below MIN_GAS_LIMIT (200,000) are automatically increased to the minimum (from MetaProver.sol:105-107).
Message Dispatch
Internal dispatch implementation from MetaProver.sol:118-147:
contracts/prover/MetaProver.sol
function _dispatchMessage(
uint64 domainID,
bytes calldata encodedProofs,
bytes calldata data,
uint256 fee
) internal override {
UnpackedData memory unpacked = _unpackData(data);
(
uint32 sourceChainDomain,
bytes32 recipient,
bytes memory message
) = _formatMetalayerMessage(
domainID,
encodedProofs,
unpacked.sourceChainProver
);
ROUTER.dispatch{value: fee}(
sourceChainDomain,
recipient,
new ReadOperation[](0), // No read operations
message,
FinalityState.INSTANT,
unpacked.gasLimit
);
}
From MetaProver.sol:224-243:
contracts/prover/MetaProver.sol
function _formatMetalayerMessage(
uint64 domainID,
bytes calldata encodedProofs,
bytes32 sourceChainProver
) internal pure returns (
uint32 domain,
bytes32 recipient,
bytes memory message
) {
// Convert domain ID with overflow check
if (domainID > type(uint32).max) {
revert DomainIdTooLarge(domainID);
}
domain = uint32(domainID);
// Use source chain prover as recipient
recipient = sourceChainProver;
message = encodedProofs;
}
Message Reception
From MetaProver.sol:77-91:
contracts/prover/MetaProver.sol
function handle(
uint32 origin,
bytes32 sender,
bytes calldata message,
ReadOperation[] calldata /* operations */,
bytes[] calldata /* operationsData */
) external payable only(address(ROUTER)) {
if (origin == 0) revert MessageOriginChainDomainIDCannotBeZero();
if (sender == bytes32(0)) revert MessageSenderCannotBeZeroAddress();
_handleCrossChainMessage(sender, message);
}
The handle() function:
- Validates call is from the Metalayer router
- Validates origin domain ID is non-zero
- Validates sender address is non-zero
- Checks sender is whitelisted
- Extracts chain ID from message prefix
- Processes intent proofs
Fee Calculation
From MetaProver.sol:158-165:
contracts/prover/MetaProver.sol
function fetchFee(
uint64 domainID,
bytes calldata encodedProofs,
bytes calldata data
) public view override returns (uint256) {
return _fetchFee(domainID, encodedProofs, _unpackData(data));
}
Internal implementation (from MetaProver.sol:175-205):
contracts/prover/MetaProver.sol
function _fetchFee(
uint64 domainID,
bytes calldata encodedProofs,
UnpackedData memory unpacked
) internal view returns (uint256) {
(
uint32 sourceChainDomain,
bytes32 recipient,
bytes memory message
) = _formatMetalayerMessage(
domainID,
encodedProofs,
unpacked.sourceChainProver
);
// Create custom hook metadata with actual gas limit
bytes memory feeHookMetadata = StandardHookMetadata.formatMetadata(
ETH_QUOTE_VALUE, // Very high value (1e36) to avoid failures
unpacked.gasLimit, // Use actual gas limit (min 200k)
msg.sender, // Refund address
bytes("") // Optional custom metadata
);
return ROUTER.quoteDispatch(
sourceChainDomain,
recipient,
message,
feeHookMetadata
);
}
The contract uses a very high ETH quote value (1e36) in fee calculation metadata to avoid failures in the Metalayer router’s quote dispatch function (from MetaProver.sol:44).
Usage Example
// On destination chain (e.g., Optimism)
address metaProver = 0x...; // MetaProver on Optimism
uint64 sourceDomainID = 1; // Metalayer domain ID for Ethereum
// Prepare proof data
bytes32[] memory intentHashes = new bytes32[](1);
intentHashes[0] = intentHash;
// Encode additional data
bytes memory data = abi.encode(
IMetaProver.UnpackedData({
sourceChainProver: bytes32(uint256(uint160(ethereumProverAddress))),
gasLimit: 250000
})
);
// Calculate fee
uint256 fee = IProver(metaProver).fetchFee(
sourceDomainID,
encodedProofs,
data
);
// Send proof (called by Inbox contract)
IProver(metaProver).prove{value: fee}(
msg.sender,
sourceDomainID,
encodedProofs,
data
);
Domain ID Reference
Always check Metalayer’s official documentation for accurate domain IDs. These are specific to their routing system and NOT chain IDs.
Finality States
Metalayer supports different finality states (from MetaProver.sol:144):
contracts/prover/MetaProver.sol
ROUTER.dispatch{value: fee}(
sourceChainDomain,
recipient,
new ReadOperation[](0),
message,
FinalityState.INSTANT, // Or SAFE, FINALIZED
unpacked.gasLimit
);
Available states:
INSTANT - Fastest, lowest security
SAFE - Balanced security and speed
FINALIZED - Highest security, slower
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);
}
Gas Limit Enforcement
The contract enforces a minimum gas limit (from MetaProver.sol:105-107):
contracts/prover/MetaProver.sol
if (unpacked.gasLimit < MIN_GAS_LIMIT) {
unpacked.gasLimit = MIN_GAS_LIMIT;
}
The contract uses accurate gas limits in fee calculation to ensure proper fee estimation (from MetaProver.sol:151-156 comments):
contracts/prover/MetaProver.sol
/**
* @notice Fetches fee required for message dispatch
* @dev Uses custom hook metadata with actual gas limit to ensure accurate fee estimation.
* Fixes issue where 3-parameter quoteDispatch used hardcoded 100k gas limit.
*/