Final ERC standard. The canonical NFT interface: each token has a unique `tokenId`, an owner, and metadata. Powers virtually every NFT collection, marketplace, and on-chain identity primitive on EVM chains.
- 01NFT collections
- 02digital collectibles
- 03in-game items with provenance
- 04tokenized real-world assets
- 05on-chain identity / soulbound bases
- pnpm add @openzeppelin/contracts
Implement ERC-721 by extending `ERC721` from `@openzeppelin/contracts/token/ERC721/ERC721.sol` (or Solady's `ERC721` for gas-optimized, or `ERC721A` from Chiru Labs for cheap batch mints). Required interface (`IERC721`): `balanceOf(address)`, `ownerOf(uint256)`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, `getApproved`, `isApprovedForAll`. Pair with `IERC721Metadata` for `name()`, `symbol()`, `tokenURI(uint256)`. Use `_safeMint(to, tokenId)` for new mints — it calls `onERC721Received` on contract recipients to prevent stuck NFTs. Always implement `supportsInterface` (ERC-165) to advertise compliance and any extensions like ERC-2981 royalties or ERC-4906 metadata updates.
- ⚑`transferFrom` does NOT check whether the recipient is a contract that can handle NFTs — use `safeTransferFrom` to avoid permanently stuck tokens.
- ⚑`tokenURI` is not enforced to be unique, immutable, or even resolvable. Off-chain metadata at IPFS/HTTP can rug or change unless content-addressed and pinned.
- ⚑Enumerable extension (`ERC721Enumerable`) is gas-expensive on every transfer. Skip it and use an off-chain indexer (subgraph, Alchemy NFT API) instead.
- ⚑Approval state is per-token AND per-operator. Marketplaces typically request `setApprovalForAll` — users should know this approves the entire collection, not one token.
- ⚑ERC-721A optimizes mint cost but makes `ownerOf` O(N) in the worst case for unminted-but-claimed ranges; some analytics tools choke on it.