PermanentCollectionMosaicRenderer
0x40aa9edb0063ca5d12eef8A53be45F7e15a3Fe10 · view on evm.now · deployed at block 25270161
The shipped renderer, installed behind RendererRegistry. It composes
the artwork as a true mosaic of the actual collected CryptoPunks: an
11×10 grid of 24×24 trait cells plus one pulled-out "final type" cell
beneath the grid, on a square 356×356 canvas, entirely on-chain SVG.
Every cell reads live protocol state (collectedMask, pendingMask,
firstVaultedPunk from PermanentCollection) and renders one of three
states: collected (the vaulted Punk's pixel tile on a #8F918B swatch),
pending (the uncollected look with a 1-px dashed border, meaning a return
auction is live for that trait), or uncollected (a flat dim cell with the
trait's icon).
It also serves every JSON metadata envelope in the protocol: the zero-arg
tokenURI() for the $111 ERC20, contractURI(address) for both the
ERC20 and the PunkVault collection page, and tokenURI(uint256) for the
PunkVault ERC721 tokens, where ids 0..110 are delegated to the dedicated
Proof renderer and id 111 renders the Vault Title here. The contract
holds no state of its own, has no admin, and moves no value; it's
replaceable via RendererRegistry.setImplementation until the registry
locks.
Concepts
The canvas
The design coordinate space is 356×356 (viewBox="0 0 356 356"): a
24-px outer pad around an 11×10 grid of 28-px cells (24-px tile + 4-px
gap). The root SVG's intrinsic width/height are emitted at 8× (2848)
so a right-click "Copy Image" rasterizes at print- and share-friendly
resolution; display sizing is unaffected since containers constrain via
CSS. The bottom 28-px slot holds a two-line pixel-font inscription on the
left (the "N / 111" progress line in dim #6a6a6a above the
"PERMANENT COLLECTION" headline in #f5f5f5) and the pulled-out cell on
the right at (306, 306). The pulled-out trait is trait 4, the Zombie
type; the other four types stay in the grid's last row.
Cell states
| State | Rendering |
|---|---|
| Uncollected | flat #1c1c1c cell + the trait's icon |
| Pending | the uncollected look + a 1-px dashed #454545 border (a return auction is live for a Punk targeting this trait) |
| Collected | #8F918B swatch + the first-vaulted Punk's pixel fragment |
Trait icons come in three visual families, matching the trait taxonomy: types and head variants draw a canonical exemplar Punk, attribute-count traits draw an N-of-7 dot strip, and accessories draw the pixel diff between a canonical Punk and its bare head-variant baseline (isolating just the accessory). Six rare-type trait ids (the Alien, Ape and Zombie types and their matching head variants) rotate their exemplar Punk per block, so those icons cycle across renders by design.
The two caches
Rendering is cache-first. Collected cells read the vaulted Punk's tile
from PunkSvgFragmentCache; uncollected and pending cells read the trait
icon from TraitIconCache. Both caches are public, immutable, and start
empty; anyone can pay gas to bake entries. When an entry isn't baked the
renderer falls through to on-the-fly computation from the sealed
PunksData contract and never reverts, so caching is a gas optimization
for eth_call consumers, not a precondition of metadata. The on-the-fly
bytes are byte-identical to the baked ones, an invariant enforced by
test. cacheTrait on this contract bakes a collected trait's Punk tile;
TraitIconCache.cacheTrait bakes trait icons.
Reading the artwork
# The raw mosaic SVG, no JSON envelope
cast call 0x40aa9edb0063ca5d12eef8A53be45F7e15a3Fe10 "svg()(string)" \
--rpc-url https://ethereum-rpc.publicnode.com
# The Vault Title metadata (base64 JSON data URI)
cast call 0x40aa9edb0063ca5d12eef8A53be45F7e15a3Fe10 "tokenURI(uint256)(string)" 111 \
--rpc-url https://ethereum-rpc.publicnode.comCanonical consumers go through RendererRegistry
(0x760421B7916917Ffd72ECeAa4c1F7ffC4D12eEc7), which forwards the same four views.
Write functions
cacheTrait
function cacheTrait(uint8 traitId) external returns (address pointer)Access: permissionless
Bakes the Punk tile for traitId's first-vaulted Punk into the public
PunkSvgFragmentCache, so future renders of that cell read one SSTORE2
pointer instead of recomputing from raw pixels. Looks up
collection.firstVaultedPunk(traitId) and reverts TraitNotCollected if
the trait hasn't entered the vault yet; otherwise forwards to
punkSvgCache.cachePunk(punkId) and returns the SSTORE2
storage-contract address holding the fragment. Anyone can call this once
a trait is collected; it's an off-chain catch-up activity that lowers
view gas, with no effect on rendered output.
Read functions
cachedPunkForTrait
function cachedPunkForTrait(uint8 traitId) external view returns (uint16 punkId, bool exists)Convenience wrapper around collection.firstVaultedPunk(traitId).
Returns the Punk responsible for the trait's entry into the collection
and an exists flag, or (0, false) if the trait is uncollected.
collection
function collection() external view returns (IPermanentCollectionForMosaic)The immutable PermanentCollection records core. Read for
collectedMask, pendingMask, collectedCount, isComplete, and
per-trait firstVaultedPunk on every render.
contractURI
function contractURI(address token) external view returns (string)ERC-7572 contract-level metadata. Returns the collection envelope, a
base64 JSON data URI with name "PERMANENT COLLECTION", the live mosaic
image, and Traits Collected / Traits Total / Punks Vaulted attributes.
Both the PunkVault collection page and the $111 ERC20 resolve through
this view, so the symbol field is keyed off the token argument:
"PERMANENTCOLLECTION" when token is the vault, "111" otherwise.
isTraitCached
function isTraitCached(uint8 traitId) external view returns (bool)True iff traitId is collected AND its first-vaulted Punk has a baked
fragment in PunkSvgFragmentCache. Returns false for uncollected traits
rather than reverting, so callers don't need an existence check first.
The value to read before deciding whether cacheTrait is worth calling.
proofRenderer
function proofRenderer() external view returns (IPCProofRenderer)The immutable PermanentCollectionProofRenderer address that
tokenURI(uint256) delegates Proof ids 0..110 to. Pinned at
construction; the on-chain way to discover the Proof renderer's address.
punksData
function punksData() external view returns (IPunksData)The immutable sealed PunksData contract, the pixel, palette, and trait
taxonomy source everything is drawn from. The same instance
PermanentCollection pins by dataset hash.
punkSvgCache
function punkSvgCache() external view returns (PunkSvgFragmentCache)The immutable public PunkSvgFragmentCache holding baked full-Punk SVG
fragments for collected cells.
svg
function svg() external view returns (string)The raw mosaic SVG string for the current chain state, no JSON envelope.
Reads collectedMask, pendingMask, and collectedCount and renders
all 111 cells plus the footer. Useful for off-chain tooling; the metadata
views embed this same image as a base64 data URI.
tokenURI
function tokenURI() external view returns (string)Zero-arg ERC20-flavored metadata, consumed by the $111 token's metadata
passthrough. Returns the same collection envelope as contractURI with
symbol "111": name "PERMANENT COLLECTION", the live mosaic image, and the
Traits Collected / Traits Total / Punks Vaulted attributes.
tokenURI
function tokenURI(uint256 id) external view returns (string)ERC721 metadata dispatch for PunkVault-issued token ids:
0 <= id <= 110: delegated toproofRenderer.tokenURI(id), the corresponding Proof (tokenId == traitIddirectly). RevertsProofNotMintedthere if that Proof hasn't been mintedid == 111: the Vault Title, rendered here. A base64 JSON envelope with the live mosaic image and Punks Vaulted / Traits Collected / Traits Total / Collection Complete attributesid >= 112: revertsUnknownTokenId
PunkVault.tokenURI gates on mint state before forwarding, so an
unminted but in-range id only reaches this dispatch when called directly
on the renderer or registry.
traitIconBytes
function traitIconBytes(uint16 traitId) external view returns (bytes)Inspection view exposing the exact per-cell trait-icon bytes the renderer
composes internally, so tests and off-chain tools can verify that
TraitIconCache's baked bytes match what this renderer would produce on
the fly. Takes traitId in 0..110 and reverts (with a require string)
otherwise. Fetches palette and head-variant baselines fresh on each call
so a single trait can be checked without rendering the whole mosaic. For
the six rotating rare-type traits the result depends on block.number.
traitIconCache
function traitIconCache() external view returns (TraitIconCache)The immutable public TraitIconCache holding baked trait-icon fragments
for uncollected and pending cells. Empty at deploy; permissionlessly
bakeable.
vault
function vault() external view returns (IPunkVaultForMosaic)The immutable PunkVault reference, read for lockedPunkCount when
building the Punks Vaulted attribute in the metadata envelopes.
Errors
TraitNotCollected(uint8 traitId)
cacheTrait was called for a trait with no first-vaulted Punk yet.
There's nothing to bake; wait until a Punk carrying the trait is vaulted.
UnknownTokenId(uint256 id)
tokenURI(uint256) was called with an id of 112 or higher. Only ids
0..110 (Proofs) and 111 (the Vault Title) exist; the revert is loud so a
stale marketplace poll can't silently receive wrong data.