Documentation
How it works on Ethereum.
- Overview
- Smart Contracts
- Claiming
- Onchain Rendering
- Trait Rules
- Existence Check
- Treasury
- Ownership
- Existence Toggle
- Improvenance
- Source of Truth
- Verification
Overview
This Punk Does Not Exist (TPDNE) is a fully onchain ERC-721 that generates CryptoPunk trait combinations not found in the original 10,000 collection. Same pixel art, same palette, same rendering — just combinations that were never claimed.
Every punk is generated and rendered entirely onchain. No IPFS, no external hosting, no off-chain metadata. The SVG image and JSON attributes are constructed in Solidity from stored pixel data and returned as base64 data URIs.
Token IDs start at 10000, continuing where the original collection ends.
TPDNE launches in Does Not Exist mode: generated onchain, visually absent. See Existence Toggle.
Smart Contracts
The system is two contracts on Ethereum mainnet:
ThisPunkDoesNotExist.sol
The ERC-721 token. Handles claiming, random generation, duplicate checking,
and treasury management. Stores each punk as a packed
bytes8 value.
PunkRenderer.sol
The rendering engine. Stores the full 120-color RGBA palette and pixel data for all 11 base types and 87 traits (male and female variants). Composites layers with alpha blending and outputs SVG.
The renderer is sealable. Once sealed, no pixel data, palette, or trait metadata can be changed. The art is permanently frozen onchain.
Claiming
Claiming is a single transaction. You call mint(count) with the
protocol fee, and the contract generates a random punk directly from
block.prevrandao, your address, and the current supply.
You don't choose your punk's traits. The blockchain does.
Generation
For each claim, the contract:
- Picks a random base type (0–10): Male 1–4, Female 1–4, Zombie, Ape, or Alien
- Rolls a trait count using a cumulative distribution that mirrors the original collection (most punks have 2–3 traits, very few have 0 or 7)
- Picks random categories via Fisher-Yates shuffle (hair, eyes, mouth, etc.)
- Selects a random trait from each chosen category's pool
- Checks the combo doesn't duplicate a real CryptoPunk or a previously claimed TPDNE
If the generated combo already exists, the contract retries (up to 10 attempts per claim). Batch claiming supports up to 20 punks per transaction.
Trait Count Distribution
The CDF used for trait count selection (roll is a random uint8, 0–255):
Traits CDF Approx. probability 0 1/256 0.4% 1 9/256 3.1% 2 100/256 35.5% 3 215/256 44.9% 4 251/256 14.1% 5 254/256 1.2% 6 255/256 0.4% 7 256/256 0.4% (roll = 255 only)
This is a byte-quantized approximation of the observed original distribution. Counts 1–4 match closely. At the tails, the smallest non-zero slot in a single-byte CDF is 1/256, so 0-trait and 7-trait punks each get ~0.4% despite different observed rates in the original 10k (8 zero-trait punks vs one 7-trait punk, #8348). The original generation process is undocumented; this CDF is reverse-engineered from the observed trait-count histogram, not from the original Larva Labs code.
Storage Format
Each punk is packed into 8 bytes. Byte 0 encodes both the base type (bits 0–4) and trait count (bits 5–7). Bytes 1–7 store sorted trait indices.
bytes8: [(traitCount << 5) | baseType] [trait0] [trait1] ... [traitN] [0x00...]
Onchain Rendering
tokenURI() returns a fully self-contained
data:application/json;base64 URI containing the name, description,
attributes, and a data:image/svg+xml;base64 image.
Pixel Encoding
Pixel data uses the same 3-byte-per-block encoding as the original CryptoPunks. Each block covers a 2×2 pixel region:
- Byte 0: Position — high nibble = block X, low nibble = block Y
- Byte 1: Palette color index (0–119)
- Byte 2: Bitmasks — high nibble = color mask, low nibble = black mask
Each bit in the mask maps to a sub-pixel: bit 0 = (0,0), bit 1 = (0,1), bit 2 = (1,0), bit 3 = (1,1).
Compositing
Layers are composited bottom-to-top using the standard alpha "over" operator. The base type is drawn first, then each trait layer is composited on top in the canonical order defined by punksdata.eth.
Layer Order
Every trait has an asset ID. Traits are sorted by asset ID ascending (lower ID = rendered first = further back). Categories are interleaved across the ID range, so the effective render order from back to front is:
Every trait shown as pixel art, grouped by category in render order
(back to front). Hover for names. Render order verified pixel-by-pixel
against all 10,000 original CryptoPunks via
indexedPixelsOf() on punksdata.eth.
See methodology below.
Where layers overlap
Most trait pairs have no pixel overlap at all. Where they do, the rendering order is verified against real CryptoPunks:
| Front | Back | Real punk |
|---|---|---|
| Beard | Chain (neck) | Big Beard covers Gold Chain on #2239, #4644 |
| Mouth | Beard | Cigarette shows over Luxurious Beard on #2886 |
| Mouth | Lips | Medical Mask covers Purple Lipstick on #2187 |
| Hoodie (hair) | Beard | Hoodie covers Big Beard on #1644, #8515 |
| Hoodie (hair) | Earring (ears) | Hoodie covers Earring on #87 |
| Glasses / Shades (eyes) | Medical Mask (mouth) | 3D Glasses over Medical Mask on #1123, VR on #9636 |
| Glasses / Shades (eyes) | Hoodie (hair) | Regular Shades over Hoodie on #87 |
| Clown Nose | Eyes / Mouth | Clown Nose over VR on #2766, over Medical Mask on #4173 |
| Vampire Hair | Beard | Vampire Hair covers Muttonchops on #236, Chinstrap on #1425 |
| Purple Hair | Beard | Purple Hair covers Big Beard on #1127, #9054 |
| Beard | Expressions (Buck Teeth, Frown, Smile) | Normal Beard hides Buck Teeth on #998, Handlebar hides Frown on #1303 |
| Mouth | Mole / Rosy Cheeks / Spots | Medical Mask covers Rosy Cheeks on #3871, Spots on #7360 |
Palette
TPDNE’s renderer stores a 120-color RGBA palette (480 bytes): 105 fully opaque and 15 semi-transparent. These are the colors referenced by individual trait pixel layers (accessories, hair, facial features, etc.).
punksdata.eth exposes a larger 222-color palette via paletteRgbaBytes().
The additional 102 colors are used in punksdata.eth’s pre-composited
indexedPixelsOf() output, which flattens all layers (base type skin,
accessories, alpha-blended results) into a single indexed image. TPDNE doesn’t
need those extra colors because it composites from individual trait layers at render
time, not from pre-composited pixel data.
Trait Rules
TPDNE generates only trait combinations that could have existed in the original collection. Each base type has specific trait pools based on what appears on real punks of that type:
| Type | Categories | Count |
|---|---|---|
| Male (1–4) | Hair, Eyes, Mouth, Face, Expression, Beard, Neck, Ears | 8 |
| Female (1–4) | Hair, Eyes, Mouth, Face, Lips, Neck, Ears | 7 |
| Zombie | Hair, Eyes, Mouth, Face, Expression, Beard, Neck, Ears | 8 |
| Ape | Hair, Eyes, Mouth, Neck, Ears | 5 |
| Alien | Hair, Eyes, Mouth, Ears | 4 |
Category Exclusivity
Within each category, a punk can have at most one trait. This mirrors the original collection: you can't have two hairstyles, or two pairs of eyewear.
Expression vs. Mouth: In the original collection, expression traits (Buck Teeth, Frown, Smile) and mouth accessories (Cigarette, Medical Mask, Pipe, Vape) are separate categories. A punk can have both a Frown and a Cigarette — 83 real CryptoPunks do. TPDNE preserves this distinction.
Type-Specific Pools
Not every trait can appear on every type. For example, Aliens only have 7 hair options, 2 eye options, and 2 mouth options. Apes can't have beards. These restrictions are hardcoded in the contract and verified against punksdata.eth bitmaps.
Existence Check
Before claiming, the contract verifies the generated combination doesn't exist in the real 10k collection. This uses punksdata.eth trait bitmaps directly onchain.
The check ANDs together bitmap words for:
- The head variant (which base type the punk is)
- The attribute count (how many traits)
- Each individual accessory
If any word in the AND result is non-zero, at least one real CryptoPunk has that exact combination of type + count + accessories. The combo is rejected and the contract retries.
Previously claimed TPDNE combos are tracked separately via a
usedCombos mapping to prevent duplicates within the collection.
Treasury
100% of claim fees go to the contract itself, forming a shared treasury. The treasury's purpose is to buy real CryptoPunks (both V1 and V2) on behalf of all TPDNE holders.
Protocol Fee
0.000404 ETH per claim. The owner can adjust this, capped at
a maximum of 0.01 ETH.
Permissionless Buying
Anyone can trigger a treasury punk purchase — no admin key required. Both V2 (CryptoPunksMarket) and V1 (Jalil's trustless PunksMarket) are supported. The caller specifies slippage protection parameters to prevent front-running:
buyV2Punk(punkIndex, maxPrice, expectedSeller)— reverts if the listing price exceedsmaxPriceor the seller changedbuyV1Punk(punkId, maxPrice)— sendsmaxPriceto PunksMarket, refund returns to the contract
The contract records the total cost (purchase price + brokerage) as the punk's cost basis. This is used to enforce profitability on sales.
Brokerage
A 10% brokerage on purchases goes to the fee recipient. For V2 buys, brokerage is computed on the listing price. For V1 buys, brokerage is computed on the actual cost (balance before − balance after), handling any refunds from the marketplace. The brokerage is permanently disableable onchain.
Owner-Only Selling
Only the contract owner can sell treasury punks, and only by accepting existing bids — the contract cannot list punks for sale or set prices. Every sale enforces profitability: the bid must be at least the punk's cost basis.
acceptV2Bid(punkIndex)— accepts the current highest V2 bid, using cost basis as the minimum priceacceptV1Bid(bidId, punkId, expectedWei)— accepts a specific V1 bid, reverts ifexpectedWei < costBasis
This design means the treasury can never sell a punk at a loss. Punks go in, and they only come out at a profit.
Ownership
The contract has a single owner address that controls admin functions:
toggling collection visibility (existence toggle),
accepting bids on treasury punks, adjusting the protocol fee, changing the fee
recipient, disabling brokerage, and updating the renderer contract.
The owner cannot claim, cannot access the treasury ETH directly, and cannot list punks for sale. Buying punks is permissionless — anyone can trigger it.
Ownership transfer is two-step: transferOwnership() sets a pending
owner, then acceptOwnership() must be called by the new address to
complete the transfer. This prevents accidental loss of ownership to a wrong
address. The modular upgrade path: start with an EOA, transfer to a multisig
(e.g. Safe), then to a full DAO (e.g. OZ Governor with timelock) as the
community grows. No custom governance code needed — battle-tested
infrastructure handles it.
Existence Toggle
TPDNE launches in Does Not Exist mode: every punk is generated and stored onchain exactly as described above, but token images render only as the CryptoPunks blue background. No trait layers. No improvenance pixel. The metadata name reads "Punk #10000 Does Not Exist".
This Punk Does Not Exist until permission does.
The contract has a global doesNotExist boolean (default true)
and two owner-only functions:
punkDoes()— setsdoesNotExist = false. The collection renders visible punks with all trait layers, the improvenance pixel, and a strikethrough name: "Punk #10000 Does ̶N̶o̶t̶ Exist".punkDoesNot()— setsdoesNotExist = true. The collection reverts to blue-background-only rendering.
This is collection-level, not per-token. Holders cannot control reveal. The owner controls whether the punks exist or do not. Because it uses the same ownership model, control can be transferred to a Safe, DAO executor, or timelock.
Claiming, generation, storage, existence checks, trait rules, treasury logic, and token IDs are completely unaffected by the toggle. Everything is generated and committed onchain regardless of visibility state.
Improvenance
Every TPDNE punk carries a single-pixel marker at position (23, 23) —
the bottom-right corner of the 24×24 grid. The pixel is
rgba(160, 80, 80), a muted red that's invisible when cropped
as a PFP but visible in the raw image.
This is intentional. It's a provenance marker that distinguishes generated punks from real CryptoPunks at the pixel level, while being practically invisible in everyday use.
Source of Truth
All trait rules, pixel data, and existence checks are verified against
punksdata.eth
(0x9cF9C8eA737A7d5157d3F4282aCe30880a7A117C), the sealed onchain
data surface for CryptoPunks. It is the single source of truth for:
- Trait names and IDs (111 traits: 5 types, 11 head variants, 8 attribute counts, 87 accessories)
- Per-type trait pools (which accessories can appear on which punk type)
- Trait bitmaps (which punks have which traits, used for existence checking)
- Pixel art data and the full 222-color composited-pixel palette
punksdata.eth is sealed and immutable. No one can modify the data it serves. TPDNE's trait pools, rendering order, and existence checks are all verified against it — see Verification below.
Verification
The original CryptoPunks have no documentation for their compositing rules. Trait layering order, category groupings, and special-case behaviors were reverse-engineered by comparing our renderer's output against the canonical pixel data stored onchain in punksdata.eth.
10,000 / 10,000 pixel-perfect
A verification script renders every one of the original 10,000 CryptoPunks
through our renderer and compares the result pixel-by-pixel against
indexedPixelsOf() on punksdata.eth. The canonical data is
fetched via Multicall3 in batches of 50, converted from palette indices
to RGBA, and compared against our composited output for all 576 pixels
per punk.
The result is a 100% match — zero pixel discrepancies across all 10,000 punks. This confirms that our layer compositing order, alpha blending, and pixel data are identical to the originals.
How the rules were determined
The layering rules were discovered iteratively. The initial approach (sort by asset ID only) produced 139 mismatched punks. Each batch of mismatches revealed a pattern — for example, "all 53 punks with beard + expression are wrong" pointed to expressions needing to render under beards. After each fix, the full 10k sweep was re-run to confirm the fix didn't introduce regressions. The final rule set:
| Priority | Category | Notes |
|---|---|---|
| 0 | Face | Mole, Rosy Cheeks, Spots (skin-level, under everything) |
| 1 | Ears | |
| 2 | Hair | Most hair traits |
| 3 | Neck | Chains |
| 4 | Lips | |
| 5 | Expressions | Buck Teeth, Frown, Smile (hidden by beard) |
| 6 | Beard | |
| 7 | Hair (special) | Hoodie, Purple Hair, Vampire Hair (cover beard area) |
| 10 | Mouth | Cigarette, Medical Mask, Pipe, Vape |
| 12 | Eyes | Glasses and shades render on top of mask and hoodie |
| 13 | Clown Nose | Renders over everything including eyes |
This priority table is implemented identically in three places: the
JavaScript renderer (renderer.js), the Solidity renderer
(PunkRenderer.sol), and the verification script. A
consistency
check confirms all three return the same priority for every trait.
Running the verification
# Full 10k pixel comparison (requires Ethereum RPC) node scripts/verify-layering.js # Single punk node scripts/verify-layering.js --punk 4173 # Offline consistency checks (no RPC needed) node scripts/verify-consistency.js # Solidity unit tests forge test # Solidity fork tests against mainnet punksdata.eth forge test --match-contract Verification --fork-url $RPC_URL