Withdraw ERC20 tokens from ZKsync

Learn how to create a script to withdraw any ERC20 token from ZKsync to Ethereum.

This how-to guide explains how to create a script that withdraw any ERC20 token from ZKsync to Ethereum.

What you'll learn:

  • How L2 withdraw work
  • How to withdraw ERC20 tokens to ZKsync


  • - zksync-ethers
Last Updated: May 09, 2024

Withdrawing assets from Layer 2 (L2) back to Layer 1 (L1) is an essential procedure to retrieve your assets in ZKsync. This guide outlines how to withdraw ETH using the ZKsync Javascript SDK, ensuring a successful transfer of assets to L1. In ZKsync, withdrawing involves burning tokens on L2 and unlocking the corresponding funds on the L1 bridge.


  • Node.js: Ensure Node.js is installed. If not, download it from here.
  • Private Key: Have access to a private key for the account you'll be using.
  • L1 RPC Endpoint: A URL to an Ethereum node to interact with. You can find RPC endpoints for Sepolia and Ethereum mainnet on Chainlist or use a node provider like Infura.

Understanding L2 to L1 Withdrawals

Withdrawing assets from L2 to L1 involves the following steps:

  1. Burning L2 Tokens: Initially, the specified tokens on L2 are burned.
  2. Sending L2 to L1 Message: A message detailing the withdrawal is sent from L2 to L1.
  3. Finalizing Withdrawal: The finalizeWithdrawal method is called on the L1 bridge to complete the withdrawal process, unlocking the funds from the L1 bridge and sending them to the recipient.
During the Alpha phase, withdrawals in ZKsync Era take 24 hours for additional security.

Setup Environment

Create a new directory for your withdrawal scripts and navigate into it:

mkdir withdraw-erc20-script && cd withdraw-erc20-script
npm init -y
npm i typescript ts-node ethers@^5.7.2 zksync-ethers@5 dotenv

Set up environment variables by creating a .env file in the project root containing your private key and the L1 RPC endpoint:


Create the Withdrawal Script

Create a new file withdraw-erc20.ts and insert the following code. This script utilizes the withdraw method from the Wallet class of the ZKsync Javascript SDK to withdraw ETH. Adjust the AMOUNT and TOKEN_ADDRESS variable if necessary.

withdraw-erc20.ts script

import { Wallet, Provider, utils } from "zksync-ethers";
import * as ethers from "ethers";

// load env file
import dotenv from "dotenv";

// HTTP RPC endpoints
const L1_RPC_ENDPOINT = process.env.L1_RPC_ENDPOINT || ""; // or an RPC endpoint from Infura/Chainstack/QuickNode/etc.
const L2_RPC_ENDPOINT = process.env.L2_RPC_ENDPOINT || "https://sepolia.era.zksync.dev"; // or the ZKsync Era mainnet

// ERC-20 Token address in L2

// Amount of tokens
const AMOUNT = "5";

const WALLET_PRIV_KEY = process.env.WALLET_PRIV_KEY || "";

  throw new Error("Wallet private key is not configured in env file");

  throw new Error("Missing L1 RPC endpoint. Check chainlist.org or an RPC node provider");

  throw new Error("Missing address of the ERC-20 token in L1");

async function main() {
  console.log(`Running script to bridge ERC-20 to L1`);

  // Initialize the wallet.
  const l1provider = new ethers.providers.JsonRpcProvider(L1_RPC_ENDPOINT);
  const l2provider = new Provider(L2_RPC_ENDPOINT);
  const wallet = new Wallet(WALLET_PRIV_KEY, l2provider, l1provider);

  console.log(`L1 Balance is ${await wallet.getBalanceL1()}`);
  console.log(`L2 Balance is ${await wallet.getBalance()}`);

  // withdraw ERC-20 token to L2
  const withdrawErc20Handle = await wallet.withdraw({
    to: wallet.address, // can bridge to a different address in L1
    token: TOKEN_ADDRESS,
    amount: ethers.utils.parseEther(AMOUNT), // assumes ERC-20 has 18 decimals
  console.log(`Withdraw ERC-20 transaction sent ${withdrawErc20Handle.hash}`);

  .catch((error) => {
    process.exitCode = 1;

Run the Script

Execute the script using the following command:

npx ts-node withdraw-erc20.ts

Verify the Output

Upon running the script, you should see an output similar to below, indicating the withdrawal transaction has been sent and is being processed on L1:

Running script to bridge ERC-20 to L1
L1 Balance is 19421054769191270
L2 Balance is 2969626077250000000
Withdraw ERC-20 transaction sent 0x280a2168f464c93e8c56df3291076bbb6cff78ebdc30fdaad22bc275d56aa3ed


By following this guide, you have successfully withdrawn ERC-20 token from L2 to L1 using the ZKsync Javascript SDK. This is a significant step towards managing your assets on the ZKsync Era.

Made with ❤️ by the ZKsync Community