Overview

Metalayer allows developers to send arbitrary messages between chains. Metalayer message passing is implemented as an extension of Hyperlane’s message-passing protocol.

How Messages Work in Metalayer

  1. A contract on the source chain calls dispatch, sending a payload to a contract on the destination chain.
  2. Hyperlane’s relayers transport the message securely.
  3. The recipient contract processes the message by decoding it.

Example Message Passing

For full developer documentation, see the Cross-Chain dApps section.

This example demonstrates a simple cross-chain messaging system using Metalayer. We’ll create two contracts:

  • A sender contract that dispatches messages
  • A receiver contract that counts and stores received messages

Hello World Sender

The sender contract needs to:

  • Store the router address and destination information
  • Calculate gas fees for message delivery
  • Format and dispatch messages
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract HelloSender {
    // The MetalayerRouter on the local chain
    IMetalayerRouter public immutable router;
    // The domain (chain ID) where messages will be sent
    uint32 public immutable destinationDomain;
    // Estimated gas needed for message processing
    uint256 private constant ESTIMATED_GAS_LIMIT = 100000;

    constructor(
        address _metalayerRouter,
        uint32 _destinationDomain
    ) {
        router = IMetalayerRouter(_metalayerRouter);
        destinationDomain = _destinationDomain;
    }

    function sayHello(address recipient, string calldata message) external payable {
        // Calculate required gas payment
        uint256 gasPayment = router.quoteGasPayment(
            destinationDomain,
            ESTIMATED_GAS_LIMIT
        );
        require(msg.value >= gasPayment, "Insufficient gas payment");

        // Format the message data
        bytes memory callData = abi.encode(message);
        
        // Create empty reads array since we're not querying data
        ReadOperation[] memory reads = new ReadOperation[](0);

        // Send the cross-chain message
        router.dispatch{value: gasPayment}(
            destinationDomain,
            recipient,
            reads,
            callData,
            true // Wait for finality
        );
    }
}

Hello World Receiver

The receiver contract must:

  • Implement the IMetalayerRecipient interface
  • Store the router address and verify message sources
  • Track received messages
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract HelloReceiver is IMetalayerRecipient {
    // The MetalayerRouter on this chain
    IMetalayerRouter public immutable router;
    // The domain (chain ID) where messages come from
    uint32 public immutable sourceDomain;
    
    // Message tracking
    uint256 public messageCount;
    mapping(uint256 => string) public messages;
    mapping(uint256 => address) public senders;

    constructor(
        address _metalayerRouter,
        uint32 _sourceDomain
    ) {
        router = IMetalayerRouter(_metalayerRouter);
        sourceDomain = _sourceDomain;
    }

    function handle(
        uint32 _originDomain,
        address _sender,
        bytes calldata _message,
        ReadOperation[] calldata _reads,
        bytes[] calldata _readResults
    ) external payable {
        // Verify message comes from our router
        require(msg.sender == address(router), "Unauthorized router");
        // Verify message comes from expected chain
        require(_originDomain == sourceDomain, "Wrong source domain");

        // Decode and store the message
        string memory helloMessage = abi.decode(_message, (string));
        messages[messageCount] = helloMessage;
        senders[messageCount] = _sender;
        messageCount++;
    }

    // View functions to read message history
    function getMessageDetails(uint256 index) external view returns (
        string memory message,
        address sender
    ) {
        require(index < messageCount, "Message index out of bounds");
        return (messages[index], senders[index]);
    }
}