Protocol

The mechanism in detail.

Permanent Collection moves through three motions. Trading feeds a live bid. An eligible Punk accepts. A 72-hour return auction decides whether the Punk returns to circulation or enters the vault. This page walks through each motion, the contracts behind them, and the invariants that make the work durable.

Architecture

Permanent Collection sits on top of the artcoins protocol. Artcoins handles the token deploy, the V4 pool, the hook, the LP locker, and the MEV module. PC adds the live bid, the return auction, the vault, and the rendering stack that turns vault state into on-chain art. PunksData is the sealed external source for trait names and pixel data; Punks is the canonical 2017 market where vaulted Punks actually live, at PunkVault's address.

ArtCoinsFactorydeploys + binds, owner-only$111ERC20 artcointokenURI renders the workV4 Pool$111 / ETH0.5% LP + 6% skimHook + MEVHookSkimFee + LinearSkimanti-sniper windowLocker + EscrowArtCoinsLpLockerFeeEscrow buffers claimsFee adaptersLiveBid · ProtocolFee · Referralvia FeeAutoSwapperPatronlive-bid ETH hubacceptBid / acceptListingReturnAuctionModule72-hour auctioncleared or vaultedPunkVaultimmutable custodyERC721 issuer (Title + Proofs)acceptsettlePunkVault NFTsTitle + 111 ProofsRendererRegistrystable front addressMosaicRendererTitle + ProofRendererCachesPunkSvg + TraitIconPunks marketcanonical 2017 contractvaulted Punks live at PunkVault addressPunksDatasealed, canonicaltrait names + per-Punk pixelsholds Punksreads trait data
Architecture: the artcoins factory deploys $111 and the V4 pool, with the hook, locker, and MEV module bound to it; PC adds the live bid (Patron), the return auction (ReturnAuctionModule), the vault (PunkVault), and the renderer stack; the bottom band is the external contracts everything else depends on.
The official pool

$111 pairs with native ETH in a Uniswap V4 pool. Total fee on every swap is 6.5%: 0.5% is the V4 LP fee paid to liquidity providers via the standard mechanism, plus 6% is a baseline skim the hook splits inside the same transaction. The split happens before the user sees the result; there's no separate router and no off-chain collection.

At launch the LP fee also feeds the live bid: an LP locker holds 100% of LP positions and routes its single reward slot to FeeAutoSwapper, which converts the $111 side to ETH and forwards it to LiveBidAdapter. The reward slot is admin-locked to a dead address so it can't be redirected. Public LPs can mint positions after the anti-sniper window and earn their pro-rata share alongside the locker.

For the first ~30.000000000000004 minutes after launch, the pool runs an anti-sniper window. The fee starts at 90% and decays linearly to 6% at 2.8% per minute. Everything above the 6% baseline routes 100% to the live bid; the baseline split below is what runs forever.

Fee routing

Of the 6% baseline skim, three legs route inside the same swap through dedicated adapters, each to a fixed destination from block one. The bid leg routes to Patron (the live bid). The protocol leg routes to PCController. The referral leg, when a swap carries an attribution payload, routes a thin slice from the protocol leg to the named referrer.

Pool + Hook6% per swap5%1%up to 0.25%LiveBidAdapterProtocolFeePhaseAdapterReferralPayoutPatronlive bidPCControllerPC treasury + $LAYER burnReferrerattributed swaps
Fee routing: the 6% baseline skim splits inside the hook on every swap, each leg to a fixed destination from block one: the bid leg to Patron, the protocol leg to PCController, and a referral slice carved from the protocol leg for the named referrer on attributed swaps. The separate 0.5% V4 LP fee (not shown) is paid to LP holders; at launch the LP locker routes its share through FeeAutoSwapper to the live bid too.
  • 5% → LiveBidAdapter: always routes to Patron (the live bid). The adapter meters it in two modes: below an activation threshold it fills the bid fast (the launch warm-up); above it a rate cap throttles, so a single big swap can't flood the bid in one block
  • 1% → ProtocolFeePhaseAdapter: sweeps to PCController from block one, which splits 86.67% to the PC treasury and 13.33% to the $LAYER buy-and-burn
  • Up to 0.25% → ReferralPayout: if a swap carries a PCAttribution payload, a thin slice routes from the protocol leg to the named referrer, from the first swap. The referrer pulls from a per-address ledger. With no referrer the slice stays in the protocol leg
  • 0.5% LP fee → liquidity providers: paid via V4's standard mechanism, not the hook. At launch the LP locker holds 100% of LP positions and routes its share through FeeAutoSwapper (which converts the $111 side to ETH) to LiveBidAdapter, so the LP fee effectively joins the live bid until public LPs add depth
The live bid

Patron holds the live bid as native ETH. The bid is an accounted total, bidBalance(), that only fills through LiveBidAdapter: Patron's receive() rejects every other sender, and ETH forced in by any other route never counts toward the bid. The number on the homepage is bidBalance().

Any address can top up the live bid by sending ETH to the LiveBidAdapter, which meters it into Patron. To accept the bid, the owner of an eligible Punk first lists it exclusively to Patron at a price at or below the live bid (the frontend defaults to the full bid), then anyone can finalize the acceptance by calling acceptBid. Anyone can call acceptListing against an allowlisted listing contract (see below).

Patron buys the Punk at the listed price, so the canonical 2017 market pays the seller: the proceeds queue in the market's pendingWithdrawals and the seller collects them with withdraw(). The Punk transfers to ReturnAuctionModule and the 72-hour return auction opens.

Listings from other protocols

Some Punks sit inside autonomous protocols rather than at an owner's wallet. PunkStrategy (PNKSTR) is the canonical example: it buys floor Punks and immediately re-lists them at 1.2× cost on the 2017 Punks market; when one of those listings sells, PunkStrategy uses the proceeds to buy and burn PNKSTR. The Punks pass through the contract on a fixed-flow yoyo.

Permanent Collection has a custom path for protocols like this: any address can call acceptListing against an allowlisted listing contract whose published price is at or below the live bid, bridging the trade in one transaction. PunkStrategy receives its 1.2× and triggers its own buy-and-burn cycle; PC takes custody of the Punk and opens the same 72-hour return auction. Both protocols' cycles complete on the same swap.

The caller earns a small finder fee — a share of the live-bid balance, not the listing price: 0.5% of the live bid, hard-capped at 0.01 ETH. The acceptListing path only opens once the live bid is at least 0.5 ETH.

At launch the allowlist seeds PunkStrategy only. Patron.addAllowedSeller stays editable past the protocol's 1-year admin auto-lock (one of the four scoped carve-outs) so new peer protocols can be registered as they emerge. Any contract that lists Punks via the canonical 2017 market's offerPunkForSale surface is eligible; the allowlist gates which ones PC is willing to source from. The mechanism on the caller side is permissionless.

The return auction

Once a Punk is in the return auction, anyone can bid at or above the reserve to return it to circulation. The reserve is set at acceptance time from the acquisition cost and the number of times the protocol has already tried for that trait: cost × (101 + previousAttempts) / 100, rounded up. First attempt for a trait reserves at 1.01× cost; each subsequent attempt against the same trait adds 1%.

Bids in the last 15 minutes extend the auction by 1 hour. There is no cap on extensions. The auction either clears (a bid lands above the reserve) or it doesn't.

T = 0acceptBidT = 72hdeadlinePunk → auctioncost paid to ownerAuction endscleared or silencedbid at or above the reserve clears (15min anti-snipe extends +1h)Clearedbid at or above reservePunk → buyerSilencedno bid by deadlinePunk → PunkVault65% cost → LiveBidAdapter25% cost → BuybackBurner10% cost + premium → VaultBurnPoolchosen trait → permanentProof mints to sellerVaultBurnPool → BuybackBurner
Return auction lifecycle: 72 hours from acceptance to settle. A bid at or above the reserve clears the auction; no bid sends the Punk to the vault and the chosen trait becomes permanent. Any premium the cleared bid carries above cost queues in the vault burn pool and flushes to BuybackBurner on the next vaulted settle.

On clear, the high bidder takes the Punk. The acquisition cost splits three ways: 65% refills the live bid via LiveBidAdapter, 25% buys back and burns $111 via BuybackBurner, and 10% goes to the vault burn pool. Any premium the high bid carries above cost also routes to the vault burn pool — minus up to 5% of that premium to the winning bid's referrer, if one is attributed. The vault burn pool sweeps to BuybackBurner on the next vaulted settle.

On silence (no bid by the deadline), the Punk transfers to PunkVault. The chosen trait flips from pending to permanent on PermanentCollection. A Proof NFT mints to the original seller. The vault burn pool sweeps on the same settle, feeding any accumulated premium into the buyback.

The vault

PunkVault is the immutable custody contract. It has no transfer, withdraw, rescue, or sweep selector. This is asserted at the bytecode level: the deployed contract's selector table is scanned by a fork test that fails if any market-write or admin-exit pattern appears.

The vault is also the issuer of 112 named tokens. Token id 111 is the Vault Title, auctioned through PunkVaultTitleAuction once 22 of 111 traits are collected. Token ids 0..110 are the 111 Proofs, one per trait, minted on first-vaulting to the original seller. The Title and the Proofs are ERC721 and freely transferable; the Punks themselves are not ERC721 and live at the canonical 2017 Punks contract.

Title grants no withdrawal rights, no admin control, no governance, and no claim on the Punks. It's a stewardship record, named in the contract for display purposes only.

Records

Every acceptance appends a row to PermanentCollection.Acquisition[]. The log records the Punk id, the chosen trait, the pending-mask snapshot at acquisition, the acquirer, the original seller, the price paid, and the block. Rows never delete and never reorder. Custody on each row moves forward only: from InReturnAuction to either ReturnedToMarket or Vaulted, then freezes.

Acquisition isn't the same as collection. The trait bitmap (collectedMask) only flips a bit when a Punk actually enters the vault carrying that trait, and only for the recorded target trait, not for every uncollected bit on the Punk's mask.

Proofs encode that record as art. Each Proof carries the Punk id, the trait id, the sequence (Nth Proof minted), and the vault-settle block. The metadata is frozen at mint time and survives transfer.

Composability

Two builder surfaces are live from day one:

  • Attribution: every swap can carry a sourceId and referrer field via hookData. The official hook emits a SwapAttribution event for every attributed swap. Permissionless, no allowlist
  • Referral fee: up to 0.25% of swap volume can flow to the referrer on every attributed swap, pulled from the protocol slice. The live bid stays structurally untouched
Contracts

The full system is below. Each name links to the deployed contract on evm.now. The source for every contract is public: github.com/ripe0x/permanent-collection holds the protocol, and github.com/ripe0x/artcoins holds the launcher it runs on (hook, factory, lockers).

Permanent core

  • PermanentCollectionAppend-only acquisition log and the collected-trait bitmap. No funds, no Punks
  • PatronLive-bid ETH hub. Entry point for acceptBid and acceptListing
  • ReturnAuctionModule72-hour return auction. Settles cleared or vaulted
  • ReturnAuctionEscrowSettlement escrow tied to ReturnAuctionModule by construction
  • PunkVaultImmutable custody. ERC721 issuer for the Title and the 111 Proofs
  • BuybackBurnerPaced buy-and-burn of $111 from cleared-auction revenue and vault-burn-pool sweeps
  • VaultBurnPoolAccumulator for the auction premium above cost. Flushes to BuybackBurner on every vaulted settle
  • ProtocolAdmin1-year auto-locking admin role over a handful of economic parameters

Fee adapters

  • LiveBidAdapter5% bid leg plus the LP fee. Sweeps 100% to Patron
  • ProtocolFeePhaseAdapter1% protocol leg. Sweeps to PCController from block 1, which splits 86.67% to the PC treasury and 13.33% to the LAYER burn
  • ReferralPayoutPer-address pull ledger for the referral slice

Composability and admin

  • PCSwapContextTransient-storage reentrancy registry shared across PC contracts
  • TokenAdminPokerHolds the $111 tokenAdmin role. Exposes the bind-extension safety valve

Renderer

  • PermanentCollectionMosaicRendererRenders the Title (token 111) and dispatches Proof renders to the Proof renderer
  • PermanentCollectionProofRendererPer-Proof renderer for token ids 0..110
  • RendererRegistryStable address fronting the live renderer. Swappable until frozen
  • PunkVaultTitleAuctionKickoff plus auction for the Vault Title once 22 traits are collected
Invariants

These are the durability claims the protocol holds. Each is enforced at the bytecode level (selector scans) or via the adversarial fork test suite. None of them can be loosened without a redeploy:

  • collectedMask is monotonically increasing. Bits never unset
  • Acquisition[] only grows. Rows never delete or reorder; only the custody field mutates forward
  • Custody transitions are strictly InReturnAuction → ReturnedToMarket | Vaulted, then frozen
  • Acquisition does not imply collection. recordAcquisition never touches collectedMask; only markCustody(Vaulted) does
  • Vaulted collects only the recorded target trait, not every uncollected bit on the Punk's mask
  • No Punk can leave PunkVault or PermanentCollection. Neither contract holds a Punks market-write selector
  • The cleared-path proceeds split is hard-coded: CLEARED_BID_BPS = 6500. No setter, no admin override
  • address(patron).balance >= bidBalance(). The bid only fills through LiveBidAdapter; ETH forced in by any other route never counts toward it
  • Token holders have no governance over the protocol
  • The 6% baseline skim split is enforced at swap-time inside the hook, not collected post-hoc