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 up to 24 hrs before epoch start |
EXPIRY_WINDOW (MARKET_EXPIRY_WINDOW) | 604,800 seconds | 7 days after epoch end before force-close is available |
CLOSE_TIMEOUT_SECONDS | 604,800 seconds | 7 days after resolution before CloseMarket is eligible |
REFERRAL_DURATION_SECONDS | 31,536,000 s | 365-day referral lifetime (first-touch attribution) |
FEE_CONFIG_RATE_LIMIT_SECONDS | 3,600 seconds | Min seconds between UpdateFeeConfig calls |
TREASURY_UPDATE_RATE_LIMIT_SECONDS | 3,600 seconds | Min seconds between UpdateTreasuryRecipients calls |
OPERATIONAL_PARAMS_RATE_LIMIT_SECONDS | 3,600 seconds | Min seconds between UpdateOperationalParams calls |
ORACLE_TIMELOCK_SECONDS | 172,800 seconds | 48-hour timelock for Pyth program-ID governance |
Worked examples: market ID and 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%) |
MAKER_BAND_BPS | 1,000 | Max maker order distance from mid (±10%) |
STATIC_MIN_BPS_FOR_BAND | 100 | Static lower bound on resting maker price (1%) |
STATIC_MAX_BPS_FOR_BAND | 9,900 | Static upper bound on resting maker price (99%) |
DEFAULT_MIN_RESTING_NOTIONAL | 2 | Minimum notional for a resting order remainder (base units) |
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 = 80; // 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#
Fee Curve Parameters#
Seesaw uses a capped-linear-decay taker fee curve:
code
fee_bps(p) = min(fee_cap_bps, decay_rate_bps × (10_000 − p) / 10_000)
typescript
const FEE_CAP_BPS = 200; // 2.00% — maximum taker fee
const DECAY_RATE_BPS = 600; // 6.00% — curve slope
const MAX_FEE_CAP_BPS = 500; // Admin ceiling: fee_cap_bps <= 500
| Constant | Value | Description |
|---|---|---|
FEE_CAP_BPS | 200 | Fee curve ceiling (2.00% of notional) |
DECAY_RATE_BPS | 600 | Decay slope: fee → 0 as price → 1.00 |
MAX_FEE_CAP_BPS | 500 | Admin-enforced upper bound on fee_cap_bps |
Three-Way Fee Split#
Every taker fee is split three ways (as bps of the fee, summing to 10_000):
typescript
const PROTOCOL_FEE_BPS = 5_000; // 50% of taker fee → protocol treasury
const DEFAULT_CREATOR_FEE_BPS = 1_000; // 10% of taker fee → market creator
const REFERRAL_SHARE_BPS_OF_FEE = 4_000; // 40% of taker fee → referrer (or protocol if none)
| Constant | Value | Description |
|---|---|---|
PROTOCOL_FEE_BPS | 5_000 | Protocol share (50% of taker fee) |
DEFAULT_CREATOR_FEE_BPS | 1_000 | Creator share (10% of taker fee) |
REFERRAL_SHARE_BPS_OF_FEE | 4_000 | Referral share (40% of taker fee) |
The three shares MUST sum to exactly 10_000 bps of the fee. Makers pay zero.
Referral Lifetime#
typescript
const REFERRAL_LIFETIME_SECONDS = 365 * 86_400; // 365 days (first-touch attribution)
Fee Calculations#
typescript
// Capped-linear-decay taker fee
function calculateTakerFee(
notional: bigint,
priceBps: number,
feeCapBps: number = FEE_CAP_BPS,
decayRateBps: number = DECAY_RATE_BPS
): bigint {
// Raw curve value in bps of notional
const raw = (BigInt(decayRateBps) * BigInt(10_000 - priceBps)) / BigInt(10_000);
const feeBps = raw < BigInt(feeCapBps) ? raw : BigInt(feeCapBps);
// Round UP (protocol-favorable)
return (notional * feeBps + BigInt(9_999)) / BigInt(10_000);
}
// Example: 1000 USDT notional at p = 0.50
const notional = 1000_000_000n; // 1000 USDT (6 decimals)
const priceBps = 5000;
const takerFee = calculateTakerFee(notional, priceBps);
// → fee_bps = min(200, 600 × 5000 / 10000) = 200 (capped) → 20_000_000 (20 USDT)
// Protocol receives: 10 USDT (50% of fee)
// Creator receives: 2 USDT (10% of fee)
// Referral receives: 8 USDT (40% of fee) — to referrer if set, else protocol
// Example: 1000 USDT notional at p = 0.95
const takerFee95 = calculateTakerFee(notional, 9500);
// → fee_bps = min(200, 600 × 500 / 10000) = 30 → 3_000_000 (3 USDT)
Crank Rewards#
typescript
const DEFAULT_CLOSER_REWARD_LAMPORTS = 200_000; // 0.0002 SOL
const CLOSER_REWARDS_PER_MARKET = 5; // Rewards reserved per market lifecycle
Closer rewards are paid from the market creator's deposited SOL when lifecycle operations are cranked.
| Operation | Reward |
|---|---|
snapshot_end | 0.0002 SOL |
resolve_market | 0.0002 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 = 624; // After all phases incl. pull-oracle governance
| Account | Size (bytes) | Description |
|---|---|---|
MarketAccount | 512 | Market state |
OrderbookAccount | 10,240 | Order storage (canonical 63+63 orders) |
PositionAccount | 256 | User position |
ConfigAccount | 624 | Protocol config (post pull-oracle phase) |
TraderLedgerAccount | 56 + num_seats × 72 | Per-market trader balance ledger |
ReferrerEarningsAccount | 72 | Per-referrer earnings ledger |
ReferralAccount | 104 | Per-user referral attribution |
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,
UPDATE_AUTHORITY: 0x01,
CLAIM_AUTHORITY: 0x02,
CREATE_MARKET: 0x03,
SNAPSHOT_END: 0x04,
RESOLVE_MARKET: 0x05,
EXPIRE_MARKET: 0x06,
MINT_SHARES: 0x07,
DEPOSIT_FUNDS: 0x08,
WITHDRAW_FUNDS: 0x09,
WITHDRAW_SHARES: 0x0a,
PLACE_ORDER: 0x0b,
PLACE_MULTIPLE_POST_ONLY_ORDERS: 0x0c,
SWAP_WITH_FREE_FUNDS: 0x0d,
PLACE_LIMIT_ORDER_WITH_FREE_FUNDS: 0x0e,
PLACE_MULTIPLE_POST_ONLY_ORDERS_WITH_FREE_FUNDS: 0x0f,
CANCEL_ORDER: 0x10,
CANCEL_MULTIPLE_ORDERS_BY_ID: 0x11,
CANCEL_ALL_ORDERS: 0x12,
CANCEL_UP_TO: 0x13,
REDUCE_ORDER: 0x14,
CANCEL_MULTIPLE_ORDERS_BY_ID_WITH_FREE_FUNDS: 0x15,
CANCEL_ALL_ORDERS_WITH_FREE_FUNDS: 0x16,
CANCEL_UP_TO_WITH_FREE_FUNDS: 0x17,
REDUCE_ORDER_WITH_FREE_FUNDS: 0x18,
RECLAIM_EXPIRED_ORDER: 0x19,
REDEEM: 0x1a,
FORCE_CLOSE: 0x1b,
MARK_POSITION_SETTLED: 0x1c,
CLOSE_POSITION: 0x1d,
CLOSE_MARKET: 0x1e,
UPDATE_FEE_CONFIG: 0x1f,
UPDATE_TREASURY_RECIPIENTS: 0x20,
SET_REFERRER: 0x21,
INIT_REFERRER_EARNINGS_ACCOUNT: 0x22,
CLAIM_CREATOR_FEES: 0x23,
CLAIM_REFERRER_EARNINGS: 0x24,
PAUSE: 0x25,
UNPAUSE: 0x26,
ENABLE_POST_ONLY_MODE: 0x27,
DISABLE_POST_ONLY_MODE: 0x28,
UPDATE_TICK_SIZE: 0x29,
UPDATE_MIN_RESTING_NOTIONAL: 0x2a,
UPDATE_MARKET_CAP: 0x2b,
SET_MARKET_EMERGENCY_STATUS: 0x2c,
FORCE_CANCEL_MARKET_ORDERS: 0x2d,
ENSURE_TRADER_LEDGER_SPACE: 0x2e,
UPDATE_OPERATIONAL_PARAMS: 0x2f,
PROPOSE_PYTH_PROGRAM_ID: 0x30,
APPLY_PYTH_PROGRAM_ID: 0x31,
UPDATE_MARKET_DEFAULTS: 0x32,
LOG: 0xff,
} as const;
Public instructions are dense across 0x00..=0x32 (51 instructions). LOG = 0xFF
is the internal binary recorder self-CPI tag.
Oracle Constants#
Pyth Configuration#
typescript
// Pyth sponsored price-feed program ID (mainnet, upgraded Pyth Core)
const PYTH_PROGRAM_ID = new PublicKey('pyt2F414BA6dPttK6RddPZUdHfapoBN24GL5wbrPCou');
// 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_WINDOW: 604,800s MAX_PRICE: 9,999 bps
CLOSE_TIMEOUT: 604,800s DEFAULT_TICK: 100 bps
ORACLE_TIMELOCK: 172,800s MAKER_BAND: ±1,000 bps from mid
RATE_LIMIT: 3,600s STATIC_MIN_BAND: 100 bps
STATIC_MAX_BAND: 9,900 bps
LIMITS FEES (capped-linear-decay)
──────────────────────── ──────────────────────────────────────
MAX_ORDERS/SIDE: 63 FEE_CAP: 200 bps (2.00% cap)
MAX_FILLS/TX: 10 DECAY_RATE: 600 bps (slope)
DEFAULT_MAX_ORDER: 1,000,000 PROTOCOL_SHARE: 50% of fee
MIN_RESTING_NOTIONAL: 2 CREATOR_SHARE: 10% of fee
REFERRAL_SHARE: 40% of fee
MAKER_FEE: 0
CLOSER_REWARD: 200,000 lamports
REWARDS/MARKET: 5
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: 624B Vault: [seesaw, vault, mkt]
Ledger: 56+seats×72B YES Mint: [seesaw, yes_mint, mkt]
ReferrerEarn: 72B NO Mint: [seesaw, no_mint, mkt]
ReferralAcct: 104B YES Escrow: [seesaw, escrow_yes, mkt]
NO Escrow: [seesaw, escrow_no, mkt]
TraderLedger: [seesaw, trader_ledger, mkt]
Position: [seesaw, position, mkt, user]
ReferrerTreasury: [seesaw, referrer_treasury, shard_index]
Referral: [seesaw, referral, user]
ReferrerEarnings: [seesaw, referrer_earnings, referrer]
Next Steps#
- Review Error Codes for troubleshooting
- See Glossary for terminology