---
title: "Contracts — PDKeychains"
description: "The capsule machine's ERC-721: one-of-one living charms drawn entirely on-chain, luck-tilted rarity rolls, the polish attestation, the keeper bond, and the christened name."
category: "contracts"
keywords: ["PDKeychains", "keychains", "charms", "capsule machine", "ERC-721", "on-chain art", "luck", "polish"]
last_updated: "2026-10-12"
---

# Contracts — PDKeychains

The [Le Depanneur's capsule machine](/docs/keychains/overview) on-chain: an ERC-721 where every Keychain is a one-of-one generative charm, drawn entirely at read time by a renderer contract — no stored images, no external anything. In the app's test phase the machine runs on sim-ETH; this contract is the same machine for real ETH, and deploys with the platform's launch wave.

## The crank

```solidity
function crank(uint8 coin, uint32 streak, uint8 rank, uint256 deadline, bytes calldata sig)
    external payable returns (uint256 tokenId);
```

Insert coin, crank, a random charm drops — one per crank, that's the ritual. The two **coin slots** (`0` YIN · `1` YANG) steer palette, face, and accessory weights; shapes are universal, so the rarest chase is equal on either slot. The crank commits the charm to its own block and `reveal` records the seed from the next block on — the same commit-reveal as [PDProject](/docs/contracts/pd-project) — so no wallet or contract can see a roll and revert it. Anyone may reveal (PD's server does, within seconds); until then the charm shows a placeholder, and `MetadataUpdate` (ERC-4906) tells marketplaces when the art arrives. Payment is exact (`priceWei + revealFeeWei`) and force-sent inside the transaction — of the price, platform 5% (wallet read live from [the factory](/docs/contracts/pd-factory)) and the remainder to the shop; the **reveal fee** on top goes whole to the factory's storage-fee wallet, the wallet that sends the reveal and pays its gas — so a payee that refuses ETH still gets it and can never stop a crank, and no ETH ever rests here. Two wallets fixed in the contract (`FREE_CRANKER_A` and `FREE_CRANKER_B`) crank `FREE_CRANKS` (100) times each for the reveal fee alone, with no price; no admin can add, remove or refill them, and `freeCranksLeft(address)` reads what each has left.

**THE ART BELONGS TO THE CHARM, NOT THE KEEPER.** Color, finish, and chain metal all roll per charm off its own seed on a common→rare ladder — gold and chrome are genuine rare pulls. What the keeper's PD life buys is **LUCK**: `sig` is the app's attestation of the cranker's live PriceStreak and PriceRank, signed by the factory's dedicated **attestation key** (its own key, deliberately not the settlement key — two unrelated jobs never share one secret); the machine derives an odds tier (0–3) from it on-chain and freezes it into the charm forever — it shows as the charm's **Luck** trait, the charm's provenance. The attestation's deadline may sit at most **fifteen minutes** out — it carries no nonce by design, so its short life is what stops one signature becoming a permanent luck pass. Luck only widens the rare end of the rolls — it never repaints a charm afterwards. An empty `sig` cranks unattested at luck 0, the floor odds, so the machine keeps turning even if the signer is down — and that can't be gamed, because the floor is the worst tier there is.

## The living layer

- **Polish** — `polish(...)` syncs the keeper's attested PriceStreak/PriceRank onto the charm's traits (EIP-712 signature, per-charm nonce, keeper-bound, deadline at most fifteen minutes out — so an unused polish can't be held back and played after the streak behind it is gone). It records their life; it cannot change the art, by design — the suite proves a maximum-life polish leaves the artwork byte-identical.
- **The bond** — every hand-to-hand transfer wipes the polish: a keeper's life is not a purchasable state. The charm's own identity — seed, coin, luck, and christened name — survives forever, so a sold charm looks exactly as it always did. Christening, polishing and the wipe each emit `MetadataUpdate`, so marketplaces refresh the name tag and traits.
- **Christen** — name your charm once, ever, engraved on its tag and carried as its **Name** trait (the token itself stays `Price Discussion Keychain #<tokenId>`). 2–12 characters, A–Z 0–9 and one space between words (never leading, trailing or doubled), charset-guarded on-chain so a name can never break the metadata and the tag engraves exactly the stored name.

## The art, on-chain

`tokenURI` and `charmSVG` draw the complete charm — shape, googly face, rubber-hose arms, chain, finish, accessories, the swing and the blink — as a self-contained SVG assembled by **PDKeychainRenderer** with three part stores beside it (shapes · faces · trait names), split only for the EVM's contract-size limit and referenced immutably: the four contracts are one artist. The attributes lead with the charm's **Element** (six elements, two shapes each), then coin, luck, shape, palette, material, eyes, and the rest, plus the keeper's attested PriceStreak and PriceRank.

The renderer address is swappable by the shop admin during an art-bugfix window; a swap emits `BatchMetadataUpdate` (ERC-4906) over every charm minted so far, so marketplaces redraw them. Then `lockRenderer()` freezes the art forever — one way, no reopening. The collection is **Price Discussion Keychains** (the ERC-721 `name()`, and the name `contractURI()` serves with its description as on-chain JSON for marketplaces); every charm is `Price Discussion Keychain #<tokenId>`.

## Royalties and admin

5% secondary royalty (ERC-2981) to `royaltyReceiver`, fixed at deploy: the sticker shop's solo [StickerSplitter](/docs/contracts/pd-stickers) vault, which splits it 3% to the sticker shop's admin and 2% to the factory's live platform wallet. Admin holds price and on/off dials, the reveal fee (only inside a floor/ceiling corridor fixed at deploy), plus the renderer window, transfers two-step, and can never touch a minted charm's seed, luck, name, or traits. A dust-rescue sweep exists for ether force-sent to the contract — cranks push everything out in-flight and there is no other way for ETH to land here, so the sweep can only ever recover dust, never touch the machine's money.

## Further reading

- [Keychains in the app](/docs/keychains/overview) — Le Depanneur, wearing, the hang
- [PDFactory](/docs/contracts/pd-factory) — the attestation key the attestations ride
- [Contracts Overview](/docs/contracts/overview)
