Custom base tokens
You can customize your ZK chain by changing the base token used for gas from ETH to an ERC20 token.
With the initial release of ZK stack, custom ERC20 base tokens must be added to an allowlist for use as a base token for a chain.
The allowed addresses for all ERC20 tokens that can be used as a base token for a ZK chain are stored in the BridgeHub
contract on the L1.
In the future it will be possible to add a new token in a permissionless process. For now, you have the ability to add any tokens to the allowlist in your local ecosystem.
Custom Base Token Setup
The overall flow for setting up a local chain with a custom ERC20 base token looks like the following:
- Deploy an ERC20 token to the L1.
- Create your new chain that uses the ERC20 token and set it as the default.
- When you set up your chain, you will be asked for the nominator and denominator for your base token.
- The base token nominator and denominator are used to define the relationship of the base token price with ETH.
- For example, if you set the nominator to 20 and the denominator to 1, together the relation is 20/1. This would mean that 20 tokens would be given the equivalent value as 1 ETH for gas.
- Mint or send ERC20 tokens to the ecosystem governor address on the L1.
- You can find the address for the governor in
<YOUR_ECOSYSTEM_DIRECTORY>/configs/wallets.yaml
undergovernor
.
- You can find the address for the governor in
- Initialize the new chain in the ecosystem.
- Bridge tokens from the L1 to your new chain.
When you bridge the ERC20 token to your ZK chain, the token resolves to the same address used for ETH on a standard ZK chain:
const L2_BASE_TOKEN_ADDRESS = '0x000000000000000000000000000000000000800a';
If you want to check the balance of your bridged tokens with ZKsync CLI, you don't need to input the token address.
zksync-cli wallet balance --address <0x_YOUR_ADDRESS> --rpc http://localhost:3050
Restarting a custom base token chain
To run your custom ZK chain again after setting up and shutting it down, follow the steps below:
- If you shut down your local L1 node that had your ERC20 token deployed to it:
- Redeploy your ERC20 contract to the L1.
- Update the base token address in
<YOUR_ECOSYSTEM_DIRECTORY>/chains/<YOUR_CHAIN_NAME>/configs/contracts.yaml
underl1.base_token_addr
and in<YOUR_ECOSYSTEM_DIRECTORY>/chains/<YOUR_CHAIN_NAME>/ZkStack.yaml
underbase_token.address
. - Send ERC20 tokens to both the ecosystem and chain governor addresses on the L1.
- Initialize the chain in the ecosystem
- Start the chain server with
zkstack server
. - Bridge ERC20 tokens from the L1 to L2.
Bridging custom base tokens
You can see an example of how to bridge custom base tokens from the L1 to your chain using hardhat
and ethers
:
import { ethers } from 'hardhat';
async function main() {
const [wallet] = await ethers.getWallets();
const initialBalance = await wallet.getBalance();
console.log('INITIAL L2 Base Token Balance 🎉:', ethers.formatEther(initialBalance));
const depositTx = await wallet.deposit({
token: await wallet.getBaseToken(),
amount: ethers.parseEther('5'),
approveBaseERC20: true,
});
const tx = await depositTx.wait();
console.log('Deposit Tx Hash:', tx.hash);
const finalBalance = await wallet.getBalance();
console.log('FINAL L2 Base Token Balance 🎉:', ethers.formatEther(finalBalance));
}
Bridging ETH to your chain
If you try to bridge regular ETH to a chain with a custom base token, the ETH will be bridged to a different token contract address.
To get the token address for ETH on your chain, you can pass the ETH_ADDRESS_IN_CONTRACTS
constant to the l2TokenAddress
method from zksync-ethers
.
import { ethers } from 'hardhat';
import { utils } from 'zksync-ethers';
async function main() {
const [wallet] = await ethers.getWallets();
const l2ETHAddress = await wallet.l2TokenAddress(utils.ETH_ADDRESS_IN_CONTRACTS);
console.log('L2 ETH Address:', l2ETHAddress);
const initialBalance = await wallet.getBalance(l2ETHAddress);
console.log(`INITIAL L2 ETH Balance 🎉: ${ethers.formatEther(initialBalance)} ETH`);
const depositTx = await wallet.deposit({
token: utils.ETH_ADDRESS,
amount: ethers.parseEther('5'),
approveBaseERC20: true,
});
const tx = await depositTx.wait();
console.log('Deposit Tx Hash:', tx.hash);
const finalBalance = await wallet.getBalance(l2ETHAddress);
console.log('FINAL L2 ETH Balance 🎉:', ethers.formatEther(finalBalance));
}