Skip to main content

What is ERC721?

ERC721 is Ethereum's non-fungible token standard, drafted by Dieter Shirley (then CTO of Dapper Labs) as GitHub issue #721 on 22 September 2017, and finalized on 24 January 2018 as EIP-721 with William Entriken as lead author alongside Shirley, Jacob Evans, and Nastassia Sachs. Every token has a unique uint256 tokenId, one owner, and one metadata URI. It became the substrate for the entire NFT economy β€” CryptoKitties, generative art, PFP collections β€” while exposing exactly the limits a minimum interface leaves behind. LUKSO's LSP8 addresses each one at the standard level.

Origin​

Dapper Labs needed a way for each CryptoKitty to be a uniquely-owned on-chain object with its own provenance, and Shirley's draft was the substrate. CryptoKitties launched on 28 November 2017, between the draft and the final spec, and immediately demonstrated both the thesis (people would pay real money for unique on-chain assets) and the limit (one-transfer-per-token congested Ethereum at launch). The EIP-721 Rationale is candid about its central design tradeoff: "Different functions are used for transfer and safeTransferFrom to give the caller control over their risk." The safety hook was opt-in by design.

The interface​

// 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-721 metadata extension β€” optional
function tokenURI(uint256 tokenId) external view returns (string memory);

Where it breaks down​

The tokenURI trap​

tokenURI(tokenId) returns one string per token β€” an HTTPS URL or an IPFS CID pointing to off-chain JSON. Nothing about the resolution chain is typed or verified on-chain: a marketplace fetches the URI, downloads JSON from a server or gateway, parses it against the de-facto OpenSea metadata schema, then fetches the referenced image from yet another host. When a pinning subscription lapses or a backend goes dark, the NFT renders blank everywhere at once β€” a failure that has hit collections worth hundreds of millions of dollars. ERC-4906 added a MetadataUpdate signal event in 2022, but it doesn't carry the new state or guarantee anyone re-reads the URI.

setApprovalForAll is collection-wide​

A single signature grants blanket authority over every token in the collection β€” past, present, and future mints. The named-collection phishing drains that recur again and again ride exactly this primitive: one bad signature, every token gone.

safeTransferFrom is only half a hook​

onERC721Received is checked only on the safeTransferFrom variant. The plain transferFrom still ships alongside it, and NFTs routinely land in contracts with no idea what to do with them β€” the receiver problem was a deliberate scope-out, not an oversight.

uint256 tokenIds carry no declared meaning​

Cheap for sequential mints, but a bare uint256 gives no signal about what it represents β€” a content hash and a plain counter look identical to any tool reading the contract. There's no standard way to declare "these IDs are hashes" or "these IDs are addresses," so teams write their own documentation and every integrator has to go find it.

One token, one transaction​

Transferring 50 NFTs is 50 transactions. CryptoKitties exposed the cost at launch; every airdrop, sweep, and migration since has paid the same tax.

What the standard's own author says​

Two years after EIP-721 shipped, Dieter Shirley β€” co-author and Dapper Labs CTO β€” published a retrospective naming three specific shortcomings of the standard he helped design (Medium, 23 March 2020):

"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... is very interesting in some use cases, and required a new specification (ERC-998) to be created."

On LUKSO, accounts are contracts by default β€” a Universal Profile can own LSP8 NFTs, other profiles, or LSP7 tokens, and any of those can own further assets recursively, without a separate composability standard.

"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."

LSP17 Contract Extension is a first-class standard for adding behavior to a deployed account after the fact β€” the "we deployed it, now it's frozen" problem becomes a designed-for upgrade path.

"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."

ERC725Y with LSP4 puts per-asset and per-token data on typed keys rather than one central mapping, so storage sits with whichever entity actually reads and pays for it.

The LUKSO successor​

LSP8 Identifiable Digital Asset keeps ERC721's ownership model and upgrades every primitive around it.

ERC721LSP8
Token ID typeuint256 β€” no standard way to declare what it meansbytes32 with a declared LSP8TokenIdFormat β€” number, string, address, or hash
MetadatatokenURI(id) β†’ string (off-chain, untyped)getDataForTokenId(id, key) β†’ bytes (typed, on-chain reference)
Off-chain media integritytrust the hostVerifiableURI β€” on-chain hash, swaps detectable
Transfer hookonERC721Received, opt-in onlyLSP1 universalReceiver, on every transfer to an LSP1-supporting contract
Operator authorizationcollection-wide setApprovalForAllper-token, gated through LSP6
Is ERC721 still the right choice?

When token IDs are sequential counters, metadata is immutable or already redundantly pinned, and the contract is built to be consumed by code that hardcodes the ERC721 ABI, the smaller surface is genuinely the feature. LSP8 wins once the token ID needs a declared, standardized meaning, per-token state mutates after mint, or transfers into LSP1-supporting contracts β€” not just the opt-in safe variant β€” need to notify the recipient.

FAQ​

What's the difference between ERC20 and ERC721?​

ERC20 is fungible β€” balances are interchangeable. ERC721 is non-fungible β€” each tokenId is unique and individually owned via ownerOf(tokenId). See What is ERC20? for the fungible counterpart.

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

ERC-721A is Azuki's gas-optimized implementation of ERC721 β€” same external ABI, same selectors, internal batch-mint optimizations. It reduces mint gas but doesn't change tokenURI, doesn't add transfer hooks, and doesn't fix the approval model.

Can I migrate an ERC721 collection to LSP8?​

Yes β€” the ownership model is the same, so migration is contract-level. See the full migration guide.

Related reading: ERC721 vs LSP8 Β· ERC721's dynamic metadata problem Β· ERC721's opt-in safe transfer Β· ERC721 token ID limits