Skip to content

Evospin — Master Architecture Reference

Read this alongside 2–3 of the flow docs in docs/flows/ and you will have a complete working mental model of the system: who talks to whom, what state lives where, which traces answer which question, and where the known potholes are. Generated: 2026-04-16 · Sources: all 15 Phase-2 flow docs, 3 security findings, docs/observability.md, CLAUDE.md, the three source repos (ebit-api/, ebit-fe/, ebit-admin-fe/).

0. Workspace reality

The workspace root (<REPO_ROOT>) is three sibling git repos (ebit-api/, ebit-fe/, ebit-admin-fe/) plus tests-e2e/ and a shared top-level docker-compose.yml. The compose file is the glue — each repo still has its own Dockerfile, package.json, and CI config and ships independently.

Canonical stack inventory (versions, package managers, role-per-repo): engineering/stack.md §2. Don't re-state versions here.

Do not mix package managers across repos — pnpm install in ebit-api/ or npm install in the FE repos corrupts lockfiles.


1. Context (C4 L1) — external systems + actors

flowchart LR
    subgraph actors["Actors"]
        direction TB
        player(("Player<br/><i>Browser on dropbet</i>"))
        admin(("Admin / Ops<br/><i>Internal operator</i>"))
        bot(("House bot fleet<br/><i>BullMQ-driven simulated activity</i>"))
    end

    subgraph ebit["ebit platform"]
        direction TB
        dropbet["dropbet<br/><i>ebit-fe + ebit-api<br/>sign-in · wallet · bets · leaderboard</i>"]
        admin_ui["Admin dashboard<br/><i>ebit-admin-fe + ebit-api<br/>user mgmt · bet review · config</i>"]
    end

    subgraph externals["External systems"]
        direction TB
        subgraph game_fair["Game fairness"]
            eos["EOS blockchain<br/><i>Wax/Jungle · speed-roulette<br/>waitForBlock</i>"]
        end
        subgraph auth_msg["Auth & messaging"]
            recaptcha["Google reCAPTCHA<br/><i>sign-up bot gate</i>"]
            smtp["SMTP provider<br/><i>sign-up / reset / welcome</i>"]
        end
        subgraph obs["Observability (non-local)"]
            sentry["Sentry<br/><i>JS + Nest error events</i>"]
            otlp_ext["External OTLP / APM<br/><i>collector fan-out</i>"]
        end
        subgraph dead["Stubbed / orphan"]
            evo["EVO-Games wallet RPC<br/><i>only orphan ebit-bj reaches it</i>"]:::orphan
            ft["FastTrack bonus tracker<br/><i>RabbitMQ sink, disabled=true</i>"]:::stub
        end
    end

    %% Actor → ebit
    player -- "HTTPS + WSS"          --> dropbet
    admin  -- "HTTPS (2FA-gated)"     --> admin_ui
    bot    -- "BullMQ producer"       --> dropbet
    admin_ui -- "same ebit-api"       --> dropbet

    %% ebit → externals
    dropbet -- "waitForBlock"         --> eos
    dropbet -- "verify token"         --> recaptcha
    dropbet -- "send mail"            --> smtp
    dropbet -- "error events"         --> sentry
    dropbet -- "OTLP (non-local)"     --> otlp_ext

    classDef stub fill:#eee,stroke:#999,color:#666,stroke-dasharray:5 5;
    classDef orphan fill:#fff3cd,stroke:#a07000,color:#000;

Trust boundaries

  • Player — untrusted; all /auth/*, /bets/*, /accounting/*, /promo/* paths guarded by JwtGuard + per-route rate limiting (sliding-window Lua on cache Redis).
  • Admin — JWT + PermissionGuard('<key>'); SuperAdmin routes additionally gated by RolesGuard(SuperAdmin) + OtpGuard (per-request TOTP).
  • Bots — run inside apps/api as BullMQ producers; skip HTTP layer.
  • EOS — hard external dependency for speed-roulette (§5 and dropbet-speed-roulette.md §6 #2). Outage stalls the round queue.
  • EVO-Games — reachable only from the orphan ebit-bj app on 4002; no dropbet path touches it (see dropbet-blackjack.md §6 #4).

2. Containers (C4 L2) — runtime topology

flowchart LR
    subgraph actors["Actors"]
        direction TB
        player(("Player"))
        admin(("Admin"))
    end

    subgraph fe["Browser-facing"]
        direction TB
        fe1["ebit-fe :3000<br/><i>Next.js 14 dropbet<br/>i18n · @vercel/otel RUM</i>"]
        fe2["ebit-admin-fe<br/><i>:5173 host / :3003 compose<br/>Vite + React 19<br/>AF-1 integration bugs</i>"]
    end

    subgraph nest["ebit-api NestJS monorepo (5 apps)"]
        direction TB
        api["ebit-api :4000<br/><i>REST + Swagger</i>"]
        rt["ebit-rt :4001<br/><i>socket.io /events</i>"]
        bo["ebit-bo :4003<br/><i>ops backoffice</i>"]
        sr["ebit-speed-roulette :4004<br/><i>EOS-anchored, BullMQ concurrency=1</i>"]
        bj["ebit-bj :4002<br/><i>orphan blackjack</i>"]:::orphan
    end

    subgraph data["Stateful"]
        direction TB
        pg[("Postgres 5555→5432<br/>public · blackjack · speed_roulette")]
        redisCache[("Redis :6379<br/>cache · BullMQ · throttler<br/>ONLINE_USERS · pub/sub")]
        redisBot[("Redis :6380<br/>bot state")]
        rmq["RabbitMQ :5672<br/><i>vhost=ft · fast-track stub</i>"]:::stub
    end

    %% Actor → FE
    player -- "HTTPS + WSS" --> fe1
    admin  -- "HTTPS"        --> fe2

    %% FE → Nest
    fe1 -- "fetch + traceparent" --> api
    fe1 -- "socket.io"           --> rt
    fe2 -- "fetch (AF-1)"        --> api

    %% Nest → Data
    api --> pg
    api --> redisCache
    rt  --> redisCache
    sr  --> redisCache
    sr  --> pg
    bj  --> pg
    bo  --> pg
    api --> redisBot
    api -. "fast-track stub" .-> rmq

    %% Inter-app RPC (AF-2 trace gap)
    api == "@GatewayMethod<br/>AF-2 NO traceparent" ==> rt
    api == "ExternalControllerClient<br/>AF-2 NO traceparent" ==> sr

    classDef stub fill:#eee,stroke:#999,color:#666,stroke-dasharray:5 5;
    classDef orphan fill:#fff3cd,stroke:#a07000,color:#000;
    linkStyle 14,15 stroke:#cc0000,stroke-width:2.5px;

Red thick edges = inter-app RPC over Redis pub/sub with no traceparent propagation (AF-2 in ./weaknesses-register.md). Dashed edges = stubbed (RabbitMQ disabled=true).

Observability sidecar (OTel collector + Jaeger + Prometheus + Loki + Grafana) is deliberately not shown here — it's a sidecar bus on every Nest container, out-of-band relative to the request path. See observability.md + architecture/tracing-flow.md for the full telemetry topology.

Detailed runtime topology + port-by-port edge inventory: ./architecture/service-map.md.

Port contract

Service Port Exposed by compose Notes
ebit-fe 3000 yes dev server (pnpm dev)
ebit-admin-fe 3001 host-networking workaround (see memory project_compose_decisions) four known integration bugs block cross-service traces
ebit-api 4000 yes /swagger enabled
ebit-rt 4001 yes main.ts boots with http: null — only socket.io
ebit-bj 4002 yes orphan; no dropbet path
ebit-bo, ebit-speed-roulette internal no HTTP surface exercised by E2E
Postgres 5555→5432 yes three schemas in one DB
Redis cache 6379 yes password cache
Redis bot 6380 yes password bot
RabbitMQ 5672 + UI 15672 yes rabbitmq/rabbitmq, vhost ft — receives zero traffic until fast-track stub removed
otel-collector 4317 / 4318 yes observability/otel-collector.yml
Jaeger 16686 yes
Prometheus 9090 yes
Loki 3100 yes
Grafana 3003 yes admin/grafana

3. Components (C4 L3) — inside ebit-api

The NestJS monorepo declares 5 applications in nest-cli.json sharing 11 libs. Path aliases: @api/*, @rt/*, @bj/*, @bo/*, @speed-roulette/* (apps); @app/shared, @app/auth, @app/games, @app/accounting, @app/integrations, @app/gateway, @app/ws-throttler, @app/modules, @app/is-localhost, @app/_prisma, @app/guards (libs).

Module inventory of apps/api/src/, grouped by domain. No edges drawn between these — they're a categorisation, not a flow. (For request-flow edges see ./flows/; for inter-app RPC see §2 above + AF-2 in ./weaknesses-register.md.)

Application modules (apps/api/src/)

Domain Modules
Auth + session auth/ (AuthController · AuthService · cookies.ts · JwtGuard · RolesGuard · OtpGuard · session/session.queue-producer.ts BullMQ) · user/ (UserController · AdminUserController · AdminNotesController · UserService · UserRepository · ProfileNotifierService · OnlineTrackerService) · kyc/ · api-key/
Bet pipeline (shared by all house games) bet/ (BetController · BetCrudService · BetRepository · BetQueueProducer BullMQ bet_settled_queue) · casino/ (/casino/games/house/* controllers: dice · limbo · mines · plinko · blackjack) · provably-fair/ (ProvablyFairService · popUserSeed · seed rotation) · accounting/ (UserBalanceRepository · ledger transaction log · ExchangeRatesService.toUsd)
Promo + challenge + loyalty challenge/ (AdminChallengeController · award-only, no user-claim route) · promo/ (PromoController — missing @Post on claim, SF in #38 · PromoEffectService · FastTrackBonusService stub) · leaderboard/ (LeaderboardService · dead-code LeaderboardQueueProducer SF-020 · leaderboard_user_position_view) · rakeback/ (calc on bet_settled_queue handler) · vip-program/ · tips/ + admin-tips/
External integrations payment/ (deposit / withdraw adapters) · sportbook/ (no dropbet history endpoint) · external-notification-sender/ (SMTP fan-out) · telegram/ (bot endpoints) · captcha/ (RecaptchaService, local='pass' bypass FM-1) · fast-track/ (RabbitMQ producer — .rmq.module.ts:8 disabled=true)
Admin + platform dashboard-v2/ + dashboard/ (admin analytics) · site-config/ (feature flags) · faq/ (CMS) · coming-soon/ (waitlist) · country/ (geo-IP + ban list) · chat/ · bots/ (house-bot fleet, BullMQ) · rt-notification/ (publishers → ebit-rt) · gateway/ (EventsGateway + @MessagePattern consumers, intra-Nest RPC)

Shared libraries (libs/)

Lib Role
@app/shared Nest bootstrap · OTel pre-main · EvoLogger · NestLoggerModule (pino) · base.main.ts
@app/auth Shared JWT guard + decorators
@app/games Cross-game DTOs + RNG helpers
@app/accounting Currency registry · balance DTOs
@app/integrations EOS client · EVO-Games client · external APIs
@app/gateway EventsGateway base + event name registry (Private.*, Server.*, Client.*) · @ExternalControllerClient + @GatewayMethod
@app/ws-throttler Socket-level rate limit
@app/modules AdminLoggerInterceptor (writes admin_action_log) · idempotency lock · pagination DTOs
@app/is-localhost env-gated helpers
@app/_prisma Split schema (api + blackjack + speed_roulette) · PrismaService · seed entrypoint
@app/guards Shared HTTP guards

Noteworthy module boundaries

  • @app/gateway is the spinal cord. @ExternalControllerClient + @GatewayMethod + @MessagePattern implement inter-app RPC on Nest's Redis pub/sub transport. This is also the single biggest blind spot in the trace graph — W3C traceparent doesn't cross the hop (memory project_otel_microservice_transport_gap §6 #1).
  • BullMQ, not RabbitMQ. Every production async path (session.queue-producer, bet_settled_queue, bots/system/bull/, challenges, leaderboard, promo tasks, user-stats-migration, skindeck-deposits, both speed-roulette queues) runs on Redis-backed BullMQ. RabbitMQ is only wired to apps/api/src/fast-track/rabbitmq/fast-track.rmq.module.ts and that module returns disabled = true. Debugging a stalled job → redis-cli -a cache KEYS bull:*, not the RabbitMQ UI.
  • AdminLoggerInterceptor (libs/modules/src/admin-logger/) wraps every non-GET /admin/* and writes an admin_action_log row from a tap(). safeLog swallows insert errors — a transient DB fault produces a silent audit gap.
  • @app/shared/basic/pre/pre-otel.main.ts is imported first in every app's main.ts; it boots @opentelemetry/sdk-node + PrismaInstrumentation + PinoInstrumentation before Nest. bj and bo bypass the createNestApp helper — they must still import @app/shared/basic/pre-imports as the first line.

Every scenario below has a 7-heading flow doc with Jaeger traceIDs and a pinned Playwright spec under tests-e2e/tests/. Read the flow doc for sequence diagram, span timing, data model, and failure modes.

# Flow Flow doc E2E spec Entry
1 Dropbet sign-up (captcha → user INSERT → floating email) dropbet-sign-up.md tests/dropbet-sign-up.spec.ts POST /auth/sign-up
2 Dropbet sign-in + 2FA dropbet-sign-in.md tests/dropbet-sign-in.spec.ts POST /auth/sign-in/auth/verify-2fa
3 Password reset (forgeable token + no one-shot persistence) dropbet-password-reset.md tests/dropbet-password-reset.spec.ts POST /auth/password-reset-request
4 Wallet / balance view (negative-balance to-vault bug) dropbet-wallet.md tests/dropbet-wallet.spec.ts GET /accounting/balances
5 Bet placement shared pipeline (dice as vector) dropbet-bet-place.md tests/dropbet-bet-place.spec.ts POST /casino/games/house/dice/bets
6 Dice vs Limbo house-game mechanics dropbet-house-game.md tests/dropbet-house-game.spec.ts POST /casino/games/house/{dice,limbo}/bets
7 Blackjack session (abandoned-hand fund lockup) dropbet-blackjack.md tests/dropbet-blackjack.spec.ts POST /casino/games/house/blackjack/init
8 Speed-roulette session (EOS-anchored, cross-service trace gap) dropbet-speed-roulette.md tests/dropbet-speed-roulette.spec.ts POST /speed-roulette/bet
9 Bet history + details (detail endpoints leak seed to anon) dropbet-bet-history.md tests/dropbet-bet-history.spec.ts GET /bets/my/house-game
10 Leaderboard (RACE_ENABLED gate, dead producer, stale cache) dropbet-leaderboard.md tests/dropbet-leaderboard.spec.ts GET /leaderboards/active
11 Challenges + promos (404 promo claim — missing @Post) dropbet-challenges.md tests/dropbet-challenges.spec.ts GET /challenges/active + POST /promo/public/:code
12 RT websocket connect + events (online count padded) rt-websocket.md tests/dropbet-rt-websocket.spec.ts WSS /events
13 Admin sign-in (cookie-name mismatch blocks admin-fe) admin-sign-in.md tests/admin-sign-in.spec.ts POST /auth/sign-in + TOTP
14 Admin user management (ban/unban/note + audit log) admin-user-mgmt.md tests/admin-user-mgmt.spec.ts POST /admin/user/all
15 Admin bet view + adjustment (no adjust endpoint; SuperAdmin MFA bypass) admin-bets.md tests/admin-bets.spec.ts POST /admin/bets

Flows 1–12 drive ebit-fe + ebit-api (+ ebit-rt for 7/8/12). Flows 13–15 drive ebit-api directly because admin-fe is blocked by the bug cluster in §7 — driving the UI would produce a broken traceparent chain. See ebit-admin-fe/src/middleware.ts:59-60 for the cookie-name culprit.

Read-order suggestions for new hires

  • Backend engineer → this doc → dropbet-sign-in.mddropbet-bet-place.mdrt-websocket.md. That tour hits auth, Prisma+BullMQ, and the gateway pub/sub blind spot.
  • Frontend engineer → this doc → dropbet-sign-in.mddropbet-wallet.mdrt-websocket.md. Covers cookie contract, socket_token handshake, and the balance fan-out.
  • Ops / SRE → this doc § 6 + § 7 → admin-user-mgmt.md § 6 → observability.md. Covers the audit path, silent-log failure mode, and trace-to-log pivot.

5. Data model — split Prisma schema

libs/_prisma/src/schema/ is declared with previewFeatures = ["prismaSchemaFolder", "multiSchema", "views", "tracing"]. Three files, one Postgres DB, three Postgres schemas:

File Postgres schema Models Primary domain
api.prisma public 65 All of the player + admin surface
blackjack.prisma blackjack 8 Blackjack-specific state (rounds, hands, cards)
speed_roulette.prisma speed_roulette 3 Speed-roulette game + bet + eos block

Schema commands are wrapped with env-cmd -f .env (or .env.test) — always use the npm scripts (db:migrate:dev, db:seed, db:reset, prisma:*:test). Calling npx prisma directly misses the env file.

Core entities

Entity Written by Read by Trace anchor
user (api.prisma) sign-up / ban / role changes every guard + admin surface POST /admin/user/all trace
user_session BullMQ session.queue-producer (skipped on first sign-up) admin lookup, same-device check §7 weakness WK-2
user_balance UserBalanceRepository · bet tx · deposit/withdraw · to-vault (SF-013) /accounting/balances · bet pipeline dropbet-wallet.md
transaction (ledger) every balance mutation rt Private.TransactionFindMany (no HTTP SF-015) dropbet-wallet.md
bet BetCrudService.placeBet inside a Prisma transaction /bets/my/*, admin /bets, history cache dropbet-bet-place.md
game_identity + game_provider + game_in_category migrations + admin every bet (fat-hydration problem — dropbet-blackjack.md §6 #3)
user_seed popUserSeed inside the bet tx fairness controller dropbet-house-game.md
leaderboard · leaderboard_user · leaderboard_user_position_view bet_settled_queue direct write GET /leaderboards/active dropbet-leaderboard.md
challenge · user_challenge admin award/rollback only browse + participation dropbet-challenges.md
promo · user_promo_code floating 404 on claim (missing @Post) GET /promo/public/:code dropbet-challenges.md
admin_action_log AdminLoggerInterceptor.tap() (non-GET /admin/*) GET /admin/user/admin-audit (filter by admin actor, not target) admin-user-mgmt.md
user_note AdminNotesController admin user detail admin-user-mgmt.md
BlackjackGame · BlackjackHand · BlackjackBet (blackjack schema) /casino/games/house/blackjack/* on ebit-api (NOT ebit-bj) same dropbet-blackjack.md
SpeedRouletteGame · SpeedRouletteBet · eos block columns ebit-speed-roulette state machine (BullMQ concurrency=1) rt pushes + repository reads dropbet-speed-roulette.md

Redis keyspace

Canonical inventory of every Redis key prefix used by the platform: data-model/redis-keyspace.md.


6. Observability catalog

Canonical observability wiring + dashboards + correlation recipe: observability.md.


7. Known weaknesses

Canonical scoreboard for every known weakness (architectural blind spots AF-*, security findings SF-*, flow-doc failure modes FM-*, workspace-level gaps WK-*): weaknesses-register.md.


8. Outstanding questions this doc does not answer

  • Full backoffice surface (ebit-bo). Only touched tangentially via the dead POST /bets proxy (SF-026).
  • Payment deposit/withdraw adapter internals — the payment/ module has its own Prisma + external APIs and deserves a dedicated flow doc.
  • KYC lifecycle (upload → review → tiered limits) — kyc/ module.
  • VIP program tier progression — vip-program/.
  • Bot-fleet behavior model (bots/) — simulated activity skews every dashboard that doesn't filter by is_bot.
  • SuperAdmin mutations (PUT /admin/user/:id/{balance,roles,permissions}) — OTP-gated and not exercised in admin-user-mgmt.md.

If you need any of these, start from the module directory listed above; the pattern established in the 15 Phase-2 flow docs is the template.