SBET Protocol Overview
The SBET Protocol is an AI-powered sports betting and AI prediction market platform built on Ethereum. It combines decentralized sports betting, betting with AI-assisted market intelligence, LMSR prediction markets, NFT wagering, SBET staking, on-chain governance, embedded market surfaces, and a modular treasury system — all secured by smart contracts with no off-chain custody of user funds.
Design Philosophy
- Non-custodial: Users retain control of their assets until the moment of trade execution. No deposits to a central pool are required for P2P trading.
- Modular: Each betting primitive (P2P, pools, NFTs, prediction markets) is an independent contract. The treasury uses a hub-and-spoke module system.
- Defense-in-depth: Every contract uses OpenZeppelin's
ReentrancyGuard,SafeERC20, andAccessControl. State changes follow the Checks-Effects-Interactions (CEI) pattern throughout. - Gas-efficient: Packed order encoding, batch operations on all endpoints, and
via_ircompilation with 200 optimizer runs. - Formally verified: Critical invariants checked with Halmos (symbolic execution), Echidna (fuzzing), and SMTChecker. See the full audit report.
Architecture
The protocol is organized into seven layers: core trading, betting primitives, AI prediction markets, staking, treasury management, integrator infrastructure, and donations. Each layer is composed of independent contracts connected through well-defined interfaces for sports betting, market-making, AI-assisted workflows, and governance.
Contract Hierarchy
Security Primitives
Every contract in the protocol inherits from a common set of security primitives.
| Primitive | Source | Purpose |
|---|---|---|
ReentrancyGuard |
OpenZeppelin | Prevents re-entrant calls on all state-mutating functions |
SafeERC20 |
OpenZeppelin | Safe token transfers — handles non-standard return values |
AccessControl |
OpenZeppelin | Role-based permission system for admin operations |
Pausable |
OpenZeppelin | Emergency circuit breaker for all user-facing endpoints |
| CEI Pattern | Convention | Checks-Effects-Interactions ordering in every function |
| Timelocks | Custom | 24h delay on match finalization; 2-day delay on treasury migration |
Compiler Configuration
// foundry.toml
[profile.default]
solc_version = "0.8.34"
evm_version = "prague"
via_ir = true
optimizer = true
optimizer_runs = 200
P2P Trading
The core trading engine uses EIP-712 typed-data signatures for gasless order creation. Orders are signed off-chain
and settled on-chain when matched. The system supports both taker-initiated trades (trade()) and
permissionless order matching (matchOrders()).
See the Trading API reference for full function signatures.
EIP-712 Order Struct
Every order is an EIP-712 typed struct with 14 fields. The domain separator uses the name
"Sports BET (SBET) Protocol", version "1.0", a dynamic chainId,
the deployed contract address as verifyingContract, and a deterministic salt
derived from keccak256("53019554...").
// EIP-712 Order type — matches SBETStorage.EIP712_ORDER_SCHEMA_HASH
struct Order {
address maker; // Order creator
address taker; // Designated counterparty (address(0) = anyone)
address token; // ERC-20 settlement token
uint256 matchId; // Sporting event identifier
uint256 amount; // Maximum trade size in token base units
uint256 price; // Price in range [0, MAX_PRICE]
uint256 direction; // 0 = Back (long), 1 = Lay (short)
uint256 expiry; // Unix timestamp — order invalid after this
uint256 timestamp; // Creation time — used by cancelAll()
uint256 orderGroup; // Logical grouping for batch cancellation
uint256 nonce; // Unique per-maker, prevents replay
bool isNFT; // Whether this order involves NFT collateral
address nftAddress; // NFT contract (if isNFT)
uint256 nftId; // NFT token ID (if isNFT)
}
// EIP-712 Domain
{
name: "Sports BET (SBET) Protocol",
version: "1.0",
chainId: <dynamic>,
verifyingContract: <SBET contract address>,
salt: keccak256("53019554eadfcd6f3ee53ec6d0bcd18db643b20cfcf8655aae83229cdeb079bd")
}
Price Model
Prices are expressed as integers in the range [0, MAX_PRICE] where MAX_PRICE = 1,000,000,000 (1 billion).
This provides sub-cent granularity for any settlement token. A price of 500,000,000 represents even odds (50/50).
For two orders to be matchable, their prices must satisfy the spread condition:
leftPrice + rightPrice >= MAX_PRICE. This ensures the combined collateral from both sides
covers the full payout regardless of the outcome.
Order Lifecycle
Nonce System
The protocol provides three levels of order cancellation granularity:
cancelOrderNonce(nonce)— Cancel a single specific order by nonce. See API.cancelUpTo(nonce)— Invalidate all orders with nonces below the given value. See API.cancelAll()— Invalidate all orders signed before the current block timestamp. See API.
Audit note: Finding C-01 identified thatcancelAll()originally did not prevent order execution in thetrade()path. This was remediated by adding acancelTimestampscheck to_unpackOrderSoft(). See audit findings for details.
Match Finalization
Matches are finalized through M-of-N grader signatures via finalizeWithGraders().
The grader configuration (quorum size and addresses) is encoded in the matchId itself.
Finalization initiates a 24-hour timelock before the result can be committed via
executeFinalization(). The timelock checks oracle
staleness before committing, providing a window to detect and respond to incorrect results.
Pool Betting
Pool betting provides a simpler, mutuel-style wagering system. Unlike P2P trading, pools aggregate all stakes and distribute payouts pro-rata to winners. This model works well for multi-outcome events (e.g., tournament winners, exact score predictions). See the Pool Betting API for function details.
Pool Lifecycle
- Create: An admin creates a pool with
createPool(token, numOutcomes), specifying the settlement token and number of possible outcomes (minimum 2). - Join: Users place bets by calling
joinPoolBet(poolId, outcome, amount). Tokens are transferred into the pool contract. - Finalize: After the event concludes, an admin calls
finalizePool(poolId, winningOutcome)to lock in the result. - Claim: Winners call
claimPoolWinnings(poolId)to receive their pro-rata share. Batch claiming across multiple pools is supported viabatchClaimPools().
Payout Formula
Payouts are calculated using a straightforward pro-rata formula. The total pool is distributed among winners in proportion to their stake on the winning outcome.
// Pool payout calculation
payout = (userStakeOnWinningOutcome / totalStakeOnWinningOutcome) * totalPoolStake
Batch Operations
Users with positions across multiple pools can claim all winnings in a single transaction using
batchClaimPools(), reducing gas costs significantly for active bettors.
NFT Betting
The NFT betting module enables users to wager ERC-721 and ERC-1155 tokens on sporting events. NFTs are held in escrow by the NFTVault contract during the bet and transferred to the winner upon match finalization. See the NFT Betting API for full function signatures.
Bet Lifecycle
- Create (Side A): A user calls
createBetWithNFT(matchId, nftAddress, nftId)to stake their NFT. The token is transferred to NFTVault for escrow. - Join (Side B): A counterparty stakes their NFT via
joinBetWithNFT(). - Finalize: After the match result is determined,
finalizeMatchNFTs()transfers both NFTs to the winner. Batch finalization is available viafinalizeMatchNFTsBatch()for matches with many NFT bets.
NFT Vault Escrow
The NFTVault contract provides secure escrow for both ERC-721 and ERC-1155 tokens. It supports batch deposit/withdraw operations and provides view functions for querying vault holdings. An emergency transfer function (admin-only) exists for recovering stuck assets in edge cases.
Blacklist System
Admins can blacklist NFT collections that are fraudulent, compromised, or otherwise unsuitable for betting
via setNFTBlacklist(). Blacklisted collections cannot
be used in new bets. Existing bets with blacklisted NFTs can still be finalized and claimed.
Per-Collection Fee Configuration
The TreasuryNFTFees module allows per-collection fee configuration. Fees can be fixed-amount or percentage-based (with min/max bounds). Users can be granted permanent or temporary fee exemptions. Fee configuration is managed through the treasury system.
Prediction Markets
The prediction market module implements a full LMSR (Logarithmic Market Scoring Rule) automated market maker for multi-outcome event prediction. Markets support creation, share trading, resolution, and dispute handling — all on-chain. See the PredictionMarket API and PredictionAMM API for function details.
LMSR Cost Function
The AMM uses Hanson's Logarithmic Market Scoring Rule to price outcome shares.
The cost function C(q) for a quantity vector q of n outcomes is:
// LMSR cost function
// b = liquidity parameter (higher = less price impact)
// q = vector of outstanding shares per outcome
C(q) = b * ln( sum( e^(q_i / b) ) ) for i = 1..n
// Price of buying Δ shares of outcome k:
cost = C(q_1, ..., q_k + Δ, ..., q_n) - C(q_1, ..., q_k, ..., q_n)
// Implied probability of outcome k:
p_k = e^(q_k / b) / sum( e^(q_i / b) )
The b parameter controls price sensitivity. A larger b means the market can absorb
bigger trades before prices move significantly, but requires more initial liquidity.
b parameters. Lower b values cause prices to move faster with each trade.Market Creation
Markets are created via createMarket(params) with a
MarketParams struct specifying the question, outcome labels, resolution source, timing parameters,
settlement token, fee configuration, and grader setup. The creator provides initial liquidity which is
seeded into the LMSR pool via seedLiquidity().
Share Trading
Users buy outcome shares via buy(marketId, outcomeIndex, shares, maxCost)
and sell via sell(marketId, outcomeIndex, shares, minPayout).
Both functions include slippage protection parameters (maxCost and minPayout)
to prevent front-running losses.
Preview functions getCostToBuy() and getPayoutToSell()
allow UIs to display estimated costs and payouts before submitting a transaction.
Resolution
Markets are resolved via resolveMarket() using M-of-N grader
signatures, similar to match finalization in P2P trading. After resolution, holders of the winning outcome
can redeem their shares for a pro-rata portion of the collateral pool via
redeem(). The liquidity provider reclaims remaining collateral
via removeLiquidity().
Dispute System
Resolved markets can be disputed by posting a bond via disputeResolution().
Disputes are adjudicated by a separate M-of-N dispute council through resolveDispute().
If the dispute succeeds, the bond is returned and the market outcome is corrected. If it fails, the bond
is forfeited. Markets can also be voided entirely by an admin via voidMarket(),
which refunds all participants their original deposits.
PredictionExchange (EIP-712 Limit Orders)
The PredictionExchange contract adds a Central Limit Order Book (CLOB) to prediction markets.
Users sign EIP-712 typed data off-chain and publish orders to a bulletin board. Orders are filled on-chain
by counterparties via fillOrder() or matched peer-to-peer
via matchOrders().
See the PredictionExchange API for function details.
Order Structure
Each exchange order contains:
struct Order {
uint256 salt; // 256-bit random nonce (crypto.getRandomValues)
address maker; // Order creator
address signer; // EIP-712 signer (may differ for smart wallets)
address taker; // Specific counterparty or address(0) for anyone
uint256 tokenId; // Outcome share token ID
uint256 makerAmount; // Amount maker provides
uint256 takerAmount; // Amount taker must provide
uint256 expiration; // Unix timestamp expiry
uint256 nonce; // Invalidation nonce (incrementNonce cancels older orders)
uint256 feeRateBps; // Fee in basis points (max 10000)
uint8 side; // 0 = BUY, 1 = SELL
uint8 signatureType; // 0 = EOA
}
EIP-712 Signing
Orders are signed using the EIP-712 typed structured data standard. The domain separator uses the
exchange contract address and current chain ID. Before signing, the frontend validates that chainId
and verifyingContract match the active network to prevent replay attacks.
The order price is implicitly encoded in the makerAmount / takerAmount ratio.
For BUY orders, the price in cents is takerAmount * 100 / (makerAmount + takerAmount).
For SELL orders, makerAmount * 100 / (makerAmount + takerAmount).
Prices range from 1 to 99 cents.
Nonce & Cancellation
Each order includes a nonce field. Calling incrementNonce() on-chain invalidates
all orders with a nonce less than the new value, providing efficient batch cancellation. Individual orders
can be cancelled via cancelOrder() which marks the specific order hash as filled/cancelled.
Fill Types
The exchange supports three match types for order filling:
- COMPLEMENT (0): Standard counterparty fill — one side buys, the other sells.
- MINT (1): Both parties contribute collateral to mint new outcome shares.
- MERGE (2): Both parties contribute complementary shares to recover collateral.
Partial Fills
Orders support partial fills. getOrderStatus() returns
{ isFilledOrCancelled, remaining } so UIs can display how much of an order is still available.
The remaining amount decreases with each partial fill until fully consumed.
Order Bulletin Board
Signed orders are published to the indexer's bulletin board at
/api/orderbook. The server verifies EIP-712 signatures
on publish. Orders are stored with their hash, price, remaining amount, maker address, and expiry.
The frontend orderbook view displays aggregated bids and asks with click-to-fill functionality.
URL-Shareable Orders
Orders can be encoded into URL-safe base64 strings and shared as links. The recipient's browser decodes the order, verifies the signature, checks on-chain remaining amount, and presents a fill card. Maximum URL length is 4000 characters. The schema uses compact single-letter keys to minimize size.
Market Browser
The dApp provides a full-featured market browser at the /markets route with grid and list view modes,
paginated loading (20 markets per page), and multiple filter/sort options:
- Quick filters: All, Live, Popular, Favorites (persisted via localStorage).
- Category filter: Sidebar categories with counts, derived from on-chain market metadata.
- Sort options: Trending, Latest, Closing Soon, Volume.
- Search: Full-text search across market questions.
Each market card shows the question, outcome prices (via getOutcomePrices), volume, countdown timer,
status badge, and AI edge indicators when the AI layer is configured.
Trading Modes
The market detail page provides three trading modes, selectable via a tab switcher:
-
Simple: Quick buy/sell with outcome selection and size input. Includes slippage protection
via
maxCost/minPayoutparameters. Shows counterparty risk warnings (high/critical only). - Advanced: Depth chart visualization, detailed pricing breakdown, and position summary.
-
Limit: Full limit order panel with four sub-tabs:
- Form — Manual order builder: outcome, side (BUY/SELL), price (1–99¢), size (ETH), expiry. Signs EIP-712 and auto-publishes to orderbook.
- Smart — Natural language input (AI-powered). Type an order in plain English, preview parsed result, then sign.
- Copilot — AI-generated quote ladders for liquidity providers with risk tolerance control.
- Bot — Algorithmic strategy templates (Grid Trading, Kelly Spread, Momentum). Kelly and Momentum are gated by trusted AI confidence and use label-matched per-outcome probabilities, so they support binary and multi-outcome markets.
The limit order panel also displays the live orderbook (bids/asks from the bulletin board), a list of the user’s open orders with cancel functionality, and incoming orders from URL-shared links.
Market Detail Page
The market detail view includes:
- Probability Hero: AI vs. market probability comparison strip with per-outcome breakdown (AI probability, market price, edge).
- Price chart: Historical price data from the indexer, with SSE live updates.
- Market Health Card: Manipulation risk indicator (green/amber/red) from the trust layer.
- Depth Ladder: Aggregated order depth from the bulletin board.
- Kelly Widget: AI-optimal bet sizing (f = (bp−q)/b, capped 25%, half-Kelly mode).
- Route Preview: AMM/orderbook split visualization with savings badge.
- Alert Setup: Price alert subscription card (above/below threshold).
- Position Summary: User’s current shares held and unrealized P&L.
- Status Timeline: Visual lifecycle tracker (Active → ResolutionPending → Resolved).
- Related Markets: Similar market suggestions based on category and embeddings.
- Theme Engine: Fully responsive Light and Dark modes with class-based toggling (
<html class="dark">), persisting via user configuration settings. - Internationalization: 16 languages with RTL support for Arabic and Urdu, plus 14 separately selectable fiat currencies (USD, EUR, AED, BDT, BRL, CNY, INR, JPY, KRW, PHP, PKR, RUB, THB, TRY) with live Coinbase exchange rates.
- Portfolio Risk Analysis: Dedicated
/portfoliopage with net position aggregation, AI-powered correlation matrix, diversification grading (A–F), concentration scoring, and hedging suggestions. - Data Integrity: Strict enforcement of real on-chain and indexer data. All legacy client-side mock states have been completely removed for production readiness.
Embeds & Product Explorer
The frontend exposes two public embed routes: /embed/match/:matchId renders a standalone match card from
URL query parameters, while /embed/market/:marketId renders a live prediction market card backed by the
on-chain market and AMM hooks. Both are designed for lightweight sharing and partner-site embeds.
Explorer views are also split by product. Betting surfaces deep-link to
/console/explorer?product=betting and market surfaces deep-link to
/console/explorer?product=prediction, so transaction history, event badges, and summary stats stay
scoped to the relevant product area.
Market Creation
Markets can be created through three methods in the dApp:
- Manual creation (CreateMarketDialog): Multi-step wizard — (1) enter question with AI quality validation (score, issues list, suggested rewording, pgvector duplicate detection), (2) define outcomes (2–20), (3) set resolution source and timing, (4) review and submit. AI validation requires an acknowledgment checkbox if the quality score is low.
-
Polymarket import (ImportPolymarketDialog): Fetches top 10 trending events from the Polymarket Gamma API,
displays them in a selection grid, and batch-creates selected markets on-chain with sequential
createMarketcalls and progress tracking. - AI generation (GenerateMarketsDialog): AI generates prediction market ideas from trending topics. Category filters and count slider (1–10). Select, review, and batch-create in a single dialog.
Market Status & Lifecycle
Markets follow a defined lifecycle with six statuses, tracked by the canonical enum in
src/constants/marketStatus.ts:
| Status | Value | Description |
|---|---|---|
| Active | 0 | Accepting trades via AMM and limit orders. |
| Paused | 1 | Trading frozen by admin. No buys/sells/fills. |
| ResolutionPending | 2 | Resolution submitted, awaiting dispute window expiry. |
| Resolved | 3 | Final outcome set. Winners can redeem shares. |
| Disputed | 4 | Resolution challenged. Dispute council adjudicates. |
| Voided | 5 | Market cancelled. All participants refunded. |
The derived status enum adds NonExistent (0), Tradeable (1), and
ClosedAwaitingResolution (2) for finer frontend state management.
Local Order Storage
Signed limit orders are persisted in localStorage (key: sbet_limit_orders_v1)
with a maximum of 100 orders and automatic pruning of orders older than 30 days. The store uses
useSyncExternalStore for React integration. Each stored order includes the full order struct,
signature, chain ID, market ID, and outcome label for display purposes.
Treasury System
The treasury uses a hub-and-spoke architecture. The central SBETTreasury contract manages token
allowlists, deposits, withdrawals, daily limits, and delegates specialized functionality to pluggable modules.
See the Treasury API and Treasury Modules API for function details.
RBAC Roles
The treasury uses OpenZeppelin AccessControl with the following roles:
| Role | Permissions | Typical Holder |
|---|---|---|
ADMIN_ROLE |
Deposits, fee config, module management, migration queuing | Protocol multisig |
WITHDRAWER_ROLE |
Standard withdrawals (subject to daily limits) | Operations wallet |
EMERGENCY_ADMIN_ROLE |
Emergency withdrawals (bypass limits), emergency pause | Cold storage multisig |
TOKEN_MANAGER_ROLE |
Add/remove allowed tokens | Protocol admin |
LOCK_MANAGER_ROLE |
Lock/unlock treasury (required for migration) | Protocol multisig |
Daily Limits
Withdrawals are subject to per-token daily limits. Each token can have an individual daily cap, and there is
also a global daily limit across all tokens. These limits provide a safety net against compromised admin keys —
an attacker with WITHDRAWER_ROLE can only drain up to the daily limit before the emergency admin
intervenes.
Treasury Modules
The treasury hub delegates functionality to 9 specialized modules:
- FeeManager: Configures fee recipients with percentage allocations. Fee operations require a timelock. Distributes accumulated fees to all recipients.
- MultiRewardStaking: Treasury staking module for SBET emissions and ETH/ERC-20 fee sharing. Reads balances from
StakedSBET, maintains independent accumulators per reward token, and supports both direct funding and pre-funded activation. - Vesting: Creates token vesting schedules with cliff periods. Supports revocable and irrevocable schedules, batch release, and per-beneficiary queries.
- Budgets: Creates time-bounded budgets with department allocations and spending approvals. Enables controlled, auditable expenditure.
- MultiSig: Governance module for proposing and voting on treasury operations (transfers, signer changes, threshold adjustments). Requires M-of-N signer approval.
- Yield: Deploys treasury funds to yield-generating strategies. Supports target allocations, rebalancing, and harvest operations across multiple strategies.
- NFTManager: Manages NFT deposits and withdrawals through the NFTVault on behalf of the treasury. Supports single and batch operations for ERC-721 and ERC-1155 tokens.
- NFTFees: Per-collection fee configuration for NFT betting. Supports fixed and percentage-based fees with min/max bounds, plus user exemptions.
- IntegratorHub: Manages integrator fee accrual and distribution. See the Integrators section.
Migration System
The treasury supports migration to a new contract via a two-step timelocked process:
queueMigration(newTreasury)— Starts a 2-day timelock. The treasury must be locked (setLock(true)) at this point.executeMigration()— After the timelock elapses, executes the migration. Requires the treasury to have been continuously locked since the migration was queued.
The continuous lock requirement prevents a scenario where an admin unlocks the treasury during the timelock period to drain funds before the migration completes.
Integrator Hub
The IntegratorHub enables third-party applications to earn a share of protocol fees for trades routed through them. It provides self-service registration, configurable fee sharing, periodic sweep payouts, and per-integrator analytics. See the IntegratorHub API for function details.
Self-Registration
Third-party developers register their app by calling registerMyApp(payout, period),
specifying a payout address and a sweep period (Daily, Weekly, or Monthly). A registration fee may be required
(paid via msg.value). Admins can also register integrators on their behalf via
ownerRegister().
Fee Accrual Flow
Fee Split
The default split is 95% to the integrator and 5% to the protocol treasury.
Admins can adjust the split per-integrator by modifying their bps setting.
Fees are consumed (split and paid out) by calling consumeAccrued()
or in batch via batchConsume().
Sweep Periods
Each integrator chooses a sweep period at registration time:
- Daily — Fees can be consumed once per day.
- Weekly — Fees accumulate for 7 days before becoming consumable.
- Monthly — Fees accumulate for 30 days before becoming consumable.
The isDue(router) view function checks whether
an integrator's sweep period has elapsed and fees are ready for consumption.
View Functions
Integrators can query their status and pending fees using:
appOf(router)— Full app info (payout address, period, bps split, status).dueOf(router, token)— Pending amounts due to integrator and treasury.payoutAddressOf(router)— Configured payout address.
Donations
The DonationManager contract provides a transparent, on-chain donation system for charitable organizations. It supports ERC-20 and ETH donations to verified organizations, cause-based distribution, and donation-from-winnings — enabling users to donate a portion of their betting winnings directly to charity.
Donation Lifecycle
- Register: A
DONATION_MANAGER_ROLEholder registers organizations with name, cause, description, image, and wallet address. Limited toMAX_ORGANIZATIONS(500). - Verify: The contract owner verifies organizations before they can receive donations.
- Donate: Users donate ERC-20 tokens via
donate()or ETH viadonateETH()to a verified organization. - Cause-based:
donateByCause()distributes a donation evenly among all verified organizations for a given cause. The last organization receives the remainder to eliminate rounding dust. - From winnings:
donateFromWinnings()allows the protocol to donate a portion of a user's winnings on their behalf.
SBET Staking
The live staking and governance stack is built from four contracts:
StakedSBET, MultiRewardStaking, SBETGovernor, and
SBETTimelock. Users deposit SBET 1:1 into StakedSBET and receive
stkSBET, a transferable ERC-20 voting token with OpenZeppelin ERC20Votes
checkpoints. Rewards are distributed by MultiRewardStaking, which reads
stkSBET balances and pays multiple reward assets without taking custody of the staked SBET itself.
Reward Formula
Each reward token has its own independent Synthetix-style accumulator. This allows the protocol to distribute ETH fee sharing, SBET emissions, and other approved reward assets at the same time while keeping accounting isolated per asset.
// Per reward token r
rewardPerToken_r = stored_r
+ (min(block.timestamp, periodFinish_r) - lastUpdateTime_r)
* rewardRate_r
* 1e18
/ totalSupply
// Per-user earned calculation
earned(user, r) = balanceOf(user)
* (rewardPerToken_r - userRewardPerTokenPaid[user][r])
/ 1e18
+ rewards[user][r]
Reward periods can be started either by transferring fresh funds with notifyRewardAmount or by
activating balances that were already pushed into the contract via treasury module flows with
notifyRewardFromBalance.
User Operations
| Function | Description |
|---|---|
stake(amount) |
Deposit SBET and mint stkSBET 1:1. First-time stakers are auto-self-delegated so voting power is immediately usable. |
unstake(amount) |
Burn stkSBET and receive SBET back. A short cooldown blocks same-block transfer and unstake patterns used in flash-loan voting attacks. |
delegate(delegatee) |
Assign voting power for governance proposals and votes. Delegation follows the standard ERC20Votes checkpoint model. |
claimReward(token) / claimAllRewards() |
Claim accrued rewards across the active ETH and ERC-20 reward set managed by MultiRewardStaking. |
View Functions
| Function | Returns |
|---|---|
balanceOf(account) |
The account's stkSBET balance, which is both the staking balance and governance voting weight source. |
earned(account, token) |
Pending rewards for a specific active reward token. Removed reward tokens return zero rather than reverting. |
rewardPerToken(token) |
The current accumulator value for a specific reward asset. |
getRewardTokens() |
The list of currently active reward assets in MultiRewardStaking. |
getVotes(account) / getPastVotes(account, timepoint) |
Current and historical voting power for governance snapshots. |
Admin Operations
Governance controls the admin surface through SBETGovernor and a 2-day
SBETTimelock. The timelock owns StakedSBET and holds the admin roles on
MultiRewardStaking, allowing proposals to update cooldowns, reward durations, reward tokens,
and distributors.
| Function | Description |
|---|---|
setCooldownPeriod(period) |
Updates the stkSBET transfer and unstake cooldown, bounded between 12 seconds and 1 day. |
addRewardToken(token, distributor, duration) |
Registers a new ETH or ERC-20 reward asset. The staking token itself cannot be added as a reward token. |
setDistributor(token, distributor) |
Rotates the operational distributor/keeper for a reward token. The distributor can activate rewards but cannot change staking configuration. |
notifyRewardAmount(token, amount) / notifyRewardFromBalance(token) |
Distributor-only reward activation paths for fresh top-ups and pre-funded balances respectively. |
removeRewardToken(token) |
Removes a finished reward token. Users must claim before removal; remaining balances are swept back to the configured distributor. |
Security Properties
- 1:1 backing: Every
stkSBETtoken is minted against deposited SBET and burned on unstake, preserving full backing. - Flash-loan friction: Cooldown enforcement on transfers and burns, plus Governor voting delay and timepoint snapshots, prevent same-transaction governance attacks.
- Multi-token accounting isolation: Each reward token has its own accumulator, tracked balance, and reward rate to prevent cross-asset contamination.
- Anti-donation accounting: Reward balance tracking uses internal accounting rather than trusting raw contract balances.
- Reentrancy protection: State-changing reward and claim paths use
ReentrancyGuardand update user accounting before external transfers. - No mandatory lock: Users can unstake at any time after cooldown; governance participation does not require ve-style lockups.
Operational Flow
The live system separates governance control from operational reward activation. Governance owns configuration, while a narrow distributor role is used for routine reward activation.
- Bootstrap: Deployment registers ETH and SBET as reward assets in
MultiRewardStaking. The deployer acts as the initial distributor/keeper, and governance can later rotate that role to a bot or multisig. - Fee sharing setup: Governance queues and executes
FeeManagerrecipient changes after the fee manager's own timelock, then the timelock triggersdistributeFees(token). - Reward activation: The active distributor calls
notifyRewardAmountfor fresh top-ups ornotifyRewardFromBalanceafter treasury or fee-manager transfers. - User accrual and claims: Rewards accrue continuously against
stkSBETbalances, and users claim by token or claim all active rewards in one transaction.
Prediction exchange fees are collected as ERC-1155 position tokens and currently route to the timelock. They are
governance-controlled assets, but they are not automatically shareable by MultiRewardStaking until
they are manually liquidated into ETH/ERC-20 or routed through a future adapter.
Supporting Contracts
SBETQuery
A read-only diagnostic contract that wraps calls to the main SBET contract for convenient frontend consumption.
Provides 29 view functions across 7 categories: user positions, match queries, pool queries, NFT queries,
order/nonce queries, system state, and aggregated portfolio views. All functions are view or pure
and never modify state. See the SBETQuery API for full function details.
SBETMath
Internal math library used across SBET contracts. Provides safe arithmetic for position calculations
including exposureDelta, effectiveBalance, priceDivide,
and position-safe add/subtract operations. All functions are pure and internal.
See the SBETMath API for function signatures.
Security
Security is a first-class concern across the entire protocol. Every contract follows defense-in-depth principles, and the codebase has been analyzed with 5 different security tools plus manual review. See the full audit report for all 125 findings and their remediations.
Defense-in-Depth Summary
- Reentrancy protection: Every state-mutating external function uses OpenZeppelin's
ReentrancyGuard. - Safe token handling: All ERC-20 interactions use
SafeERC20to handle non-standard return values and revert on failure. - Access control: Role-based permissions via OpenZeppelin
AccessControlon all privileged operations. - CEI pattern: All functions follow Checks-Effects-Interactions ordering to prevent cross-function reentrancy.
- Pausability: Emergency circuit breaker via
Pausableon all user-facing endpoints. - Integer safety: Solidity 0.8.34 built-in overflow/underflow checks. Additional safe math in SBETMath for position calculations.
Timelock Protections
| Operation | Timelock | Purpose |
|---|---|---|
| Match finalization | 24 hours | Window to detect incorrect results before committing |
| Treasury migration | 2 days | Community review period before funds move to a new contract |
| Fee recipient changes | Configurable | Prevents instant fee redirection by a compromised admin |
Formal Verification
Critical protocol invariants have been verified using multiple tools:
- Slither: Static analysis for common vulnerability patterns (reentrancy, unchecked returns, access control issues).
- Foundry Forge: 1,203 unit, integration, and fuzz tests across 22 test suites covering all contract functions and edge cases.
- Halmos: Symbolic execution for verifying mathematical invariants (e.g., LMSR price bounds, position accounting).
- Echidna: Property-based fuzzing for state machine invariants (e.g., pool totals, nonce monotonicity).
- SMTChecker: Compiler-integrated formal verification for overflow/underflow and assertion checking.
Authentication & Session Security
The backend indexer API uses Sign-In with Ethereum (SIWE) for session authentication, hardened across 4 security review rounds. Sessions are stored in Redis with 24-hour TTL.
| Layer | Mechanism |
|---|---|
| Session tokens | HttpOnly; Secure; SameSite=Lax cookie. Token never returned in JSON body or accessible via document.cookie. |
| CSRF protection | CSRFOriginMiddleware validates Origin (fallback Referer) against cors_origin_list for all state-changing methods. Missing origin with session cookie → 403. |
| CORS | Explicit origin allowlist, credentials: true, tightened allow_headers to ["Content-Type", "Authorization", "X-Internal-Token"]. |
| Admin authority | On-chain hasAnyTreasuryRole() via Web3 RPC, cached 60s in Redis. admin_wallets env var as emergency override. Fail-closed. |
| Internal services | X-Internal-Token with hmac.compare_digest. Fail-closed (empty key = reject all). |
| Bearer sunset | Legacy Bearer header fallback auto-disables after bearer_sunset_date (2026-04-15). Deprecation warnings logged before sunset. |
| Origin separation | Admin MPA (admin.html) served from separate origin (admin.sbetchain.ai) with tighter CSP. |
AI Engine & Backend Infrastructure
SBET includes a multi-provider AI stack centered on the backend indexer. Inference, caching, research, validation, routing, and trust alerts run server-side; the frontend consumes those outputs for Kelly widgets, EV overlays, and bot strategy previews. API keys are managed server-side — no secrets are ever exposed to the browser. The AI layer is strictly read-only and never signs transactions or moves funds.
Multi-Provider Architecture
The AI system supports three providers — Grok, Claude, and OpenAI —
with automatic fallback. If the primary provider fails, the system transparently retries with the next available provider.
A provider registry (src/ai/base.py) defines the abstract interface; each provider implements
chat(), estimate_probability(), and research() methods.
2-tier caching: Responses are cached first in Redis (hot cache, sub-millisecond reads) and then persisted
to a Postgres ai_cache table (warm cache). Cache keys are derived from the prompt hash + provider + model.
Every AI call is logged to ai_audit_log with provider, model, token counts, latency, and cost.
AI Probability & EV Scanner
The /api/ai/probability endpoint accepts a batch of market payloads
(id, question, outcomes, current_prices) and returns
per-outcome probabilities keyed by outcome label, plus confidence and reasoning.
The frontend currently batches up to 10 markets per request with a 5-minute stale time via TanStack Query.
The /api/ai/ev-scan endpoint compares those probabilities against current market prices and returns
outcome-level opportunities with ai_probability, market_price, ev_edge,
kelly_fraction, confidence, and reasoning.
When the backend cannot parse a model response, it falls back to equal probabilities with
confidence = 0; the frontend treats that as no AI signal rather than a tradable edge.
Kelly Criterion Sizing
The Kelly widget computes optimal bet size using the formula f = (bp − q) / b, where
b is the decimal odds minus 1, p is the AI probability, and q = 1 − p.
The result is capped at 25% of bankroll and a half-Kelly conservative mode is available.
This runs client-side from backend AI probability data — no additional API call is required after the
probability fetch. Advice is suppressed when AI confidence is zero or unavailable.
Smart Order Routing
The /api/routing/quote endpoint accepts an order (market, side, amount) and returns an optimal
split between the AMM pool and the on-chain order book (CLOB). The response includes the exact allocation
percentages, effective price per venue, and savings compared to single-venue execution.
The frontend displays a visual split-bar with a savings badge.
Natural Language Orders
The /api/ai/parse-order endpoint accepts a natural language string (e.g., “Buy YES at 58 cents,
0.01 ETH, 24h”) and returns a structured order object with side, price,
amount, and expiry. The frontend renders a preview card before signing.
Bot Strategy Templates
Three pre-built algorithmic strategies are available: Grid Trading (evenly spaced limit orders), Kelly Spread (orders sized by Kelly fraction at varying prices), and Momentum (directional orders based on AI-vs-market edge). Grid Trading does not require AI input; Kelly Spread and Momentum require trusted AI confidence and use label-matched per-outcome probabilities, so they work across both binary and multi-outcome markets. Each strategy generates a batch of limit orders that can be previewed in a table and signed in a single EIP-712 flow.
Maker Copilot
The /api/ai/maker-copilot endpoint generates quote ladders for liquidity providers.
It accepts a market ID and risk tolerance (low / medium / high) and returns a set of bid/ask quotes
with optimal spread and size. Backend validation keeps BUY quotes below fair value and SELL quotes above fair
value before they reach the UI. Any quote can be applied to the limit order form with one click.
AI Market Generation
AI generates prediction market ideas from trending topics. The frontend provides category filters and
a count slider (1–10). Selected markets can be batch-created on-chain in a single dialog with
sequential createMarket calls and progress tracking.
AI Oracle / Multi-Agent Grading
For market resolution, up to 3 AI providers independently evaluate the outcome. Each provider returns a vote, confidence score, and reasoning. The final resolution uses majority consensus. This provides a decentralized grading mechanism that reduces single-provider bias.
Dispute Resolution AI
When a market resolution is disputed, the /api/ai/research endpoint performs deep analysis
with a merit score (0–100), lists of supporting and counter evidence, and a recommendation.
This integrates with on-chain dispute bonds and deadline tracking.
Anomaly Detection Pipeline
The backend indexer runs detection routines every 5 minutes (configurable via detection_interval_sec):
- Wash trading detector: Identifies buy/sell round-trip pairs from the same address within 30-minute windows.
- Manipulation detector: Flags large trade reversals consistent with pump-and-dump patterns.
Detected anomalies create Alert and AddressLabel entries with provenance metadata
(source, confidence, observed_at). These feed into the Trust & Integrity
layer visible in the frontend as market health indicators and counterparty risk warnings.
Detectors dedupe against unresolved active alerts so the same address is not re-alerted every cycle.
Portfolio Correlation
The /api/ai/portfolio-correlation endpoint analyzes a wallet’s open positions across markets.
It returns a correlation matrix, a diversification grade (A–F), a concentration score, and specific
hedging suggestions. The /api/positions/{wallet} endpoint provides the underlying net position
aggregation.
Market Validation & Embeddings
The /api/ai/validate-market endpoint scores proposed market questions on quality (0–100),
identifies potential issues, and suggests rewording. It also performs duplicate detection using pgvector
(HNSW index with 1536-dimensional embeddings). When a MarketCreated event is detected on-chain,
the indexer auto-embeds the market for future duplicate checks.
Real-Time SSE Infrastructure
The indexer provides a Server-Sent Events stream at GET /api/events/stream. Event types include
price_update, trade, indexer_lag, and alert_triggered.
- Backend:
SSEBroadcasterwith per-subscriber asyncio queues (max 256), 15-second heartbeats, monotonic event IDs. - Authentication: Single-use stream tickets (60s TTL) via
POST /api/auth/stream-ticket. Wallet stored in ticket payload. Authenticated users bypass per-IP cap but are subject to per-wallet cap. - Connection limits: 2 connections per IP (anonymous), 5 per wallet (authenticated), 100 total, 5 connection attempts/min rate limit, 5-minute idle timeout.
- Frontend: EventSource client with exponential backoff (1s → 30s cap), 45-second heartbeat timeout, TanStack Query cache injection, and timestamp dedup.
- Optimization: When SSE is connected, polling intervals are relaxed (price history 60s → 5min, lag badge 30s → 2min).
Alert Subscriptions
Users can create price alert subscriptions via /api/alerts (CRUD). When the indexer detects
a price crossing the threshold, it marks the alert as triggered and fires an SSE alert_triggered
event. The frontend dispatches a sbet:alert CustomEvent for toast notifications.
Trust & Integrity Layer
The indexer maintains address labels, wallet clusters, and alerts with full provenance. The frontend exposes this data through market health cards (manipulation risk with green/amber/red indicators) and counterparty risk warnings (shown only for high/critical risk levels). All trust UI gracefully degrades when the indexer is unconfigured.
Admin treasury flows also use the trust layer for token admission preflights. A vendored BlockWallet banned-assets snapshot is exposed through admin-only token screening endpoints, so exact denylist hits are blocked before a token is added to the treasury allowlist. Unsupported chains degrade to manual-review warnings.
Finality & reorg protection: The indexer only processes blocks at depth 12 (configurable).
Each price snapshot stores its block_hash. On startup, check_for_reorg() compares
stored hashes against the chain and triggers handle_reorg() if a mismatch is found.
Decentralized Oracle
The SBET Protocol uses a decentralized grader quorum to settle bets and resolve prediction markets without relying on a single oracle. Multiple independent grader bots sign off on outcomes, and the smart contract enforces the quorum before releasing funds. No single grader can finalize a match alone.
Architecture
The oracle system has three layers:
- Data Sources — For sports bets, automated grader bots fetch final scores from independent APIs (TheSportsDB, Odds API). For prediction markets, multi-provider AI consensus (Grok + Claude + GPT-4o) determines which outcome occurred based on verifiable evidence.
-
Grader Network — Each grader bot runs independently, holds its own private key, and
submits a signed grade to the indexer API (
POST /api/oracle/grade). The indexer validates the signature (personal_sign style matching the contract’s ecrecover) and stores the grade. - On-Chain Finalization — Once the required quorum of graders agree on an outcome, anyone can submit the finalization transaction. The contract verifies all signatures on-chain before settling.
Sportsbook Settlement
Sportsbook bets settle via SBET.finalizeWithGraders(witness, graderQuorum, graderFee, graders[], finalPrice, sigs[]).
The finalPrice uses the contract’s 1–999,999,999 scale (where 999,999,999 = full win, 1 = full loss,
500,000,000 = push/void). Supported market types: head-to-head (h2h), over/under, both teams to score (BTTS),
spread, and total.
Graders sign keccak256(abi.encodePacked(address(this), matchId, finalPrice)) with the Ethereum Signed Message
prefix. Signatures are packed as [r, sv] where sv = s | ((v - 27) << 255) (EIP-2098 compact encoding).
Prediction Market Resolution
Prediction markets follow a two-step process:
-
resolveMarket — Submits the winning outcome and per-outcome grader signatures. Each
sub-market (one per outcome) has its own matchId and price, so graders sign each sub-market separately.
The winning outcome gets
finalPrice = MAX_PRICE - 1and losers getfinalPrice = 1. The market entersResolutionPendingstatus with a dispute window. -
finalizeResolution — After the dispute window passes, anyone can call this to
execute
SBET.finalizeWithGradersfor each sub-market. The market becomesResolvedand AMM redemption is unlocked.
AI-Powered Grading
For prediction markets and community bets where outcomes are not covered by sports APIs, the protocol uses
multi-provider AI consensus. Three independent LLM providers are queried concurrently with a strict oracle prompt.
All providers must agree (consensus) with confidence ≥ 80% for the grade to be accepted. The AI output is
validated against a strict schema (validate_grade_result) to prevent prompt injection attacks.
AI grading is advisory until signed. The AI determines the outcome, but the actual on-chain grade must be signed by registered grader wallets. The AI never signs transactions directly.
Safety & Recovery
If graders fail to reach quorum within the recovery deadline, users can call recoverFunds(matchId)
to retrieve their stake at the preset cancel price. The recovery mechanism ensures funds are never permanently
locked, even if the entire grader network goes offline.
The oracle cycle uses Redis advisory locks with ownership-safe CAS delete to prevent duplicate submissions in multi-replica deployments. Matches stuck in grading for more than 6 hours are automatically reset for retry.
Oracle Dashboard
The dApp includes a real-time Oracle Dashboard at /console/oracle showing:
- Pending matches awaiting grading
- Quorum progress bars per match (X of N graders signed)
- Data source indicators (TheSportsDB, AI Consensus, Manual)
- Finalization history with Etherscan transaction links
- Permissionless “Finalize On-Chain” button for sportsbook matches with quorum met
Six lifecycle states are tracked: pending → grading →
quorum_met → resolution_pending → finalized
(and failed).
Deployment
Current Status
The protocol is fully developed, tested, and audited. The full contract suite and modern dApp flows are active on Sepolia for end-to-end testing, while mainnet rollout remains staged. The SBET token is already deployed on Ethereum mainnet.
Mainnet Token
| Contract | Network | Address |
|---|---|---|
| SBET Token (ERC-20) | Ethereum Mainnet | 0x2ed2cc2c858a8a8219fd2f2d9e170285dbd02756 |
Compiler Configuration
| Setting | Value |
|---|---|
| Solidity version | 0.8.34 |
| EVM target | prague |
| IR pipeline | via_ir = true |
| Optimizer | Enabled, 200 runs |
Contract Addresses
The full contract suite is deployed on Sepolia (redeployed 2026-03-27 with fast settlement constants). All time-based parameters are optimized for 1–3 minute payouts. Mainnet deployment is staged — the SBET token is already live on Ethereum mainnet.
| Contract | Sepolia | Mainnet |
|---|---|---|
| SBET Token (ERC-20) | 0xf66d8dD87CC7c1884D5CC2Be092aBA884409334d |
0x2ed2cc2c858a8a8219fd2f2d9e170285dbd02756 |
| SBET Core (Trading + Pools + NFT + Claims) | 0x9D355D2FEdc1c8dcb1FEE1833812da6f69bfe832 |
Pending |
| SBETQuery | 0x1ec3bdda137ad34a99c6a8a4d7cea684d8c8167b |
Pending |
| SBETTreasury | 0x644AdE8b9F09e2C76604E55bEDBF40f2E329625F |
Pending |
| IntegratorHub | 0xcD2462aCf0079b0B2c70D9243C13318f7129a7cD |
Pending |
| NFTVault | 0x35561d8935B45ba5573E3BBdFd3a2773BD9fB665 |
Pending |
| TreasuryFeeManager | 0xa39C2De2b8cF06E323875d5ad84Bf69374AD58f6 |
Pending |
| TreasuryBudgets | 0x404f0c04082CE8d342bA7a708607420C646369ec |
Pending |
| TreasuryVesting | 0x5094c564626F62774a922d2099e93e89f4D44407 |
Pending |
| TreasuryYield | 0x0a12347d40646d9F3DE2717b6fb16e0eA5dc2922 |
Pending |
| TreasuryMultiSig | 0xdD3d4777D7DEcDA5CF83BBEB2473a0b741ED490b |
Pending |
| TreasuryNFTManager | 0x6148A994AAC47b29192ac1C1Ca8eEeDf72C1cE1c |
Pending |
| TreasuryNFTFees | 0x15CFE74ECF92D71F3E2C7c7965c961526295E719 |
Pending |
| PredictionMarket | 0x2290560C272A716CEb8cd592074646B90d10c254 |
Pending |
| PredictionAMM | 0x4747080CFe376803e29B30974c5087F598536398 |
Pending |
| PredictionExchange | 0x1F2b536E2eB7C7D1995Ef511E18F1561C9cAE54d |
Pending |
| DonationManager | 0xB4037b216E96a535414148F0213a4A360708904c |
Pending |
| StakedSBET | 0xEB22630E55Bb3ed925f73e5415374C1ACB7b698b |
Pending |
| MultiRewardStaking | 0x7B1D557d3d82A5303D29deFBBfEd9F608C5e6479 |
Pending |
| SBETGovernor | 0x49c23D5794E392522E88950450242274Eb751BF1 |
Pending |
| SBETTimelock | 0xCA817bcA55e853653a7B6205470018dDEA7431C3 |
Pending |
Note: TreasuryFacade exceeds the 24 KB contract size limit and is not deployed.
All 21 Sepolia addresses above are from the 2026-03-27 redeployment with fast settlement constants.
Settlement Times
All time-based constants are optimized for fast, no-KYC crypto betting payouts. The target is 1–3 minute settlement from event end to claimable funds.
| Parameter | Value | Purpose |
|---|---|---|
| Finalization delay (oracle path) | 1 minute | Timelock between requestFinalization and executeFinalization |
| Grader oracle (quorum path) | Instant | finalizeWithGraders settles immediately when quorum is met |
| Prediction market dispute window | 1 minute | Window after resolveMarket before finalizeResolution can execute |
| Oracle staleness threshold | 5 minutes | Maximum age of Chainlink oracle data for finalization |
| Emergency duration | 1 hour | Protocol pause on emergency trigger (owner-only) |
| Governance voting delay | 5 minutes | Delay before voting starts on a proposal |
| Governance voting period | 15 minutes | Duration of the voting window |
| Timelock delay | 5 minutes | Delay between proposal queue and execution |
Typical Payout Timelines
| Bet Type | Settlement Path | Expected Time |
|---|---|---|
| Sportsbook (P2P) | Game ends → grader bots sign → finalizeWithGraders → claim |
~1–2 min |
| Prediction market | Event resolves → AI grades → resolveMarket → 1 min dispute → finalizeResolution → claim |
~2–3 min |
| Pool betting | Owner calls finalizePool → claim |
< 1 min |