🏠 Home
Beginner
01 — Introduction to Solidity 02 — Setting Up Your Environment 03 — Your First Smart Contract 04 — Data Types & Variables 05 — Functions & Visibility 06 — Control Flow 07 — Arrays & Mappings 08 — Structs & Enums
Intermediate
09 — Events & Logging 10 — Modifiers 11 — Inheritance 12 — Interfaces & Abstract Contracts 13 — Error Handling 14 — Ether & Wei 15 — Payable Functions 16 — msg.sender & msg.value 17 — Storage vs Memory vs Stack
Advanced
18 — Gas Optimization 19 — ERC-20 Tokens 20 — ERC-721 NFT Standard 21 — Contract Security 22 — Reentrancy Attacks 23 — Oracles & Chainlink 24 — Upgradeable Contracts 25 — Deploying to Mainnet
SolidityMaster / Lesson 19
Lesson 19 of 25

ERC-20 Tokens

Build a fully compliant fungible token from scratch.

Advanced

The ERC-20 Standard

ERC-20 is the token standard for fungible assets on Ethereum. Every DeFi protocol, exchange, and wallet understands ERC-20. The standard defines 6 required functions and 2 events.
Solidity
// The ERC-20 interface
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

Full ERC-20 Implementation

Here's a minimal but complete ERC-20 token:
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyToken {
    string  public name;
    string  public symbol;
    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(string memory _name, string memory _symbol, uint256 _supply) {
        name = _name; symbol = _symbol;
        totalSupply = _supply * 10**decimals;
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function transfer(address to, uint256 amount) public returns (bool) {
        require(balanceOf[msg.sender] >= amount, "Insufficient");
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        require(allowance[from][msg.sender] >= amount, "Not approved");
        require(balanceOf[from] >= amount, "Insufficient");
        allowance[from][msg.sender] -= amount;
        balanceOf[from] -= amount;
        balanceOf[to]   += amount;
        emit Transfer(from, to, amount);
        return true;
    }
}