Skip to main content

Message service

The message service is responsible for cross-chain messages between Ethereum and Linea, which:

  • Allows a contract on the source chain to safely interact with a contract on the target chain (e.g. L1TokenBridge triggering mint on the L2TokenBridge),
  • Is responsible for bridging ETH (native currency on L1 and L2)
  • Supports:
    • push: auto-execution on target layer if a fee is paid
    • pull: users / protocols responsible for triggering the transaction

Message service contracts​

How to use​

Workflow​

  1. Dapp calls sendMessage(...) on the origin layer using the proxy contract at one of the testnet addresses above.
    • Args:
      • _to: the destination address on the destination chain
      • _fee: the message service fee on the origin chain
        • An optional field used to incentivize a Postman to perform claimMessage(...) automatically on the destination chain (not available when bridging from L2 to L1, or for non-ETH transfers)
      • _calldata: a flexible field that is generally created using abi.encode(...)
  2. Dapp uses the Postman SDK to simplify the execution of messages on the destination layer by:
    • Triggering the delivery
      • If messages don't get delivered by the postman, the message can be manually claimed by calling, with the parameters detailed in the interface below, one of:
      • L2: claimMessage
      • L1: claimMessageWithProof
        You can also use the SDK to claim messages.
    • Receiving the delivery in the dapp smart contract
      • This triggers claimMessage(...) on the destination layer that will call _to with _calldata and a value equal to.
      • The dapp smart contract can inherit from MessageServiceBase.sol to:
        • Verify that the call comes from the MessageService onlyMessagingService
        • Verify that the sender on the origin chain comes from a trusted contract (usually the dapp sibling contract) using onlyAuthorizedRemoteSender()
Proxy contract

A proxy contract is one that simply points towards the actual "implementation" contracts. This model is beneficial as it allows the implementation contracts to be upgraded independently of the proxy, allowing contract upgrades without having to start afresh and lose the proxy contract's history. When the implementation contracts are updated, the proxy contract is simply amended to point towards the new implementation contract addresses.

Interface IMessageService.sol​

IMessageService.sol
pragma solidity ^0.8.19;

interface IMessageService {
/**
* @dev Emitted when a message is sent.
* @dev We include the message hash to save hashing costs on the rollup.
*/
event MessageSent(
address indexed _from,
address indexed _to,
uint256 _fee,
uint256 _value,
uint256 _nonce,
bytes _calldata,
bytes32 indexed _messageHash
);

/**
* @dev Emitted when a message is claimed.
*/
event MessageClaimed(bytes32 indexed _messageHash);

/**
* @dev Thrown when fees are lower than the minimum fee.
*/
error FeeTooLow();

/**
* @dev Thrown when fees are lower than value.
*/
error ValueShouldBeGreaterThanFee();

/**
* @dev Thrown when the value sent is less than the fee.
* @dev Value to forward on is msg.value - _fee.
*/
error ValueSentTooLow();

/**
* @dev Thrown when the destination address reverts.
*/
error MessageSendingFailed(address destination);

/**
* @dev Thrown when the destination address reverts.
*/
error FeePaymentFailed(address recipient);

/**
* @notice Sends a message for transporting from the given chain.
* @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
* @param _to The destination address on the destination chain.
* @param _fee The message service fee on the origin chain.
* @param _calldata The calldata used by the destination message service to call the destination contract.
*/
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;

/**
* @notice Deliver a message to the destination chain.
* @notice Is called automatically by the Postman, dApp or end user.
* @param _from The msg.sender calling the origin message service.
* @param _to The destination address on the destination chain.
* @param _value The value to be transferred to the destination address.
* @param _fee The message service fee on the origin chain.
* @param _feeRecipient Address that will receive the fees.
* @param _calldata The calldata used by the destination message service to call/forward to the destination contract.
* @param _nonce Unique message number.
*/
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external;

/**
* @notice Returns the original sender of the message on the origin layer.
* @return The original sender of the message on the origin layer.
*/
function sender() external view returns (address);
}

Abstract contract MessageServiceBase.sol​

MessageServiceBase.sol
// SPDX-License-Identifier: OWNED BY Consensys Software Inc.
pragma solidity ^0.8.19;

import "./interfaces/IMessageService.sol";

/**
* @title Base contract to manage cross-chain messaging.
* @author Consensys Software Inc.
*/
abstract contract MessageServiceBase {
IMessageService public messageService;
address public remoteSender;

uint256[10] private __base_gap;

/**
* @dev Thrown when the caller address is not the message service address
*/
error CallerIsNotMessageService();

/**
* @dev Thrown when remote sender address is not authorized.
*/
error SenderNotAuthorized();

/**
* @dev Thrown when an address is the default zero address.
*/
error ZeroAddressNotAllowed();

/**
* @dev Modifier to make sure the caller is the known message service.
*
* Requirements:
*
* - The msg.sender must be the message service.
*/
modifier onlyMessagingService() {
if (msg.sender != address(messageService)) {
revert CallerIsNotMessageService();
}
_;
}

/**
* @dev Modifier to make sure the original sender is allowed.
*
* Requirements:
*
* - The original message sender via the message service must be a known sender.
*/
modifier onlyAuthorizedRemoteSender() {
if (messageService.sender() != remoteSender) {
revert SenderNotAuthorized();
}
_;
}

/**
* @notice Initializes the message service and remote sender address
* @dev Must be initialized in the initialize function of the main contract or constructor
* @param _messageService The message service address, cannot be empty.
* @param _remoteSender The authorized remote sender address, cannot be empty.
**/
function _init_MessageServiceBase(address _messageService, address _remoteSender) internal {
if (_messageService == address(0)) {
revert ZeroAddressNotAllowed();
}

if (_remoteSender == address(0)) {
revert ZeroAddressNotAllowed();
}

messageService = IMessageService(_messageService);
remoteSender = _remoteSender;
}
}