Add render-token service: token accounts, model registry, render jobs - #4
Merged
Merged
Conversation
Coders can now buy prepaid render tokens and spend them running custom AI models (Claude for code generation, Workers AI for images), and publish their own models for others to run and earn from. - migrations/0018_token_service.sql: token_accounts, append-only token_ledger, token_packs/token_purchases, render_models + render_model_versions (immutable, versioned), render_jobs. - src/db/tokens.ts: the ledger is the source of truth; balance is a cache updated in the same D1 batch under a `balance + delta >= 0` guard, so an overdraw touches neither table. Idempotency keys make every mutation replay-safe instead of double-applying on retry. - src/db/render.ts: model registry (owner, kind, provider, visibility) with append-only versions; provider/kind pairing is validated at registration time, not at render time. - src/ai/inference.ts: dispatches to Claude API (code) or Workers AI (images) behind a uniform interface; RENDER_MOCK=1 (opt-in, never in production — same convention as GITHUB_MOCK/PINNING_MOCK) returns deterministic canned output for local dev and CI. - src/tokens/handlers.ts + src/render/handlers.ts: account/ledger/packs endpoints; token purchase confirmed by verifying an on-chain ETH transfer to a treasury address (no payment processor, reuses the mint flow's receipt-verification pattern); model CRUD + publish + render, with debit-before-run and full refund on failure. - New users get a 200-token signup grant (auth/siwe.ts), idempotent on user id so it applies exactly once regardless of sign-in count. - wrangler.toml: [ai] binding added to env.production only — bound at the top level it forces `wrangler dev` into remote mode and hard- fails without a CLOUDFLARE_API_TOKEN, breaking local dev entirely. - 8 new unit tests (ledger lifetime-counter accounting, provider/kind gating) and 20 new smoke checks (signup grant, purchase flow, model registry, render + idempotent replay, balance never negative) — 26/26 unit tests and 108/108 smoke checks pass; typecheck and `wrangler deploy --dry-run` (both environments) are clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
generatedart | cb88a5e | Commit Preview URL Branch Preview URL |
Aug 17 2026, 08:03 AM |
GeneratedArt's render-model registry previously covered two lanes: Claude for code generation and Workers AI's fixed catalogue for reference images. This adds the lane the platform is actually positioned on — coders publishing their own trained models (a fine-tune or LoRA on a diffusion base), the way Refik Anadol builds a bespoke model on a curated dataset rather than prompting Midjourney or DALL-E. Workers AI can't host this: its LoRA fine-tuning (open beta) only accepts text model_types (mistral/gemma/llama), no image/diffusion LoRA support. fal.ai's private-model inference fills the gap without breaking the budget stance — pay-per-run, no idle GPU cost, unlike self-hosting or Replicate's cold-start-billed custom deploys. - migrations/0019_custom_model_provider.sql: adds training_method, base_model, dataset_note, weights_ref to render_model_versions (append-only, matches the 0002_projects_extras.sql convention). Lives on the version row, not the model shell, since a creator retraining on a new dataset publishes a new version. - src/db/render.ts: `fal_custom` added to MODEL_PROVIDERS, allowed for kind=image. Training lineage fields are public on publicModelVersion (the provenance disclosure); weights_ref stays owner-only like system_prompt — it's what would let someone else clone the model outright. - src/ai/inference.ts: runFalCustom() — raw HTTP against fal.run (Authorization: Key header, images[0].url response), not an SDK call, since this is a third-party non-Anthropic provider. Fails closed on missing FAL_KEY or a version with no weights_ref. - src/render/handlers.ts: publishVersionHandler validates the new fields and requires weights_ref when provider=fal_custom — there's no catalogue fallback the way provider_model_id alone works for the other two providers, so a version without it would publish and then fail every render. - types.ts / .dev.vars.example: FAL_KEY as a secret, same fail-closed/mock convention as ANTHROPIC_API_KEY and RENDER_MOCK. - 4 new unit tests (provider/kind gating, isTrainingMethod) — 28/28 pass. 4 new smoke checks (registration, weights_ref requirement, provenance-public/weights-private on the anon read path) — 112/112 pass. Typecheck clean; wrangler d1 migrations apply --local clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz
guillaumelauzier
marked this pull request as ready for review
August 17, 2026 08:07
guillaumelauzier
pushed a commit
that referenced
this pull request
Aug 21, 2026
Item 2 of the platform build backlog (design spec: Step 6 of the published design brief). The render-token API (PR #4) had no frontend — every wallet, catalogue, and publish interaction had to go through raw fetch calls. This adds the two Jekyll pages and their client TypeScript, matching the existing client/*.ts + esbuild build:* convention (fetchJson helper, escapeHtml, event-delegated forms, Bootstrap modals, brand.css tokens only). - client/wallet.ts + wallet/index.html: balance hero, token packs, on-chain purchase (send ETH to the treasury address, wait for the receipt, POST the tx hash to confirm — same wallet-connect + send-tx shape client/mint.ts already uses), and paginated ledger history. - client/models.ts + models/index.html + models/detail/index.html: public catalogue with a kind filter, "your models" + publish-a-model modal, and model detail (query-string routed like /p/?id=N and /t/?p=N&id=T, since Jekyll can't statically generate a page per D1 row) with the version/provenance list, an owner-only publish-version form whose fields adapt to the model's provider (training method / base model / dataset note / weights_ref only for fal_custom; system prompt only for code kind), and a render form with the cost shown before the click. - package.json: build:wallet / build:models scripts, wired into build:all. Testing: both new client files pass a manual strict tsc pass (client/ isn't in tsconfig's include, so this is on top of, not instead of, the project's own `npm run typecheck`) and bundle cleanly with esbuild; caught and fixed two real type issues in the process (a possibly-null form-field chain, and an `EIP1193Provider.request` overload mismatch from a `Hex | null` field passed where TS needs local narrowing to `Hex`). Full Jekyll build (`./bin/jekyll build`) succeeds with the three new pages producing balanced markup and no unresolved Liquid. API unit suite still 33/33. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A render-token service: coders buy prepaid tokens and spend them running custom AI models (Claude for code generation, Cloudflare Workers AI for images), and can publish their own models for others to run and earn tokens from. This is the platform's first monetized AI-creative-coding surface — the AI writes/renders, the token is the meter.
How it works
src/db/tokens.ts):token_ledgeris append-only and is the source of truth;token_accounts.balanceis a cache updated in the same D1 batch under an identicalbalance + delta >= 0guard on both statements, so an overdraw touches neither table. Every mutation carries an idempotency key — retries replay the original entry instead of double-applying.confirmDeploy/confirmMintinprojects/mint.ts) before crediting tokens.UNIQUE(chain_id, tx_hash)stops a tx being redeemed twice.src/db/render.ts): creators register a model (kind:code/image, provider:anthropic/workers_ai/mock) and publish immutable, versioned recipes (provider model id, system prompt, price). Provider/kind pairing is validated at registration, not at render time, so a bad combo can't get someone charged before it fails.src/ai/inference.ts): a singlerunInference()dispatches to the Claude API (via the official@anthropic-ai/sdk,thinking: {type: "adaptive"}) or the Workers AI binding behind one return shape.RENDER_MOCK=1(opt-in only, never in production — same convention asGITHUB_MOCK/PINNING_MOCK) returns deterministic canned output, which is what makes the render pipeline exercisable inwrangler devand CI without live credentials.auth/siwe.ts), idempotent on user id so calling it on every sign-in is safe — it applies exactly once.New endpoints
GET/POST /v1/tokens/{account,ledger,packs,purchase/confirm},GET/POST/PATCH /v1/models[/mine]/:slug[/versions|/render],GET /v1/jobs[/:id].Config
wrangler.toml:[ai]binding added toenv.productiononly. Binding it at the top level forceswrangler devinto remote mode and hard-fails without aCLOUDFLARE_API_TOKEN— that would have broken local dev for every contributor without one, so local dev exercises the render path viaRENDER_MOCK=1instead..dev.vars.example:TOKEN_TREASURY_ADDRESS,ANTHROPIC_API_KEY(secret in prod),RENDER_MOCK.migrations/0018_token_service.sql— applies cleanly against local D1; not yet applied to production (runnpm run migrate:prodwhen ready to ship).Testing
npm run typecheck— clean.npm test— 26/26 unit tests pass (8 new: ledger lifetime-counter accounting, provider/kind gating).npm run smokeagainst a localwrangler devwithRENDER_MOCK=1— 108/108 checks pass (20 new: signup grant, purchase-unconfigured path, model CRUD, provider/kind rejection, render + idempotent-replay-not-double-charge, insufficient-balance never goes negative).wrangler deploy --dry-runclean for both the default and--env productiontargets.Not in this PR
Client/dashboard UI for the token balance, purchase flow, and model catalogue — this PR is the backend service.
TOKEN_TREASURY_ADDRESSandANTHROPIC_API_KEYare unset in production until deliberately configured; both fail closed (503) until then.🤖 Generated with Claude Code
https://claude.ai/code/session_01QuSa99HABSrWRarT9BQ5Tz
Generated by Claude Code