Marking the winner has to settle every open stake at once, or the ledger and the leaderboard disagree
A prediction platform for football fixtures where the operator marks a result once and every open stake, balance and standing resolves from that single action. A settlement that happens in two steps is a settlement that can happen halfway.
The shape of the work
E-commerce & Retail
18 weeks
Dedicated team
Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.
The hard problem
Their previous system settled in stages. An operator marked a result, a job worked through the stakes, and balances updated as it went. When the job stalled (and it did stall), some users had been paid and some hadn't, the leaderboard showed a standing the balances didn't support, and support had no way to tell which half of the settlement had run.
A staged settlement can stop between stages, and the state it stops in can't be described: some users paid, some not, a leaderboard showing standings the balances didn't support, and no way to tell from the data which half had run. Balances were also a stored number that could be edited, so the repair for a bad settlement was to type a corrected figure, which erased the evidence.
One settlement action that either completes or doesn't happen. Marking a winner resolves every open stake on that market inside a single transaction, writes the wallet movements, and recomputes the standings from the stake records, never from a running total that can drift.
How the pieces fit
- 01
Made settlement a single transaction, so a stalled job can't leave half the market paid
Marking a winner resolves every open stake, writes the wallet movements and invalidates standings inside one transaction, so there is no state between.
- 02
Recomputed standings from stake records every time, with no running total to drift
Standings recompute from the stake records every time, so a failed job costs a retry instead of a reconciliation.
- 03
Built the manual gateway as a first-class path, never a fallback bolted onto the automated one
Manual deposits were roughly a third of volume, so treating them as an exception would have made a third of the ledger unauditable.
- 04
Kept every wallet movement as an immutable row, so a balance is a sum and never an edit
The wallet is an append-only ledger and the balance is its sum, so nothing writes a balance and a correction is a reversing entry, never an overwrite.
- 05
Separated the operator who marks a result from the operator who approves a deposit
The separation was the operator's own requirement and turned out to be what made a disputed settlement answerable at all.
The system we were asked to build
The operator runs prediction contests on football fixtures. We built the platform: leagues and matches, the questions users answer, the wallet the stakes come out of, deposits through both automated and manual gateways, and the back office where results are marked.
An operator running prediction contests on football fixtures, whose previous platform settled markets in stages: an operator marked a result and a background job worked through the stakes. The engagement was commissioned after a weekend where that job stalled mid-market and support spent two days establishing which users had been paid.
Full-Stack Engineering
Settlement is atomic
Marking a winner resolves every open stake in one transaction. There's no state where half the market has been paid.
Marking a winner resolves every open stake on that market inside one database transaction: outcomes written, wallet movements recorded, standings invalidated. There's no state in which half the market has been paid. The transaction either completes or the market stays open, and that's the only acceptable arrangement when the alternative is a partially settled market and no way to tell which half.
- Every stake on a market resolved in one transaction
- No partially-paid state can exist
- Failure leaves the market open, never half-settled
The settlement desk after the winner is marked: every open stake on the market resolved by one transaction, six payouts each the stake multiplied by the odds it was taken at, none left open, and the audit trail from the mark to the commit.
Balances are sums
One player's statement as support reads it: every line a movement and the balance a running sum, the query that derives it, and a disputed payout answered with a reversing entry and a re-post instead of an edit.
The wallet is an append-only ledger of movements, so a balance can always be traced to the events behind it.
The wallet is an append-only ledger of movements and the balance is their sum, so a figure can always be traced to the events behind it. Nothing writes a balance directly. A disputed amount is answered by showing the movements instead of opening an investigation, and a bug that produced a wrong entry is corrected with a reversing entry, so history is never overwritten.
- Append-only movement ledger; the balance is derived
- Nothing writes a balance directly
- Corrections are reversing entries, never overwrites
Manual deposits are first-class
A bank transfer approved by an operator produces the same ledger record as an automated one, differing only in who confirmed it.
A bank transfer approved by an operator produces the same kind of ledger record as an automated deposit, differing only in the confirmation source and the approver's identity. Treating manual money as a second-class path is how reconciliation breaks: the two drift, and only one of them gets trusted. Here there is one deposit concept with two ways of being confirmed.
- Manual and automated deposits produce identical ledger records
- They differ only in confirmation source and approver
- One deposit concept, so nothing drifts out of reconciliation
The deposits desk: bank transfers waiting on a named approver beside card deposits in the same table, and the ledger row both paths write, differing only in confirmation source and who confirmed it.
The leaderboard recomputed from stake records at read time, with no running total kept, one player's points traced to the records they were counted from, and a recompute that timed out simply retried.
Standings are derived
Leaderboards are recomputed from stake records, so there's no running total to drift when a job fails.
Leaderboards are recomputed from the stake records, because a running total is only ever as correct as the last job that updated it, and a failed job leaves a wrong number that nobody can tell from a right one. Recomputation is cached for reads, but the cache is derived and can always be thrown away and rebuilt.
- Standings recomputed from stake records, never accumulated
- A failed job can't leave an undetectably wrong figure
- The cache is derived and disposable by design
Split operator duties
Marking results and approving deposits are separate permissions, because one person holding both is how disputes become unanswerable.
Marking results and approving deposits are separate permissions that can't be held by the same operator account. One person able to both decide an outcome and approve the money against it makes every dispute unanswerable, whatever actually happened. The separation is what lets the platform show its work when challenged.
- Result marking and deposit approval are separate permissions
- No single account can hold both
- Disputes are answerable because the duties were split
Operators and roles: marking results and approving deposits held by different accounts, a grant that would have put both on one operator refused, and the day's audit log naming who did each.
Phase by phase
Phase 1: Fixtures And Markets
Leagues, Matches, Questions
Modeled leagues, fixtures and the questions users answer against them, so a market belongs to a match and a match belongs to a league. That's what makes bulk settlement addressable at all.
- Leagues
- Fixtures
- Question Markets
Phase 2: The Wallet
Movements, Not Balances
Built the wallet as an append-only ledger of movements. A balance is the sum of its rows, so no code path can quietly set a number, and every figure on screen can be traced to the event that caused it.
- Movement Ledger
- Balance Derivation
- Statement View
Phase 3: Deposits
Two Gateways, One Path
Built the automated gateway and the manual one against the same deposit record. A manually approved transfer isn't a special case in the ledger; it differs only in who confirmed it and when.
- Automated Gateway
- Manual Approval Queue
- Deposit Audit
Phase 4: Settlement
One Action, All Or Nothing
Built the settlement desk. The operator selects the winning answer, the platform resolves every open stake on that market in one transaction, and the standings are recomputed from the resulting records.
- Settlement Desk
- Atomic Resolution
- Standings Recompute
The failure path on the same machinery: an abandoned fixture voided, all twelve stakes across its questions returned in one transaction at the amounts they were taken, and standings unmoved.
What it carries now
1
Steps to settle a market
0
Partially settled markets
None
Balance rows that are editable
2
Deposit paths sharing one ledger
Steps to settle a market is one. Partially settled markets is zero by construction: the transaction either completes or the market stays open. Editable balance rows is none, because the wallet is append-only. Deposit paths sharing one ledger is a count, and describes manual and automated deposits producing the same record shape.
Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.
What the architecture settled
- 01
A settlement that runs in stages is a settlement that can stop between them, and the state it stops in is unexplainable.
A staged settlement can halt between stages, and the resulting state is worse than wrong: it's undescribable. Nobody could say which stakes had been paid.
- 02
Derive standings from the records instead of maintaining a total, and a failed job costs you a retry, not a reconciliation.
Deriving standings makes a failed job cheap: rerun it. Maintaining a total makes the same failure a reconciliation nobody can perform confidently.
- 03
Manual payment paths deserve the same ledger as automated ones. Treating them as an exception is what makes them unauditable.
Treating manual deposits as an exception is what makes them unauditable. Giving them the same ledger record keeps the two paths from drifting.
- 04
Separating who marks results from who approves money isn't bureaucracy. It's the only thing that makes a dispute answerable.
The operator asked for this before we proposed it, having already spent a weekend unable to answer a customer who believed a market had been marked wrongly.
How the work was run
The operator's support team joined the first two weeks, because they were the people who could describe what a half-settled market looked like from the outside. Their ticket history set the acceptance criteria: every figure on a user's screen has to be traceable to the events behind it, and no screen may show a number the ledger can't explain.
A dedicated team for eighteen weeks, with settlement atomicity fixed as a non-negotiable in the first design session. Every later decision about wallets, standings and deposits follows from it. Separating the operator who marks results from the one who approves deposits was the operator's own requirement, and it turned out to be the thing that made disputes answerable at all.
One result, marked three ways
A settlement that runs in stages can stop between them. One that runs as a transaction can't.
The same market with ten open stakes, settled first by the staged job the operator used to run, then by the single transaction Fulltime runs now, and finally with that transaction failing halfway. Switch tabs, or use the arrow keys once one is focused.
The old platform marked the result, then a background job worked through the stakes, updating balances as it went. When it stalled, some winners had been paid and some hadn't, the leaderboard already showed standings the balances didn't support, and nothing in the data said which half had run.
- Result marked · Brackenfield win
- Leaderboard running total bumped
- Job pays stakes 1 to 3
- Job pays stakes 4 to 5
- Job stalls · no marker of where
- J. Okafor · Home · $10.002.10+$21.00+3
- M. Brandt · Home · $5.002.20+$11.00+3
- R. Castellano · Draw · $8.003.30Lost0
- T. Nguyen · Home · $20.002.05+$41.00+3
- A. Whitlock · Away · $6.003.60Lost0
- D. Pruitt · Home · $4.002.25Not paid+3
- L. Haddad · Home · $12.002.10Not paid+3
- K. Sorensen · Draw · $10.003.25—0
- E. Marsh · Home · $15.002.00Not paid+3
- V. Ibarra · Away · $5.003.60—0
3 winners paid, 3 not, and the leaderboard already credits all 6. The ledger and the leaderboard disagree.
Why there is no state between: the outcomes, the wallet movements and the market’s close are one unit of work, and standings are counted from what that unit wrote. Step timings above are illustrative.
From one mark to a ledger and a leaderboard that agree
Settlement isn't a job that works through stakes. It's a single transaction, and everything downstream of it is either written inside that transaction or derived from what it wrote.
- 01 · TriggerWinner marked on the settlement deskOne action per market. A market belongs to a fixture and a fixture to a league, so every open stake on it is addressable at once.
- 02 · GatePermission checkOnly an account holding result marking may act, and that account can never also approve deposits.
- 03 · EngineOne database transactionOutcomes written, wallet movements recorded, standings invalidated. It completes, or the market stays open with nothing written.
- 04 · StateAppend-only movements in MySQLA balance is the sum of its rows. Nothing writes a balance, and a correction is a reversing entry, never an overwrite.
- 05 · DeliveryStandings derived on readRecomputed from stake records, never accumulated. The read cache is derived, so it can be thrown away and rebuilt.
When a customer says the result was wrong
Settlement integrity & answerable disputes
No market can be half-settled
Every open stake on a market resolves inside one transaction. If it fails, the market stays open with nothing written, so there is never a state where some users were paid and nobody can say which.
Every figure traces to its movements
Wallets are append-only and a balance is their sum. A disputed amount is answered by showing the rows, and a wrong entry is corrected with a reversing entry, so the evidence is never erased.
The outcome and the money have different owners
Marking results and approving deposits are separate permissions no single operator account can hold, which is what lets the operator show its work when a result is challenged.
Moving money in stages that can stop halfway? Scope your build in 3 minutes.
Scope your buildNearby engagements
Data & AnalyticsA checkout that stopped losing sales to a 6-second load
Profiling found the real bottleneck behind a slow checkout (an N+1 query and an oversized bundle) and tuned both, with before-and-after metrics locked in as a baseline against future regressions.
E-commerce & Retail · 5 weeks
Web PlatformsA reader people finish, and a library that remembers where they stopped
A reading platform for a comics catalog: a browse surface people can actually navigate, a reader that gets out of the way, and a history that puts everyone back on the page they left.
E-commerce & Retail · 18 weeks
Web PlatformsOne number, a whole handset, and a database that keeps up with 125 brands
A metered IMEI lookup service that turns fifteen digits into a device, its specifications and its status. It's sold three ways to three audiences and backed by a device database that maintains itself.
Consumer Electronics · 22 weeks
Let's talk
Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.














