src/transport/weights.ts
v0.2.0 · 2.8 KB
// Request weights (documentation: docs.nado.xyz developer-resources/api/rate-limits).
//
// Documented limits: queries 2400 weight/min per IP (burst 400 / 10 s), executes 600/min per wallet
// (burst 100 / 10 s). Neither was measured against a live 429; the numbers feed the own throttle.
// Execute weights are the own-throttle convention (1 for place/cancel, 30 for link_signer), not documented.
import type { QueryRequest } from './types.js';
/** Documented query weights by `type`. */
export const QUERY_WEIGHTS: Readonly<Record<string, number>> = {
contracts: 1,
order: 1,
market_price: 1,
symbols: 2,
subaccount_info: 2,
nonces: 2,
subaccount_orders: 2,
linked_signer: 5,
all_products: 5,
};
/** Weight per product id of `orders`. */
export const ORDERS_WEIGHT_PER_PRODUCT = 2;
/** Weight per product id of `market_prices` (approximate). */
export const MARKET_PRICES_WEIGHT_PER_PRODUCT = 1;
/** Weight assumed for query types that are not documented (`fee_rates`, ...). @experimental */
export const DEFAULT_QUERY_WEIGHT = 2;
/** Execute weights used by the own throttle. */
export const EXECUTE_WEIGHTS: Readonly<Record<string, number>> = {
place_order: 1,
cancel_orders: 1,
cancel_product_orders: 1,
link_signer: 30,
};
/** Weight of an execute action without an entry above. */
export const DEFAULT_EXECUTE_WEIGHT = 1;
function countIds(body: QueryRequest): number {
const ids = body.product_ids;
return Array.isArray(ids) ? ids.length : 0;
}
/** Weight of a query body: documented per type; `orders` and `market_prices` scale with `product_ids`. */
export function queryWeight(body: QueryRequest): number {
if (body.type === 'orders') return Math.max(ORDERS_WEIGHT_PER_PRODUCT, ORDERS_WEIGHT_PER_PRODUCT * countIds(body));
if (body.type === 'market_prices') return Math.max(1, MARKET_PRICES_WEIGHT_PER_PRODUCT * countIds(body));
return QUERY_WEIGHTS[body.type] ?? DEFAULT_QUERY_WEIGHT;
}
/** The single action key of an execute body (`place_order`, `cancel_orders`, `link_signer`). */
export function executeAction(body: Record<string, unknown>): string | undefined {
const keys = Object.keys(body);
return keys.length === 1 ? keys[0] : undefined;
}
/** Weight of an execute body by its action. */
export function executeWeight(body: Record<string, unknown>): number {
const action = executeAction(body);
return (action && EXECUTE_WEIGHTS[action]) || DEFAULT_EXECUTE_WEIGHT;
}
/** Splits product ids into explicitly sized chunks. */
export function chunkProductIds(ids: readonly number[], size: number): number[][] {
if (!Number.isInteger(size) || size <= 0)
throw new RangeError(`chunk size must be a positive integer, got ${String(size)}`);
const out: number[][] = [];
for (let i = 0; i < ids.length; i += size) out.push(ids.slice(i, i + size));
return out;
}