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.

27
Contracts
25K+
Lines of Solidity
1,203
Tests Passing
146/146
Audit Findings Addressed

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, and AccessControl. State changes follow the Checks-Effects-Interactions (CEI) pattern throughout.
  • Gas-efficient: Packed order encoding, batch operations on all endpoints, and via_ir compilation 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

CORE TRADING SBET (SBETTrading + SBETClaims) BETTING PRIMITIVES SBETPool (module of SBET) SBETNFT (module of SBET) NFTVault PREDICTION MARKETS PredictionMarket PredictionAMM SUPPORT SBETQuery SBETMath DONATIONS DonationManager STAKING StakedSBET + MultiRewardStaking TREASURY SYSTEM SBETTreasury (Hub) FeeManager Vesting Budgets MultiSig Yield NFTManager NFTFees INTEGRATOR INFRASTRUCTURE IntegratorHub SHARED: OpenZeppelin ReentrancyGuard + AccessControl / Ownable + SafeERC20 + Pausable
Figure 1 — Contract architecture hierarchy. The crimson dashed line shows the core-to-treasury fee flow.

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

Sign Order EIP-712 off-chain Submit to relayer / UI Match On-Chain trade() / matchOrders() Finalize Match grader sigs + timelock Claim claim() / batchClaim() Cancel (nonce/all)
Figure 2 — Order lifecycle from off-chain signing to on-chain settlement and payout.

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 that cancelAll() originally did not prevent order execution in the trade() path. This was remediated by adding a cancelTimestamps check 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

  1. Create: An admin creates a pool with createPool(token, numOutcomes), specifying the settlement token and number of possible outcomes (minimum 2).
  2. Join: Users place bets by calling joinPoolBet(poolId, outcome, amount). Tokens are transferred into the pool contract.
  3. Finalize: After the event concludes, an admin calls finalizePool(poolId, winningOutcome) to lock in the result.
  4. Claim: Winners call claimPoolWinnings(poolId) to receive their pro-rata share. Batch claiming across multiple pools is supported via batchClaimPools().

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
Total Pool: 1,000 USDC Outcome A (Winner) 600 USDC (60%) Outcome B 300 USDC (30%) Outcome C 100 USDC (10%) Example Payout (Outcome A wins): Alice staked 300 of 600 on A Payout = (300/600) * 1000 = 500 USDC Bob staked 150 of 600 on A Payout = (150/600) * 1000 = 250 USDC
Figure 3 — Pool payout distribution. The entire pool (including losing stakes) is divided among winners.

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

  1. Create (Side A): A user calls createBetWithNFT(matchId, nftAddress, nftId) to stake their NFT. The token is transferred to NFTVault for escrow.
  2. Join (Side B): A counterparty stakes their NFT via joinBetWithNFT().
  3. Finalize: After the match result is determined, finalizeMatchNFTs() transfers both NFTs to the winner. Batch finalization is available via finalizeMatchNFTsBatch() 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.

0.0 0.25 0.50 0.75 1.0 0 25 50 75 100 Shares purchased for Outcome A (binary market) Price of A b=10 b=50 b=200 50% (equal odds start)
Figure 4 — LMSR price curves for different 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/minPayout parameters. 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 /portfolio page 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 createMarket calls 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:

StatusValueDescription
Active0Accepting trades via AMM and limit orders.
Paused1Trading frozen by admin. No buys/sells/fills.
ResolutionPending2Resolution submitted, awaiting dispute window expiry.
Resolved3Final outcome set. Winners can redeem shares.
Disputed4Resolution challenged. Dispute council adjudicates.
Voided5Market 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.

SBET Treasury FeeManager Fee recipients + distribution Vesting Token vesting schedules Budgets Department spending limits MultiSig Governance proposals Yield Strategy deployment NFTManager NFT asset management NFTFees Per-collection fee config IntegratorHub Fee accrual + payout
Figure 5 — Treasury hub-and-spoke architecture. Each module is a separate contract connected to the central treasury hub.

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:

  1. queueMigration(newTreasury) — Starts a 2-day timelock. The treasury must be locked (setLock(true)) at this point.
  2. 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

Trade Executes via integrator router Fee Accrued accrue(router, token) Sweep Period Daily / Weekly / Monthly Integrator 95% (default) Treasury 5% (default) Payout ERC-20
Figure 6 — Integrator fee flow. Fees accrue per-trade, accumulate until the sweep period elapses, then split between integrator and treasury.

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:

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

  1. Register: A DONATION_MANAGER_ROLE holder registers organizations with name, cause, description, image, and wallet address. Limited to MAX_ORGANIZATIONS (500).
  2. Verify: The contract owner verifies organizations before they can receive donations.
  3. Donate: Users donate ERC-20 tokens via donate() or ETH via donateETH() to a verified organization.
  4. 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.
  5. 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 stkSBET token 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 ReentrancyGuard and 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.

  1. 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.
  2. Fee sharing setup: Governance queues and executes FeeManager recipient changes after the fee manager's own timelock, then the timelock triggers distributeFees(token).
  3. Reward activation: The active distributor calls notifyRewardAmount for fresh top-ups or notifyRewardFromBalance after treasury or fee-manager transfers.
  4. User accrual and claims: Rewards accrue continuously against stkSBET balances, 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 SafeERC20 to handle non-standard return values and revert on failure.
  • Access control: Role-based permissions via OpenZeppelin AccessControl on all privileged operations.
  • CEI pattern: All functions follow Checks-Effects-Interactions ordering to prevent cross-function reentrancy.
  • Pausability: Emergency circuit breaker via Pausable on 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.

LayerMechanism
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: SSEBroadcaster with 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:

  1. 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.
  2. 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.
  3. 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:

  1. 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 - 1 and losers get finalPrice = 1. The market enters ResolutionPending status with a dispute window.
  2. finalizeResolution — After the dispute window passes, anyone can call this to execute SBET.finalizeWithGraders for each sub-market. The market becomes Resolved and 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: pendinggradingquorum_metresolution_pendingfinalized (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