|
| 1 | +--- |
| 2 | +sidebar_position: 14 |
| 3 | +--- |
| 4 | + |
| 5 | +# Messages with L1 network |
| 6 | + |
| 7 | +You can exchange messages between L1 & L2 networks: |
| 8 | + |
| 9 | +- L2 Starknet mainnet ↔️ L1 Ethereum. |
| 10 | +- L2 Starknet testnet ↔️ L1 Sepolia ETH testnet. |
| 11 | +- L2 local Starknet devnet ↔️ L1 local ETH testnet (anvil, ...). |
| 12 | + |
| 13 | +You can find an explanation of the global mechanism [here](https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/). |
| 14 | + |
| 15 | +Most of the code for this messaging process will be written in Cairo, but Starknet.js provides some functionalities for this subject. |
| 16 | + |
| 17 | +## L1 ➡️ L2 messages |
| 18 | + |
| 19 | +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. |
| 20 | +The interface of this function: |
| 21 | + |
| 22 | +```solidity |
| 23 | +/** |
| 24 | + Sends a message to an L2 contract. |
| 25 | + This function is payable, the paid amount is the message fee. |
| 26 | + Returns the hash of the message and the nonce of the message. |
| 27 | +*/ |
| 28 | +function sendMessageToL2( |
| 29 | + uint256 toAddress, |
| 30 | + uint256 selector, |
| 31 | + uint256[] calldata payload |
| 32 | +) external payable returns (bytes32, uint256); |
| 33 | +``` |
| 34 | + |
| 35 | +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: |
| 36 | + |
| 37 | +```typescript |
| 38 | +import { RpcProvider, constants } from 'starknet'; |
| 39 | +const provider = new RpcProvider({ nodeUrl: constants.NetworkName.SN_SEPOLIA }); // for testnet |
| 40 | + |
| 41 | +const responseEstimateMessageFee = await provider.estimateMessageFee({ |
| 42 | + from_address: L1address, |
| 43 | + to_address: L2address, |
| 44 | + entry_point_selector: 'handle_l1_mess', |
| 45 | + payload: ['1234567890123456789', '200'], // without from_address |
| 46 | +}); |
| 47 | +``` |
| 48 | + |
| 49 | +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. |
| 50 | + |
| 51 | +### L1 ➡️ L2 hashes |
| 52 | + |
| 53 | +Starknet.js proposes 2 functions to calculate hashes related to a L1 ➡️ L2 message : |
| 54 | + |
| 55 | +- The L2 message hash: |
| 56 | + For a L1 tx requesting a message L1->L2, some data extracted from etherscan : https://sepolia.etherscan.io/tx/0xd82ce7dd9f3964d89d2eb9d555e1460fb7792be274950abe578d610f95cc40f5 |
| 57 | + |
| 58 | + ```typescript |
| 59 | + const l1FromAddress = '0x0000000000000000000000008453fc6cd1bcfe8d4dfc069c400b433054d47bdc'; |
| 60 | + const l2ToAddress = 2158142789748719025684046545159279785659305214176670733242887773692203401023n; |
| 61 | + const l2Selector = 774397379524139446221206168840917193112228400237242521560346153613428128537n; |
| 62 | + const payload = [ |
| 63 | + 4543560n, |
| 64 | + 829565602143178078434185452406102222830667255948n, |
| 65 | + 3461886633118033953192540141609307739580461579986333346825796013261542798665n, |
| 66 | + 9000000000000000n, |
| 67 | + 0n, |
| 68 | + ]; |
| 69 | + const l1Nonce = 8288n; |
| 70 | + const l1ToL2MessageHash = hash.getL2MessageHash( |
| 71 | + l1FromAddress, |
| 72 | + l2ToAddress, |
| 73 | + l2Selector, |
| 74 | + payload, |
| 75 | + l1Nonce |
| 76 | + ); |
| 77 | + // l1ToL2MessageHash = '0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090' |
| 78 | + ``` |
| 79 | + |
| 80 | + Can be verified here : https://sepolia.starkscan.co/message/0x2e350fa9d830482605cb68be4fdb9f0cb3e1f95a0c51623ac1a5d1bd997c2090#messagelogs |
| 81 | + |
| 82 | +- The L2 transaction hash: |
| 83 | + For the same message: |
| 84 | + ```typescript |
| 85 | + const l1ToL2TransactionHash = hash.calculateL2MessageTxHash( |
| 86 | + l1FromAddress, |
| 87 | + l2ToAddress, |
| 88 | + l2Selector, |
| 89 | + payload, |
| 90 | + constants.StarknetChainId.SN_SEPOLIA, |
| 91 | + l1Nonce |
| 92 | + ); |
| 93 | + // l1ToL2TransactionHash = '0x67d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07' |
| 94 | + ``` |
| 95 | + Can be verified here : https://sepolia.starkscan.co/tx/0x067d959200d65d4ad293aa4b0da21bb050a1f669bce37d215c6edbf041269c07 |
| 96 | + |
| 97 | +## L2 ➡️ L1 messages |
| 98 | + |
| 99 | +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). |
| 100 | + |
| 101 | +If necessary you can estimate this fee with the generic `estimateInvokeFee` function: |
| 102 | + |
| 103 | +```typescript |
| 104 | +const { suggestedMaxFee: estimatedFee1 } = await account0.estimateInvokeFee({ |
| 105 | + contractAddress: testAddress, |
| 106 | + entrypoint: 'withdraw_to_L1', |
| 107 | + calldata: ['123456789', '30'], |
| 108 | +}); |
| 109 | +``` |
| 110 | + |
| 111 | +The result is in `estimatedFee1`, of type BN. |
| 112 | + |
| 113 | +### L2 ➡️ L1 hash |
| 114 | + |
| 115 | +Starknet.js proposes a function to calculate the L1 ➡️ L2 message hash : |
| 116 | + |
| 117 | +```typescript |
| 118 | +const l2TransactionHash = '0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819'; |
| 119 | +const l1MessageHash = await provider.getL1MessageHash(l2TransactionHash); |
| 120 | +// l1MessageHash = '0x55b3f8b6e607fffd9b4d843dfe8f9b5c05822cd94fcad8797deb01d77805532a' |
| 121 | +``` |
| 122 | + |
| 123 | +Can be verified here : https://sepolia.voyager.online/tx/0x28dfc05eb4f261b37ddad451ff22f1d08d4e3c24dc646af0ec69fa20e096819#messages |
0 commit comments