L1 -> L2 communication
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:
- Fetch the gas price that you will use during the transaction call.
- Get the base cost for the transaction.
- Call the transaction, while passing the needed
valuewith 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-contractsnpm 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-contractsIn 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)gasPriceis a parameter that contains the transaction gas price._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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_amountis 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._zkSyncAddressis a parameter which specifies which address should the deposited funds go to._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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_tokenis 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._amountis a parameter that contains the amount of the token to deposit._zkSyncAddressis a parameter that specifies which address should the deposited funds go to._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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)gasPriceis a parameter that contains the transaction gas price._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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.
_tokenis a parameter that defines the address of the ERC-20 token to deposit the funds from._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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)gasPriceis a parameter that contains the transaction gas price._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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.
_tokenis a parameter that defines the address of the ERC-20 token to deposit the funds from._amountis a parameter that contains the amount of the token to deposit._tois a parameter that contains the address to which to withdraw the funds._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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)_gasPriceis a parameter that contains the transaction gas price._ergsLimitis a parameter that contains theergslimit of the transaction call. You can learn more aboutergsand the zkSync fee system here._calldataLengthis a parameter that contains the length of the calldata in bytes._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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_contractAddressL2is a parameter that defines the address of the contract to be called._calldatais a parameter that contains the calldata of the transaction call. It can be encoded the same way as on Ethereum._ergsLimitis a parameter that contains theergslimit of the transaction call. You can learn more aboutergsand the zkSync fee system here._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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)_gasPriceis a parameter that contains the transaction gas price._ergsLimitis a parameter that contains theergslimit of the transaction call. You can learn more aboutergsand the zkSync fee system here._bytecodeLengthis a parameter that contains the length of the bytecode in bytes._calldataLengthis a parameter that contains the length of the calldata in bytes._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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_bytecodeis a parameter that contains the bytecode of the contract to be deployed._calldatais a parameter which contains the contstructor calldata. Unlike the execute request, the calldata here must be encoded with our SDK (see the example below)._ergsLimitis a parameter that contains theergslimit of the transaction call. You can learn more aboutergsand the zkSync fee system here._queueTypeis a parameter required for the priority mode functionality. For the testnet,Operations.QueueType.Dequeshould always be supplied._opTreeis a parameter required for the priority mode functionality.Operations.OpTree.Fullshould 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();