Build & Deploy an ERC‑20 Token (Week 6)
Use Ganache + MetaMask + Remix to launch your own token, then transfer it between accounts.
- Part I — Ganache (Windows example) workspace with 10,000 ETH & 8,000,000 gas limit
- Part II — Optional: Truffle + Ganache workflow
- Part III — MetaMask setup and connection to Ganache
What is ERC‑20?
ERC‑20 is the most widely used standard for fungible tokens on Ethereum. It defines a consistent interface so wallets, dApps, and exchanges can interoperate.
Standard Functions
- totalSupply()– total tokens
- balanceOf(address)– account balance
- transfer(address,uint256)– send tokens
- approve(address,uint256)– allow spender
- transferFrom(address,address,uint256)– spender moves tokens
- allowance(owner,spender)– remaining approved amount
Use‑Cases
- Currency (e.g., stablecoins)
- Voting power in DAOs
- Ownership claims to assets
- Access control or staking incentives
Bitcoin tokens live on the Bitcoin blockchain. ERC‑20 tokens live on Ethereum. Different chains, different standards.
Part I — Ganache (Windows example)
- Install & open Ganache → New Workspace (name it “Voting Token Station”).
- Accounts & Keys → set Default Balance to 10000 ETH.
- Server → set Gas Limit to 8,000,000.
- Save & Start. Note the RPC (usually http://127.0.0.1:7545).
Part II — (Optional) Truffle + Ganache
If you prefer a framework workflow for later labs:
npm install -g truffle
mkdir erc20-truffle && cd erc20-truffle
truffle init
# Configure development network (host 127.0.0.1, port 7545, network_id "*") in truffle-config.jsFor HW5 you can stay in Remix; this Truffle path is optional.
Part III — MetaMask
- Install MetaMask extension → create a wallet and store the seed securely.
- Network → Add network →
        - Name: Ganache
- RPC URL: http://127.0.0.1:7545(or8545for CLI)
- Chain ID: 1337(or Ganache’s shown ID)
- Symbol: ETH
 
- Name: 
- (Optional) Import a Ganache private key to use a prefunded account.
ERC‑20 Token Code (OpenZeppelin)
Use OpenZeppelin’s audited implementation. In Remix, create VotingToken.sol and paste:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract VotingToken is ERC20 {
    address public owner;
    constructor(uint256 initialSupply) ERC20("VotingToken", "VOTE") {
        owner = msg.sender;
        // Mint using token units (not wei): pass 1_000_000 to get 1,000,000 * 10^18
        _mint(owner, initialSupply * 10 ** decimals());
    }
}
If Remix can’t resolve @openzeppelin/…
      Enable the "OpenZeppelin Contracts" library in Remix (File Explorers → remixd & libraries) or replace the import with a GitHub URL (version‑pin recommended):
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/token/ERC20/ERC20.sol";Plan B: Minimal ERC‑20 (no external imports)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract MiniERC20 {
    string public name = "VotingToken";
    string public symbol = "VOTE";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    constructor(uint256 initialSupply){
        totalSupply = initialSupply * (10 ** uint256(decimals));
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }
    function transfer(address to, uint256 value) external returns (bool){
        require(balanceOf[msg.sender] >= value, "balance");
        balanceOf[msg.sender] -= value; balanceOf[to] += value;
        emit Transfer(msg.sender, to, value); return true;
    }
    function approve(address spender, uint256 value) external returns (bool){
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value); return true;
    }
    function transferFrom(address from, address to, uint256 value) external returns (bool){
        require(balanceOf[from] >= value, "balance");
        require(allowance[from][msg.sender] >= value, "allowance");
        allowance[from][msg.sender] -= value;
        balanceOf[from] -= value; balanceOf[to] += value;
        emit Transfer(from, to, value); return true;
    }
}
Deploy with Remix + MetaMask + Ganache
- Open Remix.
- Compile VotingToken.sol(Solidity 0.8.x).
- Deploy & Run tab → Environment: Injected Web3 (connects to MetaMask on Ganache).
- Select your Ganache account in MetaMask.
- Constructor input: initialSupply(e.g.,1_000_000).
- Click Deploy → approve in MetaMask.
Interact
- Use balanceOf(account)to check balances.
- Use transfer(recipient, amount)to send tokens between Ganache accounts (remember 18 decimals if using raw units).
- Optional voting idea: treat balances as voting power, or add your own vote(proposalId, amount)function in a separate contract that readsbalanceOf.
If the Code or Ports Don’t Work
Free Port 8545 on Windows
netstat -ano | findstr :8545
# Note the PID from the LISTENING row
 taskkill /PID <PID> /FStart Ganache CLI on 8545 (alt to GUI 7545)
ganache-cli --port 8545 --networkId 1337 --chain.chainId 1337Connect MetaMask
- Network: Ganache
- RPC URL: http://127.0.0.1:8545(or7545for GUI)
- Chain ID: 1337, Symbol:ETH
Point Remix to MetaMask
In Remix → Deploy & Run → Environment: Injected Web3. Confirm MetaMask is on the Ganache network.
Optional: Use a different port (8546)
ganache-cli --port 8546 --networkId 1337 --chain.chainId 1337Update MetaMask RPC URL accordingly.
Quiz (answers below)
- What does ERC‑20 standardize?
- Which chain do ERC‑20 tokens live on?
- Name three core ERC‑20 functions.
- What does allowance(owner, spender)return?
- Why multiply by 10 ** decimals()when minting?
- What MetaMask Environment should you pick in Remix?
- Default Ganache GUI RPC URL/port?
- How do you free port 8545 on Windows?
- Two token use‑cases in dApps?
- How would you map token balance to voting power?
Show/Hide Answers
- A standard interface for fungible tokens (transfer, approve, allowance, etc.).
- Ethereum (or EVM‑compatible chains following ERC‑20).
- Examples: totalSupply,balanceOf,transfer,approve,transferFrom,allowance.
- The remaining amount a spender is allowed to transfer from an owner.
- To convert human‑readable tokens to smallest units (18‑decimal base by default).
- Injected Web3 (uses MetaMask’s current network, i.e., Ganache).
- http://127.0.0.1:7545with network ID shown in Ganache.
- netstat -ano | findstr :8545→ note PID →- taskkill /PID <PID> /F.
- Currency/payments, governance voting, staking/access, ownership claims.
- Use balanceOf(addr)as voting weight or snapshot balances before voting.
Submission Checklist
- Screenshot: Ganache workspace (10,000 ETH & gas limit 8,000,000).
- Screenshot: MetaMask on Ganache.
- Screenshot: Remix compile success for VotingToken.sol.
- Screenshot: Deployment tx confirmation in MetaMask.
- Screenshot: balanceOfand a successfultransfercall.
HW5_FirstnameLastname.pdf with screenshots in order.