Thirteen modules, described by what you do with them
A module list is usually a feature list in disguise. This one is ordered the way it was built — each module finished, with its administration and its failing-case tests, before the next one started — and each entry says what an operator actually does with it on a working day.
What happens at a desk, module by module
M0
Foundation
Nothing, and that is the point.
Boot order, configuration, the response envelope, the error classifier, the audit log, the index builds that finish before the port binds. Every module above it inherits these, which is why none of them reinvents them.
M1
Auth
Watch a player sign up and sign in, and sign a stolen session out.
Registration, login, refresh with rotation, forgotten-password recovery, a self-hosted captcha, and one live session per account — a second device signs the first out and the app receives SESSION_REPLACED, so it can explain instead of just failing.
M2
Profile and security
Reset a withdrawal password a player has locked themselves out of.
Two passwords that behave differently: the login password changes whenever the player wants and ends every session when it does; the withdrawal password is set once and only an operator reset clears it. The security score is computed on request from three checks, never stored.
M3
Wallet and ledger
Read a balance and be able to say where every unit of it came from.
One entry point writes money. A pending row goes down before the balance moves, one guarded atomic update moves it, and a debit past zero is rejected rather than clamped. Idempotency is a unique index, not a lookup.
M4
Payments
Work the deposit queue and approve withdrawals.
Deposits arrive as agent transfers and sit pending until a staff member matches the transaction id — nothing auto-credits. Agent numbers come from a pool claimed by a single atomic round-robin, so two players cannot be handed the same number at the same moment.
M5
Aggregator integration
Sync the catalog, curate the lobby, and never touch a bet by hand.
Four games-API endpoints we call and five wallet endpoints we host. A catalog sync overwrites only provider-owned fields, so re-syncing can never undo an operator’s curation, and a game whose category does not map is reported rather than filed somewhere wrong.
M6
Bonus and turnover
Set a deposit bonus and know exactly when it is cleared.
The requirement is the bonus amount times the multiplier, never the deposit times the multiplier. Turnover accrues per game weight. Forfeit deducts what the bonus made, not what the player already had.
M7
Rewards
Run daily check-in, streaks, rebate and cashback.
Five reward kinds behind one unique index on player, kind and period — the single guard that makes a retried claim return the first result instead of paying twice.
M8
VIP
Set the tiers and let the salary run.
A tier needs both thresholds met, deposited and wagered. A stray “or” there turns a retention programme into a withdrawal fee, so the rule is written once and tested at the boundary.
M9
Referral and agent
Pay agents on a schedule and read the tree.
Three levels of commission, recomputed on a schedule rather than on every bet, because the bet callback is the one thing that cannot be slow. The recompute is re-runnable: a schedule you cannot retry eventually pays twice.
M10
Records and reports
Answer “what happened yesterday” without opening a database client.
Betting, transaction, bonus and referral records read from the one ledger. A row in the betting record is a round, not a transaction. Day buckets are cut in the operator’s own IANA timezone and stay correct across a DST change.
M11
Content
Change the banner, the promotion and the notice yourself.
Banners, promotions, announcements, FAQ and the theme registry are rows, not deploys. A ninth theme is one style block and one row.
M12
Back office
All of the above, from a desk, by staff who are not engineers.
Eight groups, a menu rendered from a permission table rather than from markup, and an append-only audit row for everything anyone did. A group with no page the person may open is absent, not greyed out.
Three rules that are easy to state and easy to get backwards
or there turns a retention programme into a withdrawal fee.The rules above, on the screens a player actually touches




Money is an integer, and time is a timezone
356000. A human-typed decimal is rounded once, at the edge, and never again. The scale is the aggregator’s own wire scale, so the adapter converts nothing and rounds nothing on the way in.What is deliberately not included
Payments ship as reference adapters for one market. bKash, Nagad, Rocket and Upay are Bangladeshi mobile-money rails, and they run through a manual reconciliation queue because none of those providers offers a merchant API to a gambling account. A deposit stays pending until a staff member matches the transaction id; it never auto-credits. A new market means a new adapter written against that market’s rails — the queue, the channel pool and the audit trail are already there, the rail is not. We do not claim any payment method, because that claim is never true.
SMS codes are logged, not sent. No gateway is contracted, so verification and recovery codes are written to the server log until you contract one. One-time codes are switched off for registration and login on purpose: blocking signup on a message that may never arrive loses accounts.
The sportsbook is a list of brand links. Players are handed off to a sportsbook you have a relationship with. There is no odds feed, no bet slip and no settlement here, and the vertical is deferred rather than half-built.
Withdrawal is gated on remaining turnover, outright. While anything is owed, a withdrawal is refused — not partial, not pro-rata. The turnover figure is snapshotted when the request is made, not when it is approved, and a payout card becomes immutable once it has paid out. This is a design decision, not an omission, and it is the behaviour your support desk will be explaining.