Skip to main content
Version: Next

Messages with L1 network

You can exchange messages between L1 & L2 networks:

  • L2 Starknet mainnet ↔️ L1 Ethereum.
  • L2 Starknet testnet ↔️ L1 Sepolia ETH testnet.
  • L2 local Starknet devnet ↔️ L1 local ETH testnet (anvil, ...).

You can find an explanation of the global mechanism here.

Most of the code for this messaging process will be written in Cairo, but Starknet.js provides some functionalities for this subject.

L1 ➡️ L2 messages

To send a message from L1 to L2, you need a solidity smart contract in the L1 network, calling the SendMessageToL2 function of the Starknet core contract. The interface of this function:

/**
Sends a message to an L2 contract.
This function is payable, the paid amount is the message fee.
Returns the hash of the message and the nonce of the message.
*/
function sendMessageToL2(
uint256 toAddress,
uint256 selector,
uint256[] calldata payload
) external payable returns (bytes32, uint256);

You have to pay in the L1 an extra fee when invoking sendMessageToL2 (of course paid with the L1 fee TOKEN), to pay the L2 part of the messaging mechanism. You can estimate this fee with this function:

import { RpcProvider, constants } from 'starknet';
const provider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_SEPOLIA }); // for testnet

const responseEstimateMessageFee = await provider.estimateMessageFee({
from_address: L1address,
to_address: L2address,
entry_point_selector: 'handle_l1_mess',
payload: ['1234567890123456789', '200'], // without from_address
});

If the fee is paid in L1, the Cairo contract at to_Address is automatically executed, function entry_point_selector (the function shall have a decorator #[l1_handler] in the Cairo code, with a first parameter called from_address: felt252). The payload shall not include the from_address parameter.

L1 ➡️ L2 hashes

Starknet.js proposes 2 functions to calculate hashes related to a L1 ➡️ L2 message :

L2 ➡️ L1 messages

To send a message to L1, you will just invoke a Cairo contract function, paying a fee that will pay all the processes (in L1 & L2).

If necessary you can estimate this fee with the generic estimateInvokeFee function:

const { suggestedMaxFee: estimatedFee1 } = await account0.estimateInvokeFee({
contractAddress: testAddress,
entrypoint: 'withdraw_to_L1',
calldata: ['123456789', '30'],
});

The result is in estimatedFee1, of type BN.

L2 ➡️ L1 hash

Starknet.js proposes a function to calculate the L1 ➡️ L2 message hash :

const l2TransactionHash = '0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819';
const l1MessageHash = await provider.getL1MessageHash(l2TransactionHash);
// l1MessageHash = '0x55b3f8b6e607fffd9b4d843dfe8f9b5c05822cd94fcad8797deb01d77805532a'

Can be verified here : https://sepolia.voyager.online/tx/0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819#messages