Skip to main content

On-chain art

The single biggest improvement over the original Fantums is that the art lives on-chain. No API. No IPFS pin. No server to go 502. When you call tokenURI(uint256), the contract assembles an SVG from on-chain bytes and base64-encodes it inline. Marketplaces read the bytes and render them. Forever.

The 502 lesson

The original Fantums collection stored only a per-token Keccak hash and a base URI of https://api.fantums.com/token/. Image rendering happened on a server. When the server died — and it did, and it has been returning 502 across all 10,860 tokens for months — the art effectively disappeared from the network. Of 10,860 PNGs, only 7 were ever publicly cached by Wayback Machine. The collection's visual identity, from a marketplace's point of view, was erased.

We don't repeat that mistake.

Pattern A — what we shipped

We considered two patterns:

Pattern A (cheap, recommended, shipped) Store per-tier SVG templates in the contract as immutable bytes. Per-token traits (archetype, fluro variant, OG badge, hat/cape/item) pack into 4 bytes inside the existing Fantums struct. tokenURI() assembles the SVG from the template and trait inputs, base64-encodes it inline.

Pattern B (full on-chain pixel grid) Store the entire 32x32 pixel grid per token. ~1 KB per token × 10,860 = ~11 MB of contract storage. At Sonic gas rates this is $50k-$100k one-time. Too expensive for marginal gain over Pattern A.

Pattern A is the call. Templates live in contracts/lib/FantumRenderer.sol. Trait sprites live in a small lookup library next to it.

Gas math

  • Mint hot path: unchanged. No extra writes happen at mint. The renderer reads from immutables.
  • Read path (tokenURI): ~30k extra gas per call vs a static URI. Paid by the caller (marketplace, indexer), not the user.

The trade-off is: every tokenURI call burns 30k more gas, in exchange for the collection never disappearing. Worth it.

What's stored on-chain per token

FieldBytesPurpose
archetype1Which of the 19 archetypes
tier1Common / Uncommon / Rare / Epic / Legendary
fluroVariant1Pumpkin / Pink / Blue / Green / Orange / Phantom / Holo / two-tone codes
traits4Packed hat / cape / item / face-mod indices
isOGbitSet true if minted via legacy window
legacyDna32Ancestor FUM hash. Immutable. Forever.

Combat stats (str/dex/con/int/wis/cha + weapon + ELO) live in a separate slot for fast read in Duel.sol. They're also fully on-chain.

What's in the templates

Each rarity tier has one SVG template baked in as immutable bytes:

bytes private constant TEMPLATE_COMMON = hex"..."; // Pumpkin Gold base
bytes private constant TEMPLATE_UNCOMMON = hex"..."; // Fluro Blue base
bytes private constant TEMPLATE_RARE = hex"..."; // Fluro Green base
bytes private constant TEMPLATE_EPIC = hex"..."; // Fluro Pink base
bytes private constant TEMPLATE_LEGENDARY = hex"..."; // Multi-variant template

The templates contain slots — {TRAIT_HAT}, {TRAIT_CAPE}, {TRAIT_ITEM}, {TRAIT_FACE} — that the renderer substitutes with the per-token trait sprites.

Trait sprite library

Trait sprites live in a small library indexed by traitId:

CategoryCountExamples
Hats12top hat, beret, headphones, crown, halo, monk hood, horns, opera flower, bandana, jester cap, ringmaster, hood
Capes8velvet red, fur-lined, electric cyan, vinyl black, gold leaf, jester harlequin, opera tails, none
Held items12candelabra, microphone, dagger, scroll, music sheets, mask, torch, wand, vinyl disc, magnifying glass, baton, palette
Face mods8scar, eyepatch, monocle, glasses, blindfold, none, etc.

Each sprite is a tiny byte string (a few dozen bytes) — they pack tightly into the library's bytecode.

The DNA hook

Every OG Reborn token has a non-zero legacyDna field. The renderer reads it and surfaces it as a trait:

Descendant of Fantum #146
DNA: 0x000000926c16...

The ancestor's tokenId is derivable from the hash by looking up the published data/fantum-snapshot/dna.json. Marketplaces with rich-trait support display this; basic marketplaces show it as a normal trait.

What this means for the holder

  • Your Reborn cannot disappear from a marketplace because we hosted an image server.
  • Your Reborn renders identically on every marketplace that supports tokenURI SVG rendering (which is all of them).
  • Off-chain rendering services can build cached versions, but those caches are decoration — the canonical art is on Sonic forever.

See also


Last updated: 2026-05-21