Polytraders SDK — shared types & helpers
The minimal type and helper surface every bot is expected to import. Pure functions, no side-effects, no network calls. If a helper needs to call out, it lives in a separate package — not here.
Why a shared SDK
Ninety-seven bots, six output schemas, four WebSocket channels, two API styles. Without a shared type and helper surface, every bot reinvents (and re-bugs) the same primitives. The SDK is the place where rounding, ID formatting, EIP-712 building, fee math, and timestamp parsing get done once.
Top-level types
// Identifiers
type ConditionId = string; // 0x-prefixed hex, 32 bytes
type OutcomeId = string; // YES or NO token id
type MarketId = ConditionId;
type IntentId = string; // UUIDv4 emitted by Strategy
type PlanId = string; // UUIDv4 emitted by Risk pipeline
type FillId = string; // CLOB fill hash, 0x-prefixed
type BotId = string; // dot-notation, e.g. "risk.liquidity_guard"
type BuilderCode = string; // bytes32 hex, 0x-prefixed (V2)
// Money
type UsdAmount = number; // pUSD, 6-decimal precision on chain, plain number in code
type Price = number; // 0..1, 4 decimals max (Polymarket tick = 0.001)
type Bps = number; // basis points, integer
// Time
type IsoTimestamp = string; // RFC3339, e.g. "2026-05-09T05:51:12Z"
type EpochMs = number; // V2 order timestamp field
Helpers — pure
// price math
roundToTick(price: number, tick = 0.001): number
clampPrice(price: number): number // 0.001..0.999
// money
toUsdcUnits(usd: UsdAmount): bigint // multiply by 1e6
fromUsdcUnits(units: bigint): UsdAmount
// fee math (V2)
platformFee(notional: UsdAmount, feeRate: Bps, p: Price): UsdAmount
// C × feeRate × p × (1-p), peaks at p=0.5
builderFee(notional: UsdAmount, builderFeeBps: Bps): UsdAmount
// notional × builderFeeBps / 10000
// IDs
parseConditionId(s: string): ConditionId | null
parseBuilderCode(s: string): BuilderCode | null // accepts hex or string label
toBytes32(s: string): BuilderCode // pads label to bytes32
// time
nowIso(): IsoTimestamp
nowMs(): EpochMs
isStale(ts: IsoTimestamp, maxAgeS: number): boolean
// JSON canonicalisation (for structured logs and dedupe keys)
canonicalize(v: unknown): string // RFC8785 JCS
// Reason codes
isHardReject(code: string): boolean
severityOf(code: string): "HARD_REJECT" | "RESHAPE" | "WARN" | "EXPLAIN" | "INFO"
EIP-712 builders (V2)
The SDK provides typed-data builders so bots never hand-roll the hashing. The Exchange domain version is "2"; the ClobAuth domain version stays at "1".
buildOrderTypedData(order: OrderV2, chainId: number): EIP712TypedData
buildClobAuthTypedData(nonce: bigint, ts: number): EIP712TypedData
// OrderV2 shape (matches CTFExchangeV2.matchOrders)
type OrderV2 = {
salt: bigint;
maker: Address;
signer: Address;
taker: Address; // 0x0 = any
tokenId: bigint; // outcome id
makerAmount: bigint; // pUSD units (6 dp) or outcome token units
takerAmount: bigint;
expiration: bigint;
side: 0 | 1; // 0 = BUY, 1 = SELL
signatureType: 0 | 1 | 2;
timestamp: bigint; // ms — REPLACES nonce/feeRateBps/taker fields from V1
metadata: bytes32; // arbitrary
builder: bytes32; // builderCode (zero = no builder)
};
WebSocket helpers
// Endpoints — exposed as constants, never hard-coded inside bot logic.
WS_MARKET = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
WS_USER = "wss://ws-subscriptions-clob.polymarket.com/ws/user";
WS_SPORTS = "wss://sports-api.polymarket.com/ws";
WS_RTDS = "wss://ws-live-data.polymarket.com";
// Reconnection wrapper with exponential backoff + last-message-id resume.
openWs(endpoint: string, opts: WsOpts): WsClient
HTTP endpoints
GAMMA_BASE = "https://gamma-api.polymarket.com"; // public, no auth
DATA_BASE = "https://data-api.polymarket.com"; // public, no auth (historical)
CLOB_BASE = "https://clob.polymarket.com"; // public read + auth trade
BRIDGE_BASE = "https://bridge.polymarket.com"; // proxy of fun.xyz
// All wrappers attach: User-Agent: polytraders/, Accept: application/json,
// and idempotency-key on POSTs.
fetchGamma(path: string, q?: object): Promise<any>
fetchData(path: string, q?: object): Promise<any>
fetchClobPublic(path: string, q?: object): Promise<any>
fetchClobAuth(path: string, body?: object): Promise<any> // signs L1 + L2 headers
Result type — every bot returns this
type BotResult<Out> =
| { ok: true; out: Out; inputs_used: string[]; checked_at: IsoTimestamp }
| { ok: false; error: string; reason_code: string; severity: Severity; inputs_used: string[]; checked_at: IsoTimestamp };
type Severity = "HARD_REJECT" | "RESHAPE" | "WARN" | "EXPLAIN" | "INFO" | "P0" | "P1";
What the SDK does not do
- It does not submit orders. Order submission is owned by SmartRouter.
- It does not read or write user wallets. Signing happens through the wallet adapter, never the SDK.
- It does not maintain runtime state. Every helper is pure and idempotent.
- It does not retry network calls beyond a single transparent retry on 5xx. Retry policy lives in each bot's runbook.
Versioning
The SDK is versioned independently from any bot. A breaking change increments the major version. Bots declare which SDK major they target in their version block. Two SDK majors may run side-by-side during migration.