The Ethereum Request for Comment (ERC)-20 is a technical standard for creating and implementing tokens on the Ethereum blockchain. It defines a set of rules and functions that a token contract must follow. This ensures that tokens can be easily exchanged and integrated with various decentralised applications (dApps) and platforms within the Ethereum ecosystem. ERC-20 tokens can be used as rewards, company shares, a sense of identity in a community, or a means of exchange on Layer Two (L2) networks.
Tokens and coins are not the same. Coins are native currencies of Layer 1 blockchains, e.g. BTC for Bitcoin, ETH for Ethereum, and SOL for Solana, etc. Tokens, on the other hand, are cryptocurrencies built on top of existing blockchains, often using smart contracts, e.g. LINK, AAVE, UN,I etc
In this article, you will learn how to build a multi-chain ERC-20 generator using GetBlock RPC (a web3 RPC provider). This means users can create their token from a user interface without writing code, as seen below:
Prerequisites
- A web3 wallet, preferably Metamask
- A code editor, such as VSCode
- Must have installed Node
- A package manager installed on your machine, like yarn, npm, or pnpm
- Basic programming knowledge
- A bottle of water
Tools and Technologies Needed
- OpenZeppelin Contract
- A frontend framework(I will be using React)
- Ether.js
- TailwindCss for styling
- Smart contract
Setting Up Your GetBlock Account
-
Sign up on GetBlock using either your Gmail, GitHub or Metamask wallet
-
After signing up, you will be routed to your dashboard
-
Scroll down to
My endpoints
-
Select your choice of protocol, network, and then click “Get”
-
Automatically, a URL will be generated for you containing your access token
Ensure you keep this URL safe.Please note that you are on a free plan, which means you are limited to only two endpoints and cannot access other features like Usage Statistics. I encourage you to upgrade your account to enjoy more features.
Setting Up Your Dev Environment
-
Create a new Folder with any name of your choice, e.g
ERC-20-generator'
-
Install the frontend framework of your choice
-
Install Hardhat using the following command:
//npm npm install --save-dev hardhat //yarn yarn add --dev hardhat //pnpm pnpm add -D hardhat
-
Initialise hardhat using this command:
npx hardhat init
-
Select
Create an empty hardhat.config.js
using the arrow key and accept all promptsileolami@MacBookAir erc-20 % npx hardhat init WARNING: You are currently using Node.js v23.9.0, which is not supported by Hardhat. This can lead to unexpected behavior. See https://hardhat.org/nodejs-versions 888 888 888 888 888 888 888 888 888 888 888 888 888 888 888 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888 888 888 "88b 888P" d88" 888 888 "88b "88b 888 888 888 .d888888 888 888 888 888 888 .d888888 888 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b. 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888 👷 Welcome to Hardhat v2.23.0 👷 ✔ What do you want to do? · Create an empty hardhat.config.js ✨ Config file created ✨ Give Hardhat a star on Github if you're enjoying it! ⭐️✨ https://github.com/NomicFoundation/hardhat
-
Install other dependencies using this command:
npm install --save-dev @nomicfoundation/hardhat-toolbox npm install @openzeppelin/contracts ether
You may also install your choice of CSS framework at this point
Writing and Compiling the ERC-20 Contract
You're only writing and compiling the ERC-20 contract for this project because it will serve as a template. Users will provide their token details, and deployment will be handled from the frontend.
-
Create a new folder
contract
-
Create a new file
token.sol
and import the following code:// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract XToken is ERC20 { constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) { _mint(msg.sender, initialSupply); } }
-
import "@openzeppelin/contracts/token/ERC20/ERC20.sol”
: This imports the OpenZeppelin ERC20 implementation, which provides all standard ERC-20 functionality. -
Inside
XToken is ERC20
, it defines the template in which the users will be able to create their token. After that, it mints theinitialSupply
tokens and assigns them to the address that deployed the contract (msg.sender
)
-
To compile this code, run the following command:
npx hardhat compile
This will compile the smart contract and generate an
abi
andbytecode
. This JSON file is what you will use to interact with the smart contract. You will see this file underartifacts
folder: -
In your
src
folder, create two new files and name themtoken.json
andtokenBytecode.json
respectively -
Copy the
abi
fromXToken.json
and paste it insidetoken.json
using this format:{ "abi": [ ... ] }
-
In the
XToken.json
file, scroll down, copy thebytecode
, and paste it intotokenBytecode.json
using this format:{ "bytecode": "0x608060405234801561001057600080fd5b50604051..." }
Building the User Interface (UI)
Creating and Storing Access Tokens in .env
- Go to your GetBlock account and create multiple node endpoints.
I will be using Ethereum(Sepolia) and Rootstock Testnet.
-
Create a
.env
file. -
Copy the endpoints you created earlier and store them in the
.env
fileROOTSTOCK_RPC=https://go.getblock.io/<ACCESS_TOKEN> ETHEREUM_RPC=https://go.getblock.io/<ACCESS_TOKEN>
Network Configuration
-
Create a
config
folder undersrc
directory -
Create a
networks.jsx
file and add this code:// Ensure to edit this code to suit your choice of networks. export const networks = { ethereum: { chainId: 11155111, rpc: import.meta.env.ETHEREUM_RPC_URL, name: "Ethereum", nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18, }, blockExplorer: "https://sepolia.etherscan.io", }, rootstock: { chainId: 31, rpc: import.meta.env.ROOTSTOCK_RPC_URL, name: "Rootstock", nativeCurrency: { name: "TRBTC", symbol: "TRBTC", decimals: 18, }, blockExplorer: "https://rootstock-testnet.blockscout.com", }, };
You can list of networks’ detail here
Utility Functions
-
Under
src
folder, create another subfolder and name itutils
-
Under
/utils
Create two files calledtoken.jsx
andwallet.jsx
At this point, your folder should be looking like this:
/src ├── config/ │ └── networks.js # Network configuration │ ├── utils/ │ ├── tokenUtils.jsx # Token deployment and wallet integration functions │ └── walletUtils.jsx # Wallet connection and network switching functions │ ├── App.jsx # Main TokenCreator component ├── index.css # Global styles including glass effect ├── main.jsx # Entry point for the React application ├── token.json # ERC-20 token ABI └── tokenBytecode.json # ERC-20 token bytecode
-
Inside
token.jsx
, Copy and paste the following code:import { ethers } from "ethers"; import XToken from "../token.json"; import XTokenBytecode from "../tokenBytecode.json"; export const deployToken = async (name, symbol, supply, signer) => { const factory = new ethers.ContractFactory( XToken.abi, XTokenBytecode.bytecode, signer ); const contract = await factory.deploy( name, symbol, ethers.parseUnits(supply, 18) ); await contract.waitForDeployment(); const tokenAddress = await contract.getAddress(); return tokenAddress; }; export const addTokenToWallet = async (address, symbol) => { await window.ethereum.request({ method: "wallet_watchAsset", params: { type: "ERC20", options: { address, symbol, decimals: 18, }, }, }); };
This codebase:
- Creates a contract factory using the smart contract ABI and bytecode
- Deploys the token with its name, symbol, and supply
- Converts supply to proper units (with 18 decimals)
- Waits for deployment confirmation
- Returns the deployed token's address
- Adds the newly created token to MetaMask
- Uses the MetaMask API's
wallet_watchAsset
method
-
Inside
wallet.jsx
, Copy and paste the following code:import { ethers } from "ethers"; import { networks } from "../config/networks"; export const connectWallet = async (networkId) => { if (!window.ethereum) throw new Error("Please install MetaMask"); const selected = networks[networkId]; const hexChainId = "0x" + selected.chainId.toString(16); // Switch or add chain try { await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hexChainId }], }); } catch (switchError) { // Add the network if it doesn't exist in MetaMask if (switchError.code === 4902) { await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: hexChainId, chainName: selected.name, rpcUrls: [selected.rpc], nativeCurrency: selected.nativeCurrency, blockExplorerUrls: [selected.blockExplorer], }, ], }); } else { throw switchError; } } // Now connect const provider = new ethers.BrowserProvider(window.ethereum); await provider.send("eth_requestAccounts", []); return await provider.getSigner(); }; export const switchNetwork = async (networkId, currentChainId) => { const selected = networks[networkId]; const hexChainId = "0x" + selected.chainId.toString(16); if (currentChainId !== selected.chainId) { try { await window.ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hexChainId }], }); } catch (switchError) { if (switchError.code === 4902) { await window.ethereum.request({ method: "wallet_addEthereumChain", params: [ { chainId: hexChainId, chainName: selected.name, rpcUrls: [selected.rpc], nativeCurrency: selected.nativeCurrency, blockExplorerUrls: [selected.blockExplorer], }, ], }); } else { throw switchError; } } const provider = new ethers.BrowserProvider(window.ethereum); await provider.send("eth_requestAccounts", []); return await provider.getSigner(); } return null; };
This codebase:
- Auto-adds networks to MetaMask if they don't exist
- Handles network switching with proper error management
- Request for transaction approval
- Converts chain IDs to hex format for MetaMask compatibility
The User Interface
At this point, you must have created the network configuration and the utility functions. In this phase, you are building the UI for your users to create their token.
-
Clear out what is inside
App.jsx
and add the following code:import React, { useState } from "react"; import { networks } from "./config/networks"; import { connectWallet, switchNetwork } from "./utils/wallet"; import { deployToken, addTokenToWallet } from "./utils/token"; const TokenCreator = () => { const [form, setForm] = useState({ name: "", symbol: "", supply: "", network: "ethereum", }); const [status, setStatus] = useState(""); const [walletConnected, setWalletConnected] = useState(false); const [signer, setSigner] = useState(null); const handleChange = (e) => { setForm({ ...form, [e.target.name]: e.target.value }); }; const handleConnectWallet = async () => { try { const signerInstance = await connectWallet(form.network); setSigner(signerInstance); setWalletConnected(true); setStatus("✅ Wallet connected to " + form.network); } catch (err) { console.error(err); setStatus("❌ Failed to connect or switch network"); } }; const handleDeployToken = async () => { const { name, symbol, supply, network } = form; if (!signer) { setStatus("❌ Wallet not connected"); return; } try { // Check if we need to switch networks const currentNetwork = await signer.provider.getNetwork(); const selected = networks[network]; if (currentNetwork.chainId !== selected.chainId) { setStatus("Switching to " + network + " network..."); const newSigner = await switchNetwork(network, currentNetwork.chainId); if (newSigner) setSigner(newSigner); } setStatus("Deploying token..."); const tokenAddress = await deployToken(name, symbol, supply, signer); setStatus(`✅ Token deployed at ${tokenAddress}`); await addTokenToWallet(tokenAddress, symbol); } catch (err) { console.error(err); setStatus("❌ Failed to deploy token"); } }; return ( <div className="glass-background p-4 max-w-md mx-auto my-48 rounded-xl shadow-md border space-y-4"> <h2 className="text-xl font-semibold text-center">ERC-20 Token Maker</h2> {!walletConnected ? ( <button onClick={handleConnectWallet} className="w-full bg-green-600 text-white p-2 rounded hover:bg-green-700 shadow-2xl" > Connect Wallet </button> ) : ( <> <input type="text" name="name" placeholder="Token Name" value={form.name} onChange={handleChange} className="w-full p-2 border rounded" /> <input type="text" name="symbol" placeholder="Token Symbol" value={form.symbol} onChange={handleChange} className="w-full p-2 border rounded" /> <input type="number" name="supply" placeholder="Initial Supply" value={form.supply} onChange={handleChange} className="w-full p-2 border rounded " /> <select name="network" value={form.network} onChange={handleChange} className="w-full p-2 border rounded" > <option value="ethereum">Ethereum</option> <option value="rootstock">Rootstock</option> </select> <button onClick={handleDeployToken} className="w-full bg-blue-600 text-white p-2 rounded hover:bg-blue-700" > Deploy Token </button> </> )} {status && <p className="text-sm mt-2">{status}</p>} </div> ); }; export default TokenCreator;
This codebase contains Tailwindcss styling, ensure to edit this code.
In this codebase:
- Conditional rendering based on wallet connection
- Form for token creation (name, symbol, supply)
- Network selection dropdown
- Status messages for user feedback
At the end, you should have something like this:
Congratulations 🥂, you just created your own ERC-20 Maker
Conclusion
GetBlock allows developers to create multi-chain dApps using their RPC. This projects eliminate the need for users to write code when creating their token.
You can add extra features to this project e.g:
a. Airdrop feature that lets users distribute their tokens to their community based on activities, contributions, or tasks.
b. Logo for token identity