ERC-20: Fungible Token Standard
Overview
ERC-20 defines a standard interface for fungible tokens on Ethereum and EVM-compatible chains. Every token is identical and interchangeable; one USDC is the same as any other USDC. The standard specifies how tokens are transferred, how balances are queried, and how third parties can be authorized to spend tokens on behalf of a holder.
ERC-20 is the foundation of onchain finance. Lending, borrowing, swaps, liquidity pools, governance, stablecoins, all of it is built around ERC-20 compatibility. Any new token standard that wants to work with existing DeFi infrastructure typically extends or maintains compatibility with ERC-20.
We wrote a broader overview of the ERC standards we think matter most in our ERC Token Standards blog post.
Interface Specification
The ERC-20 interface defines six mandatory functions, two mandatory events, and three optional metadata functions.
Events
Transfer
event Transfer(address indexed from, address indexed to, uint256 value);
Emitted when tokens move between addresses. This includes minting (where from is the zero address) and burning (where to is the zero address). Every token movement onchain emits this event, it's what indexers like Hgraph use to track transfer history and compute balances.
Approval
event Approval(address indexed owner, address indexed spender, uint256 value);
Emitted when an owner authorizes a spender to transfer tokens on their behalf via the approve function.
Mandatory Functions
totalSupply
function totalSupply() external view returns (uint256);
Returns the total number of tokens in existence. Note: this may not reflect circulating supply. Tokens sent to burn addresses (like 0x000...dead) are still counted unless the contract explicitly decrements the supply.
balanceOf
function balanceOf(address account) external view returns (uint256);
Returns the token balance of the given address. Balances are stored in a mapping(address => uint256). This means you can look up any individual balance, but you cannot enumerate all holders from the contract itself. Reconstructing a full holder list requires indexing every Transfer event from contract deployment.
transfer
function transfer(address to, uint256 amount) external returns (bool);
Sends amount of tokens from the caller's address to to. Must emit a Transfer event. Reverts if the caller doesn't have enough balance.
allowance
function allowance(address owner, address spender) external view returns (uint256);
Returns how many tokens spender can still pull from owner's balance, the unspent portion of a previous approve call.
approve
function approve(address spender, uint256 amount) external returns (bool);
Authorizes spender to transfer up to amount of the caller's tokens. Must emit an Approval event.
transferFrom
function transferFrom(address from, address to, uint256 amount) external returns (bool);
Transfers amount tokens out of from's balance and into to, deducting from the caller's allowance. The caller must have been approved for at least amount by from. Must emit a Transfer event.
The approve + transferFrom pattern is how DeFi works. When you deposit tokens into a lending protocol or swap on a DEX, you first approve the contract to spend your tokens, then the contract pulls them via transferFrom. This two-step delegation is what makes composability possible.
Optional Metadata Functions
These are not part of the core spec but are implemented by virtually every ERC-20 token in practice:
function name() external view returns (string memory); // e.g. "USD Coin"
function symbol() external view returns (string memory); // e.g. "USDC"
function decimals() external view returns (uint8); // e.g. 6 for USDC, 18 for UNI
decimals are critical for display and arithmetic. Token amounts are stored as integers. A balance of 1000000 for a token with 6 decimals represents 1.0 tokens. Common values: 18 (most tokens), 6 (USDC, USDT), 8 (WBTC).
Function Selectors
For low-level integrations and ABI encoding:
| Function | Selector |
|---|---|
name() | 0x06fdde03 |
symbol() | 0x95d89b41 |
decimals() | 0x313ce567 |
totalSupply() | 0x18160ddd |
balanceOf(address) | 0x70a08231 |
allowance(address,address) | 0xdd62ed3e |
transfer(address,uint256) | 0xa9059cbb |
approve(address,uint256) | 0x095ea7b3 |
transferFrom(address,address,uint256) | 0x23b872dd |
Notable ERC-20 Tokens
| Token | Symbol | Decimals | Ethereum Mainnet Address |
|---|---|---|---|
| USD Coin | USDC | 6 | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
| Tether USD | USDT | 6 | 0xdAC17F958D2ee523a2206206994597C13D831ec7 |
| Chainlink | LINK | 18 | 0x514910771AF9Ca656af840dff83E8264EcF986CA |
| Uniswap | UNI | 18 | 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 |
| Wrapped Ether | WETH | 18 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
Risks and Gotchas
Front-Running on approve
The approve function has a well-known race condition. If Alice has approved Bob for 100 tokens and wants to change it to 50, Bob can front-run the transaction: he calls transferFrom for the original 100 before Alice's new approve(50) is mined, then calls transferFrom again for 50, extracting 150 total.
Mitigations:
- Set allowance to 0 first, then set to the desired value (two transactions)
- Use EIP-2612
permit()for signature-based approvals
Missing Return Value
The spec says transfer, approve, and transferFrom must return bool. Several major tokens, most notably USDT, return nothing. This causes standard Solidity calls to revert at the ABI decoder level even when the transfer succeeds.
Mitigation: Always use OpenZeppelin's SafeERC20 library, which handles both compliant and non-compliant tokens.
Fee-on-Transfer Tokens
Some ERC-20 tokens deduct a fee on transfer. If your contract does transferFrom(user, address(this), 100), the contract may receive fewer than 100 tokens.
Mitigation: Always check the actual balance change after the transfer, not the amount parameter you passed in.
Rebasing Tokens
Tokens like stETH automatically adjust balances across holders. A stored balanceOf value can become stale between blocks.
Mitigation: Wrap rebasing tokens before integrating them (e.g., use wstETH instead of stETH) to get a non-rebasing ERC-20 interface with a stable balance.
Decimals Are Not Always 18
USDC and USDT use 6 decimals. WBTC uses 8. Assuming 18 decimals is a common source of bugs.
Mitigation: Always read decimals() from the contract and handle arithmetic accordingly. Never hardcode 18.
Infinite Approvals
Setting an allowance to type(uint256).max is a common UX pattern to avoid repeated approvals. Some implementations don't decrement this value on transferFrom (an intentional gas optimization). Infinite approvals persist through proxy upgrades. If the implementation contract changes, all approved funds are at risk.
Mitigation: Use finite approvals for the exact amount needed per transaction. If infinite approvals are unavoidable, regularly audit and revoke stale allowances using tools like Revoke.cash.
ERC-20 Data on Hgraph
Hgraph indexes ERC-20 token data on Hedera (production) and Ethereum (beta) including metadata, holder balances, and full transfer history. The schema below describes the Hedera side; the Ethereum schema differs and is documented separately.
| Data | Fields |
|---|---|
| Token metadata | name, symbol, decimals, total_supply, token_evm_address, contract_type, metadata_reliability_score, created_timestamp |
| Holder balances | account_id, token_evm_address, balance, balance_timestamp |
| Transfer history | sender_evm_address, receiver_evm_address, amount, consensus_timestamp, transaction_hash, transfer_type |
For full GraphQL schema details and example queries (token search, holder lookups, transfer history, and analytics), see the ERC Indexer query examples.
Want to integrate ERC-20 data into your application? Get a free API key.
External References
- EIP-20: Token Standard. Official specification.
- OpenZeppelin ERC20 Implementation. Reference implementation.
- OpenZeppelin SafeERC20. Safe wrapper for non-compliant tokens.
- OpenZeppelin ERC20 Documentation. Usage guide.
- EIP-2612: Permit. Gasless approvals via signatures.
- Ethereum.org ERC-20 Guide. Conceptual overview.
- Hgraph ERC Indexer Docs. Hgraph indexer documentation.
- ERC Token Standards Blog Post. Our overview of the standards that matter most right now.