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:
typescript
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 | 86,400 seconds | Markets can be created 24 hrs early |
EXPIRY_WINDOW | 86,400 seconds | 24 hours until force close available |
MAX_SNAPSHOT_DELAY | 3600 seconds | 1 hour grace period for snapshots |
Calculating Market Timing#
typescript
// 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#
typescript
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#
typescript
// 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#
typescript
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#
typescript
const MAX_ORDERS_PER_SIDE = 63; // 63 bids + 63 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 | 63 | 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#
typescript
// 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#
typescript
const TAKER_FEE_BPS = 200; // 2.0% (configurable)
const PROTOCOL_FEE_BPS = 150; // 1.5% — portion of taker fee to treasury
const CREATOR_FEE_BPS = 50; // 0.5% — portion of taker fee to market creator
const MAX_TAKER_FEE_BPS = 500; // Maximum: 5%
| Constant | Value | Description |
|---|---|---|
TAKER_FEE_BPS | 200 | 2.0% default taker fee (configurable) |
PROTOCOL_FEE_BPS | 150 | 1.5% to protocol treasury |
CREATOR_FEE_BPS | 50 | 0.5% to market creator |
MAX_TAKER_FEE_BPS | 500 | Maximum allowed taker fee |
Makers pay zero fees. The taker fee is split between the protocol treasury and the market creator.
Fee Calculations#
typescript
// 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
}
// Example: 1000 USDC trade at 2.0% taker fee
const tradeAmount = 1000_000_000n; // 1000 USDC (6 decimals)
const takerFee = calculateTakerFee(tradeAmount, TAKER_FEE_BPS); // 20_000_000 (20 USDC)
// Protocol receives: 15 USDC (1.5%)
// Creator receives: 5 USDC (0.5%)
Crank Rewards#
typescript
const DEFAULT_CLOSER_REWARD_LAMPORTS = 5_000; // 0.000005 SOL
const CLOSER_REWARDS_PER_MARKET = 2; // Two rewards per market lifecycle
Closer rewards are paid from the market creator's deposited SOL when lifecycle operations are cranked.
| Operation | Reward |
|---|---|
snapshot_end | 0.000005 SOL |
resolve_market | 0.000005 SOL |
Account Sizes#
Storage Requirements#
typescript
const MARKET_ACCOUNT_SIZE = 512;
const ORDERBOOK_ACCOUNT_SIZE = 10240;
const POSITION_ACCOUNT_SIZE = 256;
const CONFIG_ACCOUNT_SIZE = 256;
| Account | Size (bytes) | Description |
|---|---|---|
MarketAccount | 512 | Market state |
OrderbookAccount | 10,240 | Order storage (63+63 orders) |
PositionAccount | 256 | User position |
ConfigAccount | 256 | Protocol config |
Rent Costs#
typescript
// 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#
typescript
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#
typescript
import { PublicKey } from '@solana/web3.js';
// Config PDA
function deriveConfigPda(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 deriveMarketPda(
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 deriveOrderbookPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from(SEED_PREFIX), Buffer.from(SEED_ORDERBOOK), marketPda.toBuffer()],
programId
);
}
// Vault PDA
function deriveVaultPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from(SEED_PREFIX), Buffer.from(SEED_VAULT), marketPda.toBuffer()],
programId
);
}
// Position PDA
function derivePositionPda(
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#
typescript
const DISCRIMINATORS = {
INITIALIZE_CONFIG: 0x00,
CREATE_MARKET: 0x01,
SNAPSHOT_END: 0x02,
RESOLVE_MARKET: 0x03,
EXPIRE_MARKET: 0x04,
PLACE_ORDER: 0x05,
CANCEL_ORDER: 0x06,
MINT_SHARES: 0x07,
REDEEM: 0x08,
WITHDRAW_SHARES: 0x09,
PAUSE: 0x0a,
UNPAUSE: 0x0b,
UPDATE_FEES: 0x0c,
UPDATE_TICK_SIZE: 0x0d,
FORCE_CLOSE: 0x0e,
CLOSE_MARKET: 0x0f,
} as const;
| Instruction | Discriminator |
|---|---|
initialize_config | 0x00 |
create_market | 0x01 |
snapshot_end | 0x02 |
resolve_market | 0x03 |
expire_market | 0x04 |
place_order | 0x05 |
cancel_order | 0x06 |
mint_shares | 0x07 |
redeem | 0x08 |
withdraw_shares | 0x09 |
pause | 0x0A |
unpause | 0x0B |
update_fees | 0x0C |
update_tick_size | 0x0D |
force_close | 0x0E |
close_market | 0x0F |
Oracle Constants#
Pyth Configuration#
typescript
// 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#
typescript
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,
REDEEM: 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#
typescript
// 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: 86,400s MIN_PRICE: 1 bps
EXPIRY: 86,400s MAX_PRICE: 9,999 bps
DEFAULT_TICK: 100 bps
LIMITS FEES
──────────────────────── ────────────────────────
MAX_ORDERS/SIDE: 63 TAKER_FEE: 200 bps (2.0%)
MAX_FILLS/TX: 10 PROTOCOL_FEE: 150 bps (1.5%)
CREATOR_FEE: 50 bps (0.5%)
CLOSER_REWARD: 5,000 lamports
ACCOUNTS PDA SEEDS
──────────────────────── ────────────────────────
Market: 512B Config: [seesaw, config]
Orderbook: 10,240B Market: [seesaw, market, feed, dur, id, creator]
Position: 256B Orderbook:[seesaw, orderbook, mkt]
Config: 256B Position: [seesaw, position, mkt, user]
Next Steps#
- Review Error Codes for troubleshooting
- See Glossary for terminology