comparison · spec-grade diff

ERC-721 vs LSP·8 Ethereum/EVM standard comparison

ERC-721 standardized NFT ownership. LSP8 keeps the ownership model, then upgrades token IDs to bytes32, replaces tokenURI with typed per-token data, and routes every transfer through LSP1.

Spec diff.

ERC-721 LSP·8
token ID type uint256 bytes32
id semantics numeric / counter number, hash, serial, encoded reference
metadata pointer tokenURI(id) → string getDataForTokenId(id, key) → bytes
dynamic updates MetadataUpdate event (ERC-4906) — signal only setDataForTokenId — typed write
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
off-chain integrity trust the host VerifiableURI option in LSP4
per-token data writes external — server / proxy / extra mappings setDataForTokenId — typed, on-chain, permissioned
interface ABI ERC-721 selectors — consumed by code that hardcodes them different selectors — distinct dispatch from ERC-721

How they differ structurally

ERC-721 is ownerOf(tokenId) plus a transfer event, with tokenURI(tokenId) returning a string. Everything else — dynamic metadata, receiver behavior, marketplace cooperation — is convention layered on top.

LSP8 keeps ownerOf-equivalent behavior but widens the token ID to bytes32 and replaces tokenURI with a typed per-token key-value store. Want a content hash as the ID? Store the hash. Want per-token attributes that change at mint, on reveal, on a game event? Set the data key, gated by whatever permissions you’ve configured on the asset contract.

What changes for downstream code

LSP8 publishes different function selectors. A contract that calls IERC721(token).transferFrom(...) or reads tokenURI(id) won’t accidentally hit LSP8 — the ABIs are disjoint by design. That’s a feature: it stops legacy code from misinterpreting an LSP8 asset as an ERC-721 and treating its data incorrectly. The cost is real: anything you build against LSP8 has to call its functions explicitly.

bytes32 is the small detail that matters

A uint256 token ID is fine when IDs are sequential counters. The moment IDs need to mean something (a hash of mint inputs, a serial keyed to off-chain state, a reference encoded with its provenance), uint256 forces a side mapping. bytes32 makes the ID itself the data structure.

Read the source.