Home
Developer guide
Getting started
  • Developer guide
  • Developer quickstart
  • Solidity/Vyper support
  • Deploying contracts
  • Frontend integration
  • L1 -> L2 communication
  • L2 -> L1 communication
  • Tutorial: Hello World
  • Tutorial: Cross-chain governance
  • Home
  • /
  • Developer guide
  • /
  • L1 -> L2 communication

L1 -> L2 communication

What's on the page
  • Structure
  • Using contract interface in your project
  • Deposit
  • AddToken
  • Withdraw
  • Execute
  • Deploy Contract

This section describes the interface for interaction with zkSync from L1. It assumes that you are already familiar with the basic concepts of working with the priority queue. If you are new to this topic, you can read the conceptual introduction here. If you want to dive straight into the code, then you can read the cross-chain governance tutorial.

Structure

Besides the input parameters of the operations, all of the requests contain the last two parameters: the _queueType and the _opTree. These are the important parts of the censorship resistant protocol called priority queue. For the testnet alpha preview, these two values should be supplied as 0, i.e. Operations.QueueType.Deque and Operations.OpTree.Full respectively.

To prevent the users from requesting too many heavy operations from the operator, the user must provide the fee to be paid to the zkSync operator in ETH. Each transaction has base cost in gas and the layer 2 tip, which is equal to the basic cost of the requested transaction subtracted from the ETH value passed with the call.

Please note that the basic costs are defined in gas and not in ETH, so the actual amount of ether that the submission of the transaction will cost depends on the transaction gas price. Generally the flow for calling any of these methods should be the following:

  1. Fetch the gas price that you will use during the transaction call.
  2. Get the base cost for the transaction.
  3. Call the transaction, while passing the needed value with the call.

Please note that since transactions in Operations.QueueType.Deque are processed sequentially, it does not make sence to pass more value that the minimal required.

Using contract interface in your project

To interact with the zkSync bridge contract using Solidity, you need to use the zkSync contract interface. There are two main ways to get it:

  • By importing it from the @matterlabs/zksync-contracts npm package. (preferred)
  • By downloading the contracts from the repo.

The @matterlabs/zksync-contracts package can be installed by running the following command:

yarn add -D @matterlabs/zksync-contracts

In all of the examples below we assume that the interface is accessed via the @matterlabs/zksync-contracts npm package.

Deposit

Getting the base cost

Deposit is free for now

For the time of the testnet, depositing funds to zkSync is free (the base cost is 0). However, you should design your system (and especially L1 smart contracts) in a way that can be adapted in case the zkSync deposit base fee becomes non-zero.

The deposit base cost will be non-zero during priority mode, so it is crucial for long-term stability to keep in mind that deposits may require a fee.

function depositBaseCost(
    uint256 gasPrice,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) public view returns (uint256)
  • gasPrice is a parameter that contains the transaction gas price.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

The view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction in ETH.

Interface

There are two separate methods used to bridge ETH and ERC-20 tokens from Ethereum to zkSync.

/// @param _amount ETH amount
/// @param _zkSyncAddress The receiver Layer 2 address
/// @param _queueType Type of data structure in which the priority operation should be stored
/// @param _opTree Priority operation processing type - Common or OnlyRollup
function depositETH(
    uint256 _amount,
    address _zkSyncAddress,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable nonReentrant
  • _amount is a parameter which contains the number of ETH in wei to deposit. The rest of the suplied ETH is used to pay for the base cost of the transactions and the L2 tip.
  • _zkSyncAddress is a parameter which specifies which address should the deposited funds go to.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.
/// @param _token Token address
/// @param _amount Token amount
/// @param _zkSyncAddress Receiver Layer 2 address
/// @param _queueType Type of data structure in which the priority operation should be stored
/// @param _opTree Priority operation processing type - Common or OnlyRollup
function depositERC20(
    IERC20 _token,
    uint256 _amount,
    address _zkSyncAddress,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable nonReentrant
  • _token is a parameter that defines the address of the ERC-20 token to deposit the funds from. Please note that the zkSync smart contract should have the necessary allowance set before this method is being called.
  • _amount is a parameter that contains the amount of the token to deposit.
  • _zkSyncAddress is a parameter that specifies which address should the deposited funds go to.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

Examples

Solidity

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/contracts/interfaces/IZkSync.sol";
// Importing `Operations` library which has the `Operations.QueueType` and `Operations.OpTree` types
import "@matterlabs/zksync-contracts/contracts/libraries/Operations.sol";

contract Example {
    function callZkSync(
        // The address of the zkSync smart contract.
        // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
        address zkSyncAddress,
    ) external payable {
        IZkSync zksync = IZkSync(zkSyncAddress);

        // Example of depositing ETH.
        // Even though currently deposits are free, the contracts
        // should be built in mind that deposits may require fee in the future.
        zksync.depositETH{value: msg.value}(
            // The amount to deposit
            1 ether,
            // The address of the recipient of the funds on L2.
            0xf7109c2dd48d4ec903c8fb123db57a40856566bf,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full
        );

        // Example of depositing an ERC20 token.
        // Make sure that your contract has ERC20 balance before running this code.
        // Also, make sure tha the zkSync smart contract has enough allowance.
        // Even though currently deposits are free, the contracts
        // should be built in mind that deposits may require fee in the future.
        zksync.depositERC20{value: msg.value}(
            // The ERC20 token address to deposit.
            0xd35cceead182dcee0f148ebac9447da2c4d449c4s,
            // The amount to deposit
            1000000000,
            // The address of the recipient of the funds on L2.
            0xf7109c2dd48d4ec903c8fb123db57a40856566bf,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full

        );
    }
}

zksync-web3

import { Wallet, Provider, ContractFactory } zksync from "zksync-web3";import { ethers } 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);// ETH depositconst ethDepositTx = await wallet.deposit({    token: utils.ETH_ADDRESS,    amount: '1000'});// Wait until the deposit is processed by zkSyncawait ethDepositTx.wait();// ERC20 depositconst ercDepositTx = await wallet.deposit({    token: '0xe8e98bda32a1842c8e941267f11b705c51f496de',    amount: '1000000',    // If the zkSync contract does not have the necessary allowance yet,    // we can set this flag to approve the deposit    approveERC20: true});// Wait until the deposit is processed by zkSyncawait ercDepositTx.wait();

AddToken

Getting the base cost

function addTokenBaseCost(
    uint256 _gasPrice,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) public view returns (uint256)
  • gasPrice is a parameter that contains the transaction gas price.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

The view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction in ETH.

Interface

/// @param _token Token address
/// @param _queueType Type of data structure in which the priority operation should be stored
function addToken(
    IERC20 _token,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable nonReentrant {

The addToken transaction is used to add a first-class citizen token to zkSync.

  • _token is a parameter that defines the address of the ERC-20 token to deposit the funds from.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

With the method call, some amount of ETH should be supplied to cover the base cost of the transaction + layer 2 operator tip.

Examples

Solidity

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/contracts/interfaces/IZkSync.sol";
// Importing `Operations` library which has the `Operations.QueueType` and `Operations.OpTree` types
import "@matterlabs/zksync-contracts/contracts/libraries/Operations.sol";

contract Example {
    function callZkSync(
        // The address of the zkSync smart contract.
        // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
        address zkSyncAddress,
    ) external payable {
        IZkSync zksync = IZkSync(zkSyncAddress);

        zksync.addToken{value: msg.value}(
            // The L1 address of the ERC20 token to add.
            0xd35cceead182dcee0f148ebac9447da2c4d449c4s,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full
        );
    }
}

zksync-web3

import { Wallet, Provider, ContractFactory } zksync from "zksync-web3";import { ethers } 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();const txCostPrice = await wallet.addTokenBaseCost({    gasPrice});console.log(`Adding the token will cost ${ethers.utils.formatEther(txCostPrice)} ETH`);const addTokenTx = await wallet.addToken({    token: '0xf8b74a809db32c3289150efa3259b7198f82affb',    overrides: {        gasPrice    }});await addTokenTx.wait();

Withdraw

Getting the base cost

function withdrawBaseCost(
    uint256 _gasPrice,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) public view returns (uint256)
  • gasPrice is a parameter that contains the transaction gas price.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

The view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction in ETH.

Interface

/// @param _token Token address
/// @param _to Address of account to withdraw funds to
/// @param _amount Amount of funds to withdraw
/// @param _queueType Type of data structure in which the priority operation should be stored
/// @param _opTree Priority operation processing type - Common or OnlyRollup
function requestWithdraw(
    address _token,
    uint256 _amount,
    address _to,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable nonReentrant {

The Withdraw transaction allows users to withdraw first-class citizen tokens to L1.

  • _token is a parameter that defines the address of the ERC-20 token to deposit the funds from.
  • _amount is a parameter that contains the amount of the token to deposit.
  • _to is a parameter that contains the address to which to withdraw the funds.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

With the method call, some amount of ETH should be supplied to cover the base cost of the transaction + layer 2 operator tip.

This operation will request a withdrawal of _amount of the token with address _token from the msg.sender zkSync account to the _to address on L1.

Examples

Solidity

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/contracts/interfaces/IZkSync.sol";
// Importing `Operations` library which has the `Operations.QueueType` and `Operations.OpTree` types
import "@matterlabs/zksync-contracts/contracts/libraries/Operations.sol";

contract Example {
    function callZkSync(
        // The address of the zkSync smart contract.
        // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
        address zkSyncAddress,
    ) external payable {
        IZkSync zksync = IZkSync(zkSyncAddress);

        zksync.requestWithdraw{value: msg.value}(
            // The address of the token to withdraw.
            // In this example it is ETH.
            0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE,
            // 1 ETH
            1000000000000000000,
            // The address to withdraw the ETH to.
            0xf7109c2dd48d4ec903c8fb123db57a40856566bf,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full
        );
    }
}

zksync-web3

import { Wallet, Provider, ContractFactory } zksync from "zksync-web3";import { ethers } 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();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();

Execute

Getting the base cost

function executeBaseCost(
    uint256 _gasPrice,
    uint256 _ergsLimit,
    uint32 _calldataLength,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) public view returns (uint256)
  • _gasPrice is a parameter that contains the transaction gas price.
  • _ergsLimit is a parameter that contains the ergs limit of the transaction call. You can learn more about ergs and the zkSync fee system here.
  • _calldataLength is a parameter that contains the length of the calldata in bytes.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

The view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction in ETH.

Interface

function requestExecute(
    address _contractAddressL2,
    bytes memory _calldata,
    uint256 _ergsLimit,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable nonReentrant
  • _contractAddressL2 is a parameter that defines the address of the contract to be called.
  • _calldata is a parameter that contains the calldata of the transaction call. It can be encoded the same way as on Ethereum.
  • _ergsLimit is a parameter that contains the ergs limit of the transaction call. You can learn more about ergs and the zkSync fee system here.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

With the method call, some amount of ETH should be supplied to cover the base cost of the transaction + layer 2 operator tip.

Examples

Solidity

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/contracts/interfaces/IZkSync.sol";
// Importing `Operations` library which has the `Operations.QueueType` and `Operations.OpTree` types
import "@matterlabs/zksync-contracts/contracts/libraries/Operations.sol";

contract Example {
    function callZkSync(
        // The address of the zkSync smart contract.
        // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
        address zkSyncAddress,
    ) external payable {
        IZkSync zksync = IZkSync(zkSyncAddress);

        zksync.requestExecute{value: msg.value}(
            // The address of the L2 contract to call
            0xdba0833e8c4b37cecc177a665e9207962e337299,
            // Encoding the calldata for the execute
            abi.encodeWithSignature("someMethod()"),
            // Ergs limit
            1000,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full
        );
    }
}

zksync-web3

import { Wallet, Provider, ContractFactory } zksync 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 calldata can be encoded the same way as for Ethereumconst calldata = "0x...";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    }});await executeTx.wait();

Deploy Contract

Getting the base cost

function deployContractBaseCost(
    uint256 _gasPrice,
    uint256 _ergsLimit,
    uint32 _bytecodeLength,
    uint32 _calldataLength,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) public view returns (uint256)
  • _gasPrice is a parameter that contains the transaction gas price.
  • _ergsLimit is a parameter that contains the ergs limit of the transaction call. You can learn more about ergs and the zkSync fee system here.
  • _bytecodeLength is a parameter that contains the length of the bytecode in bytes.
  • _calldataLength is a parameter that contains the length of the calldata in bytes.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

The view function returns the amount of ETH that is needed to be supplied by the user to cover the base cost of the transaction in ETH.

Interface

function requestDeployContract(
    bytes memory _bytecode,
    bytes memory _calldata,
    uint256 _ergsLimit,
    Operations.QueueType _queueType,
    Operations.OpTree _opTree
) external payable
  • _bytecode is a parameter that contains the bytecode of the contract to be deployed.
  • _calldata is a parameter which contains the contstructor calldata. Unlike the execute request, the calldata here must be encoded with our SDK (see the example below).
  • _ergsLimit is a parameter that contains the ergs limit of the transaction call. You can learn more about ergs and the zkSync fee system here.
  • _queueType is a parameter required for the priority mode functionality. For the testnet, Operations.QueueType.Deque should always be supplied.
  • _opTree is a parameter required for the priority mode functionality. Operations.OpTree.Full should always be supplied.

With the method call, some amount of ETH should be supplied to cover the base cost of the transaction + layer 2 operator tip.

Examples

Solidity

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/contracts/interfaces/IZkSync.sol";
// Importing `Operations` library which has the `Operations.QueueType` and `Operations.OpTree` types
import "@matterlabs/zksync-contracts/contracts/libraries/Operations.sol";

contract Example {
    function callZkSync(
        // The address of the zkSync smart contract.
        // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
        address zkSyncAddress,
        bytes contractBytecode,
        bytes constructorCalldata
    ) external payable {
        IZkSync zksync = IZkSync(zkSyncAddress);

        zksync.requestDeployContract{value: msg.value}(
            // The zkEVM bytecode of the contract
            contractBytecode,
            // The constructor calldata
            constructorCalldata,
            // Ergs limit
            10000,
            // The queue type
            Operations.QueueType.Deque,
            // The operation tree type
            Operations.OpTree.Full
        );
    }
}

zksync-web3

import { Wallet, Provider, ContractFactory } zksync 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:const factory = new ContractFactory(    abi,    bytecode,    wallet);// `wallet.address` here is a constructor parameter.const l2DeployCalldata = counterFactory.getDeployTransaction(wallet.address).data!;// `l2DeployCalldata is a concatenation of `keccak256(bytecode)` and the actuall 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.arraify(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
Frontend integration
Next
L2 -> L1 communication
Edit on GitHub
  • Structure
  • Using contract interface in your project
  • Deposit
  • AddToken
  • Withdraw
  • Execute
  • Deploy Contract