PredictionLabs

Security

Trust assumptions, contract guards, and frontend responsibilities.

Trust assumptions

Market creators choose a resolver contract when creating a market. Users who trade on a market are trusting that market's resolver to resolve correctly. The resolver cannot be changed after market creation.

Resolvers are pluggable. UMAResolver uses UMA's Optimistic Oracle. KalshiResolver uses Kalshi tickers via UMA. PolymarketResolver reads from resolved Polymarket conditions. Anyone can deploy a new resolver type. Trust is per-market, not protocol-wide.

Gnosis CTF is trusted for token accounting. Outcome tokens, position splitting, and payout redemption all happen through CTF. If CTF has a bug, token balances could be incorrect.

USDC is the settlement currency. If Circle blacklists a contract address, funds in that contract become frozen. USDC is also pausable by Circle.

What nobody can do

  • Change a market's resolver after creation
  • Modify existing markets or their parameters
  • Move funds from the OrderBook or Treasury
  • Change the fee percentage (hardcoded at 0.1%)
  • Change the treasury recipient (immutable)
  • Pause or stop trading
  • Create a market with the same conditionId twice
  • Resolve a market before its deadline
  • Cancel someone else's order (except expired orders after resolution)
  • Fill orders on a resolved market
  • Call reportPayouts on the CTF twice for the same condition

No admin, no governance

There is no deployer role, no owner, no admin functions, no timelock, no proxy, no upgradeable storage, no governance. All contracts are immutable once deployed. The only protocol-level safety valve is forceVoid, which is permissionless.

Emergency scenarios

Resolver stops responding. If assertions are stuck (callbacks never fire), the forceVoid path on MarketFactory activates. Anyone can call forceVoid after 90 days past a market's deadline (if no pending assertions) or after 180 days (unconditional emergency override, resets pending count). Voided markets pay out equally to all outcome holders.

Resolver resolves incorrectly. If an incorrect assertion passes the liveness window undisputed, the market resolves incorrectly. There is no reversal mechanism - CTF payouts are final. The liveness window is the dispute opportunity.

USDC blacklists a contract. Funds in that contract become inaccessible. This is a systemic risk of using USDC. The protocol does not have a fallback collateral mechanism.

Contract guards

  • ReentrancyGuard on all external state-changing functions in OrderBook and resolvers
  • SafeERC20 for all USDC transfers (handles non-standard return values)
  • ERC-1155 receiver implemented on OrderBook (required by CTF for token transfers)
  • Minimum order/fill size prevents dust griefing (0.01 USDC minimum)
  • Seller proceeds check prevents zero-value transfers that could drain sell orders
  • Pending assertion count on MarketFactory tracks active + disputed assertions to prevent premature voiding via forceVoid

Frontend responsibilities

The protocol is permissionless. Contracts don't protect users from bad decisions - frontends must. These are not optional nice-to-haves; they are required for user safety.

Whitelist trusted resolvers. Anyone can create a market with any resolver, including a malicious EOA that resolves in their favor. Your frontend should maintain a list of trusted resolver addresses (UMAResolver, KalshiResolver, PolymarketResolver) and either hide or prominently warn about markets using unknown resolvers.

Check resolver linking status. Kalshi and Polymarket resolvers require a linkMarket() call before they can resolve. A market with an unlinked resolver is tradeable but can never be resolved normally - funds are locked until forceVoid (90+ days). Before displaying a Kalshi/Polymarket market as active:

// Kalshi: check if ticker is set
const ticker = await kalshiResolver.read.kalshiTickers([conditionId]);
const isLinked = ticker !== "0x" + "0".repeat(64);

// Polymarket: check if condition is linked
const polyCond = await polyResolver.read.polymarketConditions([conditionId]);
const isLinked = polyCond !== "0x" + "0".repeat(64);

Show a clear warning if unlinked. Do not show unlinked markets in default views.

Warn about post-deadline trading. Markets trade until on-chain resolution, not until deadline. Between deadline and resolution, informed traders can pick off stale limit orders from users who forgot to cancel. Your frontend should:

  • Show a prominent warning on markets past deadline but not yet resolved
  • Prompt users to cancel open orders before or at deadline
  • Consider auto-hiding or deprioritizing post-deadline markets

Help users cancel before deadline. Surface open orders clearly. Show time-to-deadline. Send notifications if configured. Make cancellation easy and obvious.

Validate order parameters. The contracts enforce minimum order sizes (0.01 USDC) and valid price ranges, but your frontend should validate before submitting to save users gas on reverts.

Audit status

6 independent internal audit passes. Critical vulnerability found and fixed (resolver linking bait-and-switch). Zero remaining critical or high findings. Full test suite: 362 tests (unit, fuzz, integration, fork, invariant, lifecycle). No formal third-party audit yet. Treat accordingly.

On this page