home

Get started

Authentication

Sign-In With Solana for the Seeryx API: the message, the token in X-Auth-Token or Bearer, and the trading session key that signs your orders.

Overview

CallsNeeds
Market data: markets, rounds, books, trades, events, profilesNothing
Your account: cash, positions, orders, activityA sign-in token in X-Auth-Token
Orders, cancels, splits, mergesThe token and an Idempotency-Key; on seeryx.com every order, split and merge also carries signed, made by your trading session key
Deposits, withdrawals, the session itselfThe token, and your wallet's signature on the transaction the server prepares

Sign in with a wallet

Seeryx uses Sign-In With Solana: your wallet signs a short text message, which costs nothing and sends nothing on chain.

  1. POST /api/auth/nonce with { "address": "<your wallet>" } returns the message. It is valid for 5 minutes and works once.
  2. Check that line 1 names this site and line 2 your address, then sign the message's exact UTF-8 bytes with the wallet's Ed25519 key and encode the 64-byte signature in base58.
  3. POST /api/auth/verify with address, message and signature returns token. The first sign-in creates the account.
The message
seeryx.com wants you to sign in with your Solana account:
4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm

Sign in to Seeryx. This does not send a transaction or cost a fee.

URI: https://seeryx.com
Version: 1
Chain ID: mainnet
Nonce: a4b5fb6f571c43b4b1c5057abaa8cea2
Issued At: 2026-10-13T18:29:28.000Z
Expiration Time: 2026-10-13T18:34:28.000Z
Chain ID names the Solana cluster of the server.
seeryx.ts
// 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
}

Sign-in tokens

Header
Send the token as X-Auth-Token: <token> or Authorization: Bearer <token> on every signed-in request, and as token in the WebSocket's account subscription.
Lifetime
7 days. Sign in again before then, or when a call answers 403 (seeryx.com) or 401 with "Invalid or expired token".
Cookies
The website itself signs in with an HttpOnly cookie and a CSRF token, which only pages of the site can use. API clients use the token header and need neither.
Limits
30 requests a minute per IP address for each of /auth/nonce and /auth/verify.

The trading session key

On seeryx.com every trade settles on Solana through the Seeryx program, and the program accepts only orders signed for your account. Instead of asking the wallet for each order, the wallet approves a session key once:

FieldTypeDescription
keyrequiredpublic keyA new Ed25519 key your bot made. It signs orders, splits and merges.
expiresAtrequiredUnix secondsWhen the key stops working: at most maxSessionTtl of GET /api/funds from now (24 hours).
limitrequiredUSDCThe most the key may spend on buys and splits in all.
maxTraderequiredUSDCThe most one order may spend; at most limit.
  • It can trade, never withdraw. The key can only sign orders, splits and merges for your account within its limits. Withdrawals always need the wallet itself.
  • One at a time. A new session replaces the old key; resting orders signed by the old key are cancelled once the new one is in use.
  • Limit orders must not outlive it. A resting order is refused if it would rest past the session's end: open a new session before quoting a round that closes later (the 1-day rounds, for example).
  • Free. Seeryx pays the network fee and the account rent of the session transaction. Your first deposit must have opened your account first (until then: 409 "Deposit first").
  • Close it early with POST /api/relay { "action": "close_session" }: your resting orders are cancelled and the key stops working at once.

How to open the session and sign with it, byte by byte: Signing orders.

Signing out

  • POST /api/auth/logout ends the token you send; POST /api/auth/logout-all ends every token of the account.
  • Neither ends the trading session key: close it with close_session, or let it expire.
curl
curl -s -X POST https://seeryx.com/api/auth/logout-all -H "X-Auth-Token: $TOKEN"
# {"success":true}

Sign-in endpoints

Sign in with a Solana wallet: ask for a message, sign its exact bytes with the wallet key, exchange the signature for a token. Nothing is sent on chain and no fee is paid.

POST/api/auth/nonce

Get the message to sign

Public
FieldTypeDescription
addressbodyrequiredstringSolana wallet address (base58)
Request
curl -X POST "https://seeryx.com/api/auth/nonce" \
  -H "Content-Type: application/json" \
  -d '{"address":"4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm"}'
Response
{
  "message": "seeryx.com wants you to sign in with your Solana account:\n4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm\n\nSign in to Seeryx. This does not send a transaction or cost a fee.\n\nURI: https://seeryx.com\nVersion: 1\nChain ID: mainnet\nNonce: a4b5fb6f571c43b4b1c5057abaa8cea2\nIssued At: 2026-10-13T18:29:28.000Z\nExpiration Time: 2026-10-13T18:34:28.000Z",
  "nonce": "a4b5fb6f571c43b4b1c5057abaa8cea2"
}
  • The message is valid for 5 minutes and can be used once. Chain ID names the Solana cluster of the server.
StatusWhen
400Not a Solana address
429More than 30 requests a minute from your IP address
POST/api/auth/verify

Exchange the signed message for a token

Public
FieldTypeDescription
addressbodyrequiredstringThe same wallet address
messagebodyrequiredstringThe message exactly as received
signaturebodyrequiredstringEd25519 signature of the message bytes (UTF-8), base58
Request
curl -X POST "https://seeryx.com/api/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{"address":"4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm","message":"...","signature":"5Kq3..."}'
Response
{ "token": "eyJ0eXAiOiJKV1Qi...", "user": { "id": 3, "address": "4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm", "createdAt": "2026-10-13 18:29:28" } }
  • The token is valid for 7 days. Send it as X-Auth-Token (or Authorization: Bearer) on every signed-in request. The first sign-in creates the account.
StatusWhen
401"Signature verification failed" or "Invalid or expired login message"
403This account is blocked
429More than 30 requests a minute from your IP address
GET/api/auth/me

The signed-in account and its cash

Signed in
Request
curl "https://seeryx.com/api/auth/me" \
  -H "X-Auth-Token: $TOKEN"
Response
{ "user": { "id": 3, "address": "4hfqTJ6sVSszXZGCYjXEr4YuAXz9nyLut8725NP9mVvm", "username": null, "avatar": null },
  "balance": { "total": 474.097965, "available": 474.097965, "locked": 0, "currency": "USDC" } }
POST/api/auth/logout

End this sign-in token

Signed in
Request
curl -X POST "https://seeryx.com/api/auth/logout" \
  -H "X-Auth-Token: $TOKEN"
Response
{ "success": true }
  • Other tokens of the account keep working. It does not end the trading session key: close that with POST /api/relay close_session.
POST/api/auth/logout-all

End every sign-in token of the account

Signed in
Request
curl -X POST "https://seeryx.com/api/auth/logout-all" \
  -H "X-Auth-Token: $TOKEN"
Response
{ "success": true }
  • Every token made so far stops working, this one too; sign in again for a new one.
POST/api/auth/username

Set or change the public username

Signed in
FieldTypeDescription
usernamebodyrequiredstring2-20 letters, digits or _; unique ignoring case
Request
curl -X POST "https://seeryx.com/api/auth/username" \
  -H "X-Auth-Token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"username":"satoshi_bot"}'
Response
{ "success": true, "username": "satoshi_bot" }
  • The name shows on the leaderboard and your public profile; without one they show your shortened wallet.