From a062991197387889a55e7a92bb72f4c7231b16e6 Mon Sep 17 00:00:00 2001 From: Etienne Donneger Date: Tue, 12 May 2026 15:51:33 -0400 Subject: [PATCH] feat(polymarket): support batched filter parameters on 3 endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter params now accept a single value, a comma-separated list, or a repeated query key — matching the batched-parameter convention already used on EVM/SVM/TVM and Hyperliquid routes. Batched fields per endpoint: - `/markets` condition_id, market_slug, token_id, event_slug - `/users` user - `/users/positions` user, token_id, condition_id, market_slug The remaining Polymarket endpoints stay scalar for now: - `/markets/activity` is a UNION ALL fan-out across 5 ledger tables; warrants a separate perf review before batching. - `/markets/oi` keeps its "provide exactly one of condition_id or market_slug" contract; adding multi-value support there changes the response semantic. - `/markets/ohlc` and `/markets/positions` are per-token time-series / leaderboard surfaces — mirrors the scalar-only contract on Hyperliquid OHLC and EVM/SVM/TVM `/pools/ohlc`. Side effects: - `/markets` SQL switches from a CASE-dispatch WHERE clause (first non-null filter wins) to additive AND filters. This is the same shape used elsewhere in the codebase. Behavior change for callers that previously passed two scoping filters at once (e.g. `condition_id=A&market_slug=B`): the CASE silently used the first non-null one and ignored the rest. After this change, both filters apply together, so mismatched combinations now correctly return an empty result. - `token_id` resolution in `/markets` and `/users/positions` now uses `asset_id IN (SELECT toUInt256(arrayJoin(...)))` so multiple token ids resolve to the union of their condition ids. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/routes/polymarket/markets.sql | 29 +++++++++-------------------- src/routes/polymarket/markets.ts | 8 ++++---- src/routes/polymarket/positions.sql | 8 ++++---- src/routes/polymarket/positions.ts | 8 ++++---- src/routes/polymarket/users.sql | 2 +- src/routes/polymarket/users.ts | 2 +- 6 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/routes/polymarket/markets.sql b/src/routes/polymarket/markets.sql index fd96b906..367d191e 100644 --- a/src/routes/polymarket/markets.sql +++ b/src/routes/polymarket/markets.sql @@ -1,11 +1,8 @@ -WITH resolved_token AS ( - SELECT condition_id +WITH token_condition_ids AS ( + SELECT DISTINCT condition_id FROM {db_scraper:Identifier}.polymarket_markets_by_asset_id - WHERE isNull({condition_id:Nullable(String)}) - AND isNull({market_slug:Nullable(String)}) - AND isNotNull({token_id:Nullable(String)}) - AND asset_id = toUInt256({token_id:Nullable(String)}) - LIMIT 1 + WHERE notEmpty({token_id:Array(String)}) + AND asset_id IN (SELECT toUInt256(arrayJoin({token_id:Array(String)}))) ) SELECT m.condition_id, @@ -26,19 +23,11 @@ SELECT FROM {db_scraper:Identifier}.polymarket_markets m FINAL LEFT JOIN {db_scraper:Identifier}.polymarket_events e FINAL ON e.condition_id = m.condition_id -WHERE - CASE - WHEN isNotNull({condition_id:Nullable(String)}) - THEN m.condition_id = {condition_id:Nullable(String)} - WHEN isNotNull({market_slug:Nullable(String)}) - THEN m.market_slug = {market_slug:Nullable(String)} - WHEN isNotNull({token_id:Nullable(String)}) - THEN m.condition_id = (SELECT condition_id FROM resolved_token) - WHEN isNotNull({event_slug:Nullable(String)}) - THEN e.slug = {event_slug:Nullable(String)} - ELSE 1 = 1 - END - AND (isNull({closed:Nullable(UInt8)}) OR m.closed = {closed:Nullable(UInt8)}) +WHERE (empty({condition_id:Array(String)}) OR m.condition_id IN {condition_id:Array(String)}) + AND (empty({market_slug:Array(String)}) OR m.market_slug IN {market_slug:Array(String)}) + AND (empty({token_id:Array(String)}) OR m.condition_id IN (SELECT condition_id FROM token_condition_ids)) + AND (empty({event_slug:Array(String)}) OR e.slug IN {event_slug:Array(String)}) + AND (isNull({closed:Nullable(UInt8)}) OR m.closed = {closed:Nullable(UInt8)}) ORDER BY m.volume_num DESC LIMIT {limit:UInt64} OFFSET {offset:UInt64} diff --git a/src/routes/polymarket/markets.ts b/src/routes/polymarket/markets.ts index 1b007839..809221df 100644 --- a/src/routes/polymarket/markets.ts +++ b/src/routes/polymarket/markets.ts @@ -18,10 +18,10 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './markets.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - condition_id: { schema: polymarketConditionIdSchema, optional: true }, - market_slug: { schema: polymarketSlugSchema, optional: true }, - token_id: { schema: polymarketTokenIdSchema, optional: true }, - event_slug: { schema: polymarketSlugSchema, optional: true }, + condition_id: { schema: polymarketConditionIdSchema, batched: true, optional: true }, + market_slug: { schema: polymarketSlugSchema, batched: true, optional: true }, + token_id: { schema: polymarketTokenIdSchema, batched: true, optional: true }, + event_slug: { schema: polymarketSlugSchema, batched: true, optional: true }, closed: { schema: booleanFromString, optional: true }, sort_by: { schema: polymarketMarketSortBySchema, prefault: 'volume' }, }); diff --git a/src/routes/polymarket/positions.sql b/src/routes/polymarket/positions.sql index d130a5b5..a8cb4735 100644 --- a/src/routes/polymarket/positions.sql +++ b/src/routes/polymarket/positions.sql @@ -22,8 +22,8 @@ LEFT JOIN {db_scraper:Identifier}.polymarket_markets_by_asset_id a LEFT JOIN {db_polymarket:Identifier}.state_latest_price lp FINAL ON lp.asset_id = p.token_id WHERE p.interval_min = 0 - AND p.user = {user:String} - AND (isNull({token_id:Nullable(String)}) OR p.token_id = toUInt256({token_id:Nullable(String)})) - AND (isNull({condition_id:Nullable(String)}) OR a.condition_id = {condition_id:Nullable(String)}) - AND (isNull({market_slug:Nullable(String)}) OR a.market_slug = {market_slug:Nullable(String)}) + AND p.user IN {user:Array(String)} + AND (empty({token_id:Array(String)}) OR p.token_id IN (SELECT toUInt256(arrayJoin({token_id:Array(String)})))) + AND (empty({condition_id:Array(String)}) OR a.condition_id IN {condition_id:Array(String)}) + AND (empty({market_slug:Array(String)}) OR a.market_slug IN {market_slug:Array(String)}) GROUP BY p.user, p.token_id, a.condition_id, a.market_slug, a.outcome_label, a.closed, lp.close diff --git a/src/routes/polymarket/positions.ts b/src/routes/polymarket/positions.ts index a28b6254..b044c006 100644 --- a/src/routes/polymarket/positions.ts +++ b/src/routes/polymarket/positions.ts @@ -19,10 +19,10 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './positions.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - user: { schema: evmAddress }, - token_id: { schema: polymarketTokenIdSchema, optional: true }, - condition_id: { schema: polymarketConditionIdSchema, optional: true }, - market_slug: { schema: polymarketSlugSchema, optional: true }, + user: { schema: evmAddress, batched: true }, + token_id: { schema: polymarketTokenIdSchema, batched: true, optional: true }, + condition_id: { schema: polymarketConditionIdSchema, batched: true, optional: true }, + market_slug: { schema: polymarketSlugSchema, batched: true, optional: true }, closed: { schema: booleanFromString, optional: true }, sort_by: { schema: polymarketPositionSortBySchema, prefault: 'position_value' }, }); diff --git a/src/routes/polymarket/users.sql b/src/routes/polymarket/users.sql index 92bb9400..05ec5bb7 100644 --- a/src/routes/polymarket/users.sql +++ b/src/routes/polymarket/users.sql @@ -13,4 +13,4 @@ SELECT last_trade FROM {db_polymarket:Identifier}.state_user FINAL WHERE interval_min = {interval_min:UInt32} - AND (isNull({user:Nullable(String)}) OR user = {user:Nullable(String)}) + AND (empty({user:Array(String)}) OR user IN {user:Array(String)}) diff --git a/src/routes/polymarket/users.ts b/src/routes/polymarket/users.ts index 3bd1155f..d5bd5c7b 100644 --- a/src/routes/polymarket/users.ts +++ b/src/routes/polymarket/users.ts @@ -16,7 +16,7 @@ import { validatorHook, withErrorResponses } from '../../utils.js'; import baseQuery from './users.sql' with { type: 'text' }; const querySchema = createQuerySchema({ - user: { schema: evmAddress, optional: true }, + user: { schema: evmAddress, batched: true, optional: true }, interval: { schema: userLookbackIntervalSchema, optional: true }, sort_by: { schema: polymarketUserSortBySchema, prefault: 'total_volume' }, });