Provably Fair: The Math
Every winner is chosen by math and Bitcoin — not by us. Here's the full breakdown.
The entire fairness model rests on one rule: the ticket snapshot and target Bitcoin block are locked before the draw. Here's the exact sequence:
- Round closes. The operator freezes the ticket snapshot — no more entries accepted.
- The target Bitcoin block height is set to current height + 1. This is published.
- The ticket snapshot hash is published — anyone can verify it later.
- When Bitcoin block #N+1 is mined, its hash is observed and used as the random seed.
- The full manifest is published, containing every input and output.
The operator cannot choose the target block after seeing its hash because the snapshot is already frozen and published. This is the standard commit-reveal pattern used by provably fair lotteries.
Most online lotteries are black boxes. DegenDraw publishes every calculation so you can run the math yourself. The only unpredictable input is the Bitcoin block hash — something no human, miner, or operator controls in advance.
If you can reproduce any draw from public inputs, you know the game is honest.
When a round closes, the operator commits to the next Bitcoin block height. Once that block is mined, three public values combine into a single random seed:
// Every input is public and independently verifiable
const seed = hash({
roundId: 42, // The round number
bitcoinBlockHash: "0000...a1b2", // Bitcoin block hash (unpredictable)
ticketSnapshotHash: "7ba9...2928" // Hash of all ticket entries
});
// Result: a 64-character hex string
// "e126326c4eef61ad55f8201ba4748da4ed70c75a875124c4d79befeda60ae33b"The Bitcoin block hash is the only unpredictable component. The round ID and ticket snapshot are frozen before the draw and published in the manifest.
A Bitcoin miner who finds the target block could theoretically withhold it to re-roll the hash. This is economically irrational — they risk losing the block subsidy of 3.125 BTC plus transaction fees. The operator uses the first publicly observed block at the target height, which prevents the operator from cherry-picking among visible blocks, but cannot prevent a miner from withholding an unpublished block. For ordinary lottery sizes, this is not a realistic attack vector.
The random seed is converted to a number and reduced modulo the total tickets. This guarantees a uniform distribution — every ticket has exactly equal odds.
// Convert hex seed to BigInt
const seedNumber = BigInt("0x" + seed);
// Modulo total tickets — negligible bias for realistic ticket counts
// With SHA-256 (256-bit output) and normal ticket counts (≤10,000),
// the bias is far below 1 in 2^200 — mathematically undetectable
const winningTicket = Number(seedNumber % BigInt(totalTickets));
// Example: totalTickets = 237, seed hash = "e126...3b"
// winningTicket = BigInt("0xe126...3b") mod 237n = 89
// Ticket #89 wins the jackpotThe winner address is found by scanning the ticket snapshot: the entry whose range contains the winning ticket number holds the jackpot.
50% of the pot goes to instant prizes. Half the tickets win 75% of their entry back, the other half win 25%. Every ticket wins something.
// Instant prize pool = 50% of pot
const instantPrizePool = (totalPot * 50n) / 100n;
// Count tickets, split into two tiers
const highCount = floor(totalTickets / 2);
const lowCount = totalTickets - highCount;
// Each tier splits its half of the pool evenly
const halfPool = (instantPrizePool * 5000n) / 10000n; // 50% of instant pool
const lowPerTicket = halfPool / BigInt(lowCount);
const highPerTicket = halfPool / BigInt(highCount);
// Which tickets get high vs low? Deterministic per ticket:
// hash("TNK_LOTTERY_INSTANT_PRIZE_TIER_V1" + seed + ticketNumber)
// Convert to number mod totalTickets. If n >= lowCount → high tierBonus tickets from the Loyalty Boost are excluded from instant prize distribution — they compete only for the jackpot and Daily Grand.
10% of every deposit is deducted first for the Daily Grand reserve. The remaining 90% becomes the round pot. The round pot splits as percentages of itself:
// Gross deposit: 100%
// Daily Grand: 10% (deducted at deposit time, accumulated in reserve)
// Round pot: 90% of gross deposit
// From the round pot (in basis points):
const winner37 = applyBasisPoints(roundPot, 3700); // 37.00% — jackpot winner
const instant50 = applyBasisPoints(roundPot, 5000); // 50.00% — instant prizes
const host2 = applyBasisPoints(roundPot, 200); // 2.00% — operator fee
const watcher1 = applyBasisPoints(roundPot, 100); // 1.00% — independent watcher
// Total = 90% of round pot (81% of gross deposit + 10% daily grand = 100%)
// applyBasisPoints(amount, bp) = (amount * bp) / 10000
// Uses BigInt throughout — no decimal rounding ever
// Bonus tickets are excluded from pot calculation:
// totalPot = paidTickets * ticketPrice (bonus tickets have zero purchase value)Bonus tickets from the Loyalty Boost compete for the jackpot and Daily Grand but do not increase the pot or receive instant prizes. Instant prize distribution uses only paid-ticket counts.
Every 20 tickets purchased in a single transaction earns 1 free bonus entry (max 5 at 100 tickets). These tickets:
- Are eligible for the Hourly Jackpot (count in totalTickets)
- Are eligible for the Daily Grand Prize (count in bonusEntryTickets)
- Are excluded from instant prize distribution (marked source: "bonus")
- Have zero purchase value — they cost nobody anything
The instant prize pool is calculated from only the non-bonus ticket counts, so bonus entries never dilute anyone's instant win.
Every round produces a manifest — a JSON document containing every input and output of the draw. You can run the same math and check that the manifest hash matches.
// 1. Get the manifest from the operator or audit feed
const manifest = fetchManifest(roundId);
// 2. Recompute the random seed from public inputs
const ticketSnapshotHash = hashTicketSnapshot(manifest.ticketSnapshot);
const randomHash = hash({
roundId: manifest.roundId,
targetBitcoinHeight: manifest.targetBitcoinHeight,
bitcoinBlockHash: manifest.bitcoinBlockHash,
ticketSnapshotHash,
});
// 3. Derive the winning ticket
const winningTicket = BigInt("0x" + randomHash) % BigInt(manifest.totalTickets);
// 4. Rebuild the full manifest and compare hashes
const recomputed = buildRoundManifest({...inputs...});
assert(recomputed.manifestHash === manifest.manifestHash);
// If this passes, the draw was honestThe operator operator verifies every manifest before publishing. An independent watcher also runs this check and publishes its own confirmation.
All hashing uses a deterministic canonical JSON serialization followed by SHA-256. Object keys are sorted, numbers are serialized without whitespace, and the result is always identical for the same inputs.
// Canonical serialization guarantees deterministic output
function hashCanonical(obj) {
const serialized = JSON.stringify(obj, sortedKeys, noSpaces);
return SHA256(serialized); // hex-encoded
}
// Used for: ticket snapshot hash, random seed, manifest hash
// All strings are normalized (lowercase hex, no "0x" prefix)You Don't Need to Trust Us
The Bitcoin blockchain is the random number generator. The ticket snapshot is public before the draw. The manifest is published after. If you can perform the hash, the modulo, and the comparison — you've verified the entire game. No trust required.