Erste Schritte
Schnellstart
Von null zur ersten Order auf Seeryx: eine Live-Runde und ihr Orderbuch lesen, mit einer Solana-Wallet anmelden, eine Order platzieren und stornieren.
Einen Markt lesen
Marktdaten sind öffentlich. Frage die laufende 5-Minuten-Runde von BTC ab, dann ihr Orderbuch und ihre letzten 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 }]Ein Projekt einrichten
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.jsonZahle vor dem Handel etwas USDC auf das Seeryx-Konto dieser Wallet ein: auf der Website (verbinde dieselbe Wallet) oder per Code, siehe Einzahlungen und Abhebungen. Die Wallet braucht kein SOL.
Anmelden
Speichere den folgenden Client als seeryx.ts (oder seeryx.py), für TypeScript mit api-url.mjs daneben. Er meldet sich mit der Wallet an und sendet das Token mit jeder Anfrage; Authentifizierung erklärt jeden Schritt.
// 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, '_');Eine Order platzieren
Orders auf seeryx.com werden von einem Sitzungsschlüssel signiert. Speichere chain.ts und message.ts (oder chain.py und message.py) aus Orders signieren und führe dann dies aus: Es öffnet eine Sitzung (eine Wallet-Signatur, kein SOL), legt eine Kauforder über 5 Anteile unter dem Markt ins Orderbuch und storniert sie.
// 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 }Die Order stornieren
# 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]}