# 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.

#### [​](https://docs.caldera.xyz/metalayer/message-passing/cross-chain-calls#overview)Overview <a href="#overview" id="overview"></a>

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

#### [​](https://docs.caldera.xyz/metalayer/message-passing/cross-chain-calls#how-messages-work-in-metalayer)How Messages Work in Metalayer <a href="#how-messages-work-in-metalayer" id="how-messages-work-in-metalayer"></a>

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.

#### [​](https://docs.caldera.xyz/metalayer/message-passing/cross-chain-calls#example-message-passing)Example Message Passing <a href="#example-message-passing" id="example-message-passing"></a>

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

[**​**](https://docs.caldera.xyz/metalayer/message-passing/cross-chain-calls#hello-world-sender)**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
        );
    }
}
```

[**​**](https://docs.caldera.xyz/metalayer/message-passing/cross-chain-calls#hello-world-receiver)**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]);
    }
}
```
