Current State
# Current State
Last verified against code: 2026-04-18
Deployment: live at [llatria.carbonaromedia.com](https://llatria.carbonaromedia.com)
This document describes what Llatria actually is today — not what the original design docs said it would be. It is written to be honest about limitations so readers have an accurate mental model.
---
## What's live
A working prediction-market browsing and trading application, running in alpha on a single Hetzner VM in Germany.
- [llatria.carbonaromedia.com](https://llatria.carbonaromedia.com) — frontend
- `/api/v1/*` — backend
- Magic-link authentication via Resend email
- TLS via Caddy with automatic Let's Encrypt certificates
Active resources as of this writing:
| | Count |
|---|---|
| Llatria-native markets (all `ACTIVE` state) | 13 |
| Kalshi markets cached for browsing | ~30,000 |
| Curve trades executed | 258 |
| Registered users | 7 |
All Llatria markets are still in the `ACTIVE` state — no market has reached the $10k graduation threshold, so no market has transitioned to `GRADUATING` or `GRADUATED`. The graduation path exists in code but has not run against production data.
---
## What works end-to-end
### Browse
Homepage shows two sections: "New on Llatria" (creator-launched bonding-curve markets) and "Graduated / On the books" (Kalshi-catalog markets). Each section is organized into categories (Politics, Sports, Finance, Culture, Climate & Science, World, Local, Other). Category tiles support pagination and sort by volume or close time.
Cards on the homepage show YES price, 24-hour volume, close time, and a 24-hour sparkline. Sparklines for Kalshi markets come from a 15-minute background sync worker; Llatria sparklines are generated from the actual curve-trade history via a separate 15-minute refresh loop.
### Search
Full-text search across both Llatria-native markets and Kalshi's catalog, with autocomplete typeahead in the header and a full results page at `/search`. Uses Postgres `tsvector` with GIN indexes; no external search service.
### Auth
Magic-link login only. Flow: user submits email → server generates token, sends link via Resend → user clicks link → server sets HMAC-signed session cookie with 14-day expiry. No passwords, no OAuth, no KYC at this stage.
### Market creation
Any authenticated user can create a market from `/markets/new`. The form collects question, settlement rules (free-text description), category, resolution source, close time (up to 2 years out), initial probability (1–99%), and seed amount ($10–$50). As of commit `8f58b20`, the form shows a live preview of the resulting pool depth and warns when extreme probabilities would make the market effectively un-tradeable.
On submit, the creator's Mock balance is debited, a row is inserted into `markets`, a pool is initialized in `curve_pools`, and a `market.created` event is appended to the `event_log` — all in one transaction.
### Trading
`TradePanel` on the market detail page supports:
- Buying either side by dollar amount (fees shown in advance)
- Selling a full position (partial sells not yet supported)
- Real-time quote fetch (debounced 250ms) as the user adjusts inputs
- Idempotency-safe execution — a single `idempotency_key` per "trade intent" is held stable across button clicks, so double-submits deduplicate server-side
- Server returns the updated pool, which is optimistically merged into the UI
The CPMM math runs server-side in `math/big` — all swap calculations and collateralization invariants are exact integer arithmetic. Every trade is recorded in `curve_trades` with a full JSONB snapshot of the pool before and after, for forensic audit.
### Real-time updates
When a trade executes, the market service publishes a JSON event through an in-process pub/sub broker to all Server-Sent Event subscribers watching that market. Open tabs on the market detail page receive the push within milliseconds — no polling. Heartbeat every 30 seconds keeps proxies from closing idle streams. EventSource auto-reconnects on disconnect and pulls a fresh snapshot when the connection is re-established.
### Charts
The market detail page has a hand-rolled SVG line chart with five range options (1h / 24h / 7d / 30d / all). Points come from a server-side endpoint that buckets `curve_trades` rows by time window and returns closing price per bucket. Hover shows a vertical guide and tooltip; screen-reader ARIA summary describes price trajectory.
### Dashboard
`/me` shows the signed-in user their created markets and their open positions (both YES and NO, joined with market metadata).
### Docs
`/about` is a four-tab documentation page (About, How It Works, Architecture, API Reference) audited against the actual code. Useful for curious visitors and for anchoring external-facing explanations.
---
## What is demo / simulated
Everything that touches custody of real money is mocked.
### Kalshi custody — Mock only
In production, the backend uses an in-memory `kalshi.Mock` for balances, debits, and credits. Unknown users get auto-seeded with $500 of play-money balance on first contact. There is a `kalshi.RealClient` implementation that signs requests with a Kalshi-issued key, but:
- It is not enabled in production (`KALSHI_API_KEY_ID` is unset)
- The market-creation and position-grant methods return `ErrOperationNotSupported` because Kalshi has not published the partner APIs they would need
This is an intentional alpha choice. Every UI element that looks like a real trade is backed by Mock bookkeeping, not by money movement.
### Graduation — coded, untested against real money
The `graduation-worker` service polls for markets whose cumulative notional has crossed $10,000 and drives them through `BeginGraduation → CreateMarket (Kalshi) → GrantPositions (Kalshi) → CompleteGraduation`. The `Mock` client implements all of these with synthetic ticker generation and idempotency tracking.
Because no market has reached $10k yet — and because the Kalshi-side partner API doesn't exist — the graduation flow has never run in production. It's fully tested against the Mock in the `internal/graduation` test suite, but the real-world case is gated behind the Kalshi conversation.
### Resolution — manual only today
The `resolution-worker` service scans for markets past their close time and dispatches to registered adapters. Three adapters exist:
- `manual` — a placeholder that never resolves (markets have to be resolved by the creator via the UI)
- `fake` — returns a fixed outcome, used in tests
- `espn_nfl` — an actual working adapter that queries ESPN's public scores endpoint for NFL games
Only `manual` is wired up to the `Demo (manual resolution)` source in production today. The `espn_nfl` adapter is registered but no market has used it yet.
### No payment rails
There is no ACH, no card processing, no bank integration. "Funding your account" doesn't exist as a concept. Every user gets $500 of synthetic balance and trades with that. Real-money funding is gated behind the Kalshi partnership (Phase 1) or an eventual self-custody / DCM path (Phase 2).
### No KYC
No identity verification. Anyone with an email address can sign up. Under the Phase 1 plan with Kalshi, KYC runs on Kalshi's side; the Llatria-user-to-Kalshi-account link would carry the KYC guarantee. Under a Phase 2 / own-DCM plan, Llatria would need to stand up KYC itself (Persona, Alloy, etc.) — that work is not started.
---
## Architecture at a glance
Monorepo with three deployable surfaces.
### Backend — Go 1.25
```
cmd/
api/ HTTP API server
migrate/ goose-based migration runner
kalshi-sync/ polls Kalshi public endpoints, upserts catalog cache
resolution-worker/ polls for due markets, dispatches to adapters
graduation-worker/ polls for markets past $10k, drives migration
internal/
cpmm/ constant-product bonding curve (math/big, pure)
market/ trade-execution service (transactions + events)
storage/ 8 repositories + query helpers (pgx)
kalshi/ adapter interface + Mock + RealClient
catalog/ browse feed, search, on-demand sparkline fetch
auth/ magic-link + HMAC session cookies
pubsub/ in-memory fan-out broker
api/ HTTP handlers + middleware + SSE
db/ pgxpool setup + migration runner
resolution/ engine + adapter interface
graduation/ worker
```
### Frontend — Next.js 16 + React 19 + TypeScript
```
web/src/
app/ App Router pages
page.tsx homepage
markets/[id]/ market detail
markets/new/ create market form
c/[slug]/ category detail
events/k/[ticker]/ Kalshi event detail
markets/k/[ticker]/ Kalshi market detail
search/ search results
me/ user dashboard
about/ docs page (4 tabs)
login/ magic-link entry
auth/callback/ token exchange
error.tsx global error boundary
components/ 15 components (TradePanel, PriceChart, CatalogCard, ...)
lib/ API client + user helpers + formatters
```
### Database — PostgreSQL 16
8 goose-managed migrations, covering:
- `users`, `markets`, `curve_pools`, `curve_trades`, `positions`, `fee_ledger`, `resolutions`, `event_log` (0001)
- `login_tokens` (0002)
- `kalshi_series`, `kalshi_events`, `kalshi_markets` catalog cache (0003, 0004)
- Full-text search indexes (0005)
- `kalshi_candles` sparkline cache (0006)
- `market.description` field (0007)
- `llatria_candles` Llatria-native sparkline cache (0008)
### Infrastructure
Single VM (Hetzner CX23, 2 vCPU / 4GB RAM, Germany region):
- PostgreSQL, Go API, Next.js web, three workers, Caddy reverse proxy — all on one box
- Systemd unit per service, `EnvironmentFile=/etc/llatria/env`
- Caddy auto-issues Let's Encrypt certs; SSE route has `flush_interval -1` for streaming
---
## Test coverage
- 76+ Go tests across 10 packages. Per-package test databases (`llatria_test_market`, `_api`, `_storage`, ...) enable parallel execution (~5 seconds vs ~30s serial).
- 58 Vitest unit + component tests covering formatters, sparkline, catalog card, search bar, price chart, recent trades card.
- 5 Playwright E2E tests covering homepage, search typeahead, search page, about tabs, login demo explainer.
CI is not yet automated — tests run locally via `make test` and `npm run build && npx vitest run`. Adding a GitHub Actions pipeline is an open item.
---
## Code review status
A full four-phase code review was completed on 2026-04-18, producing 94 findings (1 CRITICAL, 11 HIGH, 40 MEDIUM, 40 LOW/NIT) documented in [`Docs/25 Code Review Findings.md`](../Docs/25%20Code%20Review%20Findings.md). 33 of the findings were fixed and shipped to production. The CRITICAL item — an infinite `/v1/auth/me` fetch loop in the UserChip component — is resolved.
Remaining items are split into:
- Blocked on Kalshi partnership decisions (e.g. GrantPositions idempotency semantics, outbox pattern for failed credits)
- Launch prerequisites (CSRF middleware, Sentry integration, stuck-graduation recovery)
- Polish (accessibility, a11y on chart, onboarding UX)
---
## What's deliberately missing
Things that don't exist today, with the reason each is absent:
| Missing | Reason |
|---|---|
| Real money movement | Requires Kalshi partnership (Path A) or own DCM (Path B) |
| KYC flow | Kalshi handles this in Phase 1; Llatria would add Persona/Alloy in Phase 2 |
| Email notifications (beyond login) | Not yet built; Resend integration exists but only sends magic links |
| CSRF middleware | Open item in code review; cookies use `SameSite=Strict` as partial defense |
| Observability (Sentry, Prometheus) | Open item; no production visibility beyond stdout logs |
| Multi-AZ / DR | Single VM; acceptable for alpha, replace before launch |
| Mobile native apps | Web-only; mobile browser UX is adequate but not optimized |
| Payout / withdrawal UX | Not relevant until real money exists |
---
## Summary
What's built: a functioning prediction-market application with real-time trading, search, creator economics, resolution infrastructure, and a graduation pipeline. 4,000+ lines of Go, 4,000+ lines of TypeScript, 76+ backend tests, 58 frontend tests, deployed and running.
What's honest to say: the product works, the math is correct, and the architecture is intentional. But no real money has touched Llatria, and the path to real money runs through regulated infrastructure that Llatria does not yet own — either Kalshi's, or its own DCM. That gap is the subject of [`03 Strategic Paths.md`](./03%20Strategic%20Paths.md).

Replies