DEV Community

Cover image for Building an ERC-20 Token From A to Z (Hardhat + OpenZeppelin)
DevForge
DevForge

Posted on

Building an ERC-20 Token From A to Z (Hardhat + OpenZeppelin)

⚠️ Disclaimer
This article is for educational purposes only.
The token created here is not money, has no intrinsic value, and is not associated with any real-world brand or stablecoin.
Do not impersonate existing projects or tickers. Always use your own unique token name and symbol.

Why I Wrote This Article

Many people entering crypto ask the same questions:

  • “Why did a random token appear in my wallet?”
  • “Why do I see a balance but can’t sell it?”
  • “How are ERC-20 tokens actually created?”
  • “Why does a token look real if it’s fake?”

The best way to truly understand this is simple:

Create your own ERC-20 token once.

When you do that, everything suddenly makes sense:

  • balances
  • wallets
  • approvals
  • fake tokens
  • approvals

This article walks through the entire process from zero to deployment, step by step.

What We Will Build

We will build a clean educational ERC-20 token with:

  • OpenZeppelin ERC-20 implementation
  • Role-based access control
  • Pausable transfers
  • Minting
  • Deployment with Hardhat
  • Adding the token to MetaMask
  • Understanding how wallets detect balances

1. What Is ERC-20 (Plain English)

ERC-20 is not a currency.
It’s simply a standard interface — a set of functions that wallets and dApps know how to call.

At minimum, an ERC-20 token has:

  • balanceOf(address)
  • transfer(address, amount)
  • totalSupply()
  • name(), symbol(), decimals()

Important
An ERC-20 contract does not know its price.
Price exists only if:

  • a market exists
  • liquidity exists
  • someone is willing to trade

No liquidity → no price → no real value.

2. Environment Setup
Requirements

  • Node.js (LTS)
  • npm
  • MetaMask wallet
  • Sepolia test ETH
  • RPC provider (Alchemy, Infura, etc.)

Create the Project

mkdir my-token
cd my-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npx hardhat

Enter fullscreen mode Exit fullscreen mode

Choose:

Create a JavaScript project

3. Install OpenZeppelin

OpenZeppelin provides audited, production-grade smart contracts.

npm install @openzeppelin/contract

4. Writing the ERC-20 Token Contract

Create contracts/MyToken.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/*
    MyToken — an educational ERC-20 token.
    NOT money. NOT a stablecoin.
*/

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";

contract MyToken is ERC20, AccessControl, Pausable {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    constructor(uint256 initialSupply)
        ERC20("My Educational Token", "MYT")
    {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);

        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount)
        external
        onlyRole(MINTER_ROLE)
    {
        _mint(to, amount);
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    function _update(address from, address to, uint256 amount)
        internal
        override
        whenNotPaused
    {
        super._update(from, to, amount);
    }
}

Enter fullscreen mode Exit fullscreen mode

Key Concepts Here

  • Name and symbol are just strings
  • The contract itself stores balances
  • Tokens exist only after mint
  • If you don’t mint → balance will be zero

5. Hardhat Configuration
Create hardhat.config.js:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: "0.8.20",
  networks: {
    sepolia: {
      url: process.env.SEPOLIA_RPC,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
  },
};

Enter fullscreen mode Exit fullscreen mode

6. Environment Variables

Create .env (never commit this):

SEPOLIA_RPC=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
PRIVATE_KEY=0xYOUR_PRIVATE_KEY

Enter fullscreen mode Exit fullscreen mode

7. Deployment Script

Create scripts/deploy.js:

const hre = require("hardhat");

async function main() {
  const [deployer] = await hre.ethers.getSigners();

  console.log("Deploying with address:", deployer.address);

  const initialSupply = hre.ethers.parseUnits("1000", 18);

  const MyToken = await hre.ethers.getContractFactory("MyToken");
  const token = await MyToken.deploy(initialSupply);

  await token.waitForDeployment();

  console.log("Token deployed at:", await token.getAddress());
}

main().catch(console.error);

Enter fullscreen mode Exit fullscreen mode

8. Compile and Deploy

npx hardhat compile
npx hardhat run scripts/deploy.js --network sepolia

Enter fullscreen mode Exit fullscreen mode

You will receive a contract address.

9. Adding the Token to MetaMask

  1. Switch MetaMask to Sepolia
  2. Go to Assets → Import token
  3. Paste the contract address
  4. MetaMask auto-fills symbol & decimals

If balance shows 0, check:

  • correct network
  • minted address
  • decimals
  • wallet cache (reload MetaMask)

10. Why Tokens Can Appear “Out of Nowhere”
Here’s the critical realization:

Wallets do not store tokens.

A wallet simply calls:
balanceOf(yourAddress)
If any contract returns a non-zero number → the wallet displays it.

That means:

  • someone can mint tokens to your address
  • without asking
  • without permission

Your wallet will still show them.

11. Why Name, Symbol, and Icon Mean Nothing

Blockchains have:

  • no global registry of names
  • no trademark enforcement
  • no “official token” flag

Anyone can deploy:

  • any name
  • any symbol
  • any decimals
  • any logo The only real identity of a token is:

its contract address

12. The Core Security Lesson

Once you understand this, many scams become obvious:

  • “free tokens” → meaningless balances
  • fake stablecoins → just ERC-20 contracts
  • approval traps → dangerous permissions

Knowledge beats fear.

Conclusion

ERC-20 tokens are just smart contracts.
Balances are just numbers.
Price comes from markets, not from code.
Trust comes from verification, not appearance.

Creating your own token once is the fastest way to understand all of this.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.