Block-Sena Technical Whitepaper
Security and Transparency

Auditable, on-chain draw verifiable by anyone

In Block-Sena, the draw uses a two-phase commit-reveal flow, combined with a future blockhash and prevrandao. This makes the result deterministic after execution, auditable on the blockchain, and resistant to manual manipulation of the drawn numbers.

6 de 30 Base game with 6 unique picks and a draw of 6 unique numbers
6 a 10 Multiple-pick ticket with combinatorial winning quotas (4/5/6 hits)
Multi-Token Per-token payout with independent settlement and auditable carry-over

Overview

Block-Sena is an on-chain lottery in which ticket purchases, round lock, draw execution, and prize distribution are recorded on the blockchain. The protocol uses a two-phase flow: blockSenaPauseDraw (locks the round and records the commitment) and blockSenaDrawPay (reveals, draws, and pays).

Goal

Guarantee an auditable, reproducible process with transparent payout rules for every round.

Auditability

Anyone can verify inputs (commit, salt, target block), seed calculation, drawn numbers, and settlement events.

Commit-Reveal (2 Phases)

Phase 1: Pause + Commit

The function blockSenaPauseDraw(usdValuePoolSnapshot, commitHash) locks the round for new purchases, sets the future target block, and stores only the secret salt hash.

Fase 2: Reveal + Draw + Pay

The function blockSenaDrawPay(salt) validates that keccak256(salt) == commitHash, uses the target-block blockhash, generates the 6 unique numbers, and executes payouts.

Technical blockhash window

The blockhash can only be read for ~256 blocks. If it expires, the round needs an operational reset. This reset is a rare fail-safe with on-chain timeout, and every use stays public on-chain.

Salt Commitment

commitHash = keccak256(abi.encodePacked(salt))
The salt is not revealed in phase 1. Only the hash is public. In phase 2, the revealed salt must match the commit exactly.

Target Block

targetBlock = block.number + 5
The draw uses the hash of a future block. At commit time, this blockhash does not exist yet, so nobody knows this input in advance.

Draw Flow (21:00 UTC -> +5 blocks -> DrawPay)

The Block-Sena operational flow clearly separates ticket closing from generation of the 6 numbers. This separation removes the risk of buying tickets after the result is known or trying to adjust tickets to match the drawn numbers.

1) 21:00 UTC (operational window) -> blockSenaPauseDraw

The function blockSenaPauseDraw(usdValuePoolSnapshot, commitHash) locks the round, blocks new purchases, records the commitHash, defines targetBlock = block.number + 5, and stores the pool snapshot in USD.

2) Tickets frozen before randomness exists

After PauseDraw, no new ticket can enter that round. At that moment, blockhash(targetBlock) still does not exist, so the final 6 numbers cannot yet be predicted or calculated.

3) +5 blocks (after target block) -> blockSenaDrawPay

The function blockSenaDrawPay(salt) validates the salt against the commit, reads the target-block blockhash, generates the 6 unique numbers, and executes per-token payouts. If called before the target block, the transaction reverts on-chain.

4) Public auditability at any time

Anyone can verify in the explorer: the blockSenaPauseDraw tx, targetBlock, the blockSenaDrawPay tx, revealed salt, drawn numbers, and settlement/payout events.

What this design prevents in practice

It is not possible to buy a ticket after seeing the 6 numbers of the round, because the round was already locked in blockSenaPauseDraw before the draw was executed.

It is not possible to know the 6 numbers at lock time, because the formula depends on a future block (targetBlock) that does not exist yet at that moment.

It is not possible to execute the draw before the technical time, because blockSenaDrawPay requires on-chain that the current block is already past the targetBlock.

About ResetDraw (rare scenario)

blockSenaResetDraw exists only to prevent a round from being permanently stuck if a long infrastructure failure happens (for example: RPC outages, service maintenance, network/infra outages, or prolonged operational issues) and DrawPay cannot be executed before blockhash(targetBlock) expires.

In the normal flow, the expected behavior is to execute DrawPay a few blocks after PauseDraw (target at +5 blocks). Reset is a liveness safeguard, not part of the normal draw flow.

Mathematical Draw Formula (6 unique numbers)

The draw seed is derived from the combination of a future block, the committed salt, and round data. From it, the contract generates numbers from 1 to 30, discarding duplicates until it reaches 6 unique numbers.

In operational security terms: because tickets are already frozen in the PauseDraw phase before that future block exists, the draw cannot be "crafted" to match a ticket inserted later, and no participant can bet in the same round after knowing the result.

Draw Seed

seed = keccak256(abi.encodePacked(blockhash(targetBlock), salt, block.prevrandao, roundId))
Real inputs used by the contract: blockhash(targetBlock), salt, block.prevrandao, and currentRoundId.

Generation of Each Number

num = (keccak256(seed, nonce) mod 30) + 1
If the number was already drawn in that round, it is discarded and the nonce is incremented. The process repeats until 6 unique numbers are obtained.

This procedure prevents repeated numbers in the final result and keeps the draw within the valid range 1..30.

Important (transparency) The result of a finalized round is deterministic and reproducible with on-chain data. Any operational intervention attempt (e.g., round reset) leaves a public and auditable trace on-chain.

Multiple-Pick Ticket (6 to 10 numbers) and Combinatorial Quotas

In Block-Sena, a ticket can contain from 6 to 10 numbers. This ticket represents multiple equivalent combinations of 6 numbers. Prize calculation is based on winning quotas (combinations), not just “people”.

Total Combinations for a Ticket with k Numbers

combos(k) = C(k, 6)
Example: if k = 10, then C(10,6) = 210 equivalent combinations.

Winning quotas by tier (4/5/6)

Consider a ticket with k numbers and x matches inside the 6 drawn numbers.

q6 = C(x, 6)
q5 = C(x, 5) * C(k - x, 1)
q4 = C(x, 4) * C(k - x, 2)
The contract sums these quotas per wallet and pays them in an aggregated form per token (more gas efficient).

Example (10-number ticket with the 6 drawn numbers inside the 10)

q6 = 1, q5 = 24, q4 = 90. The same ticket can win simultaneously in all 3 tiers.

Multi-Token Payout (Per-Token Settlement)

Each pool token (e.g., native, USDC, USDT) is settled independently. The contract calculates winners and prizes by round and by token, maintaining exact history in RoundTokenStats.

Tier Split

6-hit tier = 80% of the token pool
5-hit tier = 15% of the token pool
4-hit tier = 5% of the token pool

Prize per winning quota

prizePerWinnerX = floor(prizeBucketX / quotasX)
Division uses integer arithmetic on-chain. Rounding leftovers (dust) remain as carry-over for the next round.

If there are no winners

The amount for that tier is not lost: it becomes carry-over for that token in the next round.

If there are multiple winners

The prize is divided by winning quotas. The same wallet may receive several quotas aggregated into a single payment per token.

How to Audit a Round (Step by Step)

  1. Find the blockSenaPauseDraw tx and note the commitHash and targetBlock.
  2. After the target block, find the blockSenaDrawPay tx and read the submitted salt.
  3. Recalculate keccak256(salt) and confirm it matches the commitHash.
  4. Read blockhash(targetBlock) in the explorer and the prevrandao from the draw tx block.
  5. Reproduce the seed and the 6-number generation routine (discarding duplicates) and compare with DrawExecuted.
  6. If a rare operational failure happened, inspect DrawReset (roundId, targetBlock, reset block) to validate timeout and reset traceability.
  7. Check RoundTokenSettled events for pools, per-tier payouts, and per-token carry-over.
  8. Confirm native/ERC20 transfers to validate payments to winners.

Guarantees, Transparency and Operational Limits

Block-Sena was designed so that a round result is auditable, reproducible, and transparent. Commit-reveal combined with a future block significantly reduces the possibility of manual result manipulation.

In technical terms, the model makes the result unpredictable at lock time, because the round is closed before the blockhash(targetBlock) used in the seed exists. This keeps the formula public and auditable without allowing later bets based on the result.

What is guaranteed on-chain

Draw rules, quota calculations, tier payout split (80/15/5), per-token history, events, and traceability of each step.

Emergency operation

There is a draw reset function for rare technical scenarios (e.g., blockhash expiration caused by prolonged infra/network failure). It can only be executed with the round locked and after on-chain timeout. Every use is public, recorded on-chain, and auditable in the explorer.

Reset does not move funds, delete tickets, or alter the pool. It only unlocks the round cycle to allow a new draw attempt for the same round in a rare failure scenario.

Prize funds remain in the contract until the draw and automatic payouts to winners. If there are no winners in a tier, the value remains accounted as carry-over for the next round. The owner does not have a function to withdraw funds already accounted in the prize pool.

The rescueAll function exists only to rescue unaccounted surplus (e.g., mistaken direct transfers), calculating real balance - accounted pool. It does not reach prize funds already accounted for the pool.

In other words: Block-Sena does not ask for blind trust. It delivers a flow that can be observed, mathematically reproduced, and verified on-chain by anyone at any time.