Skip to main content

Launch and rewardsLottery manager

Lottery manager

The shared service that rolls a Chainlink VRF lottery on every qualifying ShareOFT buy and pays winners from the creator's gauge pot.

LotteryManager4626 turns each qualifying ShareOFT () buy, and each free AMOE entry, into an instant lottery roll. It resolves each creator's stack from the registry, draws randomness from Chainlink VRF 2.5, and pays winners in ShareOFT from the triggering vault's gauge pot.

At a glance​

RoleInstant lottery on hub-chain ShareOFT buys plus AMOE (no purchase required) entries
DeployedShared, one per chain
Key relationshipsResolves stacks via Registry4626; triggered by ShareOFT buys; pays from the gauge controller jackpotReserve; reads CreatorOracle for share pricing, not ticket size

How it works​

  1. Fund the pot. Every qualifying buy pays the 6.9% fee to the gauge; 69% of that fee stays in the gauge's jackpotReserve as ShareOFT.
  2. Enter. The buy calls processSwapLottery in the same transaction. AMOE entries arrive through processAmoeEntry from the authorized relayer (the ZK router's submitAmoeEntryZK path).
  3. Price the ticket. The stake is the USD the buyer actually spent, before fees. The win chance follows the formula below.
  4. Roll. Each entry requests Chainlink VRF and is an independent roll.
  5. Pay or miss. On a win, the manager calls the gauge's payJackpot and the winner receives 69% of the triggering vault's pot in ShareOFT. On a miss, nothing is paid.

Win chance​

Before boosts, winChancePPM = swapValueUSD / 250_000, capped at baseCeilingPPM (40,000 PPM). Put simply, $1 traded is about a 0.0004% instant win chance.

Trade sizeWin chance
$10.0004%
$1000.04%
$1,0000.4%
$10,0004% (pre-boost ceiling)

Solana remote entries scale that base chance by 400 / 690 (Raydium's published 4% tier vs Base's 6.9% buy fee) before boosts. Other EVM remotes use the Base formula, since they pay the same 6.9% buy fee. Only Solana odds are discounted; stake USD and jackpot EV stay on the true venue amount.

Stake is before fees​

amountUSD is the cash the buyer spent: the gross quote times this chain's neutral price feed. Do not subtract the pool fee.

PathStakePayload
Base hubLotteryVenueQuoteBuffer quote × feedprocessSwapLottery (same tx)
Solanaquote_lamports × SOL/USDV3 word 6 (nonzero)
Other EVM remotesSame-tx buffer quote × that chain's USDC ($1) or WETH × ETH/USDV4 word 8 (venueAmountUsd1e6); word 6 stays coverage
AMOEpointsBurnedAsUSD (no pool fee)ZK / relayer

Un-upgraded EVM spokes still send V3 and use share × CreatorOracle. If fee tiers change, update only the 400 / 690 constants (SOLANA_TRADE_FEE_BPS, BASE_TRADE_FEE_BPS); do not haircut USD.

A Solana message creates a ticket only when V3 word 6 is a positive venue-USD amount. A zero word returns after RemoteLotteryEntryReceived and emits no LotteryEntryCreated. The submit worker leaves that inbox row queued. With SOLANA_LOTTERY_VENUE_USD_ENABLED off (the default) the reason is solana_lottery_venue_usd_disabled. With the flag on, a zero or unresolvable gross quote uses solana_lottery_venue_usd_missing. The flag default stays off. USD comes from the inbox quote_lamports (gross SOL/WSOL); the hook ring does not carry it.

Prize payout​

By default (singleVaultJackpotOnly = true), a winner gets rewardPercentage (default 69%, 6900 bps) of the triggering vault's gauge jackpotReserve, paid in ShareOFT through payJackpot. Multi-vault scanning happens only if singleVaultJackpotOnly is disabled. The payout is always ShareOFT, never vault claims (). See jackpot payout.

Each buy has its own request and stored odds. Pending requests do not earmark shares. A winning callback sizes its prize against the remaining reserve at settlement, subject to the existing fair-EV cap. For example, two uncapped wins against an initial 1,000-share reserve pay 690 shares and then 213.9 shares, even if their callbacks arrive in reverse request order.

The repository manager now emits LotteryEntryCreated for a successful paid entry, as well as AMOE. A buy transaction has no randomness result yet. Match the event's namespaced request ID to LotteryResultProcessed; the raw local consumer ID is converted with keccak256(abi.encode("LOCAL", rawRequestId)). A zero return from the entry call means no request was created, including unavailable VRF funding or an ineligible venue quote.

A failed positive jackpot transfer reverts manager settlement, preserving its request and reserve. VRFConsumer4626 retains the fulfilled random word and exposes permissionless retryLocalCallback(rawRequestId). While the manager is paused, callbacks instead store randomness in its FIFO queue; after unpause, permissionless applyDeferredVrf or processDeferredVrfBatch settles that queue. A failed deferred payout keeps the same FIFO head for another attempt.

The existing grace policy is unchanged: vrfResultGracePeriod defaults to four hours from request creation. An active-manager callback arriving after that deadline settles as an explicit stale loss. This also applies to a retry of an earlier failed payout, so operators must recover those callbacks before the deadline. Randomness received during a manager pause follows the deferred queue and has its timestamp refreshed on settlement. These are repository defaults, not a claim about verified live settings.

Cross-chain fee sponsorship​

A hybrid model keeps VRF messaging fees bounded:

  • Caller-funded (default). msg.value must equal the integrator-quoted nativeFee. If the VRF send fails, the fee is refunded and the roll is skipped.
  • Sponsored fallback (opt-in). If msg.value == 0 and sponsorship is enabled, the manager pays from its own balance under policy limits.

Key functions​

FunctionWhat it doesWho can call
processSwapLottery(buyer, tokenIn, amountIn, buyerCurrentShareBalance)Creates an entry for a qualifying buy and dispatches VRF (payable for the VRF fee)Authorized swap contracts
processAmoeEntry(buyer, token, pointsBurnedAsUSD)Creates a no-purchase AMOE entryauthorizedAmoeRelayer only
receiveRandomWords(requestId, randomWords) / receiveRandomWords(randomWords, sequence)VRF callbacks (local and cross-chain) that resolve entriesVRF consumer / integrator
calculateWinChance(swapAmountUSD)Pre-boost win chance in PPMView
setLotteryConfig(minSwap, rewardPercentage, isActive, baseWinChance, maxWinChance, usdMultiplierBps)Lottery parameters; rewardPercentage changes are timelockedOwner
setSponsoredVrfMinSwapAmountUSD(minSwapAmountUSD)Minimum swap for sponsored VRFOwner
setSponsorshipRateLimits(vrfMaxPerBuyerPerEpoch, vrfMaxPerOriginPerEpoch, callbackMaxPerBuyerPerEpoch, callbackMaxPerOriginPerEpoch)Per-epoch sponsorship capsOwner
setVrfSponsorshipPolicy(enabled, maxFeePerMessage, budgetPerEpoch, epochDuration)VRF sponsorship on/off and budgetOwner
setCallbackSponsorshipPolicy(enabled, maxFeePerMessage, budgetPerEpoch, epochDuration)Winner-callback sponsorship on/off and budgetOwner

Numbers​

ParameterValue
Base win chancemin(swapUSD / 250_000, 40_000) PPM (4% max at a $10k swap)
Solana odds scalar400 / 690
Win payout69% of the triggering pot (rewardPercentage 6900 bps of pot)
Personal boost (once armed)Up to 2.5×
SponsorshipOff by default for both VRF and callbacks
Sponsored VRF minimum swap$10 (sponsoredVrfMinSwapAmountUSD)
Sponsorship rate limits per epochVRF: 2 per buyer, 10 per origin · Callback: 1 per buyer, 10 per origin

The 69% win payout is a share of the pot. It is a different number from the 69% slice of the buy fee that funds the pot.

Gotchas​

  • Boost is not live. As of 2026-09-05, v1 is live with boostManager and ve4626GaugeVoting at address(0). That is expected on the live canary: the personal multiplier is 1.0× and the gauge add is 0.
  • v2 is documented, not live. Personal boost stays at 1.0× until it is armed. v2 is classic Curve on swapEff = min(swapUSD, 10_000) with D = 100_000, coverage from locked 4626 USD (not decaying ve-units), and a unified 24h UTC consume at 1× veUSD across all chains. Full formula: Lottery personal boost v2.
  • Share-coverage code in v1 is not v2. Arming the live Share-coverage _applyBoost path (l = min(creatorShareUSD, swapUSD), L = creator Share supply) is not v2. The gauge lottery pot stays a separate additive.
  • Boost order. Personal Curve boost, optional gauge probability, and maxWinChance apply after the base formula, once armed. Kernel bounds: ve4626, 2.5× proof, Lean targets.
  • CreatorOracle does not size tickets. Stake comes from the same-transaction venue quote. A missing quote fails closed; there is no CreatorOracle fallback for stake.
  • Legacy AMOE path is off. LotteryAmoeRouter.submitAmoeEntry (ECDSA) always reverts; submitAmoeEntryZK is the live path.
  • Deploy order. USD pricing and oracle guards live in LotteryManager4626PricingLib (an external library call) so the manager stays under EIP-170. Deploy the library first (DeployLotteryManagerCreate2*.s.sol), then the manager. Foundry links the library at CREATE2 (EIP-2470, create2_library_salt default 0). As of 2026-09-05, that pin is 0x1d74A8e2…7C6D for the library initcode. Bytecode manifests must fully link placeholders via script/lib/extract_linked_bytecode.py.

Source​