Rust SDK#
Seesaw ships two Rust crates:
seesaw-sdk(packages/sdk-rust) — the trust-based SDK: PDA derivation, account decoding, instruction builders for the full instruction set, error codes, optional HTTP/WebSocket helpers for the hosted indexer, and referral/creator helpers.seesaw-trustless(packages/trustless-rust) — validated account resolution straight from any standard Solana RPC node, with no Seesaw-operated service in the trust path. See the Trustless SDK guide.
Installation#
Add to your Cargo.toml:
[dependencies]
seesaw-sdk = "0.1.0-rc.1" # or: { path = "packages/sdk-rust" } from the monorepo
solana-pubkey = "2.2"
solana-instruction = "2.2"
# Trustless layer (depends on seesaw-sdk; default feature "http" = ureq transport):
seesaw-trustless = { path = "packages/trustless-rust" }
Optional seesaw-sdk feature flags:
| Feature | Adds | Extra deps |
|---|---|---|
| (none) | PDA, codecs, instructions, types, errors, referral, creator, pricing, validation, streams (message types) | — |
http | SeesawHttpClient — blocking REST client for the hosted indexer | ureq |
rpc | RPC helpers via solana-client | solana-client, solana-sdk |
seesaw-sdk = { version = "0.1.0-rc.1", features = ["http"] }
seesaw-sdk = { version = "0.1.0-rc.1", features = ["rpc"] }
Module Layout#
| Module | Contents |
|---|---|
seesaw_sdk::constants | PROGRAM_ID, seeds, discriminators, account sizes, DISC_* instruction discriminants, trader_ledger_account_size() |
seesaw_sdk::pda | PDA derivation + pick_treasury_index + derive_referral_triple |
seesaw_sdk::instructions | Account structs + instruction builders (incl. create_market_bundle) |
seesaw_sdk::codecs | Account decoders |
seesaw_sdk::types | Enums and account/param structs |
seesaw_sdk::errors | SdkError — complete mirror of the on-chain error set |
seesaw_sdk::client | High-level helpers with automatic PDA derivation (create_market_bundle, place_order, …) |
seesaw_sdk::pricing | Maker price-band math (compute_band, MAKER_BAND_BPS) |
seesaw_sdk::validation | Input validators |
seesaw_sdk::referral | Referrer resolution (pure-function port of @seesaw/core) |
seesaw_sdk::creator | Creator dashboard parsing (list_creator_markets) |
seesaw_sdk::http | SeesawHttpClient (feature http) |
seesaw_sdk::streams | WebSocket channel names + message types |
Quick Start#
For an end-to-end walkthrough (install → list markets → read the order book), see the Quickstart and Building Transactions guides. The Rust quick start in the Trustless SDK guide shows the full trustless flow in Rust.
The snippet below shows the core primitives — PDA derivation, account decoding,
and building a PlaceOrder instruction:
use seesaw_sdk::constants::PROGRAM_ID;
use seesaw_sdk::pda::{derive_market_pda, pick_treasury_index};
use seesaw_sdk::instructions::{place_order, PlaceOrderAccounts};
use seesaw_sdk::types::{OrderSide, OrderType, PlaceOrderParams};
use seesaw_sdk::codecs::decode_market;
use solana_pubkey::Pubkey;
const TOKEN_PROGRAM: Pubkey =
Pubkey::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
const SYSTEM_PROGRAM: Pubkey =
Pubkey::from_str_const("11111111111111111111111111111111");
// 1. Derive a market PDA
let feed_id = [0xAB_u8; 32];
let (market_pda, _bump) = derive_market_pda(&feed_id, 900, 1, &creator, &PROGRAM_ID);
// 2. Decode a market account from raw bytes
let market = decode_market(&account_data)?;
println!("Market state: {:?}, outcome: {:?}", market.state, market.outcome);
let current_slot = 12_345; // Fetch the current slot from your Solana RPC client.
let treasury_index = pick_treasury_index(&wallet.pubkey(), current_slot, 8);
// 3. Build a PlaceOrder instruction (18 accounts, in on-chain order)
let params = PlaceOrderParams {
side: OrderSide::BuyYes,
price_bps: 5000, // 50%
quantity: 100_000_000, // 100 USDT (6 decimals)
order_type: OrderType::Limit,
worst_acceptable_price_bps: 0,
min_fill_quantity: 0,
match_limit: 0,
self_trade_behavior: 1,
max_age_seconds: 0,
reject_post_only_would_cross: 1,
protocol_treasury_index: treasury_index, // must match treasury_token_account below
};
let accounts = PlaceOrderAccounts {
market: market_pda,
orderbook: orderbook_pda,
user_position: position_pda,
user_token_account: user_stablecoin_ata,
vault: vault_pda,
user: wallet.pubkey(),
config: config_pda,
treasury_token_account: treasury_ata, // config.treasury_recipients[protocol_treasury_index]
token_program: TOKEN_PROGRAM,
system_program: SYSTEM_PROGRAM,
settlement_mint,
yes_escrow: yes_escrow_pda,
no_escrow: no_escrow_pda,
user_yes_ata,
user_no_ata,
yes_mint: yes_mint_pda,
no_mint: no_mint_pda,
trader_ledger: trader_ledger_pda,
};
let ix = place_order(&accounts, ¶ms, &PROGRAM_ID);
// Add ix to a transaction and send
Treasury index pairing. The on-chain program rejects with
TreasuryRecipientMismatchiftreasury_token_accountdoes not equalconfig.treasury_recipients[protocol_treasury_index]. Compute the index withseesaw_sdk::pda::pick_treasury_index(payer, slot, 8)and pass the matching account from a decodedConfigAccount— or use the trustless resolver, which pairs them structurally so the mismatch cannot occur.
Creating Markets — create_market_bundle#
CreateMarket (0x03) requires both the trader_ledger PDA and deep
orderbook PDA to be pre-allocated to their requested sizes; Solana caps each
realloc at 10,240 bytes (MAX_PERMITTED_DATA_INCREASE). Use
create_market_bundle() — not bare create_market() — to get the full
instruction list with the idempotent EnsureTraderLedgerSpace (0x2E) and
EnsureDeepOrderbookSpace (0x33) preludes prepended automatically:
use seesaw_sdk::instructions::{create_market_bundle, CreateMarketAccounts};
use seesaw_sdk::types::{CreateMarketParams, MarketSizeParams};
let params = CreateMarketParams {
max_confidence_ratio_bps: 500, // 5%; 0 disables the confidence guard
duration_seconds: 900, // 15-minute market
max_oracle_jump_bps: 0, // 0 = protocol default jump guard (5,000 bps)
market_size_params: MarketSizeParams {
bids_size: 512, // deep-orderbook tier: 64, 128, 256, 512, 1024, 2048, or 4096
asks_size: 512, // must match bids_size
num_seats: 128,
},
oracle_mode: 1, // launch default: 1 = Pyth pull; 0 = legacy-compatible Pyth push
};
// Returns Vec<Instruction>: EnsureTraderLedgerSpace prelude(s) +
// EnsureDeepOrderbookSpace prelude(s) + CreateMarket. Submit in order.
// For large/max seat or orderbook tiers, split the preludes across
// transactions and emit CreateMarket (the last entry) in the final one.
let instructions = create_market_bundle(&accounts, ¶ms, &PROGRAM_ID);
For oracle_mode = 1, accounts.pyth_feed is a Receiver-owned
PriceUpdateV2 account used for the create snapshot; the market stores the
Pull sentinel and later validates lifecycle updates by the 32-byte
pyth_feed_id. For legacy-compatible oracle_mode = 0, accounts.pyth_feed
is the Receiver-owned Pyth push feed account address and the market binds that
exact account.
Keep Pyth Hermes bearer keys on server-side relays only. Rust clients that fetch
Pull updates should call that relay or otherwise load PYTH_API_KEY from a
server environment, never from a shipped end-user app.
A higher-level variant with automatic PDA derivation lives at
seesaw_sdk::client::create_market_bundle(&CreateMarketInput, &PROGRAM_ID)
(and create_market_bundle_default), returning
SdkBundleResult { instructions, accounts }. During beta, that high-level
client helper rejects seat counts above 4,225; use the lower-level
instruction builder and explicit split planning for larger protocol-valid
ledgers.
API Reference#
PDA Derivation (seesaw_sdk::pda)#
All helpers take program_id: &Pubkey explicitly and return (Pubkey, u8):
| Function | Seeds |
|---|---|
derive_config_pda | ["seesaw", "config"] |
derive_market_pda | ["seesaw", "market", feed_id, duration, market_id, creator] |
derive_orderbook_pda | ["seesaw", "orderbook", market] |
derive_vault_pda | ["seesaw", "vault", market] |
derive_position_pda | ["seesaw", "position", market, user] |
derive_asset_pda | ["seesaw", "asset", feed_id] |
derive_yes_mint_pda | ["seesaw", "yes_mint", market] |
derive_no_mint_pda | ["seesaw", "no_mint", market] |
derive_yes_escrow_pda | ["seesaw", "escrow_yes", market] |
derive_no_escrow_pda | ["seesaw", "escrow_no", market] |
derive_trader_ledger_pda | ["seesaw", "trader_ledger", market] |
derive_referral_pda | ["seesaw", "referral", referee] |
derive_referrer_earnings_pda | ["seesaw", "referrer_earnings", referrer] |
derive_referrer_treasury_pda | ["seesaw", "referrer_treasury", &[shard_index]] |
pick_treasury_index(payer, slot, n) -> u8 computes the deterministic
protocol-treasury shard pick (bit-exact across TS/Python/Rust);
derive_referral_triple derives the optional 3-account referral tail.
Instruction Builders (seesaw_sdk::instructions)#
Builders exist for the full public instruction set (0x00–0x35). Each
takes an accounts struct (and a params struct where applicable) plus
program_id, and returns a solana_instruction::Instruction. Selected
builders:
| Function | Discriminator | Data Size |
|---|---|---|
initialize_config | 0x00 | 5 bytes |
create_market | 0x03 | 28 bytes |
snapshot_end | 0x04 | 1 byte |
resolve_market | 0x05 | 1 byte |
expire_market | 0x06 | 1 byte |
mint_shares | 0x07 | 9 bytes |
deposit_funds | 0x08 | 9 bytes |
withdraw_funds | 0x09 | 9 bytes |
withdraw_shares | 0x0A | 10 bytes |
place_order | 0x0B | 31 bytes |
cancel_order | 0x10 | 9 bytes |
redeem | 0x1A | 10 bytes |
force_close | 0x1B | 1 byte |
close_market | 0x1E | 1 byte |
update_fee_config | 0x1F | 11 bytes |
set_referrer | 0x21 | 1 byte |
init_referrer_earnings_account | 0x22 | 2 bytes |
claim_creator_fees | 0x23 | 1 byte |
claim_referrer_earnings | 0x24 | 1 byte |
pause / unpause | 0x25/0x26 | 1 byte |
update_tick_size | 0x29 | 3 bytes |
ensure_trader_ledger_space | 0x2E | 5 bytes |
update_operational_params | 0x2F | 49 bytes |
propose_pyth_program_id | 0x30 | 34 bytes |
apply_pyth_program_id | 0x31 | 1 byte |
update_market_defaults | 0x32 | 35 bytes |
Free-funds variants (swap_with_free_funds,
place_limit_order_with_free_funds, cancel_*_with_free_funds,
reduce_order_with_free_funds), the batch/cancel family, and the
admin/lifecycle instructions (reclaim_expired_order,
mark_position_settled, close_position, update_treasury_recipients,
set_market_emergency_status, force_cancel_market_orders, …) are all
exported.
Account Decoders (seesaw_sdk::codecs)#
| Function | Account Size | Returns |
|---|---|---|
decode_config | 656 bytes | Result<ConfigAccount, SdkError> |
decode_market | 512 bytes | Result<MarketAccount, SdkError> |
decode_deep_orderbook | 192 + capacity × 224 bytes | Result<OrderbookAccount, SdkError> |
decode_position | 256 bytes | Result<UserPositionAccount, SdkError> |
decode_asset_state | 128 bytes | Result<AssetState, SdkError> |
Types (seesaw_sdk::types)#
Enums: MarketState, Outcome, OrderSide, OrderType, TokenType
Account structs: ConfigAccount, MarketAccount, OrderbookAccount,
Order, UserPositionAccount, AssetState
Param structs: InitializeConfigParams, CreateMarketParams,
MarketSizeParams, PlaceOrderParams, CancelOrderParams,
MintSharesParams, RedeemParams, WithdrawSharesParams,
UpdateTickSizeParams, UpdateFeeConfigParams
Error Codes (seesaw_sdk::errors)#
SdkError is a complete mirror of the 80+ on-chain error codes
(category-prefixed hex, e.g. 0x1002 InvalidState,
0x3007 SlippageExceeded) plus SDK-side decode errors. Use
SdkError::try_from(code) to convert a raw u32.
Trustless Variant#
Everything above builds instructions from accounts you supply —
typically fetched from the hosted indexer. The seesaw-trustless crate
removes that dependency: it derives, discovers, and validates every account
directly from any standard Solana RPC node (ownership, discriminator, size,
PDA re-derivation, mint/authority pinning), fails closed with typed
TrustlessErrors, and returns slot-anchored results.
use seesaw_sdk::constants::PROGRAM_ID;
use seesaw_sdk::instructions::{place_order, PlaceOrderAccounts};
use seesaw_sdk::types::{OrderSide, OrderType, PlaceOrderParams};
use seesaw_trustless::{
list_markets, HttpTransport, ResolvePlaceOrderParams, TrustlessResolver, TrustlessRpc,
SYSTEM_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS,
};
let rpc = TrustlessRpc::new(HttpTransport::new("https://your-node.example"));
let resolver = TrustlessResolver::new(&rpc);
// Discover markets straight from chain state (no Seesaw API).
let markets = list_markets(&rpc, &PROGRAM_ID)?;
// Resolve everything PlaceOrder needs — validated, slot-anchored. The
// treasury index/account pair is picked structurally from the on-chain
// config, and idempotent ATA-create instructions are returned for any of
// your token accounts that don't exist yet.
let r = resolver.resolve_place_order(&ResolvePlaceOrderParams::new(markets.value[0].address, user))?;
let t = r.accounts.trading;
let ix = place_order(
&PlaceOrderAccounts {
market: t.market,
orderbook: t.orderbook,
user_position: t.user_position,
user_token_account: t.user_token_account,
vault: t.vault,
user,
config: r.accounts.config,
treasury_token_account: r.accounts.treasury_token_account,
token_program: TOKEN_PROGRAM_ADDRESS,
system_program: SYSTEM_PROGRAM_ADDRESS,
settlement_mint: t.settlement_mint,
yes_escrow: t.yes_escrow,
no_escrow: t.no_escrow,
user_yes_ata: t.user_yes_ata,
user_no_ata: t.user_no_ata,
yes_mint: t.yes_mint,
no_mint: t.no_mint,
trader_ledger: t.trader_ledger,
},
&PlaceOrderParams {
side: OrderSide::BuyYes,
price_bps: 6_500,
quantity: 1_000_000,
order_type: OrderType::Limit,
worst_acceptable_price_bps: 0,
min_fill_quantity: 0,
match_limit: 0,
self_trade_behavior: 1,
max_age_seconds: 0,
reject_post_only_would_cross: 1,
protocol_treasury_index: r.protocol_treasury_index,
},
&PROGRAM_ID,
);
// Assemble [r.prepend_instructions..., ix] into one transaction, simulate
// via rpc.simulate_transaction(&base64_tx), then sign & send.
Trustless reads — the indexer-free equivalents of the hosted API:
let balances = resolver.get_trader_balances(&market, &user)?.value; // ledger buckets
let orders = resolver.find_user_orders(&market, &user)?.value; // resting orders
let position = resolver.get_position(&market, &user)?.value;
let status = resolver.get_referral_status(&user, resolver.get_chain_unix_timestamp()?)?;
Full guide — trust model, all ten safeguards, cancel/redeem flows: Trustless SDK.
Referral and creator namespace#
The seesaw_sdk::referral and seesaw_sdk::creator modules implement the
same resolution logic as @seesaw/core, useful for backend services and CLI
integrations. They accept pre-fetched snapshots rather than making network
calls directly, so they work in any async runtime.
use seesaw_sdk::referral::{resolve_referrer, ReferrerInput, IndexerSnapshot, ReferrerSource};
use seesaw_sdk::creator::list_creator_markets;
use solana_pubkey::Pubkey;
use std::str::FromStr;
let wallet = Pubkey::from_str("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1")?;
let referrer = Pubkey::from_str("11111111111111111111111111111112")?;
let input = ReferrerInput {
wallet,
indexer_snapshot: Some(IndexerSnapshot {
referrer: referrer.to_string(),
expires_at_ms: 4_070_908_800_000,
}),
rpc_snapshot: None,
pending_referrer: None,
earnings_exists: true,
};
let resolution = resolve_referrer(&input);
assert_eq!(resolution.source, ReferrerSource::Indexer);
// Attach trailing accounts to place_order only after checking:
// if resolution.eligible_for_triple { ... }
// list_creator_markets parses the creators endpoint JSON
let entries = list_creator_markets(&raw_json)?;
let claimable: Vec<_> = entries.iter().filter(|e| e.claimable_now).collect();
For a fully on-chain referral check (no indexer), use the trustless
resolver's get_referral_status instead. Full examples (in
packages/sdk-rust/examples/): referral_resolve_and_lock,
creator_claim_all, build_place_order, instruction_cookbook,
http_client, ws_orderbook.
See the Referral and creator fees guide
for the full reference.
Cross-Language Validation#
The Rust SDK validates against the same golden test vectors
(packages/test-vectors) used by the TypeScript and Python SDKs, so
instruction encoding is byte-identical across languages. Run:
cd packages/sdk-rust
cargo test