Skip to main content

public-web (bitview-app) — consumer frontend

The user-facing Next.js app. Streamers run their entire on-chain product workflow here — token launch, distribution creation, NFT collection creation, sponsorship acceptance. Viewers connect a wallet, link their Twitch handle, watch their accrual live, and claim their share.

Every wallet signature happens in this app. The backend (bitview-bot) builds the unsigned transactions; this app signs and submits them through the connected wallet.

Responsibilities

AreaResponsibility
Wallet integrationSolana wallet-adapter (Phantom / Solflare / Backpack). Holds the user's connection state. Signs every on-chain instruction the app submits.
Streamer studio5-step distribution wizard (/studio/distributions/new), 8-phase resumable streamer-token launch (/studio/token), NFT collection creation (/studio/collections/new), sponsorship acceptance, KYC profile.
Viewer dashboardReal-time accrual leaderboard, linked-streams panel, history, Twitch link/unlink.
Claim UX (/rewards/)Calls /claims-api/summary, fetches per-claim proofs, requests an unsigned claim (SPL) or claim_nft_core (NFT) transaction from the bot, signs with the wallet, submits to RPC.
Twitch OAuth handoffDirect OAuth flow (no Keycloak broker) at /api/auth/twitch-link/start → Twitch → callback → bot's auth-api/twitch/callback.
Public swapUSDC ↔ BTV ↔ STREAM routing via Jupiter (default) or direct Meteora DLMM.
i18nLocale-scoped routing under /[locale]/ via next-intl.
Marketing surfacesLanding, top-10, IDO page, pricing redirect.

Tech stack

LayerLibrary
FrameworkNext.js 16 (App Router)
RuntimeReact 19, TypeScript 5.7
StylingTailwind v4, Radix UI primitives, framer-motion
Wallet@solana/wallet-adapter-{base,react,react-ui,wallets}
Solana client@solana/web3.js
Anchor client@coral-xyz/anchor 0.29
Metaplex@metaplex-foundation/{umi, mpl-core, mpl-bubblegum, mpl-token-metadata}
Meteora@meteora-ag/cp-amm-sdk 1.4.x
Auth (server-side)iron-session, jose (JWT verify)
i18nnext-intl 4.x
Chartsrecharts
Toastssonner
Twitch embedreact-twitch-embed

Folder layout

bitview-app/bitview-app/src/
├── app/
│ ├── [locale]/
│ │ ├── layout.tsx — providers, fonts, locale boundary
│ │ ├── page.tsx — landing
│ │ ├── streamer/ — public streamer pitch / KYC entry
│ │ ├── studio/
│ │ │ ├── page.tsx — streamer dashboard
│ │ │ ├── distributions/
│ │ │ │ ├── new/ — 5-step wizard (Tier, Params, Funding, Register, Confirm)
│ │ │ │ └── finalize/
│ │ │ ├── token/ — 8-phase streamer-token launch (resumable)
│ │ │ ├── collections/
│ │ │ │ ├── new/ — Metaplex Core collection mint
│ │ │ │ └── page.tsx
│ │ │ ├── fund-distribution/ — SPL distributor init + vault fund
│ │ │ ├── fund-event/ — multi-asset event funding
│ │ │ ├── nft/ — NFT distribution manager
│ │ │ ├── sponsorships/[id]/accept/
│ │ │ └── profile/ — streamer KYC + tier
│ │ ├── viewer/
│ │ │ ├── page.tsx — viewer dashboard
│ │ │ └── profile/ — preferences, Twitch link
│ │ ├── rewards/ — viewer claim page (SPL + NFT)
│ │ ├── swap/ — public swap UI (Jupiter + Meteora)
│ │ ├── ido/ — Identity-tier launch announcement
│ │ ├── staking-pools/ — public staking surface
│ │ ├── top-10/ — leaderboard
│ │ ├── sponsor/[streamer]/offer/ — brand sponsorship offer page
│ │ ├── auth/twitch/callback/ — Twitch OAuth callback page
│ │ └── pricing-plan/ — redirect to /streamer
│ └── api/
│ ├── auth/keycloak/{login,callback,session,link-wallet,logout}/
│ ├── auth/twitch-link/{start,finalize}/
│ └── auth/streamer-app/{submit,me}/
├── components/
│ ├── Global/ — header, footer, layout shells
│ ├── home/ — landing sections
│ ├── streamer/wizard/ — Step1..Step5 components, AssetSelector
│ ├── streamer/token/ — StreamerTokenPage, phase components
│ ├── viewer/ — accrual widgets, session list
│ ├── sponsor/ — sponsor-side offer + accept components
│ ├── swap/ — JupiterSwapPanel, MeteoraSwapPanel, SwapPanelTabs
│ └── ui/ — Radix primitives
├── lib/
│ ├── api.ts — typed REST client (all bot scopes)
│ ├── claim.ts — Anchor claim transaction builder
│ ├── streamerToken.ts — multi-phase launch orchestrator
│ ├── poolMath.ts — bin-step / active-id helpers
│ ├── useBtvCostEstimate.ts — wizard cost calc
│ ├── wizard.ts — availableTiersFor(billing), tier gates
│ ├── launchpadAllowlist.ts — derive AllowlistEntry PDAs
│ └── merkle_distributor.json — distributor IDL
├── hooks/ — useNetwork, auth hooks
├── i18n/ — request.ts, routing.ts, navigation.ts
├── config/ — env.ts, serverEnv.ts, types.ts
└── messages/ — en.json, fr.json, ... (per-locale)

Streamer flows

Distribution wizard (/studio/distributions/new)

Five steps. Tier availability is gated by Stripe billing tier:

TierNative (BTV)Identity (streamer-token)Sponsored (brand-funded)
Free
Pro
Plus

lib/wizard.ts::availableTiersFor(billing) is the gate. Free wallets do not see Identity or Sponsored options in the picker.

  1. Tier — Native / Identity / Sponsored. Sponsored distributions never originate here; they come from /sponsor/[id]/accept.
  2. Parameters — Channel, title, asset (BTV / streamer-token / NFT), amount, duration, per-viewer cap.
  3. Funding (skipped for Sponsored) — Swap USDC/SOL → BTV (Native) or buy streamer-token (Identity) via Jupiter or Meteora DLMM.
  4. Register — Backend creates the distribution doc; streamer signs the bundled withdraw_for_distribution (from streamer-vault) + init_distributor transaction.
  5. Confirmation — Success screen with explorer links.

Multi-asset events share an event_id; the backend creates N merkle trees, one per asset.

Streamer-token launch (/studio/token)

Resumable 8-phase wizard. Each phase persists its signature + derived PDAs to the backend (launch_progress) so the wizard auto-resumes on return. Phases:

  1. Create Mint — Token-2022 initialize_mint2
  2. Mint Supply — mint full supply to streamer ATA
  3. Create Metadata — Metaplex Token Metadata (Token-2022 path)
  4. Revoke Mint Authority — lock supply
  5. Init Streamer Vault — deposit 85% of supply into the purpose-gated streamer-vault PDA
  6. Init Pool — create STREAM/BTV Meteora DLMM pair (preset parameters; non-SOL/USDC quote)
  7. Seed Pool — atomic withdraw_for_pool_seeding + add_liquidity_by_strategy2
  8. Vesting — two Streamflow streams: 15% Identity reserve to streamer, 5% Protocol allocation to BitView treasury (12-month linear)

Streamer can interrupt and return; the wizard reads the existing token record and resumes at the first unsigned phase.

NFT collection creation (/studio/collections/new)

Client-side Metaplex Core collection mint via UMI. Streamer wallet signs; frontend posts the resulting mint to /streamer-api/collections to register.

Viewer flow

Claim (/rewards/)

viewer wallet public-web bot distributor program
│ │ │ │
│ connect wallet ─────▶│ │ │
│ │ GET /claims-api/summary/$WALLET ─▶ │
│ │◀─ ClaimSummary { claimable[] } │
│ │ │ │
│ click "Claim" ──────▶│ │ │
│ │ GET /claims-api/proof/$DIST/$WALLET ─▶ │
│ │◀─ UserProof { index, amount, proof[] } │
│ │ │ │
│ │ POST /distributor-tx-api/build-claim ─▶ │
│ │◀─ base64 unsigned tx + signers │
│ │ │ │
│ sign tx ◀────────────│ │ │
│ ─────────── submit ───────────────────────────────────────────────▶ claim(idx, amt, │
│ proof) │
│ │
│ tokens land in viewer ATA ◀────────────────────────────────────────────────────── │

SPL and NFT claims share the same shape; only the build-tx endpoint differs (build-claim vs build-claim-nft). NFT claims mint a fresh Metaplex Core asset to the claimant's wallet (Bubblegum compressed path exists but is not surfaced in the UI yet).

Twitch linking

Direct OAuth flow — does not go through Keycloak (works around Keycloak's JSON-array scope parsing). The browser hits /api/auth/twitch-link/start, gets redirected to Twitch, comes back to /auth/twitch/callback, the server posts the code to the bot's auth-api/twitch/callback which writes the twitch_login attribute on the Keycloak user (if any) and the user record on MongoDB.

On-chain integrations

Program / SDKUsed for
merkle-distributor (own)SPL + NFT claims, distributor init
streamer-vault (own)85% STREAM supply lock + purpose-gated withdrawals
launchpad-vault (own)BTV match-funding for Identity-tier pool seeding
Meteora DLMM v2 (@meteora-ag/cp-amm-sdk)STREAM/BTV pools, pool init + bin-array + position + add-liquidity
Meteora DAMM v2 (instruction builders via bot)Streamer-token launch pool (alternative to DLMM)
Metaplex Core (mpl-core)NFT collections, per-claim asset mint
Metaplex Token Metadata (mpl-token-metadata)Streamer-token metadata (Token-2022-aware path)
Streamflow vestingIdentity reserve + protocol allocation streams
Token-2022 / SPL TokenMint creation, ATAs
Jupiter (via bot)Public swap default routing

Frontend never constructs Anchor instructions for the vault or launchpad programs directly — those go through the bot's /vault-api/*, /launchpad-vault-api/*, and /launchpad-api/* unsigned-tx builders. The frontend only signs and submits.

Environment

All runtime config goes through src/config/env.ts. Every key is NEXT_PUBLIC_* because it's read in the browser. Never put secrets here.

VarPurpose
NEXT_PUBLIC_BACKEND_URLbitview-bot REST base URL (default http://localhost:4477)
NEXT_PUBLIC_RPC_URLSolana RPC endpoint (public-web reads on-chain state directly)
NEXT_PUBLIC_DISTRIBUTOR_PROGRAM_IDMerkle distributor program ID
NEXT_PUBLIC_DISTRIBUTOR_COLLECTIONDefault NFT distributor collection mint
NEXT_PUBLIC_TWITCH_CLIENT_IDTwitch OAuth client ID
NEXT_PUBLIC_TWITCH_REDIRECT_URIOAuth callback URL
NEXT_PUBLIC_KEYCLOAK_ISSUERKeycloak bitview realm URL (optional)
NEXT_PUBLIC_KEYCLOAK_CLIENT_IDKeycloak public client (PKCE)
NEXT_PUBLIC_BTV_MINTBTV mint
NEXT_PUBLIC_BITVIEW_TREASURYTreasury wallet (referenced by some UI surfaces)

Server-only (not inlined):

VarPurpose
KEYCLOAK_DISCOVERY_URL, KEYCLOAK_CLIENT_ID, KEYCLOAK_REDIRECT_URIOIDC code-flow on the server
KEYCLOAK_SESSION_PASSWORDiron-session encryption key (32+ chars)
BITVIEW_API_KEYAdmin key forwarded on KYC submission proxy

Status

SurfaceState
Wallet adapter integration✅ Built
Twitch OAuth handoff + wallet linking✅ Built
Streamer wizard — Native tier✅ Built
Streamer wizard — Identity tier (asset selector + funding)✅ Built
Streamer wizard — Sponsored tier (brand-initiated)✅ Built
Streamer-token launch (8-phase, resumable, Token-2022 + Metaplex + Meteora pool + Streamflow)✅ Built
Streamer-vault deposit (Step 5 of launch)✅ Built
NFT collection creation (Metaplex Core)✅ Built
Viewer dashboard✅ Built
Claim UX — SPL single + batch✅ Built
Claim UX — NFT-Core single✅ Built
Claim UX — NFT batch🟡 In progress
Public swap UI (Jupiter + Meteora DLMM direct)✅ Built
Sponsorship offer + accept (3-tx bundle)✅ Built
Stripe checkout entry✅ Built
i18n (next-intl per-locale routing)✅ Built
Launch-resume graceful early-exit🟡 In progress
OBS overlay widget🔴 Not started
Plus-tier sponsorship marketplace seller UI🔴 Not started

Local development

cd bitview-app/bitview-app
cp .env.example .env.local
$EDITOR .env.local
pnpm install
pnpm dev
# http://localhost:3000

The app talks to bitview-bot on http://localhost:4477 by default; run the backend in a second terminal. See Operations for the full end-to-end local setup.

Deployment

Multi-stage Docker build (Node 22 Alpine):

  1. depspnpm install with cache mount
  2. builderpnpm build (inlines all NEXT_PUBLIC_* at build time from build args)
  3. runner — Node 22 Alpine, non-root nextjs user (UID 1001), next start -H 0.0.0.0 -p 3000

Images push to registry.bitview.club/bitview/bitview-app. CI: .github/workflows/build-and-deploy.yml builds on main and triggers GitOps sync against bitview-gitops.

Going deeper

For the internal engineering view — wizard state-management strategy, the launch-resume orchestrator design, the Meteora preset-parameter selection logic, OBS overlay widget plan — see the internal _internal/technical/bitview-app-detail.md doc.

Cross-references