Skip to content

Add render-token service: token accounts, model registry, render jobs - #4

Merged
guillaumelauzier merged 2 commits into
mainfrom
claude/generatedart-backend-fixes-dmhq3v
Aug 17, 2026
Merged

Add render-token service: token accounts, model registry, render jobs#4
guillaumelauzier merged 2 commits into
mainfrom
claude/generatedart-backend-fixes-dmhq3v

Conversation

@guillaumelauzier

Copy link
Copy Markdown
Contributor

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

  • Ledger (src/db/tokens.ts): token_ledger is append-only and is the source of truth; token_accounts.balance is a cache updated in the same D1 batch under an identical balance + delta >= 0 guard on both statements, so an overdraw touches neither table. Every mutation carries an idempotency key — retries replay the original entry instead of double-applying.
  • Purchases: no payment processor. The client sends ETH directly to a configured treasury address on the existing mint chain; the Worker verifies the transaction + receipt via the public RPC (same verification shape as confirmDeploy/confirmMint in projects/mint.ts) before crediting tokens. UNIQUE(chain_id, tx_hash) stops a tx being redeemed twice.
  • Model registry (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.
  • Inference (src/ai/inference.ts): a single runInference() 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 as GITHUB_MOCK/PINNING_MOCK) returns deterministic canned output, which is what makes the render pipeline exercisable in wrangler dev and CI without live credentials.
  • Render jobs: debit-before-run, so concurrent requests can't outrun a balance; a failed job is refunded in full as a second ledger entry rather than reversing the first. Successful jobs credit 70% of the price to the model's owner (skipped on self-render).
  • Signup grant: new users get 200 free tokens on first sign-in (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 to env.production only. Binding it at the top level forces wrangler dev into remote mode and hard-fails without a CLOUDFLARE_API_TOKEN — that would have broken local dev for every contributor without one, so local dev exercises the render path via RENDER_MOCK=1 instead.
  • New env vars documented in .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 (run npm run migrate:prod when 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 smoke against a local wrangler dev with RENDER_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-run clean for both the default and --env production targets.

Not in this PR

Client/dashboard UI for the token balance, purchase flow, and model catalogue — this PR is the backend service. TOKEN_TREASURY_ADDRESS and ANTHROPIC_API_KEY are 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

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
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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
guillaumelauzier marked this pull request as ready for review August 17, 2026 08:07
@guillaumelauzier
guillaumelauzier merged commit 375740f into main Aug 17, 2026
2 checks passed
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants