the standard · since January 2018
// standard · drafted sept 2017 · final jan 2018 · nft substrate · ready

What is ERC-721?

Ethereum's non-fungible token standard — and the gaps a minimum interface left to integrators.

ERC-721 ·EIP-721 ·by William Entriken + Dieter Shirley + Jacob Evans + Nastassia Sachs

updated · 11 min read · by ercs-solved maintainers

ERC-721 is the standard that made Ethereum the home of the NFT — every CryptoPunk, every Bored Ape, every art drop, every PFP collection speaks the same nine-function interface that Dieter Shirley first drafted in September 2017 and that William Entriken led to final specification on 24 January 2018. That minimalism is exactly why so much built on top of it, and exactly why every team since has paid an integration tax for metadata that goes offline, approvals that drain entire collections, and safe-transfer hooks that the spec itself describes as a deliberate "caller-control" tradeoff. This page walks the history, the ABI, the limits people keep working around, and the LSP8 standard the author of ERC-20 designed instead.

on this page
  1. What is ERC-721?
  2. At a glance
  3. Origin story
  4. The spec
  5. What's broken
  6. tokenURI trap
  7. setApprovalForAll is collection-wide
  8. safeTransferFrom is only half a hook
  9. uint256 tokenIds force a single integer namespace
  10. One token, one transaction — gas at scale
  11. Criticism from the source
  12. LUKSO alternative
  13. ERC-721 vs LSP8
  14. When to use which
  15. FAQ
  16. Sources
  17. Keep reading
at a glance

The standard, in one card.

Standard
ERC-721 / EIP-721
Finalized
24 January 2018
Authors
Entriken · Shirley · Evans · Sachs
Required functions
9 (8 + ERC-165)
ID type
uint256
Modern alternative
LUKSO LSP8
how the standard came to be

The origin story.#

On 22 September 2017, Dieter Shirley — then CTO at Dapper Labs — opened GitHub issue #721 on the Ethereum repository with a draft proposal for a non-fungible token standard. The team at Dapper Labs was a few months from launching CryptoKitties, and the spec was the substrate they needed: a way for each kitty to be a uniquely-owned on-chain object with its own provenance, transferable through the same primitives that ERC-20 had standardized for fungible balances.

CryptoKitties launched on 28 November 2017 — between Shirley's draft and the spec's finalization — and immediately demonstrated both the thesis and the limits. The thesis: people would pay real money for unique on-chain assets. The limit: ERC-721's one-transfer-per-token model meant the viral growth congested Ethereum, with the network briefly overwhelmed by kitty breeding and trading.

Over the next few months William Entriken took on the lead-author role, joined by Shirley, Jacob Evans, and Nastassia Sachs, and EIP-721 was finalized on 24 January 2018. Entriken's framing — visible in his later interviews and in his GitHub bio ("Lead author of ERC-721") — was that he had deliberately designed "a minimal standard which is ERC-721," after studying use cases that went well beyond the cat-collecting example: government assets, identities, physical goods, provenance.

The minimum interface is a feature, not a limitation. It is what made the rest of the NFT economy possible — the same interface used by CryptoKitties is the one used by Punks, Bored Apes, Art Blocks, and every collection since.

The cost shows up in the parts the spec deliberately left to the application layer. The Rationale section of EIP-721 is honest about one of these tradeoffs:

Different functions are used for transfer and safeTransferFrom to give the caller control over their risk.

That is the authors saying, in plain text, that the safety hook is opt-in by design. The rest of this page is about what each "control over their risk" delegation has cost the people building on the standard since — and how one of the co-authors (Shirley) has framed those costs in retrospect, in the dedicated Criticism from the source section below.

  1. Sept 2017 Dieter Shirley (CTO, Dapper Labs) opens GitHub issue #721 with a draft proposal for a non-fungible token standard.
  2. Nov 2017 CryptoKitties launches on 28 November 2017 and immediately exposes ERC-721's per-token gas cost when its growth congests Ethereum.
  3. Jan 2018 EIP-721 is finalized on 24 January 2018 with William Entriken as lead author, alongside Shirley, Evans, and Sachs.
  4. 2020-09 ERC-2981 (NFT Royalty Standard) proposed to give NFTs a recommended royaltyInfo function — enforcement is left to the marketplace.
  5. 2021–22 NFT mania. ERC-721 absorbs PFP collections, generative art, music drops, gaming, and brand experiments. Every limit the spec left to the application layer gets paid for at scale.
  6. 2022-04 ERC-4906 adds the MetadataUpdate event so contracts can signal that off-chain tokenURI JSON has changed.
  7. 2023 LUKSO mainnet launches with LSP8 as the native NFT primitive — bytes32 IDs, per-token ERC-725Y metadata, LSP1 receiver hooks, and LSP6 operator scoping.
  8. 2024–26 Marketplace royalty enforcement debates continue. Transfer-validator patterns and creator-token standards emerge as opt-in policy layers on top of ERC-721, while LSP8 carries the same policy as substrate-level account permissions.
the spec, end to end

What ERC-721 actually is.#

ERC-721 EIP-721 interface solidity
// EIP-721 — required
function balanceOf(address owner)                  external view returns (uint256);
function ownerOf(uint256 tokenId)                  external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) external payable;
function safeTransferFrom(address from, address to, uint256 tokenId) external payable;
function transferFrom(address from, address to, uint256 tokenId)    external payable;
function approve(address to, uint256 tokenId)                       external payable;
function setApprovalForAll(address operator, bool approved)         external;
function getApproved(uint256 tokenId)              external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

// EIP-165 — required (interface detection)
function supportsInterface(bytes4 interfaceId) external view returns (bool);

// EIP-721 metadata extension — optional
function name()     external view returns (string memory);
function symbol()   external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);

// EIP-721 receiver — required for safe-transfer destinations
function onERC721Received(address operator, address from, uint256 tokenId, bytes data)
  external returns (bytes4);
ERC-721 interface, grouped
read: balanceOf · ownerOf · getApproved · isApprovedForAll
write: transferFrom · safeTransferFrom · approve · setApprovalForAll
emit: Transfer · Approval · ApprovalForAll
metadata (optional): name · symbol · tokenURI

The mental model is small: an ownership map (ownerOf), a way to move ownership (transferFrom / safeTransferFrom), a way to delegate authority (per-token approve or collection-wide setApprovalForAll), and three events so the chain can be indexed. The optional metadata extension adds the tokenURI(tokenId) that returns a string — typically a URL pointing to off-chain JSON. EIP-721 mandates ERC-165 (supportsInterface) so consumers can ask a contract whether it implements the NFT interface before calling into it. The canonical specification is at eips.ethereum.org/EIPS/eip-721.

the integration tax

What's broken about ERC-721.#

Five gaps, in order of how often they cost users money or render their collections unviewable. Each one has produced its own follow-on EIP, its own marketplace policy fight, its own startup. None of those follow-ons fix the substrate.

  1. The tokenURI trap.#

    tokenURI(tokenId) returns one string per token — typically an HTTPS URL or an ipfs:// CID — pointing to off-chain JSON. The contract has no opinion on what the string points to, what the JSON schema should be, whether the host is still up, who can change the contents, or what changed when. The resolution chain is brittle by design: a marketplace fetches the URI, downloads the JSON from a server or IPFS gateway, parses it according to its own interpretation of OpenSea's de-facto metadata schema (name, description, image, attributes, animation_url, external_url), then fetches the image referenced inside the JSON from yet another host. Each hop is a separate point of failure, and none of the points are typed or verified on-chain. When a pinning subscription expires or a backend goes down, the NFT renders blank in every wallet and marketplace simultaneously — a problem that has hit collections worth hundreds of millions of dollars. Marketplaces and wallets cache results on their own schedules, which is why 'refresh metadata' buttons exist: there is no standard signal for what changed. ERC-4906 (April 2022) added a MetadataUpdate event, but it only signals that *something* changed — it doesn't carry the new state, name the affected field, or guarantee anyone re-reads the URI. Dynamic NFTs, on-chain attribute updates, evolving art, royalty configuration, and trait revelations all depend on off-chain infrastructure the spec deliberately leaves undefined. The contract holds the tokenId; everything that makes the NFT visually or semantically real lives somewhere else, on terms set by whoever is hosting it.

    ERC-721 tokenURI(tokenId) returns (string)
    one URI per token off-chain JSON no schema no integrity check no contract-readable typing
    LSP8 + LSP4 getDataForTokenId(tokenId, key) returns (bytes)
    typed per-key storage VerifiableURI hash-anchored contract-readable account-permissioned updates
    workarounds tried
    • mutable IPFS pointers (still depends on someone pinning the CID)
    • Arweave permanent storage (paid once at upload)
    • server-rendered metadata APIs (Manifold, Reservoir, custom backends)
    • ERC-4906 MetadataUpdate event (signal-only — doesn't carry the new state)
    • fully on-chain SVG generators (expensive, awkward for rich media)
    • base64-encoded data URIs (on-chain bytes inside the tokenURI string)

    read the deep-dive

  2. setApprovalForAll is collection-wide.#

    A single signature grants blanket authority over every token in the collection — past, present, and future mints. Marketplaces ask for it once and never have to ask again. The phishing pattern that has drained named NFT collections again and again rides this primitive: trick the holder into one bad signature and every token is gone. Per-token approve exists, but the operator-for-all pattern is the de-facto UX.

    ERC-721 setApprovalForAll(operator, true)
    collection-wide phishable lasts forever
    LSP8 + LSP6 authorizeOperator(op, tokenId, data)
    per-token account-gated instant revoke
    workarounds tried
    • revoke.cash
    • marketplace allowlists
    • signed-order patterns
    • transfer-validator hooks
  3. safeTransferFrom is only half a hook.#

    ERC-721 defines IERC721Receiver and a safeTransferFrom variant that checks it — but the check is opt-in, the receiver shape is ERC-721-specific, and the non-safe transferFrom still ships. NFTs land in contracts that have no idea what to do with them. The spec's own Rationale is honest about this: 'Different functions are used for transfer and safeTransferFrom to give the caller control over their risk.' The receiver problem was a deliberate scope-out.

    ERC-721 onERC721Received(...) returns (bytes4)
    one asset type opt-in only
    LSP1 universalReceiver(typeId, data)
    every transfer every asset type
    workarounds tried
    • implement every receiver interface per asset standard
    • OpenZeppelin Holder mixins
    • sweeper / rescue contracts

    read the deep-dive

  4. uint256 tokenIds force a single integer namespace.#

    Cheap and predictable for sequential mints. Awkward when the ID needs to mean something — a content hash, a serial keyed to off-chain state, a structured reference. Teams end up maintaining side mappings to translate, and the ID itself stops being self-describing. You read tokenId 42 and have to consult the contract (or a backend) to know what it points to.

    ERC-721 uint256 tokenId
    one number needs side mappings
    LSP8 bytes32 tokenId
    hash · serial · ref · number
    workarounds tried
    • off-chain registries
    • hash truncation to uint256
    • multiple side mappings

    read the deep-dive

  5. One token, one transaction — gas at scale.#

    Transferring 50 NFTs is 50 transactions. CryptoKitties exposed the consequence at launch in late 2017, when its viral growth congested Ethereum and made the per-token gas tax visible at scale. Every airdrop, marketplace sweep, and inventory migration since has paid the same tax. ERC-721A (Azuki's gas-optimized implementation) batches mints internally but still ships the same one-call-per-token transfer ABI; ERC-1155 redesigned the standard from scratch to fix it.

    ERC-721 safeTransferFrom(from, to, id) × N
    one tx per token no batching
    LSP8 + LSP25 batch ops + relay execution
    bundled gas-aware gasless-onboardable
    workarounds tried
    • ERC-721A (Azuki impl — mint-batching, same transfer ABI)
    • ERC-1155 (different standard entirely)
    • marketplace bulk-action wrappers
criticism from the source

What ERC-721 co-authors said about the standard.#

Two years after EIP-721 was finalized, Dieter Shirley — one of the four co-authors and CTO of Dapper Labs, the team whose CryptoKitties contract was the standard's launch use case — published a retrospective naming three specific shortcomings of the ERC-721 architecture. The quotes below are verbatim from that essay. The LUKSO answers were designed into the LSP suite from the bottom up by Fabian Vogelsteller.

  1. The ownership-model assumption

    ERC-721 defines an ownership model for NFTs that assumes that only Ethereum addresses can own an NFT. However, the idea of an asset itself owning other assets (like a CryptoKitty owning a nifty pair of sunglasses) is very interesting in some use cases, and required a new specification (ERC-998) to be created. ERC-998 is very powerful, but it's also much more complicated than ERC-721.

    LUKSO answer LSP0 +LSP8

    On LUKSO, accounts ARE contracts. A Universal Profile (LSP0) can own LSP8 NFTs, other Universal Profiles, fungible LSP7 tokens, or any combination — and any of those owned assets can in turn own further assets, because the same account primitive composes recursively. The 'asset owning another asset' arrangement that required a separate, more complicated ERC-998 extension on Ethereum is the substrate's default behaviour on LUKSO, not an opt-in layer.

  2. The retroactive-upgrade impossibility

    Implementing it [ERC-998] properly is very difficult, and retroactively applying its features to existing ERC-721 assets is effectively impossible due to the immutable nature of Ethereum smart contracts.

    LUKSO answer LSP17 +LSP6 +LSP14

    LSP17 Contract Extension is a first-class standard for adding behaviour to a contract account after deployment. LSP6 Key Manager is replaceable on a deployed Universal Profile, so the permission and authorisation layer can be upgraded without redeploying the assets it controls. LSP14 Ownable 2-Step gives ownership transitions and renunciation a defined lifecycle. The 'we deployed it, now it's frozen' problem becomes a designed-for upgrade path instead of an irreversible accident.

  3. The central-ledger storage model

    With the ledger model, it's hard to know who should pay this rent. For example, the CryptoKitties contract represents tens of thousands of players with almost two million Kitties and over 111MB of on-chain data. Ethereum provides no way of fairly charging rent to all those Kitty owners.

    LUKSO answer ERC-725Y +LSP4 +LSP5

    ERC-725Y is the typed key-value storage substrate every LUKSO account and asset is built on. LSP4 defines per-asset metadata under ERC-725Y on the asset itself; per-token data lives there too via getDataForTokenId. LSP5 keeps standardised records of received assets on the owner's account. Storage sits with whichever entity actually reads and pays for it — the asset, the per-token slot, or the owner — instead of all of it accumulating in one central per-contract mapping that nobody can fairly charge for.

the LUKSO alternative

LUKSO designed it differently.#

ERC-721's authors did exactly what a minimum-interface standard is supposed to do — they shipped a primitive small enough to be adopted everywhere, and explicit about the parts they were leaving to application code. The downside is the downside of every minimum spec: every gap turns into a known problem the rest of the industry pays for forever.

Fabian Vogelsteller had already lived this once. He wrote ERC-20 in November 2015 and watched the same dynamic play out on the fungible side: a minimum interface that won the network effect, plus six years of layered EIPs trying to patch what the substrate didn't say. When he co-founded LUKSO with Marjorie Hernandez in 2017 — the same year Shirley drafted ERC-721 — the thesis was direct: design the surrounding system that a minimum interface deliberately can't specify.

For non-fungible tokens, that surrounding system is LSP8. The structural moves are small and deliberate:

  • bytes32 tokenIds. The integer case still works — cast a uint256 and proceed. The new cases (content hashes, encoded serials, structured references) become first-class instead of side mappings. The ID becomes a typed value, not a counter pretending to be a name.
  • Per-token ERC-725Y data, via getDataForTokenId(tokenId, key). The metadata stops being a URL behind a server you don't control. It's a typed key-value graph on-chain, dynamic updates are setData calls gated by LSP6 account permissions, and off-chain media still works — with VerifiableURI when you want hash-anchored integrity.
  • Receiver hooks via LSP1 on every transfer, not only on the opt-in safeTransferFrom variant. One hook, every asset type, declared policy.
  • Operator authorization scoped per token and gated through LSP6 on the account side. The collection-wide setApprovalForAll pattern simply doesn't exist; an operator can be authorized for a specific tokenId, with the controller doing the authorization itself bound by named, revocable permissions.

LUKSO mainnet went live in 2023. LSP8 is an EVM contract — it doesn't require the LUKSO chain to run; it requires the LUKSO chain to cohere. On Ethereum, an LSP8 contract works, but every marketplace and indexer around it still expects ERC-721's ABI. On LUKSO, the substrate matches the standard: Universal Profiles hold the assets, LSP6 authorizes the operators, LSP1 routes the transfer hooks, LSP4 holds the metadata, and a non-fungible token finally gets to live in a system designed for it instead of one bolted on.

The practical takeaway: if an ERC-721 collection depends on fragile tokenURI infrastructure, collection-wide approvals, opt-in receiver hooks, or one-token-per-transfer flows, LSP8 and LSP4 show the NFT model rebuilt around typed IDs, typed metadata, receiver awareness, and account-level permissions.

spec to spec, at a glance

ERC-721 vs LSP8 in one table.#

row ERC-721 LSP8
token ID type uint256 bytes32
metadata model tokenURI(id) → string (off-chain JSON, untyped, unverifiable, optional) getDataForTokenId(id, key) → bytes (typed, on-chain, VerifiableURI-anchored)
metadata readable from contracts no — string return only usable from web3 clients yes — typed bytes, callable from other contracts
off-chain media integrity trust the host (silent swap possible) VerifiableURI on-chain hash — swap detectable
transfer hook onERC721Received (only on safeTransferFrom) universalReceiver (LSP1) on every transfer
transfer data payload bytes _data (only on safeTransferFrom) bytes data on every transfer + force flag
interface ABI ERC-721 selectors — consumed by code that hardcodes them distinct LSP8 selectors — won't accidentally dispatch as ERC-721

full matrix → erc-721 vs lsp8

be honest about scope

When to use which.#

people also ask

FAQ.#

  • What does ERC-721 stand for? #

    ERC stands for Ethereum Request for Comments. ERC-721 is the 721st proposal in that series, and it specifies a minimum interface for non-fungible tokens on Ethereum — assets that are unique and individually owned, in contrast to ERC-20's fungible balances. The proposal was formalized as EIP-721 (Ethereum Improvement Proposal 721), which is the canonical specification at eips.ethereum.org/EIPS/eip-721.

  • Who created ERC-721? #

    Dieter Shirley, then CTO of Dapper Labs, drafted the original proposal on GitHub on 22 September 2017 as the substrate for CryptoKitties. The finalized EIP-721 was published on 24 January 2018 with William Entriken as lead author, alongside Shirley, Jacob Evans, and Nastassia Sachs. Entriken's GitHub bio identifies him as the 'Lead author of ERC-721'.

  • When was ERC-721 created? #

    The GitHub draft proposal opened on 22 September 2017 (ethereum/EIPs issue #721). The formalized EIP-721 was published on 24 January 2018. CryptoKitties launched on Ethereum mainnet on 28 November 2017, between the two milestones, and provided the early load-bearing test of the spec.

  • Is ERC-721 the same as EIP-721? #

    Yes — they refer to the same standard. ERC-721 is the original community proposal name (the proposal number assigned to the GitHub issue); EIP-721 is the formalized specification once it moved into the EIP repository. Most developers say ERC-721 in casual use; eips.ethereum.org/EIPS/eip-721 is the authoritative spec.

  • What's the difference between ERC-20 and ERC-721? #

    ERC-20 is a fungible token standard — balances are interchangeable. balanceOf(address) returns a uint256 and one token is worth the same as any other token in the contract. ERC-721 is a non-fungible token standard — each tokenId is unique and individually owned via ownerOf(tokenId). Both were proposed by Vogelsteller (ERC-20) and Shirley/Entriken et al (ERC-721) in 2015 and 2017–18 respectively. See ercsolved.dev/erc-20/ for the fungible counterpart.

  • What is an NFT? #

    A non-fungible token is a unique, individually-owned, on-chain identifier — most commonly an ERC-721 tokenId on Ethereum or an EVM-compatible chain. The token itself carries little data: it's a uint256 with an ownerOf(...) mapping. Everything visual or semantic about the NFT (image, attributes, description) is typically referenced through tokenURI as off-chain JSON. This is why pinning, hosting, and metadata standards matter so much in practice.

  • What's wrong with ERC-721? #

    Five gaps. (1) tokenURI returns one string per token with no on-chain typing or integrity check — pinning failures kill rendering. (2) setApprovalForAll grants blanket collection-wide authority to operators, the primitive behind nearly every NFT phishing drain. (3) safeTransferFrom only checks IERC721Receiver, is opt-in, and is shipped alongside a non-safe transferFrom that strands tokens silently. (4) uint256 tokenIds are awkward for hashes or structured IDs. (5) One transfer = one transaction, which is what CryptoKitties exposed at launch and every airdrop has paid for since.

  • What is LSP8? #

    LSP8 is the LUKSO Identifiable Digital Asset standard — the LUKSO equivalent of ERC-721, designed by Fabian Vogelsteller (the author of ERC-20). It keeps ERC-721's ownership model and widens tokenId to bytes32, replaces tokenURI with a typed per-token ERC-725Y key/value store, and routes every transfer through the LSP1 Universal Receiver. Paired with LSP6 on the account side, operator authorization stops being a forever-promise to a marketplace.

  • What is ERC-721A, and does it fix anything? #

    ERC-721A is Azuki's gas-optimized implementation of the ERC-721 standard — same external ABI, same selectors, batch-mint optimizations on the internal storage. It materially reduces gas for sequential mints of large collections (the original use case was Azuki's 10,000-piece drop). It does not change tokenURI, doesn't add transfer hooks, doesn't fix the approval model, and doesn't replace the standard. It's an implementation, not a new specification.

  • What about NFT royalties? #

    ERC-721 itself does not define royalties — that was a deliberate scope-out. ERC-2981 added a royaltyInfo recommendation in 2020, but enforcement is marketplace-side and inconsistent. On the LUKSO side, LSP8 likewise doesn't define royalties in its base contract; instead, the LSP6 Key Manager and transfer-layer permissions give finer-grained policy control — royalty enforcement becomes a controller decision on the account, not a marketplace policy negotiation.

  • Can I migrate an ERC-721 collection to LSP8? #

    Yes. The ownership model is the same, so migration is contract-level rather than user-level. The LUKSO docs publish an end-to-end guide for deploying an LSP8 equivalent and bridging holders; the site's own walkthrough is at ercsolved.dev/build/migrate/erc721-to-lsp8/.

  • What is tokenURI and how does it work in ERC-721? #

    tokenURI(tokenId) is the optional ERC-721 function that returns a single string per token — typically an HTTPS URL or an ipfs:// CID — pointing to off-chain JSON describing the NFT. Marketplaces and wallets resolve it by fetching the URI, downloading the JSON, parsing it according to the de-facto OpenSea metadata schema (name, description, image, attributes, animation_url), then fetching the referenced image from yet another host. Nothing about this is enforced on-chain: the contract has no opinion on what the URI points to, what schema the JSON should follow, or whether the host is still up. ERC-721 mandates ERC-165 for interface detection but doesn't mandate the metadata extension at all — it's optional. The result is that 'an NFT' depends on infrastructure outside the contract for everything visual or semantic, which is the source of most NFT metadata problems.

  • Why do NFT images disappear or show as broken? #

    Because the actual image isn't on-chain — the contract just stores a tokenURI string pointing to off-chain JSON, which in turn points to the image at another off-chain location. When any link in that chain fails, the NFT renders blank. Common causes: IPFS pinning subscriptions expire and no peer is keeping the CID alive; the backend server hosting the metadata API goes down or is decommissioned; the image hosting (Cloudflare, S3, the project's own server) returns 404 or is taken down; or the project shuts down and metadata stops being maintained. ERC-4906 added a MetadataUpdate event in 2022 so contracts can tell marketplaces 'refresh this token' — but it doesn't fix any of the underlying availability issues. The LSP4 + VerifiableURI model on LUKSO addresses this by putting typed metadata on-chain under ERC-725Y storage and (for off-chain media) anchoring the hash on-chain so the chain enforces what the off-chain blob must be — a swap of the underlying file becomes detectable on-chain.

  • Has any ERC-721 co-author publicly criticised the standard? #

    Yes. In a March 2020 Medium essay, Dieter Shirley — co-author of ERC-721 and then-CTO of Dapper Labs (the team that built CryptoKitties) — named three specific shortcomings of the standard he helped design: ERC-721 assumes only Ethereum addresses can own an NFT (asset-owning-asset composability needed the more complicated ERC-998 extension); features can't be added retroactively because deployed Ethereum contracts are immutable; and the central-ledger storage model makes per-asset rent accounting unfair (citing the live CryptoKitties contract: ~2M Kitties, 111MB of on-chain data, all in one mapping). The full verbatim quotes paired with their LUKSO LSP answers are in the 'Criticism from the source' section above (ercsolved.dev/erc-721/#author-criticism). The other three co-authors (William Entriken, Jacob Evans, Nastassia Sachs) don't have equivalent retrospective criticism on the record.

glossary 15 terms used on this page
NFT
Non-fungible token. A unique, individually-owned on-chain identifier — most commonly an ERC-721 tokenId. Unlike an ERC-20 balance, each NFT is distinct.
tokenURI
The optional ERC-721 metadata function returning a single string URL per token, pointing to off-chain JSON describing the asset. The contract holds the string; the JSON, the schema interpretation, and the image hosting all live elsewhere.
OpenSea metadata schema
The de-facto JSON shape NFT marketplaces and wallets interpret tokenURI payloads against: name, description, image, attributes, animation_url, external_url. Not part of EIP-721 — just what the largest indexer expected, and everyone followed.
ERC-4906
April 2022 EIP that added the MetadataUpdate(tokenId) event so contracts can signal to marketplaces that a token's metadata changed. Signal-only — doesn't carry the new state or specify which field changed.
ERC-725Y
The typed key-value storage substrate underneath every LUKSO account and asset. bytes32 keys map to bytes values, with LSP2 schemas defining how to interpret them. The 'on-chain database' LSP4 metadata lives in.
VerifiableURI
An LSP4 metadata primitive that lets an ERC-725Y key point to off-chain media WITH an on-chain hash, so the chain enforces what the off-chain blob must be. A silent swap of the underlying file becomes detectable on-chain.
data URI
A URI scheme (data:image/svg+xml;base64,...) that encodes content directly in the URL. Used by on-chain NFT projects to ship the entire image inside tokenURI itself — eliminates the off-chain dependency at the cost of gas.
IPFS
InterPlanetary File System. Content-addressed storage commonly used to host NFT metadata and media. Files persist only as long as someone pins them; the address (CID) is permanent but the data may not be.
Arweave
A permanent storage network paid for once at upload. Used by NFT projects that want stronger durability guarantees than IPFS pinning.
pinning
Telling an IPFS node to keep a file available. If no one is pinning the CID, peers may garbage-collect the content and the NFT becomes unviewable — even though the CID still resolves to nothing.
mint
Creating a new NFT — the transaction that calls _mint(to, tokenId) and emits Transfer(0x0, to, tokenId).
PFP
Profile-picture NFT. A collection (typically 5,000–10,000 pieces) designed to be used as a social avatar — the dominant NFT format from 2021 onward.
royalty
A percentage of secondary-sale proceeds paid to the original creator. Not defined by ERC-721; recommended by ERC-2981; enforced (or not) by marketplaces.
EIP-2981
The NFT Royalty Standard. Adds royaltyInfo(tokenId, salePrice) so marketplaces can read a recommended royalty. Compliance is voluntary.
ERC-165
Standard for runtime interface detection — supportsInterface(bytes4). ERC-721 mandates ERC-165 so consumers can ask a contract 'are you an NFT?'.