GachamonGACHAMON
Buy
Protocol

Raffle algorithm

Each draw selects one winner from the token holders, weighted by balance. The selection is seedless and reproducible: anyone can recompute it from public on-chain data, and the contract pays exactly the holder the data selects.

Overview

A draw runs over a fixed window. At the window's close block, the protocol takes a snapshot of every eligible holder's balance and derives a single random value — the roll — from that block's hash. The roll selects one holder, weighted by stake. The result is determined entirely by public information and can be reproduced by anyone.

There is no operator seed and no commit-and-reveal of a secret. That is deliberate: a secret the operator controls is something the operator could grind or withhold. By removing it, the only inputs are values neither we nor anyone else can manipulate after the fact.

01The roll

When a draw opens, the operator commits a future close block number on-chain. Because that block has not been mined yet, its hash does not exist and cannot be predicted. Once the block is mined, the contract reads the hash itself and computes:

roll   = keccak256( closeBlockHash ‖ drawId )
offset = roll mod totalWeight

The operator cannot choose a favourable hash (the block was fixed before it existed) and cannot withhold anything (there is no secret to reveal). The roll is public and final the instant the close block lands.

02The snapshot

At the close block the protocol records every eligible holder and their balance, excluding liquidity pools, the burn address and protocol wallets. Holders are sorted by address and assigned a contiguous range of the total weight:

holder[i]:  [ cumBefore , cumBefore + weight )
cumBefore = sum of all earlier holders' weight

The snapshot is committed as a Merkle root whose leaves are keccak256( keccak256( abi.encode(address, cumBefore, weight) ) ). Publishing the root lets anyone verify the snapshot against real balances at the close block, and lets the contract verify a single holder without storing the whole list.

03Winner selection

The winner is the holder whose weight range contains the offset — a standard weighted draw where a holder's probability equals their share of the eligible supply:

winner = the holder where
         cumBefore <= offset < cumBefore + weight

Because the ranges are contiguous and cover [0, totalWeight), exactly one holder matches any offset. A holder with 4% of the eligible supply wins ~4% of draws.

04On-chain proof

Settlement is permissionless. Anyone can submit the winner together with a Merkle proof of their snapshot leaf. The contract recomputes the roll, checks that the claimed holder's range covers the offset, and verifies the proof against the published root:

settle(drawId, winner, cumBefore, weight, proof):
  offset = keccak256(closeBlockHash ‖ drawId) mod totalWeight
  require cumBefore <= offset < cumBefore + weight
  require MerkleProof.verify(proof, snapshotRoot, leaf(winner))
  → records exactly this winner

The prize vault then pays that recorded winner. Because the winner is proven against public data, the operator cannot substitute a different address — settlement either matches the public roll or reverts.

What it guarantees

An operator, even one whose hot key is compromised, cannot:

  • Influence the roll — the close block was committed before its hash existed; the contract reads the canonical hash itself.
  • Withhold or re-roll — there is no seed to reveal, so an unfavourable result cannot be quietly discarded.
  • Alter the holder set — the snapshot root is published and recomputable from on-chain balances.
  • Pay the wrong winner — settlement verifies a Merkle proof of the selected interval, or reverts.

The one residual assumption, common to any block-hash randomness, is that a block producer for the exact close-block slot has marginal influence over its hash. The effect is small and bounded; it can be eliminated entirely with a verifiable randomness beacon, a planned upgrade.

Verify it yourself

Every draw can be recomputed from its public inputs — the close-block hash and the holder snapshot — using the same code the contract runs. The interactive verifier does this in your browser; paste a draw id and watch each step reproduce on-screen.