Deposit ERC20 tokens to ZKsync

Learn how to create a script to deposit any ERC20 token to ZKsync.

This how-to guide explains how to create a script that deposits any ERC20 token to ZKsync.

What you'll learn:

  • How L2 deposits work
  • How to deposit ERC20 tokens to ZKsync
GitHub

Tools:

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

Depositing ERC-20 tokens from Layer 1 (L1) to Layer 2 (L2) is a vital step to engage with ZKsync efficiently. This guide outlines the process of depositing ERC-20 tokens using the ZKsync Javascript SDK, ensuring a streamlined transition of assets to L2. In ZKsync, assets deposited from L1 are secured in a smart contract, while corresponding representations are created on L2, paving the way for swift and economical transactions.

Prerequisites

  • Node.js: Ensure you have Node.js 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 Alchemy.

Understand L1 - L2 Deposits

Depositing assets from L1 to L2 involves calling the deposit method on the L1 bridge contract. Here's a simplified breakdown of the process:

  1. Locking L1 Tokens: Initially, the specified tokens on L1 are sent to the L1 bridge, where they are locked.
  2. Initiating L1 to L2 Transaction: The L1 bridge then initiates a transaction to the L2 bridge, marking the start of the deposit process.
  3. Minting L2 Tokens: Tokens corresponding to the deposit are minted on L2 and sent to the designated L2 address.
    • If the token contract isn’t already present on ZKsync Era, a new contract is deployed with a new L2 token address, which is derived from the original L1 address, name, and symbol.
  4. Confirmation Logging: Each executed L1 to L2 transaction is confirmed with a log message sent from L2 back to L1.
  5. Finalizing Deposit: Finally, the finalizeDeposit method is called on the L2 bridge to complete the deposit process, ensuring the funds are minted on L2.

This structured flow ensures a secure and orderly transfer of assets from L1 to L2, paving the way for further interactions on the Layer 2 network.

Set Up Environment

Set up the node script:

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

Create a .env file in the project root containing your private key and the L1 RPC endpoint.

WALLET_PRIV_KEY=<YOUR_PRIVATE_KEY>
L1_RPC_ENDPOINT=<RPC_URL>

Create the Deposit Script

Create a new file deposit-erc20.ts and insert the below code:

deposit-erc20.ts
import { Wallet, Provider, utils } from "zksync-ethers";
import * as ethers from "ethers";

// load env file
import dotenv from "dotenv";
dotenv.config();

// 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 (DAI) address in L1
const TOKEN_ADDRESS = "0x5C221E77624690fff6dd741493D735a17716c26B";

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

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

if (!WALLET_PRIV_KEY) {
  throw new Error("Wallet private key is not configured in env file");
}

if (!L1_RPC_ENDPOINT) {
  throw new Error("Missing L1 RPC endpoint. Check chainlist.org or an RPC node provider");
}

if (!TOKEN_ADDRESS) {
  throw new Error("Missing address of the ERC-20 token in L1");
}

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

  // Initialize the wallet.
  const l1provider = new Provider(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()}`);

  // Deposit token to L2
  const depositHandle = await wallet.deposit({
    to: wallet.address, // can bridge to a different address in L2
    token: TOKEN_ADDRESS,
    amount: ethers.utils.parseEther(AMOUNT), // assumes ERC-20 has 18 decimals
    // performs the ERC-20 approve action
    approveERC20: true,
  });
  console.log(`Deposit transaction sent ${depositHandle.hash}`);
  console.log(`Please wait a few minutes for the deposit to be processed in L2`);
}

main()
  .then()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  });

Run the Script

Execute the script using the following command:

npx ts-node deposit-erc20.ts

Verify the Deposit

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

Running script to bridge ERC-20 to L2
L1 Balance is 19500035772482145
L2 Balance is 2969807401250000000
Deposit transaction sent 0xffb8e302430b0584e2e0104dd6295a03688c98ba7b6e9279b01dba65188cc444

Conclusion

By adhering to this guide, you have successfully deposited ERC-20 tokens from L1 to L2 using the ZKsync Javascript SDK, making a significant stride towards interacting with the ZKsync Era.


Made with ❤️ by the ZKsync Community