Friendly prediction markets for the Microsoft developer ecosystem. Bet shipcoins (play tokens) on conference announcements, product renamings, GA dates, and ship slips. No real money. No KYC. No Glücksspielgesetz exposure. Settle stakes in person, with coffee.
- One-shot, full-stack build. .NET 9 Minimal API + React 18 SPA + Azure infra, all in this repo.
- LMSR-priced markets with sub-second live updates over Server-Sent Events.
- Binary and categorical (2–8 outcome) markets share the exact same math path.
- AI assistant drafts well-formed market suggestions and falsifiable resolution criteria via Azure OpenAI (gpt-4o-mini).
- Disputes with 72h voting windows for the cases where the creator can't or won't resolve.
- Mobile-first dark UI, designed for a 390-px viewport.
- Production-shaped: optimistic concurrency on trades, RFC 7807 errors, structured Serilog → App Insights, RBAC, managed identity end-to-end.
Mobile:
┌────────────────────┐ HTTPS ┌──────────────────────┐
│ React SPA (Vite) │ ───────────────► │ .NET 9 Minimal API │
│ Azure Static Web │ ◄────── SSE ──── │ Azure App Service │
│ Apps (West EU) │ │ (West EU, B1 Linux) │
└────────────────────┘ └──────────┬───────────┘
│
┌──────────────┬────────────┼──────────────┐
│ │ │ │
┌──────▼─────┐ ┌─────▼─────┐ ┌────▼────┐ ┌──────▼─────┐
│ Azure SQL │ │ Azure │ │ App │ │ Key Vault │
│ DB Basic │ │ OpenAI │ │ Insights │ │ + ACS Mail │
└────────────┘ │ gpt-4o- │ └─────────┘ └────────────┘
│ mini │
└───────────┘
- Backend (
api/): three projects —BetPoint.Domain(pure math + entity models),BetPoint.Infrastructure(EF Core + AES-GCM crypto),BetPoint.Api(Minimal API + Microsoft.Identity.Web + Swashbuckle + Serilog + native SSE + background jobs). - Frontend (
web/): React 18 + Vite + Tailwind + react-query + zustand-ready + MSAL React +@microsoft/fetch-event-source. - Infra (
infra/): Bicep + Azure Verified Modules. - CI (
.github/workflows/): two pipelines, OIDC federated credentials, no client secrets in repo.
The full design lives in docs/plan.md. Contributor rules and the bd-beads workflow live in CLAUDE.md.
Prereqs: .NET 9 SDK, Node 20+, Python 3.10+ (for the LMSR reference notebook only).
# 1. Backend — builds Domain, Infrastructure, Api; runs migrations on first startup.
cd api
dotnet test BetPoint.sln # 107 tests should pass
dotnet run --project src/BetPoint.Api # http://localhost:5080
# 2. Frontend — Vite + Tailwind + react-query
cd ../web
npm install
npm run dev # http://localhost:5173
# 3. Open http://localhost:5173 — DevAuth gives you a synthetic admin user
# seeded with 1000 shipcoins.Dev mode runs without MSAL — the SPA submits requests without bearer tokens, and the API authenticates them via the DevAuth policy scheme. To switch to real AAD auth, fill web/.env with your app registration ids, set VITE_DEV_AUTH=false, and set DevAuth:Enabled=false in api/src/BetPoint.Api/appsettings.json. The Production safety guard in Program.cs refuses to start if either DevAuth is on or the AzureAd ids are still placeholders.
# Run the LMSR Python reference (cross-validates the .NET math)
python3 docs/lmsr_reference.py
# Regenerate the OpenAPI TS types after the API contract changes
cd web && npx openapi-typescript ./openapi.json -o src/lib/api/schema.ts
# Capture fresh screenshots for the README (both servers must be running)
cd web && node scripts/screenshots.mjs
# Find available beads work and claim one
bd ready
bd update WillItShip-<id> --claimdotnet test # 107 tests across Domain/Infra/Api projectsBreakdown:
BetPoint.Domain.Tests— 84 tests, including:- LMSR property tests: uniform prices for N=2/3/4/8, sum-to-one on asymmetric books, large-q stability (10000-shares without overflow), path independence, round-trip net-zero, max loss values, default liquidity.
- LMSR cross-validation: 19 canonical values matched 1e-10 against
docs/lmsr_reference.py(independent Python implementation, per plan §5). - Trade rules: no-shorting, slippage cap, non-finite input rejection.
BetPoint.Infrastructure.Tests— 10 tests: encryptor round-trip (dev fallback + configured key), tampered-payload rejection, fail-fast on non-Development without configured key, DbContext seed and Markets row-version rotation, composite-key uniqueness on Position, optimistic concurrency conflict.BetPoint.Api.Tests— 12 tests withWebApplicationFactory: full trade lifecycle (cost matches Python reference), slippage 409, no-shorting 400, insufficient-balance 400, Invalid resolve refunds + zeroes positions, comment sanitisation strips<script>while keeping**bold**, AI suggest returns ≥ 3, AI quota 429, categories seed, and the load-bearing stale-RowVersion 409 concurrency test.
30 endpoints, base path /api. Bearer required except /api/health* and /api/categories. JSON throughout; errors follow RFC 7807. Full machine-readable spec at web/openapi.json.
auth POST /api/auth/me
users GET /api/users/me PATCH /api/users/me
GET /api/users/leaderboard GET /api/users/{id}
markets GET /api/markets POST /api/markets
GET /api/markets/{id} PATCH /api/markets/{id} DELETE /api/markets/{id}
GET /api/markets/{id}/trades GET /api/markets/{id}/prices
POST /api/markets/{id}/trade # LMSR-priced, RowVersion-guarded
comments GET /api/markets/{id}/comments POST /api/markets/{id}/comments
PATCH/DELETE /api/markets/{id}/comments/{commentId}
resolve POST /api/markets/{id}/resolve # creator or admin
dispute POST /api/markets/{id}/dispute POST /api/markets/{id}/dispute/vote
GET /api/markets/{id}/dispute
me GET /api/me/portfolio GET /api/me/trades
GET /api/me/notifications POST /api/me/notifications/read
GET /api/me/activity
public ref GET /api/categories GET /api/activity
ai POST /api/ai/suggest-markets POST /api/ai/draft-resolution-criteria
admin POST /api/admin/markets/{id}/force-resolve
POST /api/admin/users/{id}/adjust-balance
POST /api/admin/users/{id}/toggle-admin
GET /api/admin/audit
sse GET /api/sse/markets/{id} # text/event-stream
health GET /api/health GET /api/health/ready
Realtime event names: connected, price-updated, trade-executed, market-resolved, market-disputed, comment-added.
See infra/README.md for the step-by-step deployment guide. In short:
- Create an Entra ID app registration for the API (Single-page application redirect URIs for
https://<swa-url>andhttp://localhost:5173, exposed scopeapi://<api-app-id>/access_as_user, signInAudienceAzureADandPersonalMicrosoftAccount). az login, thenaz deployment group create --resource-group rg-willitship-prod-weu --template-file infra/main.bicep --parameters infra/parameters.prod.bicepparam(target time: under 15 minutes from scratch).- Configure GitHub Actions OIDC federated credentials against the deployment principal.
- Fill the App Service settings with the real client id and the Production Encryption key (
Encryption:LocalKey, base64 32 bytes). - Push to
main. The two workflows under.github/workflows/(api.yml,web.yml) deploy the API and the SPA via OIDC.
Cost ballpark: about EUR 35–55/month for B1 App Service Plan + SQL Basic + SWA Standard + Log Analytics + a light OpenAI deployment. Optimised for a private friends-of-friends use case, not for a public product.
- ✅ Backend complete, 30 endpoints, 107 tests pass.
- ✅ SPA complete, 11 pages, live SSE, dark default, mobile-friendly.
- ✅ AI integration with dev-mode FakeAiClient + production Azure OpenAI client.
- ✅ Background jobs: auto-close, market-closing notifications, stale-resolution auto-dispute, dispute tally.
- ⚙️ Bicep + GitHub Actions: skeletons land in this build; final hardening + first end-to-end Azure deploy tracked as beads.
- ⚙️ Open follow-ups visible via
bd list --status=open(deferred architect/security findings are P2/P3 polish).
Run bd stats for the live counts.
Read CLAUDE.md first. It encodes 12 non-negotiable invariants from the plan: LMSR as the gate, server-as-source-of-truth, slippage protection, optimistic concurrency, SSE-only on market detail, no-shorting, server-side markdown sanitisation, OpenAPI as contract, no em dashes in user-facing strings, encrypted Teams webhooks, AI budget caps. Tracking lives in bd — do not use TodoWrite/TaskCreate/markdown TODOs.
Real money. Order-book trading. Native mobile apps. M365 Copilot agent / Teams app (incoming webhook only). Multi-region, geo-redundancy. Localisation (English only). Public marketing.
Working name: WillItShip. Code namespace: BetPoint.* (Domain, Infrastructure, Api). Token: shipcoins, behind a single constant. Primary colour: violet/indigo (Tailwind ship-*).








