Skip to main content

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

AreaResponsibility
KPI dashboardReal-user earning ratio, active linked viewers, 24h tokens distributed, active distributions count + time series.
Distribution monitorPer-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 monitorLive state of every Meteora pool (BTV/SOL, BTV/USDC, STREAM/BTV) with bin distribution charts, depth, drift.
Streamer managementKYC application board (approve / reject with notes), profile drill-down, tier, vesting schedules.
Sponsorship adminBrand offers: pending / active / completed / disputed; engagement metrics.
Fraud queueSoft-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 overviewSix wallet cards, runway gauge, 90-day inflow/outflow, LP rebalance triggers. Mutations are out-of-band via Squads multisig.
ReportsQuarterly transparency CSV export + print-to-PDF.
Audit logFilterable 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

LayerLibraryNotes
FrameworkNext.js 16 (App Router)Match bitview-app runtime.
RuntimeReact 19, TypeScript 5.7
StylingTailwind v4 + shadcn-style primitives + Radix
ChartsTremor-style + echarts-for-reactECharts for DLMM bin distributions and high-volume time series.
TablesTanStack Table v8 + TanStack VirtualServer-side pagination/filter/sort; virtualized fraud queue.
DataTanStack Query v5refetchInterval for KPI; cache invalidation on SSE events.
Wallet / Solana@solana/wallet-adapter-react, @coral-xyz/anchor, @solana/web3.jsSame versions as bitview-app.
AuthKeycloak (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/multisigSensitive ops (fee authority changes, treasury moves) proposed to a Squads v4 multisig instead of signed directly.
Command palettecmdk⌘K for navigation + wallet/streamer search.
Toastssonner
StateTanStack Query + URL params + local useStateNo 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

SurfaceStateNotes
Repo scaffold✅ shippedNext.js 16 + Tailwind v4 + shadcn-style primitives.
Keycloak auth (primary)✅ shippedOIDC 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)⚪ dormantCodepath present but ADMIN_PUBKEYS is empty in production. Delete once nothing depends on it.
Bot proxy /api/bot/[...path]✅ shippedForwards Keycloak bearer + injects X-Admin-Api-Key legacy back-channel.
Network + Info pages✅ shippedBacked 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✅ shippedPublish 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✅ shippedAllowlist editor, derives Meteora pool client-side.
Bot status (/bot-status)✅ shippedReads bot-admin-api/listeners + …/accrual-status.
Command palette (⌘K)✅ shippedNavigation + user/distribution search wired.
Theme toggle (dark default)✅ shipped
Old admin in bitview-app✅ removedSources deleted.
Fine-grained per-endpoint role enforcement🔴 PlannedbotRoles exposed in session; not enforced per-route yet.
Squads multisig propose flow🔴 PlannedSensitive ops still signed directly by the operator wallet.

Configuration

Client-safe (NEXT_PUBLIC_*)

VarPurpose
NEXT_PUBLIC_BITVIEW_BOT_URLBot upstream (e.g., https://api.bitview.club)
NEXT_PUBLIC_SOLANA_CLUSTERmainnet-beta / devnet / testnet / localnet
NEXT_PUBLIC_BTV_MINTBTV mint
NEXT_PUBLIC_DISTRIBUTOR_PROGRAM_IDMerkle distributor program ID

Server-only

VarPurpose
ADMIN_API_KEYForwarded to bot as X-Admin-Api-Key
AUTH_SECRETiron-session encryption key (32+ chars)
SOLANA_RPC_URLPrivate RPC endpoint for server-side reads
KEYCLOAK_ISSUER / KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRET / KEYCLOAK_REDIRECT_URIOIDC code-flow on the server (enables Keycloak path when set)
ADMIN_PUBKEYSComma-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): depsbuilder (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