--

8BALL

Prediction markets on Bittensor

Trade on your insights. Provide liquidity. Earn rewards.

MARKETS

How 8Ball Works

A technical overview of the prediction market and liquidity incentive system

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                              8BALL SYSTEM                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐        │
│   │ OutcomeRegistry │    │ PredictionCLOB  │    │ ConditionalTokens│        │
│   │                 │    │   (Orderbook)   │    │    (ERC-1155)   │        │
│   │  • Markets      │◄──►│  • Bids/Asks    │◄──►│  • YES tokens   │        │
│   │  • Resolution   │    │  • Fills        │    │  • NO tokens    │        │
│   │  • Oracle       │    │  • Locks        │    │  • Redemption   │        │
│   └─────────────────┘    └─────────────────┘    └─────────────────┘        │
│            │                      │                      │                  │
│            └──────────────────────┼──────────────────────┘                  │
│                                   │                                          │
│                          ┌────────▼────────┐                                │
│                          │     VALLEY      │                                │
│                          │  Reward Engine  │                                │
│                          │                 │                                │
│                          │  • Epochs       │                                │
│                          │  • Utilization  │                                │
│                          │  • Optimization │                                │
│                          │  • Token Alloc  │                                │
│                          └─────────────────┘                                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
            

Valley: The Reward Engine

Valley incentivizes market makers to provide tight, long-lived liquidity through a utilization-based reward system.

Epoch Structure

┌─────────────────────────────────────────────────────────────────────────────┐
│                         EPOCH (15 minutes)                                   │
├─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────────────┤
│ Band 1  │ Band 2  │ Band 3  │   ...   │ Band N  │ Settle  │  Distribute    │
│ 40-80s  │ 40-80s  │ 40-80s  │         │ 40-80s  │ Scores  │  Rewards       │
└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────────────┘
              
  • Bands: Short time windows where the optimizer runs
  • Epochs: Collection of bands, rewards distributed at epoch end
  • Settlement: Calculate utilization and allocate tokens

Utilization & Kappa

utilization = matched_volume / posted_volume κ₁ = max(0.1, 1.0 - 0.9 × utilization)
  • High utilization: Liquidity is being used → Lower κ₁ → Cheaper bridging
  • Low utilization: Quotes not getting filled → Higher κ₁ → Penalize wide spreads

The Optimization Problem

Maximize: Σ xᵢⱼ (total matched flow)

Subject to: Σ c(dᵢⱼ)·xᵢⱼ ≤ B (budget constraint) Σⱼ xᵢⱼ ≤ Bᵢ (bid capacity) Σᵢ xᵢⱼ ≤ Aⱼ (ask capacity) Σ dᵢⱼ·xᵢⱼ ≤ s̄·Σxᵢⱼ (spread quality)

Where: dᵢⱼ = ask_price - bid_price (span distance) c(d) = κ₁·d + κ₂·d² (convex bridging cost)

The optimizer allocates flow across bid-ask pairs, penalizing wider spreads with a convex cost function.

Liquidity Mining

Separate from Valley, 8Ball runs a straightforward liquidity mining program that rewards market makers for keeping tight, committed quotes on the book. No optimizer, no convex programs — just three numbers multiplied together. Scoring runs across all 4 orderbooks per market (YES bids, YES asks, NO bids, NO asks).

The Score Formula

score = size × duration × e−40·d

Where: size = notional value of resting order (USD) duration = seconds the order was live this snapshot d = |order_price − midpoint| (distance from mid)

Your score is proportional to how much capital you post, how long you leave it there, and how close to the midpoint you price it. The exponential decay heavily rewards tightness:

Spread Scoring Curve

Distance from Mid Spread Score Relative Reward
0% (at mid) 1.000 100% — maximum
0.5% 0.819 82%
1% 0.670 67%
2.5% 0.368 37%
5% (cutoff) 0.135 14%
>5% 0 does not qualify

Qualification Rules

Rule Value Why
Max spread from mid 5% Only reward useful liquidity, not far-out-of-the-money orders
Min lock duration 30 seconds Prevent cancel/replace gaming — you must commit
Size > 0 Order must have actual notional behind it

Orders that fail any of these checks earn zero score for that snapshot. The lock requirement is the key anti-gaming measure: you can't flash-post liquidity and pull it before anyone can trade against it.

Reward Distribution

1

Snapshot

Every 45–90s (randomized), read all resting orders across all 4 books (YES bid/ask, NO bid/ask)

2

Score

For each qualifying order: size × duration × spread_score. Sum per LP

3

Pro-rata

Each LP gets (their_score / total_score) × snapshot_reward

4

Aggregate

Snapshots accumulate over 24h epochs. Daily pool is fixed

snapshot_reward = daily_pool × (interval / 86400) your_reward = (your_score / total_score) × snapshot_reward

Valley vs. Liquidity Mining

Valley Liquidity Mining
Complexity Convex optimization (CVXPY) Multiplication + exponential
What it rewards Matched flow through bid-ask spans Resting liquidity near the midpoint
Epoch length 15 minutes (bands within) 24 hours (snapshots within)
Anti-gaming Utilization-based kappa pricing Randomized snapshots + mandatory locks
When to use Mature markets with active trading Bootstrapping liquidity, simpler to reason about

Order Locks

Market makers can lock their orders to signal commitment.

Parameter Value Description
Maximum Lock 1800 seconds 30 minutes maximum commitment
Minimum Quote Age 5 seconds Quotes must exist 5s before counting for rewards
Lock Effect Cancel blocked Maker cannot cancel/amend until lock expires
Fills Always allowed Takers can fill locked orders anytime

Reward Distribution

1

Track Quotes

Record all quotes and their durations during the epoch

2

Run Optimizer

Solve convex program to allocate flow optimally

3

Calculate Shares

Determine each maker's contribution to total volume

4

Distribute Tokens

Allocate reward pool proportionally to addresses

API Documentation

Interact with 8Ball markets programmatically

Contract Addresses

OrderBook:  Loading...
Registry:   Loading...
USDC:       Loading...
CTF:        Loading...

REST API

Use these endpoints to fetch market data without connecting to the blockchain directly. All data is cached server-side and updated every 2 minutes.

Data Freshness

REST API data is cached and may be up to 2 minutes behind the chain. For time-sensitive operations, query the blockchain directly.

Best for

UI display, charts, browsing, price estimates

Use chain for

Order fills, balance checks, real-time trading

Rate Limits

Limit:    60 requests per minute per IP
Headers:  X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Status:   429 Too Many Requests (when exceeded)

GET /api/markets

Fetch all markets with orderbook data. Best for initial load.

// Request
fetch('/api/markets')

// Response
{
  "markets": {
    "1": {
      "market": {
        "id": "1",
        "collateral": "0x3c499c...",
        "ctf": "0x4D97DC...",
        "yesPositionId": "67526...",
        "noPositionId": "65657...",
        "active": true,
        "resolved": false,
        "winningOutcome": 0
      },
      "orderBooks": {
        "0": {  // YES position
          "bids": [{ "price": "4500", "depth": "1000000000" }],
          "asks": [{ "price": "5500", "depth": "500000000" }]
        },
        "1": {  // NO position
          "bids": [...],
          "asks": [...]
        }
      },
      "lastUpdated": 1706395200000
    },
    "2": { ... }
  },
  "marketList": [...],      // Market metadata
  "contracts": { ... },     // Contract addresses
  "chainId": 137,
  "feeBps": 100,
  "preloadComplete": true,
  "cachedCount": 20,
  "totalCount": 20
}

GET /api/market?id={marketId}

Fetch a single market. Returns fresh data if not cached.

// Request
fetch('/api/market?id=1')

// Response
{
  "market": { ... },
  "orderBooks": { ... },
  "lastUpdated": 1706395200000
}

GET /api/fee

Get the current trading fee in basis points.

// Request
fetch('/api/fee')

// Response
{ "feeBps": 100 }  // 100 bps = 1%

GET /api/price-history

Get historical price snapshots for charting. Data logged every 5 minutes.

// All markets, last 24 hours (default)
fetch('/api/price-history')

// Specific market
fetch('/api/price-history?market=1')

// Last 6 hours
fetch('/api/price-history?hours=6')

// Response
{
  "snapshots": [
    {
      "timestamp": 1706395200000,
      "isoTime": "2026-01-27T12:00:00.000Z",
      "yes": {
        "bestBid": 0.45,
        "bestAsk": 0.55,
        "midPrice": 0.50,
        "spread": 0.10,
        "bidDepth": 1000.50,   // USD
        "askDepth": 500.25,
        "bidLevels": 5,
        "askLevels": 3
      },
      "no": { ... }
    },
    ...
  ],
  "totalSnapshots": 288,
  "oldestSnapshot": "2026-01-26T12:00:00.000Z",
  "newestSnapshot": "2026-01-27T12:00:00.000Z"
}

Example: JavaScript Client

// Fetch all markets
async function getAllMarkets() {
  const resp = await fetch('/api/markets');
  if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
  return resp.json();
}

// Get best prices for a market
async function getBestPrices(marketId) {
  const data = await getAllMarkets();
  const market = data.markets[marketId];
  if (!market) return null;
  
  const yesBids = market.orderBooks[0].bids;
  const yesAsks = market.orderBooks[0].asks;
  
  return {
    yesBestBid: yesBids[0] ? Number(yesBids[0].price) / 10000 : null,
    yesBestAsk: yesAsks[0] ? Number(yesAsks[0].price) / 10000 : null,
  };
}

// Handle rate limiting
async function fetchWithRetry(url, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const resp = await fetch(url);
    
    if (resp.status === 429) {
      const data = await resp.json();
      const waitMs = (data.retryAfter || 60) * 1000;
      console.log(`Rate limited, waiting ${waitMs}ms`);
      await new Promise(r => setTimeout(r, waitMs));
      continue;
    }
    
    return resp;
  }
  throw new Error('Max retries exceeded');
}

Example: Python Client

import requests
import time

BASE_URL = "https://your-8ball-domain.com"

def get_markets():
    """Fetch all market data."""
    resp = requests.get(f"{BASE_URL}/api/markets")
    resp.raise_for_status()
    return resp.json()

def get_price_history(market_id=None, hours=24):
    """Fetch price history for charts."""
    params = {"hours": hours}
    if market_id:
        params["market"] = market_id
    resp = requests.get(f"{BASE_URL}/api/price-history", params=params)
    resp.raise_for_status()
    return resp.json()

def get_best_prices(market_id):
    """Get current best bid/ask for a market."""
    data = get_markets()
    market = data["markets"].get(str(market_id))
    if not market:
        return None
    
    yes_bids = market["orderBooks"]["0"]["bids"]
    yes_asks = market["orderBooks"]["0"]["asks"]
    
    return {
        "yes_best_bid": int(yes_bids[0]["price"]) / 10000 if yes_bids else None,
        "yes_best_ask": int(yes_asks[0]["price"]) / 10000 if yes_asks else None,
    }

# Example usage
if __name__ == "__main__":
    markets = get_markets()
    print(f"Loaded {markets['cachedCount']} markets")
    
    prices = get_best_prices(1)
    print(f"Market 1 - Bid: {prices['yes_best_bid']}, Ask: {prices['yes_best_ask']}")

Understanding Markets

Each market has a unique numeric ID assigned sequentially when created.

How Market IDs Work

Markets are stored in the OutcomeRegistry contract with IDs starting from 1.

// Get the next available market ID (tells you how many markets exist)
const nextId = await registry.nextMarketId();
// If nextId = 21, markets 1-20 exist

// List all markets
for (let id = 1; id < nextId; id++) {
  const market = await registry.get(id);
  console.log(`Market ${id}: ${market.resolutionCriteria}`);
}

Market Structure

Each market contains the following fields:

const market = await registry.get(marketId);

{
  collateral: address,      // USDC contract address
  ctf: address,             // ConditionalTokens contract
  questionId: bytes32,      // Unique question identifier
  conditionId: bytes32,     // CTF condition ID
  yesIndexSet: uint256,     // Index set for YES outcome
  yesPositionId: uint256,   // ERC-1155 token ID for YES
  noPositionId: uint256,    // ERC-1155 token ID for NO
  oracleAdapter: address,   // Resolution oracle
  resolutionCriteria: string, // Human-readable question
  active: bool,             // Whether trading is enabled
  resolved: bool,           // Whether outcome is known
  winningOutcome: uint8     // 0=YES won, 1=NO won (if resolved)
}

Example: Finding a Specific Market

Search markets by criteria text to find its ID:

async function findMarketByKeyword(keyword) {
  const nextId = await registry.nextMarketId();
  
  for (let id = 1; id < nextId; id++) {
    const market = await registry.get(id);
    if (market.resolutionCriteria.toLowerCase().includes(keyword.toLowerCase())) {
      return { id, market };
    }
  }
  return null;
}

// Example: Find the Bitcoin market
const btcMarket = await findMarketByKeyword('bitcoin');
console.log(`Bitcoin market ID: ${btcMarket.id}`);
// Output: Bitcoin market ID: 11

Using deployment.json

For known deployments, market info is pre-cached in deployment.json:

// Fetch deployment info
const resp = await fetch('/deployment.json');
const deployment = await resp.json();

// Access markets directly
deployment.markets.forEach(m => {
  const id = m.id || m.marketId;
  console.log(`ID: ${id}, Question: ${m.criteria}`);
  console.log(`  YES token: ${m.yesPositionId}`);
  console.log(`  NO token:  ${m.noPositionId}`);
});

// Example output:
// ID: 1, Question: GOP retains House in 2026?
//   YES token: 4883681747517307224539...
//   NO token:  1934846715766243280345...

Position Token IDs

Each market has YES and NO position tokens (ERC-1155). Use these for balance checks and transfers:

// Get user's YES token balance for market 1
const market = await registry.get(1);
const yesBalance = await ctf.balanceOf(userAddress, market.yesPositionId);
const noBalance = await ctf.balanceOf(userAddress, market.noPositionId);

console.log(`YES: ${yesBalance}, NO: ${noBalance}`);

Reading Orderbook State

getLevelSnapshots

Get all price levels with liquidity for a given market/position/side.

// Parameters
marketId: uint256    // Market ID (1, 2, 3, ...)
position: uint8      // 0 = YES, 1 = NO
side: uint8          // 0 = BID (buy), 1 = ASK (sell)

// Returns array of:
{
  price: uint256,         // Price in 4 decimals (5000 = $0.50)
  totalRemaining: uint256,// Total size at this price
  lastUpdate: uint64,     // Timestamp of last update
  cumShareSeconds: uint256// Cumulative liquidity-time
}

// Example (ethers.js)
const levels = await orderbook.getLevelSnapshots(1, 0, 0);
// Returns all bid levels for YES position in market 1

orders

Get details of a specific order by ID.

// Returns
{
  maker: address,
  marketId: uint256,
  price: uint256,        // 4 decimals (1 = 0.0001, 10000 = 1.0)
  remaining: uint256,    // Remaining size
  createdAt: uint64,
  lastUpdate: uint64,
  lockUntil: uint64,     // 0 if not locked
  side: uint8,           // 0 = BID, 1 = ASK
  position: uint8,       // 0 = YES, 1 = NO
  feeBps: uint256,
  active: bool,
  cumShareSeconds: uint256
}

// Example
const order = await orderbook.orders(42);

getActivePrices

Get all price levels with active liquidity. Lighter than getLevelSnapshots.

const prices = await orderbook.getActivePrices(
  marketId,  // uint256
  position,  // 0=YES, 1=NO
  side       // 0=BID, 1=ASK
);
// Returns uint256[] of active price levels

getOrdersAtPrice

Get all order IDs at a specific price level.

const orderIds = await orderbook.getOrdersAtPrice(
  marketId,  // uint256
  position,  // 0=YES, 1=NO
  side,      // 0=BID, 1=ASK
  price      // uint256, 4 decimals
);

// Paginated version for busy levels
const { orderIds, total } = await orderbook.getOrdersAtPricePaginated(
  marketId, position, side, price,
  offset,  // uint256
  limit    // uint256
);

priceConfig

Get price precision constants from the contract.

const [decimals, maxPrice] = await orderbook.priceConfig();
// decimals = 4 (0.0001 precision)
// maxPrice = 10000 (1.0000)

getOrderLock

Check if an order is currently locked (cannot be cancelled/amended).

const [locked, lockUntilTimestamp] = await orderbook.getOrderLock(orderId);
// locked: bool - true if block.timestamp < lockUntil
// lockUntilTimestamp: uint64 - unix timestamp when lock expires

Placing Orders

placeBid / placeBidWithLock

Place a buy order. Requires USDC approval first.

// Calculate escrow: notional + fee buffer
// notional = price * size / 1e4 (UNIT)
// fee = notional * feeBps / 10000
const notional = price * size / 10000n;
const fee = notional * 100n / 10000n;  // 100 bps = 1%
const escrow = notional + fee;
await usdc.approve(orderbookAddress, escrow);

// Place bid (buy order)
const tx = await orderbook.placeBid(
  marketId,    // uint256
  price,       // uint256 (4 decimals, e.g. 4500 = $0.45)
  size,        // uint256 (6 decimals, e.g. 100000000 = 100 shares)
  position     // 0 = YES, 1 = NO
);

// With lock (for Valley rewards)
const tx = await orderbook.placeBidWithLock(
  marketId,
  price,
  size,
  position,
  lockSeconds  // Max 1800 (30 minutes)
);

placeAsk / placeAskWithLock

Place a sell order. Requires CTF token approval first.

// Approve CTF tokens first
await ctf.setApprovalForAll(orderbookAddress, true);

// Place ask (sell order)
const tx = await orderbook.placeAsk(
  marketId,
  price,       // 4 decimals
  size,
  position     // 0 = YES, 1 = NO
);

// With lock
const tx = await orderbook.placeAskWithLock(
  marketId, price, size, position, lockSeconds
);

Filling Orders (Taking)

fillBid

Fill a buy order (you are selling). Requires CTF approval.

// You must own the position tokens to sell
await ctf.setApprovalForAll(orderbookAddress, true);

const tx = await orderbook.fillBid(
  orderId,     // uint64 - the bid order to fill
  size         // uint256 - amount to fill
);

fillAsk

Fill a sell order (you are buying). Requires USDC approval.

// Calculate cost (notional + fee, taker pays both)
const order = await orderbook.orders(orderId);
const notional = order.price * size / 10000n;
const fee = notional * order.feeBps / 10000n;
await usdc.approve(orderbookAddress, notional + fee);

const tx = await orderbook.fillAsk(
  orderId,     // uint64 - the ask order to fill
  size         // uint256 - amount to fill
);

Managing Orders

amendOrder

Change the size of an existing order (cannot amend if locked).

await orderbook.amendOrder(
  orderId,     // uint64
  newSize      // uint256 - new remaining size
);

cancelOrder

Cancel all or part of an order (cannot cancel if locked).

// Full cancel (pass the full remaining size)
const order = await orderbook.orders(orderId);
await orderbook.cancelOrder(orderId, order.remaining);

// Partial cancel (must leave remainder >= 10e6 or cancel fully)
await orderbook.cancelOrder(orderId, sizeToCancel);

// Alternative: amendOrder to zero also fully cancels
await orderbook.amendOrder(orderId, 0);

cancelOrderTo

Cancel and withdraw in one call. For asks, sends ERC-1155 positions directly to a recipient.

// Cancel ask and send position tokens to a different address
await orderbook.cancelOrderTo(
  orderId,       // uint64
  sizeToCancel,  // uint256
  recipientAddr  // address - receives the position tokens
);

withdrawResidual

Withdraw leftover USDC escrow from a fully-filled or cancelled bid order.

// After a bid is fully filled, rounding dust may remain in escrow
await orderbook.withdrawResidual(orderId);
// Returns any remaining USDC to the maker

withdrawPosition

Pull-withdraw ERC-1155 position tokens accumulated from ask order amendments/partial cancels.

// When ask orders are amended down or partially cancelled,
// refundable positions accumulate for pull-withdrawal
await orderbook.withdrawPosition(
  orderId,     // uint64 - must be an inactive ask order you own
  toAddress    // address - receives the position tokens
);

getOrdersByMaker

Get all order IDs placed by a specific address. Useful for building a portfolio view.

const orderIds = await orderbook.getOrdersByMaker(userAddress);
// Returns uint64[] of all order IDs (active and inactive)

// Paginated version for large order histories
const { orderIds, total } = await orderbook.getOrdersByMakerPaginated(
  userAddress,
  offset,  // uint256 - skip first N
  limit    // uint256 - max to return
);

Minimum Order Size

All orders must meet the minimum size of 10 shares (10,000,000 in 6-decimal units). Partial cancels/amends must leave at least this much remaining, or cancel fully.

ORDER_FLOOR = 10_000_000  // 10 shares minimum (6 decimals)
LOCK_CEILING = 1800       // Max lock duration: 30 minutes
FEE_CEILING = 1000        // Max fee: 10% (1000 bps)
PRICE_UNIT = 10000        // 1.0000 in 4-decimal format

CTF Operations (Minting & Merging)

Interact directly with the Conditional Token Framework to mint and merge position tokens.

splitPosition (Mint Tokens)

Deposit USDC to mint equal amounts of YES and NO tokens. This is how new positions are created.

// Get market info
const market = await registry.get(marketId);
const ctf = new ethers.Contract(market.ctf, CTF_ABI, signer);
const usdc = new ethers.Contract(market.collateral, ERC20_ABI, signer);

// Amount to mint (in 6 decimals for USDC)
const amount = ethers.parseUnits("100", 6); // 100 USDC

// Approve CTF to spend USDC
await usdc.approve(ctfAddress, amount);

// Split into YES + NO tokens
const partition = [1, 2]; // [YES, NO]
const parentCollectionId = "0x0000000000000000000000000000000000000000000000000000000000000000";

const tx = await ctf.splitPosition(
  market.collateral,    // USDC address
  parentCollectionId,   // bytes32 - always zero for root
  market.conditionId,   // bytes32 - from market struct
  partition,            // uint256[] - [1,2] for YES/NO
  amount                // uint256 - amount in USDC
);
await tx.wait();

// Result: You now have 100 YES tokens + 100 NO tokens

mergePositions (Redeem for USDC)

Burn equal amounts of YES and NO tokens to get USDC back. Works before market resolution.

// Amount to merge (must have equal YES and NO)
const amount = ethers.parseUnits("50", 6); // 50 of each

// Merge back into USDC
const tx = await ctf.mergePositions(
  market.collateral,    // USDC address
  parentCollectionId,   // bytes32 - always zero
  market.conditionId,   // bytes32 - from market
  partition,            // [1, 2]
  amount                // amount to merge
);
await tx.wait();

// Result: Burned 50 YES + 50 NO, received 50 USDC

redeemPositions (After Resolution)

After market resolution, redeem winning tokens for their USDC payout.

// Check if market is resolved
const [resolved, winningOutcome] = await registry.getResolutionStatus(marketId);
if (!resolved) throw new Error("Market not resolved yet");

// Redeem all positions (CTF calculates payout based on winning outcome)
const indexSets = [1, 2]; // Submit both YES and NO

const tx = await ctf.redeemPositions(
  market.collateral,    // USDC address
  parentCollectionId,   // bytes32 - always zero
  market.conditionId,   // bytes32 - from market
  indexSets             // [1, 2] - redeem both outcomes
);
await tx.wait();

// Result: Winning tokens converted to USDC, losing tokens burned

Check Position Balances

CTF tokens are ERC-1155. Check balances using the position IDs from the market.

const ctf = new ethers.Contract(market.ctf, ERC1155_ABI, provider);

// Get position token IDs
const yesPositionId = market.yesPositionId;
const noPositionId = market.noPositionId;

// Check balances
const yesBalance = await ctf.balanceOf(userAddress, yesPositionId);
const noBalance = await ctf.balanceOf(userAddress, noPositionId);

console.log(`YES: ${ethers.formatUnits(yesBalance, 6)}`);
console.log(`NO: ${ethers.formatUnits(noBalance, 6)}`);

CTF ABI (Essential Functions)

const CTF_ABI = [
  "function splitPosition(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)",
  "function mergePositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] partition, uint256 amount)",
  "function redeemPositions(address collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint256[] indexSets)",
  "function balanceOf(address account, uint256 id) view returns (uint256)",
  "function setApprovalForAll(address operator, bool approved)",
  "function isApprovedForAll(address account, address operator) view returns (bool)"
];

Events

Subscribe to these events for real-time updates.

// Order placed
orderbook.on("OrderPlaced", (marketId, orderId, maker, side, position, price, size, createdAt, lockUntil) => {
  console.log(`New order ${orderId} in market ${marketId}`);
});

// Order matched (trade executed)
orderbook.on("TradeFilled", (marketId, bidOrderId, askOrderId, bidMaker, askMaker, taker, position, bidPrice, askPrice, size, notional, feePaid, bidCreatedAt, askCreatedAt) => {
  console.log(`Trade: ${size} contracts at ${askPrice/10000}`);
});

// Order cancelled
orderbook.on("OrderCancelled", (marketId, orderId, side, position, sizeCancelled, sizeRemaining, cancelledAt, reason) => {
  console.log(`Order ${orderId} cancelled`);
});

Quick Start Example

import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);

// ABIs (simplified)
const orderbookAbi = [
  "function placeBid(uint256,uint256,uint256,uint8) returns (uint64)",
  "function getLevelSnapshots(uint256,uint8,uint8) view returns (tuple(uint256,uint256,uint64,uint256)[])"
];

const orderbook = new ethers.Contract(ORDERBOOK_ADDRESS, orderbookAbi, signer);

// Read YES bids for market 1
const bids = await orderbook.getLevelSnapshots(1, 0, 0);
console.log('Bid levels:', bids);

// Place a bid at $0.45 for 100 YES contracts
// price: 4500 (4 decimals = 0.45), size: 100000000 (6 decimals = 100 shares)
const tx = await orderbook.placeBid(1, 4500, 100000000, 0);
await tx.wait();

Gas Costs

View functions are free (no gas). Transactions require gas and modify state.

Function Type Gas
getLevelSnapshots view Free
orders view Free
getOrdersAtPrice view Free
getOrderLock view Free
getActivePrices view Free
getOrdersByMaker view Free
priceConfig view Free
placeBid / placeBidWithLock tx ~150k-200k
placeAsk / placeAskWithLock tx ~150k-200k
fillBid tx ~180k-250k
fillAsk tx ~180k-250k
amendOrder tx ~80k-120k
cancelOrder / cancelOrderTo tx ~100k-150k
withdrawResidual tx ~50k-80k
withdrawPosition tx ~80k-120k
approve (USDC/CTF) tx ~45k

Price & Size Conventions

Field Decimals Example Meaning
price 4 4500 $0.45 per contract
size 6 100000000 100 contracts (1 share = 1e6)
notional (USDC) 6 45000000 $45.00 (price × size / 1e4)
position - 0 or 1 0 = YES, 1 = NO
side - 0 or 1 0 = BID, 1 = ASK

REWARDS

--

MARKET #--
Collateral: --
CTF: --

ORDER BOOK

IMPLIED: -- / --
PRICE SIZE TOTAL
-- SPREAD
PRICE SIZE TOTAL
USDC --
YES --
NO --
50¢ 99¢
Share Cost: $0.0000
Trading Fee: $0.0000
You Pay: $0.0000
EXECUTION PREVIEW
Best Price: --
Avg Price: --
Slippage: --
Total Cost: --
You Receive: --
Min Acceptable: --

MINT POSITION TOKENS

Deposit USDC to mint equal amounts of YES and NO tokens.

You will receive equal YES + NO tokens
YOU WILL RECEIVE
YES Tokens: 0
NO Tokens: 0
USDC Cost: $0.00
How does minting work?

When you mint, you deposit USDC into the Conditional Token Framework (CTF). In return, you receive equal amounts of YES and NO tokens.

This is useful for:

  • Providing liquidity on both sides of the market
  • Selling one side to effectively "buy" the other at a discount
  • Market making strategies

You can later merge YES + NO tokens back into USDC, or sell them individually.

MY ORDERS

ID MARKET SIDE POS PRICE REMAINING LOCKED ACTIONS
No active orders

Portfolio

Your positions across all markets

TOTAL VALUE --
POSITIONS --
MARKETS --
Connect wallet to view portfolio