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/:id: 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
|
|
|
/**
|
|
|
|
|
* Development-only routes. Not mounted in production.
|
|
|
|
|
*/
|
|
|
|
|
import { Router, type Request, type Response } from "express";
|
2026-03-19 05:44:35 +00:00
|
|
|
import { eq, or } from "drizzle-orm";
|
|
|
|
|
import { db, invoices, sessions, bootstrapJobs } from "@workspace/db";
|
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/:id: 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
|
|
|
import { lnbitsService } from "../lib/lnbits.js";
|
|
|
|
|
|
|
|
|
|
const router = Router();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /dev/stub/pay/:paymentHash
|
2026-03-19 05:44:35 +00:00
|
|
|
* Simulates an invoice being paid.
|
|
|
|
|
* - In LNbits stub mode: marks the invoice as paid in the in-memory store.
|
|
|
|
|
* - In real LNbits mode (FakeWallet): looks up the BOLT11 from DB and pays it via LNbits.
|
|
|
|
|
* Searches the invoices, sessions, and bootstrapJobs tables.
|
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/:id: 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
|
|
|
*/
|
2026-03-19 05:44:35 +00:00
|
|
|
router.post("/dev/stub/pay/:paymentHash", async (req: Request, res: Response) => {
|
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/:id: 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
|
|
|
const { paymentHash } = req.params as { paymentHash: string };
|
2026-03-19 05:44:35 +00:00
|
|
|
|
|
|
|
|
if (lnbitsService.stubMode) {
|
|
|
|
|
lnbitsService.stubMarkPaid(paymentHash);
|
|
|
|
|
res.json({ ok: true, paymentHash, mode: "stub" });
|
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/:id: 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
|
|
|
return;
|
|
|
|
|
}
|
2026-03-19 05:44:35 +00:00
|
|
|
|
|
|
|
|
// Real LNbits mode — look up the BOLT11 in all tables that store invoices
|
|
|
|
|
let bolt11: string | null = null;
|
|
|
|
|
|
|
|
|
|
// 1. Check job eval/work invoices table
|
|
|
|
|
const jobInvoiceRows = await db.select().from(invoices).where(eq(invoices.paymentHash, paymentHash)).limit(1);
|
|
|
|
|
if (jobInvoiceRows.length > 0) {
|
|
|
|
|
bolt11 = jobInvoiceRows[0]!.paymentRequest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Check sessions table (deposit + topup invoices)
|
|
|
|
|
if (!bolt11) {
|
|
|
|
|
const sessionRows = await db.select().from(sessions).where(
|
|
|
|
|
or(eq(sessions.depositPaymentHash, paymentHash), eq(sessions.topupPaymentHash, paymentHash))
|
|
|
|
|
).limit(1);
|
|
|
|
|
if (sessionRows.length > 0) {
|
|
|
|
|
const s = sessionRows[0]!;
|
|
|
|
|
bolt11 = s.depositPaymentHash === paymentHash
|
|
|
|
|
? s.depositPaymentRequest
|
|
|
|
|
: (s.topupPaymentRequest ?? null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. Check bootstrapJobs table
|
|
|
|
|
if (!bolt11) {
|
|
|
|
|
const bjRows = await db.select().from(bootstrapJobs).where(eq(bootstrapJobs.paymentHash, paymentHash)).limit(1);
|
|
|
|
|
if (bjRows.length > 0) {
|
|
|
|
|
bolt11 = bjRows[0]!.paymentRequest;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!bolt11) {
|
|
|
|
|
res.status(404).json({ error: "Invoice not found for paymentHash" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await lnbitsService.payInvoice(bolt11);
|
|
|
|
|
res.json({ ok: true, paymentHash, mode: "real" });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
res.status(500).json({ error: err instanceof Error ? err.message : "Failed to pay invoice" });
|
|
|
|
|
}
|
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/:id: 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
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export default router;
|