Skip to main content

Distribution modes & asset selection

Every distribution event is shaped by two orthogonal choices:

  1. Mode — who funds the rewards (Native, Identity, Sponsored).
  2. Assets — what viewers actually earn (BTV, the streamer's own token, NFT collections, or any combination).

The wizard at /studio/distributions/new infers the mode from the streamer's billing tier and the assets they pick — there is no separate tier-picker screen. Sponsored events originate elsewhere (brands initiate them at /sponsor); the streamer-side wizard only ever produces Native and Identity distributions.

Tier matrix

PlanBTVIdentity tokenNFT collection / airdropSponsorship
Free
Pro
Plus

Tier is enforced as a hard gate, not a preview: a Free wallet doesn't see the Token or NFT pages in the studio at all. The asset selector only renders the rows the streamer's plan unlocks.

The three modes

Native

Available on every plan. Funded by the streamer in USDC or SOL.

The streamer swaps USDC or SOL into BTV inside the wizard, then funds the merkle distributor with BTV. Viewers earn BTV that they can claim and trade. This is the canonical mode and the one the wizard defaults to.

Touch points:

  • Step 1: pick Native (default).
  • Step 2: parameters (channel, title, periodicity, duration, pool size) — and now the asset selector.
  • Step 3: swap USDC/SOL → BTV via Jupiter (SwapPanel).
  • Step 4: sign one tx per distribution. The wizard creates a Metaplex Core asset that anchors the event, then POSTs to /distributions-api/register.

Identity

Pro and Plus only. Distribute your own streamer token.

The streamer first launches a fungible token via /studio/token. The launchpad sequence runs on the backend's /launchpad-api/* routes — create mint, mint full supply, attach Metaplex metadata, then revoke mint authority (the supply-cap invariant). The wallet signs four transactions and one backend registration.

One streamer token per wallet. The backend enforces this with a unique Mongo index on streamer_tokens.streamer_wallet. A duplicate registration returns 409 Conflict. Once the token is registered, the creation form is hidden — /studio/token flips to the detail view showing mint address, supply, decimals, and explorer links.

The token then becomes selectable in the distribution wizard. Identity-mode distributions match-fund the STREAM/BTV pool with BTV so viewers can cash out — see streamer-token.

Plus only. A brand pays the rewards through the marketplace.

The streamer accepts a brand's sponsorship offer; the brand's USDC funds the vault directly (no streamer-side swap step). The streamer earns a 5% co-share on every accepted sponsorship. The asset selector still works — the brand can pay rewards in BTV, the streamer's token, or NFT airdrops.

Asset selection

Step 2 of the wizard renders an Asset selector with one row per selectable asset:

  • BTV — always on, always first. Amount mirrors the main pool slider. Cannot be removed (every event must distribute at least one asset, and BTV is the canonical one).
  • Streamer token — visible iff the streamer has launched one. The row carries the token's symbol and lets the streamer set an independent amount.
  • NFT collections — one row per collection registered at /studio/collections. Amount = number of NFTs to airdrop.

If the streamer selects multiple assets, the wizard spawns N distributions at step 4 — one per asset — and groups them under a shared event_id. There is no on-chain bundling: each asset has its own merkle tree, its own claimable PDA, and its own claim window. That keeps the on-chain surface simple and lets viewers claim each asset independently.

Asset registry

Two new Mongo collections back this:

  • streamer_tokens — one document per streamer wallet. Unique index on streamer_wallet is the hard guarantee of one-per-streamer.
  • nft_collections — many documents per streamer wallet. Indexed by streamer_wallet + created_at for the studio list view.

REST surface under /streamer-api/:

  • GET /streamer-api/token/{wallet}200 with the token doc, or 404 when none is registered.
  • POST /streamer-api/token — register a freshly-minted token. Returns 409 if the streamer already has one.
  • GET /streamer-api/collections/{wallet} — list registered collections.
  • POST /streamer-api/collections — register a newly-created Metaplex Core collection.
  • GET /streamer-api/profile/{wallet} — now includes streamer_token and nft_collections so the admin dashboard reflects launch state alongside KYC, tier, and distribution counts.

NFT collections use Metaplex Core (mpl-core) and are created client-side via Umi — the backend only persists the resulting collection address. The fungible token launch uses Token-2022 and runs through the existing /launchpad-api/* builders.

Admin reflection

The admin app at bitview-admin surfaces both registries on the streamer detail page (/streamers/[wallet]):

  • Streamer token card — name, symbol, mint, decimals, total supply. Only renders when the wallet has a registered token.
  • NFT collections card — list of every collection the streamer has registered, with cap and short address.

The admin can use these to verify a streamer's launchpad state when investigating fraud flags or sponsorship eligibility.

Funding model

The wizard does not fund the merkle distributor vault directly. For BTV and streamer-token distributions the flow is:

  1. Wizard (/studio/distributions/new) anchors the event by creating a Metaplex Core asset per selected asset and posting a Distribution row per asset to /distributions-api/register.
  2. Accrual loop in the bot tracks per-viewer earnings against each Distribution row.
  3. Snapshot phase (admin-triggered or scheduled) takes the final accrual state, builds the merkle tree, and computes the (max_total_claim, max_num_nodes, root) needed by the on-chain init_distributor instruction.
  4. /distributor-tx-api/build-fund-vault returns two unsigned transactions (init + fund) that, when signed by the streamer, atomically create the distributor PDA + vault ATA and transfer the snapshot's max_total_claim from the streamer's ATA to the vault.

Because step 4 is parameterised on mint, BTV and streamer-token distributions use the exact same code path — they only differ in which SPL ATA the streamer is transferring from. No per-asset funding UI is needed in the wizard.

NFT distribution (on-chain merkle distributor)

The merkle-distributor program already ships init_nft_distributor + claim_nft_core (see merkle-distributor/src/instructions/init_nft_distributor.rs and claim_nft_core.rs). The handler verifies the leaf proof, flips a bitmap, and CPIs into mpl-core::CreateV2 to mint the asset directly to the claimant — the bot is bypassed entirely at claim time. The bot only ever builds the unsigned transactions; the streamer and viewers sign in their browsers.

  1. Collection creation (/studio/collections/new) creates a Metaplex Core collection with the streamer as update_authority.
  2. Accrual loop counts NFT slots a viewer earned, exactly the same way it counts SPL token amounts. asset_kind = "nft-collection" on the Distribution row, mint carries the collection address.
  3. Snapshot phase materialises the merkle tree via bitview-merkle::build_nft_tree and writes one node per claimant to merkle_snapshot_nodes.
  4. Streamer init (one signature for the whole event)lib/nftDistributor.ts::initNftDistributor orchestrates:
    1. mpl-core UpdateV2 transferring the collection's update_authority to the (deterministic) NFT distributor PDA,
    2. POST /distributor-tx-api/build-init-nft returning the init_nft_distributor tx (streamer + ephemeral base Keypair co-sign).
  5. Viewer claim (per claim, viewer signs)lib/claim.ts::claimNftDistribution:
    1. GET /claims-api/proof/{distribution_id}/{wallet} returns (index, metadata_uri, proof),
    2. POST /distributor-tx-api/build-claim-nft returns the claim_nft_core tx (viewer + fresh asset Keypair co-sign).

After step 5 the viewer holds a freshly-minted Core asset with a Royalties plugin pointing at the streamer's wallet at the configured royalty_bps. Trustless on-chain, no backend signer.

Streamer-token launchpad (whitepaper 10 / 5 / 85)

The full launchpad sequence runs from /studio/token and matches streamer-token.md §Supply allocation exactly:

StepWhatSignature path
1create-mint (Token-2022)Streamer + ephemeral mint Keypair
2mint-supply (100% to streamer ATA)Streamer
3create-metadata (Metaplex)Streamer
4revoke-mint-authorityStreamer
5Streamer reserve Streamflow stream — 10% of supply, 24-month linear, no cliffStreamer + reserve metadata Keypair
6BitView protocol allocation Streamflow stream — 5% of supply, 48-month linear, no cliffStreamer + protocol metadata Keypair
7Register with backend(no signature, REST POST)

After step 7 the streamer's ATA holds the remaining 85% — pool seeding + distributions draw from this float. Steps 5 and 6 lock the whitepaper-mandated portions in Streamflow streams so the streamer cannot dump their full supply on day one. Both streams are uncancellable, non-transferable, non-pausable per identity_reserve_params / protocol_allocation_params.

Step 6 is skipped when NEXT_PUBLIC_BITVIEW_TREASURY is unset (dev environments without a treasury wallet). The streamer can re-run it later from /studio/token once the operator publishes a treasury pubkey.

What's intentionally out of Phase 3

  • On-chain multi-asset distributors. The Anchor program still takes one mint per distributor — multi-asset is orchestrated at the UI layer with N independent distributors sharing an event_id.
  • Batch-packed NFT mints. Phase 3 sends one tx per NFT to keep the UX predictable; optimised packing (3-4 creates per tx) is a follow-up.
  • Cross-tab race on token creation. The unique Mongo index guarantees only one row ever lands, but the second tab will see a 409 rather than the existing token. The studio refresh fixes that on the next load.