Algorithms
Role of HyperPredictV1Pair
HyperPredictV1Pair is an on-chain pair tied to a single underlying asset and trading interval (intervalSeconds). Each pair is responsible for:
- letting the operator (an automation bot) pull prices from the Pyth oracle and progress the rounds (
epoch) sequentially - accepting bettors' stakes and positions (Bull/Bear) and storing them inside the
ledgeranduserRoundsmaps - calculating the reward base (
rewardBaseCalAmount) and the amount to distribute (rewardAmount) whenever a round closes successfully - protecting funds during invalid rounds or oracle outages and allowing refunds when necessary
The sections below organize the round lifecycle, Pyth integration, safety mechanisms, and reward algorithms in that order.
Round Lifecycle
Each round has three phases, Start → Lock → Close, and every phase is separated by intervalSeconds. The bet window spans startTimestamp through lockTimestamp, prices are observed until closeTimestamp, and executeRound advances the system to the next step.

- Call
genesisStartRoundandgenesisLockRoundonce each to bootstrap the very first round. - After genesis,
executeRoundhandles three actions in a single transaction for round n:- Lock round n-1 using the latest oracle price.
- Close round n-2 and fix its final price.
- Open round n for new bets.
- To allow a safe halt even when consecutive calls are delayed, the factory enforces a global
bufferSeconds. Any phase that fails to finish withinbufferSecondsis rejected.
Pyth Oracle Integration and Fallbacks
_getPriceFromOracle relies on Pyth’s getPriceNoOlderThan, so only prices published within bufferSeconds are accepted. Safety is ensured by:
- storing the publish time (
publishTime) asoracleLatestRoundId, preventing the operator from accidentally using stale data - normalizing Pyth’s
expotoPYTH_PRICE_DECIMALS (=8)and adding overflow checks even for negative values - keeping
oracleCalledasfalsewhenever the price cannot be fetched or updates lag beyondbufferSeconds, which triggers the refund path described later

When price retrieval fails, the round is marked as “missing.” Because it never satisfies the claimable condition, bettors can reclaim their full stake via the refundable path. The operator can safely resume by calling pause/unpause or by replaying the genesis steps.
Bet Intake and State Management
betBullandbetBearare protected bywhenNotPausedandnonReentrant, and thenotContractmodifier restricts access to EOAs only.- For each round, stakes are added to
round.totalAmountand to the side-specific buckets (bullAmount/bearAmount); mixing positions within the same round is prohibited. - A bet must be greater than or equal to
factory.minBetAmount()to block dust spam. ledger[epoch][user]stores the betamountand itsclaimedstatus, both of which can be paginated throughgetUserRounds.- If a round ends with
oracleCalled == false, a user can request a refund onceblock.timestamp > closeTimestamp + bufferSeconds.
Reward Calculation Algorithm
Rewards for round n-2 are finalized when the operator calls executeRound. The contract uses the following equations:
treasuryAmt = total * treasuryFee / 10000
rewardAmount = total - treasuryAmt
rewardBaseCalAmount = closePrice > lockPrice ? bullAmount : bearAmount
treasuryFee,treasuryFeeWithReferral, andreferralFeeare configured at the factory level (seehyperpredict-contract-v1/config/HyperPredictV1Factory.ts). On BSC the values are3%,1%, and1%respectively.- When
closePrice == lockPrice(a draw),rewardAmountbecomes 0 and everyone receives a refund. - The winning player’s base payout is
userAmount / rewardBaseCalAmount * rewardAmount. This equation always usestreasuryFee(3% on BSC) to keep the math deterministic on-chain, even if a bettor later qualifies for referral rebates. - When a round is invalidated,
_calculateRewardsexits early and the pot is refundable.
Referral Allocation
Referral handling is deferred to claim time and runs inside _applyReferralBonus only when the bettor has a registered referrer:
referralFee(1%) defines the pool that funds the referrer. The user’s share isreferrerBonus = total * referralFee / 10000 * userBet / rewardBaseCalAmount, which is subtracted from the winner’s payout and transferred to the referrer.- To offset that subtraction and to make the treasury effectively charge only
treasuryFeeWithReferral(1%) whenever a referral exists, winners also receive a rebate. The rebate pool is(treasuryFee - treasuryFeeWithReferral)(2% on BSC) and the winner’s portion iswinnerBonus = total * (treasuryFee - treasuryFeeWithReferral) / 10000 * userBet / rewardBaseCalAmount. This amount is debited from the accumulated treasury balance when the claim executes. - The final reward paid to the bettor is
baseReward + winnerBonus - referrerBonus. When no referrer is present,_applyReferralBonusexits early and the bettor simply receivesbaseReward, so the treasury keeps the full 3%.
This lazy calculation ensures that bettors with a referral net out to the cheaper fee schedule (1% treasury + 1% referral), while bettors without a referral remain on the 3% flat treasury fee.
Claim-Time Execution
When a user calls claim, _claim evaluates each epoch in order:
- If
oracleCalled == trueand the entry isclaimable, compute thebaseRewardfor the winning side. - Run
_applyReferralBonusto settle the referral split and finalize thewinnerBonusplus any referrer reward. - If
oracleCalled == falsewhilerefundableholds, simply return the original stake. - Sum every
addedReward, send the total in native currency, and if referral bonuses accumulated, send those in a separate transfer while emittingReferralPaid.
Concrete Example
A: Stake 100 USDC on Bull
B: Stake 100 USDC on Bear
C: Referrer (invited A)
treasuryFee = 3%
treasuryFeeWithReferral = 1%
referralFee = 1%
Round result: Bull wins
totalAmount = 200 USDCtreasuryAmount (initial) = 200 * 0.03 = 6 USDCrewardAmount = 200 - 6 = 194 USDCrewardBaseCalAmount = bullAmount = 100 USDC- A’s
baseReward = 100 / 100 * 194 = 194 USDC - Referral pool
referralPool = 200 * 0.01 = 2 USDCsoreferrerBonus = 2 USDC - Treasury rebate pool
treasuryRefundPool = 200 * (0.03 - 0.01) = 4 USDCsowinnerBonus = 4 USDC - Winner receives
194 + 4 - 2 = 196 USDC - Referrer receives
2 USDC - Treasury keeps
6 - 4 = 2 USDC, which matches the 1% schedule for referred bets
If the bettor had no referrer, _applyReferralBonus would skip the rebate path entirely: A would claim just the baseReward of 194 USDC and the treasury would retain the full 3% (6 USDC). B still loses because their position finished out of the money unless the oracle failed, in which case both sides can refund their stakes.
Safety and Fail-Safes
- Role separation:
onlyOperatordrives the round progression, whileonlyAdminhandles treasury actions (claimTreasury,recoverToken). - Pausable + Genesis: Abnormal conditions can be halted with
pause, then restarted cleanly viagenesisStartRound/genesisLockRound, avoiding duplicate or undefined rounds. - ReentrancyGuard & non-contract gate: Every function, including payout paths, is non-reentrant, and EOAs only are allowed, blocking multicall contracts.
- Buffer enforcement:
_safeLockRoundand_safeEndRoundreject operations outsidebufferSeconds, stopping timing games and price manipulation. - Fund protection:
claimable/refundableconditions prevent double claims while guaranteeing principal returns during oracle downtime. - Oracle input validation:
getPriceNoOlderThandiscards stale data and sanitizes negative values or overflow scenarios.
Together these mechanisms ensure HyperPredictV1Pair can run an entirely on-chain prediction market safely, autonomously, and with transparent payout and referral accounting.