Token Standards
ERCs are the "npm conventions" of Ethereum — agreed interfaces so wallets, exchanges, and dApps can speak a common language without reading each contract's code.
Goal — internalize ERC-20 / 721 / 1155, know the lesser-known extensions (2612, 4626), and build a marketplace backend.
1. The big three
| ERC-20 | ERC-721 | ERC-1155 | |
|---|---|---|---|
| Unit | Fungible | Non-fungible | Multi-token (both) |
| State | balance[addr] | owner[id] | balance[id][addr] |
| Use case | Currencies, LP tokens | Art, identity, tickets | Games (1000 swords + unique boss) |
| Gas/mint | ~50k | ~90k | ~70k (batch cheaper) |
| Analogy | USD in a bank | Car title | Steam inventory |
2. ERC-20 extensions you should know
- ERC-2612 (permit) — sign an off-chain message to approve spending; no separate
approvetx. Huge UX win. - ERC-4626 (tokenized vault) — standard for yield vaults:
deposit,withdraw,totalAssets. Used by every yield aggregator now. - ERC-777 — hooks on send/receive. Avoid; creates reentrancy surface.
3. ERC-721 extensions
- Enumerable — adds
tokenByIndex/tokenOfOwnerByIndex. Expensive; usually index off-chain instead (Phase 8). - URI storage — per-token metadata URI.
- ERC-2981 — on-chain royalty signal (marketplaces may honor it).
4. ERC-1155 — why it exists
Games/marketplaces have millions of items. Deploying a contract per item is insane. ERC-1155 stores balance[tokenId][owner] in one contract and supports batch transfers — O(1) contract count, O(batch) gas.
// transfer 5 of #42 and 1 of #43 in one tx
token.safeBatchTransferFrom(from, to, [42, 43], [5, 1], "0x");
5. Approvals: the UX & security crux
// ERC-20
approve(spender, amount) // then spender.transferFrom(...)
// ERC-721/1155
setApprovalForAll(spender, true) // blanket "spend anything"
Danger —
setApprovalForAll is the #1 NFT phishing vector. If a user signs it for a malicious contract, attacker can transfer every NFT the user owns. Always show the spender and recommend "revoke.cash" hygiene.6. EIP-712 typed data
Wallets can sign structured messages (human-readable), not just a blob of bytes. Used by permit, OpenSea listings, meta-transactions.
const domain = { name: "MyToken", version: "1", chainId: 1, verifyingContract: TOKEN };
const types = { Permit: [
{ name: "owner", type: "address" }, { name: "spender", type: "address" },
{ name: "value", type: "uint256" }, { name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
]};
const sig = await signer.signTypedData(domain, types, value);
Analogy — EIP-712 is to raw
eth_sign what a typed API schema is to curl-ing random JSON: the wallet can render "Approve 100 USDC to Uniswap" instead of "sign 0xdeadbeef…".7. Marketplace primitives
An NFT marketplace is mostly: off-chain orderbook + on-chain settlement. Seller signs an EIP-712 order; buyer submits it to a contract that verifies the signature and atomically swaps NFT ↔ ETH.
Seller: sign { nft, id, price, expiry } (off-chain, free)
Listed in DB / API
Buyer: call settle(order, signature) + send ETH (on-chain, 1 tx)
contract: verify sig → pull NFT from seller → pay seller → transfer to buyer
8. A minimal on-chain settlement
struct Order {
address seller; address nft; uint256 tokenId;
uint256 price; uint256 nonce; uint256 expiry;
}
function fulfill(Order calldata o, bytes calldata sig) external payable {
require(block.timestamp <= o.expiry, "expired");
require(msg.value == o.price, "bad price");
require(!usedNonces[o.seller][o.nonce], "used");
bytes32 h = _hashOrder(o); // EIP-712 digest
require(ECDSA.recover(h, sig) == o.seller, "bad sig");
usedNonces[o.seller][o.nonce] = true;
IERC721(o.nft).safeTransferFrom(o.seller, msg.sender, o.tokenId);
(bool ok,) = o.seller.call{value: o.price}(""); require(ok);
}
9. Stablecoins & the "real" ERC-20s
Not all ERC-20s are polite. Quirks you'll hit:
- USDT has no return value on
transfer. UseSafeERC20or you'll revert decoding "()". - Fee-on-transfer tokens — received amount < sent. Always measure
balanceAfter − balanceBefore. - Rebasing (e.g., stETH) — balances change without transfers. Accounting nightmare.
- USDC has a
blacklist; you can be frozen. Not all tokens are equal.
10. Project
Deliverable — extend your Phase 8 backend to serve an NFT marketplace: POST
/orders stores signed EIP-712 orders, GET /orders?nft=... returns active ones. Your contract settles via fulfill(order, sig). Add integration tests using Hardhat's impersonate.Quiz
Q. A user reports their NFT disappeared. You find a transaction from their address calling
safeTransferFrom to an unknown address, signed by their own wallet. Most likely cause?- A chain reorg
- Contract bug in the marketplace
- They signed
setApprovalForAllfor a phishing contract earlier, which then yanked the NFT - Wallet keys were stolen
Most NFT "hacks" are blanket approvals. Keys are fine — the attacker got permission, not the key.