Architecture
Technical deep dive into Seesaw's on-chain architecture. This section is for developers building integrations, tooling, or understanding the protocol internals.
System Overview
Account Model
Account Hierarchy
Account Sizes
| Account | Size (bytes) | Rent (SOL) |
|---|
| Config | 128 | ~0.001 |
| Market | 256 | ~0.002 |
| Orderbook | 8,192 | ~0.059 |
| Vault | 165 | ~0.002 |
| Position | 96 | ~0.001 |
PDA Derivation
All accounts use deterministic Program Derived Addresses:
// Config (singleton)
seeds = ["seesaw", "config"]
// Market
seeds = ["seesaw", "market", market_id.to_le_bytes()]
// Orderbook
seeds = ["seesaw", "orderbook", market.key()]
// Vault
seeds = ["seesaw", "vault", market.key()]
// Position
seeds = ["seesaw", "position", market.key(), user.key()]
Derivation Example
import { PublicKey } from '@solana/web3.js';
const PROGRAM_ID = new PublicKey('Sesaw...');
function deriveMarketPDA(marketId: bigint): [PublicKey, number] {
const buffer = Buffer.alloc(8);
buffer.writeBigUInt64LE(marketId);
return PublicKey.findProgramAddressSync(
[Buffer.from('seesaw'), Buffer.from('market'), buffer],
PROGRAM_ID
);
}
function derivePositionPDA(market: PublicKey, user: PublicKey): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from('seesaw'), Buffer.from('position'), market.toBuffer(), user.toBuffer()],
PROGRAM_ID
);
}
Instructions
Instruction Overview
Instruction Details
| Instruction | Signer | Key Accounts | Description |
|---|
create_market | Payer | Config, Market, Orderbook, Vault | Initialize new market |
place_order | User | Market, Orderbook, Position, User Token | Submit limit order |
cancel_order | User | Market, Orderbook, Position, Order | Cancel open order |
mint_shares | User | Market, Vault, Position, User Token | Deposit USDC for shares |
snapshot_start | Crank | Market, Pyth Feed | Capture opening price |
snapshot_end | Crank | Market, Pyth Feed | Capture closing price |
resolve_market | Crank | Market | Determine outcome |
settle_position | User | Market, Vault, Position, User Token | Claim winnings |
withdraw_shares | User | Market, Vault, Position, User Token | Withdraw unused shares |
close_market | Crank | Market, Orderbook, Vault | Reclaim rent |
State Transitions
Market State Machine
Valid Transitions
| Current State | Instruction | Next State | Conditions |
|---|
| (none) | create_market | CREATED | t >= t_start - 24h |
| CREATED | snapshot_start | TRADING | t >= t_start, Pyth valid |
| TRADING | place_order | TRADING | Order valid |
| TRADING | cancel_order | TRADING | Order exists, owner matches |
| TRADING | snapshot_end | SETTLING | t >= t_end, Pyth valid |
| SETTLING | resolve_market | RESOLVED | Both snapshots exist |
| RESOLVED | settle_position | RESOLVED | Position exists |
| RESOLVED | close_market | CLOSED | All positions settled |
Error Codes
#[error_code]
pub enum SeesawError {
#[msg("Market is not in the correct state for this operation")]
InvalidMarketState, // 6000
#[msg("Order price is out of valid range")]
PriceOutOfRange, // 6001
#[msg("Insufficient shares for this operation")]
InsufficientShares, // 6002
#[msg("Insufficient collateral for this operation")]
InsufficientCollateral, // 6003
#[msg("Oracle price timestamp is invalid")]
InvalidOracleTimestamp, // 6004
#[msg("Oracle price is non-positive")]
InvalidOraclePrice, // 6005
#[msg("Oracle confidence exceeds threshold")]
OracleConfidenceTooHigh, // 6006
#[msg("Snapshot already captured")]
SnapshotAlreadyCaptured, // 6007
#[msg("Market not yet resolved")]
MarketNotResolved, // 6008
#[msg("Position already settled")]
PositionAlreadySettled, // 6009
#[msg("Order not found")]
OrderNotFound, // 6010
#[msg("Unauthorized signer")]
Unauthorized, // 6011
#[msg("Arithmetic overflow")]
MathOverflow, // 6012
}
Security Model
Access Control
Invariants Enforced
| Invariant | Description | Enforcement |
|---|
| Solvency | Vault >= max(yes, no) | Checked on every trade |
| No Crossed Book | Best bid < best ask | Matching engine |
| No Negative Shares | Shares >= 0 | Checked on sell/settle |
| Immutable Snapshots | Once set, never change | State check |
| Deterministic Resolution | Same inputs = same output | Pure logic |
CPI (Cross-Program Invocation)
Token Transfers
// Transfer USDC from user to vault
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.user_token_account.to_account_info(),
to: ctx.accounts.vault.to_account_info(),
authority: ctx.accounts.user.to_account_info(),
},
),
amount,
)?;
Pyth Oracle Read
// Read price from Pyth
let price_feed = &ctx.accounts.pyth_price_feed;
let price_data = price_feed.get_price_unchecked();
require!(
price_data.publish_time >= boundary_time,
SeesawError::InvalidOracleTimestamp
);
require!(
price_data.price > 0,
SeesawError::InvalidOraclePrice
);
Event Emission
Events for indexers and UIs:
#[event]
pub struct MarketCreated {
pub market_id: u64,
pub pyth_feed: Pubkey,
pub start_time: i64,
pub end_time: i64,
}
#[event]
pub struct OrderPlaced {
pub market: Pubkey,
pub user: Pubkey,
pub side: OrderSide,
pub price: u16,
pub quantity: u64,
pub order_id: u64,
}
#[event]
pub struct Trade {
pub market: Pubkey,
pub maker: Pubkey,
pub taker: Pubkey,
pub price: u16,
pub quantity: u64,
pub maker_fee: i64, // Negative = rebate
pub taker_fee: u64,
}
#[event]
pub struct MarketResolved {
pub market: Pubkey,
pub outcome: Outcome,
pub start_price: i64,
pub end_price: i64,
}
Related Documentation
- Accounts - Detailed account layouts
- Instructions - Full instruction reference
- PDAs - PDA derivation guide
- Security - Security considerations