Tutorial GitHub Repository
For developers looking to create standard ERC20 tokens on EVM rollups, we
recommend using the
ERC20Factory
contract.
Project Setup
First, create a new project directory:
mkdir erc20-factory
cd erc20-factory
Initialize the project and install the required dependencies:
npm init
npm install viem
npm install dotenv
Create two directories:
src: For our script files.
abis: For our contract
ABIs.
Download the ERC20 and ERC20Factory ABIs into the abis directory:
curl -o abis/erc20Abi.json \
https://raw.githubusercontent.com/initia-labs/examples/main/evm/erc20-factory/abis/erc20Abi.json
curl -o abis/erc20FactoryAbi.json \
https://raw.githubusercontent.com/initia-labs/examples/main/evm/erc20-factory/abis/erc20FactoryAbi.json
Setting up Environment Variables
Create a .env file in your project root:
This file will store your private environment variables, such as your wallet’s
private key and the deployed ERC20Factory contract address. These values are
required for signing transactions and interacting with the MiniEVM.
The ERC20Factory contract is automatically deployed on all MiniEVM rollups as
part of the chain’s bootstrapping process.
To retrieve the deployed factory address, query
{ROLLUP_REST_URL}/minievm/evm/v1/contracts/erc20_factory.
{ROLLUP_REST_URL} refers to the REST endpoint of the rollup you are
interacting with.
Example:
curl -X GET "https://rest-evm-1.anvil.asia-southeast.initia.xyz/minievm/evm/v1/contracts/erc20_factory" -H "accept: application/json"
Example response:
{
"address": "0xf36924c9C2aD25d73c43F6aC59BB6D06BC944D93"
}
Store this address in your .env file as ERC20_FACTORY_ADDRESS.
Next, export an EVM-compatible private key from your Ethereum wallet. This
private key will be used to sign and send transactions to the MiniEVM.
Your .env file should look like this:
PRIVATE_KEY={{ETHEREUM_PRIVATE_KEY}}
ERC20_FACTORY_ADDRESS={{FACTORY_CONTRACT_ADDRESS}}
Never share your .env file or private key. Anyone with this key can control
your account. Keep it local and add .env to your .gitignore so it is
never committed to Git.
Development
Creating the Chain Configuration File
To interact with the MiniEVM using Viem, we need a chain configuration file.
This file contains essential details about the chain, including the chain ID,
name, native currency, and RPC URLs.
Create a chain.js file in the src directory:
Add the following configuration:
const { defineChain } = require('viem')
const miniEVM = defineChain({
id: 2594729740794688,
name: 'MiniEVM',
nativeCurrency: {
decimals: 18,
name: 'Gas Token',
symbol: 'GAS',
},
rpcUrls: {
default: {
http: ['https://jsonrpc-evm-1.anvil.asia-southeast.initia.xyz'],
},
},
})
module.exports = miniEVM
Interacting with the ERC20Factory Contract
Next, create a script to interact with the ERC20Factory contract:
Start with the imports:
const {
createPublicClient,
createWalletClient,
decodeEventLog,
getContract,
http,
} = require('viem')
const { privateKeyToAccount } = require('viem/accounts')
const erc20Abi = require('../abis/erc20Abi.json')
const erc20FactoryAbi = require('../abis/erc20FactoryAbi.json')
const miniEVM = require('./chain')
Load the environment variables:
You can also find factory addresses on the
Networks page or by calling the
/minievm/evm/v1/contracts/erc20_factory endpoint on any MiniEVM rollup.
require('dotenv').config()
const rawPrivateKey = process.env.PRIVATE_KEY
if (!rawPrivateKey) {
throw new Error('PRIVATE_KEY environment variable is not set.')
}
const privateKey = rawPrivateKey.startsWith('0x')
? rawPrivateKey
: `0x${rawPrivateKey}`
const erc20FactoryAddress = process.env.ERC20_FACTORY_ADDRESS
if (!erc20FactoryAddress) {
throw new Error('ERC20_FACTORY_ADDRESS environment variable is not set.')
}
To interact with the chain and contracts, create a wallet client and a public
client:
// Create an account from the private key.
const account = privateKeyToAccount(privateKey)
// Create a wallet client.
const client = createWalletClient({
account,
chain: miniEVM,
transport: http(),
})
// Create a public client.
const publicClient = createPublicClient({
chain: miniEVM,
transport: http(),
})
Now you can create a new ERC20 token using the createERC20 method of the
ERC20Factory contract.
Implement the createERC20 function:
// Send the transaction.
async function createERC20() {
try {
// Call createERC20 function on the factory contract to create a new ERC20 token.
const hash = await client.writeContract({
address: erc20FactoryAddress,
abi: erc20FactoryAbi,
functionName: 'createERC20',
args: ['Test', 'TST', 18],
})
console.log('Transaction sent. Hash:', hash)
// Wait briefly for the transaction to be processed.
await new Promise((resolve) => setTimeout(resolve, 500))
// Get the transaction receipt and parse the logs for the ERC20Created event.
const receipt = await publicClient.getTransactionReceipt({ hash })
const erc20CreatedLog = receipt.logs.find(
// Check if the log is from the factory address.
(log) => log.address.toLowerCase() === erc20FactoryAddress.toLowerCase(),
)
// Check whether the ERC20Created event exists in the logs, then decode the created ERC20 address.
if (erc20CreatedLog) {
const decodedLog = decodeEventLog({
abi: erc20FactoryAbi,
data: erc20CreatedLog.data,
topics: erc20CreatedLog.topics,
})
console.log('New ERC20 address:', decodedLog.args.erc20)
// Try reading data from the new ERC20 contract.
const erc20 = await getContract({
address: decodedLog.args.erc20,
abi: erc20Abi,
client: {
public: publicClient,
wallet: client,
},
})
console.log('ERC20 name:', await erc20.read.name())
console.log('ERC20 symbol:', await erc20.read.symbol())
console.log('ERC20 decimals:', await erc20.read.decimals())
} else {
console.log('ERC20Created event not found in logs')
}
} catch (error) {
console.error('Error sending transaction:', error)
}
}
createERC20()
Full Script Reference
Here is the entire script for reference:
const {
createPublicClient,
createWalletClient,
decodeEventLog,
getContract,
http,
} = require('viem')
const { privateKeyToAccount } = require('viem/accounts')
const erc20Abi = require('../abis/erc20Abi.json')
const erc20FactoryAbi = require('../abis/erc20FactoryAbi.json')
const miniEVM = require('./chain')
require('dotenv').config()
const rawPrivateKey = process.env.PRIVATE_KEY
if (!rawPrivateKey) {
throw new Error('PRIVATE_KEY environment variable is not set.')
}
const privateKey = rawPrivateKey.startsWith('0x')
? rawPrivateKey
: `0x${rawPrivateKey}`
const erc20FactoryAddress = process.env.ERC20_FACTORY_ADDRESS
if (!erc20FactoryAddress) {
throw new Error('ERC20_FACTORY_ADDRESS environment variable is not set.')
}
// Create an account from the private key.
const account = privateKeyToAccount(privateKey)
// Create a wallet client.
const client = createWalletClient({
account,
chain: miniEVM,
transport: http(),
})
// Create a public client.
const publicClient = createPublicClient({
chain: miniEVM,
transport: http(),
})
// Send the transaction.
async function createERC20() {
try {
// Call createERC20 function on the factory contract to create a new ERC20 token.
const hash = await client.writeContract({
address: erc20FactoryAddress,
abi: erc20FactoryAbi,
functionName: 'createERC20',
args: ['Test', 'TST', 18],
})
console.log('Transaction sent. Hash:', hash)
// Wait briefly for the transaction to be processed.
await new Promise((resolve) => setTimeout(resolve, 500))
// Get the transaction receipt and parse the logs for the ERC20Created event.
const receipt = await publicClient.getTransactionReceipt({ hash })
const erc20CreatedLog = receipt.logs.find(
// Check if the log is from the factory address.
(log) => log.address.toLowerCase() === erc20FactoryAddress.toLowerCase(),
)
// Check whether the ERC20Created event exists in the logs, then decode the created ERC20 address.
if (erc20CreatedLog) {
const decodedLog = decodeEventLog({
abi: erc20FactoryAbi,
data: erc20CreatedLog.data,
topics: erc20CreatedLog.topics,
})
console.log('New ERC20 address:', decodedLog.args.erc20)
// Try reading data from the new ERC20 contract.
const erc20 = await getContract({
address: decodedLog.args.erc20,
abi: erc20Abi,
client: {
public: publicClient,
wallet: client,
},
})
console.log('ERC20 name:', await erc20.read.name())
console.log('ERC20 symbol:', await erc20.read.symbol())
console.log('ERC20 decimals:', await erc20.read.decimals())
} else {
console.log('ERC20Created event not found in logs')
}
} catch (error) {
console.error('Error sending transaction:', error)
}
}
createERC20()
Running the Script
Run the script to create a new ERC20 token:
A successful run outputs something like this:
Transaction sent. Hash: 0x58b0b9326e9c877c0b966db112b3df8db710f986ba309345601ac222ddcb4c77
New ERC20 address: 0x9F363EB9649879b1C4993f9Ea9821d48346c3e04
ERC20 name: Test
ERC20 symbol: TST
ERC20 decimals: 18
You have now successfully created a new ERC20 token on the MiniEVM!