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 (not yet available)
    • pull: users / protocols responsible for triggering the transaction

Contracts​

ContractL1 (Goerli) AddressL2 (Linea) Address
Transparent Proxy

0x70BaD09280FD342D02fe64119779BC1f0791BAC2

0xC499a572640B64eA1C8c194c43Bc3E19940719dC

Implementation

0x2652e1547Ac6b9a0311cF1B7F024a378f30ad8D8

0xc0557e2149751e201749b87f86acd91DB22e2662

How to use​

Workflow​

  1. dApp calls sendMessage(...) on the origin layer using MessageService.sol
    • 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 (this is not yet available)
      • _calldata: a flexible field that is generally created using abi.encode(...)
        • Example: the Canonical Token Bridge (link to Etherscan)
  2. dApp uses the Postman SDK (details to come) to pay for 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 claimMessage with the parameters detailed in the interface below or by using the SDK.
    • 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()

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.
*/x
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;
}
}