Atomic Betting

September 2024 → August 2025

Code source

Atomic Betting lets a group of friends or colleagues bet against each other on just about anything: a sports result, an office wager, an open question. Play money only, it's a social game, not a gambling platform.

The idea came out of a joke in class: we thought it would be funny to bet on who'd show up late. I figured I might as well build it for real, to experiment and learn. It's my first project with genuine data modelling and full authentication.

Modelling, the project's real difficulty

This is my first project where the data schema took more thought than the interface. It revolves around a few notions:

  • teams, with memberships carrying a role and a status: a member can be pending, active, a manager or an administrator,
  • bets, for now in the form of option questions, with a type built to be extended later,
  • options, for bets that aren't simple yes/no,
  • transactions, tracing every movement of play money.

The thing I hadn't anticipated, and that forced a rework: a multiple-choice bet and a binary bet don't model the same way, but must behave the same from the balance's point of view. Separating the bet, its options and the stakes took a while to settle.

Odds, a pari-mutuel

The technical part I'm happiest with. Odds aren't set in advance: they emerge from what people stake. An option's odds are the inverse of its share of the total stakes. The more an option is backed, the lower its odds, and the other way around.

On a bet where a hundred coins have been staked in total:

Option Stakes Share Odds Payout for 10 staked
A 60 60 % 1.67 16
B 30 30 % 3.33 33
C 10 10 % 10.0 100

Backing the option nobody believes in pays big; following the crowd pays little. At close, an administrator or manager picks the correct answer, the final odds are frozen from the final distribution of stakes, and each winner receives their stake times the odds, rounded down.

It's the only module I covered with tests, using Vitest, and that's no accident: it's the mathematical core of the app, the place where a mistake shows up immediately on people's balances.

The balance: stored, but kept honest by a ledger

Each membership carries its own coin balance, and I read it directly when rendering a page. A stored balance reads fast, with no recomputation.

The risk with a stored balance is that it drifts from history: an operation interrupted at the wrong moment, and the displayed figure no longer matches the real sum of movements. I guard against that with atomicity. Placing a bet writes two things in one block: a transaction in the ledger, and the decrement of the balance. Both succeed or both fail, never one without the other.

The transaction ledger, for its part, keeps a trace of everything: stakes, winnings, rewards. The balance is the number you read, the ledger is the memory that lets you reconstruct it all and understand how you got there.

Authentication and roles

No passwords to handle: sign-in goes through three OAuth providers, GitHub, Discord and Google, with sessions stored in the database, via Auth.js and its Prisma adapter.

A team is joined through an invitation link, and a membership waits for a lead's approval before it becomes active. Inside, three roles: member, manager, administrator. Sensitive actions, creating a bet or picking its winner, check the role on the server before running, not just by hiding a button in the interface.

The daily reward

To give a reason to come back, each member can claim a daily reward, and a run of consecutive days earns a bonus. The base amount and the streak amount are set per team. Claiming it releases a shower of confetti, a free little touch, but one that raises a smile, which is rather the point.

The pages around the app

A year of on-and-off development left time to build the pages people often skip: a landing page, a features overview, an FAQ, a welcome flow, the privacy and terms pages. Without them, a newcomer lands straight on a sign-in screen with no idea what any of it is.

What the project means to me

It's the pivot project. Before it I wrote scripts and interfaces; it's the first where I had to think about the consistency of state shared between several users, about what happens when two people act at once, and about how to guarantee that a displayed number is true.

Technologies used