Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board

← Principles & schemas

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

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.