import { rateLimiter, EndpointCategory } from "./rate-limiter.js";
import { getConfig } from "./config.js";
const cfg = getConfig();
const CLOB_BASE = cfg.host;
async function clobFetch<T>(path: string, params: Record<string, string> = {}): Promise<T> {
await rateLimiter.acquire(EndpointCategory.CLOB_MARKET_DATA);
const url = new URL(path, CLOB_BASE);
for (const [k, v] of Object.entries(params)) {
url.searchParams.set(k, v);
}
const res = await fetch(url.toString());
if (!res.ok) {
throw new Error(`CLOB API error: ${res.status} ${res.statusText}`);
}
return (await res.json()) as T;
}
async function clobPost<T>(path: string, body: unknown): Promise<T> {
await rateLimiter.acquire(EndpointCategory.CLOB_MARKET_DATA);
const url = new URL(path, CLOB_BASE);
const res = await fetch(url.toString(), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
throw new Error(`CLOB API error: ${res.status} ${res.statusText}`);
}
return (await res.json()) as T;
}
export interface PriceData {
bid: number;
ask: number;
mid: number;
}
export interface BookLevel {
price: string;
size: string;
}
export interface OrderBook {
market: string;
asset_id: string;
bids: BookLevel[];
asks: BookLevel[];
hash: string;
timestamp: string;
}
export interface SpreadData {
spread: number;
bid: number;
ask: number;
}
export async function getPrice(tokenId: string): Promise<PriceData> {
const [bidRes, askRes] = await Promise.all([
clobFetch<{ price: string }>("/price", { token_id: tokenId, side: "buy" }),
clobFetch<{ price: string }>("/price", { token_id: tokenId, side: "sell" }),
]);
const bid = Number.parseFloat(bidRes.price);
const ask = Number.parseFloat(askRes.price);
return { bid, ask, mid: (bid + ask) / 2 };
}
export async function getBook(tokenId: string): Promise<OrderBook> {
return clobFetch<OrderBook>("/book", { token_id: tokenId });
}
export async function getSpread(tokenId: string): Promise<SpreadData> {
const book = await getBook(tokenId);
const bestBid = book.bids.length > 0 ? Number.parseFloat(book.bids[0].price) : 0;
const bestAsk = book.asks.length > 0 ? Number.parseFloat(book.asks[0].price) : 0;
return {
spread: bestAsk > 0 && bestBid > 0 ? bestAsk - bestBid : 0,
bid: bestBid,
ask: bestAsk,
};
}
export async function getMidpoint(tokenId: string): Promise<{ mid: string }> {
return clobFetch<{ mid: string }>("/midpoint", { token_id: tokenId });
}
export async function getPriceHistory(
tokenId: string,
interval?: string,
fidelity?: number,
startTs?: number,
endTs?: number,
): Promise<{ history: Array<{ t: number; p: number }> }> {
const params: Record<string, string> = { market: tokenId };
if (interval) params.interval = interval;
if (fidelity !== undefined) params.fidelity = String(fidelity);
if (startTs !== undefined) params.startTs = String(startTs);
if (endTs !== undefined) params.endTs = String(endTs);
return clobFetch("/prices-history", params);
}
export async function getBatchPrices(
tokenIds: string[],
): Promise<Record<string, { price: string }>> {
const body = tokenIds.map((token_id) => ({ token_id }));
return clobPost("/prices", body);
}
export async function getBatchSpreads(
tokenIds: string[],
): Promise<Record<string, SpreadData>> {
const body = tokenIds.map((token_id) => ({ token_id }));
return clobPost("/spreads", body);
}
export async function getBatchBooks(
tokenIds: string[],
): Promise<Record<string, OrderBook>> {
const body = tokenIds.map((token_id) => ({ token_id }));
return clobPost("/books", body);
}