All three contracts share the same storage layout via an ERC-7201 namespaced storage slot.
Why Libraries?
Hyperliquid enforces a 13 514-byte runtime bytecode limit. Splitting logic into external libraries keeps each deployed contract within this limit.
Cross-Chain Draw Flow
Retry Mechanism
If the CCIP send on Base fails (e.g. insufficient ETH for fees), the random word is persisted in pendingRandomWords[roundId]. The owner can top up the contract and call retryFulfillViaCCIP(roundId) to resend.
Random Number Generation
The VRF provides a single uint256 random word. The contract derives all drawn numbers deterministically from it:
Because the seed comes from Chainlink VRF, the outcome is tamper-proof and independently verifiable on-chain.
Round Lifecycle
State
Description
OPEN
Players can buy tickets. Pools accumulate.
DRAWING
CCIP message in flight. No purchases allowed.
DRAWN
Randomness applied. Ready for settlement.
SETTLED
Winners counted, payouts calculated, next round opened.
Timing
Parameter
Default
Description
upkeepInterval
86 400 s (1 day)
Minimum time between draws
DRAW_GRACE_PERIOD
300 s (5 min)
After this, anyone can trigger a public draw
EMERGENCY_CANCEL_DELAY
24 h
After this, anyone can cancel a stuck draw
Settlement
Settlement iterates over all tickets in the round, counts matching winners, and calculates per-winner payouts.
Default batch size: 500 tickets.
Rounds with ≤ 500 tickets: single settleRound() call.
Larger rounds: multiple settleRoundBatch() calls until all tickets are processed.
After settlement:
currentRound increments.
Unclaimed pools roll over to the new round.
The new round opens in OPEN state.
Upgrade Pattern (UUPS)
HyperLotteryV1 follows the UUPS (Universal Upgradeable Proxy Standard) pattern with a 72-hour timelock and a 1-hour execution window:
Owner calls proposeUpgrade(newImplementation) — starts the 72-hour countdown.
After 72 hours, upgradeToAndCall() can execute within a 1-hour window.
If the window passes, the proposal expires and must be re-proposed.
During the execution window, ticket purchases are blocked to prevent state corruption.
Automation Server
A Node.js automation server (in server/) polls the contract and triggers:
closeBettingAndDraw() when the upkeep interval elapses.
settleRoundBatch() when a round is in the DRAWN state.
Configuration is read from server/.env. The server can be managed with PM2 via the root ecosystem.config.cjs.