Skip to main content

Make NFT Metadata Verifiable, Not Just Hosted

ERC-721's tokenURI(tokenId) returns a single string, and the contract has no opinion about what that string points to, who can change it, or whether a marketplace's cached copy is still accurate. LSP8 replaces the single URI with typed ERC725Y key-value storage per token: getDataForTokenId(tokenId, key) returns the raw bytes stored under that key, which a client decodes against the key's declared LSP2 schema, and LSP4's VerifiableURI type pairs an off-chain pointer with an on-chain hash, so a client can detect a silent swap of the underlying file instead of trusting it blindly.

Before / after​

PropertyERC-721 tokenURILSP8 + LSP4
Storage shapeone string per tokentyped ERC725Y key-value pairs per token
Integritynone β€” trust whoever hosts the JSONVerifiableURI carries an on-chain hash of the payload, checked client-side β€” the chain itself never fetches or validates the remote file
Update signalERC-4906 MetadataUpdate event (says something changed, not what)a setDataForTokenId call, readable directly on-chain
Update authorizationcontract-specific, often owner-onlyownership-based by default (the token contract's owner()) β€” LSP6-controller scoping only applies if that owner is itself an LSP0 account with a Key Manager attached
Read patternfetch the URI, fetch the JSON, trust the cachegetDataForTokenId(tokenId, key) returns raw bytes, decoded client-side, hash-checkable via VerifiableURI

Why one cached string can't carry dynamic state​

tokenURI(tokenId) returns one string, and the contract has no opinion on what changes, when, or by whom. If the metadata is meant to be dynamic, the actual truth lives wherever the JSON is hosted β€” and every marketplace, wallet, and explorer caches its own copy on its own schedule. "Refresh metadata" buttons exist precisely because there's no standard signal for what changed; ERC-4906 added a MetadataUpdate event, but consumers still have to go ask the server what the new state actually is.

What people try today​

Mutable IPFS pointers are convenient, but the JSON is still off-chain and trust-anchored to whoever holds the ability to repin the CID. Server-rendered metadata APIs work at scale but couple the NFT permanently to the issuer's infrastructure β€” the collection falls over when the server does. ERC-4906's MetadataUpdate event signals that something changed without saying what changed or carrying the new state. Fully on-chain SVG generators give the strongest integrity guarantee, but are expensive and awkward for rich media β€” a good answer for a narrow set of collections, not a general one.

How LSP8 and LSP4 make metadata a verifiable graph​

LSP8 assets store per-token metadata through ERC725Y data keys instead of a single tokenURI string. LSP4 defines the metadata conventions β€” name, symbol, JSON schema β€” and its VerifiableURI type lets a key point to an off-chain payload with a hash, so a client can verify that payload against the on-chain hash rather than trusting it outright β€” the chain stores and exposes the hash, but never fetches or enforces the remote file itself. Apps read getDataForTokenId(tokenId, key) and get the raw bytes back, decoded against the key's LSP2 schema. Dynamic updates go through setDataForTokenId, gated by the token contract's own ownership model β€” LSP6 scoping applies only when that owner is deliberately set up as an LSP0 account with a Key Manager, not automatically. The metadata stops being a URL to trust and becomes a typed key-value graph you can verify.

Verifiable beats cached

For any collection where metadata legitimately changes after mint, LSP8's VerifiableURI gives consumers a hash to check against client-side β€” instead of asking them to trust that the cache is current.

Related reading: ERC-721's safe transfer gap Β· ERC-721 token ID limits Β· Building dynamic NFTs Β· ERC721 vs. LSP8