admin-web (bitview-admin) — operator console
The internal operator console. A separate Next.js app sibling to
public-web, used by the BitView ops team to monitor
distributions, pools, and treasury, run the per-event publish flow,
review the anti-fraud queue, manage streamer applications, and
configure the on-chain network. It is not exposed to streamers or
viewers and lives behind a Keycloak gate on the bitview-ops realm.
Why a separate app
The consumer app (bitview-app) used to ship a small admin surface at
/admin/distributions/[id]/publish. As the operator surface grew
(pool monitoring, slashing queue, treasury readouts, sponsorship
admin, transparency report builder), keeping it in the consumer bundle
pulled in unwanted dependencies and made the auth posture awkward.
Splitting it gives:
- A separate deploy with its own auth posture (Keycloak
bitview-ops- server-side proxy), so the consumer app never ships admin endpoints to the browser.
- A separate dependency surface (
@tanstack/react-table,echarts,cmdk) consumer viewers never download. - A different visual direction (Linear-dense, dark-default, single accent) tuned for engineers doing ops at 2am, not for marketing.
The two apps share the same backend (bitview-bot) and the same
on-chain program family; they reuse the same wallet-adapter setup and
the same Solana primitives.
Responsibilities
| Area | Responsibility |
|---|---|
| KPI dashboard | Real-user earning ratio, active linked viewers, 24h tokens distributed, active distributions count + time series. |
| Distribution monitor | Per-distribution view: state, accrual rate, claim progress, top claimers, sybil flag count. Two-step publish wizard (init_distributor + fund_vault, ported from public-web). |
| Pool monitor | Live state of every Meteora pool (BTV/SOL, BTV/USDC, STREAM/BTV) with bin distribution charts, depth, drift. |
| Streamer management | KYC application board (approve / reject with notes), profile drill-down, tier, vesting schedules. |
| Sponsorship admin | Brand offers: pending / active / completed / disputed; engagement metrics. |
| Fraud queue | Soft-flagged wallets pending review, slashing history, per-layer hitrates, slash / exonerate one-click. (Endpoints stubbed on the bot; mock-data fallback wired in the UI.) |
| Treasury overview | Six wallet cards, runway gauge, 90-day inflow/outflow, LP rebalance triggers. Mutations are out-of-band via Squads multisig. |
| Reports | Quarterly transparency CSV export + print-to-PDF. |
| Audit log | Filterable admin action log with payload hashes. |
| Network config | /network edits the singleton app_config MongoDB doc with cluster-switch confirmation. /info is the read-only address book. |
| Bot status | /bot-status shows live listeners + per-distribution accrual snapshots. |
| Launchpad vault | /launchpad-vault admin: streamer BTV allowlist editor, derives Meteora pool client-side. |
| Health probes | /health checks bot, MongoDB, Solana RPC liveness. |
Tech stack
| Layer | Library | Notes |
|---|---|---|
| Framework | Next.js 16 (App Router) | Match bitview-app runtime. |
| Runtime | React 19, TypeScript 5.7 | |
| Styling | Tailwind v4 + shadcn-style primitives + Radix | |
| Charts | Tremor-style + echarts-for-react | ECharts for DLMM bin distributions and high-volume time series. |
| Tables | TanStack Table v8 + TanStack Virtual | Server-side pagination/filter/sort; virtualized fraud queue. |
| Data | TanStack Query v5 | refetchInterval for KPI; cache invalidation on SSE events. |
| Wallet / Solana | @solana/wallet-adapter-react, @coral-xyz/anchor, @solana/web3.js | Same versions as bitview-app. |
| Auth | Keycloak (bitview-ops realm) via OIDC Authorization Code + PKCE; iron-session cookie holds access + refresh; jose for JWT verification. | Legacy SIWS path still in the codebase but dormant (ADMIN_PUBKEYS empty in production). |
| Multisig (planned) | @sqds/multisig | Sensitive ops (fee authority changes, treasury moves) proposed to a Squads v4 multisig instead of signed directly. |
| Command palette | cmdk | ⌘K for navigation + wallet/streamer search. |
| Toasts | sonner | |
| State | TanStack Query + URL params + local useState | No global store. |
Auth model
staff browser
│
│ /sign-in → /api/auth/keycloak/login (PKCE)
▼
Keycloak `bitview-ops` realm ──── Authorization Code (PKCE), MFA if enrolled,
│ realm-role check in /api/auth/keycloak/callback
│
│ iron-session cookie (httpOnly, sealed; holds access + refresh)
▼
admin UI ──── /api/bot/[...path] proxy ───▶ bitview-bot
Authorization: Bearer <kc-access-token>
X-Admin-Api-Key: <server-only secret> ◄── legacy back-channel,
accepted while consumer
paths also rely on it
The middleware (src/middleware.ts) gates every request on the
iron-session cookie containing a valid Keycloak subject with at least
one of admin / ops / support realm roles. The bot's
auth_guard::require_admin validates the bearer against the realm's
JWKS and accepts the legacy header as a fallback. Neither auth
artefact reaches the browser — both are server-side only.
The bot proxy auto-refreshes the Keycloak access token when it nears
expiry; a refresh-token failure surfaces as a 401 with reason and the
client redirects to /sign-in.
The legacy SIWS path remains in the codebase as a fallback but the
cluster ships with ADMIN_PUBKEYS empty so no wallet can sign in
through it. Identity is Keycloak.
Folder layout
bitview-admin/
├── README.md
├── next.config.mjs
├── package.json
├── tsconfig.json
├── Dockerfile
├── public/
└── src/
├── middleware.ts — auth gate
├── app/
│ ├── sign-in/ — Keycloak login (primary) + SIWS dormant fallback
│ ├── (admin)/
│ │ ├── layout.tsx — sidebar shell + breadcrumb + ⌘K palette
│ │ ├── page.tsx — KPI dashboard (home)
│ │ ├── distributions/
│ │ │ ├── page.tsx — list (TanStack Table)
│ │ │ └── [id]/
│ │ │ ├── page.tsx — live monitor
│ │ │ └── publish/ — publish wizard
│ │ ├── pools/
│ │ │ ├── page.tsx — all pools
│ │ │ └── [lbPair]/page.tsx — DLMM bin chart + depth
│ │ ├── streamers/
│ │ │ ├── page.tsx
│ │ │ ├── applications/ — KYC review board
│ │ │ └── [wallet]/page.tsx
│ │ ├── sponsorships/page.tsx
│ │ ├── users/page.tsx
│ │ ├── fraud/
│ │ │ ├── page.tsx
│ │ │ └── slashing/page.tsx
│ │ ├── treasury/page.tsx
│ │ ├── reports/page.tsx
│ │ ├── activity/page.tsx — audit log
│ │ ├── health/page.tsx
│ │ ├── network/page.tsx — edit app_config
│ │ ├── info/page.tsx — read-only address book
│ │ ├── bot-status/page.tsx
│ │ └── launchpad-vault/page.tsx
│ └── api/
│ ├── auth/
│ │ ├── keycloak/{login,callback}/
│ │ ├── nonce/, verify/ — legacy SIWS (dormant)
│ │ ├── sign-out/, whoami/
│ └── bot/[...path]/ — server-only proxy
│
├── components/ — ui/, charts/, layout/, kpi/, distributions/, pools/, treasury/, fraud/, ...
└── lib/
├── api.ts — typed bot client
├── auth.ts — iron-session config
├── keycloak.ts — OIDC + JWKS verifier + refresh
├── mock.ts — fallback shapes for not-yet-shipped bot endpoints
├── queries.ts — TanStack Query hooks
├── types.ts — shared models
└── format.ts, cn.ts
Status
| Surface | State | Notes |
|---|---|---|
| Repo scaffold | ✅ shipped | Next.js 16 + Tailwind v4 + shadcn-style primitives. |
| Keycloak auth (primary) | ✅ shipped | OIDC Authorization Code (PKCE) against bitview-ops. iron-session cookie holds access + refresh. Middleware checks realm roles. Auto-refresh in the proxy. |
| SIWS auth (legacy fallback) | ⚪ dormant | Codepath present but ADMIN_PUBKEYS is empty in production. Delete once nothing depends on it. |
Bot proxy /api/bot/[...path] | ✅ shipped | Forwards Keycloak bearer + injects X-Admin-Api-Key legacy back-channel. |
| Network + Info pages | ✅ shipped | Backed by GET/PATCH /admin-api/app-config. |
| KPI dashboard | ✅ shipped (frontend) | Mock fallback until /metrics-api/kpi ships. |
| Distribution list + detail + live monitor + publish wizard | ✅ shipped | Publish wizard ported from bitview-app. |
| Pool monitor (list + detail + DLMM bin chart) | ✅ shipped (frontend) | Mock fallback for /amm-api/pools + /amm-api/pool-status/{lb_pair}. |
| Streamer list + profile + applications board | ✅ shipped (frontend) | |
| Sponsorship admin | ✅ shipped (frontend) | Pending / active / completed / disputed tabs; mock fallback for /marketplace-api/sponsorships. |
| Fraud queue + slashing log | ✅ shipped (frontend) | Virtualized list; mock fallback for /admin-api/fraud/*. |
| Treasury overview | ✅ shipped (frontend) | Mock fallback for /treasury-api/status. |
| Transparency report builder | ✅ shipped (frontend) | CSV export + print-to-PDF. |
| Audit log viewer | ✅ shipped (frontend) | Mock fallback until persistence lands on the bot. |
| Launchpad vault admin | ✅ shipped | Allowlist editor, derives Meteora pool client-side. |
Bot status (/bot-status) | ✅ shipped | Reads bot-admin-api/listeners + …/accrual-status. |
| Command palette (⌘K) | ✅ shipped | Navigation + user/distribution search wired. |
| Theme toggle (dark default) | ✅ shipped | |
Old admin in bitview-app | ✅ removed | Sources deleted. |
| Fine-grained per-endpoint role enforcement | 🔴 Planned | botRoles exposed in session; not enforced per-route yet. |
| Squads multisig propose flow | 🔴 Planned | Sensitive ops still signed directly by the operator wallet. |
Configuration
Client-safe (NEXT_PUBLIC_*)
| Var | Purpose |
|---|---|
NEXT_PUBLIC_BITVIEW_BOT_URL | Bot upstream (e.g., https://api.bitview.club) |
NEXT_PUBLIC_SOLANA_CLUSTER | mainnet-beta / devnet / testnet / localnet |
NEXT_PUBLIC_BTV_MINT | BTV mint |
NEXT_PUBLIC_DISTRIBUTOR_PROGRAM_ID | Merkle distributor program ID |
Server-only
| Var | Purpose |
|---|---|
ADMIN_API_KEY | Forwarded to bot as X-Admin-Api-Key |
AUTH_SECRET | iron-session encryption key (32+ chars) |
SOLANA_RPC_URL | Private RPC endpoint for server-side reads |
KEYCLOAK_ISSUER / KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRET / KEYCLOAK_REDIRECT_URI | OIDC code-flow on the server (enables Keycloak path when set) |
ADMIN_PUBKEYS | Comma-separated allowlist for legacy SIWS path (empty in production) |
SQUADS_MULTISIG | (Optional) Multisig address — if set, sensitive ops are proposed instead of signed directly |
Deployment
Multi-stage Docker (Node 22 Alpine): deps → builder (inlines
NEXT_PUBLIC_* from build args) → runner (non-root nextjs user,
port 3000). Pushes to registry.bitview.club/bitview/bitview-admin.
CI on .github/workflows/build-and-deploy.yml builds + triggers
GitOps sync.
The admin should sit behind Cloudflare Access or an equivalent identity-aware proxy. The Keycloak realm gate is the primary authentication layer; the proxy is defense-in-depth.
Going deeper
For the internal engineering view — sprint-level breakdown, backend
endpoint specifications, and the operational runbook — see the
internal _internal/technical/bitview-admin-detail.md doc.
Cross-references
- public-web — consumer-facing sibling app
- bot — backend the admin proxies into
- distributor — on-chain programs the admin invokes
- identity — Keycloak
bitview-opsrealm - Treasury policy
- Anti-fraud
- Operations