Constants
Protocol constants and configuration values for the Seesaw protocol.
Overview
Time Constants
Market Duration
Market durations are configurable per-market, from 60 seconds to 7 days:
const MIN_DURATION_SECONDS = 60; // 1 minute
const MAX_DURATION_SECONDS = 604_800; // 7 days
const DEFAULT_DURATION_SECONDS = 900; // 15 minutes
Market Timing
| Constant | Value | Description |
|---|
MIN_DURATION | 60 seconds | Minimum market duration |
MAX_DURATION | 604,800 seconds | Maximum market duration (7 days) |
DEFAULT_DURATION | 900 seconds | Default market duration (15 min) |
PRE_CREATE_WINDOW | 300 seconds | Markets can be created 5 min early |
EXPIRY_WINDOW | 604800 seconds | 7 days until force close available |
MAX_SNAPSHOT_DELAY | 3600 seconds | 1 hour grace period for snapshots |
Calculating Market Timing
// Market ID from timestamp and duration
function getMarketId(timestamp: number, durationSeconds: number): bigint {
return BigInt(Math.floor(timestamp / durationSeconds));
}
// Market boundaries
function getMarketTimes(
marketId: bigint,
durationSeconds: number
): { tStart: number; tEnd: number } {
const tStart = Number(marketId) * durationSeconds;
const tEnd = tStart + durationSeconds;
return { tStart, tEnd };
}
// Current market ID (for a 15-minute market)
const now = Math.floor(Date.now() / 1000);
const currentMarketId = getMarketId(now, 900); // e.g., 1892160n
Price Constants
Basis Points
const PRICE_SCALE = 10_000; // 100% = 10,000 bps
const MIN_PRICE_BPS = 1; // 0.01%
const MAX_PRICE_BPS = 9_999; // 99.99%
const DEFAULT_TICK_SIZE_BPS = 100; // 1%
Price Calculations
| Constant | Value | Description |
|---|
PRICE_SCALE | 10,000 | 100% in basis points |
MIN_PRICE_BPS | 1 | Minimum valid price (0.01%) |
MAX_PRICE_BPS | 9,999 | Maximum valid price (99.99%) |
DEFAULT_TICK_SIZE_BPS | 100 | Default tick (1%) |
MAX_TICK_SIZE_BPS | 1,000 | Maximum tick size (10%) |
Converting Prices
// Basis points to percentage
function bpsToPercent(bps: number): number {
return bps / 100; // 6000 bps = 60%
}
// Percentage to basis points
function percentToBps(percent: number): number {
return percent * 100; // 60% = 6000 bps
}
// Basis points to decimal
function bpsToDecimal(bps: number): number {
return bps / PRICE_SCALE; // 6000 bps = 0.60
}
// NO price from YES price
function noFromYes(yesPriceBps: number): number {
return PRICE_SCALE - yesPriceBps; // 6000 -> 4000
}
Tick Rounding
const TICK_SIZE = 100; // 1%
// Round bid down
function roundBid(priceBps: number): number {
return Math.floor(priceBps / TICK_SIZE) * TICK_SIZE;
}
// Round ask up
function roundAsk(priceBps: number): number {
const remainder = priceBps % TICK_SIZE;
return remainder === 0 ? priceBps : priceBps + (TICK_SIZE - remainder);
}
// Examples
roundBid(6050); // 6000
roundAsk(6050); // 6100
Order Book Limits
Capacity
const MAX_ORDERS_PER_SIDE = 64; // 64 bids + 64 asks
const MAX_FILLS_PER_TX = 10; // Compute budget limit
const ORDER_SIZE_BYTES = 48; // Per order storage
| Constant | Value | Description |
|---|
MAX_ORDERS_PER_SIDE | 64 | Maximum orders per side |
MAX_FILLS_PER_TX | 10 | Maximum fills per transaction |
MIN_ORDER_QUANTITY | 1 | Minimum order quantity |
MAX_ORDER_QUANTITY | 2^64-1 | Maximum order quantity |
Order ID
// Order IDs are monotonically increasing per orderbook
const ORDER_ID_BITS = 64;
const MAX_ORDER_ID = BigInt(2) ** BigInt(64) - BigInt(1);
Fee Constants
Trading Fees
const TAKER_FEE_BPS = 30; // 0.3%
const MAKER_REBATE_BPS = 10; // 0.1%
const NET_PROTOCOL_FEE_BPS = 20; // 0.2%
const MAX_TAKER_FEE_BPS = 500; // Maximum: 5%
| Constant | Value | Description |
|---|
TAKER_FEE_BPS | 30 | 0.3% taker fee |
MAKER_REBATE_BPS | 10 | 0.1% maker rebate |
NET_PROTOCOL_FEE | 20 | Net fee to protocol |
MAX_TAKER_FEE_BPS | 500 | Maximum allowed taker fee |
Fee Calculations
// Calculate taker fee (rounds UP)
function calculateTakerFee(amount: bigint, feeBps: number): bigint {
const fee = amount * BigInt(feeBps);
return (fee + BigInt(9999)) / BigInt(10000); // Ceiling division
}
// Calculate maker rebate (rounds DOWN)
function calculateMakerRebate(amount: bigint, rebateBps: number): bigint {
return (amount * BigInt(rebateBps)) / BigInt(10000); // Floor division
}
// Example: 1000 USDC trade
const tradeAmount = 1000_000_000n; // 1000 USDC (6 decimals)
const takerFee = calculateTakerFee(tradeAmount, TAKER_FEE_BPS); // 3_000_000
const makerRebate = calculateMakerRebate(tradeAmount, MAKER_REBATE_BPS); // 1_000_000
Crank Rewards
const CRANK_REWARD_LAMPORTS = 1_000_000; // 0.001 SOL
const CRANK_REWARD_SOL = 0.001;
| Operation | Reward |
|---|
create_market | 0.001 SOL |
snapshot_start | 0.001 SOL |
snapshot_end | 0.001 SOL |
resolve_market | 0.001 SOL |
settle_position | 0.001 SOL |
close_market | 0.001 SOL |
Account Sizes
Storage Requirements
const MARKET_ACCOUNT_SIZE = 256;
const ORDERBOOK_ACCOUNT_SIZE = 10240;
const POSITION_ACCOUNT_SIZE = 192;
const CONFIG_ACCOUNT_SIZE = 128;
| Account | Size (bytes) | Description |
|---|
MarketAccount | 256 | Market state |
OrderbookAccount | 8,192 | Order storage (64+64 orders) |
PositionAccount | 192 | User position |
ConfigAccount | 128 | Protocol config |
Rent Costs
// Approximate rent-exempt minimums (varies by Solana runtime)
const MARKET_RENT_LAMPORTS = 2_600_000; // ~0.0026 SOL
const ORDERBOOK_RENT_LAMPORTS = 57_000_000; // ~0.057 SOL
const POSITION_RENT_LAMPORTS = 1_900_000; // ~0.0019 SOL
PDA Seeds
Seed Constants
const SEED_PREFIX = 'seesaw';
const SEED_CONFIG = 'config';
const SEED_MARKET = 'market';
const SEED_ORDERBOOK = 'orderbook';
const SEED_VAULT = 'vault';
const SEED_POSITION = 'position';
PDA Derivation
import { PublicKey } from '@solana/web3.js';
// Config PDA
function findConfigPda(programId: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from(SEED_PREFIX), Buffer.from(SEED_CONFIG)],
programId
);
}
// Market PDA (includes feed, duration, market ID, and creator)
function findMarketPda(
pythFeedId: Uint8Array, // 32-byte feed ID
durationSeconds: bigint,
marketId: bigint,
creatorPubkey: PublicKey,
programId: PublicKey
): [PublicKey, number] {
const durationBuffer = Buffer.alloc(8);
durationBuffer.writeBigUInt64LE(durationSeconds);
const marketIdBuffer = Buffer.alloc(8);
marketIdBuffer.writeBigUInt64LE(marketId);
return PublicKey.findProgramAddressSync(
[
Buffer.from(SEED_PREFIX),
Buffer.from(SEED_MARKET),
pythFeedId,
durationBuffer,
marketIdBuffer,
creatorPubkey.toBuffer(),
],
programId
);
}
// Orderbook PDA
function findOrderbookPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from(SEED_PREFIX), Buffer.from(SEED_ORDERBOOK), marketPda.toBuffer()],
programId
);
}
// Vault PDA
function findVaultPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from(SEED_PREFIX), Buffer.from(SEED_VAULT), marketPda.toBuffer()],
programId
);
}
// Position PDA
function findPositionPda(
marketPda: PublicKey,
userPubkey: PublicKey,
programId: PublicKey
): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[
Buffer.from(SEED_PREFIX),
Buffer.from(SEED_POSITION),
marketPda.toBuffer(),
userPubkey.toBuffer(),
],
programId
);
}
Instruction Discriminators
Discriminator Values
const DISCRIMINATORS = {
INITIALIZE_CONFIG: 0x00,
CREATE_MARKET: 0x01,
PLACE_ORDER: 0x04,
RESOLVE_MARKET: 0x06,
SETTLE_POSITION: 0x07,
CLOSE_MARKET: 0x08,
FORCE_CLOSE: 0x09,
EXPIRE_MARKET: 0x0c,
} as const;
| Instruction | Discriminator |
|---|
initialize_config | 0x00 |
create_market | 0x01 |
place_order | 0x04 |
resolve_market | 0x06 |
settle_position | 0x07 |
close_market | 0x08 |
force_close | 0x09 |
expire_market | 0x0C |
Oracle Constants
Pyth Configuration
// Pyth program ID (mainnet)
const PYTH_PROGRAM_ID = new PublicKey('FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH');
// Common feed IDs
const PYTH_FEEDS = {
BTC_USD: 'e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
ETH_USD: 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
SOL_USD: 'ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d',
};
// Confidence ratio (optional gating)
const MAX_CONFIDENCE_RATIO = 100; // 1% = conf/price <= 0.01
Compute Budget
CU Estimates
const COMPUTE_ESTIMATES = {
PLACE_ORDER_NO_MATCH: 15_000,
PLACE_ORDER_ONE_FILL: 25_000,
PLACE_ORDER_MAX_FILLS: 60_000,
CANCEL_ORDER: 10_000,
SETTLE_POSITION: 20_000,
RESOLVE_MARKET: 30_000,
};
// Recommended CU limits
const RECOMMENDED_CU = {
SIMPLE_OPERATION: 100_000,
COMPLEX_OPERATION: 200_000,
MAX_FILLS_OPERATION: 400_000,
};
Network Constants
RPC Endpoints
// Public endpoints
const RPC_ENDPOINTS = {
MAINNET: 'https://api.mainnet-beta.solana.com',
DEVNET: 'https://api.devnet.solana.com',
};
// WebSocket endpoints
const WS_ENDPOINTS = {
MAINNET: 'wss://api.mainnet-beta.solana.com',
DEVNET: 'wss://api.devnet.solana.com',
};
Quick Reference
TIME PRICES
──────────────────────── ────────────────────────
DEFAULT_DURATION: 900s PRICE_SCALE: 10,000
PRE_CREATE: 300s MIN_PRICE: 1 bps
EXPIRY: 604,800s MAX_PRICE: 9,999 bps
DEFAULT_TICK: 100 bps
LIMITS FEES
──────────────────────── ────────────────────────
MAX_ORDERS/SIDE: 64 TAKER_FEE: 30 bps
MAX_FILLS/TX: 10 MAKER_REBATE: 10 bps
CRANK_REWARD: 0.001 SOL
ACCOUNTS PDA SEEDS
──────────────────────── ────────────────────────
Market: 256B Config: [seesaw, config]
Orderbook: 8,192B Market: [seesaw, market, feed, dur, id, creator]
Position: 192B Orderbook:[seesaw, orderbook, mkt]
Config: 128B Position: [seesaw, position, mkt, user]
Next Steps
- Review Error Codes for troubleshooting
- See Glossary for terminology