Get started
SDKs for Python and Rust
Official Seeryx SDKs for Python and Rust: market data, Sign-In With Solana, signed orders, deposits and withdrawals checked before signing, live streams.
Overview
The official SDKs wrap the API of these pages for Python and Rust, with the same concepts and names in both. They are open source under the MIT license: github.com/Seeryx/seeryx-python and github.com/Seeryx/seeryx-rust.
- Market data: markets, live and past rounds, order books, trades, volume, fees, stats, the leaderboard, profiles and event markets, without an account.
- Sign-In With Solana with your wallet’s keypair; when the token ends, the SDK signs in again by itself.
- Trading: a trading session opened with one wallet signature, then limit, market and post-only orders, batches of up to 15, cancels, splits and merges, all signed on your machine.
- Funds: deposits from your wallet, withdrawals to your own wallet and delayed withdrawals, each transaction checked before it is signed.
- Live: WebSocket streams of a round and of your account that reconnect by themselves.
- Helpers: the price grid, fees, round slugs and round times.
Install
Python 3.9 or newer (a blocking Client and an asyncio AsyncClient with the same methods), or Rust 1.88 or newer (async, on tokio). Both install from GitHub:
# Python 3.9 or newer
pip install "git+https://github.com/Seeryx/seeryx-python"Read a market
Market data needs no account. Prices are in cents; book levels are [price, shares], best first.
"""The live BTC 5-minute round, its order book and its last trades. No account needed.
python quickstart.py
"""
from seeryx import Client
with Client() as client:
rnd = client.current_round("BTC", "5m")
book = client.order_book(rnd["slug"], "up") # levels are [cents, shares], best first
trades = client.trades(rnd["slug"])
bid = book["bids"][0][0] if book["bids"] else None
ask = book["asks"][0][0] if book["asks"] else None
print(rnd["slug"], "opened at", rnd["open"], "ends at", rnd["end"])
print("Up bid", bid, "ask", ask, "chance of Up", f"{(bid + ask) / 2}%" if bid and ask else "no quotes yet")
print("last trade", trades[0] if trades else "none yet")
print("taker fee", client.fees()["rateBps"] / 100, "% of p x (1 - p) per share")Sign in
Load your wallet from a Solana CLI keypair file or a base58 secret key; it never leaves your process. Before the wallet signs the sign-in text, the SDK checks that it names this site, this wallet and the nonce just issued, then keeps the 7-day token.
Where GET /api/funds says onChainTrading, one wallet signature opens a trading session; its key, made on your machine, then signs every order. With a path the session is saved (readable by you only) and reused by the next run; without one it stays in memory.
from seeryx import Client, Keypair
client = Client() # https://seeryx.com
wallet = Keypair.from_file("wallet.json") # a solana-keygen file; it never leaves this process
client.sign_in(wallet) # checks the sign-in text, signs it, keeps the 7-day token
if client.funds().get("onChainTrading"):
# one wallet signature; the session key then signs every order. Saved with mode 0600 and reused next time
session = client.open_session(wallet, path="session.json")
print("session key", session.address, "until", session.expires_at)Place an order
Prices are cents (tenths where the market allows them), sizes are shares, amounts are USDC. The SDK chooses the signed form (PMO2, PMO3 or PMO4), the order id and the expiry; a market order needs cap, the worst price you accept.
"""Sign in, rest a small post-only buy below the market, list it, then cancel it.
SEERYX_WALLET=~/.config/solana/id.json python limit_order.py
"""
import os
from seeryx import Client, Keypair
wallet = Keypair.from_file(os.path.expanduser(os.environ.get("SEERYX_WALLET", "wallet.json")))
with Client() as client:
client.sign_in(wallet)
if client.funds().get("onChainTrading"):
# one wallet signature opens a trading session; its key then signs every order. Saved (mode 0600) so the
# next run reuses it; leave out `path` to keep the key in memory only.
client.open_session(wallet, path="session.json")
rnd = client.current_round("BTC", "5m")
book = client.order_book(rnd["slug"], "up")
bid = book["bids"][0][0] if book["bids"] else 10
limit = max(1, int(bid) - 5) # 5c under the best bid: it rests in the book
placed = client.buy(rnd["slug"], "up", shares=5, limit=limit, post_only=True) # post_only: never trades at once
print("resting order", placed["order"], "at", limit, "c")
print("open orders", client.open_orders())
if placed["order"]:
print(client.cancel_order(placed["order"]))"""Spend up to $5 on Up now, paying at most 2c over the best ask. What the book cannot fill is dropped.
SEERYX_WALLET=~/.config/solana/id.json python market_buy.py
"""
import os
from seeryx import Client, Keypair
wallet = Keypair.from_file(os.path.expanduser(os.environ.get("SEERYX_WALLET", "wallet.json")))
with Client() as client:
client.sign_in(wallet)
if client.funds().get("onChainTrading"):
client.open_session(wallet, path="session.json")
slug = client.current_round("BTC", "5m")["slug"]
cap = client.market_cap(slug, "up", "buy", slippage=2) # the worst price you accept, on the price grid
result = client.market_buy(slug, "up", amount=5, cap=cap)
print(result["status"], "filled", result["filled"], "shares for", result["spent"], "USDC, average", result.get("avgPrice"))Live updates
A stream follows one round or event market (both books, trades, volume) and, once you are signed in, your account. It reconnects with a growing delay and subscribes again; when a round ends, follow the next one on the same connection.
"""Live books and trades of the BTC 5-minute rounds, and your account, over the WebSocket. Reconnects by itself.
python stream.py # market data only
SEERYX_WALLET=~/.config/solana/id.json python stream.py # and your account
"""
import asyncio
import os
import time
from seeryx import AsyncClient, Keypair
async def main() -> None:
async with AsyncClient() as client:
path = os.environ.get("SEERYX_WALLET")
if path:
await client.sign_in(Keypair.from_file(os.path.expanduser(path)))
rnd = await client.current_round("BTC", "5m")
async with client.stream(rnd["slug"], account=bool(path)) as stream:
while True:
# a message comes only when something changed: wait at most until the round ends
message = await stream.next(timeout=max(1.0, rnd["end"] - time.time()))
if message is None or time.time() >= rnd["end"]:
rnd = await client.current_round("BTC", "5m") # follow the next round, same connection
await stream.watch_round(rnd["slug"])
continue
if message["type"] == "round":
up = message["up"]
print(message["slug"], "bid", up["bids"][:1], "ask", up["asks"][:1], "volume", message["volume"])
elif message["type"] == "account":
print("cash", message["balance"]["available"], "open orders", len(message["orders"]))
else:
print("error", message["error"])
asyncio.run(main())Deposit and withdraw
The exchange prepares each transaction and pays its network fee, so your wallet needs no SOL. The SDK checks it against exactly what you asked for and signs nothing else. A withdrawal is signed by the session key where sessionWithdraw is on (at least 1 USDC, to your own wallet only), otherwise by your wallet.
"""Deposit USDC from your wallet, then withdraw some of it to the same wallet. The exchange pays every network fee:
the wallet needs no SOL. Every transaction is inspected before it is signed.
SEERYX_WALLET=~/.config/solana/id.json python withdraw.py
"""
import os
from seeryx import Client, Keypair
wallet = Keypair.from_file(os.path.expanduser(os.environ.get("SEERYX_WALLET", "wallet.json")))
with Client(before_sign=lambda review: print("signing:", review.headline) or True) as client:
client.sign_in(wallet)
print("wallet", client.wallet_funds()) # USDC in the wallet; "account" is None until the first deposit
print("deposit", client.deposit(wallet, 10)) # the first deposit is at least 1 USDC
print("balance", client.balance())
# signed by the trading session key when one is open and the server allows it, else by the wallet;
# max_fee refuses a withdrawal fee above 1 USDC
if client.funds().get("onChainTrading"):
client.open_session(wallet, path="session.json")
print("withdrawal", client.withdraw(4, wallet=wallet, max_fee=1))
print("balance", client.balance())Errors and retries
A refusal comes as an error with the status, the server’s text and its code; the codes of the error list have their own types. 429 and 503 answers are retried as Retry-After says, and writes keep their Idempotency-Key on every retry, so nothing is placed twice.
from seeryx import ApiError, BelowMinimum, WouldTake
try:
client.buy(slug, "up", shares=5, limit=60, post_only=True)
except WouldTake:
print("it would trade at once: post only refused it")
except BelowMinimum as e:
print("the smallest order is", e.minimum, "shares")
except ApiError as e:
print(e.status, e.code, e.message) # 429 and 503 were already retried as Retry-After saidKeys and checks
- Your wallet key and the session key never leave your process: only public keys and signatures are sent.
- Every prepared transaction is decoded and must be the Seeryx program’s own instruction with your accounts and your amounts: an approval, a transfer, another signer or another program is refused.
- On seeryx.com the program, the USDC mint and the Solana cluster are pinned, whatever the server reports.
- Nothing that contains a secret is printed or logged, and a saved session file is readable by its owner only.
Examples
Both languages have the same programs in their examples folders:
| Example | What it does |
|---|---|
quickstart | The live BTC 5-minute round, its book and its last trades, without an account |
limit_order | Sign in, rest a post-only buy below the market, list it and cancel it |
market_buy | Spend up to $5 at once, paying at most 2¢ over the best ask |
stream | Live books, trades and your account over the WebSocket |
market_maker | A small two-sided quoting loop with post-only orders and merges |
withdraw | Deposit from your wallet and withdraw to it again |
Source code, every method, the tests and the changelog: github.com/Seeryx/seeryx-python and github.com/Seeryx/seeryx-rust.