The draw
Commit on the closing swap, wait 10 slots, reveal with that slot's hash and deliver the prizes. The weights freeze first, so nothing done after the close can change the winners.
flowchart TD A["Closing swap: commit"]:::key --> B["Tickets freeze, target set"] B --> C["Target slot passes"] C --> D["Someone calls reveal"] D -->|"hash still there"| E["Winners picked and paid"]:::key D -->|"512 slots passed"| F["New target"] F --> C classDef key fill:#3a2d10,stroke:#f6c869,color:#ffeccb
Commit on the closing swap
Section titled “Commit on the closing swap”The swap that brings a round to 150 counted swaps does the commit inside its own transaction. Anyone can also call the commit instruction once a round is 12 hours old, and earns $0.05 from the free pot, never more than it holds. The commit:
- copies the live ticket totals into a frozen copy, so the round’s weights can’t change any more;
- opens the next round and sets its floor;
- names the target slot, the current slot plus 10;
- marks a draw as pending.
While a draw is pending, the next round can’t close and no table entry can be replaced. Only one draw is ever pending, so draws come at most about every 5 seconds.
The target slot
Section titled “The target slot”The target is 10 slots after the commit, about 4 seconds. Its hash doesn’t exist when the commit happens.
A reveal before the target slot has passed fails. So a commit and its reveal can never share a transaction, and nobody can try a trade, see whether it wins, and undo it.
SlotHashes as the randomness
Section titled “SlotHashes as the randomness”Solana’s SlotHashes sysvar holds the hashes of the most recent 512 slots. The draw uses the target slot’s entry. If the target slot was skipped and has no block, it uses the next slot that has one.
The seed is:
seed = sha256("raffle-draw" || slot hash || seed slot as u64 LE || round as u32 LE || raffle-hook program id)The slot hash is the hash SlotHashes records for that slot (the bank hash). It is not the blockhash that getBlock returns.
Anyone can reveal, for a bounty
Section titled “Anyone can reveal, for a bounty”After the target slot, anyone can call reveal. The caller is called the liquidator. In one transaction, the reveal:
- picks the winners from the seed and the frozen tickets;
- moves the jackpot’s share of new inflow and sizes the prizes by the payout rule, from the pot balance the liquidator planned from. That balance must be at most the live balance and trail it by no more than the larger of $1 and 0.1% of the live balance. A reveal that would be a dust draw at the planned balance must also be one at the live balance. Otherwise the reveal is refused;
- sends each prize to the winner’s USDC account, creating a missing one for an account fee taken from that prize, and carries one memo listing every delivered prize;
- writes the draw into the status block: its seed, the frozen ticket total, and each winner’s token account, wallet, amount and frozen tickets;
- pays the liquidator the larger of $0.05 and 0.25% of the three regular prizes (a jackpot paid in the same draw doesn’t count), from the pot.
The bounty gives anyone a reason to reveal quickly. The team runs a public crank, the engine, as a backstop, with no special rights.
The re-target if nobody reveals in time
Section titled “The re-target if nobody reveals in time”If nobody reveals before the target slot drops out of SlotHashes, after 512 slots or about 3.4 minutes, its hash is no longer readable on chain. The next reveal call then sets a new target 10 slots ahead and draws nothing. It pays no bounty.
The frozen tickets stay exactly as they were. Only the randomness moves. A reveal after the new target draws as normal.
Frozen weights
Section titled “Frozen weights”The draw reads the frozen copy, not the live table. Buys, sells and transfers after the commit can’t move the result, even inside the reveal’s own transaction. In testing, two holders sold everything in the reveal transaction and the winners did not change.
How winners are picked
Section titled “How winners are picked”- Three prize picks, one per prize. For prize k (0, 1 or 2), take the first 16 bytes of
sha256(seed || k)as a little-endian number and reduce it modulo the round’s total frozen tickets. Call that u. The winner is the first entry, in table order, whose running total of frozen tickets is above u. - Picks are independent, with replacement. One account can win two or all three prizes in a draw. This keeps splitting neutral: your expected share equals your ticket share however many accounts you use. Without replacement, a single large wallet could win at most one prize per draw, so splitting it would pay.
- The jackpot. A separate roll from the same seed decides whether the jackpot is paid, with the chance , where is the time between this round’s close and the previous one. The roll is bytes 16 to 19 of
sha256(seed || 255)as a little-endian u32, and the jackpot is paid if it is below , computed in the program’s fixed point. If it is paid, one more pick with k = 254 names its winner. A dust draw always pays it when the round closed on the 12-hour fallback with no swaps of any size. - An empty round pays nothing. If nobody held tickets through the round, the draw pays no prizes.
To recompute a draw yourself, see Verify it on chain.
Pot Draw runs on public programs on Solana. Addresses are published at launch. Nothing here is financial advice or a promise of returns.