What an ERC-20 Token Really Is
A fungible token on Ethereum is just a smart contract that tracks who owns how much. The ERC-20 standard defines a common interface so wallets, exchanges, and other contracts can interact with any token the same way. At its core the contract holds a mapping from addresses to balances, plus an allowance mapping that lets one address authorize another to spend on its behalf. There is no physical coin—ownership is simply a number in the contract's storage, and transferring tokens means decrementing one balance and incrementing another while emitting a Transfer event.
Cricket analogy: An ERC-20 balance mapping is like the official scorebook: no physical runs exist, just numbers next to each batter's name that everyone agrees on, updated ball by ball.
The Required Interface
ERC-20 mandates a specific set of functions and events: totalSupply(), balanceOf(address), transfer(to, amount), approve(spender, amount), allowance(owner, spender), and transferFrom(from, to, amount), plus the Transfer and Approval events. The approve/transferFrom pair enables the allowance pattern, which lets contracts like a DEX move your tokens only up to a limit you set—critical for DeFi composability. Optional metadata functions name(), symbol(), and decimals() (usually 18) tell wallets how to display the token. Adhering exactly to this interface is what makes a token 'just work' across the entire Ethereum ecosystem.
Cricket analogy: The ERC-20 interface is like the fixed set of signals an umpire must know—out, four, six, wide; approve/transferFrom is the captain pre-authorizing a substitute to field within agreed limits.
A Minimal Implementation From Scratch
Writing ERC-20 by hand is instructive: the constructor mints the initial supply to the deployer, transfer moves tokens between balances after checking the sender has enough, and approve plus transferFrom implement delegated spending. Each state-changing function emits the appropriate event so off-chain tools can index activity. Note the decimals convention: with 18 decimals, one whole token is represented as 1e18 base units, so amounts are always in the smallest unit. This from-scratch version is great for learning, but for anything real you should extend OpenZeppelin's audited ERC20 base instead of trusting your own edge-case handling.
Cricket analogy: Coding ERC-20 by hand is like learning to keep score manually before trusting the electronic scoreboard—valuable practice, but for a Test match you use the certified system.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken {
string public name = "SimpleToken";
string public symbol = "SMPL";
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 ** 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, "Insufficient 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, "Insufficient balance");
require(allowance[from][msg.sender] >= value, "Allowance exceeded");
allowance[from][msg.sender] -= value;
balanceOf[from] -= value;
balanceOf[to] += value;
emit Transfer(from, to, value);
return true;
}
}The production-grade approach is a few lines: import OpenZeppelin's ERC20 and mint in the constructor. Example: contract MyToken is ERC20 { constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 1_000_000 * 10 ** decimals()); } }. You inherit audited transfer, approve, and allowance logic for free.
A classic pitfall is the approve race condition: changing an existing allowance directly can let a spender front-run and spend both the old and new amounts. Prefer OpenZeppelin's safeIncreaseAllowance/safeDecreaseAllowance, or set the allowance to zero before setting a new value. Also remember amounts are in base units—forgetting the 18-decimal scaling is a common bug.
- An ERC-20 token is a contract that stores balances and allowances in mappings—no physical coin exists.
- The standard interface (totalSupply, balanceOf, transfer, approve, allowance, transferFrom + Transfer/Approval events) makes tokens interoperable.
- The approve/transferFrom allowance pattern enables DeFi composability like DEX swaps.
- decimals is typically 18, so one whole token equals 1e18 base units; always work in base units.
- Writing ERC-20 from scratch teaches the mechanics, but production tokens should extend OpenZeppelin's audited ERC20.
- Watch for the approve race condition and use safeIncreaseAllowance/safeDecreaseAllowance to avoid it.
Practice what you learned
1. What does an ERC-20 contract fundamentally store to represent token ownership?
2. What is the purpose of the approve/transferFrom pair?
3. If a token has 18 decimals, how is one whole token represented in base units?
4. Which event must a compliant ERC-20 transfer emit?
5. What is the recommended way to build a production ERC-20?
Was this page helpful?
You May Also Like
ERC-20 Token Standard
The interface and mechanics behind fungible tokens on Ethereum, covering core functions, the approve/allowance pattern, events, and safe implementation.
Solidity Best Practices
A practical checklist of security, gas, and maintainability practices for production Solidity: checks-effects-interactions, access control, safe external calls, custom errors, and reuse of audited libraries.
ERC-721 NFT Standard
The standard for non-fungible tokens on Ethereum, covering unique token IDs, ownership and metadata, safe transfers with receiver hooks, and minting.
Your First Smart Contract
Write, compile, and deploy a complete storage contract, learning about the constructor, functions, events, access control, and the payable keyword along the way.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics