Token Standards: ERC-20, ERC-721 & ERC-1155
The ecosystem overview promised that DeFi, NFTs, and DAOs are all built out of the same primitive: a smart contract holding state on the shared world computer. This page cashes that promise in with the most important building block of all — the token — and shows that a “token” is not a new kind of thing on Ethereum. It is a very old kind of thing wearing an agreed-upon name.
Here is the whole surprise up front: Ethereum has exactly one native asset — ETH. There is no ERC-20 machinery in the protocol, no NFT opcode, no “mint” instruction in the EVM. Every other token you have ever heard of — USDC, a Bored Ape, a game sword — is just a smart contract that keeps a map of who owns what in its own storage, exactly like the counter contract from Day 24 kept a number in slot 0. The genius of the token economy is not a technology. It is a handshake: everyone agreed on the same function names.
A token is a balance map in a contract’s storage
Section titled “A token is a balance map in a contract’s storage”Recall from the EVM part that every contract has its own private storage — a key→value map that survives between calls and is fingerprinted into the world state root. A contract can put anything in there. A fungible-token contract puts one thing: a map from address to balance.
USDC contract's storage ┌───────────────────────────────────────┐ │ balances[0xAlice] = 1_500_000_000 │ ← 1,500 USDC (6 decimals) │ balances[0xBob] = 250_000_000 │ ← 250 USDC │ balances[0xCarol] = 0 │ │ totalSupply = ... │ └───────────────────────────────────────┘That is the entire ontology of an ERC-20 token. “Alice owns 1,500 USDC” is not a fact the Ethereum protocol knows or protects. It is a number in a specific contract’s storage slot. When Alice “sends” USDC to Bob, no ETH moves, no native transfer happens — Alice calls a function on the USDC contract, and that function subtracts from her entry and adds to Bob’s. The chain only guarantees the function ran deterministically (the EVM part) and that everyone agrees on the resulting state (the consensus part). It has no idea what “USDC” means. The meaning lives entirely in the contract’s code and the social agreement that this particular contract is “the real USDC.”
This is the first-principles punchline, and it is worth sitting with: a token is an accounting ledger that a contract keeps about itself. Nothing more. The rest of this page is about the interface everyone agreed to use for that ledger.
The “standard” is just an agreed function interface
Section titled “The “standard” is just an agreed function interface”An ERC (Ethereum Request for Comments) is not code. It is a specification of function signatures — a promise about which functions a contract exposes and how they behave. ERC-20, finalized as EIP-20 in 2015, says: if you want to call yourself a fungible token, expose these functions with these names and signatures.
// The ERC-20 interface — the whole "standard" is these six functions + two events.interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address owner) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}There is no enforcement here. Nothing in the EVM checks that a contract “is” an ERC-20. A wallet or exchange simply assumes that a contract at a given address answers balanceOf(you) and transfer(to, amount), and calls them. If the contract does, it behaves like a token to that wallet. The standard is a convention, held up entirely by everyone choosing to honor it — the same way a UTXO in Bitcoin only “means money” because everyone runs software that agrees it does.
Why does this matter so much? Because a shared interface is what lets code that has never heard of your token still handle it. MetaMask does not have special code for USDC, DAI, and ten thousand other tokens. It has code for the ERC-20 interface. Deploy a brand-new token this afternoon that honors those six functions, and every ERC-20-aware wallet and exchange can display and move it without a single line of change on their side. That is composability, and it is the real product of standardization.
Under the hood — transfer is a two-line state change
Section titled “Under the hood — transfer is a two-line state change”Strip away the interface and a transfer is almost anticlimactic. In pseudocode, the contract does:
function transfer(address to, uint256 amount) external returns (bool) { require(balances[msg.sender] >= amount, "insufficient balance"); balances[msg.sender] -= amount; // SSTORE: shrink caller's slot balances[to] += amount; // SSTORE: grow recipient's slot emit Transfer(msg.sender, to, amount); return true;}Two SSTOREs and an event. msg.sender is the address that called this function — the EVM guarantees it cannot be forged, which is what makes “Alice can only spend Alice’s balance” true without any password or signature check inside the contract: Alice already proved she is Alice by signing the transaction that reached the contract. The Transfer event writes nothing to state; it appends a log entry that off-chain indexers (wallets, block explorers, The Graph) watch to reconstruct “who paid whom” without replaying every transaction. Events are the token economy’s receipts.
ERC-20: fungible tokens, and the allowance footgun
Section titled “ERC-20: fungible tokens, and the allowance footgun”Fungible means interchangeable: your 10 USDC and my 10 USDC are the same, in the way one $10 bill is as good as another. The balance map above captures that perfectly — a token is just a quantity attached to an address.
The subtle part of ERC-20 is not transfer. It is the approve + transferFrom pair, and the reason it exists reveals something deep about how contracts interact.
The problem: transfer only works when you are the one calling. But DeFi is built on contracts moving your tokens on your behalf — a Uniswap pool needs to pull your USDC into itself to complete a swap; a lending market needs to pull your collateral. A contract cannot call transfer as you, because msg.sender would be the contract, not you, and the contract has no USDC of its own to send.
The solution — the allowance pattern:
Step 1: Alice APPROVES the Uniswap Router to spend up to 500 of her USDC. USDC.approve(router, 500) → allowance[Alice][router] = 500 (a second map in storage)
Step 2: Later, the Router PULLS 300 during a swap. USDC.transferFrom(Alice, pool, 300) ← called by the router → require allowance[Alice][router] >= 300 → balances[Alice] -= 300; balances[pool] += 300 → allowance[Alice][router] -= 300 (now 200 left)The contract keeps a second nested map, allowance[owner][spender], recording how much each spender is permitted to move from each owner. approve sets it; transferFrom checks it, moves the tokens, and decrements it. This is the mechanism that makes DeFi possible: you grant a contract a bounded permission to move a specific amount, and it can then act for you in future transactions.
ERC-721: non-fungible tokens, and what an NFT does not guarantee
Section titled “ERC-721: non-fungible tokens, and what an NFT does not guarantee”Change one thing about the balance map and you get an NFT. Instead of address → quantity, an ERC-721 contract (finalized 2018) keeps tokenId → owner:
CryptoPunks-style contract storage ┌──────────────────────────────────┐ │ ownerOf[1] = 0xAlice │ ← each tokenId maps to exactly ONE owner │ ownerOf[2] = 0xBob │ │ ownerOf[3] = 0xAlice │ │ tokenURI[1] = "ipfs://Qm.../1" │ ← a POINTER to metadata, often off-chain └──────────────────────────────────┘Non-fungible means each unit is unique and indivisible: tokenId 1 and tokenId 2 are different assets, and you cannot own “half” of one. The map inverts the ERC-20 relationship — where a fungible token asks “how much does this address own,” an NFT asks “who owns this specific id.” Ownership transfer is the same trick: transferFrom(from, to, tokenId) reassigns ownerOf[tokenId], guarded by the same approval logic so marketplaces can move a token you listed.
Now the honest part, because it is the most misunderstood thing in the whole ecosystem. On-chain, an NFT guarantees exactly one thing: the mapping from tokenId to owner. That is a real, tamper-proof, globally-verifiable fact. But the picture — the ape, the artwork, the meme — is almost never on-chain. tokenURI(1) returns a link, usually to IPFS or (worse) an ordinary web server, and that is where the JSON metadata and the image live.
What the chain actually secures: What lives off-chain (usually): ┌─────────────────────────┐ ┌──────────────────────────────┐ │ ownerOf[1] = 0xAlice │ ──URI──► │ ipfs://Qm.../1.json │ │ (tamper-proof, on L1) │ │ { "name": "...", │ └─────────────────────────┘ │ "image": "ipfs://.../1.png"}│ │ (a file on a server or IPFS) │ └──────────────────────────────┘So what does owning an NFT actually get you? A cryptographically provable claim that address X controls token id N in contract C. What it does not automatically get you: the copyright to the image, a guarantee the image still exists, or a guarantee the image cannot change. If the tokenURI points at a normal web server and that server goes down or swaps the file, your on-chain ownership is intact but points at nothing — “right-click-save” jokes aside, the real risk was always link rot. This is why serious projects pin metadata to content-addressed storage (IPFS, where the hash is the address, so the content can’t silently change) or, for maximal permanence, store the art fully on-chain as SVG in contract storage — expensive, but then the guarantee is total. The NFT secures the ledger entry; whether it secures the thing depends entirely on where the thing lives.
ERC-1155: one contract, many token types
Section titled “ERC-1155: one contract, many token types”ERC-20 gives you one fungible token per contract. ERC-721 gives you a collection of unique tokens per contract. But consider a game: it has gold (fungible, millions of units), health potions (fungible, thousands), and a handful of unique legendary swords (non-fungible). Deploying a separate contract for each item type is wasteful — each deployment costs gas, and moving a whole inventory means one transaction per item type.
ERC-1155 (the “multi-token standard,” finalized 2019) collapses all of that into a single contract managing many token types at once. Its core map is balances[tokenId][owner] — a per-id balance:
Game items contract (one deployment) ┌───────────────────────────────────────────────┐ │ balances[GOLD ][0xAlice] = 5000 ← fungible │ │ balances[POTION][0xAlice] = 12 ← fungible │ │ balances[SWORD ][0xAlice] = 1 ← "NFT": id │ │ balances[SWORD ][0xBob ] = 0 with supply │ └───────────────────────────────────────────────┘ of exactly 1A token id with a large supply behaves fungibly (gold); a token id whose supply is exactly 1 behaves like an NFT (the sword). The same interface expresses both. And its killer feature is batch operations: safeBatchTransferFrom moves many token ids in one transaction, so handing over a full game inventory — gold, potions, and the sword together — is a single gas-metered call instead of a dozen. For anything managing a catalog of assets (games, editions, ticketing), ERC-1155 is dramatically cheaper. The trade-off is that it is a more complex interface than ERC-20’s six functions, and its optional metadata and safe-transfer callbacks add surface area to reason about.
Why standards are the real invention
Section titled “Why standards are the real invention”Step back and notice what none of these three standards added to Ethereum. There is no new opcode, no protocol change, no cryptography. ERC-20, ERC-721, and ERC-1155 are all just contracts keeping maps in storage — the exact primitive you already built. What they added was agreement on names.
That agreement is what turns tokens into composable legos. Because a lending market can assume any collateral answers balanceOf and transferFrom, it can accept a token that did not exist when the market was written. Because a marketplace can assume any collectible answers ownerOf, it can list an NFT collection deployed years later. Because a wallet targets interfaces, not addresses, it displays your entire portfolio without knowing a single token by name. Every DeFi protocol in the pages that follow — AMMs, lending, stablecoins — is built by composing these interfaces. Take the standard away and every one of those integrations would need bespoke, per-token code, and the ecosystem would not compose at all. The tech was the EVM; the ecosystem was the handshake.
The architect’s lens
Section titled “The architect’s lens”- Why does it exist? Because Ethereum’s protocol only knows one asset (ETH), yet everyone wanted many. A token standard lets any team mint a new asset as an ordinary contract, and have every existing wallet, exchange, and protocol handle it with zero changes on their side — assets became a library, not a protocol upgrade.
- What problem does it solve? Composability. Without a shared interface, moving your USDC into a Uniswap pool would require Uniswap to have written code specifically for USDC. The standard lets code that has never heard of your token still
balanceOfandtransferFromit, so protocols snap together like legos. - What are the trade-offs? The interface is a convention, not enforced by the EVM — a contract can honor the function names and still behave maliciously (fake balances, reentrant transfers, or a
transferFromthat steals). “Looks like ERC-20” is not “is safe.” And convenience patterns like infinite approvals turn composability into standing attack surface. - When should I avoid it? When your asset is genuinely native and singular, use ETH — do not wrap it in a token contract for its own sake. When you truly need only one fungible asset, ERC-1155’s batch machinery is overkill; when you need one unique series, ERC-20’s fungibility is wrong. Pick the interface that matches the shape of ownership, not the trendiest one.
- What breaks if I remove it? Composability collapses. Every wallet would need per-token code, every DeFi integration would be bespoke and pairwise, and the “money legos” that make up the rest of this part could not click together. The EVM would still run; the ecosystem would not exist.
Check your understanding
Section titled “Check your understanding”- Ethereum has exactly one native asset. What is it, and where does “your USDC balance” actually live if not in the protocol?
- An ERC-20 “standard” contains no enforcement mechanism. What is it, then, and what holds it up?
- Why does the
approve+transferFrompattern exist — what can a contract do with it that a plaintransfercannot enable? Name one concrete danger of the common “infinite approval” shortcut. - On-chain, what single fact does owning an ERC-721 NFT actually guarantee, and what does it typically not guarantee about the associated image?
- ERC-1155 introduces no new cryptography or opcodes over ERC-20. What concrete efficiencies does it provide, and how can one contract hold both fungible and non-fungible tokens?
Show answers
- The one native asset is ETH. “Your USDC balance” is just an integer stored in the USDC contract’s own storage map (
balances[you]) — a number in a specific contract’s slot, not a fact the Ethereum protocol tracks. The chain only guarantees the transfer function ran deterministically and everyone agrees on the resulting state; it has no idea what “USDC” means. - It is a specification of function signatures (EIP-20): the names and behaviors of
balanceOf,transfer,approve,allowance,transferFrom,totalSupplyplus two events. Nothing in the EVM enforces it. It is held up purely by convention — everyone’s wallets and protocols choosing to assume and honor the same interface, exactly like a UTXO only “means money” because everyone’s software agrees it does. transferonly moves tokens when you are the caller (msg.sender), but DeFi needs contracts to move your tokens on your behalf (a pool pulling your USDC into a swap).approverecords a bounded permissionallowance[owner][spender];transferFromlets the approved contract move up to that amount in a later transaction. Danger: an infinite approval is a standing key — if the approved contract is malicious, buggy, or later upgraded to something hostile, it can drain the whole approved balance at any time with no further consent (e.g. the Badger DAO drain, Dec 2021).- It guarantees exactly one thing: the tamper-proof, globally-verifiable mapping from
tokenIdto owner (ownerOf[id] = you) in that contract. It does not automatically guarantee anything about the image —tokenURIusually returns a link to off-chain metadata/media (IPFS or a web server), so the picture can rot, disappear, or (on a mutable server) change, while the on-chain ownership record stays intact. - ERC-1155 lets a single contract manage many token ids at once (
balances[tokenId][owner]), saving the gas of deploying a separate contract per asset, and supports batch transfers (safeBatchTransferFrom) so an entire inventory moves in one transaction. A token id with a large supply behaves fungibly (game gold); a token id whose supply is exactly 1 behaves like an NFT (a unique sword) — the same interface expresses both.