Playing without the app
The Mini App is optional. Every action in BurnBola is a public instruction on a Solana program — callable from your own code, a script, or any tool that can send Solana transactions. This page is the map for the technically inclined.
Program ID
The program ID ships inside the app bundle alongside the IDL — together they give you typed access to everything below.
The shape of the program
BurnBola is an Anchor program. Its IDL (the machine-readable interface) ships with the app bundle and decodes every account and instruction; with the IDL and @coral-xyz/anchor you get typed access to everything below.
State lives in program-derived accounts (PDAs), keyed by seeds:
| Account | Seeds | What it holds |
|---|---|---|
| Lottery config | "lottery_config" | global settings, pause flag, treasury |
| Game | "game", game_id (u64 LE) | phase, round, counts, deadlines, exit price, on-chain showcase (name + social handles) |
| Bank vault | "bank_vault", game_id | the pot |
| Jackpot vault | "jackpot_vault", game_id | the sealed jackpot |
| Ticket registry | "ticket_registry", game_id | 2-bit status per ticket (the burn bitmap) |
| Owner record | "owner_record", game_id, wallet | your ticket ids in that game |
| Promoter pool | "promoter_pool", game_id | the game's escrowed 9% promoter pool |
| Contribution | "contribution", game_id, promoter | tickets credited to one promoter in that game |
| Game history | "game_history", game_id | final outcome, winner, totals |
| Ticket listing | "ticket_listing", game_id, ticket_id (u64 LE) | a P2P ticket offer: price, seller, the exit window it is valid for |
| Finalist pass | "finalist_pass", game_id, place (1 byte) | the champion pass itself — a Metaplex Core asset |
| Pass listing | "listing", game_id, place | a pass offer: price, seller |
| Entry round | "entry_round", game_id | how many mid-game entries this round has used, against its quota |
Player instructions
Every fee below goes to the lottery. There is no hidden spread: the numbers are constants in the contract, and each one is listed here.
| Instruction | What it does | Fee |
|---|---|---|
initialize_game | create a game (pays rent, sets the on-chain showcase) — REQUIRES the creator's buy_ticket in the same transaction | — |
buy_ticket | buy one ticket at the fixed price during Sale; optional promoter (referrer) pubkey | 1% (a further 9% is escrowed into the game's promoter pool) |
enter_game | join a running game in an exit window at the fair live price (per-round quota) | 10% on top of the price: 9% to the promoter pool, 1% to the lottery |
exit_ticket | sell one live ticket back at the round's fixed exit price | 1% — you receive 99% |
list_ticket | offer one of your live tickets for sale (exit window only) | — |
buy_listed_ticket | buy someone's listed ticket; the ticket id moves to you | 1%, paid by the seller — the buyer pays exactly the listed price |
cancel_ticket_listing | withdraw your own ticket offer; rent comes back to you | — |
close_stale_listing | anyone may sweep an offer whose exit window has passed; rent always returns to the original seller | — |
postpone_start | named-game creator delays the start by one sale-window (max 2, last-window only) | — |
settle_promoter_pool | permissionless: push a finished game's promoter pool to its promoters | — |
Champion passes and the tournament
A pass is a Metaplex Core NFT, minted to you by the contract but owned by your wallet like any other NFT.
| Instruction | What it does | Fee |
|---|---|---|
claim_finalist_pass | the winner (place 1) or runner-up (place 2) of a finished game mints their pass | — |
list_pass | offer a pass on the in-app market; the pass moves into program escrow while listed | — |
update_price | change your listed price (no asset movement) | — |
cancel_listing | take a pass back out of escrow | — |
buy_pass | buy a listed pass | 1%, paid by the seller |
enter_super_final | enter a pass into the open tournament — this burns the pass in exchange for a shot at the pot | — |
Sending a pass is not one of our instructions
Because a pass is an ordinary Core asset, you can transfer it to any address without touching our program at all — a plain Metaplex Core TransferV1 does it, and the app's "Send" button is exactly that. No fee is charged, and the contract never sees it.
The collection also declares a 10% royalty, but with no enforcement rule set: it is a request that an external marketplace may honour or ignore. So our 1% applies only to sales made through our own market. We would rather state this plainly than let you discover it. A listed pass cannot be sent, since escrow holds it while the offer stands.
Tickets are the opposite case: a ticket is not a token but an entry in a program-owned account, so it cannot move except through buy_listed_ticket, and its 1% cannot be avoided.
Crank instructions — what drives a game
These move a game through its phases. A Keeper sends them automatically for every game; the contract also lets any ticket holder of a game send the next step itself, as a permission-less fallback so a game can never depend on one server being up.
| Instruction | What it does |
|---|---|
advance_phase | move the game to its next legal phase (start, open/close exit windows, finish) |
commit_vrf / settle_vrf | bind an oracle randomness account (Switchboard / ORAO), then verify its reveal into the round seed |
burn_batch | execute a slice of the round's burns |
finalize_winner_payout | pay the winner, record history |
cancel_idle_sale / settle_cancelled_game / refund_ticket | the refund path for stalled games |
emergency_open_refund / emergency_exit_ticket / settle_emergency | last-resort refund mode if both oracle networks stay dead for ~30 days mid-round — the only refund that takes no fee |
close_game | reclaim a settled game's rent for its creator |
Phase legality, deadlines, and payouts are all enforced on-chain — a call that isn't due simply fails. In normal operation the Keeper sends all of these; the holder fallback exists only so a game can keep moving if the Keeper is ever down. (How a game runs itself.)
The randomness step is not our instruction
commit_vrf and super_commit_vrf do not create randomness — they bind a randomness account that already exists. Producing one is a separate, paid transaction to the oracle's own program, and without it the two instructions above cannot be called at all. This is the one step that is easy to miss.
js
// 1. Ask ORAO for randomness (its program, its fee — roughly 0.0003 SOL).
const seed = randomBytes(32);
await (await orao.request(seed)).rpc();
const randomnessAccount = randomnessAccountAddress(seed); // derived from the seed
// 2. Bind it to the round WHILE IT IS STILL EMPTY.
await program.methods.commitVrf(0 /* 0 = ORAO, 1 = Switchboard */)
.accounts({ caller, game, newRandomness: randomnessAccount, prevRandomness: null, ... })
.rpc();
// 3. Once the oracle has filled it (a second or two), anyone may settle.
await program.methods.settleVrf().accounts({ caller, game, randomness: randomnessAccount }).rpc();Step 2 must land before the oracle fills the account — usually a 1–2 second window. That is deliberate, and it is the core of the fairness guarantee: an account can only be bound while its value does not yet exist, so nobody can look at a number first and decide whether to use it. If you lose the race the instruction reverts; request a fresh seed and retry.
If a bound account never gets filled, commit_vrf again after the re-commit timeout, passing the stalled account as prevRandomness — the contract checks it is still empty before letting you switch. A delivered value can never be abandoned.
Driving the championship
The tournament is a small state machine on one global account (seeds: "super_game"). Read its state and round_id to know what to send.
| State | What it means | What to send |
|---|---|---|
0 Accumulate | games are finishing and passes are being claimed | nothing — finalists may already enter_super_final |
1 Entry open | the trigger fired: the pot is sealed and a deadline is set | nothing until entry_deadline_slot passes, then super_commit_vrf |
2 Draw pending | a randomness account is bound | super_settle_vrf once the oracle fills it — this pays both pools and returns the state to Accumulate |
The trigger is every 100 finished games, counted on-chain. Two rules catch people out:
- Entry is open the whole cycle, not only during the entry window — a finalist can enter the moment they claim, and does not have to catch a narrow slot.
- A pass belongs to one cycle.
claim_finalist_passandenter_super_finalboth require the game'ssuper_cycleto equal the currentround_id. A pass from an earlier cycle is a keepsake and can no longer be entered.
Same randomness step as a burn round: request an ORAO account, bind it with super_commit_vrf while it is empty, then super_settle_vrf. If both oracle networks stay dead for ~30 days, super_emergency_distribute splits the sealed pot evenly among whoever entered, so a stuck draw cannot trap the money.
| Instruction | What it does |
|---|---|
super_commit_vrf / super_settle_vrf | bind an oracle account for the champion draw, then settle it and pay the two pools |
close_leaderboard_epoch | seal a finished epoch and push every ranked promoter their share |
close_finished_records | permissionless cleanup: return each player's record rent after a game is settled |
Backstops — anyone may call these if we disappear
None of these need us, and none can be aimed anywhere but their fixed destination. They exist so that no pot can be stranded by our inaction.
| Instruction | When it becomes callable |
|---|---|
cancel_idle_sale | an undersubscribed sale sat with no activity for ~30 days |
force_open_super_final | no game has finished for ~30 days and no sale is open — opens the championship early so the passes that were earned still get their draw |
super_emergency_distribute | a draw has been stuck without randomness for ~30 days — splits the sealed pot evenly among the entrants, 75/25 across the two pools |
final_close_leaderboard | same idle condition — pays the standing leaderboard out |
reclaim_jackpot_carry | same idle condition — the carried jackpot has no next game to seed, so it goes to the fixed revenue address |
The privileged instructions — the complete list
For transparency, here is every instruction a player cannot call, and what it is bounded by. There are no others.
| Instruction | Who | What it can and cannot do |
|---|---|---|
withdraw_revenue | operator | moves lottery revenue only to the configured address — the caller picks the amount, never the destination |
set_paused | operator | blocks the creation of new games; running games are untouched and still finish |
set_treasury_authority · set_keeper · set_revenue_destination | upgrade authority | rotate the operator, the keeper, the revenue address |
reclaim_super_prize | upgrade authority | drains the tournament fund to the fixed revenue address, and is refused while any draw is in flight |
initialize_lottery · init_sale_lock · init_super_game · init_finalist_collection · migrate_config · resize_super_game | one-time setup | create or migrate the global accounts; they cannot run twice |
What is missing from that list is the point: no instruction can change a game's outcome, take a pot, or stop a game that is already running — not for the operator, not for the upgrade authority, not for us.
Practical notes
- Reads: all state is plain account data —
getProgramAccountsover the program id enumerates every game; any explorer shows the vault balances. - Batching caps (transaction-size limits, measured): 10
buy_ticketper transaction, 20exit_ticketper transaction. - Two leagues, two programs. Shrimp and Whale are separate deployments of the same code and share nothing on-chain — separate vaults, tournament and leaderboard. Point your client at the program id of the league you mean.
- Ticket ids are shared between
buy_ticketandenter_game: a mid-game entrant takes the next id, so ids are not "sale-only". - Timing is in slots, not wall-clock. Deadlines are slot numbers, so they drift against the clock as block times vary. Read the current slot, don't compute from a timestamp.
What is not in the contract
Everything else on this page is one of our instructions. These three are not, and you would otherwise find out the hard way:
| Action | Where it actually happens |
|---|---|
| Producing randomness | ORAO's (or Switchboard's) own program, as a paid request. Prerequisite for every VRF instruction — see above. |
| Sending a pass to someone | Metaplex Core, as a plain TransferV1. Our program is not in the transaction, so no fee is taken and we never see it. |
| A named game's banner image | uploaded to Arweave by the creator's own client before initialize_game; the contract only stores the resulting URL. The name and social handles, by contrast, are on-chain and verified by the contract. |
Everything a game needs to run and pay out is on-chain. The items above are either someone else's program or pure cosmetics — none of them can hold up a game or a payout.
Completeness
This page lists all 48 instructions of the program. That is checked mechanically against the IDL rather than by hand, in both directions: no instruction is missing, and nothing is named here that does not exist. If you find a gap, it is a bug in this page — tell us.
If you build something on top — a bot, an alternative UI, an analytics dashboard — you don't need our permission, and the contract won't know the difference.