Bet & Accounting ERD¶
This diagram covers the money-flow core: bets, transactions, balances, deposits,
withdrawals, tips, rakeback, and the game catalogue. All tables live in the
public Postgres schema (libs/_prisma/src/schema/api.prisma).
Every balance change goes through Transaction (api.prisma:712-752) — it is the
single ledger table. Bet records game-round outcomes;
Deposit/Withdraw track fiat/crypto movement. UserBalance holds the
per-currency wallet and vault amounts.
erDiagram
User {
int id PK
string username
}
Bet {
string id PK
datetime createdAt
datetime settledAt
int userId FK
enum status
decimal amount
decimal payout
float multiplier
enum currencyId
decimal usdAmount
decimal usdPayout
decimal commissionGgrUsdAmount
int gameId FK
string roundId
}
Transaction {
string id PK
enum currencyId
datetime createdAt
int userId FK
int adminUserId FK
decimal amount
decimal beforeBalance
decimal afterBalance
enum status
enum type
enum tag
string roundId
json payload
string originalId UK
}
UserBalance {
int userId PK,FK
enum currencyId PK
decimal amount
decimal vaultAmount
}
UserStats {
int userId PK,FK
enum currencyId PK
decimal totalDeposits
decimal totalWithdraws
decimal totalWagered
decimal totalPayouts
int totalBets
decimal ltv
}
UserStatsUsd {
int userId PK,FK
decimal totalDeposits
decimal totalWithdraws
decimal totalWagered
int totalBets
decimal ltv
datetime firstDepositAt
datetime lastWageredAt
}
Deposit {
string id PK
decimal amount
enum currencyId
enum status
enum providerId FK
decimal usdAmount
string externalId UK
string txHash UK
enum networkId
int userId FK
string transactionId UK,FK
string affiliateCodeId FK
}
Withdraw {
string id PK
decimal amount
enum currencyId
enum status
decimal usdAmount
int userId FK
string recipientAddress
enum providerId FK
enum networkId
string transactionId FK
int confirmedByUserId FK
}
PaymentProvider {
enum id PK
int priority UK
boolean isDepositEnabled
boolean isWithdrawEnabled
}
DepositWallet {
string id PK
enum currencyId
string address
enum providerId FK
int userId FK
enum networkId
}
CryptoCurrencyConfig {
int id PK
enum currencyId UK
string name
boolean withdrawsEnabled
boolean depositsEnabled
}
GameIdentity {
int id PK
string name
string slug UK
enum type
int providerId FK
boolean enabled
string originalSlug UK
json images
}
GameProvider {
int id PK
string name
string slug UK
string prefix
boolean enabled
}
GameInCategory {
enum category PK
int gameId PK,FK
int order
}
GamePopularity {
int gameId UK,FK
bigint gamePlayed
bigint launchCount
}
GgrCommissionRule {
string id PK
decimal ggrPercent
int providerId FK
enum category
int gameId UK,FK
}
Tip {
int id PK
int senderId FK
int recipientId FK
decimal amount
enum currencyId
boolean isPublic
}
Rakeback {
int userId PK,FK
enum currencyId PK
decimal instantClaimable
decimal dailyClaimable
decimal weeklyClaimable
decimal monthlyClaimable
}
UserFavoriteGame {
int userId PK,FK
int gameId PK,FK
}
HouseGameMines {
string gameId PK
int userId FK
json board
boolean isFinished
}
HouseGameMonkeyRun {
string gameId PK
int userId FK
decimal betAmount
enum currencyId
int step
enum difficulty
boolean isFinished
}
HouseGameBlackjack {
string roundId PK
int userId FK
json payload
boolean isFinished
}
User ||--o{ Bet : "places"
User ||--o{ Transaction : "owns"
User ||--o{ UserBalance : "holds"
User ||--o{ UserStats : "per-currency stats"
User ||--o| UserStatsUsd : "aggregate stats"
User ||--o{ Deposit : "deposits"
User ||--o{ Withdraw : "withdraws"
User ||--o{ DepositWallet : "wallet"
User ||--o{ Tip : "sends"
User ||--o{ Tip : "receives"
User ||--o{ Rakeback : "earns"
User ||--o{ UserFavoriteGame : "favorites"
User ||--o{ HouseGameMines : "plays mines"
User ||--o{ HouseGameMonkeyRun : "plays monkey run"
User ||--o{ HouseGameBlackjack : "plays house bj"
Bet }o--|| GameIdentity : "on game"
GameIdentity }o--|| GameProvider : "from"
GameIdentity ||--o{ GameInCategory : "categorized"
GameIdentity ||--o| GamePopularity : "popularity"
GameIdentity ||--o| GgrCommissionRule : "commission"
GameProvider ||--o{ GgrCommissionRule : "rules"
Deposit }o--|| PaymentProvider : "via"
Withdraw }o--|| PaymentProvider : "via"
Deposit |o--o| Transaction : "creates"
Withdraw }o--|| Transaction : "creates"
Transaction |o--o| Transaction : "rollback of"
DepositWallet }o--|| PaymentProvider : "issued by"
UserFavoriteGame }o--|| GameIdentity : "game"
Table reference¶
| Table | PK | Growth rate | Prisma line |
|---|---|---|---|
Bet |
id (uuid) |
High — every game round | api.prisma:635 |
Transaction |
id (uuid) |
Very high — every balance change | api.prisma:712 |
UserBalance |
(userId, currencyId) |
One row per user per currency | api.prisma:363 |
UserStats |
(userId, currencyId) |
Aggregated per currency | api.prisma:947 |
UserStatsUsd |
userId |
Single USD-denominated aggregate | api.prisma:965 |
Deposit |
id (uuid) |
Per crypto deposit | api.prisma:1184 |
Withdraw |
id (uuid) |
Per withdrawal request | api.prisma:1136 |
PaymentProvider |
id (enum) |
4 rows (CCPAYMENT, NOWPAYMENTS, SKINDECK, TEST) | api.prisma:1221 |
DepositWallet |
id (uuid) |
One per user per network per currency | api.prisma:1235 |
CryptoCurrencyConfig |
id (autoincrement) |
~13 rows (one per CurrencySymbol) | api.prisma:184 |
GameIdentity |
id (autoincrement) |
~2000+ (all slot/house games) | api.prisma:874 |
GameProvider |
id (autoincrement) |
~50 (slot providers + house) | api.prisma:822 |
GgrCommissionRule |
id (string) |
Per provider/category/game overrides | api.prisma:799 |
Tip |
id (autoincrement) |
User-to-user tips | api.prisma:555 |
Rakeback |
(userId, currencyId) |
Accumulated rakeback per currency | api.prisma:1396 |
HouseGameMines |
gameId (uuid) |
Active mines sessions | api.prisma:1433 |
HouseGameMonkeyRun |
gameId (uuid) |
Active monkey-run sessions | api.prisma:1449 |
HouseGameBlackjack |
roundId (string) |
Active single-player BJ sessions | api.prisma:1500 |
Key design notes¶
- Transaction tags (
api.prisma:578-600): 18 values covering every balance-change reason. Thetagfield drives reporting and filtering — it is the semantic type of the money movement. - Transaction self-referral:
originalIdlinks rollback transactions to their originals (api.prisma:735-741). - Bet.roundId uniqueness:
@@unique([roundId, userId])(api.prisma:668) prevents double-settle for the same user in the same round. - Balance views:
UserBalanceEvoandUserBalanceDbc(api.prisma:1731-1749) are Postgres views that extract single-currency balances for quick lookups.
Cross-references¶
- Flow docs:
docs/flows/place-bet.md,docs/flows/deposit.md,docs/flows/withdraw.md. - Security findings: SF-004 (double-settle), SF-006 (no pg CHECK on balance), SF-013 (vault overdraft) in
docs/security-register.md. - Bet settlement queue:
apps/api/src/bet/queue/— seedocs/recipes/add-bullmq-queue.md.