ERC-20 (Ethereum Request for Comments 20) is the technical standard for fungible tokens on Ethereum. Proposed by Fabian Vogelsteller and Vitalik Buterin in November 2015, it defines a common interface that enables tokens to be transferred, approved for spending by third parties, and queried for balance and supply information. ERC-20 is the most widely adopted token standard in blockchain, with thousands of implementations managing hundreds of billions of dollars in value.

The ERC-20 Interface

The standard defines six mandatory functions and two events. Every ERC-20 compliant token must implement this interface:

Core Functions

  • totalSupply() returns the total number of tokens in existence. This can be fixed at deployment or change dynamically through minting and burning.
  • balanceOf(address account) returns the token balance of a specific address. The contract maintains an internal mapping from addresses to balances.
  • transfer(address to, uint256 amount) moves tokens from the caller's account to the recipient. Returns a boolean indicating success. Emits a Transfer event.
  • approve(address spender, uint256 amount) authorizes a third party (the spender) to transfer up to the specified amount from the caller's account. This is the first step in the two-step "approve and transfer" pattern.
  • transferFrom(address from, address to, uint256 amount) allows an approved spender to transfer tokens from one account to another. The spender's allowance is decremented by the transfer amount. Emits a Transfer event.
  • allowance(address owner, address spender) returns the remaining amount that the spender is authorized to transfer on behalf of the owner.

Events

  • Transfer(address indexed from, address indexed to, uint256 value) is emitted on every token transfer, including mints (from the zero address) and burns (to the zero address).
  • Approval(address indexed owner, address indexed spender, uint256 value) is emitted when an allowance is set or changed.

Optional Metadata Functions

Most ERC-20 tokens also implement three optional functions that provide human-readable information:

  • name() returns the token's full name (e.g., "USD Coin").
  • symbol() returns the ticker (e.g., "USDC").
  • decimals() returns the number of decimal places the token uses.

How the Approval Mechanism Works

The two-step approve-then-transferFrom pattern is central to DeFi composability. When a user wants to interact with a protocol (for example, depositing USDC into Aave), the process works as follows:

  1. The user calls approve(aavePool, 1000e6) on the USDC contract, authorizing the Aave pool to spend up to 1,000 USDC from their account.
  2. The user calls the deposit() function on the Aave pool contract.
  3. Internally, the Aave pool calls USDC.transferFrom(user, aavePool, 1000e6) to pull the tokens into the pool.

This two-transaction pattern is necessary because smart contracts cannot initiate transfers from a user's account without explicit authorization. The approval step is a separate on-chain transaction that costs gas.

Many users set "unlimited" approvals (type(uint256).max) to avoid repeated approval transactions. While convenient, this means the approved contract has perpetual authority to transfer all of the user's tokens. If the contract is compromised, the attacker can drain every approved wallet. Review and revoke unnecessary approvals regularly using tools like Revoke.cash.

The Decimals Convention

The EVM does not support floating-point numbers. To represent fractional token amounts, ERC-20 uses integer arithmetic with a decimals value that indicates where to place the decimal point for display purposes.

Most tokens use 18 decimals (matching ETH's wei denomination). A balance of 1000000000000000000 (10^18) represents 1.0 tokens. However, stablecoins like USDC and USDT use 6 decimals: a balance of 1000000 (10^6) represents 1.0 USDC.

Decimal Mismatch Risks

The lack of a universal decimal standard creates integration risks. Smart contracts that interact with multiple tokens must account for different decimal values. A contract that assumes 18 decimals when handling 6-decimal USDC will miscalculate amounts by a factor of 10^12. This type of error has caused multiple DeFi exploits and is a common audit finding.

Common Extensions

ERC-2612: Permit (Gasless Approvals)

ERC-2612 (also called ERC-20Permit) adds a permit() function that allows approvals via off-chain signatures instead of on-chain transactions. Instead of calling approve() in a separate transaction, the user signs a typed message (EIP-712) off-chain, and the protocol submits the signature alongside the action in a single transaction.

This reduces the interaction from two transactions to one, saving gas and improving user experience. Major tokens including DAI, USDC (V2), and most modern ERC-20 implementations support Permit.

ERC-20Votes: Governance Snapshots

ERC-20Votes extends ERC-20 with vote delegation and historical balance tracking via checkpoints. Token holders can delegate their voting power to another address (or themselves), and the contract records balance snapshots at each block. This prevents double-voting attacks where a user votes, transfers tokens to another wallet, and votes again.

Governance tokens like UNI, COMP, and ARB implement this extension.

Other Notable Extensions

  • Burnable: Adds a burn() function allowing token holders to permanently destroy their tokens, reducing total supply.
  • Mintable: Adds a mint() function restricted to authorized addresses, allowing controlled supply expansion.
  • Pausable: Allows an admin to freeze all transfers in emergency situations. USDC and USDT both implement this, enabling Circle and Tether to blacklist addresses and freeze funds.
  • Capped: Enforces a maximum supply that cannot be exceeded even through minting.

Notable ERC-20 Tokens

Token Type Decimals Supply Model
USDC Stablecoin 6 Mint/burn by Circle
USDT Stablecoin 6 Mint/burn by Tether
DAI Stablecoin 18 Mint/burn by vaults
WETH Wrapped ETH 18 Wrap/unwrap
UNI Governance 18 Fixed (1B), vested
LINK Utility 18 Fixed (1B)
AAVE Governance 18 Fixed (16M)

Security Considerations

The Approve Race Condition

The original ERC-20 standard contains a known race condition in the approve function. If a user has an existing approval of 100 tokens for a spender and wants to change it to 50, the following can occur:

  1. User submits approve(spender, 50).
  2. Before this transaction is mined, the spender front-runs by calling transferFrom to use the original 100-token allowance.
  3. The user's approve(spender, 50) transaction is mined, setting a new 50-token allowance.
  4. The spender calls transferFrom again for 50 tokens, having spent 150 total instead of the intended 50.

The standard mitigation is to set the allowance to 0 before setting it to the new value (two transactions), or to use increaseAllowance and decreaseAllowance functions provided by libraries like OpenZeppelin. ERC-2612 Permit also avoids this issue by using nonces.

Fee-on-Transfer and Rebasing Tokens

Some ERC-20 tokens deviate from standard behavior. Fee-on-transfer tokens deduct a percentage on each transfer, meaning the received amount is less than the sent amount. Rebasing tokens (like stETH) adjust all holder balances automatically to reflect staking rewards.

These non-standard behaviors can break DeFi protocols that assume the amount sent equals the amount received. Many protocols explicitly exclude such tokens or implement special handling.

Key Takeaway

ERC-20 is the foundational standard that makes the entire DeFi ecosystem interoperable. Its approve-transferFrom pattern enables composability between protocols, while extensions like Permit and Votes add governance and UX capabilities. However, decimal mismatches, unlimited approvals, and non-standard token behaviors remain persistent sources of risk that developers and users must actively manage.

Related articles