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 tokensbalanceOf(address)
– account balancetransfer(address,uint256)
– send tokensapprove(address,uint256)
– allow spendertransferFrom(address,address,uint256)
– spender moves tokensallowance(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.js
For 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
(or8545
for 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> /F
Start Ganache CLI on 8545 (alt to GUI 7545)
ganache-cli --port 8545 --networkId 1337 --chain.chainId 1337
Connect MetaMask
- Network:
Ganache
- RPC URL:
http://127.0.0.1:8545
(or7545
for 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 1337
Update 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:7545
with 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:
balanceOf
and a successfultransfer
call.
HW5_FirstnameLastname.pdf
with screenshots in order.