Overview
Metalayer allows developers to send arbitrary messages between chains. Metalayer message passing is implemented as an extension of Hyperlane’s message-passing protocol.
- A contract on the source chain calls
dispatch
, sending a payload to a contract on the destination chain.
- Hyperlane’s relayers transport the message securely.
- 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]);
}
}