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
Snapshot
Every 45–90s (randomized), read all resting orders across all 4 books (YES bid/ask, NO bid/ask)
Score
For each qualifying order: size × duration × spread_score. Sum per LP
Pro-rata
Each LP gets (their_score / total_score) × snapshot_reward
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
Track Quotes
Record all quotes and their durations during the epoch
Run Optimizer
Solve convex program to allocate flow optimally
Calculate Shares
Determine each maker's contribution to total volume
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
--
ORDER BOOK
MINT POSITION TOKENS
Deposit USDC to mint equal amounts of YES and NO tokens.
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 |
|---|
Portfolio
Your positions across all markets