Documentation
There are millions of trait combinations not in the original 10,000. So many grails that will never exist. But what if they could? With permission of course!
- Overview
- Settlement Trigger
- Smart Contracts
- Claiming
- Treasury
- Deferred Reveal
- Onchain Rendering
- Dormant Renderer
- Trait Rules
- Existence Check
- Trust Model
- Improvenance
- Source of Truth
- Verification
Overview
This Punk Does Not Exist (TPDNE) is a fully onchain ERC-721 and capital-formation campaign on Ethereum. The stated goal is to acquire the CryptoPunks IP and release it into the public domain. The consolation outcome is a license or grant of permission — the right to exist without full ownership of the IP.
Both outcomes run through the same settlement mechanism: a one-time lump-sum payment for a perpetual result. The payment buys the right to exist; deal terms decide whether that’s full acquisition or a license.
100% of claim fees pool into a shared treasury. Contributions are fully refundable until settlement closes. Holders can burn their tokens at any time pre-settlement to recover 100% of what they paid. Every proposed settlement has a 7-day timelock — that window is the last chance to exit before funds are committed. If enough holders burn during the window, the deal reverts. If the campaign deadline passes without settlement, holders can still burn-for-refund.
Tokens launch as identical blue squares with no traits, no rendered art, and no per-token trait metadata. Trait generation and rendering are deferred unless and until settlement is accepted. Until then, the artwork is absence itself: a punk-shaped void on Ethereum.
If/when settlement succeeds, traits are 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 originals end.
See Settlement Trigger, Treasury, Deferred Reveal, and Dormant Renderer.
Settlement Trigger
TPDNE launches in Does Not Exist mode. All tokens render as blue squares. No trait layers, no art, no per-token metadata until settlement.
Settlement is a two-step process with a 7-day timelock:
nominateSettlement(recipient, amount)— owner-only. Proposes sendingamountETH torecipientin exchange for the right to exist — whether that’s full IP acquisition for the public domain or a license/permission grant. Starts a 7-day countdown. Can be overwritten with a new nomination at any time before acceptance (the overwrite resets the 7-day clock).acceptSettlement()— callable only by the nominatedrecipientafter the 7-day delay. Transfers the settlement funds, setsdoesNotExist = falseandsettlementAccepted = true. One-time, irreversible, lump-sum. No recurring payments.
The 7-Day Exit Window
Burn-for-refund is open at any time before settlement is accepted. But the 7-day timelock after a nomination is the critical window — the last opportunity to evaluate a proposed deal and exit with 100% of your contribution before funds are committed.
Any nomination must survive 7 days of holders being free to leave. If enough
holders burn during the window, the treasury balance drops below the settlement
amount and acceptSettlement() reverts — mass exit is a veto
on the deal.
After settlement, doesNotExist flips to false permanently,
enabling the reveal pipeline. Minting continues but is free (no fee charged
post-settlement). Burn-for-refund closes permanently — refunds are only
available pre-settlement.
Campaign Deadline
The campaign has a deadline set at deploy time (bounded between 180 days and 5 years). After the deadline, no new mints or settlement nominations can occur. The owner can extend the deadline (but never beyond the absolute maximum set at deploy). Burn-for-refund remains available after the deadline — holders can always exit.
Existence Control
Visual existence requires both doesNotExist == false (set by
settlement) and a renderer contract. The metadata name always reads
“Punk #[id] Does Not Exist” regardless of state.
A token can be revealed internally but still not visually exist if the renderer is unset. The owner can set and unset the renderer at any time, providing a second layer of control if the legal basis for rendering changes.
Smart Contracts
The system launches as a single contract on Ethereum mainnet, with the renderer deployed separately if/when needed:
ThisPunkDoesNotExist.sol
The ERC-721 token. Handles claiming, burn-for-refund, settlement trigger,
deferred reveal via blockhash commit-reveal, and campaign deadline enforcement.
Trait generation and duplicate checking happen at reveal time, not at claim.
Stores each punk as a packed bytes8 value. While the renderer
is unset (address(0)), tokenURI() returns the
blue-square state from an inline constant. Uses OpenZeppelin Ownable2Step
(two-step ownership transfer) and ReentrancyGuard.
PunkRenderer.sol
Not deployed at launch. The renderer is dormant infrastructure for a future reveal. It stores the full 120-color RGBA palette and pixel data for all 11 base types and 87 traits (male and female variants). If/when deployed and set, it composites layers with alpha blending and outputs SVG. The owner can set and unset the renderer address at any time.
The renderer is sealable. Once sealed, no pixel data, palette, or trait metadata can be changed. The art is permanently frozen onchain.
Claiming
Your token starts as a blue square — no traits, no image, no punk. Traits are generated onchain if/when settlement succeeds.
Claiming is a single transaction. You call mint(count) with the
protocol fee. Batch claiming supports up to 100 tokens per transaction.
All claimed tokens are identical: same blue square, same metadata,
differing only by token ID.
0.00404 ETH per claim. 100% goes to the shared treasury. Zero extraction — no operator fee, no brokerage. Every wei is either refundable to holders or allocated to settlement.
Your contribution is fully refundable until settlement closes. Burn your tokens anytime pre-settlement to recover 100% of what you paid. When a settlement is nominated, a 7-day window opens — your last chance to exit before funds are committed.
After settlement, claiming is free (no fee). Minting continues indefinitely post-settlement, expanding the collection with zero cost to new claimers. Post-settlement tokens have no contribution recorded and are not eligible for refund (since refunds close at settlement).
Storage Format
Each punk is packed into 8 bytes at reveal time. 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...]
Deferred Reveal
Traits are not generated at claim time. If/when settlement succeeds, they are generated onchain using a random seed derived from a blockhash commit-reveal. No third-party dependencies. This is a three-phase process:
Phase 1: Claim
You pay the protocol fee and receive a token ID. No punkData is
written. Your token renders as a blue square with the name
“Punk #[id] Does Not Exist”.
Phase 2: Seed Commitment
After settlement is accepted, the owner calls commitSeed()
which records block.number + 1 as the seed block. Once that block is
produced, anyone can call finalizeSeed() to store
blockhash(seedBlock) as the immutable reveal seed. The owner cannot
predict a future block's hash at commit time. The seed can never be changed once finalized.
Finalization must happen within 256 blocks (~50 minutes). If the window is missed,
the owner can recommitSeed() to try again.
Phase 3: Reveal
Once the seed is committed, anyone can call reveal(tokenIds) to
generate traits for a batch of up to 10 tokens. This is permissionless —
the caller pays gas, but any address can reveal any token. For each token, the
contract:
- Derives per-token randomness:
keccak256(revealSeed, tokenId, nonce, attempt) - Picks a weighted random base type (0–10): Male 1–4, Female 1–4, Zombie, Ape, or Alien — probabilities match the original 10k distribution
- 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 any of the originals or a previously revealed TPDNE
If the generated combo already exists, the contract retries (up to 10 attempts per token). If all attempts collide, the token’s nonce is incremented and it can be retried later — the next attempt explores an entirely fresh set of combinations.
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.
Base Type Distribution
Base types are selected via a weighted CDF matching the original 10k distribution:
Type CDF Approx. probability Male 1 44/256 17.2% (1723 in originals) Male 2 91/256 18.4% (1857) Male 3 139/256 18.8% (1861) Male 4 154/256 5.9% (598) Female 1 182/256 10.9% (1101) Female 2 212/256 11.7% (1174) Female 3 241/256 11.3% (1145) Female 4 252/256 4.3% (420) Zombie 254/256 0.8% (88) Ape 255/256 0.4% (24) Alien 256/256 0.4% (9, fallthrough)
Alien and Ape have ~0.4% each (1/256, the minimum non-zero slot in a byte CDF), which slightly overrepresents them vs the originals (0.09% and 0.24%). All other types match within 0.2%.
Why Blockhash
No external dependencies. No oracles. No LINK tokens. No subscriptions that can expire. The seed is derived from Ethereum’s own block production — the owner commits to a block, and the blockhash of that block becomes the seed. The owner cannot predict future blockhashes, and validators cannot predict which hash benefits any particular token without running the full trait generation. Once the seed is finalized, every token’s traits are deterministic: the same seed + token ID always produces the same punk.
Onchain Rendering
If/when existence is enabled and a token is revealed, tokenURI()
returns a fully self-contained data:application/json;base64 URI
containing the name, description, attributes, and a
data:image/svg+xml;base64 image. While Does Not Exist mode is
active, tokenURI() returns only the blue-square state —
see Dormant Renderer.
Pixel Encoding
Pixel data uses the same 3-byte-per-block encoding as the originals. 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 originals 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 the originals:
| 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.
Dormant Renderer
The renderer is not deployed at launch. The main contract references
address(0) for the renderer until it is set. While unset,
tokenURI() returns the blue-square Does Not Exist state built
from an inline SVG constant — no external call needed.
Visual rendering requires both doesNotExist == false (set by
settlement) and a non-zero renderer address. Specifically, while either
condition is unmet:
- No trait layers are rendered, visibly or invisibly
- No art from the originals is emitted
- No per-token trait metadata is returned
- No rarity information exists
- No token has generated
punkDatauntilreveal()is called after seed commitment tokenURI()returns the inline blank SVG — a blue square with no content
The owner can set and unset the renderer at any time. Setting it to
address(0) returns all tokens to the dormant state regardless
of doesNotExist. Settlement does not require a renderer —
minting, committing, and revealing all work without one. The renderer only
matters for visual output in tokenURI().
PunkRenderer.sol, when deployed, stores the full palette and pixel data for all base types and traits onchain. The renderer is sealable: once sealed, no pixel data, palette, or trait metadata can be changed, whether or not the collection visually exists.
Trait Rules
If/when revealed, 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 of the originals 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 a punk is revealed, the contract verifies the generated combination doesn't exist in the original 10k. 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 original punk has that exact combination of type + count + accessories. The combo is rejected and the contract retries with a different attempt seed.
Previously revealed 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 capital-formation fund. The treasury exists for one purpose: to fund a settlement that gives the collection the right to exist — ideally through IP acquisition for the public domain, or through a license or permission grant.
Protocol Fee
0.00404 ETH per claim. The owner can adjust this, capped at
a maximum of 0.01 ETH. After settlement, claiming is free.
Burn-for-Refund
Holders can burn their tokens at any time pre-settlement to recover 100% of
their contribution. Each token tracks the fee paid at mint time in
tokenContribution[tokenId]. Burning returns exactly that amount.
burnForRefund(tokenIds)— burns up to 100 tokens, refunds the total contribution tomsg.sender- Only the token owner can burn (but refund value travels with the token on transfer)
- The solvency invariant
balance ≥ totalRefundableholds at all times pre-settlement - Burns are available even after the campaign deadline passes
- Burns close permanently once settlement is accepted. The 7-day timelock window after a nomination is your last chance to burn-for-refund. See Settlement Trigger
If the fee changes between mints, each token refunds exactly what was paid for it, not the current fee. A token minted at 0.00404 ETH refunds 0.00404 ETH even if the fee later changes.
Settlement Flow
The owner nominates a settlement: a recipient address and an ETH amount.
After a 7-day timelock, the recipient can accept. This transfers the funds
as a one-time lump-sum, flips doesNotExist to false,
and enables the reveal pipeline. No recurring payments, no subscriptions.
See Settlement Trigger.
The 7-day window is the holders’ exit period. Burn-for-refund is open anytime pre-settlement, but the 7 days after a nomination are the last chance to leave. If enough holders burn, the balance drops below the settlement amount and acceptance reverts — mass exit is a veto on the deal.
No Operator Fee
There is no brokerage, no operator fee, no extraction of any kind. Every wei in the treasury is either refundable to holders or allocated to settlement. The owner cannot withdraw ETH directly.
Trust Model
TPDNE launches with a single owner (EOA). This is an honest disclosure, not a feature. The owner has real power, and holders should understand exactly what that power is and how it is constrained.
What the Owner Can Do
- Nominate a settlement (recipient + amount), starting the 7-day timelock
- Overwrite a pending nomination (resets the 7-day clock)
- Extend the campaign deadline (cannot exceed the absolute maximum set at deploy)
- Commit the reveal seed (one-time, after settlement)
- Re-commit if the 256-block finalization window is missed
- Set or unset the renderer contract address
- Adjust the protocol fee (capped at 0.01 ETH)
- Transfer ownership (two-step via Ownable2Step)
What the Owner Cannot Do
- Withdraw ETH from the treasury directly
- Prevent holders from burning for refund (pre-settlement)
- Choose who receives which traits (seed is deterministic)
- Reroll or manipulate the reveal seed once finalized
- Accept settlement themselves (only the nominated recipient can)
- Settle after the campaign deadline
- Renounce ownership (explicitly disabled)
Guardrails
The 7-day settlement timelock is the primary guardrail. It gives holders time to evaluate any proposed deal and exit if they disagree. The burn-for-refund mechanism is always-on pre-settlement and cannot be disabled by the owner.
The owner could nominate a bad-faith settlement (e.g., sending funds to themselves). The defense: if holders disagree, they burn-for-refund during the 7-day window. If enough holders exit, the contract balance drops below the settlement amount and acceptance reverts. This is economic rather than cryptographic protection.
What Could Go Wrong
The owner could grief the campaign by repeatedly overwriting nominations or setting unreasonable fees. This is mitigable but not eliminable at launch. The owner's reputation is the primary defense in the early stage.
Progressive Custody
The plan is to transition ownership from a single EOA to progressively more decentralized control as the campaign grows:
- Launch: single EOA (current)
- Phase 2: Safe multisig (target: TBD)
- Phase 3: DAO executor / timelock / onchain governance (target: TBD)
Ownership transfer is two-step (Ownable2Step): transferOwnership()
sets a pending owner, then acceptOwnership() must be called by the
new address. No custom governance code needed — battle-tested infrastructure
handles it. The renderer can be sealed independently of ownership.
These thresholds are placeholders. They will be defined as the campaign develops and the community forms.
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 the originals 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 the originals. 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 originals 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 10,000 originals
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
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