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;
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);
// 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: 0, // 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 the trader_ledger PDA to be pre-allocated to
exactly trader_ledger_account_size(num_seats) bytes; 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) prelude
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%
duration_seconds: 900, // 15-minute market
max_oracle_jump_bps: 0, // 0 = no jump limit
market_size_params: MarketSizeParams {
bids_size: 512,
asks_size: 512,
num_seats: 128,
},
oracle_mode: 0, // 0 = Pyth push (default), 1 = Pyth pull
};
// Returns Vec<Instruction>: EnsureTraderLedgerSpace prelude(s) + CreateMarket.
// Send the entire vec as one transaction; for the largest seat options
// (~600 KiB ledger ⇒ ~60 prelude instructions) split the prelude across
// transactions and emit CreateMarket (the last entry) in the final one.
let instructions = create_market_bundle(&accounts, ¶ms, &PROGRAM_ID);
For oracle_mode = 0, accounts.pyth_feed is the Receiver-owned Pyth push
feed account address and the market binds that exact account. 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.
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 }.
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–0x32). 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 | 624 bytes | Result<ConfigAccount, SdkError> |
decode_market | 512 bytes | Result<MarketAccount, SdkError> |
decode_orderbook | 10,240 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[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);
// resolution.eligible_for_triple == true → attach trailing accounts to place_order
// 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, 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