EventlyMarkets.sol
Status: Pending deployment — pre-launch audit complete Dependencies: OpenZeppelin ERC-1155, ERC-20 SafeERC20, PRBMath SD59x18, Redstone EVM Connector
Overview
EventlyMarkets is the production prediction markets contract for evently. It combines:
ERC-1155 tradable positions — each trade mints/burns fungible shares per outcome
LMSR AMM — Logarithmic Market Scoring Rule (b=200 USDm), permanent liquidity provider
CLOB (Central Limit Order Book) — bids and asks with price-time priority, matched automatically on
placeOrderCreator economy — 1% creator fee on all volume, claimable after finalization
Dual whitelist — separate whitelists for market creators (always enforced) and traders (beta-only)
Dispute system — any market participant can dispute; dedicated
disputeResolverrole settles; monthly VRF lottery for successful disputersOracle markets — price-feed-based binary markets resolved automatically via Redstone Pull-model oracle
Lucky Trader raffle — weekly VRF raffle over active traders; winner-takes-all bonus drawn by admin
Key Parameters
Creator collateral
50 USDm
Import fee (Polymarket markets)
10 USDm (one-time, goes to treasury)
Dispute collateral
50 USDm (waived for admin)
Dispute window
24 hours after resolution
Finalization buffer
+1 hour after dispute window (sequencer guard)
Burn delay (losing shares)
24 hours after finalization
Total fee
2.5% fixed (1% creator + 1% treasury + 0.5% resolver pool)
Max options per market
4
Min options per market
2
Min betting window
1 hour (prevents flash markets)
Min trade
0.001 shares (1e15)
Min order value
1 USDm (anti-dust)
LMSR liquidity parameter b
200 USDm
Max orders per book
200 per (market, option, side)
Max orders per user per market
10 (all options + sides combined)
Max pause duration
72 hours (auto-expiry)
Treasury withdrawal delay
24 hours timelock
Resolver pool change delay
24 hours timelock
1 winning share pays out
1 USDm exactly
Dispute lottery share
30% of treasury gains from successful disputes
Dispute lottery cycle
30 days
Dispute lottery style
Winner-takes-all (VRF selected)
Max weekly lucky traders
500 per week
Lucky trader week duration
7 days
LMSR Pricing Mechanism
LMSR (Logarithmic Market Scoring Rule) is an automated market maker where price follows a mathematically sound cost function:
q[i]= shares outstanding for option ib= liquidity parameter (200 USDm) — controls price sensitivityCost to buy shares of option i = C(q after) - C(q before)
Implied probability of option i:
At initialization all quantities are 0, so each option starts at 1/n probability. Prices always sum to exactly 1.
Market subsidy: To bootstrap liquidity, the creator locks b × ln(n) USDm at market creation. This subsidy guarantees solvency: poolBalance + subsidyDeposited >= total winning shares at all times.
Architecture
Market Lifecycle
Created — creator locks 50 USDm collateral + LMSR subsidy.
winningOptioninitialized totype(uint256).max(sentinel: not yet resolved).Active — users buy/sell shares via LMSR AMM and CLOB.
BettingClosed — after betting deadline, no new trades.
Resolved — creator declares winning option with a mandatory evidence hash. 24-hour dispute window opens.
Disputed — any market participant (trader or creator) can dispute by posting 50 USDm collateral. Admin disputes are free (security function).
disputeResolver(not admin) adjudicates with evidence hash.Finalized — dispute window closes without challenge, or
disputeResolversettles. Winners redeem at 1 USDm/share.Cancelled — only allowed before any trading activity, or after
resolutionDeadlinetimeout. If timeout: creator's accrued fees are confiscated to treasury.Slashed — admin can slash a market in
Active,BettingClosed,Resolved, orDisputedstatus. Cannot slashFinalized,Cancelled, or already-Slashedmarkets. Creator collateral goes to treasury; shareholders get pro-rata refund.
Mandatory resolution: cancelMarket is blocked if totalVolume > 0 or there are active resting orders, unless resolutionDeadline has passed. A market with any activity must be resolved (or time out) — it cannot be quietly cancelled.
Access Control Roles
admin
Multisig
Whitelist management, pause, ban, slash markets, admin markets, treasury (timelocked), admin cancel orders, transfer roles
disputeResolver
Dedicated address
settleDispute() only — cannot resolve markets or access treasury
Market creator
marketCreatorWhitelisted[addr]
Create markets, resolve their own markets, cancel their own markets
Whitelisted trader
whitelisted[addr]
Trade, dispute participated markets
The disputeResolver and admin are separate roles. Admin cannot unilaterally settle disputes.
Whitelist System
Two independent whitelists:
Trader whitelist (
whitelisted) — controls who can trade. Enforced during beta; disabled (setWhitelistEnabled(false)) at public launch.Creator whitelist (
marketCreatorWhitelisted) — controls who can create markets. Always enforced.
Admin is implicitly a market creator. Admin also bypasses the trader whitelist — the onlyWhitelisted modifier always passes for admin, regardless of whitelistEnabled.
Fee Structure
Market creator
1% (creatorFeeBps = 100)
Accrues in creatorAccruedFees, claimable after finalization
Treasury
1% (treasuryFeeBps = 100)
Accumulates in treasuryBalance
Resolver pool
0.5% (resolverFeeBps = 50)
Redirected post-TGE to staking contract (24h timelock on changes)
Total
2.5% (fixed)
Not configurable
Admin markets: creator cut goes to treasury.
Dispute lottery allocation: When a dispute is settled against the creator, 30% of the net treasury gain from that dispute (slashed collateral portion + slashed creator fees) is routed to the current month's dispute lottery pool instead of treasury. Distributed equally to all that month's successful disputers via distributeMonthlyDisputeRewards().
Dispute System
Eligibility
Any address that has placed an order or sold to the AMM on the market (hasParticipated[marketId][user] == true) can dispute. Admin can always dispute (free of charge, security function).
Imported Polymarket markets can be disputed under the same rules.
Collateral
Regular disputer: posts 50 USDm. Returned + 25 USDm reward if right; forfeited to treasury if wrong.
Admin disputer: no collateral posted. If right: nothing transferred. If wrong: 25 USDm reward added to treasury from creator collateral.
Settlement
disputeResolver calls settleDispute(marketId, creatorWasRight, finalOption, evidenceHash). Evidence hash is mandatory and emitted as ResolutionEvidence event.
If creator was right (dispute rejected):
Disputer's 50 USDm collateral → treasury (if they paid it)
If creator was wrong (dispute upheld):
Creator loses 50 USDm collateral:
Regular disputer: disputer receives back their 50 USDm + 25 USDm reward (75 USDm total). 25 USDm of creator collateral → treasury (after lottery deduction)
Admin disputer: full 50 USDm creator collateral → treasury (after lottery deduction)
All creator accrued fees slashed to treasury (after lottery deduction)
30% of net treasury gain → current month's lottery pool (
monthlyDisputePool[month])Market finalized with
_finalOption
Monthly Dispute Lottery
Successful disputers accumulate entries in _monthlyDisputers[month] (one entry per won dispute). After month M ends, admin calls distributeMonthlyDisputeRewards(M, vrfProof, vrfMessage):
Winner-takes-all — one disputer is selected via Drand BLS12-381 VRF (MegaETH native precompile).
winnerIndex = uint256(keccak256(vrfProof)) % countThe full
monthlyDisputePool[M]is transferred to the single winnerVRF proof is verified on-chain before the winner is selected — invalid proof reverts
Cannot be called again for the same month (idempotent guard)
View: getMonthlyDisputers(month) — returns disputer list for any past month.
Oracle Markets
Oracle markets are binary prediction markets resolved automatically from a Redstone price feed, without requiring a human resolver.
Creating an Oracle Market
Constraints:
Must have exactly 2 options (binary market)
priceFeedIdmust be non-zero (bytes32("ETH"),bytes32("BTC"),bytes32("SOL"),bytes32("MEGA"), etc.)strikePricemust be non-zero
Supported Price Feeds
Feeds are sourced from the redstone-primary-prod data service (5-signer threshold). The priceFeedId is a right-padded bytes32 encoding of the ticker string:
ETH / USD
bytes32("ETH")
BTC / USD
bytes32("BTC")
SOL / USD
bytes32("SOL")
MEGA / USD
bytes32("MEGA")
Resolving an Oracle Market
Permissionless — anyone can call it (typically a keeper script)
Reads the Redstone price injected in calldata by the keeper
conditionMet = strikeAbove ? price >= strike : price <= strikewinningOption = conditionMet ? 0 : 1Requires: betting deadline passed, resolution deadline not expired, evidence hash provided
Verifies Redstone data freshness and signature count (≥3 signers from
redstone-primary-prod)
View: getOracleConfig(marketId) — returns (priceFeedId, strikePrice, strikeAbove).
Keeper Script
scripts/resolve-oracle.js scans all markets, identifies eligible oracle markets, wraps the call with WrapperBuilder (injects Redstone price calldata), and calls resolveMarketWithOracle. Configure with:
Lucky Trader Weekly Raffle
Every week, one active trader wins a USDm bonus drawn by VRF.
Eligibility
Any address that calls placeOrder or sellToAMM is automatically entered into the current week's raffle — at most once per address per week (hasEnteredWeek[week][addr]). The raffle tracks up to 500 addresses per week (MAX_WEEKLY_TRADERS).
Drawing the Winner
Admin only; callable once per week (
weeklyLuckyDrawn[week])VRF verified on-chain via the Drand BLS12-381 precompile at
drandVerifierwinnerIndex = uint256(keccak256(vrfProof)) % traderCountbonusAmountis debited fromtreasuryBalance— reverts if treasury insufficientEmits
LuckyTraderWinner(week, winner, bonusAmount)
Views:
getWeeklyTraders(week)— returns all entered addresses for that weekgetCurrentWeek()— returns the active week number (block.timestamp / WEEK_DURATION)
Admin Setup
setDrandVerifier(address) — set the Drand precompile address (admin only). The precompile address is network-specific; confirm via MegaETH documentation before deployment.
Emergency Controls
Pause
pause() — admin only. Blocks all trading and market creation. Auto-expires after 72 hours (MAX_PAUSE_DURATION). Anyone can call unpause() after 72h have elapsed; admin can unpause at any time.
Ban
banAddress(addr) / unbanAddress(addr) — admin only. Banned addresses cannot interact with any market function.
Admin Cancel Orders
adminCancelOrders(orderIds[]) — admin only. Force-cancels resting orders and returns escrowed funds to order owners. Used to remediate griefing or banned-address orders.
Timelocked Admin Operations
Treasury withdrawal
24 hours
requestTreasuryWithdrawal(to, amount)
executeTreasuryWithdrawal()
cancelTreasuryWithdrawal()
Resolver pool address change
24 hours
requestResolverPoolChange(addr)
executeResolverPoolChange()
cancelResolverPoolChange()
Security Properties Implemented
F-02
claimCancelRefund — pre-burn snapshot prevents sequential claim insolvency
A-03
MAX_ORDERS_PER_BOOK = 200 per (market, option, side) — prevents O(n) DoS
F-DS-M02
_cleanBook() called on cancelOrder — dead CLOB entries removed immediately
R2-2
claimCreatorFees requires Finalized status — blocked while Disputed
L-01
nonReentrant on all fund-moving external functions; custom inline mutex (not OZ)
GROK-M01
subsidyDeposited included in cancel refund pool — recoverable by shareholders
GPT-R3-3
slashMarket handles Disputed status — no funds stranded
GROK-L02
createdAt != 0 guard on all view functions
BIZ-06
MIN_BETTING_WINDOW = 1 hour — prevents flash markets
ATCK-06
MIN_ORDER_VALUE = 1 USDm per order — prevents dust griefing
ATCK-07
FINALIZE_BUFFER = 1 hour — sequencer timestamp manipulation guard
AC-03
Treasury withdrawal 24h timelock
INV-06
winningOption sentinel = type(uint256).max — prevents accidental option-0 resolution
INV-08
_cleanBook called before length check in _restOrder
MATH-10
Last claimer receives full effectivePool — dust prevention
SEC-PAUSE
Emergency pause with 72h auto-expiry; force-unpause by anyone after expiry
SEC-BAN
Address ban + adminCancelOrders for targeted remediation
SEC-SLIPPAGE
minUsdmOut mandatory in sellToAMM — sandwich attack prevention
SEC-ORDERCAP
MAX_ORDERS_PER_USER = 10 per market per address
SEC-TIMELOCK
Resolver pool address change 24h timelock
SEC-RESOLVER
Separate disputeResolver role — admin cannot settle disputes unilaterally
SEC-EVIDENCE
Mandatory evidence hash on resolveMarket and settleDispute
SEC-DISPUTE
Disputes open to all participants; imported markets disputable; admin free
SEC-MANDATORY
cancelMarket blocked on active markets — mandatory resolution
SEC-CREATORS
Separate marketCreatorWhitelisted mapping
SEC-LOTTERY
30% of treasury gains from successful disputes → monthly lottery pool
Q7-CONDID
_validateConditionId: 66-char, 0x-prefix, lowercase hex only — prevents case-aliased duplicates
Functions
Trading
placeOrder(marketId, optionIndex, side, quantity, pricePerShare, minFill)— BUY or SELL limit order; CLOB matched first, remainder to AMM for BUYsellToAMM(marketId, optionIndex, shares, minUsdmOut)— instant sell to LMSR AMM;minUsdmOutrequiredcancelOrder(orderId)— cancel resting order; escrowed USDm or shares returned immediately
Pricing (view)
getPrice(marketId, optionIndex)— LMSR implied probability (0 to 1e18)getImpliedPrices(marketId)— array of all option probabilitiesquoteBuy(marketId, optionIndex, usdmNet)— shares out for a given net USDm (binary search)quoteSell(marketId, optionIndex, shares)— gross USDm out for a given share quantitygetQuantities(marketId)— outstanding shares per optiongetSubsidy(marketId)— subsidy deposited and b parametergetPoolBalance(marketId)— pool balance + subsidy depositedgetMarketInfo(marketId)— full market structgetCreatorFees(marketId)— accrued creator fees + claimed flaggetBidBook(marketId, optionIndex)— resting bid order IDsgetAskBook(marketId, optionIndex)— resting ask order IDsgetOrderInfo(orderId)— full order structisDisputeWindowOpen(marketId)— true if 24h dispute window is still openisBurnReady(marketId)— true if losing shares can be burned (24h after finalization)getMonthlyDisputers(month)— disputer addresses for a given 30-day lottery monthgetOracleConfig(marketId)— returns(priceFeedId, strikePrice, strikeAbove)for oracle marketsgetWeeklyTraders(week)— addresses entered in the weekly lucky trader rafflegetCurrentWeek()— active week number (block.timestamp / WEEK_DURATION)
Market Management
createMarket(...)—onlyMarketCreator+onlyWhitelisted; locks 50 USDm collateral + LMSR subsidycreateAdminMarket(...)—onlyAdmin; pays subsidy only (no collateral)createImportedMarket(...)—onlyMarketCreator; pays 10 USDm import fee + subsidy; conditionId validatedcreateOracleMarket(question, options, category, criteria, imageURI, bettingDeadline, resolutionDeadline, priceFeedId, strikePrice, strikeAbove)—onlyMarketCreator; exactly 2 options; locks collateral + subsidy; feeds via RedstoneresolveMarket(marketId, winningOption, evidenceHash)— creator resolves after betting deadline; evidence hash required. For imported Polymarket markets: admin only (creator cannot resolve)resolveMarketWithOracle(marketId, evidenceHash)— permissionless; reads Redstone price from calldata; resolves oracle market automaticallycloseBetting(marketId)— permissionless; callable by anyone afterbettingDeadlinedisputeMarket(marketId, proposedOption)— any participant; 50 USDm collateral (waived for admin)settleDispute(marketId, creatorWasRight, finalOption, evidenceHash)—onlyDisputeResolver; evidence hash requiredfinalizeMarket(marketId)— permissionless; callable by anyone after dispute window + buffercancelMarket(marketId)— blocked on active markets (mandatory resolution); allowed after timeout or if no activityslashMarket(marketId, reason)—onlyAdmin; works on Active, BettingClosed, Resolved, Disputed
Claims
redeemWinnings(marketId)— burn winning shares for 1 USDm eachclaimCancelRefund(marketId)— pro-rata refund on cancelled/slashed markets; last claimer gets full remaining poolclaimCreatorFees(marketId)— creator withdraws accrued fees (post-finalization only)burnLosingShares(marketId, holders[])— admin keeper; burns losing shares 24h after finalizationreclaimCancelledOrder(orderId)— claim escrowed funds from a cancelled order (lazy refund pattern)
Admin
distributeMonthlyDisputeRewards(month, vrfProof, vrfMessage)— draw VRF winner-takes-all from monthly dispute lottery pooldrawLuckyTrader(week, vrfProof, vrfMessage, bonusAmount)— draw weekly lucky trader winner via VRF; debits treasurysetDrandVerifier(addr)— set Drand BLS12-381 precompile addressrequestTreasuryWithdrawal(to, amount)/executeTreasuryWithdrawal()/cancelTreasuryWithdrawal()— 24h timelocked withdrawalrequestResolverPoolChange(addr)/executeResolverPoolChange()/cancelResolverPoolChange()— 24h timelocked resolver updatewithdrawResolverPool(amount)— pull accumulated resolver feessetDisputeResolver(addr)— assign thedisputeResolverroletransferAdmin(newAdmin)/acceptAdmin()— two-step admin transferaddToWhitelist(wallet)/batchWhitelist(wallets[])/removeFromWhitelist(wallet)— trader whitelistbatchAddMarketCreators(accounts[])— bulk add to creator whitelistsetWhitelistEnabled(bool)— toggle trader whitelist enforcementaddMarketCreator(addr)/removeMarketCreator(addr)— creator whitelistbanAddress(addr)/unbanAddress(addr)— block address from all functionsadminCancelOrders(orderIds[])— force-cancel resting orders (returns funds to owners)pause()/unpause()— emergency pause (72h auto-expiry; anyone can force-unpause after)upvoteMarket(marketId)— community upvote (whitelisted traders); emitsMarketUpvotedcreateInviteCode(codeHash)/redeemInviteCode(code)— invite system
Token ID Encoding
MAX_OPTIONS = 4, so each market occupies token IDs [marketId*4, marketId*4+3].
CLOB Design
Order types
BUY and SELL
Matching
Automatic on placeOrder
Price priority
Best price first (ascending for asks, descending for bids)
Time priority
FIFO within same price level
Dead entry cleanup
Immediately on cancelOrder (_cleanBook — F-DS-M02); also before length check in _restOrder (INV-08)
Book cap
200 orders per (market, option, side) — griefing cap
Per-user cap
10 active orders per market per address — per-user saturation cap
Min order value
1 USDm — prevents dust order griefing
Last updated
Was this helpful?

