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
Exchange
polymarket_us
Severity
HIGH
What Our Normalizer Expects
core/src/exchanges/polymarket_us/normalizer.ts:452-463readsbook.bids,book.offers, andbook.transactTimedirectly at the top level of the order book object passed tonormalizeOrderBook:The WebSocket handler at
websocket.ts:161explicitly unwraps themarketDataenvelope before callingnormalizeOrderBook:What The Live API Returns
The live
GET https://gateway.polymarket.us/v1/markets/{slug}/bookresponse wraps the order book inside amarketDataobject —bidsandoffersare NOT at the top level:Endpoint tested:
GET https://gateway.polymarket.us/v1/markets/{slug}/bookImpact
HIGH: When the REST fetcher passes the raw SDK response to
normalizeOrderBookwithout unwrappingmarketData, the normalizer sees:book.bids=undefined→ defaults to[]book.offers=undefined→ defaults to[]book.transactTime=undefined→ defaults toDate.now()The order book for every polymarket_us market appears permanently empty. The WebSocket path works correctly because it explicitly does
msg.marketDataextraction — confirming the REST path is the missing equivalent.Suggested Fix
In the REST fetcher (
core/src/exchanges/polymarket_us/index.ts), unwrap themarketDataenvelope before passing to the normalizer — mirroring what the WebSocket handler already does:Alternatively, update
normalizeOrderBookto detect and unwrap the envelope:Found by automated response shape drift audit