ERC-20 is a standard interface for fungible tokens on Ethereum. It is not a token. It is a set of functions and events that a smart contract must implement so that wallets, decentralised exchanges and other applications can work with the token without custom code. Fabian Vogelsteller and Vitalik Buterin wrote it as EIP-20, created on 19 November 2015. Its status is Final.
What "ERC-20" means
EIP stands for Ethereum Improvement Proposal. ERC is the category of EIPs for "application-level standards and conventions, including contract standards such as token standards", and ERC-20 is the example EIP-1 itself gives. The number 20 is the proposal's number.
The EIP sets out its motivation in one line: a standard interface "allows any tokens on Ethereum to be re-used by other applications", from wallets to decentralised exchanges.
ERC-20 tokens and ether are different things
Ether (ETH) is Ethereum's native currency. It is not an ERC-20 token. An ERC-20 token is a balance ledger kept inside a smart contract deployed on Ethereum. In practice this means:
- Each token has its own contract, and that contract records who holds how much.
- Moving an ERC-20 token is a call to its contract, so it costs gas. Gas fees "have to be paid in Ethereum's native currency, ether (ETH)". A wallet holding only an ERC-20 token and no ETH cannot send that token on its own.
- ERC-20 tokens are fungible: every unit of a given token is identical in type and value to every other unit.
The interface
Required functions
| Function | What it does |
|---|---|
totalSupply() | Returns the total token supply |
balanceOf(address _owner) | Returns the balance of an address |
transfer(address _to, uint256 _value) | Sends tokens from the caller to _to |
transferFrom(address _from, address _to, uint256 _value) | Moves tokens from _from to _to on behalf of an approved spender |
approve(address _spender, uint256 _value) | Lets _spender withdraw up to _value from the caller's account, multiple times |
allowance(address _owner, address _spender) | Returns how much _spender may still withdraw from _owner |
Required events
Transfer(address indexed _from, address indexed _to, uint256 _value)Approval(address indexed _owner, address indexed _spender, uint256 _value)
Optional fields
name(): for example, a full token namesymbol(): a short tickerdecimals(): the number of decimals the token uses. The EIP's example:8means you divide the raw amount by 100,000,000 to get the amount a user sees.
The standard says other contracts "MUST NOT expect these values to be present." The figure "maximum 18 decimals" that often goes with this list does not come from the standard. decimals() returns a uint8, and 18 is a common convention, not a limit.
How do your ERC-20 tokens move?
Answer three questions to see which ERC-20 path you are on and which rule from the standard applies to it.
Does the wallet that holds the token also hold some ETH?
- No, it holds only the ERC-20 token → You need ETH for gas first
- Yes, it holds some ETH → next question
Where are the tokens going?
- To another person's wallet address → Direct transfer
- Into a smart contract, for example a deposit into a protocol → next question
Have you already set an allowance for that contract with approve?
- No allowance yet → Approve first, then the contract pulls
- Yes, and it already covers the amount → The contract can call transferFrom
- Yes, but I want to change the amount → Set the allowance to 0 before the new value
Moving an ERC-20 token is a call to its contract, and gas is paid in ether (ETH). A wallet with only the token and no ETH cannot send the token on its own.
A direct transfer calls transfer, and the tokens leave your address. The standard says it SHOULD throw if your balance is too low. Check that the address is not a contract that cannot handle tokens: ERC-20 cannot notify a receiving contract, so tokens sent to one can be permanently lost.
Do not send the tokens with a plain transfer. Call approve(spender, amount) to set an allowance, and the contract then calls transferFrom to move up to that amount. Your first interaction takes two transactions. If the token supports ERC-2612 permit, a signed message can set the allowance instead of a separate approve transaction.
The spender contract can move up to your remaining allowance with transferFrom. The allowance function shows how much it may still withdraw.
Calling approve again overwrites the allowance; it does not add to it. Because of an attack vector on approve and transferFrom, the EIP advises user interfaces to set the allowance to 0 before setting a new value for the same spender.
This follows the rules of the EIP-20 text only: it does not check any particular token contract, whether a contract handles tokens safely, gas costs, audits or legal status.
How the two transfer paths work
Direct transfer. You call transfer and the tokens leave your address. The EIP says this SHOULD throw if your balance is too low.
Approve, then transferFrom. This is how smart contracts take tokens from you, for example when you deposit into a protocol:
- You call
approve(spender, amount), which sets an allowance. - The spender contract calls
transferFrom(you, recipient, amount)and moves up to that allowance.
transferFrom SHOULD throw unless the _from account "has deliberately authorized the sender". Calling approve again overwrites the existing allowance. It does not add to it.
Rules developers get wrong
- Handle
false. Callers "MUST handlefalsefromreturns (bool success)" and "MUST NOT assume thatfalseis never returned". - Zero-value transfers are real transfers. They must be treated as normal and must fire the
Transferevent. - Minting. A contract that creates new tokens SHOULD emit
Transferwith_fromset to0x0. - Changing an allowance. The EIP warns about an attack vector on approve/transferFrom. It advises user interfaces to set the allowance to
0before setting a new value for the same spender. The contract itself should not enforce this.
Known problems and later standards
Tokens sent to contracts can be lost
ERC-20 has no way to notify a receiving contract. Tokens sent with transfer to a contract that cannot handle them "can be permanently lost". ethereum.org puts the losses at no less than $83,656,418 as of 20 June 2024. The mitigations it lists:
- Make
transferrevert when someone tries to send tokens to the token contract itself. - Deposit into contracts with
approveplustransferFrom, not a plaintransfer. - Build contracts so that tokens sent by mistake can be withdrawn.
- Consider alternative standards such as ERC-223 or ERC-1363.
Two transactions and the ETH requirement
Because approve depends on msg.sender, a user's first interaction with a contract takes two transactions, and the user must hold ETH for gas. ERC-2612 (Final, created 13 April 2020, by Martin Lundfall) adds a permit function. It sets an allowance from a signed message, so no separate approve transaction is needed:
``solidity function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external ``
Related fungible-token standards
ethereum.org lists ERC-777, ERC-4626 (tokenized vaults) and ERC-7540 (asynchronous tokenized vaults) alongside ERC-20.
Implementations
The EIP points to two reference implementations, from OpenZeppelin and ConsenSys. It notes that they make different trade-offs, for example gas savings against improved security. Most new tokens build on an audited library rather than writing the interface from scratch.
Limitations: this guide covers the standard's text and ethereum.org's documentation. It does not cover token audits, individual token contracts or legal status.