Education › Blockchain & Web3 › Guided project

Deploy your own ERC-20 token

Go from an empty folder to your own token live on a public test network: set up Hardhat, write an ERC-20 by inheriting OpenZeppelin's audited base, test it properly, deploy it to a local chain and then to Sepolia, and interact with it from a script and your wallet — all for free.

Beginner about 5 hours 6 phases · 25 steps 0 / 25 done
What you will have at the end

A working ERC-20 token deployed to the Sepolia testnet, verified on Etherscan, visible in your wallet, and transferable — built with the professional write-test-deploy loop.

Before you start
  • The Blockchain lessons through Tokens and standards and Developer tooling — you'll use the ERC-20 interface, the develop-test-deploy loop, and a testnet
  • Node.js (18+) and npm installed, and a code editor
  • A browser wallet (MetaMask) with the Sepolia testnet added, and a little Sepolia test ETH from a faucet
Tools you will install
  • Hardhat — compile, test, and deploy the contract, and run scripts against local and test networks ↗
  • OpenZeppelin Contracts — the audited ERC-20 base you inherit instead of writing the standard yourself ↗
  • MetaMask + a Sepolia faucet — an account to deploy from and free test ETH to pay gas on the testnet ↗
  • Etherscan (Sepolia) — inspect your deployed contract, verify its source, and watch transfers ↗

Tick each step as you finish it — your progress is saved in this browser only (back up or restore on the hub). Every code block has a copy button. If something goes wrong, the troubleshooting section at the end covers the usual suspects.

Phase 1

Set up the toolkit

Get a working Hardhat project with OpenZeppelin installed, so you can compile and test contracts locally.

  1. Confirm your Node.js version is 18 or newer, since Hardhat needs a current runtime. If it's older, install a newer Node before continuing.
    bash
    node --version
    Check: The printed version is v18 or higher.
  2. Create a project folder, initialise npm, and install Hardhat and the toolbox as dev dependencies.
    bash
    mkdir my-token && cd my-token
    npm init -y
    npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
  3. Initialise a Hardhat project and choose the JavaScript project when prompted. This scaffolds the folders (contracts, test, scripts) and a config file.
    bash
    npx hardhat init
    Check: You have contracts/, test/, and scripts/ folders and a hardhat.config.js.
  4. Install OpenZeppelin Contracts, the library holding the audited ERC-20 you'll inherit from.
    bash
    npm install @openzeppelin/contracts
  5. Delete the sample contract and test that Hardhat generated (usually Lock.sol and its test) so you start clean, then confirm the project still compiles.
    bash
    rm contracts/Lock.sol test/Lock.js
    npx hardhat compile
    Check: Compilation succeeds with nothing to compile, or an empty artifacts build, and no errors.
Phase 2

Write the token

Create your ERC-20 by inheriting the audited base and minting an initial supply — the whole contract is a few lines.

  1. Create contracts/MyToken.sol. Inherit from OpenZeppelin's ERC20 and set the name and symbol in the constructor, minting an initial supply to the deployer. Remember amounts are in the smallest unit, so multiply by 10**decimals().
    solidity
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    
    contract MyToken is ERC20 {
        constructor() ERC20("My Token", "MTK") {
            _mint(msg.sender, 1000000 * 10 ** decimals());  // 1,000,000 MTK
        }
    }
  2. Compile the contract and fix any errors. Compilation turns your Solidity into the EVM bytecode that will be deployed.
    bash
    npx hardhat compile
    Check: Compilation succeeds and an artifact for MyToken appears under artifacts/.
  3. Read the OpenZeppelin ERC20 source you inherited (in node_modules/@openzeppelin) and note that transfer, approve, transferFrom, balanceOf and the Transfer/Approval events all come from it — you wrote only the constructor.
    This is the point of standards and audited libraries: your token is fully ERC-20 compliant and every wallet will support it, yet you wrote almost no code.
Phase 3

Test it

Write tests that deploy the token and check its behaviour, so you trust it before spending gas on a real network.

  1. Create test/MyToken.js. Deploy the token in the test and check the name, symbol, and that the deployer received the full initial supply.
    javascript
    const { expect } = require("chai");
    const { ethers } = require("hardhat");
    
    describe("MyToken", function () {
      it("mints the full supply to the deployer", async function () {
        const [owner] = await ethers.getSigners();
        const Token = await ethers.getContractFactory("MyToken");
        const token = await Token.deploy();
        expect(await token.name()).to.equal("My Token");
        const supply = await token.totalSupply();
        expect(await token.balanceOf(owner.address)).to.equal(supply);
      });
    });
  2. Add a test that transfers tokens from the owner to a second account and checks both balances changed correctly.
    javascript
      it("transfers tokens between accounts", async function () {
        const [owner, alice] = await ethers.getSigners();
        const Token = await ethers.getContractFactory("MyToken");
        const token = await Token.deploy();
        const amount = ethers.parseUnits("100", 18);
        await token.transfer(alice.address, amount);
        expect(await token.balanceOf(alice.address)).to.equal(amount);
      });
  3. Run the test suite against Hardhat's built-in local chain. Everything runs in seconds with no gas cost.
    bash
    npx hardhat test
    Check: All tests pass.
  4. Add one more test that confirms a transfer of more than your balance reverts, so you've exercised a failure case, and re-run the suite.
    Testing the failure path matters as much as the happy path — a token that lets you overspend is broken. OpenZeppelin's ERC20 reverts on insufficient balance; your test should assert that.
Phase 4

Deploy to a local chain

Practise the deployment itself on a free local network before touching a real testnet.

  1. Write scripts/deploy.js that deploys the token and prints its address. A deploy script is code you keep and version, so deployment is reproducible.
    javascript
    const { ethers } = require("hardhat");
    
    async function main() {
      const Token = await ethers.getContractFactory("MyToken");
      const token = await Token.deploy();
      await token.waitForDeployment();
      console.log("MyToken deployed to:", await token.getAddress());
    }
    
    main().catch((e) => { console.error(e); process.exit(1); });
  2. In one terminal, start a standalone local blockchain. It prints a set of test accounts pre-funded with fake ETH.
    bash
    npx hardhat node
    Check: A local node is running and lists 20 funded accounts with their private keys.
  3. In a second terminal, run the deploy script against that local node and note the printed contract address.
    bash
    npx hardhat run scripts/deploy.js --network localhost
    Check: The script prints 'MyToken deployed to: 0x...'.
  4. Open the Hardhat console against the local node and read a value from your deployed token to confirm it responds, then stop the node when done. This proves the deployment works before spending anything on a testnet.
    bash
    npx hardhat console --network localhost
    In the console you can attach to the address and call token.name() or token.totalSupply(). It's the quickest way to sanity-check a fresh deployment interactively.
Phase 5

Deploy to a testnet

Put your token on the public Sepolia test network, where it behaves exactly like mainnet but the ETH is free.

  1. Get a Sepolia RPC URL from a node provider (such as Alchemy or Infura — a free account gives you one), and export your deployer wallet's private key from MetaMask for a throwaway test account.
    Use a fresh test-only account, never a wallet holding real funds. A private key in a project can leak; keep real value far away from development keys.
  2. Install dotenv and create a .env file holding your RPC URL and private key. Add .env to .gitignore immediately so it is never committed.
    bash
    npm install dotenv
    printf 'SEPOLIA_RPC_URL=your-rpc-url\nPRIVATE_KEY=your-test-key\n' > .env
    echo '.env' >> .gitignore
    Check: A .env file exists with your two values, and .gitignore contains .env.
  3. Configure the Sepolia network in hardhat.config.js, reading the URL and key from the environment so no secrets are hard-coded.
    javascript
    require("@nomicfoundation/hardhat-toolbox");
    require("dotenv").config();
    
    module.exports = {
      solidity: "0.8.20",
      networks: {
        sepolia: {
          url: process.env.SEPOLIA_RPC_URL,
          accounts: [process.env.PRIVATE_KEY],
        },
      },
    };
  4. Make sure your test account holds a little Sepolia ETH (from a faucet), then deploy to Sepolia. This is a real transaction on a shared network, so it takes a few seconds to confirm.
    bash
    npx hardhat run scripts/deploy.js --network sepolia
    Check: The script prints the deployed address, and searching it on sepolia.etherscan.io shows your contract.
Phase 6

Interact and verify

Use your live token from a script and your wallet, and publish its source so anyone can read it — the finishing touches of a real deployment.

  1. Import the token into MetaMask by adding a custom token with your deployed contract address. Your 1,000,000 MTK balance appears — because your wallet is just reading the contract's ledger.
    Check: MetaMask shows your MTK balance under the Sepolia network.
  2. Send some MTK from your wallet to a friend's address (or a second account of yours) and watch the transfer confirm, then find the Transfer event on Etherscan.
    Check: The recipient's balance increases and the transfer appears on sepolia.etherscan.io.
  3. Verify the contract's source on Etherscan using the Hardhat verify plugin, so anyone can read and trust the code behind your token. You'll need a free Etherscan API key in your .env and the verify config added.
    bash
    npm install --save-dev @nomicfoundation/hardhat-verify
    npx hardhat verify --network sepolia YOUR_CONTRACT_ADDRESS
    Check: Etherscan shows a green check and displays your verified Solidity source.
  4. Write a script that reads the total supply and the deployer's balance from the live Sepolia contract, and run it, so you've interacted with your token programmatically as well as through the wallet.
    bash
    npx hardhat run scripts/read.js --network sepolia
    A small read script (getContractAt to your address, then call totalSupply/balanceOf) confirms your deployment is reachable over the public network from code, not just the wallet UI.
  5. Write down your token's address, network, and the transactions you made in a short README, so the project is a shareable portfolio piece — a token you designed, tested, deployed, and verified.
    A verified contract with a clear README is a genuine portfolio artifact. You've done the full professional loop: write, test, deploy locally, deploy to a testnet, interact, and verify.
Help

Troubleshooting

npx hardhat compile fails with a Solidity version error.
The pragma in your contract and the solidity version in hardhat.config.js must be compatible. Set both to a 0.8.x version (e.g. pragma ^0.8.20 and solidity: "0.8.20"), and ensure OpenZeppelin is installed so its imports resolve.
Deploying to Sepolia fails with 'insufficient funds'.
Your deployer account has no Sepolia ETH. Copy the account address from MetaMask, request test ETH from a Sepolia faucet, wait for it to arrive, and try again — deployment costs gas even on a testnet.
The deploy script errors with 'invalid private key' or 'network not found'.
Check the .env values are loaded (require('dotenv').config() at the top of the config) and the private key has no 0x issues or stray spaces. Confirm you passed --network sepolia and that the sepolia block is present in hardhat.config.js.
MetaMask doesn't show the token after import.
Confirm MetaMask is on the Sepolia network, and that you pasted the exact deployed contract address. The symbol and decimals should auto-fill from the contract; if the balance is zero, you may be looking at an account that isn't the deployer.
Etherscan verification fails with a bytecode mismatch.
Verification must use the exact compiler version and settings you deployed with. Ensure the solidity version in hardhat.config.js matches, that optimizer settings are identical, and that you're verifying the right address on the right network.
Next

Where to go from here

Did a step fail or feel unclear? Tell me which one →