Architecture#
Technical reference for the Seesaw on-chain program — account layouts, instruction set, PDA derivation, and the security model, for developers building integrations or tooling.
System Overview#
Account Model#
Account Hierarchy#
Account Sizes#
| Account | Size (bytes) | Rent (SOL) |
|---|---|---|
| Config | 624 | ~0.004 |
| Market | 512 | ~0.004 |
| Orderbook | 10,240 | ~0.072 |
| Vault (SPL token acct) | 165 | ~0.002 |
| Position | 256 | ~0.002 |
| TraderLedger | 56 + num_seats × 72 | Variable |
| AssetState | 128 | ~0.001 |
| ReferralAccount | 104 | ~0.001 |
| ReferrerEarningsAccount | 72 | ~0.001 |
PDA Derivation#
All accounts use deterministic Program Derived Addresses:
// Config (singleton)
seeds = ["seesaw", "config"]
// Market — multiple concurrent markets per asset, duration, and creator
seeds = ["seesaw", "market", pyth_feed_id, duration_seconds.to_le_bytes(), market_id.to_le_bytes(), creator.key()]
// Orderbook
seeds = ["seesaw", "orderbook", market.key()]
// Vault
seeds = ["seesaw", "vault", market.key()]
// Position
seeds = ["seesaw", "position", market.key(), user.key()]
Derivation Example#
Note: The Seesaw SDKs (
@seesaw/core,@seesaw/trustless) use Kit v6 types (Addressstrings) rather than@solana/web3.js. For SDK-based derivation see ../sdk/README.md. The snippet below is a low-level reference implementation.
import { PublicKey } from '@solana/web3.js';
const PROGRAM_ID = new PublicKey('SEEsawgSrxRsgtKRbaThZFEKrVqX3Y64hDipTWyi8F8');
// A market is identified by its feed, duration, epoch id, and creator, so
// multiple durations can run concurrently for the same asset.
function deriveMarketPDA(
pythFeedId: Uint8Array, // 32 bytes
durationSeconds: bigint,
marketId: bigint,
creator: PublicKey
): [PublicKey, number] {
const duration = Buffer.alloc(8);
duration.writeBigUInt64LE(durationSeconds);
const id = Buffer.alloc(8);
id.writeBigUInt64LE(marketId);
return PublicKey.findProgramAddressSync(
[
Buffer.from('seesaw'),
Buffer.from('market'),
Buffer.from(pythFeedId),
duration,
id,
creator.toBuffer(),
],
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 USDT 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#
Errors are a custom #[repr(u32)] enum that surfaces as ProgramError::Custom(code).
Codes are category-prefixed hex values (not the Anchor 6000+ range). The full,
authoritative list is in Error Codes.
#[repr(u32)]
pub enum SeesawError {
// Market 0x1001+
MarketExists = 0x1001,
InvalidState = 0x1002,
AlreadyResolved = 0x1005,
// Oracle 0x2001+
OracleMismatch = 0x2001,
StaleOracle = 0x2002,
InvalidPrice = 0x2003,
FeedIdMismatch = 0x2005,
// Order 0x3001+
InvalidQuantity = 0x3001,
WouldCross = 0x3004,
SlippageExceeded = 0x3007,
// Math / Account / Token / Fee / Authority
MathOverflow = 0x4001,
InvalidAccountType = 0x5001,
InvalidPDA = 0x5003,
InsolvencyDetected = 0x5005,
SolvencyViolation = 0x7004,
InvalidFeeSplit = 0x8002,
UnauthorizedClaim = 0x9002,
// ...
}
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#
// Move USDT from a user into the market vault (SPL Token via Pinocchio).
// TransferChecked verifies the mint and decimals. When the source is a PDA
// (e.g. vault payouts), the program signs with .invoke_signed(&seeds) instead.
TransferChecked {
from: user_token_account,
mint: usdt_mint,
to: vault,
authority: user,
amount,
decimals: usdt_decimals,
}
.invoke()?;
Pyth Oracle Read#
// Seesaw reads the Pyth PriceUpdateV2 account directly (no Pyth SDK):
// read_pyth_price validates the account discriminator and verification level,
// then reads price / confidence / exponent / publish_time at fixed offsets.
let price = read_pyth_price(pyth_price_account)?;
// Caller validates the feed id matches the market, and that the price is fresh:
if price.publish_time < boundary_time {
return Err(SeesawError::StaleOracle.into()); // 0x2002
}
if price.price <= 0 {
return Err(SeesawError::InvalidPrice.into()); // 0x2003
}
Events#
Seesaw records events for indexers and UIs through a binary event recorder (an
internal self-invocation), not a framework emit! macro. Off-chain consumers
decode the records by schema. The key event payloads:
struct MarketCreated {
market_id: u64,
pyth_feed: Pubkey,
start_time: i64,
end_time: i64,
}
struct OrderPlaced {
market: Pubkey,
user: Pubkey,
side: OrderSide,
price: u16,
quantity: u64,
order_id: u64,
}
struct Trade {
market: Pubkey,
maker: Pubkey,
taker: Pubkey,
price: u16,
quantity: u64,
taker_fee: u64, // capped-linear-decay: min(fee_cap_bps, decay_rate_bps * (10000 - price) / 10000)
protocol_fee: u64, // 50% of taker_fee
creator_fee: u64, // 10% of taker_fee
referral_fee: u64, // 40% of taker_fee (0 if taker has no referrer)
}
struct MarketResolved {
market: Pubkey,
outcome: Outcome, // None = 0, Up = 1, Down = 2
start_price: i64,
end_price: i64,
}
Related Documentation#
- Accounts — detailed account layouts
- Instructions — full instruction reference
- PDAs — PDA derivation guide
- Security — security considerations