L1->L2 Main
Step-by-step
Sending an L1->L2 transaction requires following these steps:
- Initialise the providers and the wallet that will send the transaction:
const [wallet] = await ethers.getWallets(); // Initialize the L2 provider. const l2Provider = ethers.providerL2; // Initialize the L1 provider. // eslint-disable-next-line @typescript-eslint/no-explicit-any const networkInfo = network as any; const l1RPCEndpoint = networkInfo.config.ethNetwork === 'http://localhost:8545' ? networkInfo.config.ethNetwork : '<YOUR_L1_RPC_ENDPOINT>'; const l1Provider = new ethers.Provider(l1RPCEndpoint);
- Retrieve the current gas price on L1.
// retrieve L1 gas price const l1GasPrice = await l1Provider.getGasPrice(); console.log(`L1 gasPrice ${ethers.formatEther(l1GasPrice)} ETH`);
- Populate the transaction that you want to execute on L2:
const contract = new ethers.Contract(L2_CONTRACT_ADDRESS, GREETER_ABI_JSON.abi, wallet); const message = `Message sent from L1 at ${new Date().toUTCString()}`; // populate tx object const tx = await contract.setGreeting.populateTransaction(message);
- Retrieve the gas limit for sending the populated transaction to L2 using
estimateGasL1()
. This function calls thezks_estimateGasL1ToL2
RPC method under the hood:// Estimate gas limit for L1-L2 tx const l2GasLimit = await l2Provider.estimateL1ToL2Execute({ contractAddress: L2_CONTRACT_ADDRESS, calldata: tx.data, caller: wallet.address, }); console.log(`L2 gasLimit ${l2GasLimit.toString()}`);
- Calculate the total transaction fee to cover the cost of sending the
transaction on L1 and executing the transaction on L2 using
getBaseCost
from theWallet
class. This method:- Retrieves the ZKsync BridgeHub contract address by calling
zks_getBridgehubContract
RPC method. - Calls the
l2TransactionBaseCost
function on the ZKsync BridgeHub system contract to retrieve the fee.
const baseCost = await wallet.getBaseCost({ // L2 computation gasLimit: l2GasLimit, // L1 gas price gasPrice: l1GasPrice, }); console.log(`Executing this transaction will cost ${ethers.formatEther(baseCost)} ETH`);
- Retrieves the ZKsync BridgeHub contract address by calling
- Encode the transaction calldata:
const iface = new ethers.Interface(GREETER_ABI_JSON.abi); const calldata = iface.encodeFunctionData('setGreeting', [message]);
- Finally, send the transaction from your wallet using the
requestExecute
method. This helper method:- Retrieves the Zksync BridgeHub contract address calling
zks_getBridgehubContract
. - Populates the L1-L2 transaction.
- Sends the transaction to the
requestL2TransactionDirect
function of the ZKsync BridgeHub system contract on L1.
const txReceipt = await wallet.requestExecute({ // destination contract in L2 contractAddress: L2_CONTRACT_ADDRESS, calldata, l2GasLimit: l2GasLimit, refundRecipient: wallet.address, overrides: { // send the required amount of ETH value: baseCost, gasPrice: l1GasPrice, }, }); console.log(`L1 tx hash is ${txReceipt.hash}`); console.log('🎉 Transaction sent successfully'); txReceipt.wait(1);
- Retrieves the Zksync BridgeHub contract address calling
Full example
Create a new script file in the scripts
folder called l1tol2tx.ts
, and copy/paste the full script.
touch scripts/l1tol2tx.ts