import { concat, keccak256, pad, toHex, type Hex } from "viem"; export const DRAW_ALGORITHM_VERSION = "v1"; export function nextUint(seed: Hex, index: number): bigint { const indexHex = pad(toHex(index), { size: 32 }); const digest = keccak256(concat([seed, indexHex])); return BigInt(digest); } export function buildDrawSeed(blockHash: Hex, algorithmVersion: string): Hex { const normalizedBlockHash = blockHash.toLowerCase() as Hex; return keccak256(`${normalizedBlockHash}:${algorithmVersion}`); } export function selectWinnerIndexes(ticketPoolSize: number, winnerCount: number, seed: Hex): number[] { if (!Number.isInteger(ticketPoolSize) || ticketPoolSize <= 0) { throw new Error("Ticket pool must be a positive integer"); } if (!Number.isInteger(winnerCount) || winnerCount <= 0) { throw new Error("Winner count must be a positive integer"); } if (winnerCount > ticketPoolSize) { throw new Error(`Winner count ${winnerCount} exceeds ticket pool ${ticketPoolSize}`); } const selectedIndexes = new Set(); const indexes: number[] = []; let cursor = 0; while (indexes.length < winnerCount) { const value = nextUint(seed, cursor); const idx = Number(value % BigInt(ticketPoolSize)); cursor += 1; if (selectedIndexes.has(idx)) { continue; } selectedIndexes.add(idx); indexes.push(idx); } return indexes; } export function selectDeterministicWinners( tickets: readonly T[], winnerCount: number, blockHash: Hex, algorithmVersion: string, ) { const seed = buildDrawSeed(blockHash, algorithmVersion); const indexes = selectWinnerIndexes(tickets.length, winnerCount, seed); const winners = indexes.map((idx) => tickets[idx]); return { seed, indexes, winners, }; }