Message Passing (Writes)

Metalayer allows developers to send arbitrary messages between chains. This is useful for intent settlement, contract coordination, governance execution, and multi-chain applications.

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

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

CopyAsk AI

// 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

CopyAsk AI

// 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]);
    }
}

Last updated