This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
token-gated-economy/replit.md
alexpaynex 4e8adbcb93 Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
  InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.

Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
  invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/🆔 fetches job, calls advanceJob() helper, returns state-
  appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
  - awaiting_eval_payment: checks LNbits, atomically marks paid + advances
    state via optimistic WHERE state='awaiting_eval_payment'; runs
    AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
  - awaiting_work_payment: same pattern for work invoice, runs
    AgentService.executeWork, advances to complete
  - Any agent/LNbits error transitions job to failed

Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }

Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'

Route index updated to mount all three routers

replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger

End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00

7.4 KiB

Workspace

Overview

pnpm workspace monorepo using TypeScript. Each package manages its own dependencies.

Stack

  • Monorepo tool: pnpm workspaces
  • Node.js version: 24
  • Package manager: pnpm
  • TypeScript version: 5.9
  • API framework: Express 5
  • Database: PostgreSQL + Drizzle ORM
  • Validation: Zod (zod/v4), drizzle-zod
  • API codegen: Orval (from OpenAPI spec)
  • Build: esbuild (CJS bundle)

Structure

artifacts-monorepo/
├── artifacts/              # Deployable applications
│   └── api-server/         # Express API server
├── lib/                    # Shared libraries
│   ├── api-spec/           # OpenAPI spec + Orval codegen config
│   ├── api-client-react/   # Generated React Query hooks
│   ├── api-zod/            # Generated Zod schemas from OpenAPI
│   └── db/                 # Drizzle ORM schema + DB connection
├── scripts/                # Utility scripts (single workspace package)
│   └── src/                # Individual .ts scripts, run via `pnpm --filter @workspace/scripts run <script>`
├── pnpm-workspace.yaml     # pnpm workspace (artifacts/*, lib/*, lib/integrations/*, scripts)
├── tsconfig.base.json      # Shared TS options (composite, bundler resolution, es2022)
├── tsconfig.json           # Root TS project references
└── package.json            # Root package with hoisted devDeps

TypeScript & Composite Projects

Every package extends tsconfig.base.json which sets composite: true. The root tsconfig.json lists all packages as project references. This means:

  • Always typecheck from the root — run pnpm run typecheck (which runs tsc --build --emitDeclarationOnly). This builds the full dependency graph so that cross-package imports resolve correctly. Running tsc inside a single package will fail if its dependencies haven't been built yet.
  • emitDeclarationOnly — we only emit .d.ts files during typecheck; actual JS bundling is handled by esbuild/tsx/vite...etc, not tsc.
  • Project references — when package A depends on package B, A's tsconfig.json must list B in its references array. tsc --build uses this to determine build order and skip up-to-date packages.

Root Scripts

  • pnpm run build — runs typecheck first, then recursively runs build in all packages that define it
  • pnpm run typecheck — runs tsc --build --emitDeclarationOnly using project references

Environment Variables & Secrets

Automatically provisioned (do not set manually)

Secret Purpose
AI_INTEGRATIONS_ANTHROPIC_BASE_URL Replit AI Integrations proxy base URL for Anthropic
AI_INTEGRATIONS_ANTHROPIC_API_KEY Replit AI Integrations proxy API key (dummy value, auto-managed)
DATABASE_URL PostgreSQL connection string (Replit-managed)
SESSION_SECRET Express session secret (Replit-managed)

Required secrets (set via Replit Secrets tab)

Secret Description Example
LNBITS_URL Base URL of your LNbits instance https://legend.lnbits.com
LNBITS_API_KEY Invoice/Admin API key from your LNbits wallet a3f...

Note: If LNBITS_URL and LNBITS_API_KEY are absent, LNbitsService automatically runs in stub mode — invoices are simulated in-memory and can be marked paid via svc.stubMarkPaid(hash). This is intentional for development without a Lightning node.

Packages

artifacts/api-server (@workspace/api-server)

Express 5 API server. Routes live in src/routes/ and use @workspace/api-zod for request and response validation and @workspace/db for persistence.

  • Entry: src/index.ts — reads PORT, starts Express
  • App setup: src/app.ts — mounts CORS, JSON/urlencoded parsing, routes at /api
  • Routes: src/routes/index.ts mounts sub-routers; src/routes/health.ts exposes GET /health (full path: /api/health)
  • Depends on: @workspace/db, @workspace/api-zod
  • pnpm --filter @workspace/api-server run dev — run the dev server
  • pnpm --filter @workspace/api-server run build — production esbuild bundle (dist/index.cjs)
  • Build bundles an allowlist of deps (express, cors, pg, drizzle-orm, zod, etc.) and externalizes the rest

lib/db (@workspace/db)

Database layer using Drizzle ORM with PostgreSQL. Exports a Drizzle client instance and schema models.

  • src/index.ts — creates a Pool + Drizzle instance, exports schema
  • src/schema/index.ts — barrel re-export of all models
  • src/schema/<modelname>.ts — table definitions with drizzle-zod insert schemas (no models definitions exist right now)
  • drizzle.config.ts — Drizzle Kit config (requires DATABASE_URL, automatically provided by Replit)
  • Exports: . (pool, db, schema), ./schema (schema only)

Production migrations are handled by Replit when publishing. In development, we just use pnpm --filter @workspace/db run push, and we fallback to pnpm --filter @workspace/db run push-force.

lib/api-spec (@workspace/api-spec)

Owns the OpenAPI 3.1 spec (openapi.yaml) and the Orval config (orval.config.ts). Running codegen produces output into two sibling packages:

  1. lib/api-client-react/src/generated/ — React Query hooks + fetch client
  2. lib/api-zod/src/generated/ — Zod schemas

Run codegen: pnpm --filter @workspace/api-spec run codegen

lib/api-zod (@workspace/api-zod)

Generated Zod schemas from the OpenAPI spec (e.g. HealthCheckResponse). Used by api-server for response validation.

lib/api-client-react (@workspace/api-client-react)

Generated React Query hooks and fetch client from the OpenAPI spec (e.g. useHealthCheck, healthCheck).

artifacts/api-server — Timmy API endpoints

Payment-gated job flow

BASE="https://${REPLIT_DEV_DOMAIN}"  # or http://localhost:8080 in dev

# 1. Create a job (returns eval invoice)
curl -s -X POST "$BASE/api/jobs" \
  -H "Content-Type: application/json" \
  -d '{"request": "Write a haiku about lightning payments"}'
# → {"jobId":"…","evalInvoice":{"paymentRequest":"lnbcrt10u1…","amountSats":10}}

# 2. Poll status (returns eval invoice while unpaid)
curl -s "$BASE/api/jobs/<jobId>"

# 3. (Stub mode only) Mark eval invoice paid
curl -s -X POST "$BASE/api/dev/stub/pay/<paymentHash>"

# 4. Poll again — auto-advances to awaiting_work_payment, returns work invoice
curl -s "$BASE/api/jobs/<jobId>"

# 5. (Stub mode only) Mark work invoice paid
curl -s -X POST "$BASE/api/dev/stub/pay/<workPaymentHash>"

# 6. Poll again — auto-advances to complete, returns result
curl -s "$BASE/api/jobs/<jobId>"
# → {"jobId":"…","state":"complete","result":"…"}

Job states: awaiting_eval_paymentevaluatingawaiting_work_paymentexecutingcomplete | rejected | failed

Free demo endpoint (rate-limited: 5 req/hour per IP)

curl -s "$BASE/api/demo?request=Explain+proof+of+work+in+one+sentence"
# → {"result":"…"}

Dev-only stub payment trigger

POST /api/dev/stub/pay/:paymentHash — marks a stub invoice paid in-memory. Only available in development (NODE_ENV !== 'production').

scripts (@workspace/scripts)

Utility scripts package. Each script is a .ts file in src/ with a corresponding npm script in package.json. Run scripts via pnpm --filter @workspace/scripts run <script>. Scripts can import any workspace package (e.g., @workspace/db) by adding it as a dependency in scripts/package.json.