Send an L2 to L1 message


Send an L2 to L1 message

It is impossible to send transactions directly from L2 to L1.

Instead, you can send arbitrary-length messages from zkSync Era to Ethereum, and then handle the received message on Ethereum with an L1 smart contract.

What is a message?

  • A message is like an event on Ethereum.

  • The difference is that a message publishes data on L1.

  • Solidity representationopen in new window: solidity struct L2Message { address sender; bytes data; uint256 txNumberInblock; }

Verification

  • Verification and confirmation is possible using Ethereum data.
  • However, zkSync Era has an efficient request proof function which does the same.

Common use cases

Along with zkSync Era's built-in censorship resistance that requires multi-layer interoperability, there are some common use cases that need L2 to L1 transaction functionality, such as:

  • Bridging funds from L2 to L1.
  • Layer 2 governance.

Send a message

Two transactions are required:

  • An L2 transaction which sends a message of arbitrary length.
  • An L1 read; implemented by a getter function on an L1 smart contract.
  1. Import the zkSync Era library or contract containing the required functionality.

  2. Get a Contract object that represents the L1Messenger contract.

  3. Transform the request into a raw bytes array.

  4. Use the sendToL1open in new window function from the IL1Messenger.solopen in new window interface, passing the message as a raw bytes array.

Each sent message emits an L1MessageSentopen in new window event.

event L1MessageSent(address indexed _sender, bytes32 indexed _hash, bytes _message);

function sendToL1(bytes memory _message) external returns (bytes32);

4.1 The return value from sendToL1 is the keccak256 hash of the message bytes.

Prove the result

  1. The proveL2MessageInclusionopen in new window function returns a boolean parameter indicating whether the message was sent successfully to L1.
function proveL2MessageInclusion(
    uint256 _blockNumber,
    uint256 _index,
    L2Message memory _message,
    bytes32[] calldata _proof
) public view returns (bool) {
    return _proveL2LogInclusion(_blockNumber, _index, _L2MessageToLog(_message), _proof);
}

Parameter details

  • _blockNumber: L1 batch number in which the L2 block was included; retrievable using the getBlock method.
  • _index: Index of the L2 log in the block; returned as id by the zks_getL2ToL1LogProof method. _message: Parameter holding the message data. It should be an object containing: - sender: Address that sent the message from L2. - data: Message sent in bytes. - txNumberInBlock: Index of the transaction in the L2 block; returned as transactionIndex with getTransactionReceiptopen in new window on an Ethers Provider object.
  • _proof: Merkle proof of the message inclusion; retrieved by observing Ethereum or using the zks_getL2ToL1LogProof method of the zksync-web3 API.

Example

// The Example contract below sends its address to L1 via the Messenger system contract.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing interfaces and addresses of the system contracts
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";

contract Example {
    function sendMessageToL1() external returns(bytes32 messageHash) {
        // Construct the message directly on the contract
        bytes memory message = abi.encode(address(this));

        messageHash = L1_MESSENGER_CONTRACT.sendToL1(message);
    }
}

Example output

Sending message to L1 with text Some L2->L1 message
L2 trx hash is  0xb6816e16906788ea5867bf868693aa4e7a46b68ccd2091be345e286a984cb39b
Waiting for transaction to finalize...
Getting L2 message proof for block 5382192
Proof is:  {
  id: 14,
  proof: [
    '0xd92e806d774b16f21a00230a5ee93555dde30138daf8dbbc8c225ad4aa670edd',
    '0xf970801623a03cf02838550dcca2ecf575ace6ae824e5a3339426e69a582c2d8',
    '0x389719c677f61f2681950c2136df476e78e74016268806986d4f0599e8055a4b',
    '0xb1bde90366b509799bd535f03da87f4c2b68e305bfb5166e694809c4caf0df69',
    '0x94b863aefb6546c8465f7700ec701f6b97ddf71a165a6d1e1ce1dc3c41db2534',
    '0x1798a1fd9c8fbb818c98cff190daa7cc10b6e5ac9716b4a2649f7c2ebcef2272',
    '0x66d7c5983afe44cf15ea8cf565b34c6c31ff0cb4dd744524f7842b942d08770d',
    '0xb04e5ee349086985f74b73971ce9dfe76bbed95c84906c5dffd96504e1e5396c',
    '0xac506ecb5465659b3a927143f6d724f91d8d9c4bdb2463aee111d9aa869874db'
  ],
  root: '0xbc872eb80a7d5d35dd16283c1b1a768b1e1c36404000edaaa04868c7d6a5907c'
}
L1 Index for Tx in block :>>  32
L1 Batch for block :>>  77512
Retrieving proof for batch 77512, transaction index 32 and proof id 14
Result is :>>  true