Introduction
Trust has been the central weakness of every raffle and prize system in history. Participants must trust that the operator has not rigged the odds, that the draw mechanism is genuinely random, that prizes will be paid in full, and that the rules will not change after tickets are sold. These are not theoretical risks โ they are documented failures that have occurred at every scale, from carnival games to state-run institutions.
Blockchain technology offers a path to eliminate trust entirely. A smart contract, once deployed, executes exactly as written. If ownership is renounced and no upgrade mechanism exists, the contract becomes a permanent, unmodifiable fixture of the Ethereum network. No entity โ not the deployer, not a government, not a majority of users โ can alter its behavior.
OnChain Raffles is built on this principle. It is not a platform operated by a company. It is a protocol โ a set of rules written in code, verified by the Ethereum network, and accessible to anyone with an Ethereum wallet. The code is the operator, and the code cannot lie.
Design Principles
The protocol is governed by five non-negotiable design constraints. Every architectural decision follows from these principles:
2.1 Absolute Immutability
After ownership renounce, no function in the contract can modify protocol parameters. Fee percentages, tier prices, round duration, draw mechanics, and payout logic are all hardcoded as Solidity constant declarations. There is no setFee(), no pause(), no upgrade(), no proxy pattern, no governance token, and no multisig. The contract deployed on day one is byte-identical to the contract running on day ten thousand.
2.2 Zero External Dependencies
The protocol uses no oracles, no off-chain computation, no API calls, no token balances, and no cross-contract interactions beyond OpenZeppelin's audited base contracts (ReentrancyGuard, Ownable). Randomness comes from blockhash() โ a native EVM opcode that has existed since Ethereum's genesis block in July 2015 and will exist as long as Ethereum does.
2.3 Zero Ongoing Cost
There are no subscriptions to maintain, no tokens to replenish, no servers to operate, and no keepers to fund. The protocol's only infrastructure requirement is the Ethereum network itself.
2.4 Permissionless Operation
Every state transition in the protocol can be triggered by any Ethereum address. Drawing, resolving, claiming, and refunding are all permissionless. No privileged role is required for any operation after the one-time ownership renounce.
2.5 Guaranteed Fund Safety
Participant funds are never at risk of permanent loss. If a draw cannot be resolved within the 256-block window, the round enters a refundable state. Participants reclaim their principal minus only the protocol fee that was already allocated. There is no scenario in which funds are locked indefinitely.
Protocol Mechanics
3.1 Tier Structure
The protocol operates five independent tiers, each with a fixed ticket price. Tiers run concurrently โ a draw in one tier has no effect on any other. Each tier maintains its own round counter, participant list, prize pool, and state machine.
| Tier | Ticket Price | Max Tickets | Max Prize Pool |
|---|---|---|---|
| Micro | 0.005 ETH | 1,000 | 5 ETH |
| Standard | 0.01 ETH | 1,000 | 10 ETH |
| High Roller | 0.1 ETH | 1,000 | 100 ETH |
| Whale | 0.5 ETH | 1,000 | 500 ETH |
| Mythical | 1.0 ETH | 1,000 | 1,000 ETH |
3.2 Round Lifecycle
Rounds follow a deterministic state machine. A new round is created automatically when the previous round's draw is committed. Rounds begin in a dormant state with no countdown โ the 24-hour clock starts only when the first ticket is purchased.
3.3 Fee Structure
Fees are calculated as basis points of the total ticket cost and deducted at time of purchase. All fee values are compile-time constants โ they cannot be modified after deployment.
| Fee | Rate | Recipient | Mechanism |
|---|---|---|---|
| Protocol | 5% (500 bps) | Fee receiver address | Pull-pattern withdrawal |
| Referral | 1% (100 bps) | Referring address | Claimable balance |
| Progressive pool | 1% (100 bps) | Monthly pool | Accumulated in contract |
The fee receiver address is set once before ownership renounce and becomes permanently fixed. The protocol employs a pull-pattern for fee withdrawal โ accumulated fees are stored in the contract and withdrawn via a permissionless withdrawFees() function. This prevents denial-of-service attacks where a reverting fee receiver could block ticket purchases.
Randomness Model
4.1 Committed Future Blockhash
The protocol uses a two-phase commit-reveal scheme based on Ethereum's native blockhash() opcode:
Phase 1 โ Commit. When a draw is triggered, the contract records drawBlock = block.number + 5. This is a future block that does not yet exist. Its hash is unknown to all parties.
Phase 2 โ Reveal. After the draw block is mined (~60 seconds), anyone calls resolveRound(). The contract reads blockhash(drawBlock) and mixes it with immutable round data:
seed = keccak256(
blockhash(drawBlock), // determined by network consensus
tierId, // constant per tier
roundNumber // monotonically increasing
)
winner = seed % totalTickets
4.2 Security Properties
The committed blockhash model provides the following security guarantees:
No resolve-time grinding. The seed is fully determined by the draw block's hash. The resolver's timing, gas price, and block selection have zero influence on the outcome. An earlier version mixed block.prevrandao at resolve time โ this was removed because it created a grindable axis where resolvers could choose which block to resolve in.
Attacker cost. To influence the outcome, an attacker must be the block proposer for the exact draw block. With ~900,000 active validators, the probability of being selected is approximately 1 in 900,000 per slot. Even as proposer, the attacker's only option is to withhold the block entirely โ sacrificing the block reward (~$170 at current rates) โ which shifts the draw to the next block's hash, controlled by a different proposer.
No oracle dependency. The blockhash() opcode has existed since Ethereum's genesis. It requires no external service, no token, no subscription, and no governance. It will function identically as long as the Ethereum Virtual Machine exists.
4.3 The 256-Block Safety Window
The EVM's blockhash() opcode returns data for only the 256 most recent blocks (~51 minutes on mainnet). If resolveRound() is not called within this window, the block hash becomes inaccessible and the round cannot be resolved. Rather than leaving funds locked, the protocol transitions the round to a REFUNDABLE state. Progressive pool contributions are reclaimed where available, and participants receive pro-rata refunds of the remaining prize pool.
In practice, this window is more than sufficient. Resolution is permissionless โ any address can call it, and automated bots can monitor for pending draws. The 51-minute window provides ample time even under extreme network conditions.
Progressive Pool
One percent of every ticket purchase across all tiers is allocated to a progressive pool. This pool accumulates over a 30-day cycle. At the end of each cycle, a permissionless draw selects one winner from all ticket purchasers in that period, weighted by ticket count. The draw uses the same committed-blockhash mechanism as tier rounds.
The progressive pool provides a cross-tier incentive structure: a participant buying Micro-tier tickets contributes to the same pool as a Whale-tier participant, creating a shared prize that grows with total protocol volume.
Immutability Guarantee
The contract inherits OpenZeppelin's Ownable with a single purpose: to allow the deployer to set the fee receiver address before permanently renouncing ownership. The renounceOwnership() function includes a safety check โ it requires that feeReceiver is already set, preventing accidental lockout of protocol fees.
After renounce, owner() returns the zero address: 0x0000000000000000000000000000000000000000. This is verifiable on-chain by anyone at any time. There are exactly two functions gated by onlyOwner:
| Function | Purpose | Post-Renounce |
|---|---|---|
setFeeReceiver() | Set fee destination | Permanently uncallable |
renounceOwnership() | Discard all admin rights | Already executed |
There is no proxy pattern. There is no delegatecall. There is no upgrade path. There is no selfdestruct. The bytecode deployed at the contract address is the bytecode that will execute every transaction for the remaining lifetime of Ethereum.
The contract also has no receive() or fallback() function โ it physically cannot accept ETH outside of the buyTickets() function. There is no mechanism for funds to enter or leave the contract outside of the defined protocol operations.
Winner Selection Algorithm
Participants who purchase multiple tickets receive proportionally more entries. The contract maintains a cumulative entry array using a CumulativeEntry struct. Each purchase appends a record containing the buyer's address and their cumulative ticket index. Winner selection maps the random seed to a ticket index, then performs O(log n) binary search over the cumulative entries to find the owning address.
struct CumulativeEntry {
address participant;
uint256 endIndex; // cumulative ticket count
}
// Winner lookup: O(log n) binary search
function _binarySearchOwner(entries, targetIndex)
โ returns participant address
This is more gas-efficient than storing individual ticket assignments and scales to the maximum of 1,000 entries per round without meaningful gas overhead.
Failure Modes and Recovery
A protocol that claims to run forever must have defined behavior for every failure scenario. OnChain Raffles handles the following edge cases:
Missed resolve window. If no one calls resolveRound() within 256 blocks of the draw block, the round enters REFUNDABLE state. Progressive pool contributions are reclaimed where the pool balance allows, and participants receive pro-rata refunds. A new round starts automatically.
Single-ticket rounds. If only one ticket is sold before the 24-hour expiry, the draw proceeds normally. The sole ticket holder wins the prize pool (their own payment minus fees). This is mathematically consistent and requires no special-case logic.
Fee receiver failure. Protocol fees use a pull pattern โ fees accumulate in the contract and are withdrawn by calling withdrawFees(). If the fee receiver address is a contract that reverts on receive(), only the fee withdrawal fails. Ticket purchases, draws, and prize claims are completely unaffected.
Progressive pool draw failure. If the monthly progressive pool draw's resolve window is missed, the prize returns to the pool for the next cycle. No funds are lost.
Economic Model
The protocol's economic sustainability comes from the 5% protocol fee โ a fixed, immutable parameter. This fee is the only revenue mechanism. There are no token sales, no governance extraction, no MEV capture, and no hidden fees. The 5% rate was chosen to be competitive with existing on-chain and off-chain alternatives while providing sustainable revenue for protocol maintenance and development.
The referral system allocates 1% to the referring address, creating an organic distribution mechanism with no cap on participants. Referral balances accumulate in the contract and are claimable at any time via claimReferralEarnings().
Winners receive 93% of the total prize pool. The remaining 7% is split between protocol fees (5%), referral fees (1%), and the progressive pool (1%). When no referrer is specified, the referral portion is redirected to the protocol fee, making the effective protocol take 6% and the winner take 93%.
Scalability Considerations
All storage structures use Solidity mappings, providing O(1) read/write access regardless of total protocol history. The contract can process its 100th round or its 100,000th round with identical gas costs. There are no arrays that grow with protocol age, no iteration over historical data in any payable function, and no storage patterns that degrade over time.
Each tier operates independently with its own round counter. During peak volume, all five tiers can have active rounds, drawings, and new rounds starting simultaneously. The theoretical throughput is one draw per ~60 seconds per tier โ over 7,000 draws per day across all tiers.
The getRecentWinners() view function uses bounded scanning (maximum 3ร the requested count per tier) to prevent unbounded gas consumption, regardless of how many historical rounds exist.
Verification
The contract source is verified on Etherscan. Every claim in this document can be independently verified:
| Claim | Verification Method |
|---|---|
| Ownership renounced | Call owner() โ returns zero address |
| Fees are immutable | Read PROTOCOL_FEE_BPS, REFERRAL_FEE_BPS, JACKPOT_FEE_BPS (progressive pool) |
| No admin functions | All onlyOwner functions revert (owner is zero) |
| No upgrade path | No proxy, no delegatecall, no selfdestruct in bytecode |
| No external dependencies | No oracle imports, no cross-contract calls beyond OZ base |
| Draw fairness | Reconstruct blockhash(drawBlock) and recompute seed |
| Prize math | Calculate totalTickets ร ticketPrice ร 93% |
| Refund availability | Check round status โ STATUS_REFUNDABLE allows claims |
The source code is the specification. There is no separate terms of service, no legal document that supersedes the contract, and no human interpretation required. The bytecode on Ethereum is the final authority on protocol behavior.
Conclusion
OnChain Raffles demonstrates that a fair, autonomous, and self-sustaining raffle system can exist without any trusted operator. By combining Ethereum's execution guarantees with deliberate architectural constraints โ no admin keys, no oracles, no upgrade paths, no external dependencies โ the protocol achieves a level of trustlessness that is verifiable by anyone who can read a smart contract.
The first round began the moment the contract was deployed. Rounds will continue for as long as participants choose to buy tickets. If no one buys for a hundred years, the next ticket purchase will start a fresh round as if nothing happened.
The code is the operator. The code cannot lie. The code runs forever.