Bridging ERC20 Tokens and Withdrawing Manually

This page explains how to create and bridge ERC20 tokens from Ethereum (L1) to Zircuit (L2) and back programmatically using JavaScript with the ethers and zircuit-viem libraries.

Create Node Environment

To get started, we need a node environment and install the ethers and zircuit-viem. Zircuit is built in part on the OP Stack, but certain withdrawal mechanisms have been modified to reduce user-side costs. To support these changes, we provide our own fork of the viem library called zircuit-viem for communicating with the network.

npm init -y
npm install @zircuit/zircuit-viem

Connect to Smart Contracts on L1 and L2

Next, we need to set the following variables in a new file. The following adapted code snippets can be saved to a file called bridge.js. You can run the script using Node.js. This tutorial expects, that the ERC20 contract has already been deployed on L1 and the user has a token balance. Next, we begin the setup. We connect the signing wallet to the L1 contract. RPC URLs, the private key as well as the address of the ERC20 contract have to be adapted.

import { createWalletClient, createPublicClient, http, parseEventLogs, encodeFunctionData } from '@zircuit/zircuit-viem'
import { privateKeyToAccount } from '@zircuit/zircuit-viem/accounts'
import { mainnet, zircuit } from '@zircuit/zircuit-viem/chains'
// The L1 Address of the ERC20 Contract to be bridged
const l1Erc20ContractAddress = "0xYourERC20TokenAddressHere";
// Partial ABI
const l1Erc20ContractABI = [
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "guy",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "wad",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  }
];


// Set up L1 and L2 with the correct URLs
const L1_RPC_URL = "L1_RPC_URL";
const L2_RPC_URL = "L2_RPC_URL";

// Import account from private key
const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY')
// Create clients
const walletClientL1 = createWalletClient({
  account,
  chain: mainnet,
  transport: http(L1_RPC_URL)
})

const walletClientL2 = createWalletClient({
  account,
  chain: zircuit,
  transport: http(L2_RPC_URL)
})

const publicClientL1 = createPublicClient({
  chain: mainnet,
  transport: http(L1_RPC_URL)
})

const publicClientL2 = createPublicClient({
  chain: zircuit,
  transport: http(L2_RPC_URL)
})

We also need to connect to the ERC20 Factory on Zircuit L2 to bridge ERC20s. The OptimismMintableERC20FactoryAddress is a predeployed contract on L2. The name, symbol and decimals of the ERC20 have to be adapted accordingly.

Create ERC20 contract on Zircuit L2

Next we make a call createOptimismMintableERC20WithDecimals to the L2 factory contract to create the ERC20 on Zircuit L2.

Congratulations, you have successfully deployed the contract on Zircuit on the returned address.

Bridge ERC20 Tokens to L2

With the ERC20 Token deployed on L1 and L2, a user can now bridge actual tokens from that L1 contract to the L2 contract. To bridge tokens, we need to connect to the L1StandardBridge contract, approve the bridge amount and then bridge the tokens to L2. The L1StandardBridgeProxy address can be found on this page https://github.com/zircuit-labs/docs/blob/main/infra/bridges/canonical-bridges/broken-reference/README.md. Also, the token amount needs to be set.

Double check all addresses before executing this code!

Congratulations, the ERC20 Tokens are now bridged to Zircuit.

Withdrawing ERC20 Tokens from L2 to L1

As with normal withdrawals, withdrawing assets is a three step process of initiating a withdrawal on L2, and then proving and finalizing the withdrawal transaction on L1. You can place the following code into a new file called withdrawal.js. Again, the RPC URLs and the private key needs to be set. Also, the token amount and the contract addresses of the ERC20 need to be set.

Start your withdrawal on L2

Wait until the withdrawal is ready to prove

The second step to withdrawing tokens from L2 to L1 is to prove to the bridge on L1 that the withdrawal happened on L2. You first need to wait until the rollup process of the previous L2 transaction is finished and the withdrawal is ready to prove. This may take up to an hour to finish the rollup process.

Prove and release the withdrawal on L1

Once the withdrawal is ready to be proven, you will send an L1 transaction to prove that the withdrawal happened on L2, and to release it. This can only happen after the finalization period elapsed. The finalization period lasts 5 hours on Zircuit. When the withdrawal is ready to be relayed, you can finally complete the withdrawal process.

Congrats! You've just deposited and withdrawn tokens on Zircuit.

Last updated

Was this helpful?