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.
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
Proof verified via Merkle snapshot taken before launch. No live-balance gaming.
Click any cell to regenerate that seed. Output is identical to what the deployed renderer will produce — same PRNG, same palettes, same composition pipeline.
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. Just the chain itself.
Eight logarithmic stages. Density grows, accent bands appear, satellite constellations bloom between rings. The piece remembers every owner. At stage seven, it reaches its final form.
Three-contract topology. Renderer is mutable on the NFT contract — enables visual patches without re-mint. Storage is single-purpose; reads from the renderer, writes only on transfer hook.
┌──────────────────────────────┐
│ NFT.sol │ ERC-721 · ERC-2981 · ERC-4906
│ mint() · transfer hook │ ───┐
│ tokenURI() → delegates │ │ delegates to renderer (mutable address)
└──────────────┬───────────────┘ │
│ ↓
│ writes ┌──────────────────────────────┐
│ seed + │ Renderer.sol │
│ tx counter │ render(seed, txCount) │
↓ │ Solady DynamicBuffer │
┌──────────────────────────────┐ │ asm hot paths · cos LUT │
│ Storage.sol │ ←─┤ returns full data:image URI │
│ mapping seed (bytes32) │ └──────────────────────────────┘
│ mapping txCount (uint16) │
│ ~34 bytes per token │
└──────────────────────────────┘
Standard ERC-721 with _update() override emitting MetadataUpdate per EIP-4906 and incrementing the per-token transfer counter via the storage contract. Renderer address is settable post-deploy by owner only.
Pure view contract. Single entrypoint render(bytes32, uint16) returns (string). Stateless. Built with Solady's LibString, Base64, and DynamicBuffer to keep concatenation costs sub-linear. Critical paths inlined in assembly.
Two mappings: seedOf(uint256) → bytes32 and transferCountOf(uint256) → uint16. Write authorization restricted to the NFT contract address (set at deploy, immutable). ~34 bytes per token; ~340 KB total for 10k supply.
Renderer is heavy (~20 KB bytecode). Bundling it in the NFT contract would push past the 24 KB EIP-170 limit. Splitting also enables future patches: redeploy a fixed renderer and call setRenderer(), no migration of existing tokens.
Deterministic SVG synthesis from a 32-byte seed. No raster, no off-chain proxy.
Output is data:application/json;base64,<…> with embedded
data:image/svg+xml;base64,<…>. Marketplaces parse it inline.
// 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); } // Renderer.sol — pseudo function render(bytes32 seed, uint16 txc) external view returns (string memory) { DynamicBufferLib.DynamicBuffer memory svg; svg.append('<svg viewBox="0 0 380 380" xmlns="http://www.w3.org/2000/svg">'); Composition c = decompose(seed, txc); // → palette, symmetry, rings, remate, stage drawBackground(svg, c); drawInnerRings(svg, c); // loops sym × rings, uses cosLUT drawRemate(svg, c); // scallop · pontos · liso drawBindu(svg, c); applyStageOverlays(svg, c); // log curve, gated by txc svg.append('</svg>'); return encodeMetadata(svg.data(), c); }
Mulberry32 ported to Solidity (uint32 arithmetic). Sub-seeded per ring via keccak256(seed, ringIdx) truncated to 32 bits. Bit-exact match with the JS reference implementation. Same seed → identical SVG bytes on every node, every call.
256-entry cosine lookup table in SSTORE2-stored bytes, Q16.16 fixed point. sin derived by offset. Avoids per-call fixed-point series. ~600 gas amortized per angular position vs ~8k for a Taylor expansion.
Direct byte concatenation via Solady's DynamicBuffer — preallocates 2 KB, doubles on demand, single mstore per write. Avoids the quadratic cost of native Solidity string concat. Final base64 encoding via inline assembly.
Pure functions only inside the renderer. No block.timestamp, blockhash, or external state reads. Identical bytes produced from genesis until heat death. SVG hash is provable: keccak256(render(seed, txc)) is constant per input.
Transfer hook increments a per-token counter. Counter feeds back into the renderer's composition pipeline. Eight visual stages on a logarithmic curve — denser radial motifs, accent bands, satellite constellations, alternate-ring color reinforcement. State-derived art; no signing oracle, no metadata mutation, no API refresh.
// NFT.sol — transfer hook function _update(address to, uint256 id, address auth) internal override returns (address) { address from = super._update(to, id, auth); if (from != address(0)) { storage_.incrementTransferCount(id); emit MetadataUpdate(id); // EIP-4906 → marketplace refresh } return from; }
| Stage | Transfers | Visual Additions |
|---|---|---|
| 0 | 0 | Base composition · genesis state |
| 1–3 | 1–40 | Motif density × 2–4 · inner slot echo lines |
| 4 | 41–80 | Saturated density · 4 echo lines per slot · 40% stroke weight |
| 5 | 81–200 | + thick inter-slot separators · accent reinforcement |
| 6 | 201–340 | + satellite constellations between rings (sym × 4 micro-dots) |
| 7 | 341+ | + alternate-ring accent banding · final stroke peak · 70% bolder |
Paste a wallet address (or any string). We seed a piece as if it were minted to that wallet, then render its three lives: at mint, after 50 transfers, and at the ancestral stage. This is exactly what the deployed contract will produce.
11 traits total — 8 DNA (immutable, set at mint) and 3 derived from chain state (mutable on transfer). Combinatorial space of DNA alone exceeds 600,000 unique configurations; with per-ring micro-decisions, visual variation is effectively unbounded.
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. The intent: launch with the renderer reviewed and tested, then lock irreversibly. Once locked, the rendering logic is immutable forever — same input always produces the same SVG bytes.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 with congestion. Batch minting amortizes the fixed transaction overhead — minting 25 at once is significantly cheaper per token than 25 single mints.25 per wallet, counting both paid mints and free claims combined. Per-transaction cap is also 25, so a wallet's entire allocation can be claimed in a single tx. 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 your piece in a hardware wallet display, print it on a tote bag, mount it in an LED wall. The renderer doesn't care.startTime passes. Watch the contract.