ERC-20, ERC-721, ERC-1155, ERC-4626, ERC-4337, permit, proxy standards, flash loans — the complete guide to Ethereum's token and contract standards
ERC stands for Ethereum Request for Comment — a type of Ethereum Improvement Proposal (EIP) that defines application-level standards: token interfaces, smart contract conventions, and interoperability protocols. ERCs are what make the Ethereum ecosystem composable. Without them, every token, vault, and NFT marketplace would be an island.
The EIP process is modelled on the IETF's RFC system. Anyone can propose an EIP by opening a pull request against the ethereum/EIPs GitHub repository. After community discussion, peer review, and iteration, an EIP moves through stages: Draft(being written), Review (open for feedback), Last Call(final 14-day comment window), and Final (accepted, immutable). Some stall permanently at Draft or Stagnant; the ones that reach Final become the shared vocabulary of the ecosystem.
EIPs are numbered sequentially — EIP-1 is the EIP template itself. The ERC subset covers standards that live at the application layer (token interfaces, wallet conventions, metadata formats), rather than at the protocol layer (consensus rules, opcodes) which are called Core EIPs. When developers say "ERC-20 token" they mean a smart contract that implements the interface defined in EIP-20.
| EIP Category | What It Covers | Examples |
|---|---|---|
| Core | Protocol-level changes — consensus, opcodes, gas costs | EIP-1559 (fee market), EIP-4844 (blobs), EIP-3074 (AUTH opcode) |
| Networking | devp2p and libp2p protocol changes | EIP-8 (devp2p forward compatibility) |
| Interface | ABI, JSON-RPC, wallet APIs | EIP-712 (typed structured data signing) |
| ERC | Application-layer standards — tokens, contracts, metadata | ERC-20, ERC-721, ERC-4626, ERC-4337 |
| Meta | Process and governance documents | EIP-1 (EIP process), EIP-5 (gas usage) |
| Informational | Design guidelines, not standards | EIP-2 (homestead changes) |
Every ERC is an EIP, but not every EIP is an ERC. ERC is specifically the application layer subset. In practice, the community uses "ERC" and "EIP" interchangeably for token standards — people say "ERC-20" even though the document is formally titled "EIP-20." When you see a number like ERC-4337, you can look it up at eips.ethereum.org/EIPS/eip-4337.
ERC-20, proposed by Fabian Vogelsteller in 2015 and finalised in 2017, is the most deployed smart contract interface in history. It defines a common API for fungible tokens — tokens where every unit is identical and interchangeable, like currencies or shares. USDC, USDT, LINK, UNI, and most DeFi tokens are ERC-20 tokens.
The standard defines six mandatory functions and two events. The functions aretotalSupply(), balanceOf(address),transfer(address, uint256),transferFrom(address, address, uint256),approve(address, uint256), andallowance(address, address). The events areTransfer(address indexed from, address indexed to, uint256 value)and Approval(address indexed owner, address indexed spender, uint256 value).
The approve/transferFrom pattern is how decentralised exchanges and DeFi protocols interact with tokens. Rather than tokens being sent directly, a user calls approve(spender, amount) to grant a contract permission to move up to amount tokens on their behalf. The contract then calls transferFrom(user, recipient, amount) when it needs to move funds. This two-step flow requires two transactions and has led to a well-known class of vulnerability: unlimited approvals. Many dapps request approval for the maximum possible amount (type(uint256).max) so users never need to re-approve. If the contract is later exploited, an attacker can drain the full approved balance.
| Function / Event | Signature | Purpose |
|---|---|---|
| totalSupply | totalSupply() → uint256 | Total tokens in existence |
| balanceOf | balanceOf(address account) → uint256 | Token balance of an address |
| transfer | transfer(address to, uint256 amount) → bool | Send tokens from msg.sender to recipient |
| approve | approve(address spender, uint256 amount) → bool | Authorise spender to move up to amount tokens |
| allowance | allowance(address owner, address spender) → uint256 | Remaining approved amount |
| transferFrom | transferFrom(address from, address to, uint256 amount) → bool | Move tokens using a previously granted allowance |
| Transfer | Transfer(address indexed from, address indexed to, uint256) | Emitted on every token movement |
| Approval | Approval(address indexed owner, address indexed spender, uint256) | Emitted when allowance is set |
The ERC-20 spec says transfer should return a boolean, but early implementations (notably USDT) omitted the return value. Many contracts calltoken.transfer() without checking the return value, assuming it reverts on failure. OpenZeppelin's SafeERC20 library wraps these calls to handle both compliant and non-compliant tokens by using a low-level call and checking for revert data. Always use SafeERC20 when interacting with arbitrary ERC-20 tokens.
ERC-721, proposed by William Entriken, Dieter Shirley, Jacob Evans, and Nastassia Sachs in 2017, defines the standard for non-fungible tokens — digital assets where every token has a unique identity. Each ERC-721 token has a distincttokenId and an owner. NFTs representing digital art, gaming items, on-chain identity, and real-world asset certificates all use this standard.
The core interface adds ownerOf(uint256 tokenId) to identify who owns a given token, and tokenURI(uint256 tokenId) to return a URI pointing to the token's metadata (usually a JSON document with name, image, and attributes). Transfers work similarly to ERC-20 but always move exactly one token:transferFrom(from, to, tokenId). ERC-721 adds asafe transfer mechanism — safeTransferFrom — that checks whether the recipient is a contract and, if so, callsonERC721Received on it. This prevents tokens from being permanently locked in contracts that don't know how to handle them.
ERC-721 also inherits from ERC-165 (see below), usingsupportsInterface to advertise which interfaces a contract implements. This lets marketplaces and aggregators discover NFT contracts without needing to hard-code every known contract address.
| Function | Purpose |
|---|---|
| ownerOf(tokenId) | Returns the owner of a specific token |
| balanceOf(address owner) | Count of tokens owned by address (not amount — each token is unique) |
| transferFrom(from, to, tokenId) | Transfer a token (no safety check on recipient) |
| safeTransferFrom(from, to, tokenId) | Transfer a token — reverts if recipient contract cannot receive ERC-721 |
| approve(address to, tokenId) | Approve one address to transfer one specific token |
| setApprovalForAll(operator, bool) | Approve an address to transfer ALL tokens owned by caller |
| getApproved(tokenId) | Returns the approved address for a specific token |
| isApprovedForAll(owner, operator) | Check if an operator has full approval for all of owner's tokens |
| tokenURI(tokenId) | Returns a URI for the token's metadata JSON |
tokenURI usually points to an IPFS URL or centralised server, not on-chain data. If the server goes down or the IPFS pin is lost, the metadata disappears — the token still exists (the NFT is on-chain) but the image and attributes are gone. Fully on-chain NFTs (where the SVG image and metadata are generated from the contract itself) avoid this. Nouns, Autoglyphs, and many generative art projects store everything on-chain using base64-encoded SVG returned directly fromtokenURI.
ERC-1155, proposed by Enjin's Witek Radomski in 2018, solves a problem with deploying many tokens: using a separate ERC-20 or ERC-721 contract for every token type wastes gas and makes interactions complex. ERC-1155 puts multiple token types — fungible and non-fungible — inside a single contract, identified by auint256 id. Gaming inventories (where a player might hold 1,000 arrows, 3 swords, and 1 legendary shield) are the canonical use case.
The key addition over ERC-20 and ERC-721 is batch operations.balanceOfBatch(addresses[], ids[]) returns multiple balances in one call; safeBatchTransferFrom(from, to, ids[], amounts[], data)transfers multiple token types in a single transaction. This dramatically reduces gas costs for operations that would otherwise require many separate calls. A game that awards players 5 item types at once pays one transaction instead of five.
Like ERC-721, ERC-1155 uses safe transfer hooks. The recipient contract must implementonERC1155Received (for single transfers) andonERC1155BatchReceived (for batch transfers) to accept tokens. ERC-1155 supports both fungible and non-fungible tokens: a token type withtotalSupply == 1 and no minting function is effectively non-fungible; a type with large supply is fungible.
| Standard | Token Type | Batch Support | Contract Per Token Type | Best For |
|---|---|---|---|---|
| ERC-20 | Fungible only | No | Yes — one contract per token | Currencies, governance tokens, DeFi assets |
| ERC-721 | Non-fungible only | No | Yes — one contract per collection | NFT collections, identity, certificates |
| ERC-1155 | Both (per token id) | Yes | No — many types in one contract | Gaming items, multi-asset inventories, semi-fungibles |
ERC-165 solves a simple but critical problem: how does a contract (or off-chain code) know whether a given address implements a particular interface? Without a discovery mechanism, every integration must either hard-code known contract addresses or try calling a function and catch the revert.
ERC-165 defines a single function: supportsInterface(bytes4 interfaceId) → bool. The interfaceId is computed as the XOR of all function selectors in the interface — a 4-byte fingerprint unique to that interface. For example, the ERC-721 interface ID is 0x80ac58cd. A contract that implements ERC-721 is expected to return true when queried with that ID, and false for interface IDs it does not support.
ERC-165 is a prerequisite for ERC-721 and ERC-1155 — both standards requiresupportsInterface to advertise their capabilities. Marketplaces, aggregators, and wallets use it to dynamically determine how to interact with an unknown contract. OpenZeppelin's ERC165 base contract maintains a mapping of supported interface IDs and provides _registerInterface()to add them during deployment.
ERC-2612 extends ERC-20 with a permit function that allows a token approval to be authorised off-chain via an EIP-712 signature — eliminating the separate approve transaction. This enables gasless approvals: the user signs a message in their wallet (no gas), and the dapp submits the permit together with the transfer in a single transaction.
The permit(owner, spender, value, deadline, v, r, s) function accepts a signature that encodes the owner's approval for the spender to movevalue tokens, expiring at deadline. The contract recovers the signer from the ECDSA signature components (v, r, s) and sets the allowance exactly as approve would — without requiring a transaction from the owner.
This pattern is used in almost every modern DeFi protocol. Uniswap's router accepts permit signatures so swaps can be done in one transaction. Aave's supply function accepts permit signatures so users can supply assets and get aTokens in one transaction. DAI, USDC (on some chains), and most new ERC-20 tokens implement ERC-2612. The standard also introduces a nonces(owner) function to prevent replay attacks — each permit signature is valid for exactly one use because the nonce increments on every permit call.
ERC-2612 builds on EIP-712 (typed structured data signing), which defines how to hash and sign structured off-chain data in a way that wallets can display in human-readable form. When MetaMask shows "Sign permit for 100 USDC to Uniswap Router, expires in 30 minutes," it is rendering an EIP-712 typed data structure. Without EIP-712, the user would sign an opaque hex string — which is exactly the kind of signing request phishing attacks rely on.
ERC-4626, finalised in 2022, standardises the interface for yield-bearing vaults — contracts that accept a deposit token, deploy it in a yield strategy, and issue shares representing a proportional claim on the growing pool. Before ERC-4626, every lending protocol and yield aggregator invented its own share token interface, making composability painful.
The standard extends ERC-20 (the share token is itself an ERC-20) and adds four conversion functions: convertToShares(assets),convertToAssets(shares),previewDeposit(assets), andpreviewRedeem(shares). These let integrators calculate share/asset conversions before committing a transaction. The deposit/withdrawal flow uses deposit(assets, receiver),mint(shares, receiver),withdraw(assets, receiver, owner), andredeem(shares, receiver, owner) — four entry points that cover every combination of specifying the input or output quantity.
After ERC-4626, aggregators like Yearn and yield routers like 1inch can interact with any compliant vault without custom integration code. Compound v3, Aave v3 (on some deployments), and virtually every new vault protocol targets ERC-4626. The standard also defines a known attack vector — the inflation attack— where an attacker donates assets to manipulate the share price before deposits. ERC-4626 acknowledges the attack and recommends mitigation via virtual shares (e.g._decimalsOffset() in OpenZeppelin's implementation).
ERC-4337, proposed by Vitalik Buterin and others in 2021, introduces account abstraction without requiring a protocol change. It replaces the concept of a transaction signed by an externally owned account (EOA) with aUserOperation — a data structure that can encode arbitrary validation logic. Smart contract wallets become first-class users; EOAs are no longer the only way to initiate transactions.
The system has four components. UserOperations are pseudo-transaction objects that describe the intended action, the sender (a smart contract account), and the validation logic. A Bundler is a node that collects UserOperations from a mempool, bundles them, and submits them to the EntryPoint contract in a single on-chain transaction. The EntryPoint is a singleton contract that validates and executes each UserOperation. A Paymaster is an optional contract that can sponsor gas for users — enabling gasless transactions where the dapp pays fees on behalf of users.
ERC-4337 wallets can implement arbitrary validation logic: multi-signature approval, WebAuthn passkey signatures, time-locked transactions, per-app spending limits, session keys (a temporary key authorised to perform specific actions), and social recovery (a set of guardians that can replace a lost signing key). This is the infrastructure behind "self-custody without seed phrases" — wallets like Safe, Coinbase Smart Wallet, and Biconomy are all built on ERC-4337.
| Component | Role | Who Runs It |
|---|---|---|
| Smart Account | Contract wallet with custom validation logic | User — deployed at a unique address |
| UserOperation | Pseudo-transaction describing the intended action | Constructed by the wallet/dapp |
| Bundler | Collects UserOps, simulates, and submits to EntryPoint | Node operators (e.g. Alchemy, Pimlico, Biconomy) |
| EntryPoint | Singleton that validates and executes UserOps | Global singleton — same address on all chains |
| Paymaster | Sponsors gas, or swaps ERC-20 gas fees to ETH | Dapp or protocol — optional |
| Factory | Deploys new smart accounts at deterministic addresses | Protocol — handles first-use deployment |
ERC-4337 defines how accounts interact with the EntryPoint, but does not standardise the account's internal module system. ERC-7579 (Minimal Modular Smart Accounts) adds a plugin interface so that validators, executors, hooks, and fallbacks can be installed and removed from smart accounts in a standardised way — making modules portable across wallet implementations. Safe, Biconomy, and ZeroDev have all adopted ERC-7579.
Proxy patterns — where a thin contract delegates all calls to a separate implementation contract — require careful storage management to avoid collisions. Several ERCs standardise where proxy-specific variables are stored and how upgrade authority is managed, making proxy contracts auditable and tooling-compatible.
ERC-1967 (Transparent Proxy Standard) defines specific storage slots for the implementation address, admin address, and beacon address. These slots are derived by hashing well-known strings and subtracting 1:bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1). Because the hash output is pseudo-random, it is virtually impossible for normal sequential state variables to collide with these slots. Tools like Etherscan and OpenZeppelin's Upgrades plugin use ERC-1967 slot addresses to automatically detect proxy contracts and display the implementation.
ERC-1822 (Universal Upgradeable Proxy Standard — UUPS) puts the upgrade function inside the implementation contract rather than the proxy. The proxy itself has no upgrade logic — it just stores the implementation address and delegates everything. This makes proxy deployments cheaper (less bytecode) but requires the implementation to include a upgradeTo function with proper access control. If the implementation is deployed without this function, the proxy is permanently bricked.
ERC-2535 (Diamond Standard) solves the contract size limit problem (24KB maximum on Ethereum) by splitting logic across multiple implementation contracts called facets. The Diamond proxy maintains a mapping from function selectors to facets, delegating each call to the correct implementation. This allows arbitrarily complex protocols to be deployed as a single logical address with unlimited total bytecode across all facets.
ERC-7201 (Namespaced Storage for Upgradeability) provides a formal convention for where implementation contracts store their state variables when used with proxies. Rather than starting from slot 0 (which risks colliding with the proxy's own variables), implementations use a storage struct placed at a pseudo-random slot derived from a namespace hash. OpenZeppelin's Contracts v5 uses ERC-7201 throughout.
ERC-3156, finalised in 2021, standardises the interface for flash loans — uncollateralised loans that must be borrowed and repaid within a single transaction. Before this standard, every protocol (Aave, dYdX, Uniswap) had its own flash loan interface, requiring custom integration code for each.
The standard defines two interfaces. The lender exposesmaxFlashLoan(address token) (maximum borrowable amount),flashFee(address token, uint256 amount) (the fee charged), and flashLoan(receiver, token, amount, data) (initiates the loan). The receiver (borrower) must implementonFlashLoan(initiator, token, amount, fee, data), which is called by the lender during the loan. The receiver must repayamount + fee tokens to the lender before onFlashLoanreturns, or the transaction reverts.
Flash loans are used for arbitrage (borrow to exploit a price discrepancy and repay from profits), liquidations (borrow to buy a liquidatable position and repay from the collateral), collateral swaps (replace one collateral type with another in a single transaction), and exploit amplification (attackers use flash loans to temporarily acquire large amounts of capital to manipulate prices or governance).
The real power of ERCs is not any individual standard — it is what happens when they compose. A DeFi protocol does not need to know anything about a specific token beyond its ERC-20 interface. A vault does not need to know anything about the asset it holds beyond the same interface. This composability is what makes DeFi's "money legos" metaphor accurate.
Consider a full stack using standards discussed above: a user holds USDC (ERC-20). They want to deposit it into a vault (ERC-4626) that earns yield on Aave. Instead of two transactions (approve, then deposit), they sign an ERC-2612 permit off-chain and the vault calls permit then transferFromin one transaction. The vault issues share tokens (also ERC-20). Those shares can be used as collateral in a lending protocol that accepts any ERC-4626 vault share. The lending protocol checks the vault's supportsInterface(ERC-165) to confirm it is a compliant vault. A flash loan arbitrageur (ERC-3156) borrows USDC, supplies it to rebalance the vault, and repays — all in one transaction. Throughout, no protocol needs custom code for any other protocol.
This composability comes at a cost: standards create shared attack surfaces. A bug in ERC-20's approve semantics (the allowance race condition) affects every token. ERC-721's safeTransferFrom reentrancy risk (the recipient's onERC721Received can call back into the caller) requires careful use of Checks-Effects-Interactions. ERC-4626's inflation attack requires virtual shares. Every standard that gains adoption brings its footguns into every protocol that uses it.
| Standard | What It Adds | Key Use Cases |
|---|---|---|
| ERC-20 | Fungible token interface | Currencies, governance tokens, DeFi assets |
| ERC-721 | Non-fungible token interface | NFT collections, on-chain identity, certificates |
| ERC-1155 | Multi-token (fungible + non-fungible) interface | Gaming inventories, semi-fungibles, batch transfers |
| ERC-165 | Interface introspection | Dynamic contract capability discovery |
| ERC-2612 | Gasless permit approvals via EIP-712 signature | Single-transaction DeFi flows, eliminating approve step |
| ERC-4626 | Tokenised vault interface | Yield aggregators, lending receipt tokens, LSTs |
| ERC-4337 | Account abstraction without protocol changes | Smart wallets, gasless UX, session keys, social recovery |
| ERC-1967 | Standardised proxy storage slots | Upgradeable contracts, detected by block explorers |
| ERC-1822 | UUPS proxy — upgrade logic in implementation | Lightweight upgradeable proxies |
| ERC-2535 | Diamond multi-facet proxy | Large protocols exceeding the 24KB contract limit |
| ERC-7201 | Namespaced storage for upgradeable contracts | Collision-safe state layout in implementation contracts |
| ERC-3156 | Flash loan interface | Single-protocol flash loan integrations, arbitrage bots |
Understanding how ERCs are made helps you evaluate their credibility and maturity. A Draft ERC is a proposal — its interface may change. A Final ERC is immutable — the spec will not change, only the implementations. Knowing where a standard sits in the process is important before building on it.
An author identifies a gap or proposes an improvement. Discussion happens on Ethereum Magicians (ethereum-magicians.org) and the EIPs GitHub repository. No formal EIP exists yet.
The author opens a pull request against ethereum/EIPs with a document following the EIP template. The EIP is assigned a number. The spec can change substantially. Building on a Draft standard means you accept interface instability.
The EIP is open for community review and feedback. The author addresses comments and revises. EIP editors (Ethereum Foundation contributors) assess conformance with the EIP format and process. This stage can last months or years.
A 14-day window signals the EIP is ready for final review. If no blocking issues emerge, it moves to Final. If significant issues are raised, it returns to Review.
The EIP is accepted and immutable. Its specification will not change — only errata and clarifications. This is the status to require before building infrastructure that other protocols will depend on.
An EIP inactive for 6 months becomes Stagnant. Authors can revive it. Withdrawn EIPs are permanently closed. Many promising ideas stall here.
ERCs are fundamentally a coordination technology, not a technical one. The ERC-20 interface is trivially simple — eight functions and two events. Its power comes entirely from the fact that thousands of contracts implement the same interface, and thousands of protocols assume it. A technically superior token standard that nobody implements is worthless. The ERC process is designed to build the consensus needed to make a standard worth implementing — which is why the most impactful ERCs (ERC-20, ERC-721, ERC-4337) took years of community iteration before finalisation. When you see a new ERC gaining adoption, the interesting question is not whether the interface is elegant, but whether the ecosystem is converging on it.