Entropy you can authenticate.
Luckotto combines a predetermined future Ethereum block with a mechanically selected drand signature. Every settlement input can be independently retrieved and recomputed.
Three accounting domains
Q is the fixed round bankroll. P is the player pot. outstandingClaims tracks unpaid winners, refunds and resolver bounties, excluding those liabilities from shareholder equity.
equity = address(this).balance − outstandingClaims mint assets = ceil(shares × equity ÷ totalSupply) redeem assets = floor(shares × equity ÷ totalSupply)A global post-fee NAV-per-share high-water mark governs the fixed 10% performance fee. When NAV establishes a new high, the contract mints ordinary LUCK to the immutable fee recipient; no ETH leaves the bankroll. A primary mint below the mark adds a nonrefundable equalization premium to bankroll equity while minting only the requested LUCK. Deposits do not change the HWM.
profit = max(equity − ceil(supply × HWM ÷ PAYOUT_SCALE), 0) fee assets = floor(profit × 10%) fee shares = floor(supply × fee assets ÷ (equity − fee assets))Transfers and ticket purchases perform no fee accounting. The immutable fee recipient can permanently abort only before sales close and before the committed entropy block exists. Abort initially reserves every ticket stake for refund and lets holders redeem residual bankroll equity. More than 730 days after any abort, that same recipient can sweep the complete remaining ETH balance. The deployment stays aborted and its claim records remain, although a sweep can leave them without ETH backing.
Tickets whose targets exceed P request exposure. Exposure is capped at half the bankroll and further constrained so the maximum resolver bounty remains fundable.
Fixed future Ethereum block
At round opening, the contract stores an entropy block offset of 50833 execution blocks. This covers liquidity, sales, four 32-slot epochs, four additional slots following EIP-4399 guidance, and one block needed to reveal the parent mix.
A resolver supplies the exact raw RLP execution header. Luckotto hashes the bytes, authenticates the hash through BLOCKHASH or EIP-2935, confirms block number and post-merge difficulty, then extracts timestamp and PREVRANDAO.
minimum drand time = entropy block timestamp + 3600 target drand round = first quicknet round strictly after minimum timedrand quicknet BLS proof
The quicknet chain hash, public key, scheme identifier and RFC 9380 domain are pinned in source. The contract verifies the 96-byte signature with Ethereum’s EIP-2537 BLS12-381 precompiles.
seed = keccak256( LUCKOTTO_DRAW_SEED_V1, deploymentDomain, roundId, entropyBlockNumber, PREVRANDAO, drandRound, sha256(drandSignature) )Separate domain hashes derive the stake-weighted candidate word and acceptance word. This avoids using one random word for two logically distinct decisions.
Candidate and acceptance
Tickets occupy contiguous intervals in P proportional to stake. Binary search maps the candidate position to one ticket. When that ticket asks for bankroll exposure, acceptance preserves the bankroll-relative pricing encoded by the contract.
candidate = keccak256(LUCKOTTO_CANDIDATE_V1, seed) mod P acceptance = keccak256(LUCKOTTO_ACCEPTANCE_V1, seed) payout = P + accepted exposureResolver runbook
The manual resolver utility independently hash-checks a pasted raw header, compares public drand relays, converts the signature, simulates the exact call, and only then asks a connected wallet to broadcast. Operators can also use the CLI sequence below.
HEADER=$(cast rpc --rpc-url "$ETH_RPC_URL" debug_getRawHeader \ "$(cast to-hex "$ENTROPY_BLOCK_NUMBER")") cast call "$LUCKOTTO_ADDRESS" \ "currentDrandSchedule(bytes)(uint64,uint256,uint256)" "$HEADER" cast send "$LUCKOTTO_ADDRESS" \ "resolveRound(uint256,bytes,bytes)" "$ROUND_ID" "$HEADER" "$DRAND_SIGNATURE"Empty rounds can be advanced after sales close. A populated unresolved round becomes abortable after its resolution deadline, and eventually without a header after EIP-2935 history expires.
Security posture
- No upgrade proxy or restart. The immutable fee recipient has a fairness-bounded abort power and a complete-balance sweep strictly more than 730 days afterward.
- OpenZeppelin reentrancy protection on native-ETH claims and round-ending paths.
- Pull payments and explicit liabilities rather than settlement-time recipient calls.
- Constructor self-test rejects chains without the required EIP-2537 behavior.
- Unit, fuzz, invariant and static-analysis suites are included with source.