Home
Hardhat plugins for zkSync 2.0
JS Web3 SDK
  • JS Web3 SDK
  • Getting started
  • Providers
  • Accounts: overview
  • Accounts: L1->L2 transactions
  • Contracts
  • zkSync features
  • Utilities
  • Types
  • Front-end integration
  • Home
  • /
  • JS Web3 SDK
  • /
  • Accounts: L1->L2 transactions

Accounts: L1->L2 transactions

What's on the page
  • Supported classes
  • Approving deposit of tokens
  • Depositing tokens to zkSync
  • Adding native token to zkSync
  • Requesting a withdrawal from L1
  • Calling L2 smart contracts from L1
  • Deploying L2 smart contracts from L1

This section explores the methods which allow the account classes to send transactions from L1 to L2.

If you want to get some background on how L1->L2 interaction works on zkSync, you should go through the introduction and the guide.

Supported classes

The following account classes support sending transactions from L1 to L2:

  • Wallet (if connected to an L1 provider)
  • L1Signer

Approving deposit of tokens

Bridging ERC20 tokens from Ethereum requires approving the tokens to the zkSync Ethereum smart contract.

async approveERC20(    token: Address,    amount: BigNumberish,    overrides?: ethers.CallOverrides): Promise<ethers.providers.TransactionResponse>

Inputs and outputs

NameDescription
tokenThe Ethereum address of the token.
amountThe amount of the token to be approved.
overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice, etc.
returnsethers.providers.TransactionResponse object.

Example

import * as zksync from "zksync-web3";import { ethers } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new zksync.Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new zksync.Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const USDC_ADDRESS = "0xd35cceead182dcee0f148ebac9447da2c4d449c4";const txHandle = await wallet.approveERC20(  USDC_ADDRESS,  "10000000" // 10.0 USDC);await txHandle.wait();

Depositing tokens to zkSync

Getting the base cost for a deposit

While for now the deposits are free, it is important to note that it may change in the future. zkSync team added the functionality to get the base cost for the deposit transaction:

async depositBaseCost(params?: {    gasPrice?: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;}): Promise<BigNumber>

Inputs and outputs

NameDescription
params.gasPrice (optional)The gas price of the L1 transaction that will send the request for deposit.
params.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
params.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
returnsThe base cost in ETH for requesting a deposit.

Requesting deposit operation

async deposit(transaction: {    token: Address;    amount: BigNumberish;    to?: Address;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;    operatorTip?: BigNumberish;    approveERC20?: boolean;    overrides?: ethers.CallOverrides;    approveOverrides?: ethers.CallOverrides;}): Promise<PriorityOpResponse>

Inputs and outputs

NameDescription
transaction.tokenThe address of the token to deposit.
transaction.amountThe amount of the token to be deposited.
transaction.to (optional)The address that will receive the deposited tokens on L2.
transaction.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
transaction.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
transaction.operatorTip (optional)If the ETH value passed with the transaction is not explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the base cost of the transaction. This value has no meaning for the Deque type of queue, but it will be used to prioritize the transactions that get into the Heap or HeapBuffer queues.
transaction.approveERC20 (optional)Whether or not should the token approval be performed under the hood. Set this flag to true if you bridge an ERC20 token and didn't call the approveERC20 function beforehand.
transaction.overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice, etc.
transaction.approveOverrides (optional)Ethereum transaction overrides of the approval transaction. May be used to pass gasLimit, gasPrice, etc.
returnsPriorityOpResponse object.

Example

import * as zksync from "zksync-web3";import { ethers } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new zksync.Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new zksync.Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const USDC_ADDRESS = "0xd35cceead182dcee0f148ebac9447da2c4d449c4";const usdcDepositHandle = await wallet.deposit({  token: USDC_ADDRESS,  amount: "10000000",  approveERC20: true,});// Note that we wait not only for the L1 transaction to complete but also for it to be// processed by zkSync. If we want to wait only for the transaction to be processed on L1,// we can use `await usdcDepositHandle.waitL1Commit()`await usdcDepositHandle.wait();const ethDepositHandle = await wallet.deposit({  token: zksync.utils.ETH_ADDRESS,  amount: "10000000",});// Note that we wait not only for the L1 transaction to complete but also for it to be// processed by zkSync. If we want to wait only for the transaction to be processed on L1,// we can use `await ethDepositHandle.waitL1Commit()`await ethDepositHandle.wait();

Adding native token to zkSync

Getting the base cost for adding a token to zkSync

async addTokenBaseCost(params?: {    gasPrice?: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;}): Promise<BigNumber>

Inputs and outputs

NameDescription
params.gasPrice (optional)The gas price of the L1 transaction that will send the request for adding a token.
params.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
params.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
returnsThe base cost in ETH for requesting of adding a token.

Requesting to add a token

To add a new native token to zkSync, addToken function should be called on the zkSync smart contract:

async addToken(transaction: {    token: Address;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;    operatorTip?: BigNumberish;    overrides?: ethers.CallOverrides;}): Promise<PriorityOpResponse>

Inputs and outputs

NameDescription
tokenThe address of the token.
transaction.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
transaction.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
transaction.operatorTip (optional)If the ETH value passed with the transaction is not explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the base cost of the transaction. This value has no meaning for the Deque type of queue, but it will be used to prioritize the transactions that get into the Heap or HeapBuffer queues.
overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice etc.
returnsPriorityOpResponse object.

Example

import * as zksync from "zksync-web3";import { ethers } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new zksync.Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new zksync.Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const MLTT_ADDRESS = "0x690f4886c6911d81beb8130db30c825c27281f22";const addTokenHandle = await wallet.addToken({ token: MLTT_ADDRESS });// Note that we wait not only for the L1 transaction to complete but also for it to be// processed by zkSync. If we want to wait only for the transaction to be processed on L1,// we can use `await addTokenHandle.waitL1Commit()`await addTokenHandle.wait();

Requesting a withdrawal from L1

Getting the base cost for withdrawal

async withdrawBaseCost(params?: {    gasPrice?: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;}): Promise<BigNumber>
NameDescription
params.gasPrice (optional)The gas price of the L1 transaction that will send the request for a withdrawal.
params.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
params.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
returnsThe base cost in ETH for requesting a withdrawal.

Requesting a withdrawal

async requestL1Withdraw(transaction: {    token: Address;    amount: BigNumberish;    to: Address;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;    operatorTip?: BigNumberish;    overrides?: ethers.CallOverrides;}): Promise<PriorityOpResponse>

Inputs and outputs

NameDescription
tokenThe address of the token.
amountThe amount of the token to withdraw.
toThe address that will receive the withdrawn tokens on L1. .
transaction.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
transaction.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
transaction.operatorTip (optional)If the ETH value passed with the transaction is not explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the base cost of the transaction. This value has no meaning for the Deque type of queue, but it will be used to prioritize the transactions that get into the Heap or HeapBuffer queues.
overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice etc.
returnsPriorityOpResponse object.

Example

import * as zksync from "zksync-web3";import { ethers } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new zksync.Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new zksync.Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const gasPrice = await wallet.providerL1!.getGasPrice();const txCostPrice = await wallet.withdrawBaseCost({  gasPrice,});console.log(`Withdrawing the token will cost ${ethers.utils.formatEther(txCostPrice)} ETH`);const withdrawTx = await wallet.requestL1Withdraw({  token: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",  amount: "100",  to: wallet.address,  overrides: {    gasPrice,  },});await withdrawTx.wait();

Calling L2 smart contracts from L1

Getting the base cost for contract call

async executeBaseCost(params: {    ergsLimit: BigNumberish;    calldataLength: number;    gasPrice?: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;}): Promise<BigNumber>

Inputs and outputs

NameDescription
params.ergsLimitThe ergsLimit for the call.
params.calldataLengthThe length of the calldata in bytes.
params.gasPrice (optional)The gas price of the L1 transaction that will send the request for an execute call.
params.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
params.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
returnsThe base cost in ETH for requesting the contract call.

Requesting a contract call

async requestL1Execute(transaction: {    contractAddress: Address;    calldata: ethers.BytesLike;    ergsLimit: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;    operatorTip?: BigNumberish;    overrides?: ethers.CallOverrides;}): Promise<PriorityOpResponse>

Inputs and outputs

NameDescription
contractAddressThe address of the L2 contract to call.
calldataThe calldata of the call transaction. It can be encoded the same way as in Ethereum.
ergsLimitThe ergsLimit for the call.
transaction.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
transaction.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
transaction.operatorTip (optional)If the ETH value passed with the transaction is not explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the base cost of the transaction. This value has no meaning for the Deque type of queue, but it will be used to prioritize the transactions that get into the Heap or HeapBuffer queues.
overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice etc.
returnsPriorityOpResponse object.

Example

import * as zksync from "zksync-web3";import { BigNumber, ethers } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new zksync.Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new zksync.Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const gasPrice = await wallet.providerL1!.getGasPrice();// The calldata can be encoded the same way as for Ethereum.// Here is an example on how to get the calldata from an ABI:const abi = [  {    inputs: [],    name: "increment",    outputs: [],    stateMutability: "nonpayable",    type: "function",  },];const contractInterface = new ethers.utils.Interface(abi);const calldata = contractInterface.encodeFunctionData("increment", []);const ergsLimit = BigNumber.from(1000);const txCostPrice = await wallet.executeBaseCost({  gasPrice,  calldataLength: ethers.utils.arrayify(calldata).length,  ergsLimit,});console.log(`Executing the transaction will cost ${ethers.utils.formatEther(txCostPrice)} ETH`);const executeTx = await wallet.requestL1Execute({  calldata,  ergsLimit,  contractAddress: "0x19a5bfcbe15f98aa073b9f81b58466521479df8d",  overrides: {    gasPrice,    value: txCostPrice,  },});await executeTx.wait();

Deploying L2 smart contracts from L1

Getting the base cost for deploying smart contract

async deployContractBaseCost(params?: {    ergsLimit: BigNumberish;    bytecodeLength: BigNumberish;    calldataLength: BigNumberish;    gasPrice?: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;}): Promise<BigNumber>

Inputs and outputs

NameDescription
params.ergsLimitThe ergsLimit for the call.
params.bytecodeLengthThe length of the smart contract's zkEVM bytecode in bytes.
params.calldataLengthThe length of the calldata in bytes.
params.gasPrice (optional)The gas price of the L1 transaction that will send the request for contract deployment.
params.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
params.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
returnsThe base cost in ETH for requesting a contract deployment.

Requesting a deployment of a contract

async requestL1DeployContract(transaction: {    bytecode: ethers.BytesLike;    calldata: ethers.BytesLike;    ergsLimit: BigNumberish;    queueType?: PriorityQueueType;    opTree?: PriorityOpTree;    operatorTip?: BigNumberish;    overrides?: ethers.CallOverrides;}): Promise<PriorityOpResponse>

Inputs and outputs

NameDescription
bytecodeThe bytecode of the contract to deploy.
calldataThe constructor calldata. It needs to be encoded with our SDK. (see the example below).
ergsLimitThe ergsLimit for the call.
transaction.queueType (optional)The type of the queue to use. Currently, only the default value PriorityQueueType.Deque can be used.
transaction.opTree (optional)The operational tree to use. Currently, only the default value PriorityOpTree.Full can be used.
transaction.operatorTip (optional)If the ETH value passed with the transaction is not explicitly stated in the overrides, this field will be equal to the tip the operator will receive on top of the base cost of the transaction. This value has no meaning for the Deque type of queue, but it will be used to prioritize the transactions that get into the Heap or HeapBuffer queues.
overrides (optional)Ethereum transaction overrides. May be used to pass gasLimit, gasPrice etc.
returnsPriorityOpResponse object.

Example

import { Wallet, Provider, ContractFactory } from "zksync-web3";import { ethers, BigNumber } from "ethers";const PRIVATE_KEY = "0xc8acb475bb76a4b8ee36ea4d0e516a755a17fad2e84427d5559b37b544d9ba5a";const zkSyncProvider = new Provider("https://zksync2-testnet.zksync.dev/");const ethereumProvider = ethers.getDefaultProvider("goerli");const wallet = new Wallet(PRIVATE_KEY, zkSyncProvider, ethereumProvider);const gasPrice = await wallet.providerL1!.getGasPrice();// The zkEVM bytecode of the contract.const bytecode = "0x...";// ABI of the smart contract. Here the smart contract// takes a single parameter to its constructor of an `address` type.const abi = [  {    inputs: [      {        internalType: "address",        name: "newGovernance",        type: "address",      },    ],    stateMutability: "nonpayable",    type: "constructor",  },];// Please note that the constructor calldata must be encoded by our SDK.// Here is an example of how it can be done:// 1. Create a contract factory.const factory = new ContractFactory(abi, bytecode, wallet);// 2. Get the L2 deployment transaction's data. The calldata on L1 is encoded the same as on L2.// `wallet.address` here is a constructor parameter.const l2DeployCalldata = factory.getDeployTransaction(wallet.address).data!;// 3. Remove the first 32 bytes.`l2DeployCalldata is a concatenation of `keccak256(bytecode)` and the actual calldata.// Please note that to save gas the calldata that we send to L1 doesn't have the keccak256(bytecode) part.// That's why we omit the first 32 bytes.const calldata = ethers.utils.arrayify(l2DeployCalldata).slice(32);const ergsLimit = BigNumber.from(1000);const txCostPrice = await wallet.deployContractBaseCost({  gasPrice,  calldataLength: ethers.utils.arrayify(calldata).length,  bytecodeLength: ethers.utils.arrayify(bytecode).length,  ergsLimit,});console.log(`Deploying the contract will cost ${ethers.utils.formatEther(txCostPrice)} ETH`);const deployTx = await wallet.requestL1DeployContract({  calldata,  ergsLimit,  bytecode,  overrides: {    gasPrice,  },});await deployTx.wait();
Last updated: 8/23/2022, 9:04:05 AM
Previous
Accounts: overview
Next
Contracts
Edit on GitHub
  • Supported classes
  • Approving deposit of tokens
  • Depositing tokens to zkSync
  • Adding native token to zkSync
  • Requesting a withdrawal from L1
  • Calling L2 smart contracts from L1
  • Deploying L2 smart contracts from L1