NAV Navbar
typescript

Overview

ZK Sync is a scaling and privacy engine for Ethereum. Its current functionality scope includes low gas transfers of ETH and ERC20 tokens in the Ethereum network. This document is a description of the JS library that can be used to interact with ZK Sync.

ZK Sync is built on ZK Rollup architecture. ZK Rollup is an L2 scaling solution in which all funds are held by a smart contract on the mainchain, while computation and storage are performed off-chain. For every Rollup block, a state transition zero-knowledge proof (SNARK) is generated and verified by the mainchain contract. This SNARK includes the proof of the validity of every single transaction in the Rollup block. Additionally, the public data update for every block is published over the mainchain network in the cheap calldata.

This architecture provides the following guarantees:

In other words, ZK Rollup strictly inherits the security guarantees of the underlying L1.

Getting started

To use ZK Sync, users need funds in the ZK Sync network. There are two ways to obtain funds:

  1. Depositing from Ethereum network using a transaction to the ZK Sync smart contract on the mainchain.
  2. Receiving funds from anyone that already has funds in the network.

All transactions in the ZK Sync network are authorized with a signature using private keys. Public keys and addresses can be derived from the private key. Public address in the ZK Sync network is required to receive funds. For convenience, it is possible to use Ethereum private key to derive a ZK Sync private key.

All operations inside ZK Sync are arranged in blocks. After network operator creates block it gets published to Ethereum with a Commit transaction. When a block is committed, its state is not final. After a couple of minutes, ZK proof for the correctness of this block is produced. This proof is published to Ethereum using Verify transaction and after that state is considered final. Multiple blocks can be committed but not verified yet.

There are two types of operations:

  1. Priority operations
  2. Transactions

Priority operations are initiated from the Ethereum network using transactions. For example, the user creates a deposit transaction to move funds from Ethereum to ZK Sync. Priority operation can be identified with a numerical id or hash of the ethereum transaction that created it. Priority operations can't be ignored by the network.

Transactions are signed by some accounts in the ZK Sync network and should be delivered to the network operator node.

Transactions are identified by the hash of its serialized representation.

Tutorial

Here we show how to

  1. Connect to the ZK Sync network.
  2. Create a private key in the ZK Sync.
  3. Move funds from Ethereum to the ZK Sync.
  4. Make fast transfers.
  5. Move funds from the ZK Sync to Ethereum.

Adding dependencies

yarn add zksync
yarn add ethers # For interactions with ETH network, ethers is peer dependency of zksync.

Add imports.

import {ethers} from "ethers";
import * as zksync from "zksync";

Alternatively.

const ethers = require("ethers");
const zksync = require("zksync");

Wasm

We use Wasm for the cryptography-related calculations. For the node.js environment the instructions from the previous chapter remain intact, but if zksync.js is going to be used in the browser, several additional steps are required:

  1. Consult your bundler documentation about using wasm.
  2. Make sure that your server correctly serves wasm files.

Example of how we configure our basic web client.

const zksync_promise = import('zksync');

// In the async function that does initialization in your project call:

const zksync = await zksync_promise;

Example of the nginx.conf entry adding support for the Wasm content type

location ~ ^/client {
    include /etc/nginx/mime.types;
    types {
        application/wasm                      wasm;
    }
    //..
}

Connecting to the Sync network

To interact with Sync network users have to know the endpoint of the operator node.

const syncProvider = await zksync.getDefaultProvider("testnet");

// When using WebSocket provider connection should be closed manually when needed using.
await syncProvider.disconnect();

The alternative provider is an HTTP provider.

const syncProvider = await zksync.SyncProvider.newHttpProvider("https://testnet.zksync.dev/jsrpc");

Most operations require some read-only access to the Ethereum network. We use ethers library to interact with Ethereum.

Addresses of the Sync network contracts should be known in advance, for convenience now we can get these addresses from Sync network operator using syncProvider.

const ethersProvider = new ethers.getDefaultProvider('rinkeby');

Creating Wallet

To use ZK Sync network we provide Wallet object. It can be used to sign transactions with keys stored in Signer and send transaction to the ZK Sync network using connection provided by Provider.

Wallet is wrapper around ethers.Signer object that is used to sign ethereum transactions, zksync transactions signed using Signer.

For convenience signer keys can be derived from ethereum signature of the specific message.

// Create ethereum wallet using ethers.js
const ethWallet = ethers.Wallet.fromMnemonic( MNEMONIC ).connect(ethersProvider);
// Derive zksync.Signer from ethereum wallet.
const syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider);

Moving funds from ethereum to the Sync network

We are going do deposit some funds from our ethereum wallet into sync account. For that we should create specific ethereum transaction. We can create this transaction using depositToSyncFromEthereum function.

Here we are moving "ETH" token. To transfer supported ERC20 token we should use ERC20 address or ERC20 symbol instead of "ETH".

We are going to deposit 1.0 ETH to our account, and we are going to pay at most 0.1 ETH in fees (depends on the gas price). ZK Sync account will receive 1.0 ETH.

const deposit = await syncWallet.depositToSyncFromEthereum({
    depositTo: syncWallet.address(),
    token: "ETH",
    amount: ethers.utils.parseEther("1.0"),
    maxFeeInETHCurrency: ethers.utils.parseEther("0.1")
});

After transaction is submitted to the Ethereum we can track its progress using returned object.

// If we want to wait until deposit is processed by the SyncNetwork.
const depositReceipt = await deposit.awaitReceipt();

// If we want to wait until deposit is processed and finalized using ZKP.
const depositReceipt = await deposit.awaitVerifyReceipt();

Unlocking sync wallet

To send funds inside ZK Sync network, account should have zksync key pair associated with it. It can be done using change public key transaction.

if (! await syncWallet.isSigningKeySet()) {
    if (await syncWallet.getAccountId() == undefined) {
        throw new Error("Account should be registered in the ZK Sync network (i.e. have had funds at least once)");
    } 

    const changePubkey = await syncWallet.setSigningKey();

    // Wait till transaction is committed
    await changePubkey.awaitReceipt();
}

Get balance in the ZK Sync network

To get balance of the ZK Sync account you can use getBalance method. Committed state is last state of the account that may or may not be finalized by ZK proof. Verified is referred to finalized by ZK proof state of the account.

const committedETHBalance = await syncWallet.getBalance("ETH");
const verifiedETHBalance = await syncWallet.getBalance("ETH", "verified");

To get all tokens of this account you can use getAccountState.

const state = await syncWallet.getAccountState();

const committedBalances = state.committed.balances;
const committedETHBalance = committedBalances["ETH"];

const verifiedBalances = state.verified.balances;
const committedETHBalance = verifiedBalances["ETH"];

Get balance in the Ethereum network

For convenience, there is a method with a similar signature that can be used to query balance in the Ethereum network.

const onchainETHBalance = await syncWallet.getEthereumBalance("ETH");

Moving funds inside ZK Sync network

Let's create a second wallet and transfer funds to it.

const ethWallet2 = ethers.Wallet.fromMnemonic( MNEMONIC2 ).connect(ethersProvider);
const syncWallet2 = await zksync.SyncWallet.fromEthSigner(ethWallet2, syncProvider);

To transfer funds from one Sync account to another we can use the syncTransfer method. We are going to transfer 0.999 ETH to another account and pay 0.001 ETH as a fee to the operator. (ZK Sync balance of the sender is going to be decreased by 0.999 + 0.001 ETH).

// significant digits in the transfer transaction is limited, that is why we use utils to check/strip significant digits. 
const amount = zksync.utils.closestPackableTransactionAmount(ethers.utils.parseEther("0.999")); 
const fee = zksync.utils.closestPackableTransactionFee(ethers.utils.parseEther("0.001")); 

const transfer= await syncWallet.syncTransfer({
    to: syncWallet2.address(),
    token: "ETH",
    amount,
    fee,
});

To track progress of this transaction we can use returned transaction.

const transferReceipt = await transfer.awaitReceipt();

Moving funds out of the Sync network

To withdraw funds from Sync account to ethereum account we can use the withdrawTo method.

We are going to withdraw 0.998 ETH from the second sync account to the second ethereum wallet and pay 0.001 ETH as a fee. (ZK Sync balance is going to be decreased by 0.998 + 0.001 ETH ).

// significant digits for fee in the withdraw transaction is limited, that is why we use utils to check/strip significant digits. 
const fee = zksync.utils.closestPackableTransactionFee(ethers.utils.parseEther("0.001")); 

const withdraw= await syncWallet2.withdrawTo({
    ethAddress: ethWallet2.address,
    token: "ETH",
    amount: ethers.utils.parseEther("0.998"),
    fee,
});

Funds will be withdrawn to the target wallet after ZKP for sync block with this operation is produced and verified. We can wait until ZKP verification is completed using a returned transaction.

await withdraw.awaitVerifyReceipt();

Providers

JSON-RPC protocol is used to communicate with Sync network nodes. Provider is used to abstract details of the communication and provides useful API for interaction with Sync network.

We support HTTP and WebSocket transport protocol for JSON-RPC communications. WebSocket transport is preferred since it supports subscriptions. HTTPTransport and WSTransport classes are used to implement details of communication, but usually, you don't need to deal with these objects directly.

Sync provider

Get default provider for network

import * as zksync from "zksync";


const syncWSProvider = await zksync.getDefaultProvider("testnet")

// ..

// Later to close connection.
await syncWSProvider.disconnect();

Used to connect to the common endpoint for the given network over WebSocket transport.

Supported networks are: "testnet", "localhost".

Create WebSocket provider

Creating provider over WebSocket transport. This call will create WS connection that should be closed.

import * as zksync from "zksync";


const syncWSProvider = await zksync.Provider.newWebsocketProvider(
    "wss://testnet.matter-labs.io/jsrpc-ws"
);

// ..

// Later to close connection.
await syncWSProvider.disconnect();

Create HTTP provider

Creating provider over HTTP transport.

import * as zksync from "zksync";

const syncHTTPProvider = await zksync.Provider.newHttpProvider(
    "https://testnet.zksync.dev/jsrpc"
);

Submit transaction

Signature

async submitTx(tx: any): Promise<string>;

Inputs and outputs

Name Description
tx Signed Sync transaction (see types, for detailed description)
returns 0x-prefixed hex-encoded hash of the transaction

Example

import * as zksync from "zksync";

const syncWSProvider = await zksync.getDefaultProvider("testnet")
const signedTransferTx = {
    type: "Transfer",
    from: "0x..address1",
    to: "0x..address2",
    token: 0, // id of the ETH token
    amount: "1000000000000000000", // 1 Ether in Wei
    fee: "10000000000000000", // 0.01 Ether in Wei
    nonce: 0,
    signature: {
        pubKey: "dead..", // hex encoded packed public key of signer (32 bytes)
        signature: "beef.." // hex encoded signature of the tx (64 bytes)
    }
};


const transactionHash = await syncWSProvider.submitTx(signedTransferTx);
// 0x..hash (32 bytes)

Get contract addresses

Signature

async getContractAddress(): Promise<ContractAddress>;

Inputs and outputs

Name Description
returns Addresses of the Sync network smart contracts (see types, for detailed description)

Example

import * as zksync from "zksync";

const syncWSProvider = await zksync.SyncProvider.getDefaultProvider("testnet")

const contractAddresses = await syncWSProvider.getContractAddress();

Returns

{
    "mainContract": "0xab..cd",
    "govContract": "0xef..12"
}

Get tokens

Signature

async getTokens(): Promise<Tokens>;

Inputs and outputs

Name Description
returns All supported tokens (see types, for detailed description)

Example

import * as zksync from "zksync";

const syncWSProvider = await zksync.SyncProvider.getDefaultProvider("testnet")

const contractAddresses = await syncWSProvider.getTokens();

Returns

{
  "ERC20-1": {
    "address": "0xbeeb9f55d523918f9cd2979a454610f673c2885e",
    "id": 1,
    "symbol": null
  },
  "ETH": {
    "address": "0000000000000000000000000000000000000000",
    "id": 0,
    "symbol": "ETH"
  }
}

Get account state by address

Signature

async getState(address: Address): Promise<AccountState>;

Inputs and outputs

Name Description
address 0x-prefixed hex-encoded address of the Sync account.
returns Detailed state of the Sync account, including balances, nonce. (see types, for detailed description)

Returns

{
    "address": "0x2d5bf7a3ab29f0ff424d738a83f9b0588bc9241e",
    "id": 1, // optional
    "committed": {
        "balances": {
            "ETH": "1000000000000000000", // 1 Ether in Wei 
        },
        "nonce": 1,
    },
    "verified": {
        "balances": {
            "ETH": "1000000000000000000", // 1 Ether in Wei 
            // ERC20 token
            "FAU": "1000000000000000000" 
        },
        "nonce": 0,
    }
}

Get transaction receipt

Signature

async getTxReceipt(txHash: string): Promise<TransactionReceipt>;

Inputs and outputs

Name Description
txHash sync-tx:-prefixed hex-encoded hash of the Sync transaction.
returns Receipt of this transaction (see types, for detailed description)

Returns

// Not executed yet
{
    "executed": false
}

// Success
{
    "executed": true,
    "success": true,
    "block": {
      "blockNumber": 658,
      "committed": true,
      "verified": true
    }
}

// Failure
{
    "executed": true,
    "success": true,
    "failReason": "Nonce mismatch",
    "block": {
      "blockNumber": 658,
      "committed": true,
      "verified": true
    }
}

Wait for transaction receipt

Similar to Get transaction receipt but this method will return when a given transaction is committed or verified in the Sync network.

Signature

async notifyTransaction(
    hash: string, 
    action: "COMMIT" | "VERIFY"
): Promise<TransactionReceipt> ;

Inputs and outputs

Name Description
txHash sync-tx:-prefixed hex-encoded hash of the Sync transaction.
action "COMMIT" or "VERIFY"
returns Receipt of this transaction (see types, for detailed description)

Example

import * as zksync from "zksync";

const syncWSProvider = await zksync.getDefaultProvider("testnet")

const receipt = await syncWSProvider.notifyTransaction(
    "sync-tx:1111111111111111111111111111111111111111111111111111111111111111",
    "COMMIT"
);

Get priority operation receipt

Signature

async getPriorityOpStatus(
    serialId: number
): Promise<PriorityOperationReceipt>;

Inputs and outputs

Name Description
serialId Numerical id of the priority operation, can be found in logs of the ethereum transaction that created this operation (e.g. deposit)
returns Receipt of this priority operation (see types, for detailed description)

Returns

{
    "executed": true,
    "block": {
      "blockNumber": 658,
      "committed": true,
      "verified": true
    }
}

Wait for priority operation receipt

Similar to Get priority operation receipt but this method will return when given priority operation is committed or verified in the Sync network.

Signature

async notifyPriorityOp(
    serialId: number, 
    action: "COMMIT" | "VERIFY"
): Promise<PriorityOperationReceipt>;

Inputs and outputs

Name Description
serialId Numerical id of the priority operation, can be found in logs of the ethereum transaction that created this operation (e.g. deposit)
action "COMMIT" or "VERIFY"
returns Receipt of this priority operation (see types, for detailed description)

Example

import * as zksync from "zksync";

const syncWSProvider = await zksync.getDefaultProvider("testnet")

const receipt = await syncWSProvider.notifyPriorityOp(
    178, // priority op id
    "COMMIT"
);

Current token set.

Provider stores list of the available tokens with methods for working with them. (see working with tokens)

Signature

public tokenSet: TokenSet;

Get transaction fee from the server.

Performs a query to the server, obtaining an acceptable transaction fee for transactions.

Signature

async getTransactionFee(
    txType: "Withdraw" | "Transfer",
    amount: utils.BigNumberish,
    tokenLike: TokenLike
): Promise<utils.BigNumber>;

Inputs and outputs

Name Description
txType Type of the transaction.
amount Amount of tokens in the transaction.
tokenLike Token used in the transaction.
returns Fee for the transaction is already packable and ready to be used in the transaction.

ETH Proxy

ETHProxy class is used to simplify some communication with Ethereum network.

Create ETH Proxy

Signature

constructor(
    private ethersProvider: ethers.providers.Provider,
    private contractAddress: ContractAddress
);

Inputs and outputs

Name Description
ethersProvider ethers.js provider connected to ethereum node
contractAddress Addresses of the Sync network contracts

Example

import * as zksync from "zksync";
import {ethers} from "ethers";


const ethersProvider = new ethers.getDefaultProvider('rinkeby');
const syncWSProvider = await zksync.SyncProvider.getDefaultProvider("testnet")

const ethProxy = new zksync.ETHProxy(
    ethersProvider, 
    syncProvider.contractAddress
);

Resolve token id

To sign Sync transaction users have to know the unique numerical id of the given token. It can be retrieved from the ZK Sync network governance contract.

Signature

async resolveTokenId(token: TokenAddress): Promise<number>;

Inputs and outputs

Name Description
token Ethereum token address (ERC20 contract address)
returns Numerical identifier of the given token inside Sync network.

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

const ethersProvider = new ethers.getDefaultProvider("rinkeby");
const syncProvider = await zksync.getDefaultProvider("testnet");
const ethProxy = new zksync.ETHProxy(
    ethersProvider, 
    syncProvider.contractAddress
);

const ethId = await ethProxy.resolveTokenId("0x0000000000000000000000000000000000000000"); // ETH token address is 0x0..0

 // ERC20 token if it is supported, >= 1
const erc20Id = await ethProxy.resolveTokenId("0xFab46E002BbF0b4509813474841E0716E6730136");

Estimate deposit fee

Estimates fee required for submitting deposit transactions. Fee for deposit always is always paid in ETH token.

Signature

async estimateDepositFeeInETHToken(token: TokenLike, gasPrice?: utils.BigNumber): Promise<utils.BigNumber>;

Inputs and outputs

Name Description
token Ethereum token identifier (symbol or address)
gasPrice Gas price that will be used for transaction.
returns Fee that has to be paid for deposit

Estimate emergency withdraw fee

Estimates fee required for submitting emergency withdraw transaction. Fee for emergency withdraw is always paid in ETH token.

Signature

async estimateEmergencyWithdrawFeeInETHToken(gasPrice?: utils.BigNumber): Promise<utils.BigNumber>;

Inputs and outputs

Name Description
gasPrice Gas price that will be used for transaction.
returns Fee that has to be paid for emergency withdraw

Accounts

Wallet

Wallet object is used to interact with the ZK Sync network. The wallet has an ethereum address associated with it and user that owns this ethereum account owns a corresponding ZK Sync account. By ownership of ethereum account we mean ability to send ethereum transactions and optionally ability to sign messages.

To create transactions in the ZK Sync network wallet must have ZK Sync key pair associated with it. ZK Sync keys are handled by Signer object and can be created using different methods, the most convenient way is to create these keys by deriving them from ethereum signature of the specific message, this method is used by default if user does not provide Signer created using some other method.

For ZK Sync keys to be valid user should register them once in the ZK Sync network using set signing key transaction. For ethereum wallets that do not support message signing additional ethereum transaction is required. ZK Sync keys can be changed at any time.

Creating wallet from ETH signer

Signature

static async fromEthSigner(
    ethWallet: ethers.Signer,
    provider: Provider,
    signer?: Signer
): Promise<Wallet>;

Inputs and outputs

Name Description
ethWallet ethers.Signer that corresponds to keys that own this account
provider Sync provider that is used for submitting a transaction to the Sync network.
signer (optional) Sync signer that will be used for transaction signing. If undefined Signer will be derived from ethereum signature of specific message.
returns zksync.Wallet derived from ethereum wallet (ethers.Signer)

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

const ethersProvider = new ethers.getDefaultProvider('rinkeby');
const syncProvider = await zksync.getDefaultProvider('testnet');

const ethWallet = ethers.Wallet.createRandom().connect(ethersProvider);
const syncWallet = await zksync.Wallet.fromEthSigner(ethWallet, syncProvider);

Creating wallet from ETH without ZKSync signer

Signature

static async fromEthSignerNoKeys(
    ethWallet: ethers.Signer,
    provider: Provider
): Promise<Wallet>;

This way wallet won't contain any valid ZKSync keys to perform transactions, but some of the operations can be used without it. (Deposit, Emergency exit, reading account state)

Inputs and outputs

Name Description
ethWallet ethers.Signer that corresponds to keys that own this account
provider Sync provider that is used for submitting a transaction to the Sync network.
returns zksync.Wallet derived from ethereum wallet (ethers.Signer)

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

const ethersProvider = new ethers.getDefaultProvider('rinkeby');
const syncProvider = await zksync.getDefaultProvider('testnet');

const ethWallet = ethers.Wallet.createRandom().connect(ethersProvider);
const syncWallet = await zksync.Wallet.fromEthSignerNoKeys(ethWallet, syncProvider);

Get account state

Same as Get account state from provider but gets state of this account.

Signature

async getAccountState(): Promise<AccountState>;

Inputs and outputs

Name Description
returns State of the given account.

For convenience it is possible to obtain the account ID.

Signature

async getAccountId(): Promise<number | undefined>;
Name Description
returns Numerical account ID in the the ZK Sync tree state. Returned undefined value means that the account does not exist in the state tree.

Get token balance on ZK Sync

Signature

async getBalance(
    token: TokenLike,
    type: "committed" | "verified" = "committed"
): Promise<ethers.utils.BigNumber>;

Inputs and outputs

Name Description
token token of interest, symbol or address of the supported token
type "committed" or "verified"
returns Balance of this token

Example

const wallet = ..;// setup wallet

// Get committed Ethereum balance.
const ethCommittedBalance = await getBalance("ETH");

// Get verified ERC20 token balance.
const erc20VerifiedBalance = await getBalance("0xFab46E002BbF0b4509813474841E0716E6730136", "verified"); 

Get token balance on Ethereum

Method similar to syncWallet.getBalance, used to query balance in the Ethereum network.

Signature

async getEthereumBalance(token: TokenLike): Promise<utils.BigNumber>;

Inputs and outputs

Name Description
token token of interest, symbol or address of the supported token
returns Balance of this token

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

// Setup zksync.Wallet with ethers signer/wallet that is connected to ethers provider
const wallet = ..; 

const ethOnChainBalance = await wallet.getEthereumBalance("ETH");

Unlocking ERC20 deposits

For convenience, it is possible to approve the maximum possible amount of ERC20 token deposits for the ZK Sync contract so that user would not need to approve each deposit.

Signature

async approveERC20TokenDeposits(
    token: TokenLike
): Promise<ethers.ContractTransaction>;
Name Description
deposit.token ERC20 token
returns Handle for the ethereum transaction.

Signature

async isERC20DepositsApproved(token: TokenLike): Promise<boolean>;
Name Description
deposit.token ERC20 token to be approved for deposits
returns True if the token deposits are approved

Deposit token to Sync

Moves funds from the ethereum account to the Sync account. Fees are paid by ethereum account in ETH currency. The fee should be >= base fee, calculated on the contract based on the current gas price.

Formula for base fee calculation:

Token Formula
ETH token 2 * 179000 * GAS_PRICE
ERC20 token 2 * 214000 * GAS_PRICE

See utils for estimating fee.

For ETH token deposit amount of the ETH will be deposited to the ZK Sync network and on top of that, some fee will be spent according to the formula above. In other words, user will spend (amount + fee + gasFee for deposit transaction) on the Ethereum network and receive (amount) on the ZK Sync account.

For the ERC20 token transfer fees are paid in the ETH token. To do the ERC20 token transfer, this token transfer should be approved. User can make ERC20 deposits approved forever using unlocking ERC20 token, or the user can approve the exact amount (required for a deposit) upon each deposit, but this is not recommended.

Once the deposit transaction is committed to the Ethereum network, we have to wait for 30 confirmations before accepting it in the ZK Sync network. After the transaction is committed to the ZK Sync network, funds are already usable by the recipient, meaning that there is no need to wait for verification before proceeding unless additional confirmation is required for your application. To wait for the transaction commitment on the ZK Sync network, use awaitReceipt(see utils).

Signature

async depositToSyncFromEthereum(deposit: {
    depositTo: Address;
    token: TokenLike;
    amount: utils.BigNumberish;
    maxFeeInETHToken?: utils.BigNumberish;
    ethTxOptions?: ethers.providers.TransactionRequest;
    approveDepositAmountForERC20?: boolean;
}): Promise<ETHOperation>;

Inputs and outputs

Name Description
deposit.depositTo Sync account address of the receiver
deposit.token token to be transferred (symbol or address of the supported token)
deposit.amount amount of token to be transferred
deposit.maxFeeInETHToken amount of ETH to be paid by Ethereum wallet as a fee for this transaction.
If undefined, 10x of the base fee will be assumed with current GAS_PRICE in the formula so that the user can change the gas price of the signed tx later. The user will pay a fee calculated using the formula above with his tx mined gas price and an additional sent fee will be refunded.
deposit.ethTxOptions arguments for the deposit Ethereum transaction. Used to specify e.g. a gas price or nonce.
deposit.approveDepositAmountForERC20(optional) If set, the user will be asked to approve the ERC20 token spending to our account (not required if token was unlocked)
returns Handle for this transaction.

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

const syncWallet = ..; // Setup zksync wallet from ethers.Signer.

const depositPriorityOperation = await syncWallet.depositToSyncFromEthereum({
    depositTo: "0x2d5bf7a3ab29f0ff424d738a83f9b0588bc9241e",
    token: "ETH",
    amount: ethers.utils.formatEther("1.0"),
    maxFeeInETHToken: ethers.utils.formatEther("0.1")
});


// Wait till priority operation is committed.
const priorityOpReceipt = await depositPriorityOperation.awaitReceipt();

Changing account public key

In order to send ZKSync transactions (transfer and withdraw) user has to associate zksync key pair with account. Every ZKSync account has address which is ethereum address of the owner.

There are two ways to authorize zksync key pair. 1. Using ethereum signature of specific message. This way is prefered but can only be used if your ethereum wallet can sign messages. 2. Using ethereum transaction to ZKSync smart-contract.

This function will throw an error if the account is not present in the ZK Sync network. Check the account id to see if account is present in the ZK Sync state tree.

Signature

async setSigningKey(
    nonce: Nonce = "committed",
    onchainAuth = false
): Promise<Transaction>;

Inputs and outputs

Name Description
nonce Nonce that is going to be used for this transaction. ("committed" is used for the last known nonce for this account)
onchainAuth When false ethers.Signer is used to create signature, otherwise it is expected that user called onchainAuthSigningKey to authorize new pubkey.
returns Handle of the submitted transaction

Example

import {ethers} from "ethers";

const wallet = ..;// setup zksync wallet

if (! await wallet.isSigningKeySet()) {
    const changePubkey= await wallet.setSigningKey();

    // Wait till transaction is committed
    const receipt = await changePubkey.awaitReceipt();
}

Authorize new public key using ethereum transaction

This method is used to authorize public key change using ethereum transaction for wallets that don't support message signing.

Signature

async onchainAuthSigningKey(
    nonce: Nonce = "committed",
    ethTxOptions?: ethers.providers.TransactionRequest
): Promise<ContractTransaction>;

Inputs and outputs

Name Description
nonce Nonce that is going to be used for setSigningKey transaction. ("committed" is used for the last known nonce for this account)
ethTxOptions arguments for the onchain authentication Ethereum transaction. Used to specify e.g. a gas price or nonce.
returns Handle of the submitted transaction

Example

import {ethers} from "ethers";

const wallet = ..;// setup zksync wallet

if (! await wallet.isSigningKeySet()) {
    const onchainAuthTransaction = await wallet.onchainAuthSigningKey();
    // Wait till transaction is committed on ethereum.
    await onchainAuthTransaction.wait();

    const changePubkey= await wallet.setSigningKey("committed", true);

    // Wait till transaction is committed
    const receipt = await changePubkey.awaitReceipt();
}

Check if current public key is set

Checks if current signer that is associated with wallet is able to sign transactions. See change pub key on how to change current public key.

Signature

async isSigningKeySet(): Promise<boolean>;

Inputs and outputs

Name Description
returns True if current signer that is assigned to wallet can sign transactions.

Transfer in the ZK Sync

Moves funds between accounts inside the ZK Sync network. Sender account should have correct public key set before sending this transaction. (see change pub key)

Before sending this transaction, the user will be asked to sign a specific message with transaction details using their Ethereum account (because of the security reasons). For wallets that sign messages using EIP1271 standard ethSignatureType parameter should be set to "EIP1271Signature".

After the transaction is committed, funds are already usable by the recipient, so there is no need to wait for verification before proceeding unless additional confirmation is required for your application. To wait for transaction commit use awaitReceipt(see utils).

The operators require a fee to be paid in order to process transactions. Fees are paid using the same token as the transfer. To get how to obtain an acceptable fee amount, see Get transaction fee from the server.

Signature

async syncTransfer(transfer: {
    to: Address;
    token: TokenLike;
    amount: utils.BigNumberish;
    fee: utils.BigNumberish;
    nonce?: Nonce;
    ethSignatureType?: "EthereumSignature" | "EIP1271Signature";
}): Promise<Transaction>;

Inputs and outputs

Name Description
transfer.to Sync address of the recipient of funds
transfer.token token to be transferred (symbol or address of the token)
transfer.amount amount of token to be transferred. To see if amount is packable use pack amount util
transfer.fee amount of token to be paid as a fee for this transaction. To see if amount is packable use pack fee util, also see this section to get an acceptable fee amount.
transfer.nonce Nonce that is going to be used for this transaction. ("committed" is used for the last known nonce for this account)
transfer.ethSignatureType Type of the signature used by the Ethereum wallet to sign messages. "EthereumSignature" by default
returns Handle of the submitted transaction

Example

import {ethers} from "ethers";

const wallet = ..;// setup zksync wallet

const transferTransaction = await wallet.syncTransfer({
    to: "0x2d5bf7a3ab29f0ff424d738a83f9b0588bc9241e",
    token: "0xFab46E002BbF0b4509813474841E0716E6730136", // FAU ERC20 token address
    amount: ethers.utils.parseEther("1.0"), 
    fee: ethers.utils.parseEther("0.001") 
});

// Wait till transaction is committed
const transactionReceipt = await transferTransaction.awaitReceipt();

Withdraw token from Sync

Moves funds from the Sync account to ethereum address. Sender account should have correct public key set before sending this transaction. (see change pub key)

Before sending this transaction, the user will be asked to sign a specific message with transaction details using their Ethereum account (because of the security reasons). For wallets that sign messages using EIP1271 standard, ethSignatureType parameter should be set to "EIP1271Signature".

The operators require a fee to be paid in order to process transactions. Fees are paid using the same token as the withdraw. To get how to obtain an acceptable fee amount, see Get transaction fee from the server.

The transaction has to be verified until funds are available on the ethereum wallet balance so it is useful to use awaitVerifyReceipt(see utils) before checking ethereum balance.

Signature

async withdrawFromSyncToEthereum(withdraw: {
    ethAddress: string;
    token: TokenLike;
    amount: utils.BigNumberish;
    fee: utils.BigNumberish;
    nonce?: Nonce;
    ethSignatureType?: "EthereumSignature" | "EIP1271Signature";
}): Promise<Transaction>;

Inputs and outputs

Name Description
withdraw.ethAddress ethereum address of the recipient
withdraw.token token to be transferred ("ETH" or address of the ERC20 token)
withdraw.amount amount of token to be transferred
withdraw.fee amount of token to be paid as a fee for this transaction. To see if amount is packable use pack fee util, also see this section to get an acceptable fee amount.
withdraw.nonce Nonce that is going to be used for this transaction. ("committed" is used for the last known nonce for this account)
withdraw.ethSignatureType Type of the signature used by the Ethereum wallet to sign messages. "EthereumSignature" by default
returns Handle of the submitted transaction

Example

import {ethers} from "ethers";

const wallet = ..;// setup zksync wallet

const withdrawTransaction = await wallet.withdrawFromSyncToEthereum({
    ethAddress: "0x9de880ac69f3ed1e4d6870fcdabf07cbbed6f85c",
    token: "FAU",
    amount: ethers.utils.parseEther("1.0"), 
    fee: ethers.utils.parseEther("0.001") 
});

// Wait wait till transaction is verified
const transactionReceipt = await withdrawTransaction.awaitVerifyReceipt();

Emergency withdraw from Sync

If ordinary withdraw from Sync account is ignored by network operators user could create an emergency withdraw request using special Ethereum transaction, this withdraw request can't be ignored.

Moves the full amount of the given token from the Sync account to the Ethereum account.

Fees are paid by the Ethereum account in the ETH currency. The fee should be greater or equal to the base fee, calculated on the contract based on the current gas price. The formula for base fee calculation: 2 * 170000 * GAS_PRICE

Once the deposit transaction is committed to the Ethereum network, we have to wait for 30 confirmations before accepting it in the ZK Sync network. Deposit will be processed within the ZK Sync network as soon as the required amount of confirmations is reached. After the transaction is committed to the Ethereum network, we wait for 30 confirmations before accepting it in the ZK Sync network then we try to process it as soon as possible.

See utils for estimating fee.

The transaction has to be verified until funds are available on the ethereum wallet balance so it is useful to use awaitVerifyReceipt(see utils) before checking ethereum balance.

Signature

async emergencyWithdraw(withdraw: {
    token: TokenLike;
    maxFeeInETHToken?: utils.BigNumberish;
    accountId?: number;
    ethTxOptions?: ethers.providers.TransactionRequest;
}): Promise<ETHOperation>;

Inputs and outputs

Name Description
withdraw.token token to be withdrawn (symbol or address of the supported token)
withdraw.maxFeeInETHToken amount of ETH to be paid by Ethereum wallet as a fee for this transaction.
If undefined, 10x of the base fee will be assumed with current GAS_PRICE in the formula so that the user can change the gas price of the signed tx later. The user will pay a fee calculated using the formula above with his tx mined gas price and an additional sent fee will be refunded.
withdraw.accountId Numerical id of the given account. If undefined it is going to be resolved from the ZK Sync provider.
withdraw.ethTxOptions arguments for emergency withdraw Ethereum transaction. It is used to specify a gas price or nonce.
returns Handle for this transaction.

Example

import * as zksync from "zksync";
import {ethers} from "ethers";

const syncWallet = ..; // Setup zksync wallet.

const emergencyWithdrawPriorityOp = await syncWallet.emergencyWithdraw({
    token: "ETH",
    maxFeeInETHToken: ethers.utils.parseEther("0.3")
});


// Wait till priority operation is verified.
const priorityOpReceipt = await emergencyWithdrawPriorityOp.awaitVerifyReceipt();

Signer

Create from private key

Signature

static fromPrivateKey(pk: BN): Signer;

Inputs and outputs

Name Description
pk private key
returns Signer derived from private key

Create from seed

Signature

static fromSeed(seed: Buffer): Signer;

Create from ethereum signature

Signature

static async fromETHSignature(ethSigner: ethers.Signer): Promise<Signer>;

Inputs and outputs

Name Description
ethSigner Ethereum signer that is going to be used for signature generation.
returns Signer derived from this seed

Get public key hash

Signature

pubKeyHash(): PubKeyHash;

Inputs and outputs

Name Description
returns Pubkey hash derived from corresponding public key. (sync: prefixed hex-encoded)

Sign sync transfer

Signs transfer transaction, the result can be submitted to the Sync network.

Signature

signSyncTransfer(transfer: {
    from: Address;
    to: Address;
    tokenId: number;
    amount: ethers.utils.BigNumberish;
    fee: ethers.utils.BigNumberish;
    nonce: number;
}): SyncTransfer;

Inputs and outputs

Name Description
transfer.from Address of the sender
transfer.to Address of the recipient
transfer.tokenId Numerical token id
transfer.amount Amount to transfer, payed in token
transfer.fee Fee to pay for transfer, payed in token
transfer.nonce Transaction nonce
returns Signed Sync transfer transaction

Sign Sync Withdraw

Signs withdraw transaction, the result can be submitted to the Sync network.

Signature

signSyncWithdraw(withdraw: {
    from: Address;
    ethAddress: string;
    tokenId: number;
    amount: ethers.utils.BigNumberish;
    fee: ethers.utils.BigNumberish;
    nonce: number;
}): SyncWithdraw;

Inputs and outputs

Name Description
withdraw.from Account address of the sender
withdraw.ethAddress Ethereum address of the recipient
withdraw.tokenId Numerical token id
withdraw.amount Amount to withdraw, paid in token
withdraw.fee Fee to pay for withdrawing, paid in token
withdraw.nonce Transaction nonce
returns Signed Sync withdraw transaction

Utils

Working with tokens

Tokens are identified with

  1. symbol (e.g. "ETH", "DAI")
  2. address ("0x0000000000000000000000000000000000000000" for "ETH" or "0xFab46E002BbF0b4509813474841E0716E6730136" for ERC20).

To simplify conversions TokenSet class can be used, token set can be obtained from ZK Sync provider.

Resolve token ID

Get numerical token id using its identifier.

Signature

public resolveTokenId(tokenLike: TokenLike): number;

Resolve token Address

Get token address using its identifier.

Signature

public resolveTokenAddress(tokenLike: TokenLike): TokenAddress;

Resolve token Symbol

Get token symbol using its identifier.

Signature

public resolveTokenSymbol(tokenLike: TokenLike): TokenSymbol;

Amount packing

Check if amount is packable

Transfers amount should be packable to 5-byte long floating-point representation. This function is used to check if this amount can be used as a transfer amount.

Signature

export function isTransactionAmountPackable(
    amount: utils.BigNumberish
): boolean;

Closest packable amount

Transfers amount should be packable to 5-byte long floating-point representation. This function returns the closest packable amount by setting the least significant digits to zero.

Signature

export function closestPackableTransactionAmount(
    amount: ethers.utils.BigNumberish
): ethers.utils.BigNumber;

Check if fee is packable

All fees paid in transfers and withdraws should be packable to 2-byte long floating-point representation. This function is used to check if this amount can be used as a fee.

Signature

export function isTransactionFeePackable(amount: utils.BigNumberish): boolean;

Closest packable fee

All fees paid in transfers and withdraws should be packable to 2-byte long floating-point representation. This function returns the closest packable amount by setting the least significant digits to zero.

Signature

export function closestPackableTransactionFee(
    fee: ethers.utils.BigNumberish
): ethers.utils.BigNumber;

Awaiting for operation completion

It is often useful to be able to wait until the transaction is committed or verified. It is possible to do that using objects returned from methods that submit transactions.

It is possible to wait until the transactions like Transfer is either:

  1. Committed (with awaitReceipt) when the state is updated in the ZK Sync network
  2. Verified (with awaitVerifyReceipt) when the state is finalized on the Ethereum

It is possible to wait until the operations like Deposit is either:

  1. Mined on the Ethereum network (with awaitEthereumTxCommit)
  2. Committed (with awaitReceipt) when the state is updated in the ZK Sync network
  3. Verified (with awaitVerifyReceipt) when the state is finalized on the Ethereum

Commit comes first, but there is no need to wait for commit if you are interested in the verify since await for verifying implies await for commit.

Awaiting for transaction.

import * as zksync from "zksync";
const wallet = ..; // create ZK Sync Wallet

// see transfer example for details
const transfer = wallet.syncTransfer({..}); 

// this function will return when deposit is committed to the ZK Sync chain
const receiptAfterCommit = await transfer.awaitReceipt();

// this function will return when deposit is verified with ZK proof.
const receiptAfterVerify = await transfer.awaitVerifyReceipt();

Awaiting for priority operation

import * as zksync from "zksync";

// see deposit example for details
const deposit = zksync.depositFromETH({..}); 

// this function will return when deposit request is accepted to the Ethereum.
const txMinedCommit = await deposit.awaitEthereumTxCommit();

// this function will return when deposit is committed to the ZK Sync chain
const receiptAfterCommit = await deposit.awaitReceipt();

// this function will return when deposit is verified with ZK proof.
const receiptAfterVerify = await deposit.awaitVerifyReceipt();

Types

Tokens and common types

// Symbol like "ETH" or "FAU" or token contract address(zero address is implied for "ETH").
export type TokenLike = TokenSymbol | TokenAddress;
// Token symbol (e.g. "ETH", "FAU", etc.)
export type TokenSymbol = string;
// Token address (e.g. 0xde..ad for ERC20, or 0x00.00 for "ETH")
export type TokenAddress = string;

// List of all available tokens
export interface Tokens {
    // Tokens are indexed by their symbol (e.g. "ETH")
    [token: string]: {
        address: string;
        id: number;
        symbol: string;
    };
}

// Addresses of the ZK Sync contracts
export interface ContractAddress {
    mainContract: string;
    govContract: string;
}

// 0x-prefixed, hex encoded, ethereum account address
export type Address = string;

// Committed nonce is going to be resolved to last nonce known to the ZK Sync network
export type Nonce = number | "committed";

Account state

import { utils } from "ethers";

// 0x-prefixed, hex encoded, ethereum account address
export type Address = string;
// sync:-prefixed, hex encoded, hash of the account public key
export type PubKeyHash = string;


export interface AccountState {
    // Ethereum address of the account
    address: Address;
    id?: number;
    // Committed state is the last state known to the ZK Sync network, can be ahead of verified state
    committed: {
        balances: {
            // Token are indexed by their symbol (e.g. "ETH")
            [token: string]: utils.BigNumberish;
        };
        nonce: number;
        // Public key hash of the signer keys associated with account
        pubKeyHash: PubKeyHash;
    };
    // Verified state is proved with ZKP on the Ethereum network.
    verified: {
        balances: {
            // Token are indexed by their symbol (e.g. "ETH")
            [token: string]: utils.BigNumberish;
        };
        nonce: number;
        // Public key hash of the signer keys associated with account
        pubKeyHash: PubKeyHash;
    };
}

Transactions

import { utils } from "ethers";

export interface Signature {
    pubKey: string;
    signature: string;
}

export interface Transfer {
    type: "Transfer";
    from: Address;
    to: Address;
    token: number;
    amount: utils.BigNumberish;
    fee: utils.BigNumberish;
    nonce: number;
    signature: Signature;
}

export interface Withdraw {
    type: "Withdraw";
    from: Address;
    to: Address;
    token: number;
    amount: utils.BigNumberish;
    fee: utils.BigNumberish;
    nonce: number;
    signature: Signature;
}

export interface CloseAccount {
    type: "Close";
    account: Address;
    nonce: number;
    signature: Signature;
}

export interface BlockInfo {
    blockNumber: number;
    committed: boolean;
    verified: boolean;
}

export interface TransactionReceipt {
    executed: boolean;
    success?: boolean;
    failReason?: string;
    block?: BlockInfo;
}

export interface PriorityOperationReceipt {
    executed: boolean;
    block?: BlockInfo;
}