Block-Sena Whitepaper Técnico
Segurança e Transparência

Sorteio auditável, on-chain e verificável por qualquer pessoa

No Block-Sena, o sorteio usa um fluxo commit-reveal em duas fases, combinado com blockhash futuro e prevrandao. Isso torna o resultado determinístico após a execução, auditável na blockchain e resistente a manipulação manual dos números sorteados.

6 de 30 Jogo base com 6 números únicos e sorteio de 6 números únicos
6 a 10 Aposta múltipla com quotas combinatórias (quadra, quina e sena)
Multi-Token Payout por token com liquidação independente e carry-over auditável

Visão Geral

O Block-Sena é uma loteria on-chain em que as compras de bilhetes, bloqueio da rodada, sorteio e distribuição de prêmios são registrados na blockchain. O protocolo usa um fluxo em duas fases: blockSenaPauseDraw (trava a rodada e registra o compromisso) e blockSenaDrawPay (revela, sorteia e paga).

Objetivo

Garantir um processo auditável, reproduzível e com regras de payout transparentes para cada rodada.

Auditabilidade

Qualquer pessoa pode verificar inputs (commit, salt, bloco alvo), cálculo do seed, números sorteados e eventos de liquidação.

Commit-Reveal (2 Fases)

Fase 1: Pausa + Commit

A função blockSenaPauseDraw(usdValuePoolSnapshot, commitHash) bloqueia a rodada para novas compras, define o bloco alvo futuro e armazena apenas o hash do salt secreto.

Fase 2: Reveal + Draw + Pay

A função blockSenaDrawPay(salt) valida que keccak256(salt) == commitHash, usa o blockhash do bloco alvo, gera os 6 números únicos e executa os payouts.

Janela técnica do blockhash

O blockhash só pode ser lido por ~256 blocos. Se expirar, a rodada precisa de reset operacional. Esse reset é um fail-safe raro, com timeout on-chain, e todo uso fica público on-chain.

Compromisso do Salt

commitHash = keccak256(abi.encodePacked(salt))
O salt não é revelado na fase 1. Apenas o hash é público. Na fase 2, o salt revelado precisa bater exatamente com o commit.

Bloco Alvo

targetBlock = block.number + 5
O sorteio usa o hash de um bloco futuro. No momento do commit, esse blockhash ainda não existe, então ninguém conhece esse input antecipadamente.

Fluxo do Sorteio (21h UTC -> +5 blocos -> DrawPay)

O fluxo operacional do Block-Sena separa claramente o encerramento das apostas da geração dos 6 números. Essa separação elimina o risco de comprar bilhetes depois de conhecer o resultado ou tentar ajustar bilhetes para "bater" com os números sorteados.

1) 21:00 UTC (janela operacional) -> blockSenaPauseDraw

A função blockSenaPauseDraw(usdValuePoolSnapshot, commitHash) trava a rodada, bloqueia novas compras, registra o commitHash, define targetBlock = block.number + 5 e salva o snapshot do pool em USD.

2) Apostas congeladas antes da aleatoriedade existir

Depois do PauseDraw, nenhum novo bilhete entra naquela rodada. Nesse momento, o blockhash(targetBlock) ainda não existe, então os 6 números finais ainda não podem ser previstos nem calculados.

3) +5 blocos (após o bloco alvo) -> blockSenaDrawPay

A função blockSenaDrawPay(salt) valida o salt contra o commit, lê o blockhash do bloco alvo, gera os 6 números únicos e executa os payouts por token. Se tentar chamar antes do bloco alvo, a transação reverte on-chain.

4) Auditoria pública a qualquer momento

Qualquer pessoa pode verificar no explorer: tx de blockSenaPauseDraw, targetBlock, tx de blockSenaDrawPay, salt revelado, números sorteados e eventos de liquidação/pagamento.

O que esse desenho impede na prática

Não é possível comprar um bilhete depois de ver os 6 números da rodada, porque a rodada já foi travada em blockSenaPauseDraw antes do sorteio ser executado.

Não é possível conhecer os 6 números no momento da pausa, porque a fórmula depende de um bloco futuro (targetBlock) que ainda não existe naquele momento.

Não é possível executar o sorteio antes da hora técnica, porque blockSenaDrawPay exige on-chain que o bloco atual já tenha passado do targetBlock.

Sobre ResetDraw (cenário raro)

O blockSenaResetDraw existe apenas para evitar que uma rodada fique travada para sempre se ocorrer uma falha longa de infraestrutura (ex.: RPCs indisponíveis, manutenção de serviços, outage de rede/infra ou problemas operacionais prolongados) e o DrawPay não puder ser executado antes da expiração do blockhash(targetBlock).

No fluxo normal, o esperado é executar o DrawPay poucos blocos após o PauseDraw (alvo em +5 blocos). O reset é uma proteção de liveness, não parte do fluxo normal de sorteio.

Fórmula Matemática do Sorteio (6 números únicos)

O seed do sorteio é derivado da combinação de um bloco futuro, do salt comprometido e de dados da rodada. A partir dele, o contrato gera números de 1 a 30, descartando duplicados até completar 6 dezenas únicas.

Em termos de segurança operacional: como os bilhetes já foram congelados na fase de PauseDraw antes desse bloco futuro existir, o sorteio não pode ser "montado" para casar com um bilhete inserido depois, e nenhum participante consegue apostar na mesma rodada após conhecer o resultado.

Seed do Sorteio

seed = keccak256(abi.encodePacked(blockhash(targetBlock), salt, block.prevrandao, roundId))
Inputs reais usados no contrato: blockhash(targetBlock), salt, block.prevrandao e currentRoundId.

Geração de Cada Número

num = (keccak256(seed, nonce) mod 30) + 1
Se o número já tiver sido sorteado nesta rodada, ele é descartado e o nonce é incrementado. O processo se repete até obter 6 números únicos.

Esse procedimento impede números repetidos no resultado final e mantém o sorteio dentro do intervalo válido 1..30.

Importante (transparência) O resultado de uma rodada finalizada é determinístico e reproduzível com os dados on-chain. Qualquer tentativa de intervenção operacional (ex.: reinício da rodada) deixa rastro público e auditável na blockchain.

Aposta Múltipla (6 a 10 números) e Quotas Combinatórias

No Block-Sena, um bilhete pode conter de 6 até 10 números. Esse bilhete representa várias combinações equivalentes de 6 números. O cálculo de prêmios é feito por quotas vencedoras (combinações), não apenas por “pessoas”.

Total de Combinações de um Bilhete com k números

combos(k) = C(k, 6)
Exemplo: se k = 10, então C(10,6) = 210 combinações equivalentes.

Quotas vencedoras por faixa (4/5/6)

Considere um bilhete com k números e x acertos dentro das 6 dezenas sorteadas.

q6 = C(x, 6)
q5 = C(x, 5) * C(k - x, 1)
q4 = C(x, 4) * C(k - x, 2)
O contrato soma essas quotas por carteira e paga de forma agregada por token (mais eficiente em gas).

Exemplo (bilhete com 10 números e os 6 sorteados dentro dos 10)

q6 = 1, q5 = 24, q4 = 90. O mesmo bilhete pode ganhar simultaneamente nas 3 faixas.

Payout Multi-Token (Liquidação por Token)

Cada token do pool (ex.: nativo, USDC, USDT) é liquidado independentemente. O contrato calcula vencedores e prêmios por rodada e por token, mantendo um histórico exato em RoundTokenStats.

Divisão por Faixa

Faixa 6 acertos = 80% do pool do token
Faixa 5 acertos = 15% do pool do token
Faixa 4 acertos = 5% do pool do token

Prêmio por quota vencedora

prizePerWinnerX = floor(prizeBucketX / quotasX)
A divisão usa aritmética inteira on-chain. Sobras de arredondamento (dust) permanecem como carry-over para a próxima rodada.

Se não houver vencedores

O valor daquela faixa não é perdido: ele compõe o acumulado (carry-over) do token para a próxima rodada.

Se houver múltiplos vencedores

O prêmio é dividido por quotas vencedoras. Uma mesma carteira pode receber várias quotas agregadas em um único pagamento por token.

Como Auditar uma Rodada (Passo a Passo)

  1. Localize a tx de blockSenaPauseDraw e anote o commitHash e o targetBlock.
  2. Após o alvo, localize a tx de blockSenaDrawPay e leia o salt enviado.
  3. Recalcule keccak256(salt) e confirme que bate com o commitHash.
  4. Leia o blockhash(targetBlock) no explorer e o prevrandao do bloco da tx de draw.
  5. Reproduza o seed e a rotina de geração dos 6 números (descartando duplicados) e compare com o evento DrawExecuted.
  6. Se houve falha operacional rara, confira o evento DrawReset (roundId, targetBlock e bloco do reset) para validar o timeout e a rastreabilidade do reset.
  7. Verifique os eventos RoundTokenSettled para pools, pagamentos por faixa e carry-over por token.
  8. Confira transfers nativos/ERC20 para validar os pagamentos aos vencedores.

Garantias, Transparência e Limites Operacionais

O Block-Sena foi desenhado para que o resultado de uma rodada seja auditável, reproduzível e transparente. O commit-reveal combinado com bloco futuro reduz significativamente a possibilidade de manipulação manual do resultado.

Em termos técnicos, o modelo torna o resultado imprevisível no momento do lock, porque a rodada é encerrada antes de existir o blockhash(targetBlock) usado no seed. Assim, a fórmula permanece pública e auditável sem permitir apostas posteriores baseadas no resultado.

O que é garantido on-chain

Regras de sorteio, cálculo de quotas, payout por faixa (80/15/5), histórico por token, eventos e rastreabilidade de cada etapa.

Operação emergencial

Existe função de reset de draw para cenários técnicos raros (ex.: expiração de blockhash por falha prolongada de infra/rede). Ela só pode ser executada com a rodada travada e após timeout on-chain. Todo uso é público, fica registrado on-chain e pode ser auditado no explorer.

O reset não move fundos, não apaga bilhetes e não altera o pool. Ele apenas destrava o ciclo da rodada para permitir uma nova tentativa de sorteio da mesma rodada em caso de falha rara.

Os valores do prêmio permanecem no contrato até o sorteio e os pagamentos automáticos aos vencedores. Se não houver vencedores em uma faixa, o valor permanece contabilizado como acumulado (carry-over) para a próxima rodada. O owner não tem função para retirar fundos contabilizados do pool de prêmios.

A função rescueAll existe apenas para resgatar excedentes não contabilizados (ex.: envio direto por engano), calculando saldo real - pool contabilizado. Ela não alcança os fundos do prêmio já contabilizados.

Em outras palavras: o Block-Sena não pede confiança cega. Ele entrega um fluxo que pode ser acompanhado, reproduzido matematicamente e conferido on-chain por qualquer pessoa, a qualquer momento.