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

Architecture

Destination Chain                Source Chain
━━━━━━━━━━━━━━━━                ━━━━━━━━━━━━━━
Portal                           Portal
    ↓                                ↑
MetaProver                       MetaProver
    ↓                                ↑
Metalayer Router ──────────→ Metalayer Router

Configuration

constructor(
    address router,           // Metalayer router on this chain
    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:
  • router: Chain-specific Metalayer router address
  • portal: Portal contract that initiates proving
  • 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(
    MetaProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))),
        gasLimit: 250000  // Minimum MIN_GAS_LIMIT enforced
    })
)

Receiving Proofs (Source Chain)

Metalayer router calls handle() with message containing:
  • 8 bytes: destination chain ID
  • 64 bytes per intent: [intentHash (32 bytes)][claimant (32 bytes)]
function handle(
    uint32 origin,
    bytes32 sender,
    bytes calldata message,
    ReadOperation[] calldata operations,
    bytes[] calldata operationsData
) external payable
Only whitelisted senders accepted via isWhitelisted(sender).

Fee Calculation

function fetchFee(
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data
) public view returns (uint256)
Uses custom hook metadata with actual gas limit for accurate estimation:
bytes memory metadata = StandardHookMetadata.formatMetadata(
    ETH_QUOTE_VALUE,    // 1e36 (high value to avoid quote failures)
    unpacked.gasLimit,  // Actual gas limit from data
    msg.sender,         // Refund address
    bytes("")           // Custom metadata
);
Always call before proving to get accurate fees.

Domain IDs

Critical: Metalayer uses custom domain IDs, not chain IDs.
domain = uint32(domainID);
The sourceChainDomainID parameter must be Metalayer’s domain ID. Consult Caldera Metalayer documentation for mappings.

Finality State

Messages are dispatched with FinalityState.INSTANT:
ROUTER.dispatch{value: fee}(
    sourceChainDomain,
    recipient,
    new ReadOperation[](0),  // Empty read operations
    message,
    FinalityState.INSTANT,   // Instant finality
    unpacked.gasLimit
);
This provides fast message delivery without waiting for blockchain finality.

Gas Limits

Minimum gas enforced during unpacking:
if (unpacked.gasLimit < MIN_GAS_LIMIT) {
    unpacked.gasLimit = MIN_GAS_LIMIT;
}
Default MIN_GAS_LIMIT is 200,000 gas.

Security

Whitelisting

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

Access Control

  • Only Metalayer router can call handle()
  • Only Portal can call prove()
  • Zero domain ID rejected: if (origin == 0) revert ZeroDomainID()

Validation

Sender validation in handle():
if (sender == bytes32(0)) revert SenderCannotBeZeroAddress();

Integration Example

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

MetaProver prover = new MetaProver(
    metalayerRouter,  // Chain-specific router
    portalAddress,
    trustedProvers,
    250_000          // 250k gas minimum
);

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

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

portal.fulfillAndProve{value: route.nativeAmount + fee}(
    intentHash,
    route,
    rewardHash,
    claimant,
    address(prover),
    domainID,  // Metalayer domain ID
    proverData
);

Advantages

  • Lowest Costs: Generally cheapest among message bridge provers
  • Instant Finality: Fast message delivery with FinalityState.INSTANT
  • Simple Configuration: No delegate setup, straightforward deployment
  • Caldera Optimized: Native integration with Caldera rollups

Limitations

  • Smallest Chain Coverage: Limited to Caldera-supported chains
  • Less Mature: Newer infrastructure compared to Hyperlane/LayerZero
  • Limited Tooling: Fewer third-party integrations and explorers
  • Caldera Dependency: Primarily designed for Caldera rollup ecosystem

Errors

  • RouterCannotBeZeroAddress(): Invalid router in constructor
  • ZeroDomainID(): Origin domain is zero in handle()
  • SenderCannotBeZeroAddress(): Zero sender in handle()
  • DomainIdTooLarge(uint64): Domain ID exceeds uint32.max
  • NonPortalCaller(address): Unauthorized prove() caller
  • NotWhitelisted(bytes32): Sender not in whitelist

Constants

string public constant PROOF_TYPE = "Meta";
Identifies this prover’s mechanism type.
uint256 private immutable ETH_QUOTE_VALUE = 1e36;
High value used in fee calculation metadata to avoid quote failures in Metalayer router.
I