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:
rust
// 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#
typescript
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, Pyth | Initialize market & capture opening price |
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_end | Crank | Market, Pyth Feed | Capture closing price |
resolve_market | Crank | Market | Determine outcome |
redeem | 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 | 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 | redeem | RESOLVED | Position exists |
| RESOLVED | close_market | CLOSED | All positions settled |
Error Codes#
rust
#[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#
rust
// 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#
rust
// 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:
rust
#[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