Blockster Bankroll — full reference.
The Anchor program backing every bet and every pool. Every instruction, every account, every PDA seed, every error code. Built to be read end-to-end by auditors and integrators.
Program overview
- Program ID
- 49up2uzZANpjTC3sgggbZazdHBii2vY9mVK3vk5dT2tm
- Cluster
- mainnet
- Framework
- Anchor 0.30+ (Rust)
- Crate
- contracts/blockster-bankroll/programs/blockster-bankroll
- Settler service
- contracts/blockster-settler (Node/TypeScript)
- IDL
- contracts/blockster-bankroll/target/idl/blockster_bankroll.json
- Number of instructions
- 22
- Number of account structs
- 7
- Number of error codes
- 32
- Number of emitted events
- 14
The program is a single crate — no cross-program call chains that aren't standard CPIs (System, SPL Token, Metaplex Token Metadata). It holds two vaults (SOL + BUX), mints two LP tokens (SOL-LP + BUX-LP), registers up to 10 games with parametric multipliers, implements a commit-reveal settlement scheme, and supports a two-tier referral programme.
PDAs and seeds
| PDA | Seeds | Owner | Purpose |
|---|---|---|---|
| Game registry | [b"game_registry"] | Program | Singleton; holds authority, settler, game table, bumps, pause flag |
| SOL vault | [b"sol_vault"] | System | Holds raw SOL lamports |
| SOL vault state | [b"sol_vault_state"] | Program | Accounting: liability, house_profit, volume, stats |
| BUX vault state | [b"bux_vault_state"] | Program | Accounting for the BUX pool |
| BUX token account | [b"bux_token_account"] | SPL Token | Holds BUX tokens; authority = itself |
| SOL-LP mint | [b"bsol_mint"] | SPL Token | LP token mint for the SOL pool |
| SOL-LP mint authority | [b"bsol_mint_authority"] | Program | Signs SOL-LP mint/burn via CPI |
| BUX-LP mint | [b"bbux_mint"] | SPL Token | LP token mint for the BUX pool |
| BUX-LP mint authority | [b"bbux_mint_authority"] | Program | Signs BUX-LP mint/burn via CPI |
| Player state | [b"player", player_pubkey] | Program | Per-player: pending commitment, nonce, referrer, lifetime stats |
| Bet order | [b"bet", player_pubkey, nonce.to_le_bytes()] | Program | Per-bet: amount, max_payout, commitment, status; closes on settle/reclaim |
| Referral state | [b"referral", referrer_pubkey] | Program | Per-referrer: cumulative rewards, referral count |
Account structures
GameRegistry
#[account]
pub struct GameRegistry {
pub authority: Pubkey, // 32 — set once at init
pub settler: Pubkey, // 32 — rotatable via update_config
pub bux_mint: Pubkey, // 32
pub paused: bool, // 1
pub bet_timeout: i64, // 8 — seconds before reclaim eligible
pub referral_bps: u16, // 2 — tier-1 rate
pub tier2_referral_bps: u16, // 2 — tier-2 rate
pub sol_vault_bump: u8, // 1
pub sol_vault_state_bump: u8, // 1
pub bux_vault_state_bump: u8, // 1
pub bux_token_account_bump: u8, // 1
pub bsol_mint_bump: u8, // 1
pub bsol_mint_authority_bump: u8, // 1
pub bbux_mint_bump: u8, // 1
pub bbux_mint_authority_bump: u8, // 1
pub game_count: u64, // 8
pub games: [GameEntry; 10], // 730
pub _reserved: [u8; 128], // 128
}
pub struct GameEntry {
pub game_id: u64, // 8
pub name: [u8; 32], // 32 — padded ASCII, e.g. b"coin_flip\0\0..."
pub active: bool, // 1
pub min_bet: u64, // 8 — lamports or BUX base units
pub max_bet_bps: u16, // 2 — cap 500 (= 5%)
pub fee_bps: u16, // 2 — cap 1000 (= 10%), currently unused in settlement
pub multipliers: [u16; 9], // 18 — stored as bps/100; e.g. 198 → 1.98×
pub _reserved: [u8; 2], // 2
}
SolVaultState and BuxVaultState
Parallel structures — one per vault. Same fields, different account type for Anchor type safety.
#[account]
pub struct SolVaultState {
pub total_deposited: u64, // Cumulative deposits by LPs
pub total_withdrawn: u64, // Cumulative withdrawals
pub total_liability: u64, // Sum of max_payout for pending bets
pub unsettled_count: u64, // Number of pending bets
pub unsettled_bets: u64, // Sum of wager amounts for pending bets
pub house_profit: i64, // Net vault P&L (signed, can be negative)
pub total_bets: u64, // Lifetime settled bet count (excludes reclaimed)
pub total_volume: u64, // Lifetime sum of settled wagers
pub total_payout: u64, // Lifetime sum of paid-out winnings
pub largest_bet: u64,
pub largest_payout: u64,
pub lp_deposits_count: u64,
pub lp_withdrawals_count: u64,
pub total_referral_paid: u64,
pub _reserved: [u8; 128],
}
PlayerState
#[account]
pub struct PlayerState {
pub player: Pubkey, // 32
pub pending_commitment: [u8; 32], // SHA256 of server seed for pending_nonce
pub pending_nonce: u64,
pub nonce: u64, // Next nonce to use
pub has_active_order: bool, // Reserved; currently not read or written
// Referrals
pub referrer: Pubkey, // Tier-1; Pubkey::default if none
pub tier2_referrer: Pubkey, // Auto-resolved at set_referrer
// Lifetime stats
pub total_orders: u64,
pub total_wagered_sol: u64,
pub total_wagered_bux: u64,
pub net_pnl_sol: i64, // Can be negative
pub net_pnl_bux: i64,
pub bump: u8,
pub _reserved: [u8; 64],
}
BetOrder
pub enum BetOrderStatus { Pending, Settled, Expired }
pub enum VaultType { Sol, Bux }
#[account]
pub struct BetOrder {
pub player: Pubkey,
pub game_id: u64,
pub vault_type: VaultType,
pub amount: u64, // Wager
pub max_payout: u64, // amount × multiplier_bps / 10_000
pub commitment_hash: [u8; 32], // Copied from PlayerState at placement
pub nonce: u64,
pub status: BetOrderStatus, // Always Pending while live
pub created_at: i64, // unix_timestamp
pub bump: u8,
pub rent_payer: Pubkey, // Locked at placement to game_registry.settler
}
BetOrder accounts close on settle_bet or reclaim_expired. The rent rebate goes back to rent_payer — the settler keypair in force at placement. Rotating the settler does not rotate rent_payer on already-placed bets.
ReferralState
#[account]
pub struct ReferralState {
pub referrer: Pubkey,
pub total_referrals: u64, // Unique players who chose this referrer
pub total_earnings_sol: u64,
pub total_earnings_bux: u64,
pub bump: u8,
pub _reserved: [u8; 32],
}
Constants
- MINIMUM_LIQUIDITY
- 10_000 — base units burned on first deposit (donation-attack defence)
- MAX_GAMES
- 10 — maximum registered games in the registry array
- BPS_DENOMINATOR
- 10_000 — 100% in basis points
- MAX_FEE_BPS
- 1_000 — 10% cap on per-game fee_bps
- MAX_SPLIT_BPS
- 500 — 5% cap on each referral tier
- MAX_BET_BPS
- 500 — 5% cap on per-game max_bet_bps
- MIN_BET_TIMEOUT
- 60 seconds — floor on game_registry.bet_timeout
- LP_PRICE_PRECISION
- 1_000_000_000 (1e9) — scaling factor for LP price display
- MULTIPLIER_SCALE
- 100 — multipliers are stored as bps/100 to fit u16
Instructions
The program exposes 22 instructions organised into five groups. Each is listed below with signer requirements, arguments, side effects, and the errors it can raise.
Initialization (one-time, authority-only)
| Instruction | Args | Purpose |
|---|---|---|
| initialize_registry | bet_timeout: i64 | Create GameRegistry, SolVaultState, BuxVaultState. Set authority, settler, bux_mint. Default referral_bps=100, tier2=50. |
| initialize_sol_pool | — | Create SOL-LP mint with program-owned mint authority PDA. |
| initialize_bux_pool | — | Create BUX-LP mint. |
| initialize_bux_vault | — | Create BUX token account PDA. Emits BankrollInitialized. |
| register_game | game_id, name, min_bet, max_bet_bps, fee_bps, multipliers[9] | Add a game to the registry (idempotent fails). Enforces bps caps and minimum multiplier values. |
| create_lp_metadata | vault_type, name, symbol, uri | CPI to Metaplex to attach metadata to SOL-LP or BUX-LP mint. |
Liquidity (anyone)
| Instruction | Args | Purpose |
|---|---|---|
| deposit_sol | amount: u64 | Move SOL into sol_vault, mint SOL-LP to depositor. Rejects if paused. |
| withdraw_sol | lp_amount: u64 | Burn SOL-LP, move SOL out to withdrawer. Rejects if paused or if underlying > available_balance. |
| deposit_bux | amount: u64 | Move BUX into vault, mint BUX-LP. |
| withdraw_bux | lp_amount: u64 | Burn BUX-LP, move BUX out. |
Betting (commit-reveal)
| Instruction | Signer | Purpose |
|---|---|---|
| submit_commitment | settler | Write SHA256(server_seed) to PlayerState. Nonce must be ≥ current player nonce. init_if_needed creates PlayerState first time. |
| place_bet_sol | player + settler (rent_payer) | Transfer SOL wager, create BetOrder PDA, clear commitment, increment nonce. Enforces all placement guards. |
| place_bet_bux | player + settler (rent_payer) | Same as place_bet_sol, but moves BUX. |
| settle_bet | settler | Reveal server_seed (hash must match commitment). Pay player if won. Process referral rewards on loss. Close BetOrder and return rent. |
| reclaim_expired | player | After bet_timeout, refund wager to player. Close BetOrder and return rent. Does not touch total_bets / house_profit. |
Referrals
| Instruction | Signer | Purpose |
|---|---|---|
| set_referrer | player | One-time; set tier-1 referrer. Auto-resolves tier-2 from referrer's referrer. Blocks self-refer and two-hop loops. Creates ReferralState for the referrer if needed. |
Admin (authority-only)
| Instruction | Args | Purpose |
|---|---|---|
| update_config | all Option; may update: settler, bet_timeout, referral_bps, tier2_referral_bps, per-game config | Partial update with caps enforced (max fee 10%, max split 5%, max bet 5%, min timeout 60s). |
| pause | paused: bool | Toggle global paused flag. Blocks deposit/withdraw/place_bet/submit_commitment/set_referrer. Does NOT block settle_bet or reclaim_expired (intentional). |
Error codes
| Code | Name | Meaning |
|---|---|---|
| 6000 | Paused | Global pause flag is set. |
| 6001 | ZeroAmount | Amount argument must be > 0. |
| 6002 | MathOverflow | checked_* arithmetic overflowed/underflowed. |
| 6003 | InvalidConfig | Configuration value out of valid range (e.g. multiplier < 100). |
| 6004 | FeeTooHigh | fee_bps or referral_bps exceeds its cap. |
| 6005 | SplitTooHigh | tier-1 + tier-2 referral_bps exceeds MAX_SPLIT_BPS (500). |
| 6006 | BetTimeoutTooShort | bet_timeout below MIN_BET_TIMEOUT (60s). |
| 6007 | DepositTooSmall | First deposit below MINIMUM_LIQUIDITY, or subsequent deposit that would mint 0 LP. |
| 6008 | InsufficientLiquidity | Withdrawal would exceed available_balance (after rent and liability). |
| 6009 | WithdrawTooLarge | lp_amount would redeem to 0 underlying (dust). |
| 6010 | GameRegistryFull | Attempt to register an 11th game. |
| 6011 | GameAlreadyRegistered | Duplicate game_id at registration. |
| 6012 | GameNotFound | Game ID absent or inactive at bet placement. |
| 6013 | InvalidDifficulty | difficulty index ≥ 9. |
| 6014 | MultipliersNotConfigured | Requested difficulty has stored multiplier == 0. |
| 6015 | BetTooSmall | amount < game.min_bet. |
| 6016 | BetTooLarge | amount > per-difficulty max_bet. |
| 6017 | MaxPayoutExceedsCapacity | potential profit > net_balance. |
| 6018 | ActiveBetExists | Reserved. Not currently raised. |
| 6019 | NonceMismatch | nonce arg ≠ player_state.pending_nonce (or < existing nonce in submit_commitment). |
| 6020 | NoCommitment | player_state.pending_commitment is zeroed. |
| 6021 | CommitmentMismatch | Reserved. Not currently raised (hash check uses InvalidServerSeed). |
| 6022 | InsufficientVaultBalance | Vault lacks funds for payout (defence-in-depth; should be unreachable given placement guards). |
| 6023 | OrderNotPending | BetOrder.status ≠ Pending. |
| 6024 | OrderExpired | settle_bet called after bet_timeout elapsed. Use reclaim_expired. |
| 6025 | OrderNotExpired | reclaim_expired called before bet_timeout elapsed. |
| 6026 | InvalidServerSeed | SHA256(server_seed) ≠ bet_order.commitment_hash. Also reused for has_one=player mismatch (misleading message). |
| 6027 | PayoutExceedsMax | settle_bet payout arg > bet_order.max_payout. |
| 6028 | SelfReferral | Player tried to set self as referrer. |
| 6029 | ReferrerAlreadySet | Player already has a tier-1 referrer. |
| 6030 | UnauthorizedSettler | Signer is not the registry settler or authority, depending on instruction. |
| 6031 | InvalidVaultType | vault_type mismatch at settlement (e.g. BUX bet with missing player_bux_account). |
| 6032 | InvalidMint | Mint address doesn't match expected PDA. |
| 6033 | InvalidRentPayer | rent_payer at placement ≠ game_registry.settler; or bet_order.rent_payer at settle/reclaim ≠ supplied account. |
Events
| Event | Triggered by | Fields |
|---|---|---|
| BankrollInitialized | initialize_bux_vault | authority, settler, sol_vault, bux_vault, bsol_mint, bbux_mint |
| GameRegistered | register_game | game_id, name, active |
| ConfigUpdated | update_config | field_id (1=settler, 2=timeout, 3=t1_bps, 4=t2_bps), old_value, new_value |
| PauseToggled | pause | paused |
| SolDeposited | deposit_sol | depositor, sol_amount, lp_minted, vault_balance, lp_supply |
| SolWithdrawn | withdraw_sol | withdrawer, sol_amount, lp_burned, vault_balance, lp_supply |
| BuxDeposited | deposit_bux | depositor, bux_amount, lp_minted, vault_balance, lp_supply |
| BuxWithdrawn | withdraw_bux | withdrawer, bux_amount, lp_burned, vault_balance, lp_supply |
| CommitmentSubmitted | submit_commitment | player, nonce, commitment_hash |
| BetPlaced | place_bet_* | player, game_id, vault_type, amount, max_payout, nonce |
| BetSettled | settle_bet | player, nonce, won, amount, payout, vault_type, server_seed |
| BetReclaimed | reclaim_expired | player, nonce, amount, vault_type |
| ReferrerSet | set_referrer | player, referrer, tier2_referrer |
| ReferralRewardPaid | settle_bet (on loss) | referrer, player, amount, vault_type, tier |
| ReferralRewardFailed | settle_bet (on loss, transfer failed) | referrer, player, amount, vault_type, tier |
Pause behaviour
The authority can flip game_registry.paused at any time via the pause instruction. This is the emergency brake.
- Blocks
- deposit_sol, withdraw_sol, deposit_bux, withdraw_bux, submit_commitment, place_bet_sol, place_bet_bux, set_referrer
- Does not block
- settle_bet, reclaim_expired — so any pending bet can always be resolved
- Does not block
- update_config, register_game, create_lp_metadata, pause itself — admin retains control during pause
Upgrade authority
The program is deployed with a standard Solana upgrade authority — an Ed25519 keypair that can redeploy the program bytecode at any time. This is orthogonal to the program-internal authority role; in principle they could be the same key or different.
- Held by
- Settler keypair; planned migration to 2-of-3 multisig
- Rotation
- Solana CLI solana program set-upgrade-authority
- Disabling upgrades
- Possible via solana program set-upgrade-authority --final (irreversible)
- Compromise scenario
- Attacker could redeploy malicious bytecode and drain vaults. Mitigated by multisig hold on mainnet.