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
| Area | Responsibility |
|---|---|
| Wallet integration | Solana wallet-adapter (Phantom / Solflare / Backpack). Holds the user's connection state. Signs every on-chain instruction the app submits. |
| Streamer studio | 5-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 dashboard | Real-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 handoff | Direct OAuth flow (no Keycloak broker) at /api/auth/twitch-link/start → Twitch → callback → bot's auth-api/twitch/callback. |
| Public swap | USDC ↔ BTV ↔ STREAM routing via Jupiter (default) or direct Meteora DLMM. |
| i18n | Locale-scoped routing under /[locale]/ via next-intl. |
| Marketing surfaces | Landing, top-10, IDO page, pricing redirect. |
Tech stack
| Layer | Library |
|---|---|
| Framework | Next.js 16 (App Router) |
| Runtime | React 19, TypeScript 5.7 |
| Styling | Tailwind 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) |
| i18n | next-intl 4.x |
| Charts | recharts |
| Toasts | sonner |
| Twitch embed | react-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:
| Tier | Native (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.
- Tier — Native / Identity / Sponsored. Sponsored distributions
never originate here; they come from
/sponsor/[id]/accept. - Parameters — Channel, title, asset (BTV / streamer-token / NFT), amount, duration, per-viewer cap.
- Funding (skipped for Sponsored) — Swap USDC/SOL → BTV (Native) or buy streamer-token (Identity) via Jupiter or Meteora DLMM.
- Register — Backend creates the distribution doc; streamer signs
the bundled
withdraw_for_distribution(from streamer-vault) +init_distributortransaction. - 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:
- Create Mint — Token-2022
initialize_mint2 - Mint Supply — mint full supply to streamer ATA
- Create Metadata — Metaplex Token Metadata (Token-2022 path)
- Revoke Mint Authority — lock supply
- Init Streamer Vault — deposit 85% of supply into the
purpose-gated
streamer-vaultPDA - Init Pool — create STREAM/BTV Meteora DLMM pair (preset parameters; non-SOL/USDC quote)
- Seed Pool — atomic
withdraw_for_pool_seeding+add_liquidity_by_strategy2 - 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 / SDK | Used 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 vesting | Identity reserve + protocol allocation streams |
| Token-2022 / SPL Token | Mint 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.
| Var | Purpose |
|---|---|
NEXT_PUBLIC_BACKEND_URL | bitview-bot REST base URL (default http://localhost:4477) |
NEXT_PUBLIC_RPC_URL | Solana RPC endpoint (public-web reads on-chain state directly) |
NEXT_PUBLIC_DISTRIBUTOR_PROGRAM_ID | Merkle distributor program ID |
NEXT_PUBLIC_DISTRIBUTOR_COLLECTION | Default NFT distributor collection mint |
NEXT_PUBLIC_TWITCH_CLIENT_ID | Twitch OAuth client ID |
NEXT_PUBLIC_TWITCH_REDIRECT_URI | OAuth callback URL |
NEXT_PUBLIC_KEYCLOAK_ISSUER | Keycloak bitview realm URL (optional) |
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID | Keycloak public client (PKCE) |
NEXT_PUBLIC_BTV_MINT | BTV mint |
NEXT_PUBLIC_BITVIEW_TREASURY | Treasury wallet (referenced by some UI surfaces) |
Server-only (not inlined):
| Var | Purpose |
|---|---|
KEYCLOAK_DISCOVERY_URL, KEYCLOAK_CLIENT_ID, KEYCLOAK_REDIRECT_URI | OIDC code-flow on the server |
KEYCLOAK_SESSION_PASSWORD | iron-session encryption key (32+ chars) |
BITVIEW_API_KEY | Admin key forwarded on KYC submission proxy |
Status
| Surface | State |
|---|---|
| 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):
deps—pnpm installwith cache mountbuilder—pnpm build(inlines allNEXT_PUBLIC_*at build time from build args)runner— Node 22 Alpine, non-rootnextjsuser (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
- Architecture — one-screen visual
- bot — produces the REST surface this app consumes
- distributor — the programs this app's transactions target
- admin-web — operator-side sibling app
- On-chain program — merkle-distributor, streamer-vault, launchpad-vault references
- API reference — REST request/response shapes
- Streamer flow — what the streamer sees, phase by phase
- Viewer flow — what the viewer sees, phase by phase