How 30Min works
A transparent financial protocol with a game layer. The rules live in a Solana program, the money moves through accounts anyone can inspect, and every draw can be reproduced from public data.
How the lottery works
A round lasts exactly 30 minutes. Its start and end timestamps are stored in the Round account, and the countdown on this site is computed from them.
ROUND CREATED ─▶ TICKET SALES OPEN ─▶ 30 MIN ─▶ SALES CLOSE ─▶ SNAPSHOT
─▶ RANDOMNESS (commit) ─▶ REVEAL + WINNER SELECTION ─▶ PRIZE DISTRIBUTION
─▶ ROUND FINALIZED ─▶ NEXT ROUND
status: Active ─▶ DrawPending ─▶ PayoutPending ─▶ Finalized
└─▶ Cancelled (draw couldn't complete: every ticket refunded)Every transition is an instruction on the program: start_round, buy_tickets,close_round, request_randomness, settle_draw, pay_winner, and, if a draw can't complete, cancel_round and refund_tickets. All except buying and the randomness request are permissionless, so a stalled keeper can be replaced by anyone.
Prize pool. When a round opens, a fixed share of the fee-funded $30MIN reserve (25% by default) moves into it. Every ticket bought adds its price to the same round's pool. The pool is frozen when sales close.
How tickets work
Tickets are numbered from #1 in the order they're bought. Each purchase creates one TicketPosition account holding a contiguous block:
Wallet A Tickets 1 – 50 Wallet B Tickets 51 – 70 Wallet C Tickets 71 – 170 total_tickets = 170
The program only creates a position after moving exactly ticket_price × count $30MIN from your wallet to the prize escrow. It measures the escrow balance before and after the transfer, so no tickets can exist without payment. Purchases are rejected at or after end_ts. Ticket price, the per-purchase maximum and prize tiers are copied into the round when it opens and can't be changed afterwards.
Minimum purchase. Each purchase must buy at least min_tickets_per_purchase tickets. The program enforces this itself, reading the value from the current config at purchase time (capped at the round's maximum), so it holds no matter which app or script sends the transaction. This site shows the live on-chain value.
Anyone can list every TicketPosition of a round with getProgramAccounts and check that the blocks cover #1…#N with no gaps. The round page does this for you. Blocks of finished rounds are closed after the retention period (see Old records & rent); their contents stay in the purchase transactions and in the indexer.
How randomness works
Winners are chosen with Switchboard On-Demand randomness, using a commit–reveal scheme. No server-generated number, no Math.random(), and no one on the team can pick a winner.
1. close_round ticket snapshot frozen (ticket_count fixed)
2. commit + request tx: [switchboard commit, request_randomness]
program requires seed_slot == current_slot − 1 and value unrevealed
3. reveal + settle tx: [switchboard reveal, settle_draw]
program requires reveal_slot == current_slot, stores value in DrawResultWinner derivation, identical in the program (math.rs) and in your browser (verify.ts):
for slot in 0 .. min(winner_slots, total_tickets):
for attempt in 0, 1, 2, …:
h = sha256("FEE_LOTTERY_DRAW_V1" ‖ randomness[32] ‖ round_id u64le ‖ slot u8 ‖ attempt u32le)
ticket = (u128_le(h[0..16]) mod total_tickets) + 1
if ticket not already chosen: take it, next slotOracle queue. The program only accepts randomness accounts owned by the Switchboard On-Demand program and belonging to the Switchboard queue stored in the config (switchboard_queue, shown on the Admin page), so a randomness account from a self-run queue is rejected.
At most two requests per round. If a reveal never arrives, one new request is accepted, and only after the on-chain timeout (at least 10 minutes) and only if the first request was never revealed on-chain. A value that was revealed must be used, so a bad draw can't be re-rolled. Each request increments a public counter on the round, and the round page labels a retried draw (“randomness re-requested once”), so it is visible, never silent.
Cancel and refund. If the draw still can't complete (the second request also times out, or 24 hours pass after ticket sales closed without a settled draw), anyone can cancel the round with cancel_round. Every ticket block becomes refundable at exactly what it paid: refund_tickets sends the tokens from the prize escrow back to the buyer's own token account and closes the block, so it can't be refunded twice. The keeper pushes refunds automatically, and the instruction is permissionless, so anyone can push them too. The fee-funded part of the prize goes back to the reserve for future rounds. Cancelled rounds are never closed, so a refund can't expire.
The residual trust assumption is the Switchboard oracle network. A keeper that withholds a reveal can force at most one visible retry, and if that fails too, the round is refunded. It can't pick a winner.
How creator fees are split
The coin is a regular Pump.fun create_v2 coin. It is not a holder-rewards coin and not a cashback coin, so the creator fee goes to its creator. The creator then opts into Pump's fee-sharing config with exactly two shareholders:
CREATOR FEES (SOL, every trade on the curve or PumpSwap)
│
Pump fee-sharing config (admin revoked)
┌─────────────┴─────────────┐
5,000 bps 5,000 bps
▼ ▼
lottery_vault PDA buyback_vault PDA
(program-derived) (program-derived)update_fee_shares_v2 revokes the admin, so nobody can change the split afterwards, including the team. Distribution (distribute_creator_fees_v2) is permissionless. The keeper runs it together with this program'ssync_fees, which books each inflow on-chain.
The four pools are kept apart: SOL fee accounting lives in the vaults, fee-funded $30MIN sits in the prize reserve, ticket revenue belongs to its round, and buyback tokens never touch the escrow.
How buybacks work
The keeper calls execute_swap. The program itself:
- checks the venue: the Pump bonding curve at its derived PDA while the coin is on the curve, then only the canonical PumpSwap pool;
- reads reserves from chain and computes a floor:
cp_out(spend − fee_allowance) × (1 − max_slippage); - rejects any
min_outbelow that floor, and any spend abovemax_swap_lamports(this limits sandwich size); - reads the transaction's instruction list and rejects it if any other instruction calls the Pump bonding-curve or PumpSwap program, so nobody can put their own trades around the vault's swap inside the same transaction;
- signs as the vault PDA and calls Pump's
buy_exact_quote_in_v2(curve) orbuy_exact_quote_in(PumpSwap); - measures the tokens received and SOL spent from balances, and requires
received ≥ min_out.
Each swap creates SwapRecord[seq]. Creating the same seq twice fails, so a swap can never be counted twice. The keeper also simulates every transaction before sending it. Every swap also emits a SwapExecuted event, which stays in the transaction after the record's rent is reclaimed.
How burns work
For the buyback half, the same instruction that bought the tokens burns all of them with Token-2022 burn. The mint's total supply drops. The record stores the mint supply after the burn. The swap transaction is also the burn transaction, so tokens bought for burning can't be held back.
We don't claim buybacks raise the price. The factual statement: 50% of creator fees are used to purchase the project's token and permanently burn the purchased tokens.
How to verify a round
- Open the round from History and press Verify draw. Your browser reads the
Round,DrawResultand allTicketPositionaccounts from Solana and recomputes everything. For a round whose accounts were already closed, it uses the indexer's copy (the math is the same), says so, and checks the randomness value, ticket count, prize and winning tickets against theDrawSettledevent of the settle transaction on-chain. - Do it yourself: fetch
DrawResult.randomness_value,Round.ticket_countandRound.tiers, run the derivation above, then look up which position contains each ticket. - Check the Switchboard randomness account and the reveal transaction (same transaction as
settle_draw) on an explorer. - Check each
PrizePaidtransfer goes from the prize escrow to the winner's token account.
How to verify a burn
- Open a swap row on the Burn page and follow its transaction.
- In the same transaction, you'll see the Pump buy CPI crediting the buyback vault's token account, then a Token-2022
Burnof the same amount. - Compare the mint's supply before and after, or check
SwapRecord.supply_after(or theSwapExecutedevent once the record is closed).
Old records & rent
Every Solana account holds a small SOL deposit (rent). To keep that from growing forever, old accounts are closed once they are no longer needed and their rent goes back to whoever paid it:
RoundandDrawResultof a Finalized round, and itsTicketPosition/RoundEntryaccounts, after the retention period (retention_secs, 1 day by default, shown on the Admin page);SwapRecordaccounts, after the same period;- a ticket block of a Cancelled round, when it is refunded.
Rent for ticket blocks and round entries goes back to the player's wallet; rent for rounds, draws and swap records goes back to the keeper, which paid for them. Closing is permissionless but only allowed once the rules above are met, and Cancelled rounds are never closed.
Nothing is lost: every purchase, draw, payout, refund and swap stays in its transaction on Solana as a program event (TicketsPurchased, DrawSettled, WinningsPaid, TicketsRefunded, SwapExecuted, …), and the indexer keeps a full copy. Round pages for older rounds show that copy, labelled as such, and the draw can still be verified from theDrawSettled transaction.
How the smart contract works
GlobalConfig mint, vault bumps, round & swap params, pause, totals (fees, burns, prizes, tickets)
Round id, start/end, status, frozen params, tickets, participants, prize, randomness state
TicketPosition round_id, owner, start, count, paid — one per purchase (the snapshot)
RoundEntry per-wallet per-round tickets
Player lifetime tickets / rounds / wins / prizes
DrawResult randomness value + slots, total tickets, winners[{tier, ticket, amount, wallet, paid}]
SwapRecord seq, kind (LotteryFunding | BuybackBurn), venue, spent, expected, min_out, received, burnedInvariants enforced on-chain
- Ticket: a position exists only after an exact, measured payment.
- Round: no purchases at or after
end_ts; parameters frozen at creation; every purchase within the on-chain minimum and maximum. - Randomness: only from the configured Switchboard queue; at most two requests per round; a revealed value must be used.
- Refund: only for a Cancelled round, at exactly what the block paid, to the owner's canonical token account; the block is closed in the same instruction.
- Draw:
DrawResultis created withinit, so a round can be drawn only once. - Winner: the paying position must contain the winning ticket, and the prize can only go to the owner's canonical token account.
- Payout: each winner slot has a paid flag.
- Buyback:
SwapRecord[seq]is created withinit, so no swap is counted twice. Bought tokens are burned in the same instruction, and no other Pump instruction may share the transaction.
What the admin (a multisig) can and cannot do. Can: pause new rounds, purchases and swaps; change parameters for future rounds (and the minimum purchase); rotate the keeper; set the Switchboard queue and the retention period. Cannot: pick winners, alter a live or finished round, mint tickets, move prize or vault funds, or rewrite records. There are no instructions for any of that. Pausing never blocks closing, drawing, paying or refunding a round that already exists.
Two-step authority handover. set_authority only proposes a new authority (pending_authority); nothing changes until that key signs accept_authority. A typo can't hand the program to an address nobody controls. Every admin action emits a publicAdminChange event, listed on the Admin page.
Risks
- You can lose what you spend. This is a game of chance. Most tickets win nothing. Not an investment and not a source of yield.
- Token price risk. $30MIN can lose most or all of its value. Buy & burn does not guarantee appreciation.
- Smart-contract risk. Bugs are possible despite testing. Check whether the program has been audited, and whether its upgrade authority is a multisig or has been revoked.
- Dependency risk. Pump.fun programs, Switchboard oracles, Solana congestion or outages can delay draws or swaps. The on-chain state waits and is never silently altered.
- Keeper liveness. If the keeper stops, rounds wait. Closing, drawing, paying, cancelling and refunding are permissionless, so anyone can resume them.
- Randomness failure. If the oracle doesn't deliver, a round can be cancelled. You get your tickets' price back, but the round has no winners.
- Swap execution. Buybacks can get worse prices than expected, within the enforced slippage floor.
- Legal. Lotteries and games of chance are regulated or prohibited in many jurisdictions. Check your local law before participating.