web3.path

PHASE 09 Tokens · ~3 hours

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-20ERC-721ERC-1155
UnitFungibleNon-fungibleMulti-token (both)
Statebalance[addr]owner[id]balance[id][addr]
Use caseCurrencies, LP tokensArt, identity, ticketsGames (1000 swords + unique boss)
Gas/mint~50k~90k~70k (batch cheaper)
AnalogyUSD in a bankCar titleSteam inventory

2. ERC-20 extensions you should know

3. ERC-721 extensions

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"
DangersetApprovalForAll 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:

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?
Most NFT "hacks" are blanket approvals. Keys are fine — the attacker got permission, not the key.
← Phase 8Phase 10: Security →