MANDALIA
10,000 deterministic radial compositions. State-derived from a bytes32 seed plus a uint16 transfer counter. SVG is synthesized inside tokenURI() on every eth_call — no IPFS, no metadata server, no off-chain compute, no proxy. Renderer is upgradeable; storage is immutable.
Your wallet is the artisan.
Every transfer is a new ring.
Pure Solidity. No IPFS. No servers.
The code is the art.
Once deployed, immutable.
The mandala never stops growing.
Lineage
Built in the lineage of Heraldia.
Heraldia pioneered fully onchain SVG evolution on Ethereum mainnet — a 10,000-piece collection where each token mutates on every transfer. Mandalia is its spiritual successor. Same chain. Same standard. Same conviction in permanence.
Heraldia holders receive tiered free claims as recognition of the path they laid:
1–4 held → 1 free · 5–9 held → 3 free · 10+ held → 10 free
Eligibility verified via Merkle snapshot taken before launch. No live-balance gaming.
Live Preview
Click any cell to regenerate. Output identical to what the deployed renderer produces — same PRNG, same palettes, same pipeline.
Top 10 Holders
Live ranking from chain state. Refreshes every minute.
| Rank | Wallet | Holding |
|---|---|---|
| Loading… | ||
Transfer Simulator
Input a token ID to see how it would mutate after 50 more transfers. Current state vs simulated future, rendered live from the contract.
How It Works
Three states. One contract. Forever onchain.
A 32-byte seed is derived from keccak256(prevrandao, sender, tokenId) at mint. The seed is the genome — palette, symmetry, motif family, remate, bindu. All determined. All immutable.
The contract increments a per-token counter on every transfer. EIP-4906 is emitted; marketplaces refresh the rendered image. No oracle, no off-chain trigger.
Eight logarithmic stages. Density grows, accent bands appear, satellite constellations bloom. The piece remembers every owner. At stage seven, it reaches its final form.
Architecture
Three-contract topology. Renderer is mutable until locked. Storage is single-purpose.
┌──────────────────────────────┐
│ NFT.sol (ERC-721/2981/4906)│ ───┐
│ mint · transfer hook │ │ delegates tokenURI
│ tokenURI → delegate │ ↓
└──────────────┬───────────────┘ ┌──────────────────────────┐
│ writes │ Renderer.sol │
│ seed + tx counter │ render(seed, txCount) │
↓ │ Solady DynamicBuffer │
┌──────────────────────────────┐ │ asm hot paths · cos LUT │
│ Storage.sol │ ←─┤ data:image SVG URI │
│ bytes32 seed + uint16 txc │ └──────────────────────────┘
└──────────────────────────────┘
NFT Contract
Standard ERC-721 with _update() override emitting MetadataUpdate per EIP-4906 and incrementing the per-token transfer counter via the storage contract.
Renderer Contract
Pure view contract. Single entry render(bytes32, uint16). Solady DynamicBuffer for sub-linear concat. Critical paths inlined in assembly.
Storage Contract
Two mappings: seedOf(uint256) and transferCountOf(uint256). Write authorization restricted to the NFT contract — set at deploy, immutable.
Why Split?
Renderer is heavy (~20 KB bytecode). Bundling would push past EIP-170 24 KB limit. Splitting also enables renderer patches via setRenderer() until locked.
Rendering
Deterministic SVG synthesis from a 32-byte seed. Output is data:application/json;base64,<…> with embedded SVG.
// NFT.sol function tokenURI(uint256 id) external view override returns (string memory) { bytes32 seed = storage_.seedOf(id); uint16 txc = storage_.transferCountOf(id); return renderer.render(seed, txc, id); }
PRNG
Mulberry32 ported to Solidity (uint32 arithmetic). Sub-seeded per ring. Bit-exact match with JS reference.
SVG Construction
Direct byte concat via Solady's DynamicBuffer — preallocates 2 KB, doubles on demand. Avoids quadratic native concat cost.
Symmetry via Transforms
SVG transform="rotate()" instead of trig in Solidity. Each motif drawn once at (0,−r) and rotated N times.
Determinism
No block.timestamp, no blockhash. Identical bytes from genesis until heat death. keccak256(render(seed, txc)) is constant per input.
Evolution
Transfer hook increments a counter; counter feeds back into the renderer. Eight visual stages on a log curve — denser motifs, accent bands, satellite constellations.
| Stage | Transfers | Visual Additions |
|---|---|---|
| 0 | 0 | Genesis composition |
| 1–3 | 1–40 | Motif density × 2–4 · inner slot echo lines |
| 4 | 41–80 | Saturated density · 4 echo lines · 40% stroke weight |
| 5 | 81–200 | + thick inter-slot separators |
| 6 | 201–340 | + satellite constellations between rings |
| 7 | 341+ | + alternate-ring accent banding · final form |
Traits
11 traits — 8 DNA (immutable) and 3 state-derived (mutable on transfer).
Frequently Asked
If something isn't here, the answer is probably "read the contract."
tokenURI() on every call. No IPFS, no Arweave, no proxy server. Pull the contract source from Etherscan, run eth_call against any node — the image is reproducible bit-for-bit forever.lockRenderer() is called. Intent: launch, validate, lock irreversibly. After locked, rendering logic is immutable forever.25. A 10-Heraldia holder could claim 10 free and mint 15 paid, hitting the cap at 25 total.0.0001 ETH per token (~$0.20). Plus network fees, which vary. Batch minting amortizes fixed transaction overhead.25 per wallet, counting both paid and free claims. Per-transaction cap is also 25. Cap is enforced on the receiver, so airdrops don't bypass it.tokenURI(id) on the contract gets the full SVG as a data URI. Frame it in a hardware wallet, print it on a tote, mount it in an LED wall.