putty

The seven Kits

What each Kit is, what it does today, and where its HTTP surface and its tests live.

Putty is an engine, and the Kits are the product. A Kit is a finished outcome rather than a tool: the build plan (context/build-plan-artsy.md §3.1) describes one as a guided flow, a finished-outcome definition the engine verifies rather than claims, and a public artifact.

This page says what each one does today, and where you can check it. Where a Kit falls short of its plan, that is written here too.

The seven, in build order

The plan ships one Kit per day (§7.1). The order is tools first; the Launch Kit comes last because it assembles what every other Kit builds.

1. Business Kit

A shop, its orders, and a checkout that takes a card or a stablecoin.

Ten operations under /business/*: register a shop, read and patch it, delete it, read its order list and takings summary, open a checkout, read one, and list the payment methods the deployment can actually take.

Two things make it worth reading the source for. First, the response shape is identical for both payment methods — checkoutUrl is the link the Pay button points at whether the shopper pays by card or in USDC or USDG, and the customer completes the payment on the provider's own page. Putty never takes custody: it never sees a card number, never holds a stablecoin, and never asks for a seed phrase. Second, GET /business/methods reports what the deployment has a provider wired for, so a card-only deployment renders one button rather than a dead second one.

There is no dashboard page for this Kit. The API is the whole surface.

Where to look

apps/api/src/modules/business/ · POST /business/checkout · GET /business/methods · apps/api/test/modules/business-kit.test.ts

2. Community Kit

Holder-gated bots, launch countdowns, a holder census, and a one-ballot-per-owner spotlight vote. Eleven operations under /community/*.

POST /community/verify is the centre of it, and it has three deliberate outcomes. A balance above zero passes with a 200. A balance of zero is 403 not_a_holder, not a 200 with verified: false — the refusal is the product, so it has to be a status a gateway can act on without parsing the body. A chain read that fails is 502 chain_unavailable, a third outcome on purpose: a caller must be able to tell "you are not a holder" from "we could not check", and collapsing the two would show a member an error instead of a refusal.

There is no /app/community page. The Trade page's balance check is the one dashboard code path that calls it.

Where to look

apps/api/src/modules/community/ · POST /community/verify · apps/api/test/modules/community-kit.test.ts

3. Contract & Hook Studio

A contract shape, written from a prompt, with a verdict attached before anyone signs.

The Verify surface is live and split in two on purpose. POST /verify and GET /verify/methods are public, free, and take no bearer at all — paste a contract and get the permission diff and the plain-English readback. POST /verify/ai-review is the opposite: it is the only authenticated route in the module, because it spends the caller's credits. It runs several independent model passes and returns findings ranked by severity and by how many passes agreed, beside the deterministic report rather than instead of it. The free checks stay open — safety is not an upsell.

A note on naming: the AI review is labelled AI review, never "audit". A model reading code is not an audit, and Putty does not blur it.

The Studio's own two routes are live and mounted. GET /contracts/presets returns the full catalogue — 11 presets, each with its title, description, source (wizard or ours), the OpenZeppelin files an ours preset is built on, its audit label, its licence, and every constructor parameter with its Solidity type, required flag, default, and placeholder. It takes no bearer.

POST /contracts/generate requires a bearer and is metered, not free. It returns the generated Solidity, the rendered constructor arguments, a Foundry test skeleton, the audit label, a SHA-256 fingerprint, the OpenZeppelin version the Wizard pins, its Foundry remappings, and what it was charged. A missing required parameter is a 400 naming each offending one; an unaffordable one is a 402 in billing's own shape. Generation is deterministic: the same preset and the same arguments produce the same source, and the route says so.

On the audit label. There are three, and they mean different things. audited is an OpenZeppelin library contract used as-is, and is reserved for a case where a real dependency is audited. reviewed is unmodified Wizard output — the parts are audited, the composition is not. experimental is Putty's own code, or generated code that has been hand-edited. The label is a ceiling: a preset can only be as trusted as its cleanest form, and a hand edit to a generated file drops it to experimental, which a mainnet deploy then refuses.

Where to look

apps/api/src/modules/verify/ · POST /verify · POST /verify/ai-review · GET /contracts/presets · POST /contracts/generate · apps/api/test/modules/verify-kit.test.ts · apps/api/test/modules/contract-studio.test.ts

4. Trust Kit

Onchain money logic — splits, buybacks, vesting, treasury rules — with a public Receipts page that says what can change, who can change it, and how fast.

The contracts are Foundry, in packages/contracts/src/: ten contracts — PuttyToken, PuttyTokenFactory, PuttyTreasuryRouter, PuttyFeeBase, PuttyFeeSplitter, PuttyVesting, PuttyBuybackVault, PuttyBurnVault, PuttyHolderRewards and PuttyTrustModule — green on a local EVM with 264 Foundry tests. The readable surface is GET /trust/receipts/{chain}/{token}, which reads a token's setup straight off the chain and returns it as a document — every module found, every wallet it references with its live balance.

There is also an HTML rendering of the same document at GET /trust/receipts/{chain}/{token}/page, served with script-src 'none' and every onchain string escaped, so a token's setup can be linked to anyone without the reader running any of it.

Three properties make this route worth reading:

  • It works for any token on any supported chain. There is no allowlist and no putty-only path. A module is recognised by the getters its contract answers, so a token launched on Pons or Hookr reads the same as one launched here, and a token Putty never heard of reads too.
  • A token with no modules returns an empty modules array, not a 404. The token is real; its setup is simply empty. A 404 means no contract at that address at all.
  • Every amount is a decimal string in the token's base units, never a float.

Modules are ranked by how many of their discriminating getters answered, so the identification is not order-dependent, and a tie is treated as no match at all.

Do not confuse it with GET /receipts, which is unrelated — that one lists payment receipts.

Where to look

packages/contracts/src/ · GET /trust/receipts/{chain}/{token} · apps/api/test/modules/trust-receipts.test.ts

5. Creator Kit

An NFT drop with a branded mint page. Nine operations under /creator/*: create a drop, list, read, patch, delete, record a mint, list mints, and the public page.

A drop references a brand kit rather than restating it, so one brand kit dresses many drops. The mint page is public, static, and serves the name, image, supply, price, and mint state — GET /creator/mint/{dropId} is the route that answers with no bearer, so a mint page can be linked anywhere.

The Brand studio itself is its own surface, not part of this Kit: GET/POST /brand/kits, POST /brand/kits/draft, and the per-kit routes.

Where to look

apps/api/src/modules/creator/ · GET /creator/mint/{dropId} · apps/api/test/modules/creator-kit.test.ts

6. Agent Kit

Register an agent, meter its calls, and read what it spent. Eight operations under /agents/*.

Registering an agent grants nothing. It is naming a workload so its spend is separately reportable. An agent can only make metered calls with a bearer the caller already holds — an sk- key from POST /account/api-keys, or the caller's own Privy session — and it draws on the caller's credit balance.

POST /agents/{id}/calls is the route an agent harness on a user's own host calls, and it has one property that matters more than the rest: the charge comes first. Credits are taken before the tool runs, so a caller whose balance is short gets a 402 with nothing metered and no tool run. There is no state in which the work happened and the money did not.

requestId is the idempotency key and it is the caller's job to make it stable. A retry of the same work carries the same id; the replay returns the same charge, takes no new credits, and says replayed: true.

Where to look

apps/api/src/modules/agent/ · POST /agents/{id}/calls · apps/api/test/modules/agent-kit.test.ts

7. Launch Kit

Build the unsigned transaction that creates a token, and quote a swap through a Putty pool.

POST /launches validates the launch parameters against the provider's limits and returns the unsigned transaction. The server does not sign and does not send — transactions is what the caller takes to their own wallet. A launch with anything in problems has no transactions at all, and every problem is a plain sentence naming what to fix.

Only the erc20-direct provider works today. Pons, Hookr and Orbio are registered as providers that refuse with the exact unblock condition rather than being absent from the registry — an absent venue reads as a missing feature, a refusal reads as a known problem with a name.

Pons's refusal is worth reading rather than skipping. The recorded factory's launchEnabled() is false, so launchToken reverts for every sender, and PUTTY_FEE_SPLITTER is undeployed, so the promised fee share would be silently zero. Putty refuses rather than sending a transaction that cannot succeed. POST /pools/quote quotes a swap through a Putty pool.

GET /launches/fee quotes the mainnet launch fee, and the launch route enforces that fee before it records anything. Per-account daily mainnet launches are throttled; testnet and fork launches are exempt.

Where to look

apps/api/src/modules/launch/ · POST /launches · GET /launches/fee · POST /pools/quote · apps/api/test/modules/launch-provider.test.ts

What the Kits are not yet

Stated plainly, because the repository's build-gates.md gate ledger records the same things, each with the command that proves it. Where a gap is a credential rather than code, it is named as one, so nobody re-derives it:

  • The Community Kit's bot template does not exist. Its API ships and is tested; packages/templates/ has landing and webapp and no bot. Writing packages/templates/community-bot/ closes it, and it needs no credential.
  • The Trust Kit's fork run has never executed. The ten contracts are green on a local EVM (264 Foundry tests). The fork variant is wired — anvil --fork-url is in the script — and has never run. It needs ROBINHOOD_RPC_URL.
  • The Launch Kit's Day 7 exit criterion is unmet. It is a launch on each venue with the putty fee live on a fork, which needs that RPC plus a live credential per venue.
  • Pons, Hookr and Orbio do not launch. All three are registered providers that refuse with their exact unblock condition. Pons needs a product decision to adopt PonsV2LaunchFactory and a deployed PuttyFeeSplitter; that is not a code gap.
  • The Business Kit has no dashboard page. The API is the whole surface.
  • The dashboard's Onchain page is still a placeholder — a 26-line "coming soon" component with no API behind it. /showcase is real: a full page backed by fifteen /showcase/* operations.

Two of the five need no credential at all. The other three are waiting on ROBINHOOD_RPC_URL, on live venue credentials, or on a product decision.

The fee numbers, and the one that is not the plan's

The rail fee is 0.3% (30 bps) of swap volume, decided in the build plan (§11, decision 9) and hard-capped: PuttyFeeBase's constructor reverts above 30 bps, so no deployment can exceed it.

The Pons share is 0.5% (50 bps) by owner ruling, and the plan's "30% of the project's fees" (§11-1) is deliberately not implemented. 30% is 3000 bps and the splitter's MAX_SHARE_BPS is 50, so the contract cannot express that decision. The cap is what makes it unreachable, and test_DecidedPonsShareIsNotDeployable fails if the cap is ever raised past 3000 — so closing that gap has to be a deliberate act rather than a side effect of some other change.

On this page