Appearance
Metadata format
Resolving the URL
solidity
function tokenURI(uint256 tokenId) external view returns (string);tokenURI returns baseURI concatenated with the decimal token id, with no separator — which is why baseURI always ends in /.
Read baseURI() on-chain; never hardcode the host
baseURI is owner-updatable. A hardcoded host breaks silently — you keep fetching a stale origin and see no error, just old data. Call tokenURI (or read baseURI() once and cache it briefly).
tokenURI reverts for a token that does not exist, which includes burned tokens.
Document shape
json
{
"name": "AK-47 | Redline (Field-Tested)",
"description": "Tokenized CS2 Gun held in LootFi custody.",
"image": "https://community.cloudflare.steamstatic.com/economy/image/...",
"rarity": "Classified",
"wear": "Field-Tested",
"float": 0.2374091,
"paintSeed": 661,
"paintIndex": 316,
"fadePercentage": null,
"dopplerPhase": null,
"statTrak": false,
"souvenir": false,
"stickers": [
{ "slot": 0, "stickerId": 6689, "wear": 0.91, "name": "Sticker | Example", "imageUrl": "https://..." }
],
"steamAssetId": "52941493200",
"attributes": [
{ "trait_type": "Category", "value": "Gun" },
{ "trait_type": "Rarity", "value": "Classified" },
{ "trait_type": "Wear", "value": "Field-Tested" },
{ "trait_type": "Float", "value": 0.2374091, "display_type": "number" },
{ "trait_type": "Paint Seed", "value": 661, "display_type": "number" },
{ "trait_type": "Paint Index", "value": 316, "display_type": "number" },
{ "trait_type": "Stickers", "value": 1, "display_type": "number" },
{ "trait_type": "Steam Fingerprint", "value": "730:2:52941493200:1989338770:302028390" }
]
}Fields
| Field | Type | Notes |
|---|---|---|
name | string | Steam market hash name; falls back to LootFi #<id> |
description | string | Generated from the category |
image | string | null | Steam CDN URL |
rarity | string | null | e.g. Covert, Classified |
wear | string | null | Exterior name, e.g. Field-Tested |
float | number | null | 0–1 wear value |
paintSeed | number | null | Pattern index — 0 is valid |
paintIndex | number | null | Skin identifier |
fadePercentage | number | null | Fade skins only |
dopplerPhase | string | null | Doppler/Gamma Doppler only |
statTrak | boolean | |
souvenir | boolean | |
stickers | array | null | [] = inspected, none applied |
steamAssetId | string | Metadata only — never the token id |
attributes | array | Standard NFT traits; null-valued traits are omitted |
Semantics that break naive consumers
Three traps
nullmeans "not looked up yet", not "the item has none." Stat enrichment can only run once a custody account physically holds the item, so a freshly minted token often has nulls that fill in later.paintSeed: 0is a real seed, and some seed-0 patterns are valuable.if (paintSeed)silently drops them. Test!= null.stickers: []≠stickers: null. Empty array = inspected, no stickers. Null = not inspected. Rendering both as "no stickers" is wrong.
The same trap appears in the category ordinals — Gun is 0. Never test truthiness on protocol values.
Not every item has stats
Float, paint seed and stickers exist only for inspectable items — guns, knives, gloves. Graffiti, containers, capsules and music kits are not inspectable, so those fields stay null for them.
null on its own does not tell you which case you are in. It means this value is not present in the metadata, which covers both "this kind of item never has one" and "it has not been looked up yet". Do not infer absence from null, and do not cache it as final — an empty list (stickers: []) is the only positive statement that something genuinely has none.
Mutability
The document is mutable in one direction: stat enrichment can rewrite it after mint as data resolves. The item identity — which physical asset backs the token — never changes.
Practical guidance: cache with a short TTL and re-fetch a token whose stats are null. Do not cache a null-heavy document indefinitely and assume it is final.
Rendering suggestions
- Lead with
nameandimage; both are populated at mint. - Show
wear(the human exterior) next tofloat(the precise value). Traders want both;floatalone is unreadable to most users. - Surface
paintSeedfor knives and gloves — pattern hunters search on it. - Badge
statTrakandsouvenir; they materially affect value. - Hide a stat rather than rendering "null" or "0" when it is absent.