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 (sdk_place_order, sdk_create_market, …) 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,
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}")
# 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=0, # must match the treasury_token_account passed 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 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:
from seesaw import CreateMarketAccounts, create_market_bundle
# Returns list[Instruction]: EnsureTraderLedgerSpace prelude(s) + CreateMarket.
# Send the entire list as one transaction (or split the prelude across
# transactions for very large num_seats).
instructions = create_market_bundle(
accounts, # CreateMarketAccounts with all PDAs
max_confidence_ratio_bps=500, # 5%
duration_seconds=900, # 15-minute market
bids_size=512,
asks_size=512,
num_seats=128,
max_oracle_jump_bps=0, # 0 = no jump limit
oracle_mode=0, # 0 = Pyth push (default), 1 = Pyth pull
)
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. 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
ceil(trader_ledger_account_size(num_seats) / MAX_PERMITTED_DATA_INCREASE) —
one for small seat counts (e.g. 128). The lower-level pieces
(ensure_trader_ledger_space, ensure_trader_ledger_space_prelude) are also
exported. A high-level variant with automatic PDA derivation lives at
seesaw.client.create_market_bundle.
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–0x32). 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() | 624 bytes | ConfigAccount |
decode_market() | 512 bytes | MarketAccount |
decode_orderbook() | 10,240 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[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 == True → safe to attach trailing accounts
# 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 and creator_claim_all 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