ERC-20 is the technical standard that defines how fungible tokens work on the Ethereum blockchain. If you have ever traded USDT, USDC, LINK, or UNI, you have interacted with an ERC-20 token. This guide explains exactly what ERC-20 is, how its functions work under the hood, and why it became the foundation of the entire Ethereum token ecosystem.
What is ERC-20?
ERC-20 stands for Ethereum Request for Comments 20. It is a standard interface for fungible tokens — tokens where every unit is identical and interchangeable, just like dollars in a bank account. Proposed by Fabian Vogelsteller and Vitalik Buterin in November 2015 (EIP-20), it defines a common set of functions and events that all token contracts must implement.
Before ERC-20, every token contract had its own unique interface. Wallets, exchanges, and other smart contracts had to write custom code for each token. ERC-20 solved this by creating a universal API: any wallet or application that supports ERC-20 can automatically work with any ERC-20 token, whether it was deployed yesterday or five years ago.
Today, there are hundreds of thousands of ERC-20 tokens deployed on Ethereum, representing everything from stablecoins to governance tokens to utility tokens. The standard has also been adopted by every EVM-compatible chain, including Polygon, Arbitrum, Optimism, BSC, and Avalanche.
The 6 Required Functions
Every ERC-20 token contract must implement the following six functions. These form the core interface that wallets, DEXs, and other contracts rely on.
1. totalSupply()
Returns the total number of tokens in existence. This includes all minted tokens minus any that have been burned.
function totalSupply() external view returns (uint256);2. balanceOf(address)
Returns the token balance of a specific address. This is the most frequently called function on any ERC-20 contract.
function balanceOf(address account) external view returns (uint256);3. transfer(address, uint256)
Moves tokens from the caller's address to the recipient. Returns a boolean indicating success. This is the function called when you send tokens directly from your wallet.
function transfer(address to, uint256 amount) external returns (bool);4. allowance(address, address)
Returns the remaining number of tokens that a spender is allowed to spend on behalf of the owner. This value changes when approve() or transferFrom() is called.
function allowance(address owner, address spender) external view returns (uint256);5. approve(address, uint256)
Grants a spender permission to withdraw up to a specified amount of tokens from the caller's account. This is the first step of the approve/transferFrom pattern used by DEXs and DeFi protocols.
function approve(address spender, uint256 amount) external returns (bool);6. transferFrom(address, address, uint256)
Moves tokens from one address to another using the allowance mechanism. The caller must have been previously approved to spend at least the specified amount. This is used by smart contracts (like Uniswap) to pull tokens from your wallet after you approve.
function transferFrom(address from, address to, uint256 amount) external returns (bool);The 2 Required Events
ERC-20 contracts must emit these events so that off-chain applications (wallets, block explorers, indexers) can track token movements.
Transfer Event
Emitted whenever tokens are moved, including minting (from the zero address) and burning (to the zero address).
event Transfer(address indexed from, address indexed to, uint256 value);Approval Event
Emitted whenever approve() is called, indicating that an owner has authorized a spender to use a certain amount of tokens.
event Approval(address indexed owner, address indexed spender, uint256 value);Optional Functions
While not required by the standard, virtually all ERC-20 tokens implement these three additional functions:
function name() external view returns (string); // e.g., "USD Coin"
function symbol() external view returns (string); // e.g., "USDC"
function decimals() external view returns (uint8); // e.g., 6 or 18The decimals function is especially important. Since Solidity does not support floating-point numbers, token amounts are stored as integers. A token with 18 decimals stores 1.0 token as 1000000000000000000 (10^18). USDC uses 6 decimals, so 1.0 USDC is stored as 1000000.
How Token Transfers Work
There are two ways to transfer ERC-20 tokens, and understanding the difference is critical for working with DeFi protocols.
Direct Transfer
The simplest method. You call transfer(to, amount) directly on the token contract. This is what happens when you send tokens from your wallet to another address.
// Alice sends 100 USDC to Bob
// Alice calls: usdc.transfer(bob, 100_000_000) // 100 * 10^6
// Flow: Alice -> Bob (direct)Approve + TransferFrom Pattern
This two-step pattern is used when a smart contract needs to pull tokens from your wallet. For example, when you swap tokens on Uniswap:
- Step 1 — Approve: You call
approve(uniswapRouter, amount)on the token contract, granting the Uniswap router permission to spend your tokens. - Step 2 — TransferFrom: The Uniswap router calls
transferFrom(you, pool, amount)to move tokens from your wallet into the liquidity pool.
// Step 1: Alice approves Uniswap Router to spend her USDC
usdc.approve(uniswapRouter, 100_000_000);
// Step 2: Uniswap Router pulls USDC from Alice to the pool
usdc.transferFrom(alice, pool, 100_000_000);Security tip: Avoid approving unlimited amounts (type(uint256).max) unless you trust the contract completely. If the contract is compromised, the attacker can drain all your approved tokens. Use exact amounts when possible.
Complete ERC-20 Contract Example
Here is a minimal but complete ERC-20 implementation in Solidity. In production, you would use OpenZeppelin's battle-tested implementation, but this shows the core logic:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleToken {
string public name = "SimpleToken";
string public symbol = "SIM";
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 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
require(balanceOf[from] >= amount, "Insufficient balance");
require(allowance[from][msg.sender] >= amount, "Insufficient allowance");
allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
return true;
}
}Popular ERC-20 Tokens
The following table lists some of the most widely used ERC-20 tokens on Ethereum mainnet:
| Token | Symbol | Decimals | Contract Address |
|---|---|---|---|
| Tether USD | USDT | 6 | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| USD Coin | USDC | 6 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Chainlink | LINK | 18 | 0x514910771AF9Ca656af840dff83E8264EcF986CA |
| Uniswap | UNI | 18 | 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 |
| Wrapped ETH | WETH | 18 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| Dai Stablecoin | DAI | 18 | 0x6B175474E89094C44Da98b954EedeAC495271d0F |
| Aave | AAVE | 18 | 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9 |
Notice that USDT and USDC use 6 decimals while most other tokens use 18. This is a common source of bugs when developers assume all tokens have 18 decimals. Always check the decimals() value before performing arithmetic.
Common Vulnerabilities
ERC-20 tokens have been the target of numerous exploits. Here are the most common vulnerability patterns to watch for:
Approval Race Condition
If Alice has approved Bob to spend 100 tokens and wants to change the allowance to 50, Bob can front-run the second approve() call. He spends the original 100, then the new approval sets 50, letting him spend 150 total. The mitigation is to first set the allowance to 0, then set it to the new value, or use increaseAllowance/decreaseAllowance (not part of the standard but widely supported).
Reentrancy via Token Callbacks
Some token standards (like ERC-777, which is backward-compatible with ERC-20) include callback hooks that execute code on the recipient. If a contract does not follow the checks-effects- interactions pattern, an attacker can re-enter the function during a token transfer. Always update state before making external calls.
Missing Return Value
Some older tokens (notably USDT) do not return a boolean from transfer() and transferFrom(). This can cause contracts that check the return value to revert. Use OpenZeppelin's SafeERC20 library to handle these edge cases safely.
Fee-on-Transfer Tokens
Some tokens deduct a fee on every transfer, meaning the recipient receives less than the amount sent. Contracts that assume balanceOf(to) increases by exactly amount after a transfer will miscalculate balances.
ERC-20 vs ERC-721 vs ERC-1155
Ethereum has multiple token standards for different use cases. Read our complete guide to ERC-721 for an in-depth look at the NFT standard. Here is how the three main standards compare:
| Feature | ERC-20 | ERC-721 | ERC-1155 |
|---|---|---|---|
| Token type | Fungible | Non-fungible | Both |
| Each token unique? | No | Yes (unique ID) | Optional |
| Decimal support | Yes (0-18) | No (whole units) | No (whole units) |
| Batch transfer | No | No | Yes |
| Use cases | Currencies, stablecoins, governance | Art, collectibles, identity | Gaming items, mixed collections |
| Gas efficiency | Good | Moderate | Best (batch ops) |
Frequently Asked Questions
What makes a token ERC-20 compliant?
A token is ERC-20 compliant if its smart contract implements all six required functions (totalSupply, balanceOf, transfer, allowance, approve, transferFrom) and two required events (Transfer, Approval) as defined in EIP-20. Optional functions like name, symbol, and decimals are also commonly implemented but not strictly required.
Who created the ERC-20 standard?
The ERC-20 standard was proposed by Fabian Vogelsteller and Vitalik Buterin in November 2015 as EIP-20. It was formally accepted in September 2017 and has since become the most widely adopted token standard on Ethereum.
Can anyone create their own ERC-20 token?
Yes. Anyone can deploy an ERC-20 token contract on Ethereum. You write a Solidity contract that implements the ERC-20 interface, compile it, and deploy it to the network. Libraries like OpenZeppelin provide audited base implementations you can extend. The cost of deployment is the gas fee for the transaction, typically a few dollars worth of ETH.
What is the difference between ERC-20 tokens and native ETH?
Native ETH is the base currency of the Ethereum network, built into the protocol itself. ERC-20 tokens are smart contracts that run on top of Ethereum. ETH transfers use a simple value transfer, while ERC-20 transfers require calling the transfer() function on the token contract. ETH does not conform to the ERC-20 interface, which is why Wrapped ETH (WETH) exists as an ERC-20 representation of ETH.
What is the approve and transferFrom pattern?
The approve/transferFrom pattern allows a third party (like a DEX or smart contract) to spend tokens on your behalf. You first call approve() to grant an allowance, then the third party calls transferFrom() to move tokens from your address. This two-step pattern is necessary because smart contracts cannot pull tokens directly from your wallet without explicit permission.
Try It Yourself
Want to decode ERC-20 token data? Use our free ERC-20 Token Decoder to inspect token names, symbols, decimals, and balances. Or try the ABI Encoder to encode and decode ERC-20 function calls.
Related Tools
- ERC-20 Token Info Decoder — Decode token name, symbol, decimals, and balances
- ABI Encoder / Decoder — Encode and decode smart contract function calls
- Calldata Decoder — Decode raw transaction calldata into readable parameters
- Checksum Address Converter — Convert Ethereum addresses to EIP-55 format