Account Layouts#
On-chain account specifications for the Seesaw protocol.
Account Overview#
ConfigAccount#
Protocol-wide configuration. Singleton per program. Stores the capped-linear-decay fee curve parameters, the four-way allocation (protocol / creator / referral / maker), oracle governance state, and operational-parameter tuning.
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Account type identifier |
| 8 | authority | Pubkey | 32 | Protocol admin |
| 40 | treasury_recipients | [Pubkey; 8] | 256 | Protocol fee recipient shards |
| 296 | default_settlement_mint | Pubkey | 32 | Default settlement mint |
| 328 | closer_reward_lamports | u64 | 8 | SOL per lifecycle closer |
| 336 | max_order_size | u64 | 8 | Max shares per order |
| 344 | markets_created | u64 | 8 | Total markets created |
| 352 | total_volume_low | u64 | 8 | Total volume low 64 bits |
| 360 | total_volume_high | u64 | 8 | Total volume high 64 bits |
| 368 | total_fees_collected_low | u64 | 8 | Total fees low 64 bits |
| 376 | total_fees_collected_high | u64 | 8 | Total fees high 64 bits |
| 384 | tick_size_bps | u16 | 2 | Price tick (default: 100) |
| 386 | maker_rebate_share_bps | u16 | 2 | Maker share of taker fees; zero disables rebates |
| 388 | version | u8 | 1 | Config version |
| 389 | bump | u8 | 1 | PDA bump seed |
| 390 | paused | u8 | 1 | Protocol pause flag (0 = active, 1 = paused) |
| 391 | post_only_mode | u8 | 1 | Protocol-wide post-only mode |
| 392 | referral_share_bps_of_fee | u16 | 2 | Referral share (shipped: 500 = 5% of fee) |
| 396 | maker_rebate_min_rest_seconds | u32 | 4 | Minimum program-derived resting age for rebate eligibility |
| 400 | min_resting_notional_raw | u64 | 8 | Min resting notional storage |
| 408 | protocol_fee_bps | u16 | 2 | Protocol share (default: 5000 = 50% of fee) |
| 410 | default_creator_fee_bps | u16 | 2 | Creator share (shipped: 500 = 5% of fee) |
| 412 | fee_cap_bps | u16 | 2 | Curve ceiling (default: 200 = 2.00%) |
| 414 | decay_rate_bps | u16 | 2 | Curve slope (shipped: 400 = 4.00%) |
| 416 | max_price_staleness_seconds | u64 | 8 | Oracle staleness override |
| 424 | market_expiration_window_seconds | u64 | 8 | Force-expiry delay override |
| 432 | min_market_duration_seconds | u64 | 8 | Min market duration override |
| 440 | max_market_duration_seconds | u64 | 8 | Max market duration override |
| 448 | pending_authority | Pubkey | 32 | Pending two-step authority |
| 480 | last_fee_config_update_at | i64 | 8 | Shared fee-governance cooldown timestamp |
| 488 | pending_authority_eligible_at | i64 | 8 | Authority timelock timestamp |
| 496 | last_treasury_update_at | i64 | 8 | Treasury recipient cooldown timestamp |
| 504 | last_operational_params_update_at | i64 | 8 | UpdateOperationalParams rate-limit timestamp |
| 512 | pauser | Pubkey | 32 | Escalation-only guardian; [0;32] disables pauser access fail-closed |
| 544 | spline_flags | u8 | 1 | Bit 0 enables spline attachment |
| 545 | _spline_pad | [u8; 7] | 7 | Alignment padding |
| 552 | spline_min_commitment | u64 | 8 | Minimum settlement-token collateral required to attach |
| 560 | spline_mid_jump_reset_bps | u16 | 2 | Midpoint movement that resets eligibility; zero disables |
| 562 | last_microstructure_update_at | [u8; 8] | 8 | Global tick-size/min-notional cooldown timestamp, LE i64 |
| 570 | _spline_reserved | [u8; 6] | 6 | Reserved and zeroed |
| Total | 576 |
Fee Curve Parameters#
The taker fee is computed per-fill from the fill price, not stored as a flat rate:
fee_bps(p) = min(fee_cap_bps, decay_rate_bps × (10_000 − p) / 10_000)
fee_cap_bps = 200caps the fee at 2.00% of notional (the worst-case rate for fills near p = 0).decay_rate_bps = 400means the fee decays linearly toward zero as the fill price approaches 1.00 USDT per share.- The four share fields (
protocol_fee_bps,default_creator_fee_bps,referral_share_bps_of_fee,maker_rebate_share_bps) are shares of the total fee in bps of fee, and MUST sum to exactly10_000.
The shipped allocation is 50% protocol / 5% creator / 5% referral / 40% maker rebate. Eligible maker fills credit the maker's free quote balance; ineligible maker allocation and rounding dust go to protocol, as does the referral allocation without an eligible referrer. See fee constants and allocation details.
<!-- src: program/src/logic/fee.rs:168 FeeSplit4::TARGET --> <!-- F-01: update with builder fee allocation -->Curve and allocation values above are shipped initialization defaults; governance may change deployed config. Integer rate division floors; fee amounts round up.
<!-- src: program/src/state/config.rs:1098 ConfigAccount::new -->Derivation#
let (config_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"config"],
&program_id,
);
MarketAccount#
Individual market state for a single epoch (configurable duration: 60s–7 days).
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Frozen account type tag |
| 8 | market_id | u64 | 8 | Native epoch or external opaque ID |
| 16 | settlement_mint | Pubkey | 32 | Settlement-token mint |
| 48 | t_start | i64 | 8 | Start timestamp |
| 56 | t_end | i64 | 8 | End timestamp |
| 64 | created_at | i64 | 8 | Creation timestamp |
| 72 | resolved_at | i64 | 8 | Resolution timestamp |
| 80 | start_price | i64 | 8 | Start oracle price |
| 88 | start_price_conf | u64 | 8 | Start-price confidence |
| 96 | start_price_timestamp | i64 | 8 | Start snapshot timestamp |
| 104 | end_price | i64 | 8 | End oracle price |
| 112 | end_price_conf | u64 | 8 | End-price confidence |
| 120 | end_price_timestamp | i64 | 8 | End snapshot timestamp |
| 128 | start_price_expo | i32 | 4 | Start-price exponent |
| 132 | end_price_expo | i32 | 4 | End-price exponent |
| 136 | total_yes_shares | u64 | 8 | Aggregate YES liability |
| 144 | total_no_shares | u64 | 8 | Aggregate NO liability |
| 152 | _frozen_total_collateral | u64 | 8 | Frozen internal slot; use vault.amount |
| 160 | total_volume | u64 | 8 | Total trading volume |
| 168 | total_trades | u32 | 4 | Trade count |
| 172 | total_positions | u32 | 4 | Position count |
| 176 | settled_positions | u32 | 4 | Settled-position count |
| 180 | max_confidence_ratio_bps | u16 | 2 | Confidence guard; zero disables |
| 182 | outcome | u8 | 1 | Market outcome |
| 183 | bump | u8 | 1 | Market PDA bump |
| 184 | orderbook_bump | u8 | 1 | Orderbook PDA bump |
| 185 | vault_bump | u8 | 1 | Vault PDA bump |
| 186 | _padding | [u8; 2] | 2 | Alignment padding |
| 188 | pyth_feed_id | [u8; 32] | 32 | Expected Pyth feed ID |
| 220 | yes_mint | Pubkey | 32 | YES mint PDA |
| 252 | no_mint | Pubkey | 32 | NO mint PDA |
| 284 | creator | Pubkey | 32 | Market creator |
| 316 | yes_mint_bump | u8 | 1 | YES mint PDA bump |
| 317 | no_mint_bump | u8 | 1 | NO mint PDA bump |
| 318 | creator_fee_bps | u16 | 2 | Creator fee snapshot |
| 320 | protocol_fee_bps | u16 | 2 | Protocol fee snapshot |
| 322 | expired | u8 | 1 | ExpireMarket path marker |
| 323 | _padding2 | [u8; 1] | 1 | Alignment padding |
| 324 | duration_seconds_bytes | [u8; 8] | 8 | Duration, LE u64 |
| 332 | accumulated_creator_fees_bytes | [u8; 8] | 8 | Creator fees, LE u64 |
| 340 | yes_escrow_bump | u8 | 1 | YES escrow PDA bump |
| 341 | no_escrow_bump | u8 | 1 | NO escrow PDA bump |
| 342 | emergency_status | u8 | 1 | Per-market emergency state |
| 343 | event_sequence_bytes | [u8; 8] | 8 | Event sequence, LE u64 |
| 351 | max_total_shares_bytes | [u8; 8] | 8 | Share cap, LE u64 |
| 359 | max_oracle_jump_bps_bytes | [u8; 4] | 4 | Oracle-jump limit, LE u32 |
| 363 | num_seats_bytes | [u8; 4] | 4 | Trader-ledger capacity, LE u32 |
| 367 | force_cancel_enabled_slot_bytes | [u8; 8] | 8 | Force-cancel slot, LE u64 |
| 375 | last_microstructure_update_at_bytes | [u8; 8] | 8 | Microstructure timestamp, LE i64 |
| 383 | accumulated_referral_fees_bytes | [u8; 8] | 8 | Referral fees, LE u64 |
| 391 | market_layout_version | u8 | 1 | Layout version |
| 392 | market_feature_set_bits | u16 | 2 | Feature snapshot |
| 394 | _reserved2 | [u8; 10] | 10 | Reserved |
| 404 | maker_rebates_paid_bytes | [u8; 8] | 8 | Maker rebates paid, LE u64 |
| 412 | spline_committed_collateral_bytes | [u8; 8] | 8 | Spline collateral, LE u64 |
| 420 | market_kind | u8 | 1 | Native or external market kind |
| 421 | resolver_authority | Pubkey | 32 | External resolver; zero selects native Pyth. |
| 453 | leg_count | u8 | 1 | Payout leg count |
| 454 | halted | u8 | 1 | Halt flag |
| 455 | _reserved3 | [u8; 9] | 9 | Reserved |
| Total | 464 |
Derivation#
let (market_pda, bump) = Pubkey::find_program_address(
&[
b"seesaw",
b"market",
pyth_feed_id,
&duration_seconds.to_le_bytes(),
&market_id.to_le_bytes(),
creator_pubkey.as_ref(),
],
&program_id,
);
Market PDAs are scoped by feed, duration, epoch, and creator. Multiple creators can therefore run distinct markets for the same asset and window; discovery and liquidity grouping are handled off-chain.
State Encoding#
| outcome | State |
|---|---|
| 0 | Not resolved |
| 1 | UP (end >= start) |
| 2 | DOWN (end < start) |
DeepOrderbookAccount#
Deep RBT order book for a market with bids and asks.
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Literal DEEPBOOK |
| 8 | version | u8 | 1 | Current version: 2 |
| 9 | bump | u8 | 1 | PDA bump |
| 10 | capacity_tier | u16 | 2 | One of 64, 128, 256, 512, 1024, 2048, 4096 |
| 12 | market | Pubkey | 32 | Parent market |
| 44 | next_order_id | u64 | 8 | Rust ABI field: next insertion sequence; SDK aliases: nextOrderSequence / next_order_sequence |
| 52 | bid_count | u32 | 4 | Occupied bid-side slots |
| 56 | ask_count | u32 | 4 | Occupied ask-side slots |
| 60 | _reserved | [u8; 68] | 68 | Reserved |
| 128 | bid side | variable | Side header, RBT nodes, order slots | |
| 128 + N | ask side | variable | Side header, RBT nodes, order slots | |
| Total | 192 + 224 * capacity |
Each side is DeepOrderbookSideHeader (32) + capacity * DeepOrderbookNode (32) +
capacity * Order (80).
Order Structure (80 bytes)#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | order_id | u64 | 8 | Unique ID |
| 8 | owner | Pubkey | 32 | Order owner |
| 40 | quantity | u64 | 8 | Remaining quantity |
| 48 | original_quantity | u64 | 8 | Original size |
| 56 | timestamp | i64 | 8 | Placement time |
| 64 | price_bps | u16 | 2 | Canonical YES price |
| 66 | original_side | u8 | 1 | Raw OrderSide discriminant |
| 67 | is_active | u8 | 1 | 1 when active, 0 otherwise |
| 68 | status | u8 | 1 | 0 active, 1 expired-claimable |
| 69 | max_age_seconds_bytes | [u8; 4] | 4 | LE u32 effective TTL, capped at the market close observed at placement |
| 73 | _reserved | [u8; 7] | 7 | Reserved |
Side Encoding#
| Value | Original Side | Canonical Side |
|---|---|---|
| 1 | BuyYes | Bid |
| 2 | SellYes | Ask |
| 3 | BuyNo | Ask (converted) |
| 4 | SellNo | Bid (converted) |
Derivation#
let (orderbook_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"orderbook", market_pda.as_ref()],
&program_id,
);
VaultAccount#
SPL Token account holding market collateral.
Layout#
Standard SPL Token account (165 bytes):
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | mint | Pubkey | 32 | Token mint |
| 32 | owner | Pubkey | 32 | Owner (market PDA) |
| 64 | amount | u64 | 8 | Token balance |
| 72 | delegate | Option<Pubkey> | 36 | Delegate |
| 108 | state | AccountState | 1 | Account state |
| 109 | is_native | Option<u64> | 12 | Native flag |
| 121 | delegated_amount | u64 | 8 | Delegated |
| 129 | close_authority | Option<Pubkey> | 36 | Close auth |
| Total | 165 |
Derivation#
let (vault_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"vault", market_pda.as_ref()],
&program_id,
);
UserPositionAccount#
User's position in a specific market.
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Account type identifier |
| 8 | market | Pubkey | 32 | Parent market |
| 40 | owner | Pubkey | 32 | Position owner |
| 72 | yes_shares | u64 | 8 | YES shares owned |
| 80 | no_shares | u64 | 8 | NO shares owned |
| 88 | locked_yes_shares | u64 | 8 | Locked in sell orders |
| 96 | locked_no_shares | u64 | 8 | Locked in sell orders |
| 104 | collateral_deposited | u64 | 8 | Total deposited |
| 112 | collateral_locked | u64 | 8 | Locked in buy orders |
| 120 | payout | u64 | 8 | Settlement payout |
| 128 | total_bought | u64 | 8 | Shares bought |
| 136 | total_sold | u64 | 8 | Shares sold |
| 144 | total_fees_paid | u64 | 8 | Taker fees |
| 152 | total_rebates_earned | u64 | 8 | Maker rebates |
| 160 | first_trade_at | i64 | 8 | First trade time |
| 168 | last_trade_at | i64 | 8 | Last trade time |
| 176 | order_count | u16 | 2 | Active orders |
| 178 | settled | u8 | 1 | Settlement flag |
| 179 | bump | u8 | 1 | PDA bump |
| 180 | trader_slot_hint_bytes | [u8; 4] | 4 | +1-encoded ledger hint |
| 184 | yes_withdrawn | u64 | 8 | Slot-sourced YES minted |
| 192 | no_withdrawn | u64 | 8 | Slot-sourced NO minted |
| 200 | _reserved | [u8; 56] | 56 | Future use |
| Total | 256 |
Derivation#
let (position_pda, bump) = Pubkey::find_program_address(
&[
b"seesaw",
b"position",
market_pda.as_ref(),
user_pubkey.as_ref(),
],
&program_id,
);
TraderLedgerAccount#
Per-market trader balance ledger. Header followed by a dynamic slot array. The
ledger account is pre-grown via EnsureTraderLedgerSpace before CreateMarket;
the orderbook PDA is pre-grown separately via EnsureDeepOrderbookSpace.
Layout#
Header (56 bytes):
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Account type identifier |
| 8 | market | Pubkey | 32 | Parent market |
| 40 | capacity | u32 | 4 | Total slot count (num_seats) |
| 44 | next_free_slot | u32 | 4 | Allocation search hint |
| 48 | occupied_count | u32 | 4 | Number of occupied slots |
| 52 | bump | u8 | 1 | PDA bump |
| 53 | version | u8 | 1 | Layout version (live ledgers = 2) |
| 54 | _reserved | [u8; 2] | 2 | Alignment padding |
Slot (72 bytes each, capacity slots immediately follow header):
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | trader | Pubkey | 32 | Trader this slot is assigned to |
| 32 | quote_free | [u8; 6] | 6 | Free settlement-token credit |
| 38 | yes_free | [u8; 6] | 6 | Free YES-share credit |
| 44 | no_free | [u8; 6] | 6 | Free NO-share credit |
| 50 | quote_locked | [u8; 6] | 6 | Collateral locked by open orders |
| 56 | yes_locked | [u8; 6] | 6 | YES locked by open ask orders |
| 62 | no_locked | [u8; 6] | 6 | NO locked by open ask orders |
| 68 | occupied | u8 | 1 | 0 = free; non-zero = assigned |
| 69 | _reserved | [u8; 3] | 3 | Alignment padding |
Total size: 56 + num_seats × 72 bytes.
Derivation#
let (ledger_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"trader_ledger", market_pda.as_ref()],
&program_id,
);
ReferrerEarningsAccount#
Per-referrer earnings ledger. Tracks claimable and lifetime-claimed amounts.
Credited on every attributed fill; drained by claim_referrer_earnings.
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Account type identifier |
| 8 | referrer | Pubkey | 32 | Referrer wallet |
| 40 | accumulated | u64 | 8 | Claimable amount |
| 48 | total_claimed | u64 | 8 | Lifetime claimed amount |
| 56 | referees_count | u32 | 4 | Reserved for off-chain use; never written on-chain |
| 60 | bump | u8 | 1 | PDA bump |
| 61 | treasury_index | u8 | 1 | Index of the referrer_treasury_k shard (Phase E); immutable after creation |
| 62 | _padding_a | [u8; 2] | 2 | Alignment padding |
| 64 | last_claim_at | i64 | 8 | Timestamp of the last ClaimReferrerEarnings call |
| Total | 72 |
Derivation#
let (earnings_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"referrer_earnings", referrer.as_ref()],
&program_id,
);
ReferralAccount#
Per-user first-touch referral attribution record. Written by set_referrer;
immutable for 365 days (REFERRAL_DURATION_SECONDS).
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | Account type identifier |
| 8 | referee | Pubkey | 32 | User being attributed |
| 40 | referrer | Pubkey | 32 | Referrer wallet |
| 72 | created_at | i64 | 8 | Attribution creation timestamp |
| 80 | expires_at | i64 | 8 | Expiry timestamp (created_at + REFERRAL_DURATION_SECONDS) |
| 88 | total_earned | u64 | 8 | Reserved for off-chain reconstruction; never written on-chain |
| 96 | bump | u8 | 1 | PDA bump |
| 97 | _padding | [u8; 7] | 7 | Alignment padding |
| Total | 104 |
Derivation#
let (referral_pda, bump) = Pubkey::find_program_address(
&[b"seesaw", b"referral", user.as_ref()],
&program_id,
);
YES and NO Mints#
Each market has two SPL token mints for its YES and NO shares.
Seeds#
These are standard SPL Mint accounts (82 bytes). Their authority is the market PDA so minting/burning is program-controlled.
YES and NO Escrows#
Sell orders escrow tokens here; fills transfer from escrow to the taker's ATA.
Seeds#
Standard SPL Token accounts (165 bytes), owned by the market PDA.
AssetState#
Per-feed tracking account. CreateMarket initializes or updates the
AssetState PDA for the market's Pyth feed, and resolve/expire paths validate
the supplied asset state against the market's feed id.
Layout#
| Offset | Field | Type | Size | Description |
|---|---|---|---|---|
| 0 | discriminator | [u8; 8] | 8 | sha256("account:AssetState")[..8] |
| 8 | pyth_feed_id | [u8;32] | 32 | Pyth feed id this asset state is scoped to |
| 40 | total_markets_created | u64 | 8 | Number of markets created for this feed |
| 48 | bump | u8 | 1 | PDA bump seed |
| 49 | _reserved | [u8;79] | 79 | Reserved layout space |
| Total | 128 |
Seeds#
["seesaw", "asset", pyth_feed_id]
Discriminators#
Each account type has a unique 8-byte discriminator:
fn discriminator(name: &str) -> [u8; 8] {
let hash = sha256(format!("account:{}", name));
hash[..8]
}
| Account | Discriminator Input |
|---|---|
| ConfigAccount | account:ConfigAccount |
| MarketAccount | account:MarketAccount |
| DeepOrderbookAccount | literal DEEPBOOK |
| UserPositionAccount | account:UserPositionAccount |
| AssetState | account:AssetState |
| TraderLedgerAccount | account:TraderLedgerAccount |
| ReferrerEarningsAccount | account:ReferrerEarningsAccount |
| ReferralAccount | account:ReferralAccount |
| SplineAccount | literal SPLINE\0\0 |
Rent Requirements#
Every figure below is runtime-derived: the per-byte rate is a cluster parameter
(6,960 lamports/byte today, 696 after the pending network upgrade), so the "~"
column moves by ten times at activation. Derive the value from a live
getMinimumBalanceForRentExemption probe rather than embedding it — see
Rent Costs.
| Account | Size | Rent-Exempt today (~) | After reduction (~) |
|---|---|---|---|
| ConfigAccount | 576 bytes | ~0.005 SOL | ~0.0005 SOL |
| MarketAccount | 464 bytes | ~0.004 SOL | ~0.0004 SOL |
| DeepOrderbookAccount | 192 + capacity × 224 | Variable by tier | Variable by tier |
| VaultAccount | 165 bytes | ~0.002 SOL | ~0.0002 SOL |
| UserPositionAccount | 256 bytes | ~0.003 SOL | ~0.0003 SOL |
| AssetState | 128 bytes | ~0.002 SOL | ~0.0002 SOL |
| TraderLedgerAccount | 56 + num_seats × 72 | Variable (see Market Capacity) | Variable |
| ReferrerEarningsAccount | 72 bytes | ~0.001 SOL | ~0.0001 SOL |
| ReferralAccount | 104 bytes | ~0.002 SOL | ~0.0002 SOL |
| SplineAccount | 896 bytes | ~0.007 SOL | ~0.0007 SOL |
Account Relationships#
Next Steps#
- Instructions — instruction details
- PDAs — derivation patterns