Architecture
┌──────────────────────┐ ┌──────────────────────┐
│ bitview-app │ REST + WSS │ bitview-bot │
│ (Next.js) │ ────────────────▶│ (Rust / actix-web) │
│ │ │ │
│ • streamer creator │ ◀────── Swagger │ • Twitch IRC oracle │
│ • viewer dashboard │ │ • Accrual loop │
│ • Solana wallet │ │ • Distributions API │
│ adapter │ │ • Claim proof proxy │
└────────┬─────────────┘ └────────┬──────────┬──┘
│ │ │
│ wallet.signTx │ │ proxy
▼ ▼ ▼
┌──────────────────────┐ ┌────────────┐ ┌─────────────────────┐
│ Solana cluster │ ◀─ new_claim ────│ MongoDB │ │ distributor/api │
│ │ │ (state) │ │ (axum, proofs) │
│ • merkle distrib. │ └────────────┘ └─────────────────────┘
│ program │ ▲ ▲
│ • SPL token mints │ │ │
│ │ ◀─ new_distrib. ─────────┴────────────────┘
└──────────────────────┘ (offline cli at finalize time)
Modules
| Module | Tech | Responsibility |
|---|---|---|
bitview-app | Next.js 14, wallet-adapter, Metaplex | Streamer creates distribution, viewer dashboard, claim UX |
bitview-bot | Rust 1.75+, actix-web 4, MongoDB | Twitch presence, accrual, REST API, OAuth verification |
distributor/merkle-distributor | Anchor 0.30 | Audited on-chain claim program (Jito/Jupiter fork) |
distributor/cli | Rust | Build merkle tree from CSV, fund vault, set root, send claim |
distributor/api | axum | Serves merkle proof for (mint, claimant) |
checkpoint | Docusaurus | This documentation site |
Data flow
Accrual
Every ACCRUAL_TICK_SECONDS the backend:
- Loads
distributionswherestart_at <= now <= end_atand status ispendingoractive. - For each, ensures the Twitch listener is up for the channel.
- Snapshots present logins from the in-memory
state_space_twitchmap. - For each linked viewer (look up
usersby login), upserts anaccrualsrow:+amount,+1 tick. - Skips viewers above
max_per_viewer. Stops crediting when the pool is exhausted.
Snapshot / finalize
When a distribution ends (or the operator forces it):
POST /distributions-api/{id}/finalizeflips status tosnapshotting.- The offline
cli create-merkle-treereads theaccrualsrows for that distribution and produces atree_<version>.json. cli new-distributor(or aset_enable_slotif pre-created) publishes the root on-chain.- The merkle proof API loads the tree and starts serving proofs.
- Backend flips status to
claimable.
Claim
- Viewer hits the rewards page. Frontend calls
GET /claims-api/summary/{wallet}. - For any claimable entry, it calls
GET /claims-api/proof/{wallet}(which proxies to the distributor proof API). - Frontend builds and signs a
new_claiminstruction with their wallet. - Tokens land in the viewer's ATA.
Deployment shape
Single backend process binds two ports:
4477— REST + Swagger (configurable viaHTTP_PORT)9100— Prometheus exporter (configurable viaMETRICS_PORT)
MongoDB is the only persistence layer. Solana RPC is read-only from the backend's perspective (proofs come from the distributor API, the actual on-chain writes are from the streamer's wallet on creation and the viewer's wallet on claim).
There is intentionally no separate "frontend backend" — the bot is the backend. Adding a second service would just split state.