Side project

BetCircle

Play-money prediction markets for friend groups. Private circles, market-maker pricing, parlays, head-to-head dares, live prices.

StatusPreview
demo deployment in progress
StackJava 21 · Spring Boot 3.5 · PostgreSQL · Redis · RabbitMQ · WebSocket/STOMP · Testcontainers · React · TypeScript
Updated27 Aug 2026
LinksPrivate repo

Screenshots

Screenshots land with the demo deployment.

What it does

  • Circles — invite-code groups. Every member gets a seeded wallet, a daily bonus with a consecutive-day streak, and a per-circle leaderboard by net worth.
  • Prediction markets — Yes/No or 2–8 outcomes, priced by an automated market maker so the price moves with every bet. Quotes carry a slippage guard; positions can be partially sold; markets lock on schedule and resolve with payouts or a full refund.
  • Parlays — 2–6 legs across open markets, prices locked at placement, settled automatically as the underlying markets resolve. A voided leg drops out of the multiplier.
  • Challenges — head-to-head dares with double escrow. Both sides stake, a judge resolves, and decline, cancel or expiry refund automatically.
  • Live — per-circle activity feed, live price ticks and personal notifications over WebSocket.

Play money only. Virtual coins with no monetary value — no deposits, no withdrawals, no cash-out. It isn’t a gambling service and must not be operated as one.

Why I built it

I wanted to build something fun to use that was hard to get right underneath — where the interesting part isn’t the CRUD but the invariants: money that always reconciles, prices that respond to every trade, settlement that happens after the fact and can’t double-pay. A prediction market for a group chat is a small surface with all of those problems inside it.

Under the hood

Spring Boot modular monolith · PostgreSQL (ledger, markets, users) · Redis (leaderboards, activity feed, idempotency) · RabbitMQ (domain events) · STOMP over WebSocket · React + TypeScript SPA behind nginx.

A bet is one request: lock the market row, quote the cost from the market maker, post a ledger transaction from the wallet to the market’s escrow, update outcome shares and prices, commit. After commit: publish a price tick to the market’s WebSocket topic and a bet.placed event to RabbitMQ. Resolution pays winners from escrow and publishes market.resolved; consumers then settle parlays, write notifications and rebuild the leaderboard asynchronously.

Engineering notes

Double-entry ledger

Every coin movement is a ledger transaction with two or more entries that sum to zero. Balances are denormalised onto accounts for fast reads but are always reconcilable from the ledger, and the test suite checks that they are. User wallets can never go below zero; escrow and treasury accounts absorb the residuals.

Concurrency: lock ordering and a race test

Coin-moving operations take SELECT … FOR UPDATE on the accounts involved, always in ascending id order, so two concurrent bets can’t deadlock each other. A 20-thread integration test hammers one wallet with overlapping bets and asserts two things: no overdraw, and the final balance equals the ledger sum exactly.

Idempotency on anything that moves money

Coin-moving POSTs carry an Idempotency-Key. A replay returns the original response with Idempotency-Replayed: true instead of moving coins twice — the difference between a flaky mobile connection and a duplicate bet.

Market-maker pricing

Markets are priced by a logarithmic market scoring rule (LMSR) with a liquidity parameter, so every trade moves the price and there’s always a counterparty. The cost function is evaluated in log-sum-exp form to stay numerically stable, with BigDecimal at the API edge and golden-value and invariant tests around the engine.

Event-driven settlement

Resolution doesn’t try to do everything in one transaction. It commits the payout, then publishes an event; parlay settlement, notifications, the activity feed and the leaderboard react to it. The cost is eventual consistency on the leaderboard; the benefit is a resolution path that can’t half-complete.

Status & roadmap

Done: backend (auth, circles, wallet and ledger, markets and market maker, parlays, challenges, realtime, notifications) · React front end · Dockerfiles and full-stack compose · CI · unit and Testcontainers integration suite.

In progress: public demo deployment with a seeded demo circle.

Next: route-level code splitting on the front end · API doc refresh ·

Last updated 27 Aug 2026 · ← All work