Single hardcoded binary market on Hanson's LMSR. In-memory, no DB/blockchain/auth.
- Backend: Node.js + NestJS
- Frontend: React + TypeScript + Vite + Tailwind + shadcn/ui
# Terminal 1
cd backend && npm install && npm run start:dev # http://localhost:3000
# Terminal 2
cd frontend && npm install && npm run dev # http://localhost:5173Open http://localhost:5173 (Vite proxies /api/* to the backend, no CORS setup needed).
Tests: cd backend && npm test (pricing module — golden vector, invariants, overflow, path-independence).
- Cost function uses log-sum-exp; spot price uses the stable sigmoid form — both avoid overflow at large
q. priceImpactisn't defined in the brief — used(avgPrice - spotPriceBefore) / spotPriceBefore.- API casing matches the brief literally: snake_case for
GET /market(q_yes,p_yes), camelCase for/quote(avgPrice). Internal code is camelCase everywhere; only the response boundary maps. - Shares can be fractional (LMSR is continuous), capped at 2 decimals, validated on both ends.
- No explicit lock on
trade()— it's fully synchronous with noawaitin the critical section, so JS already prevents interleaving here; would need one if persistence/async I/O were added later. - Sell (bonus) reuses
/quoteand/tradevia an optionalside: 'BUY'|'SELL'field (negative delta), not a separate endpoint. - Resolve/redeem (bonus):
resolveis one-time (OPEN → RESOLVED) and blocks further trading;redeempays out winning-side holdings at $1/share into collateral and zeroes both sides (losing shares become worthless).redeemis naturally idempotent — a second call pays out $0 since holdings are already zero.
- Move shared market/portfolio state into a Zustand store instead of prop-drilling through
App.tsx, so mutations (trade/resolve/redeem) can trigger a refetch from anywhere without threading callbacks down the tree. - Playwright e2e for the quote → confirm → portfolio flow.
- Price-history chart.
- Single demo user; state resets on server restart (as allowed by the brief).
b = 100fixed, not configurable.