A live play-money market where every World Cup footballer is a tradeable stock in one globally-consistent market. Price moves from two forces: the crowd buying/selling along a bonding-curve AMM, and live match events (a goal spikes the player's "fundamental"). The hard guarantee — no double-spend, exactly-once settlement, and strong consistency through a goal-moment write spike — is the product.
Live: region A (us-east-1) https://goal-markets.vercel.app · region B (us-east-2) https://hkazvhm6ry.us-east-2.awsapprunner.com
Client → Vercel Edge → Vercel Functions (API)
├─ DynamoDB rate-limit counters (fail-open)
├─ Aurora DSQL STAGE 1: reserve (per-user tx) ┐ active-active
└─ SQS FIFO ──────→ Lambda settler ── STAGE 2: settle ────┘ multi-region
(group=player_id) (per-player writer, us-east-1) (us-east-1 + us-east-2,
EventBridge → Ops Lambda → DSQL (windowed leaderboard rollup + reconcile) witness us-west-2)
EventBridge → Feed Lambda → football-data.org top-scorers → goal events → SQS FIFO
OTel (@vercel/otel + settler) → AWS X-Ray (traceparent rides the SQS message)
- Aurora DSQL — strongly consistent, serverless, active-active multi-region. The ACID core (balance, settlement, holdings). OCC: clashing writers to a row abort at COMMIT (retry). Money is
numeric, PKs are UUIDv7 (no sequences/FK/triggers). - SQS FIFO + Lambda settler —
MessageGroupId = player_idmakes the platform the lock: one in-flight message per player → one writer of the hotplayer_staterow → 0 OCC conflicts under the goal-moment stampede. Idempotent viaUNIQUE(fills.order_id). - Two-stage settlement — intake reserves on the user's own rows (no double-spend) and enqueues; the settler replays the bonding curve and writes the authoritative state. Append-only orders/fills → no write contention on the spike.
- Reads are served from a cached
current_price/ leaderboard rollup (~1s stale, poll-friendly). - Multi-region — both regional endpoints are one strongly-consistent logical DB; region A is on Vercel, region B is the same app on AWS App Runner; the settler stays single-region so single-writer-per-player is global.
- Live goal feed — a
goal-feedLambda (EventBridge, 1/min) polls football-data.org's free top-scorers endpoint and diffs each player's cumulative goal count; a rise means he scored, so it enqueues a goal event to that player's SQS group. It also mirrors in-play matches into alive_matchesread model for the UI ticker. - Keyless — no long-lived AWS keys at runtime: Vercel assumes an IAM role via OIDC federation, App Runner uses an instance role, the Lambdas use execution roles, and DSQL authenticates with short-lived scoped IAM tokens (the runtime
approle holds DML only).
Full design write-up, with diagrams: WRITEUP.md.
pnpm install
cp .env.local.example .env.local # set DSQL_HOST etc. (see below); AWS creds via `aws login`
pnpm dev # http://localhost:3000
pnpm dev:otel # dev server with OpenTelemetry → X-RayEnv (local uses your aws login creds; prod is keyless — Vercel assumes a role via AWS_ROLE_ARN/OIDC, App Runner uses its instance role):
DSQL_HOST, AWS_REGION, SQS_QUEUE_URL, RATELIMIT_TABLE, DSQL_USER (app runtime / admin migrations), FOOTBALL_DATA_API_KEY (live goal feed).
Ops scripts: pnpm db:migrate (Umzug), pnpm db:seed, pnpm build:lambda, pnpm smoke, pnpm loadtest.
Push to main → everything ships, only what changed:
- Region A — Vercel auto-deploys via its GitHub integration.
- AWS —
.github/workflows/deploy.yml(GitHub OIDC →goal-cirole, no stored keys):infra/migrations/**→ apply DSQL migrationslambda/**orlib/**→ rebuild + redeploy the settler/ops Lambdas- app code → build + push the region-B container to ECR → App Runner auto-deploys
So a schema or API change is just a commit — no manual deploys.