Skip to content

Bets history

Purpose

The Bets screen is the universal bet log: every wager across every game (slots, in-house games, sportbook), with filters by user, provider, game, status, date. The view is read-only except for the bet-queue retry endpoint, which lets engineering re-process settle-jobs that crashed.

Audience

Customer support (primary, "where's my missing payout"), compliance (provably-fair audit), engineering (retry stuck settles).

Path in admin-fe

Screen URL Page
Bets list /bets ebit-admin-fe/src/app/(dashboard)/bets/page.tsx
Per-game bets (drill-in) /games/[slug] ebit-admin-fe/src/app/(dashboard)/games/[slug]/page.tsx
Per-challenge bets /challenges/bets/[id] ebit-admin-fe/src/app/(dashboard)/challenges/bets/[id]/page.tsx
Per-leaderboard user bets /leaderboards/[slug] (drill-in) covered in leaderboard.md

Common bets-table component: ebit-admin-fe/src/components/common/GameBetsHistory/.

Backing API endpoints

Endpoint Source
POST /admin/bets (paginated find-many) apps/api/src/bet/admin.bet.controller.ts:19
POST /admin/bet-queue/retry (SuperAdmin) apps/api/src/bet/queue/admin.bet-queue.controller.ts:13

Frontend wiring: ebit-admin-fe/src/queries/bets/index.ts.

Key actions

Action Required permission / role API call DB tables touched Audit-logged?
List bets (paginated) user.bets.view POST /admin/bets Bet, User, Game yes
Filter by game slug, provider, status, date, user user.bets.view same with where same yes
Retry a stuck bet settle SuperAdmin POST /admin/bet-queue/retry { betId } re-enqueues into BullMQ bet-settle yes

AdminActionLog rows: every list and retry writes a row.

Filters and views

POST /admin/bets accepts a rich where object:

  • userId — required for per-user view.
  • gameSlugplinko, roulette, mines, dice, keno, limbo, monkey-run, blackjack, speed-roulette, plus slot slugs (BGaming/PM8/ST8/evogames).
  • providerhouse, bgaming, pm8, st8, evogames, sportbook.
  • statusWON, LOST, CANCELLED, PENDING_SETTLE.
  • dateFrom / dateTo — ISO range.
  • amountMin / amountMax — USD-equivalent.
  • sortByCREATED_AT (default), AMOUNT_USD, MULTIPLIER.

The provably-fair drill-in (server seed, client seed, nonce) is on the per-bet detail; for in-house games this links to flows/ provably-fair.

Common workflows

  1. "My win didn't credit." CS pulls bets by userId + dateFrom=today. Looks for status=PENDING_SETTLE. If found, escalates: engineering retries via POST /admin/bet-queue/retry { betId }. Backend re-enqueues the BullMQ settle job. Watch the logs in Loki — see observability.md.
  2. Provably-fair audit. Player asks: "How was this hash computed?" CS opens the bet detail, copies server seed (revealed after nextSeedRotateAt), client seed, nonce. Validates with the public /casino/games/.../verify endpoint or flows/ provably-fair narrative.
  3. Suspicious wagering pattern. Risk filters userId + last 24h, sorts by amount. Looks for sudden 10× wager bumps after a deposit. Cross-references with withdrawals.md and user-profile.md Notes tab.
  4. Provider outage forensics. Filter provider=bgaming + status=CANCELLED + window of the outage. Confirm the bets that got rolled back. Cross-reference with provider's incident report.
  5. Per-game RTP sanity. From game-management.md, drill into a game (e.g. /games/plinko). The bets table on that page filters automatically by gameSlug.

Edge cases / gotchas

  • POST /admin/bets is intentionally a POST, not GET. Filters can grow large (player IDs, game IDs); a POST body is more cache-friendly than a 4 KB query string. The endpoint itself is idempotent and read-only.
  • Slot bets carry minimal "round" detail. Slot rounds are atomic on the provider side; Evospin only stores aggregate Bet rows. To inspect a single slot spin, you need the provider's session log (BGaming has it; PM8 hides it).
  • PENDING_SETTLE bets do not always need a retry. Some games (sportbook in particular) hold bets until event resolution. Don't retry sportbook bets — check the event status first.
  • bet-queue/retry is SuperAdmin-only on purpose. Retrying the wrong bet ID can double-credit a player.
  • Search by user is required for fast queries. A bare POST /admin/bets {} with no userId will scan the full table — fine on staging, slow in production. The UI enforces a date or user filter by default.
  • Bet.outcomeJson schema differs per game. Plinko has path[], mines has revealedTiles[], roulette has pocket, keno has picks[] / hits[]. The UI auto-renders by Bet.gameSlug.
  • Sportbook bets are stored as a separate SportbookBet record linked to Bet. The unified /admin/bets list includes them but with different fields.

Sequence — retrying a stuck bet settle

sequenceDiagram
    actor admin as SuperAdmin
    participant admin-fe
    participant api
    participant queue as BullMQ (bet-settle)
    participant pg as Postgres
    admin->>admin-fe: open /bets, filter status=PENDING_SETTLE
    admin-fe->>api: POST /admin/bets { where: { status: PENDING_SETTLE, userId } }
    api->>api: PermissionGuard('user.bets.view')
    api->>pg: SELECT Bet WHERE status=PENDING_SETTLE
    pg-->>api: rows
    api-->>admin-fe: list
    admin->>admin-fe: click Retry on a row
    admin-fe->>api: POST /admin/bet-queue/retry { betId }
    api->>api: RolesGuard + Roles(SuperAdmin)
    api->>queue: enqueue settle for betId (priority=0)
    api->>pg: INSERT AdminActionLog (method, url, requestBody)
    api-->>admin-fe: 200 OK
    queue->>pg: UPDATE Bet SET status=WON|LOST, settle Transaction inserted
    Note over admin-fe: row will move out of PENDING_SETTLE on next refresh