pmxt-core 2.46.14 → 2.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/BaseExchange.d.ts +42 -1
  2. package/dist/BaseExchange.js +29 -2
  3. package/dist/exchanges/baozi/index.d.ts +1 -0
  4. package/dist/exchanges/baozi/index.js +5 -0
  5. package/dist/exchanges/baozi/normalizer.js +2 -0
  6. package/dist/exchanges/baozi/utils.js +28 -0
  7. package/dist/exchanges/gemini-titan/index.d.ts +6 -2
  8. package/dist/exchanges/gemini-titan/index.js +81 -1
  9. package/dist/exchanges/gemini-titan/normalizer.d.ts +7 -1
  10. package/dist/exchanges/gemini-titan/normalizer.js +54 -0
  11. package/dist/exchanges/hyperliquid/index.d.ts +3 -0
  12. package/dist/exchanges/hyperliquid/index.js +7 -0
  13. package/dist/exchanges/hyperliquid/normalizer.js +20 -0
  14. package/dist/exchanges/kalshi/api.d.ts +1 -1
  15. package/dist/exchanges/kalshi/api.js +1 -1
  16. package/dist/exchanges/kalshi/fetcher.d.ts +10 -0
  17. package/dist/exchanges/kalshi/fetcher.js +36 -0
  18. package/dist/exchanges/kalshi/index.d.ts +3 -2
  19. package/dist/exchanges/kalshi/index.js +33 -0
  20. package/dist/exchanges/kalshi/normalizer.d.ts +3 -2
  21. package/dist/exchanges/kalshi/normalizer.js +35 -0
  22. package/dist/exchanges/limitless/api.d.ts +1 -1
  23. package/dist/exchanges/limitless/api.js +1 -1
  24. package/dist/exchanges/limitless/index.d.ts +1 -0
  25. package/dist/exchanges/limitless/index.js +5 -0
  26. package/dist/exchanges/limitless/normalizer.js +10 -0
  27. package/dist/exchanges/limitless/utils.js +17 -0
  28. package/dist/exchanges/metaculus/fetchEvents.js +6 -0
  29. package/dist/exchanges/metaculus/index.d.ts +3 -0
  30. package/dist/exchanges/metaculus/index.js +3 -0
  31. package/dist/exchanges/metaculus/utils.d.ts +2 -1
  32. package/dist/exchanges/metaculus/utils.js +32 -3
  33. package/dist/exchanges/myriad/api.d.ts +1 -1
  34. package/dist/exchanges/myriad/api.js +1 -1
  35. package/dist/exchanges/myriad/index.d.ts +1 -0
  36. package/dist/exchanges/myriad/index.js +5 -0
  37. package/dist/exchanges/myriad/normalizer.js +14 -0
  38. package/dist/exchanges/myriad/utils.js +14 -0
  39. package/dist/exchanges/opinion/api.d.ts +1 -1
  40. package/dist/exchanges/opinion/api.js +1 -1
  41. package/dist/exchanges/opinion/index.d.ts +6 -2
  42. package/dist/exchanges/opinion/index.js +54 -1
  43. package/dist/exchanges/opinion/normalizer.d.ts +8 -2
  44. package/dist/exchanges/opinion/normalizer.js +42 -0
  45. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  46. package/dist/exchanges/polymarket/api-clob.js +1 -1
  47. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  48. package/dist/exchanges/polymarket/api-data.js +1 -1
  49. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  50. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  51. package/dist/exchanges/polymarket/index.d.ts +13 -2
  52. package/dist/exchanges/polymarket/index.js +49 -0
  53. package/dist/exchanges/polymarket/normalizer.d.ts +2 -1
  54. package/dist/exchanges/polymarket/normalizer.js +48 -0
  55. package/dist/exchanges/polymarket/utils.js +28 -0
  56. package/dist/exchanges/polymarket_us/index.d.ts +3 -2
  57. package/dist/exchanges/polymarket_us/index.js +57 -0
  58. package/dist/exchanges/polymarket_us/normalizer.d.ts +9 -2
  59. package/dist/exchanges/polymarket_us/normalizer.js +42 -0
  60. package/dist/exchanges/probable/api.d.ts +1 -1
  61. package/dist/exchanges/probable/api.js +1 -1
  62. package/dist/exchanges/probable/index.d.ts +3 -0
  63. package/dist/exchanges/probable/index.js +7 -0
  64. package/dist/exchanges/probable/utils.js +17 -0
  65. package/dist/exchanges/smarkets/index.d.ts +1 -0
  66. package/dist/exchanges/smarkets/index.js +5 -0
  67. package/dist/exchanges/smarkets/normalizer.js +20 -0
  68. package/dist/exchanges/suibets/index.d.ts +1 -0
  69. package/dist/exchanges/suibets/index.js +5 -0
  70. package/dist/exchanges/suibets/normalizer.js +20 -0
  71. package/dist/router/Router.d.ts +29 -2
  72. package/dist/router/Router.js +145 -0
  73. package/dist/router/index.d.ts +1 -0
  74. package/dist/router/index.js +1 -0
  75. package/dist/router/series-map.d.ts +32 -0
  76. package/dist/router/series-map.js +146 -0
  77. package/dist/server/method-verbs.json +10 -0
  78. package/dist/server/openapi.yaml +132 -0
  79. package/dist/types.d.ts +35 -0
  80. package/dist/utils/metadata.d.ts +14 -0
  81. package/dist/utils/metadata.js +33 -0
  82. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
  import { SubscribedAddressSnapshot, SubscriptionOption } from './subscriber/base';
3
- import { Balance, BuiltOrder, CandleInterval, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UserTrade } from './types';
3
+ import { Balance, BuiltOrder, CandleInterval, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UnifiedSeries, UserTrade } from './types';
4
4
  import { ExecutionPriceResult } from './utils/math';
5
5
  import type { FetchMarketMatchesParams, FetchMatchesParams, FetchEventMatchesParams, FetchArbitrageParams, FetchMatchedMarketsParams, FetchMatchedPricesParams, MatchResult, EventMatchResult, PriceComparison, ArbitrageOpportunity, MatchedMarketPair, MatchedPricePair } from './router/types';
6
6
  export interface ApiEndpoint {
@@ -74,6 +74,8 @@ export interface EventFetchParams {
74
74
  searchIn?: 'title' | 'description' | 'both';
75
75
  eventId?: string;
76
76
  slug?: string;
77
+ /** Filter events by their parent series. Accepts the venue-native series id / ticker / slug (e.g. Kalshi `"KXATPMATCH"`, Polymarket `"wta"`). Passed through to the vendor where supported, otherwise applied to `sourceMetadata` after fetch. */
78
+ series?: string;
77
79
  /** Optional client-side filter applied after fetching */
78
80
  filter?: EventFilterCriteria;
79
81
  /** Filter by category. Each event belongs to a venue-assigned category such as "Sports", "Politics", "Crypto", "Bitcoin", "Soccer", "Economic Policy" (Polymarket) or "Sports", "Mentions" (Kalshi). */
@@ -81,6 +83,24 @@ export interface EventFetchParams {
81
83
  /** Filter by tags. Returns events matching ANY of the provided tags. Tags are more specific than categories -- for example a "Politics" event might carry tags ["Politics", "Geopolitics", "Middle East", "Iran"]. Common tags include "Crypto", "Elections", "Fed Rates", "FIFA World Cup", "Trump". */
82
84
  tags?: string[];
83
85
  }
86
+ /**
87
+ * Parameters for `fetchSeries`. Venues that don't expose a series concept
88
+ * return an empty array regardless of the filters.
89
+ */
90
+ export interface SeriesFetchParams {
91
+ /** Direct lookup by venue-native series id (e.g. "KXATPMATCH" on Kalshi, "atp" or "1" on Polymarket Gamma). When set, the result is the matching series with its events populated where the venue supports it. */
92
+ id?: string;
93
+ /** Lookup by series slug (e.g. "wta", "nfl"). */
94
+ slug?: string;
95
+ /** Keyword search across series title / description. */
96
+ query?: string;
97
+ /** Filter by recurrence cadence ('daily', 'weekly', 'annual', ...). */
98
+ recurrence?: string;
99
+ /** Maximum number of results to return. */
100
+ limit?: number;
101
+ /** Pagination offset. */
102
+ offset?: number;
103
+ }
84
104
  /**
85
105
  * Deprecated - use OHLCVParams or TradesParams instead. Resolution is optional for backward compatibility.
86
106
  */
@@ -219,6 +239,8 @@ export interface ExchangeHas {
219
239
  fetchMarkets: ExchangeCapability;
220
240
  /** Whether this exchange supports fetching events. */
221
241
  fetchEvents: ExchangeCapability;
242
+ /** Whether this exchange exposes a recurring-series concept (Series -> Event -> Market -> Outcome). Venues without one return `false` and an empty array from `fetchSeries`. */
243
+ fetchSeries: ExchangeCapability;
222
244
  /** Whether this exchange supports fetching OHLCV candles. */
223
245
  fetchOHLCV: ExchangeCapability;
224
246
  /** Whether this exchange supports fetching the order book. */
@@ -451,6 +473,18 @@ export declare abstract class PredictionMarketExchange {
451
473
  * @note Some exchanges (like Limitless) may only support status 'active' for search results.
452
474
  */
453
475
  fetchEvents(params?: EventFetchParams): Promise<UnifiedEvent[]>;
476
+ /**
477
+ * Fetch the recurring series (fourth tier above Event -> Market -> Outcome)
478
+ * that this venue exposes. Returns an empty array on venues without a
479
+ * series concept (Limitless, Smarkets, Probable, Metaculus, Baozi,
480
+ * Hyperliquid, SuiBets, Polymarket US).
481
+ *
482
+ * - `params.id` -> a single matching series with its events populated where supported.
483
+ * - no params -> the full list, typically without nested events for payload size.
484
+ *
485
+ * @returns Array of unified series. Always an array, including the singular-lookup case.
486
+ */
487
+ fetchSeries(params?: SeriesFetchParams): Promise<UnifiedSeries[]>;
454
488
  /**
455
489
  * Fetch a single market by lookup parameters.
456
490
  * Convenience wrapper around fetchMarkets() that returns a single result or throws MarketNotFound.
@@ -782,6 +816,13 @@ export declare abstract class PredictionMarketExchange {
782
816
  * Implementation for searching events by keyword.
783
817
  */
784
818
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
819
+ /**
820
+ * @internal
821
+ * Implementation for fetching recurring series. Override in venue adapters
822
+ * that expose a series concept; the default returns an empty array so
823
+ * venues without one are silently a no-op.
824
+ */
825
+ protected fetchSeriesImpl(_params: SeriesFetchParams): Promise<UnifiedSeries[]>;
785
826
  /**
786
827
  * Call an implicit API method by its operationId (or auto-generated name).
787
828
  * Provides a typed entry point so unified methods can delegate to the implicit API
@@ -361,6 +361,23 @@ class PredictionMarketExchange {
361
361
  const start = offset ?? 0;
362
362
  return limit !== undefined ? events.slice(start, start + limit) : events.slice(start);
363
363
  }
364
+ /**
365
+ * Fetch the recurring series (fourth tier above Event -> Market -> Outcome)
366
+ * that this venue exposes. Returns an empty array on venues without a
367
+ * series concept (Limitless, Smarkets, Probable, Metaculus, Baozi,
368
+ * Hyperliquid, SuiBets, Polymarket US).
369
+ *
370
+ * - `params.id` -> a single matching series with its events populated where supported.
371
+ * - no params -> the full list, typically without nested events for payload size.
372
+ *
373
+ * @returns Array of unified series. Always an array, including the singular-lookup case.
374
+ */
375
+ async fetchSeries(params) {
376
+ const { limit, offset, ...venueParams } = params ?? {};
377
+ const series = await this.fetchSeriesImpl(venueParams);
378
+ const start = offset ?? 0;
379
+ return limit !== undefined ? series.slice(start, start + limit) : series.slice(start);
380
+ }
364
381
  /**
365
382
  * Fetch a single market by lookup parameters.
366
383
  * Convenience wrapper around fetchMarkets() that returns a single result or throws MarketNotFound.
@@ -1056,6 +1073,15 @@ class PredictionMarketExchange {
1056
1073
  async fetchEventsImpl(params) {
1057
1074
  throw new Error("Method fetchEventsImpl not implemented.");
1058
1075
  }
1076
+ /**
1077
+ * @internal
1078
+ * Implementation for fetching recurring series. Override in venue adapters
1079
+ * that expose a series concept; the default returns an empty array so
1080
+ * venues without one are silently a no-op.
1081
+ */
1082
+ async fetchSeriesImpl(_params) {
1083
+ return [];
1084
+ }
1059
1085
  /**
1060
1086
  * Call an implicit API method by its operationId (or auto-generated name).
1061
1087
  * Provides a typed entry point so unified methods can delegate to the implicit API
@@ -1163,7 +1189,7 @@ class PredictionMarketExchange {
1163
1189
  // ----------------------------------------------------------------------------
1164
1190
  /** All keys that appear in ExchangeHas -- kept in sync via the exhaustive check below. */
1165
1191
  static _capabilityKeys = [
1166
- 'fetchMarkets', 'fetchEvents', 'fetchOHLCV', 'fetchOrderBook', 'fetchOrderBooks',
1192
+ 'fetchMarkets', 'fetchEvents', 'fetchSeries', 'fetchOHLCV', 'fetchOrderBook', 'fetchOrderBooks',
1167
1193
  'fetchTrades', 'createOrder', 'cancelOrder', 'fetchOrder',
1168
1194
  'fetchOpenOrders', 'fetchPositions', 'fetchBalance',
1169
1195
  'watchAddress', 'unwatchAddress', 'watchOrderBook', 'watchOrderBooks',
@@ -1175,7 +1201,7 @@ class PredictionMarketExchange {
1175
1201
  // Compile-time exhaustiveness check: fails tsc if a key exists in
1176
1202
  // ExchangeHas but is missing from _capabilityKeys above.
1177
1203
  static _exhaustiveCheck = {
1178
- fetchMarkets: true, fetchEvents: true, fetchOHLCV: true,
1204
+ fetchMarkets: true, fetchEvents: true, fetchSeries: true, fetchOHLCV: true,
1179
1205
  fetchOrderBook: true, fetchOrderBooks: true, fetchTrades: true, createOrder: true,
1180
1206
  cancelOrder: true, fetchOrder: true, fetchOpenOrders: true,
1181
1207
  fetchPositions: true, fetchBalance: true, watchAddress: true,
@@ -1194,6 +1220,7 @@ class PredictionMarketExchange {
1194
1220
  static _capabilityDelegates = {
1195
1221
  fetchMarkets: 'fetchMarketsImpl',
1196
1222
  fetchEvents: 'fetchEventsImpl',
1223
+ fetchSeries: 'fetchSeriesImpl',
1197
1224
  watchOrderBooks: 'watchOrderBook',
1198
1225
  fetchMatches: 'fetchMarketMatches',
1199
1226
  fetchHedges: 'fetchRelatedMarkets',
@@ -12,6 +12,7 @@ export declare class BaoziExchange extends PredictionMarketExchange {
12
12
  fetchOpenOrders: "emulated";
13
13
  cancelOrder: false;
14
14
  watchTrades: false;
15
+ fetchSeries: false;
15
16
  };
16
17
  private auth?;
17
18
  private connection;
@@ -19,6 +19,7 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
19
19
  fetchOpenOrders: 'emulated',
20
20
  cancelOrder: false,
21
21
  watchTrades: false,
22
+ fetchSeries: false,
22
23
  };
23
24
  auth;
24
25
  connection;
@@ -59,6 +60,10 @@ class BaoziExchange extends BaseExchange_1.PredictionMarketExchange {
59
60
  return this.normalizer.normalizeMarkets(rawMarkets, params);
60
61
  }
61
62
  async fetchEventsImpl(params) {
63
+ // Venue does not expose a series concept; honoring `params.series` by returning [] rather than ignoring the filter.
64
+ if (params.series !== undefined) {
65
+ return [];
66
+ }
62
67
  const rawMarkets = await this.fetcher.fetchRawEvents(params);
63
68
  return this.normalizer.normalizeEvents(rawMarkets, {
64
69
  query: params.query,
@@ -35,6 +35,7 @@ class BaoziNormalizer {
35
35
  image: market.image,
36
36
  category: market.category,
37
37
  tags: market.tags,
38
+ sourceMetadata: market.sourceMetadata,
38
39
  };
39
40
  }
40
41
  normalizeMarkets(rawMarkets, params) {
@@ -60,6 +61,7 @@ class BaoziNormalizer {
60
61
  image: m.image,
61
62
  category: m.category,
62
63
  tags: m.tags,
64
+ sourceMetadata: m.sourceMetadata,
63
65
  }));
64
66
  }
65
67
  normalizeOrderBook(raw, outcomeId) {
@@ -20,6 +20,7 @@ const web3_js_1 = require("@solana/web3.js");
20
20
  const bs58_1 = __importDefault(require("bs58"));
21
21
  const crypto_1 = require("crypto");
22
22
  const market_utils_1 = require("../../utils/market-utils");
23
+ const metadata_1 = require("../../utils/metadata");
23
24
  const price_1 = require("./price");
24
25
  // ---------------------------------------------------------------------------
25
26
  // Constants
@@ -285,6 +286,31 @@ function parseRacePosition(data) {
285
286
  return { user, marketId, bets, totalBet, claimed };
286
287
  }
287
288
  // ---------------------------------------------------------------------------
289
+ // Promoted key sets — fields already represented by first-class unified columns.
290
+ // These are excluded from sourceMetadata to avoid duplication.
291
+ // ---------------------------------------------------------------------------
292
+ // BaoziMarket fields promoted to unified columns:
293
+ // question -> title
294
+ // resolutionTime -> resolutionDate
295
+ // yesPool / noPool -> volume + liquidity
296
+ // status -> status
297
+ // pubkey is promoted to marketId; outcomeLabels / outcomePools -> outcomes
298
+ const BAOZI_BOOLEAN_PROMOTED_KEYS = [
299
+ 'question',
300
+ 'resolutionTime',
301
+ 'yesPool',
302
+ 'noPool',
303
+ 'status',
304
+ ];
305
+ const BAOZI_RACE_PROMOTED_KEYS = [
306
+ 'question',
307
+ 'resolutionTime',
308
+ 'totalPool',
309
+ 'status',
310
+ 'outcomeLabels',
311
+ 'outcomePools',
312
+ ];
313
+ // ---------------------------------------------------------------------------
288
314
  // Mapping to Unified Types
289
315
  // ---------------------------------------------------------------------------
290
316
  function mapBooleanToUnified(market, pubkey) {
@@ -326,6 +352,7 @@ function mapBooleanToUnified(market, pubkey) {
326
352
  url: `https://baozi.bet/market/${pubkey}`,
327
353
  category: undefined,
328
354
  tags: [`tier:${layerName(market.layer)}`, 'solana', 'pari-mutuel'],
355
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(market, BAOZI_BOOLEAN_PROMOTED_KEYS),
329
356
  };
330
357
  (0, market_utils_1.addBinaryOutcomes)(um);
331
358
  return um;
@@ -361,6 +388,7 @@ function mapRaceToUnified(market, pubkey) {
361
388
  url: `https://baozi.bet/market/${pubkey}`,
362
389
  category: undefined,
363
390
  tags: [`tier:${layerName(market.layer)}`, 'solana', 'pari-mutuel', 'race'],
391
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(market, BAOZI_RACE_PROMOTED_KEYS),
364
392
  };
365
393
  // For 2-outcome races, add binary convenience getters
366
394
  (0, market_utils_1.addBinaryOutcomes)(um);
@@ -1,5 +1,5 @@
1
- import { PredictionMarketExchange, MarketFilterParams, EventFetchParams, ExchangeCredentials } from '../../BaseExchange';
2
- import { UnifiedMarket, UnifiedEvent, OrderBook, Trade, Order, Position, CreateOrderParams, BuiltOrder } from '../../types';
1
+ import { PredictionMarketExchange, MarketFilterParams, EventFetchParams, SeriesFetchParams, ExchangeCredentials } from '../../BaseExchange';
2
+ import { UnifiedMarket, UnifiedEvent, UnifiedSeries, OrderBook, Trade, Order, Position, CreateOrderParams, BuiltOrder } from '../../types';
3
3
  export interface GeminiTitanExchangeOptions {
4
4
  credentials?: ExchangeCredentials;
5
5
  sandbox?: boolean;
@@ -10,11 +10,15 @@ export declare class GeminiTitanExchange extends PredictionMarketExchange {
10
10
  private readonly normalizer;
11
11
  private readonly geminiAuth?;
12
12
  private geminiWs?;
13
+ protected readonly capabilityOverrides: {
14
+ fetchSeries: "emulated";
15
+ };
13
16
  constructor(credentials?: ExchangeCredentials | GeminiTitanExchangeOptions);
14
17
  get name(): string;
15
18
  private requireAuth;
16
19
  protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
17
20
  protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
21
+ protected fetchSeriesImpl(params: SeriesFetchParams): Promise<UnifiedSeries[]>;
18
22
  fetchOrderBook(outcomeId: string, _limit?: number, _params?: Record<string, any>): Promise<OrderBook>;
19
23
  buildOrder(params: CreateOrderParams): Promise<BuiltOrder>;
20
24
  submitOrder(built: BuiltOrder): Promise<Order>;
@@ -16,6 +16,9 @@ class GeminiTitanExchange extends BaseExchange_1.PredictionMarketExchange {
16
16
  normalizer;
17
17
  geminiAuth;
18
18
  geminiWs;
19
+ capabilityOverrides = {
20
+ fetchSeries: 'emulated',
21
+ };
19
22
  constructor(credentials) {
20
23
  const opts = credentials && 'credentials' in credentials
21
24
  ? credentials
@@ -63,10 +66,87 @@ class GeminiTitanExchange extends BaseExchange_1.PredictionMarketExchange {
63
66
  }
64
67
  async fetchEventsImpl(params) {
65
68
  const rawEvents = await this.fetcher.fetchRawEvents(params);
66
- return rawEvents
69
+ let filtered = rawEvents;
70
+ // Client-side series filter: keep only events whose series id matches.
71
+ if (params.series) {
72
+ const seriesId = params.series;
73
+ filtered = rawEvents.filter((e) => {
74
+ if (e.series == null)
75
+ return false;
76
+ const s = e.series;
77
+ const id = String(s['id'] ?? s['ticker'] ?? s['symbol'] ?? '');
78
+ return id === seriesId;
79
+ });
80
+ }
81
+ return filtered
67
82
  .map(e => this.normalizer.normalizeEventWithMarkets(e))
68
83
  .filter((e) => e !== null);
69
84
  }
85
+ async fetchSeriesImpl(params) {
86
+ // Gemini-Titan has no dedicated /series endpoint. Derive the catalog by
87
+ // fetching all events and grouping by the series id in event.series.
88
+ const rawEvents = await this.fetcher.fetchRawEvents({});
89
+ // Build a map from series id -> { rawSeries, rawEvents[] }
90
+ const seriesMap = new Map();
91
+ for (const event of rawEvents) {
92
+ if (event.series == null)
93
+ continue;
94
+ const s = event.series;
95
+ const id = String(s['id'] ?? s['ticker'] ?? s['symbol'] ?? '');
96
+ if (!id)
97
+ continue;
98
+ const existing = seriesMap.get(id);
99
+ if (existing) {
100
+ existing.raws.push(event);
101
+ }
102
+ else {
103
+ seriesMap.set(id, { raw: s, raws: [event] });
104
+ }
105
+ }
106
+ let entries = Array.from(seriesMap.entries()).map(([id, v]) => ({
107
+ id,
108
+ raw: v.raw,
109
+ raws: v.raws,
110
+ }));
111
+ // Apply params.id filter
112
+ if (params.id) {
113
+ entries = entries.filter((e) => e.id === params.id);
114
+ }
115
+ // Apply params.slug filter (treat slug same as id for Gemini series)
116
+ if (params.slug) {
117
+ const slug = params.slug;
118
+ entries = entries.filter((e) => {
119
+ const rawSlug = e.raw['slug'] != null ? String(e.raw['slug']) : e.id;
120
+ return rawSlug === slug;
121
+ });
122
+ }
123
+ // Apply params.query filter (title match)
124
+ if (params.query) {
125
+ const lowerQuery = params.query.toLowerCase();
126
+ entries = entries.filter((e) => {
127
+ const title = String(e.raw['title'] ?? e.raw['name'] ?? '');
128
+ return title.toLowerCase().includes(lowerQuery);
129
+ });
130
+ }
131
+ // Apply params.recurrence filter
132
+ if (params.recurrence) {
133
+ const recurrence = params.recurrence;
134
+ entries = entries.filter((e) => {
135
+ const freq = e.raw['frequency'] ?? e.raw['recurrence'];
136
+ return freq != null && String(freq) === recurrence;
137
+ });
138
+ }
139
+ return entries.map((e) => {
140
+ let events;
141
+ // When fetching by id, populate the events field.
142
+ if (params.id) {
143
+ events = e.raws
144
+ .map((raw) => this.normalizer.normalizeEventWithMarkets(raw))
145
+ .filter((ev) => ev !== null);
146
+ }
147
+ return this.normalizer.normalizeSeries(e.raw, events);
148
+ });
149
+ }
70
150
  async fetchOrderBook(outcomeId, _limit, _params) {
71
151
  const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
72
152
  outcomeId = resolved.outcomeId;
@@ -1,10 +1,16 @@
1
- import { UnifiedMarket, UnifiedEvent, OrderBook, Order, Position } from '../../types';
1
+ import { UnifiedMarket, UnifiedEvent, UnifiedSeries, OrderBook, Order, Position } from '../../types';
2
2
  import { IExchangeNormalizer } from '../interfaces';
3
3
  import { GeminiRawEvent, GeminiRawContract, GeminiRawOrder, GeminiRawPosition, GeminiRawOrderBook } from './types';
4
4
  export declare class GeminiNormalizer implements IExchangeNormalizer<GeminiRawEvent, GeminiRawEvent> {
5
5
  normalizeMarket(raw: GeminiRawEvent): UnifiedMarket | null;
6
6
  normalizeEvent(raw: GeminiRawEvent): UnifiedEvent | null;
7
7
  normalizeEventWithMarkets(raw: GeminiRawEvent): UnifiedEvent | null;
8
+ /**
9
+ * Produce a UnifiedSeries from a Gemini `series` object (raw Record).
10
+ * The series id is taken from the first non-null of: id, ticker, symbol.
11
+ * `events` is optionally injected by the caller when doing a single-id lookup.
12
+ */
13
+ normalizeSeries(raw: Record<string, unknown>, events?: UnifiedEvent[]): UnifiedSeries;
8
14
  normalizeMarketsFromEvent(raw: GeminiRawEvent): UnifiedMarket[];
9
15
  normalizeContract(contract: GeminiRawContract, event: GeminiRawEvent): UnifiedMarket | null;
10
16
  normalizeOrderBook(raw: GeminiRawOrderBook, _outcomeId: string): OrderBook;
@@ -2,8 +2,33 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GeminiNormalizer = void 0;
4
4
  const market_utils_1 = require("../../utils/market-utils");
5
+ const metadata_1 = require("../../utils/metadata");
5
6
  const utils_1 = require("./utils");
6
7
  const config_1 = require("./config");
8
+ // Raw GeminiRawEvent fields already promoted to first-class unified columns.
9
+ // Omitted from sourceMetadata so we capture only vendor data the unified shape
10
+ // would otherwise drop.
11
+ const GEMINI_PROMOTED_EVENT_KEYS = [
12
+ 'ticker', // -> id
13
+ 'title',
14
+ 'description',
15
+ 'slug',
16
+ 'contracts', // -> markets (nested child array, always omitted)
17
+ 'volume24h',
18
+ 'imageUrl', // -> image
19
+ 'category',
20
+ 'tags',
21
+ ];
22
+ // Raw GeminiRawContract fields already promoted to first-class unified columns.
23
+ const GEMINI_PROMOTED_CONTRACT_KEYS = [
24
+ 'instrumentSymbol', // -> marketId, slug
25
+ 'label', // -> title
26
+ 'description',
27
+ 'ticker', // parent event ticker feeds eventId
28
+ 'status', // -> status
29
+ 'prices', // -> outcomes prices
30
+ 'expiryDate', // -> resolutionDate
31
+ ];
7
32
  // ----------------------------------------------------------------------------
8
33
  // Helpers
9
34
  // ----------------------------------------------------------------------------
@@ -95,6 +120,7 @@ class GeminiNormalizer {
95
120
  category: raw.category,
96
121
  tags: raw.tags ?? [],
97
122
  image: raw.imageUrl,
123
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, GEMINI_PROMOTED_EVENT_KEYS),
98
124
  };
99
125
  }
100
126
  normalizeEventWithMarkets(raw) {
@@ -115,6 +141,29 @@ class GeminiNormalizer {
115
141
  volume24h: markets.reduce((sum, m) => sum + m.volume24h, 0),
116
142
  };
117
143
  }
144
+ /**
145
+ * Produce a UnifiedSeries from a Gemini `series` object (raw Record).
146
+ * The series id is taken from the first non-null of: id, ticker, symbol.
147
+ * `events` is optionally injected by the caller when doing a single-id lookup.
148
+ */
149
+ normalizeSeries(raw, events) {
150
+ const id = String(raw['id'] ?? raw['ticker'] ?? raw['symbol'] ?? '');
151
+ const ticker = raw['ticker'] != null ? String(raw['ticker']) : undefined;
152
+ const title = String(raw['title'] ?? raw['name'] ?? id);
153
+ const recurrence = raw['frequency'] != null
154
+ ? String(raw['frequency'])
155
+ : raw['recurrence'] != null
156
+ ? String(raw['recurrence'])
157
+ : null;
158
+ return {
159
+ id,
160
+ ticker,
161
+ title,
162
+ recurrence,
163
+ events,
164
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, ['id', 'ticker', 'title', 'name', 'frequency', 'recurrence']),
165
+ };
166
+ }
118
167
  normalizeMarketsFromEvent(raw) {
119
168
  const markets = [];
120
169
  for (const contract of raw.contracts) {
@@ -158,6 +207,10 @@ class GeminiNormalizer {
158
207
  if (event.category && !tags.includes(event.category)) {
159
208
  tags.push(event.category);
160
209
  }
210
+ const seriesExtra = {};
211
+ if (event.series != null) {
212
+ seriesExtra['series'] = event.series;
213
+ }
161
214
  const um = {
162
215
  marketId,
163
216
  eventId: event.ticker,
@@ -173,6 +226,7 @@ class GeminiNormalizer {
173
226
  tags,
174
227
  tickSize: config_1.TICK_SIZE,
175
228
  status: mapEventStatus(contract.status),
229
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(contract, GEMINI_PROMOTED_CONTRACT_KEYS, Object.keys(seriesExtra).length > 0 ? seriesExtra : undefined),
176
230
  };
177
231
  (0, market_utils_1.addBinaryOutcomes)(um);
178
232
  return um;
@@ -5,6 +5,9 @@ export interface HyperliquidExchangeOptions {
5
5
  testnet?: boolean;
6
6
  }
7
7
  export declare class HyperliquidExchange extends PredictionMarketExchange {
8
+ protected readonly capabilityOverrides: {
9
+ fetchSeries: false;
10
+ };
8
11
  private readonly config;
9
12
  private readonly fetcher;
10
13
  private readonly normalizer;
@@ -9,6 +9,9 @@ const normalizer_1 = require("./normalizer");
9
9
  const auth_1 = require("./auth");
10
10
  const errors_2 = require("./errors");
11
11
  class HyperliquidExchange extends BaseExchange_1.PredictionMarketExchange {
12
+ capabilityOverrides = {
13
+ fetchSeries: false,
14
+ };
12
15
  config;
13
16
  fetcher;
14
17
  normalizer;
@@ -69,6 +72,10 @@ class HyperliquidExchange extends BaseExchange_1.PredictionMarketExchange {
69
72
  .filter((m) => m !== null);
70
73
  }
71
74
  async fetchEventsImpl(params) {
75
+ // Venue does not expose a series concept; honoring `params.series` by returning [] rather than ignoring the filter.
76
+ if (params.series !== undefined) {
77
+ return [];
78
+ }
72
79
  const [rawQuestions, meta, mids] = await Promise.all([
73
80
  this.fetcher.fetchRawEvents(params),
74
81
  this.fetcher.fetchOutcomeMeta(),
@@ -2,8 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HyperliquidNormalizer = void 0;
4
4
  const market_utils_1 = require("../../utils/market-utils");
5
+ const metadata_1 = require("../../utils/metadata");
5
6
  const utils_1 = require("./utils");
6
7
  const config_1 = require("./config");
8
+ // Raw Hyperliquid outcome fields already promoted to first-class Unified columns —
9
+ // excluded from sourceMetadata so we capture only what the unified shape would drop.
10
+ const HL_PROMOTED_MARKET_KEYS = [
11
+ 'outcome', // -> marketId
12
+ 'name', // -> title
13
+ 'description', // -> description
14
+ 'sideSpecs', // -> outcomes (labels)
15
+ ];
16
+ // Raw Hyperliquid question fields already promoted to first-class Unified columns.
17
+ // namedOutcomes is omitted as well — it is the child-markets array and must not
18
+ // be duplicated in metadata.
19
+ const HL_PROMOTED_EVENT_KEYS = [
20
+ 'question', // -> id / eventId
21
+ 'name', // -> title
22
+ 'description', // -> description
23
+ 'namedOutcomes', // -> markets (child-markets array)
24
+ ];
7
25
  function parseDescription(description) {
8
26
  // Hyperliquid outcome descriptions use key:value pairs separated by |
9
27
  // e.g. "class:priceBinary|underlying:BTC|expiry:20260509-0600|targetPrice:79583|period:1d"
@@ -165,6 +183,7 @@ class HyperliquidNormalizer {
165
183
  tags,
166
184
  tickSize: 0.001,
167
185
  status: 'active',
186
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(outcome, HL_PROMOTED_MARKET_KEYS),
168
187
  };
169
188
  (0, market_utils_1.addBinaryOutcomes)(um);
170
189
  return um;
@@ -196,6 +215,7 @@ class HyperliquidNormalizer {
196
215
  url: buildEventUrl(raw.question),
197
216
  category: underlying ? 'Crypto' : undefined,
198
217
  tags,
218
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, HL_PROMOTED_EVENT_KEYS),
199
219
  };
200
220
  }
201
221
  normalizeEventWithMarkets(raw, outcomeMeta, mids) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
3
- * Generated at: 2026-05-26T15:22:20.006Z
3
+ * Generated at: 2026-05-30T13:35:15.520Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const kalshiApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.kalshiApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
6
- * Generated at: 2026-05-26T15:22:20.006Z
6
+ * Generated at: 2026-05-30T13:35:15.520Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -44,6 +44,14 @@ interface KalshiSeriesInfo {
44
44
  title?: string;
45
45
  tags?: string[];
46
46
  }
47
+ export interface KalshiRawSeries {
48
+ ticker: string;
49
+ title?: string;
50
+ tags?: string[];
51
+ frequency?: string;
52
+ category?: string;
53
+ [key: string]: unknown;
54
+ }
47
55
  export interface KalshiRawEventPage {
48
56
  events: KalshiRawEvent[];
49
57
  cursor?: string | null;
@@ -153,6 +161,7 @@ export declare class KalshiFetcher implements IExchangeFetcher<KalshiRawEvent, K
153
161
  }>;
154
162
  fetchRawOrders(queryParams: Record<string, any>): Promise<KalshiRawOrder[]>;
155
163
  fetchRawHistoricalOrders(queryParams: Record<string, any>): Promise<KalshiRawOrder[]>;
164
+ fetchRawSeriesList(): Promise<KalshiRawSeries[]>;
156
165
  fetchRawSeriesMap(): Promise<Map<string, KalshiSeriesInfo>>;
157
166
  fetchRawEventByTicker(eventTicker: string): Promise<KalshiRawEvent[]>;
158
167
  private fetchRawEventsDefault;
@@ -160,6 +169,7 @@ export declare class KalshiFetcher implements IExchangeFetcher<KalshiRawEvent, K
160
169
  private enrichEventsWithSeriesMap;
161
170
  private fetchActiveEvents;
162
171
  private fetchAllWithStatus;
172
+ private fetchAllWithSeriesTicker;
163
173
  private fetchPageWithStatus;
164
174
  }
165
175
  export {};
@@ -58,6 +58,11 @@ class KalshiFetcher {
58
58
  if (params.slug) {
59
59
  return this.fetchRawEventByTicker(params.slug);
60
60
  }
61
+ // When a series filter is present, delegate to server-side filtering so
62
+ // only events belonging to that series are returned — no double-filtering.
63
+ if (params.series) {
64
+ return this.fetchAllWithSeriesTicker(params.series);
65
+ }
61
66
  const status = params?.status || 'active';
62
67
  if (status === 'all') {
63
68
  const [openEvents, closedEvents, settledEvents] = await Promise.all([
@@ -213,6 +218,15 @@ class KalshiFetcher {
213
218
  const data = await this.ctx.callApi('GetHistoricalOrders', queryParams);
214
219
  return data.orders || [];
215
220
  }
221
+ async fetchRawSeriesList() {
222
+ try {
223
+ const data = await this.ctx.callApi('GetSeriesList');
224
+ return (data.series || []);
225
+ }
226
+ catch (e) {
227
+ throw errors_1.kalshiErrorMapper.mapError(e);
228
+ }
229
+ }
216
230
  async fetchRawSeriesMap() {
217
231
  try {
218
232
  const data = await this.ctx.callApi('GetSeriesList');
@@ -381,6 +395,28 @@ class KalshiFetcher {
381
395
  } while (cursor && page < MAX_PAGES);
382
396
  return allEvents;
383
397
  }
398
+ async fetchAllWithSeriesTicker(seriesTicker) {
399
+ let allEvents = [];
400
+ let cursor = null;
401
+ let page = 0;
402
+ do {
403
+ const queryParams = {
404
+ series_ticker: seriesTicker,
405
+ with_nested_markets: true,
406
+ limit: BATCH_SIZE,
407
+ };
408
+ if (cursor)
409
+ queryParams.cursor = cursor;
410
+ const data = await this.ctx.callApi('GetEvents', queryParams);
411
+ const events = data.events || [];
412
+ if (events.length === 0)
413
+ break;
414
+ allEvents = [...allEvents, ...events];
415
+ cursor = data.cursor;
416
+ page++;
417
+ } while (cursor && page < MAX_PAGES);
418
+ return allEvents;
419
+ }
384
420
  async fetchPageWithStatus(apiStatus, maxEvents, initialCursor) {
385
421
  let allEvents = [];
386
422
  let cursor = initialCursor || null;