Skip to main content
The Hyperlane Prover enables cross-chain intent verification using Hyperlane’s modular interoperability stack. It receives messages via IMessageRecipient and extends MessageBridgeProver for common proving functionality.

Architecture

Destination Chain                Source Chain
━━━━━━━━━━━━━━━━                ━━━━━━━━━━━━━━
Portal                           Portal
    ↓                                ↑
HyperProver                      HyperProver
    ↓                                ↑
Hyperlane Mailbox ──────────→ Hyperlane Mailbox

Configuration

constructor(
    address mailbox,          // Hyperlane mailbox on this chain
    address portal,           // Portal contract address
    bytes32[] memory provers  // Trusted prover addresses on other chains
)
Key Points:
  • mailbox: Chain-specific Hyperlane mailbox address
  • portal: Portal contract that initiates proving
  • provers: Whitelist of trusted provers on source chains (bytes32 format for cross-VM compatibility)
  • minGasLimit: Set to 0 in constructor (no minimum enforced, unlike LayerZero)

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(
    HyperProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(sourceProverAddress))),
        metadata: bytes(""),        // Hyperlane metadata or empty for default
        hookAddr: address(0)        // Post-dispatch hook or zero for default
    })
)

Receiving Proofs (Source Chain)

Hyperlane mailbox 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 messageBody
) public payable
Only whitelisted senders accepted via isWhitelisted(sender).

Fee Calculation

function fetchFee(
    uint64 domainID,
    bytes calldata encodedProofs,
    bytes calldata data
) public view returns (uint256)
Queries Hyperlane mailbox using quoteDispatch() for accurate fee. Always call before proving to avoid reverts.

Domain IDs

Critical: Hyperlane uses custom domain IDs, not chain IDs.
params.destinationDomain = uint32(domainID);
The sourceChainDomainID parameter must be Hyperlane’s domain ID. Consult Hyperlane Documentation for mappings. Example:
  • Ethereum: Chain ID 1 → Domain ID 1
  • Arbitrum: Chain ID 42161 → Domain ID 42161
  • Optimism: Chain ID 10 → Domain ID 10
Note: Many Hyperlane domains match chain IDs, but not all (e.g., testnets).

Metadata and Hooks

Default Configuration

metadata: bytes("")
hookAddr: address(0)
Uses mailbox’s default hook:
params.hook = IMailbox(MAILBOX).defaultHook();

Custom Hook

Specify custom post-dispatch hook for advanced configurations:
HyperProver.UnpackedData({
    sourceChainProver: sourceProver,
    metadata: customMetadata,
    hookAddr: customHookAddress
})
Hook Use Cases:
  • Custom gas payment strategies
  • Message aggregation
  • Alternative security models

Metadata

Pass custom metadata for hook configuration:
metadata: encodedMetadata
Metadata format depends on the hook being used. See Hyperlane documentation for hook-specific encoding.

Security

Whitelisting

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

Access Control

  • Only Hyperlane mailbox 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)));

HyperProver prover = new HyperProver(
    hyperlaneMailbox,  // Chain-specific mailbox
    portalAddress,
    trustedProvers
);

// Fulfill and prove
bytes memory proverData = abi.encode(
    HyperProver.UnpackedData({
        sourceChainProver: bytes32(uint256(uint160(ethereumProver))),
        metadata: "",
        hookAddr: address(0)  // Use default hook
    })
);

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

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

Advantages

  • Lower Costs: Generally cheaper than LayerZero
  • Modular Security: Flexible hook system for custom security models
  • Simpler Configuration: No delegate setup, domain IDs often match chain IDs
  • Transparent Security: Open-source validators and relayers

Limitations

  • Smaller Chain Coverage: ~30 chains vs LayerZero’s 50+
  • Less Mature Tooling: Fewer third-party integrations and explorers
  • Hook Complexity: Advanced features require understanding hook system
  • Bridge Dependency: Relies on Hyperlane infrastructure availability

Errors

  • MailboxCannotBeZeroAddress(): Invalid mailbox 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

Constant

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