Get started
Quickstart
From nothing to a first order on Seeryx: read a live round and its order book, sign in with a Solana wallet, place an order and cancel it.
Read a market
Market data is public. Ask for the live BTC 5-minute round, then its order book and its last trades.
// quickstart.mjs: the live BTC 5-minute round, its order book and its last trades. node quickstart.mjs
import { apiUrl } from './api-url.mjs';
const BASE = 'https://seeryx.com';
const get = async (path) => (await fetch(apiUrl(BASE, path), { redirect: 'error' })).json();
const round = await get('/markets/BTC/5m/current');
const up = await get(`/orderbook/${round.slug}?side=up`);
const trades = await get(`/trades/${round.slug}`);
const bid = up.bids[0]?.[0], ask = up.asks[0]?.[0]; // levels are [cents, shares], best first
console.log({ round: round.slug, openedAt: Number(round.open), ends: new Date(round.end * 1000).toISOString() });
const chance = bid && ask ? `${(bid + ask) / 2}%` : 'no quotes yet'; // the middle of bid and ask
console.log({ upBid: bid ?? '-', ask: ask ?? '-', chanceOfUp: chance });
console.log({ lastTrade: trades[0] ?? 'none yet' });{ "slug": "btc-updown-5m-1791916200", "asset": "BTC", "tf": "5m", "start": 1791916200, "end": 1791916500,
"open": 83082.005, "close": null, "outcome": null, "status": "live" }
{ "bids": [[40, 100], [39, 100]], "asks": [[49, 100], [50, 100]] }
[{ "id": 13, "side": "down", "action": "buy", "shares": 10, "price": 53, "time": 1791916330063 }]Set up a project
mkdir seeryx-bot && cd seeryx-bot
npm init -y && npm pkg set type=module
npm i @solana/kit && npm i -D tsx
# a wallet for the bot alone: a JSON array of 64 numbers, as solana-keygen writes it
solana-keygen new -o wallet.json && chmod 600 wallet.jsonDeposit some USDC to that wallet's Seeryx account before you trade: on the website (connect the same wallet) or from code, see Deposits and withdrawals. The wallet needs no SOL.
Sign in
Save the client below as seeryx.ts (or seeryx.py), with api-url.mjs next to it for TypeScript. It signs in with the wallet and sends the token with every request; Authentication explains each step.
// seeryx.ts: a small client for the Seeryx API. Node 22+, npm i @solana/kit
import { readFileSync } from 'node:fs';
import { createKeyPairFromBytes, getAddressFromPublicKey, getBase58Decoder, signBytes } from '@solana/kit';
import type { Address } from '@solana/kit';
import { apiUrl } from './api-url.mjs';
export const BASE = process.env.SEERYX_URL ?? 'https://seeryx.com';
export const ACCESS = process.env.SEERYX_ACCESS ?? ''; // only on a site with access codes
let token = '';
export interface Round {
slug: string; start: number; end: number; open: number | null; close: number | null;
outcome: 'up' | 'down' | null; status: 'upcoming' | 'live' | 'settled';
}
export interface Book { bids: [price: number, shares: number][]; asks: [price: number, shares: number][] }
export interface Account {
positions: { slug: string; side: 'up' | 'down'; shares: number; cost: number }[];
orders: {
id: number; slug: string; side: 'up' | 'down'; action: 'buy' | 'sell'; shares: number; limit: number;
}[];
balance: { available: number; total: number };
}
export class ApiError extends Error {
constructor(
message: string, readonly status: number, readonly code?: string, readonly retryAfter?: number,
) {
super(message);
}
}
// One request. Writes get a fresh Idempotency-Key unless you pass the key of the attempt you are retrying.
export async function api<T>(path: string, init: { method?: string; body?: unknown; key?: string } = {}) {
const method = init.method ?? 'GET';
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (token) headers['X-Auth-Token'] = token;
if (ACCESS) headers['X-Access-Token'] = ACCESS;
if (method !== 'GET') headers['Idempotency-Key'] = init.key ?? crypto.randomUUID();
const body = init.body === undefined ? undefined : JSON.stringify(init.body);
const res = await fetch(apiUrl(BASE, path), { method, headers, body, redirect: 'error' });
const data = await res.json().catch(() => ({}));
if (!res.ok) {
const wait = Number(res.headers.get('Retry-After')) || undefined;
throw new ApiError(data.error ?? `HTTP ${res.status}`, res.status, data.code, wait);
}
return data as T;
}
export interface Wallet { pair: CryptoKeyPair; address: Address }
// A wallet file as solana-keygen writes it: a JSON array of 64 numbers.
export async function loadWallet(path = process.env.SEERYX_WALLET ?? 'wallet.json'): Promise<Wallet> {
const pair = await createKeyPairFromBytes(new Uint8Array(JSON.parse(readFileSync(path, 'utf8'))));
return { pair, address: await getAddressFromPublicKey(pair.publicKey) };
}
// Sign-In With Solana: ask for the message, check it, sign its exact bytes, trade the signature for a token.
export async function signIn(wallet: Wallet) {
const { address } = wallet;
const { message } = await api<{ message: string }>('/auth/nonce', { method: 'POST', body: { address } });
const [first, second] = message.split('\n');
const site = `${new URL(BASE).hostname} wants you to sign in with your Solana account:`;
if (first !== site || second !== address) {
throw new Error('Unexpected sign-in message: not this site or not this wallet');
}
const bytes = await signBytes(wallet.pair.privateKey, new TextEncoder().encode(message));
const signature = getBase58Decoder().decode(bytes);
const body = { address, message, signature };
({ token } = await api<{ token: string }>('/auth/verify', { method: 'POST', body }));
return token; // valid for 7 days
}// Keep server-supplied identifiers inside the configured API. Never follow redirects with account headers.
export function apiUrl(base, path) {
const origin = new URL(base);
if (!['http:', 'https:'].includes(origin.protocol) || origin.username || origin.password || origin.pathname !== '/' || origin.search || origin.hash) {
throw new Error('API base must be an HTTP(S) origin without credentials');
}
const pathname = path.split('?')[0];
if (!/^\/[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*\/?$/.test(pathname) || /[\u0000-\u0020\u007f#\\]/.test(path)) {
throw new Error('Invalid API path');
}
const url = new URL(`/api${path}`, origin);
if (url.origin !== origin.origin || !url.pathname.startsWith('/api/')) throw new Error('Invalid API destination');
return url;
}
// JSON escapes terminal controls; explicitly remove log entry separators as well.
export const logValue = (value) => JSON.stringify(value)?.replace(/[\n\r]/g, '_').replace(/[\u2028\u2029]/g, '_');Place an order
Orders on seeryx.com are signed by a trading session key. Save chain.ts and message.ts (or chain.py and message.py) from Signing orders, then run this: it opens a session (one wallet signature, no SOL), rests a 5-share buy below the market and cancels it.
// first-order.ts: sign in, rest one small buy order below the market, then cancel it. npx tsx first-order.ts
import { api, loadWallet, signIn, type Book, type Round } from './seeryx';
import { signOrder, tradingSession, type Funds, type Order } from './chain';
const wallet = await loadWallet();
await signIn(wallet);
const funds = await api<Funds>('/funds');
// one wallet signature opens a trading session; its key then signs every order
const session = funds.onChainTrading ? await tradingSession(funds, wallet) : null;
const round = await api<Round>('/markets/BTC/5m/current');
const book = await api<Book>(`/orderbook/${round.slug}?side=up`);
const bid = book.bids[0]?.[0] ?? 10; // the best Up bid in cents
// 5 Up shares 5c under the best bid: it rests in the book (postOnly makes sure it never trades at once)
const order: Order = {
slug: round.slug, side: 'up', action: 'buy', type: 'limit', shares: 5, limit: Math.max(1, Math.floor(bid) - 5), postOnly: true,
};
const body = session ? await signOrder(funds, session, wallet.address, order) : order;
const placed = await api<{ order: number | null }>('/orders', { method: 'POST', body });
console.log('resting order', placed.order, 'at', order.limit, 'c');
console.log((await api<{ orders: { id: number }[] }>('/account')).orders);
if (placed.order) console.log(await api(`/orders/${placed.order}`, { method: 'DELETE' })); // { success: true }Cancel it
# one order
curl -s -X DELETE https://seeryx.com/api/orders/65365 -H "X-Auth-Token: $TOKEN" -H "Idempotency-Key: $(uuidgen)"
# {"success":true}
# everything still resting in one round
curl -s -X DELETE "https://seeryx.com/api/orders?slug=btc-updown-5m-1791916200" -H "X-Auth-Token: $TOKEN" -H "Idempotency-Key: $(uuidgen)"
# {"success":true,"cancelled":[65366]}