Skip to content

Response drift: polymarket_us — order book REST response wrapped in marketData envelope; normalizer reads top-level bids/offers #1091

Description

@realfishsam

Exchange

polymarket_us

Severity

HIGH

What Our Normalizer Expects

core/src/exchanges/polymarket_us/normalizer.ts:452-463 reads book.bids, book.offers, and book.transactTime directly at the top level of the order book object passed to normalizeOrderBook:

// normalizer.ts:452-463
book.bids   ?? []      // bids array
book.offers ?? []      // asks array
book.transactTime      // timestamp (fallback: Date.now())
book.bids[i].px        // price per level (Required)
book.offers[i].px      // price per level (Required)

The WebSocket handler at websocket.ts:161 explicitly unwraps the marketData envelope before calling normalizeOrderBook:

// websocket.ts:161
const payload = msg.marketData;   // explicit unwrap
// ...
normalizeOrderBook(payload)       // payload has bids/offers at top level

What The Live API Returns

The live GET https://gateway.polymarket.us/v1/markets/{slug}/book response wraps the order book inside a marketData object — bids and offers are NOT at the top level:

marketData.marketSlug: str
marketData.bids: list          ← nested under marketData
marketData.offers: list        ← nested under marketData
marketData.state: str
marketData.stats.*             ← OHLC/stats sub-object
marketData.transactTime: str   ← nested under marketData

Endpoint tested: GET https://gateway.polymarket.us/v1/markets/{slug}/book

Impact

HIGH: When the REST fetcher passes the raw SDK response to normalizeOrderBook without unwrapping marketData, the normalizer sees:

  • book.bids = undefined → defaults to []
  • book.offers = undefined → defaults to []
  • book.transactTime = undefined → defaults to Date.now()

The order book for every polymarket_us market appears permanently empty. The WebSocket path works correctly because it explicitly does msg.marketData extraction — confirming the REST path is the missing equivalent.

Suggested Fix

In the REST fetcher (core/src/exchanges/polymarket_us/index.ts), unwrap the marketData envelope before passing to the normalizer — mirroring what the WebSocket handler already does:

// In fetchOrderBook / wherever markets.book() result is normalized
const raw = response.marketData ?? response;
return normalizeOrderBook(raw);

Alternatively, update normalizeOrderBook to detect and unwrap the envelope:

// normalizer.ts:452
const book = (raw as any).marketData ?? raw;
const bids = book.bids ?? [];

Found by automated response shape drift audit

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions