EVMTools

What is ERC-20?

Learn what ERC-20 tokens are, how the standard works, the 6 required functions, transfer patterns, and common vulnerabilities.

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 18

The 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:

  1. Step 1 — Approve: You call approve(uniswapRouter, amount) on the token contract, granting the Uniswap router permission to spend your tokens.
  2. 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:

TokenSymbolDecimalsContract Address
Tether USDUSDT60xdAC17F958D2ee523a2206206994597C13D831ec7
USD CoinUSDC60xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
ChainlinkLINK180x514910771AF9Ca656af840dff83E8264EcF986CA
UniswapUNI180x1f9840a85d5aF5bf1D1762F925BDADdC4201F984
Wrapped ETHWETH180xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Dai StablecoinDAI180x6B175474E89094C44Da98b954EedeAC495271d0F
AaveAAVE180x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9

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:

FeatureERC-20ERC-721ERC-1155
Token typeFungibleNon-fungibleBoth
Each token unique?NoYes (unique ID)Optional
Decimal supportYes (0-18)No (whole units)No (whole units)
Batch transferNoNoYes
Use casesCurrencies, stablecoins, governanceArt, collectibles, identityGaming items, mixed collections
Gas efficiencyGoodModerateBest (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