Skip to main content

bot (bitview-bot) — Rust backend

The always-on backend service. Tracks Twitch chat presence, runs the accrual loop, builds the merkle trees, exposes the REST API consumed by both public-web and admin-web, and returns base64-encoded unsigned transactions for every on-chain action the product takes.

It is not an on-chain signer. The backend never holds Solana keys. Every on-chain write is signed by a streamer's, viewer's, or admin's wallet through one of the web apps. The backend's only on-chain posture is read-only (RPC reads + tx submission monitoring).

Responsibilities

AreaResponsibility
Twitch presenceMaintains IRC connections per channel with active distributions; tracks who is in chat in real time; reconciles presence on bot restart from a MongoDB-persisted view.
AccrualPer-tick loop (default 60s) credits linked viewers across all Active distributions. Catches up missed ticks on resume. Polls Twitch Helix for stream-online state so accrual auto-pauses when the streamer goes offline.
IdentityDirect Twitch OAuth callback exchange; ed25519 wallet signature verification; bridges Twitch handle ↔ Solana wallet ↔ Keycloak attribute.
REST APITyped, OpenAPI/Swagger-documented surface (port 4477). 20+ namespaced scopes.
Unsigned-tx buildersHand-rolled CPI clients for merkle-distributor, streamer-vault, launchpad-vault, Meteora DLMM v2, Meteora DAMM v2, Token-2022, Metaplex Token Metadata, Streamflow. Every product action returns a base64-encoded unsigned tx + signers + a request_id the client uses for status polling.
Tx lifecycleBackground watcher polls Solana RPC, advances tx_log state machine (built → submitted → confirmed → finalized), surfaces errors.
Merkle assemblyBuilds a SHA256 merkle tree from the accruals collection on distribution finalize, writes the root on-chain via a streamer-signed init_distributor.
PersistenceOwns every MongoDB collection in the system. The bot is the single source of truth for off-chain product state.
BillingStripe checkout session creation + webhook handler; tier sync writes billing_tiers and propagates the streamer role to Keycloak.
ObservabilityPrometheus metrics (port 9100), structured tracing logs, bot-admin-api introspection endpoints.

Tech stack

LayerLibrary
Async runtimetokio 1.38 (full features)
HTTP serveractix-web 4.8
Twitch IRCtwitch-irc 5.0
Databasemongodb 3.2 (official driver)
OpenAPI / Swaggerutoipa + utoipa-swagger-ui + utoipa-rapidoc
MetricsPrometheus exporter on :9100
Crypto (wallet linking)ed25519-dalek 2.x
Solanasolana-sdk / solana-program 3.0 (matches distributor workspace)
SPLspl-token 9 + spl-token-2022 10 + spl-associated-token-account 8
BuildCargo Chef multi-stage Docker, Debian Bookworm slim runtime

Compile target: Rust 1.75+. Single static binary. Cargo workspace with 23 crates under crates/.

Crate map

CratePurpose
bitview-botComposition root binary; boots config → metrics → db → solana RPC → accrual loop → HTTP server.
bitview-httpactix-web routes + OpenAPI registration. ~20 route modules grouped by namespace.
bitview-configdotenv loading + process-wide Config singleton.
bitview-dbMongoDB client + 18 repository types + index setup + Repositories singleton.
bitview-modelWire shapes (User, Distribution, Accrual, …) with utoipa derives.
bitview-coreShared types (Pubkey, TokenAmount, signatures).
bitview-authTwitch OAuth, Keycloak OIDC, wallet signature validation.
bitview-twitchIRC listener oracle; in-memory DashMap<channel_login, TwitchChannel> of present users.
bitview-accrualPer-tick accrual loop spawned at boot.
bitview-snapshotMerkle tree builder + distribution finalize pipeline.
bitview-merkleSHA256 merkle tree implementation + proof generation. Matches the off-chain shape the distributor program verifies.
bitview-solanaRPC client, blockhash service, transaction builder, confirmation watcher.
bitview-distributorCPI client for merkle-distributor (init_distributor, init_nft_distributor, claim, claim_nft_core, claim_nft_compressed, clawback, set_enable_slot, set_admin, accept_admin, set_clawback_receiver).
bitview-streamer-vaultCPI client for streamer-vault (initialize_vault, withdraw_for_distribution, withdraw_for_pool_seeding, withdraw_for_damm_v2_launch, close_vault).
bitview-launchpad-vaultCPI client for launchpad-vault (initialize, deposit, add_allowlist, disable_allowlist, close_allowlist, withdraw_for_launch_seeding, withdraw_for_damm_v2_launch).
bitview-ammMeteora DLMM v2 CPI client (initialize_lb_pair, initialize_bin_array, initialize_position, add_liquidity_by_strategy2, swap2).
bitview-launchpadToken-2022 mint creation + Metaplex metadata + revoke authority + extension allocation.
bitview-vestingStreamflow stream builders (identity reserve, BTV genesis, protocol allocation).
bitview-treasuryTreasury deposit/payout/report builders (stub).
bitview-nftMetaplex badge mint (Phase 5 stub → 501).
bitview-marketplaceSponsorship + orders CRUD.
bitview-swap-routerJupiter Lite v6 quote + build_swap; viewer cash-out path.
bitview-metricsPrometheus exporter.

Each crate is a pure library with a typed public API. The bitview-bot binary initializes singletons in order and binds nothing else. Crates pull dependencies from process-wide singletons (get_config, get_repos, get_rpc) rather than threading state through every function.

How accrual works

loop every accrual_tick_seconds (default 60s):
for each distribution with status == Active:
1. snapshot present viewers from the in-memory map
2. resolve their wallets via the users collection
3. compute the per-tick share:
floor( pool_tokens / (duration / tick_seconds) / max(1, viewer_count) )
4. if gap_since_last_tick > 1.5 × interval:
catch up min(gap_ticks, MAX_CATCHUP_TICKS) at once
5. upsert accrual rows (+amount, +1 tick) keyed by (distribution_id, twitch_login)
6. update AccrualLiveSnapshot rollup per distribution
7. skip viewers above max_per_viewer
8. stop crediting when the pool is exhausted

A separate watcher polls Twitch Helix /streams every minute for channels with active distributions and auto-finalizes the distribution when the streamer goes offline (3-minute hold-off to absorb stream hiccups).

No tokens move on-chain during a stream. The merkle root published at finalize is the on-chain reflection of every tick the loop ran.

REST surface

The exact request/response shapes are in API reference and at https://api.bitview.club/swagger-ui/ (live). Endpoints are grouped into namespaced scopes:

Viewer + identity

  • /bitview-api/* — Wallet ↔ Twitch linking (POST /user/link), by-login / by-wallet lookup, viewer accrual reads.
  • /twitch-api/* — Channel list, per-channel presence snapshot, idempotent listener connect.
  • /auth-api/* — Direct Twitch OAuth callback / unlink / link-via-handle; Keycloak link-twitch admin helper.
  • /viewer-api/* — Viewer session list, viewer preferences (read/write).

Distribution lifecycle

  • /distributions-api/* — Distribution CRUD, register, finalize (admin-gated), by-channel / by-event listings, debug tree builders.
  • /claims-api/* — Claim summary, proof lookup (SPL + NFT).
  • /distributor-tx-api/*Unsigned tx builders for the merkle-distributor program: build-init, build-init-nft, build-claim, build-claim-nft, build-clawback, build-set-enable-slot, build-fund-vault. Returns base64 unsigned tx + signers + request_id.
  • /vault-api/* — Unsigned tx builders for the streamer-vault program (init, withdraw-for-distribution, withdraw-for-pool-seeding, withdraw-for-damm-v2-launch, close, bundled fund-distribution, bundled fund-pool-seeding) + PDA lookup.
  • /launchpad-vault-api/* — Unsigned tx builders for launchpad-vault (init, deposit, add/disable/close allowlist, withdraw for launch seeding, withdraw for DAMM v2, snapshot, bundled fund-launch-pool-seeding).

Streamer + tokens

  • /streamer-api/* — Streamer profile, KYC application submission, streamer-token CRUD (token, tokens/drafts, tokens/live, PATCH /token/{mint}/progress), NFT collection registration.
  • /launchpad-api/* — Token-2022 mint creation + Metaplex metadata
    • revoke authorities + extension allocation. Unsigned-tx builders.
  • /metadata-api/* — Token-image + JSON upload (size-capped, rate-limited); CDN-style fetch by id.
  • /vesting-api/* — Streamflow stream builders (identity reserve, BTV genesis, protocol allocation).

Pools + swaps

  • /amm-api/* and /meteora-api/* — Meteora DLMM v2 pool lifecycle (init, bin-array, position, add-liquidity); pool snapshot + pool list; quote + build-swap (direct, no aggregator); active_id from price helper.
  • /swap-api/* — Jupiter aggregator quote + build-swap; default viewer cash-out path.

Marketplace + billing

  • /marketplace-api/* — Orders + sponsorships (offer, accept, streamer list, all listings).
  • /billing-api/* — Stripe checkout session + webhook + tier status.

Tx lifecycle

  • /tx-api/*POST /notify (frontend reports signed signature); GET /{request_id} polls status: built → submitted → confirmed → finalized (or failed). Background watcher reconciles every 8s.

Operator

  • /admin-api/* — App config (read + patch with cluster-switch confirmation), vesting stream registry, system health, fraud overview/slashing/log (stubbed), audit-log query (stubbed). Gated by X-Admin-Api-Key OR Keycloak bearer (bitview-ops realm with admin / ops / support role).
  • /bot-admin-api/* — Read-only operational introspection (version, listeners, accrual status per distribution, recent accruals).
  • /metrics-api/* — Dashboard KPI feed (leaderboard, distributed totals, KPI snapshot).
  • /public-api/network — Read-only public network metadata (RPC URL, explorer templates, mint addresses).

Other

  • /solana-rpc — JSON-RPC proxy (wallet adapters can't send session cookies, so this is intentionally unauthenticated; rate-limited).
  • /health — Liveness probe.
  • /swagger-ui/ and /rapidoc — OpenAPI docs.

MongoDB collections

The bot owns every collection in the system. Schemas live in bitview-model with utoipa derives so they round-trip into the OpenAPI document.

CollectionPurpose
usersBitView users: twitch_login, twitch_user_id, solana_wallet, linked_at
distributionsDistribution lifecycle docs (status, root, mint, pool params)
accrualsPer-(distribution, twitch_login) accrued amount + tick count
merkle_snapshotsFinalized merkle root + leaf count per distribution
merkle_snapshot_nodesPer-leaf proof nodes
channelsChannels the bot has ever joined
tx_logUnsigned-tx lifecycle: request_id, signature, status, timestamps
viewer_preferencesPer-wallet email + Twitch link + notification opts
billing_tiersStripe-driven tier (Free / Pro / Plus) per streamer wallet
streamer_applicationsKYC submissions, review state
app_configSingleton network/RPC/program-id/mint config (PATCH bumps version → pod restart)
streamer_tokensStreamer-launched Token-2022 mints + launch_progress
nft_collectionsStreamer Metaplex Core collections
vesting_streamsStreamflow stream registry
metadata_blobsInline-stored token image + JSON blobs
bot_listener_statesPer-channel listener heartbeat + uptime
accrual_live_snapshotsPer-distribution rolling rollup of total accrued + distinct earners
viewer_presencePersistent (channel_login, twitch_login, present) — used to hydrate the in-memory presence map on bot restart

Status

SubsystemState
Config + env + app_config singleton✅ Built
Twitch IRC oracle (per-channel listeners + presence reconciliation)✅ Built
Accrual loop (with catch-up + stream-online polling)✅ Built
MongoDB persistence (18 collections + index management)✅ Built
REST + Swagger + RapiDoc✅ Built
Twitch OAuth callback + ed25519 wallet linking✅ Built
Keycloak bearer verification (JWKS-cached, role-gated)✅ Built
Prometheus metrics✅ Built
Merkle-tree assembly + finalize pipeline✅ Built
Unsigned-tx builders: /distributor-tx-api/*✅ Built
Unsigned-tx builders: /launchpad-api/* (Token-2022 + metadata)✅ Built
Unsigned-tx builders: /vault-api/* (streamer-vault)✅ Built
Unsigned-tx builders: /launchpad-vault-api/*✅ Built
Unsigned-tx builders: /amm-api/* + /meteora-api/* (Meteora DLMM v2)✅ Built
Unsigned-tx builders: /swap-api/* (Jupiter Lite v6)✅ Built
Unsigned-tx builders: /vesting-api/* (Streamflow)✅ Built
Stripe checkout + webhook + tier sync✅ Built
Marketplace / sponsorships CRUD✅ Built
Tx lifecycle (tx_log, /tx-api/*, confirmation watcher)✅ Built
bot-admin-api/* operational introspection✅ Built
Admin app_config PATCH + cluster-switch trigger✅ Built
Anti-fraud engine (sybil detection layers)🔴 Routes stubbed; engine planned
Admin audit-log persistence🔴 Endpoint stubbed
Admin SSE event stream🔴 Sends keepalives only
NFT badge minting🔴 Phase 5 stub → 501
Treasury program integration🔴 Stub
Subscription-tier accrual weighting🔴 Tier exists in billing_tiers; not factored into share math yet
Streamflow auto-unlock tracking🔴 Builders ship streams; no auto-claim loop yet

Configuration

Bootstrap from env on first boot; thereafter the singleton app_config MongoDB doc is the source of truth. Admin can PATCH /admin-api/app-config; the bot's background watcher exits on version bump (Kubernetes restarts with new config).

Required (first boot)

VarPurpose
MONGODB_URLMongoDB connection string
MONGODB_DBDatabase name
SOLANA_RPC_URLSolana RPC endpoint
DISTRIBUTOR_PROGRAM_IDMerkle distributor program ID
BTV_MINTBitView token mint
USDC_MINTUSDC mint

Optional

VarDefault / Purpose
HTTP_HOST / HTTP_PORT0.0.0.0 / 4477
METRICS_PORT9100
ACCRUAL_TICK_SECONDS60
TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET, TWITCH_REDIRECT_URIOAuth callback
ADMIN_API_KEYLegacy back-channel for admin endpoints
KEYCLOAK_ISSUERSComma-separated; bearer verification
KEYCLOAK_AUDIENCE, KEYCLOAK_JWKS_CACHE_TTL_SECONDSJWKS gate
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETBilling (no-op if unset)
NETWORKmainnet / devnet (seeds initial app_config)
CORS_ALLOWED_ORIGINS* by default

Local development

cd bitview-bot
cp .env.example .env
$EDITOR .env
cargo run --release
# Verify
curl http://localhost:4477/health
open http://localhost:4477/swagger-ui/
curl http://localhost:9100/metrics

See Operations for the full local + production guide.

Deployment

Dockerfile builds with cargo-chef for layer caching. Runtime is Debian Bookworm slim + libssl3 + an unprivileged user (UID 1001). Exposes 4477 (HTTP) and 9100 (Prometheus). CI: .github/workflows/build-and-deploy.yml builds + pushes to registry.bitview.club on main and triggers GitOps sync.

Going deeper

For the internal engineering view — per-crate responsibilities, data-model schemas, performance budgets, the anti-sybil layer plan, and per-route operational runbooks — see the internal _internal/technical/bitview-bot-detail.md doc.

Cross-references