Deep dive into the ERC-20 token standard that revolutionized Ethereum's ecosystem. Learn how this simple interface enabled the explosion of DeFi, the technical implementation details, and best practices for token development.
The ERC-20 token standard is arguably one of the most important innovations in the Ethereum ecosystem. If you've used USDC, swapped tokens on Uniswap, or participated in DeFi protocols, you've interacted with ERC-20 tokens, whether you realized it or not.
In this article, I'll break down what makes ERC-20 special, how it works under the hood, and the technical considerations when implementing your own token.
Before the ERC-20 standard emerged in 2015, creating tokens on Ethereum was chaotic. Each project implemented its own custom interface for basic operations like transfers and balance checks. This meant:
The ecosystem needed standardization, and that's exactly what ERC-20 provided.
ERC-20 defines a minimal interface that all compliant tokens must implement. Here's the complete specification:
interface IERC20 {
// Returns the total token supply
function totalSupply() external view returns (uint256);
// Returns the account balance of another account
function balanceOf(address account) external view returns (uint256);
// Transfers tokens to a specified address
function transfer(address recipient, uint256 amount) external returns (bool);
// Returns the amount which spender is still allowed to withdraw from owner
function allowance(address owner, address spender) external view returns (uint256);
// Allows spender to withdraw from your account multiple times, up to amount
function approve(address spender, uint256 amount) external returns (bool);
// Transfers tokens from one address to another using the allowance mechanism
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
// Events
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
The approve and transferFrom mechanism is one of the most powerful aspects of ERC-20. It enables:
However, this pattern also introduces security considerations. Unlimited approvals can be risky if a contract is compromised, which is why modern wallets often warn users about approval amounts.
Let's look at a production-grade implementation using OpenZeppelin's battle-tested contracts:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000 * 10**18; // 1 million tokens
constructor() ERC20("My Token", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 100_000 * 10**18); // Initial supply: 100k tokens
}
function mint(address to, uint256 amount) public onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
Decimals: ERC-20 tokens typically use 18 decimals (like ETH). This means 1 token is represented as 1 * 10^18 in storage. When displaying "100 tokens", you're actually storing 100000000000000000000.
Supply Management: The contract above implements:
Security Considerations:
The standardization of ERC-20 enabled the explosion of:
DeFi Protocols: Uniswap, Aave, Compound all rely on ERC-20's composability Stablecoins: USDC, USDT, DAI provide fiat-pegged value on-chain Governance: Token-based voting in DAOs like MakerDAO and Compound Wrapped Assets: WETH, WBTC bring other assets into the Ethereum ecosystem
Today, there are several hundred thousand ERC-20 token contracts on Ethereum, with aggregate market value in the hundreds of billions of dollars (most of it concentrated in stablecoins like USDC, USDT, and DAI).
While the base ERC-20 is minimal, common extensions include:
ERC20Snapshot provided in OpenZeppelin v4)Based on years of token implementations, here are key recommendations:
Ownable or AccessControl for privileged functions