Sending a Message
In a cross-chain messaging protocol like Equito, sending a message is a crucial operation. The sendMessage
function of the Router
is responsible for initiating a cross-chain message, which will be intercepted by the Validators for verification and consensus.
Before sending a message, the contract must prepare essential details including the receiver's address, destination chain selector, and message data.
/// @notice Sends a cross-chain message using Equito.
/// @param receiver The address of the receiver.
/// @param destinationChainSelector The chain selector of the destination chain.
/// @param data The message data.
/// @return The hash of the message.
function sendMessage(
bytes64 calldata receiver,
uint256 destinationChainSelector,
bytes calldata data
) external payable returns (bytes32);
Fees payment
To request a message send, the protocol requires a small fee to allow the network to operate efficiently. Altough the fee is paid in native token, the amount is defined in USD and can be upgraded by the Equito Governance, while the actual is calculated based on the current price of the native token against USD.
The Router
exposes a getFee
method, which is actually just a wrapper of the IEquitoFees
contract's getFee
method for convenience.
/// @notice Gets the current fee amount required to send a message.
/// @param sender The address of the Message Sender, usually an Equito App.
/// @return The current fee amount in wei.
function getFee(address sender) external view returns (uint256) {
return equitoFees.getFee(sender);
}
The first operation performed by a Router
in the sendMessage
function is calling payFee
on the IEquitoFees
to pay the required fee for sending the message. This operation fails if the message value is not enough to cover the fee.
equitoFees.payFee{value: msg.value}(msg.sender);
EquitoMessage preparation
Upon receiving the message data and receiver information, the Router prepares a message for cross-chain transfer, using the EquitoMessage struct:
/// @title EquitoMessage
/// @notice The ubiquitous message structure for cross-chain communication, used by the Router contract to deliver and receive messages.
/// @dev Designed to be used by any chain supported by the Equito protocol.
struct EquitoMessage {
/// @notice Block number at which the message is emitted.
uint256 blockNumber;
/// @notice Selector for the source chain, acting as an id.
uint256 sourceChainSelector;
/// @notice Address of the sender.
bytes64 sender;
/// @notice Selector for the destination chain, acting as an id.
uint256 destinationChainSelector;
/// @notice Address of the receiver.
bytes64 receiver;
/// @notice Hash of the payload of the message to be delivered.
bytes32 hashedData;
}
The content of the EquitoMessage struct is as follows:
EquitoMessage memory newMessage = EquitoMessage({
// current block number
blockNumber: block.number,
// chain selector of this Router
sourceChainSelector: chainSelector,
// sender address, converted to bytes64
sender: EquitoMessageLibrary.addressToBytes64(msg.sender),
// chain selector of the destination chain
destinationChainSelector: destinationChainSelector,
// receiver address, converted to bytes64
receiver: receiver,
// hash of the message data
hashedData: keccak256(data)
});
Note that the message sender and receiver addresses are the contracts that implement the logic for the message send and execution. If the message contains any data that should be delivered to an account, for example some tokens, this information must be stored in messageData
, which is decoded by the receiver contract upon message execution.
MessageSendRequested event
After preparing the EquitoMessage, the Router emits a MessageSendRequested
event, containing the message and its data. This event is crucial as it signals to validators that a message is ready for validation and consensus across the network.
/// @notice Emitted when a message send request is created.
/// @param message The message being sent.
/// @param messageData The data of the message being sent.
event MessageSendRequested(
EquitoMessage message,
bytes data
);
Finally, a hash of the message is generated from the provided data. This hash will be utilized in the subsequent step to securely deliver and execute the message.
Full code
/// @notice Sends a cross-chain message using Equito.
/// @param receiver The address of the receiver.
/// @param destinationChainSelector The chain selector of the destination chain.
/// @param data The message data.
/// @return The hash of the message.
function sendMessage(
bytes64 calldata receiver,
uint256 destinationChainSelector,
bytes calldata data
) external payable returns (bytes32) {
// Pay the required fee for sending the message
equitoFees.payFee{value: msg.value}(msg.sender);
// Create a new EquitoMessage
EquitoMessage memory newMessage = EquitoMessage({
// current block number
blockNumber: block.number,
// chain selector of this Router
sourceChainSelector: chainSelector,
// sender address, converted to bytes64
sender: EquitoMessageLibrary.addressToBytes64(msg.sender),
// chain selector of the destination chain
destinationChainSelector: destinationChainSelector,
// receiver address, converted to bytes64
receiver: receiver,
// hash of the message data
hashedData: keccak256(data)
});
// Emit an event indicating the request to send the message
emit MessageSendRequested(newMessage, data);
// Return hash of the message
return keccak256(abi.encode(newMessage));
}