Python SDK#
Seesaw ships two Python packages:
seesaw-sdk(packages/sdk-python, import nameseesaw) — the trust-based SDK: PDA derivation, account decoding, instruction builders for the full instruction set, error codes, HTTP/WebSocket clients for the hosted indexer, and referral/creator helpers. Built on solders.seesaw-trustless(packages/trustless-python, import nameseesaw_trustless) — 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#
pip install seesaw-sdk
Or install from source (required until PyPI publication lands):
cd packages/sdk-python
pip install -e ".[dev]"
# Trustless layer (depends on seesaw-sdk):
cd ../trustless-python
pip install -e .
Requirements: Python 3.10+, solders
= 0.21 (the only runtime dependency; installed automatically).
Module Layout#
| Module | Contents |
|---|---|
seesaw.constants | PROGRAM_ID, seeds, discriminators, account sizes, instruction discriminants (DISC_*), trader_ledger_account_size() |
seesaw.pda | PDA derivation helpers + pick_treasury_index |
seesaw.instructions | Account dataclasses + instruction builders (incl. create_market_bundle) |
seesaw.codecs | Account decoders (decode_config, decode_market, …) |
seesaw.types | Enums (MarketState, Outcome, OrderSide, OrderType, TokenType) and account dataclasses |
seesaw.errors | SeesawError enum, get_error_description, from_error_code |
seesaw.client | High-level helpers (place_order, create_market, create_market_bundle, …) that derive PDAs for you |
seesaw.pricing | Canonical-side mapping, tick rounding, preview_place_order_pricing |
seesaw.validation | Input validators (price, amount, duration, fee, tick size) |
seesaw.http | SeesawHttpClient — hosted indexer REST client |
seesaw.streams | SeesawWebSocketClient — hosted indexer WebSocket client |
seesaw.referral | Referrer resolution (pure-function port of @seesaw/core) |
seesaw.creator | Creator dashboard parsing (parse_creator_markets) |
seesaw.widget | Referral-triple derivation for the optional place_order account tail |
Quick Start#
For an end-to-end walkthrough (install → list markets → read the order book), see the Quickstart and Building Transactions guides. The Python quick start in the Trustless SDK guide shows the full trustless flow in Python.
The snippet below shows the core primitives — PDA derivation, account decoding,
and building a PlaceOrder instruction:
from solders.pubkey import Pubkey
from seesaw import (
PROGRAM_ID,
OrderSide,
OrderType,
PlaceOrderAccounts,
decode_market,
derive_config_pda,
derive_market_pda,
pick_treasury_index,
place_order,
)
# 1. Derive a market PDA
feed_id = bytes([0xAB] * 32) # the market's 32-byte Pyth feed id
market_pda, bump = derive_market_pda(feed_id, 900, 1, creator_pubkey)
# 2. Decode a market account from raw bytes
market = decode_market(account_data)
print(f"Market state: {market.state}, outcome: {market.outcome}")
current_slot = 12_345 # Fetch the current slot from your Solana RPC client.
treasury_index = pick_treasury_index(wallet.pubkey(), current_slot, 8)
# 3. Build a PlaceOrder instruction (18 accounts, in on-chain order)
accounts = PlaceOrderAccounts(
market=market_pda,
orderbook=orderbook_pda,
user_position=position_pda,
user_token_account=user_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_ID,
system_program=SYSTEM_PROGRAM_ID,
settlement_mint=settlement_mint,
yes_escrow=yes_escrow_pda,
no_escrow=no_escrow_pda,
user_yes_ata=yes_ata,
user_no_ata=no_ata,
yes_mint=yes_mint_pda,
no_mint=no_mint_pda,
trader_ledger=trader_ledger_pda,
)
ix = place_order(
accounts,
side=OrderSide.BuyYes,
price_bps=5000, # 50%
quantity=100_000_000, # 100 USDT (6 decimals)
order_type=OrderType.Limit,
protocol_treasury_index=treasury_index, # must match the treasury_token_account above
)
# 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.pda.pick_treasury_indexand pass the matching account — 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:
from seesaw import CreateMarketAccounts, create_market_bundle
# Returns list[Instruction]: EnsureTraderLedgerSpace prelude(s) +
# EnsureDeepOrderbookSpace prelude(s) + CreateMarket. Submit in order.
# For large/max num_seats or orderbook tiers, split the preludes across
# transactions and submit CreateMarket (the last entry) in the final one.
instructions = create_market_bundle(
accounts, # CreateMarketAccounts with all PDAs
max_confidence_ratio_bps=500, # 5%; 0 disables the confidence guard
duration_seconds=900, # 15-minute market
bids_size=512, # deep-orderbook tier: 64, 128, 256, 512, 1024, 2048, or 4096
asks_size=512, # must match bids_size
num_seats=128,
max_oracle_jump_bps=0, # 0 = protocol default jump guard (5,000 bps)
oracle_mode=1, # launch default: 1 = Pyth pull; 0 = legacy-compatible Pyth push
)
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. Python services that
fetch Pull updates should load PYTH_API_KEY from server environment or secrets
management, never from a distributed client.
The number of prelude instructions equals the trader-ledger chunks plus the
deep-orderbook chunks:
ceil(trader_ledger_account_size(num_seats) / MAX_PERMITTED_DATA_INCREASE) +
ceil(deep_orderbook_account_size(bids_size) / MAX_PERMITTED_DATA_INCREASE).
The lower-level pieces (ensure_trader_ledger_space,
ensure_trader_ledger_space_prelude, ensure_deep_orderbook_space,
ensure_deep_orderbook_space_prelude) are also exported. A high-level variant
with automatic PDA derivation lives at seesaw.client.create_market_bundle and
supports every protocol-valid deep orderbook tier: 64, 128, 256, 512, 1024, 2048, 4096.
API Reference#
PDA Derivation (seesaw.pda)#
All helpers take an optional trailing program_id (defaults to PROGRAM_ID)
and return (Pubkey, int):
| 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] |
The referral PDAs (derive_referral_pda, derive_referrer_earnings_pda,
derive_referrer_treasury_pda) live in seesaw.pda as well and are
re-exported through seesaw.widget.derive_referral_triple.
pick_treasury_index(payer, slot, n=8) computes the deterministic
protocol-treasury shard pick — bit-exact across the TS, Python, and Rust
SDKs.
Instruction Builders (seesaw.instructions)#
Builders exist for the full public instruction set (0x00–0x35). Each
takes an accounts dataclass plus keyword args and returns a
solders.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) and the batch/cancel family
(place_multiple_post_only_orders, cancel_multiple_orders_by_id,
cancel_all_orders, cancel_up_to, reduce_order) are exported as well.
Account Decoders (seesaw.codecs)#
| Function | Account Size | Returns |
|---|---|---|
decode_config() | 656 bytes | ConfigAccount |
decode_market() | 512 bytes | MarketAccount |
decode_deep_orderbook() | 192 + capacity × 224 bytes | OrderbookAccount |
decode_position() | 256 bytes | UserPositionAccount |
decode_asset_state() | 128 bytes | AssetState |
Types (seesaw.types)#
Enums (all IntEnum): MarketState, Outcome, OrderSide,
OrderType, TokenType
Account dataclasses: ConfigAccount, MarketAccount,
OrderbookAccount, Order, UserPositionAccount, AssetState
Error Codes (seesaw.errors)#
All 80+ on-chain error codes are available via SeesawError(IntEnum)
(category-prefixed hex codes, e.g. 0x1002 InvalidState,
0x3007 SlippageExceeded). Use get_error_description(code) for
human-readable messages and from_error_code(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 package 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 errors, and
returns slot-anchored results.
from solders.pubkey import Pubkey
from seesaw import OrderSide, OrderType, PlaceOrderAccounts, place_order
from seesaw_trustless import (
SYSTEM_PROGRAM_ADDRESS,
TOKEN_PROGRAM_ADDRESS,
TrustlessResolver,
TrustlessRpc,
list_markets,
)
rpc = TrustlessRpc("https://your-node.example") # any standard Solana RPC
resolver = TrustlessResolver(rpc)
user = Pubkey.from_string("YOUR_WALLET")
# Discover markets straight from chain state (no Seesaw API).
markets = list_markets(rpc)
# 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.
r = resolver.resolve_place_order(market_address=markets.value[0].address, user=user)
a = r.accounts
ix = place_order(
PlaceOrderAccounts(
market=a.market, orderbook=a.orderbook, user_position=a.user_position,
user_token_account=a.user_token_account, vault=a.vault, user=user,
config=a.config, treasury_token_account=a.treasury_token_account,
token_program=TOKEN_PROGRAM_ADDRESS, system_program=SYSTEM_PROGRAM_ADDRESS,
settlement_mint=a.settlement_mint, yes_escrow=a.yes_escrow,
no_escrow=a.no_escrow, user_yes_ata=a.user_yes_ata,
user_no_ata=a.user_no_ata, yes_mint=a.yes_mint, no_mint=a.no_mint,
trader_ledger=a.trader_ledger,
),
side=OrderSide.BuyYes,
price_bps=6_500,
quantity=1_000_000,
order_type=OrderType.Limit,
protocol_treasury_index=r.protocol_treasury_index,
)
# 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:
balances = resolver.get_trader_balances(market, user).value # ledger buckets
orders = resolver.find_user_orders(market, user).value # resting orders
position = resolver.get_position(market, user).value
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.referral and seesaw.creator modules implement the same
resolution logic as @seesaw/core, useful for server-side scripts and CLI
tools.
from seesaw.referral import ReferrerSource, ResolveInput, resolve_from_input
from seesaw.creator import parse_creator_markets
WALLET = "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1"
REFERRER = "11111111111111111111111111111112"
# Resolve referrer from an indexer snapshot
resolution = resolve_from_input(
WALLET,
ResolveInput(
indexer_snapshot={"referrer": REFERRER, "expires_at": 4_070_908_800_000},
earnings_exists=True,
),
)
# resolution.source == ReferrerSource.INDEXER
# resolution.eligible_for_triple may be true only when the lock is current and the earnings PDA exists.
# Attach trailing accounts only after checking resolution.eligible_for_triple.
# Parse the creators endpoint response
markets = parse_creator_markets(raw_indexer_dict)
claimable = [m for m in markets if m.claimable_now]
For a fully on-chain referral check (no indexer), use the trustless
resolver's get_referral_status instead. Full examples: the
referral_resolve_and_lock, creator_claim_all, and instruction_cookbook examples in
packages/sdk-python/examples/. See the
Referral and creator fees guide for the
full reference.
Cross-Language Validation#
The Python SDK validates against the same golden test vectors
(packages/test-vectors) used by the TypeScript and Rust SDKs, so
instruction encoding is byte-identical across languages. Run:
cd packages/sdk-python
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -v