pmxt-core 2.51.0 → 2.51.2

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 (34) hide show
  1. package/dist/exchanges/hunch/fetcher.d.ts +4 -0
  2. package/dist/exchanges/hunch/fetcher.js +19 -6
  3. package/dist/exchanges/hunch/utils.d.ts +8 -0
  4. package/dist/exchanges/hunch/utils.js +61 -7
  5. package/dist/exchanges/kalshi/api.d.ts +1 -1
  6. package/dist/exchanges/kalshi/api.js +1 -1
  7. package/dist/exchanges/limitless/api.d.ts +1 -1
  8. package/dist/exchanges/limitless/api.js +1 -1
  9. package/dist/exchanges/limitless/index.js +4 -1
  10. package/dist/exchanges/metaculus/index.js +2 -1
  11. package/dist/exchanges/mock/index.d.ts +1 -1
  12. package/dist/exchanges/mock/index.js +37 -3
  13. package/dist/exchanges/myriad/api.d.ts +1 -1
  14. package/dist/exchanges/myriad/api.js +1 -1
  15. package/dist/exchanges/myriad/index.js +1 -1
  16. package/dist/exchanges/opinion/api.d.ts +1 -1
  17. package/dist/exchanges/opinion/api.js +1 -1
  18. package/dist/exchanges/opinion/index.js +1 -1
  19. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  20. package/dist/exchanges/polymarket/api-clob.js +1 -1
  21. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  22. package/dist/exchanges/polymarket/api-data.js +1 -1
  23. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  24. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  25. package/dist/exchanges/polymarket_us/config.js +4 -2
  26. package/dist/exchanges/probable/api.d.ts +1 -1
  27. package/dist/exchanges/probable/api.js +1 -1
  28. package/dist/exchanges/probable/index.js +1 -1
  29. package/dist/exchanges/smarkets/config.js +2 -1
  30. package/dist/server/app.d.ts +9 -0
  31. package/dist/server/app.js +34 -16
  32. package/dist/server/exchange-factory.js +9 -3
  33. package/dist/server/openapi.yaml +1 -0
  34. package/package.json +3 -3
@@ -34,6 +34,10 @@ export interface HunchRawMarket {
34
34
  /** Present in the schema; absent on the bare list endpoint — optional. */
35
35
  volumeUsd?: number;
36
36
  totalBets?: number;
37
+ /** Trailing-24h pool inflow (USD); absent on older API builds — optional. */
38
+ volume24hUsd?: number;
39
+ /** Live binary YES/NO odds on the list item; null/absent for N-way markets. */
40
+ odds?: HunchRawBinaryOdds | null;
37
41
  targetMarketCapUsd: number | null;
38
42
  outcomes: HunchRawOutcome[] | null;
39
43
  headline?: string | null;
@@ -5,6 +5,8 @@ const utils_1 = require("./utils");
5
5
  const errors_1 = require("./errors");
6
6
  const AGENT_PREFIX = '/api/agent/v1';
7
7
  const DEFAULT_LIMIT = 200;
8
+ /** Safety backstop for cursor draining: 50 pages * 200 = 10k markets. */
9
+ const MAX_LIST_PAGES = 50;
8
10
  /**
9
11
  * HunchFetcher — raw GETs against the live agent API. Hunch reads are keyless,
10
12
  * so we hit the HTTP client directly (clearer + more robust than the implicit
@@ -28,6 +30,7 @@ class HunchFetcher {
28
30
  const one = await this.fetchRawMarketById(params.slug);
29
31
  return one ? [one] : [];
30
32
  }
33
+ const explicitLimit = typeof params?.limit === 'number';
31
34
  const query = {
32
35
  limit: params?.limit ?? DEFAULT_LIMIT,
33
36
  };
@@ -36,12 +39,22 @@ class HunchFetcher {
36
39
  query.status = status;
37
40
  if (params?.query)
38
41
  query.token = params.query;
39
- const res = await this.ctx.http.get(`${this.baseUrl}${AGENT_PREFIX}/markets`, {
40
- params: query,
41
- headers: this.ctx.getHeaders(),
42
- });
43
- const markets = res.data?.markets ?? [];
44
- return markets;
42
+ // A catalog crawl (no explicit limit) follows `nextCursor` to drain
43
+ // the whole list; an explicit limit fetches just that one page.
44
+ // MAX_LIST_PAGES is a safety backstop against a runaway cursor loop.
45
+ const all = [];
46
+ let cursor;
47
+ let pages = 0;
48
+ do {
49
+ const res = await this.ctx.http.get(`${this.baseUrl}${AGENT_PREFIX}/markets`, {
50
+ params: cursor ? { ...query, cursor } : query,
51
+ headers: this.ctx.getHeaders(),
52
+ });
53
+ all.push(...(res.data?.markets ?? []));
54
+ cursor = typeof res.data?.nextCursor === 'string' ? res.data.nextCursor : undefined;
55
+ pages += 1;
56
+ } while (!explicitLimit && cursor && pages < MAX_LIST_PAGES);
57
+ return all;
45
58
  }
46
59
  catch (error) {
47
60
  throw errors_1.hunchErrorMapper.mapError(error);
@@ -45,6 +45,14 @@ export declare function parseHunchSide(outcomeId: string): {
45
45
  marketId: string;
46
46
  side: string;
47
47
  };
48
+ /**
49
+ * Map a Hunch native category to a pmxt top-level category. Hunch is
50
+ * crypto-native, so every token/price/on-chain subtype rolls up to "Crypto";
51
+ * only the manual "event" markets (fights/debates) map elsewhere.
52
+ */
53
+ export declare function mapHunchCategory(rawCategory: string | undefined): string;
54
+ /** Tags = top category + a human subtype label + the token (deduped, non-empty). */
55
+ export declare function hunchMarketTags(raw: HunchRawMarket): string[];
48
56
  /**
49
57
  * Shared market normalizer used by both the live fetch path and the (rare)
50
58
  * direct-mapping helper. Pulls a Hunch market ref into a {@link UnifiedMarket}.
@@ -5,6 +5,8 @@ exports.mapHunchStatus = mapHunchStatus;
5
5
  exports.mapStatusToHunch = mapStatusToHunch;
6
6
  exports.buildOutcomeId = buildOutcomeId;
7
7
  exports.parseHunchSide = parseHunchSide;
8
+ exports.mapHunchCategory = mapHunchCategory;
9
+ exports.hunchMarketTags = hunchMarketTags;
8
10
  exports.mapHunchMarketToUnified = mapHunchMarketToUnified;
9
11
  const market_utils_1 = require("../../utils/market-utils");
10
12
  const metadata_1 = require("../../utils/metadata");
@@ -102,6 +104,55 @@ function parseHunchSide(outcomeId) {
102
104
  side: outcomeId.slice(idx + 1),
103
105
  };
104
106
  }
107
+ // ---------------------------------------------------------------------------
108
+ // Category + tags — map Hunch's fine-grained native taxonomy onto pmxt's
109
+ // top-level categories (Crypto / Culture / …) + granular tags, so Hunch
110
+ // markets answer `?category=` filters and match with higher confidence.
111
+ // ---------------------------------------------------------------------------
112
+ /** Hunch native categories that are NOT crypto (manual-resolution markets). */
113
+ const HUNCH_TOP_CATEGORY = {
114
+ event: 'Culture',
115
+ };
116
+ /**
117
+ * Map a Hunch native category to a pmxt top-level category. Hunch is
118
+ * crypto-native, so every token/price/on-chain subtype rolls up to "Crypto";
119
+ * only the manual "event" markets (fights/debates) map elsewhere.
120
+ */
121
+ function mapHunchCategory(rawCategory) {
122
+ return HUNCH_TOP_CATEGORY[rawCategory ?? ''] ?? 'Crypto';
123
+ }
124
+ /** Human-readable granular label per Hunch native category (for tags). */
125
+ const HUNCH_SUBTYPE_LABEL = {
126
+ market_cap: 'Market Cap',
127
+ token_mcap_range: 'Market Cap',
128
+ token_mcap_flip: 'Market Cap',
129
+ token_mcap_close: 'Market Cap',
130
+ token_basket_mcap: 'Market Cap',
131
+ price_direction: 'Price',
132
+ token_price_range: 'Price',
133
+ token_return: 'Returns',
134
+ token_rank_milestone: 'Ranking',
135
+ chain_volume: 'On-chain Volume',
136
+ chain_throughput: 'Throughput',
137
+ chain_stablecoins: 'Stablecoins',
138
+ launchpad_volume: 'Launchpad',
139
+ dune_metric: 'On-chain',
140
+ volume_eta: 'Volume',
141
+ event: 'Event',
142
+ };
143
+ function titleCaseSlug(s) {
144
+ return s
145
+ .split(/[_\s-]+/)
146
+ .filter(Boolean)
147
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
148
+ .join(' ');
149
+ }
150
+ /** Tags = top category + a human subtype label + the token (deduped, non-empty). */
151
+ function hunchMarketTags(raw) {
152
+ const top = mapHunchCategory(raw.category);
153
+ const label = HUNCH_SUBTYPE_LABEL[raw.category] ?? (raw.category ? titleCaseSlug(raw.category) : '');
154
+ return [...new Set([top, label, raw.tokenSymbol].filter((t) => Boolean(t)))];
155
+ }
105
156
  /**
106
157
  * Shared market normalizer used by both the live fetch path and the (rare)
107
158
  * direct-mapping helper. Pulls a Hunch market ref into a {@link UnifiedMarket}.
@@ -119,6 +170,9 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
119
170
  if (!raw || !raw.id)
120
171
  return null;
121
172
  const marketId = raw.id;
173
+ // Explicit odds (detail/quote read) win; else fall back to the live odds the
174
+ // list item now carries — so a bare list-crawl prices binary markets too.
175
+ const effectiveOdds = odds ?? raw.odds ?? null;
122
176
  let outcomes;
123
177
  if (Array.isArray(raw.outcomes) && raw.outcomes.length > 0) {
124
178
  // N-way parimutuel ladder / date-window market.
@@ -148,9 +202,9 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
148
202
  }
149
203
  else {
150
204
  // Binary YES/NO market.
151
- const yesPrice = odds && typeof odds.yesPriceCents === 'number' ? odds.yesPriceCents / 100 : 0;
152
- const noPrice = odds && typeof odds.noPriceCents === 'number'
153
- ? odds.noPriceCents / 100
205
+ const yesPrice = effectiveOdds && typeof effectiveOdds.yesPriceCents === 'number' ? effectiveOdds.yesPriceCents / 100 : 0;
206
+ const noPrice = effectiveOdds && typeof effectiveOdds.noPriceCents === 'number'
207
+ ? effectiveOdds.noPriceCents / 100
154
208
  : yesPrice > 0
155
209
  ? 1 - yesPrice
156
210
  : 0;
@@ -166,13 +220,13 @@ function mapHunchMarketToUnified(raw, odds, ladder) {
166
220
  slug: raw.slug,
167
221
  outcomes,
168
222
  resolutionDate: raw.deadlineAt ? new Date(raw.deadlineAt) : undefined,
169
- // Hunch is parimutuel and reports no 24h volume split surface 0.
170
- volume24h: 0,
223
+ // Trailing-24h pool inflow off the list item (0 when absent / no trades).
224
+ volume24h: typeof raw.volume24hUsd === 'number' ? raw.volume24hUsd : 0,
171
225
  volume: typeof raw.volumeUsd === 'number' ? raw.volumeUsd : undefined,
172
226
  liquidity: Number(raw.virtualLiquidityUsd || 0),
173
227
  url: raw.links?.app || `${exports.DEFAULT_BASE_URL}/markets/${raw.slug || marketId}`,
174
- category: raw.category,
175
- tags: raw.tokenSymbol ? [raw.tokenSymbol] : [],
228
+ category: mapHunchCategory(raw.category),
229
+ tags: hunchMarketTags(raw),
176
230
  status: mapHunchStatus(raw.status),
177
231
  sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, exports.HUNCH_PROMOTED_MARKET_KEYS),
178
232
  };
@@ -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-06-22T13:10:46.874Z
3
+ * Generated at: 2026-06-23T09:51:53.440Z
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-06-22T13:10:46.874Z
6
+ * Generated at: 2026-06-23T09:51:53.440Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.kalshiApiSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
3
- * Generated at: 2026-06-22T13:10:46.913Z
3
+ * Generated at: 2026-06-23T09:51:53.479Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const limitlessApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.limitlessApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
6
- * Generated at: 2026-06-22T13:10:46.913Z
6
+ * Generated at: 2026-06-23T09:51:53.479Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -100,7 +100,10 @@ class LimitlessExchange extends BaseExchange_1.PredictionMarketExchange {
100
100
  callApi: this.callApi.bind(this),
101
101
  getHeaders: () => this.getHeaders(),
102
102
  };
103
- const limitlessBaseUrl = this.auth?.host || credentials?.baseUrl || utils_1.DEFAULT_LIMITLESS_API_URL;
103
+ const limitlessBaseUrl = this.auth?.host ||
104
+ credentials?.baseUrl ||
105
+ process.env.LIMITLESS_BASE_URL ||
106
+ utils_1.DEFAULT_LIMITLESS_API_URL;
104
107
  this.fetcher = new fetcher_1.LimitlessFetcher(ctx, this.http, this.auth?.getApiKey(), limitlessBaseUrl);
105
108
  this.normalizer = new normalizer_1.LimitlessNormalizer();
106
109
  }
@@ -58,7 +58,8 @@ class MetaculusExchange extends BaseExchange_1.PredictionMarketExchange {
58
58
  this.apiToken = credentials?.apiToken;
59
59
  // Rate-limit conservatively; authenticated users get higher Metaculus quotas
60
60
  this.rateLimit = 500;
61
- this.baseUrl = credentials?.baseUrl || utils_1.DEFAULT_BASE_URL;
61
+ this.baseUrl =
62
+ credentials?.baseUrl || process.env.METACULUS_BASE_URL || utils_1.DEFAULT_BASE_URL;
62
63
  const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.metaculusApiSpec, this.baseUrl);
63
64
  this.defineImplicitApi(descriptor);
64
65
  }
@@ -43,7 +43,7 @@ export declare class MockExchange extends PredictionMarketExchange {
43
43
  fillOrder(orderId: string, amount?: number): Promise<Order>;
44
44
  cancelOrder(orderId: string): Promise<Order>;
45
45
  fetchOrder(orderId: string): Promise<Order>;
46
- fetchOpenOrders(_marketId?: string): Promise<Order[]>;
46
+ fetchOpenOrders(marketId?: string): Promise<Order[]>;
47
47
  fetchMyTrades(_params?: {
48
48
  outcomeId?: string;
49
49
  marketId?: string;
@@ -277,12 +277,25 @@ class MockExchange extends BaseExchange_1.PredictionMarketExchange {
277
277
  const q = params.query.toLowerCase();
278
278
  markets = markets.filter(m => m.title.toLowerCase().includes(q));
279
279
  }
280
+ if (params?.slug) {
281
+ markets = markets.filter(m => m.slug === params.slug);
282
+ }
280
283
  if (params?.eventId) {
281
284
  markets = markets.filter(m => m.eventId === params.eventId);
282
285
  }
283
286
  if (params?.marketId) {
284
287
  markets = markets.filter(m => m.marketId === params.marketId);
285
288
  }
289
+ if (params?.outcomeId) {
290
+ markets = markets.filter(m => m.outcomes.some(o => o.outcomeId === params.outcomeId));
291
+ }
292
+ if (params?.status && params.status !== 'all') {
293
+ markets = markets.filter(m => (m.status ?? 'active') === params.status);
294
+ }
295
+ const sourceExchange = params?.sourceExchange ?? params?.exchange;
296
+ if (sourceExchange) {
297
+ markets = markets.filter(m => m.sourceExchange === sourceExchange);
298
+ }
286
299
  const offset = params?.offset ?? 0;
287
300
  const limit = params?.limit;
288
301
  return limit !== undefined ? markets.slice(offset, offset + limit) : markets.slice(offset);
@@ -296,6 +309,19 @@ class MockExchange extends BaseExchange_1.PredictionMarketExchange {
296
309
  if (params?.eventId) {
297
310
  events = events.filter(e => e.id === params.eventId);
298
311
  }
312
+ if (params?.slug) {
313
+ events = events.filter(e => e.slug === params.slug);
314
+ }
315
+ if (params?.series) {
316
+ events = events.filter(e => e.sourceMetadata?.series === params.series);
317
+ }
318
+ if (params?.status && params.status !== 'all') {
319
+ events = events.filter(e => (e.status ?? 'active') === params.status);
320
+ }
321
+ const sourceExchange = params?.sourceExchange ?? params?.exchange;
322
+ if (sourceExchange) {
323
+ events = events.filter(e => e.sourceExchange === sourceExchange);
324
+ }
299
325
  const offset = params?.offset ?? 0;
300
326
  const limit = params?.limit;
301
327
  return limit !== undefined ? events.slice(offset, offset + limit) : events.slice(offset);
@@ -378,8 +404,15 @@ class MockExchange extends BaseExchange_1.PredictionMarketExchange {
378
404
  outcomeId: id,
379
405
  });
380
406
  }
381
- const sorted = trades.sort((a, b) => b.timestamp - a.timestamp);
382
- return params.limit === undefined ? sorted : sorted.slice(0, Math.max(0, Math.floor(params.limit)));
407
+ const start = toTimestamp(params.start);
408
+ const end = toTimestamp(params.end);
409
+ const filtered = [...trades]
410
+ .sort((a, b) => b.timestamp - a.timestamp)
411
+ .filter((trade) => start === undefined || trade.timestamp >= start)
412
+ .filter((trade) => end === undefined || trade.timestamp <= end);
413
+ return params.limit === undefined
414
+ ? filtered
415
+ : filtered.slice(0, Math.max(0, Math.floor(params.limit)));
383
416
  }
384
417
  async fetchBalance(_address) {
385
418
  const locked = this._locked();
@@ -609,9 +642,10 @@ class MockExchange extends BaseExchange_1.PredictionMarketExchange {
609
642
  throw new Error(`Order not found: ${orderId}`);
610
643
  return { ...o };
611
644
  }
612
- async fetchOpenOrders(_marketId) {
645
+ async fetchOpenOrders(marketId) {
613
646
  return Array.from(this._orders.values())
614
647
  .filter(o => o.status === 'open' || o.status === 'pending')
648
+ .filter(o => marketId === undefined || o.marketId === marketId)
615
649
  .map(o => ({ ...o }));
616
650
  }
617
651
  async fetchMyTrades(_params) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
3
- * Generated at: 2026-06-22T13:10:46.925Z
3
+ * Generated at: 2026-06-23T09:51:53.491Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const myriadApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.myriadApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
6
- * Generated at: 2026-06-22T13:10:46.925Z
6
+ * Generated at: 2026-06-23T09:51:53.491Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -35,7 +35,7 @@ class MyriadExchange extends BaseExchange_1.PredictionMarketExchange {
35
35
  if (credentials?.apiKey || credentials?.privateKey) {
36
36
  this.auth = new auth_1.MyriadAuth(credentials);
37
37
  }
38
- const myriadBaseUrl = credentials?.baseUrl || utils_1.DEFAULT_BASE_URL;
38
+ const myriadBaseUrl = credentials?.baseUrl || process.env.MYRIAD_BASE_URL || utils_1.DEFAULT_BASE_URL;
39
39
  const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.myriadApiSpec, myriadBaseUrl);
40
40
  this.defineImplicitApi(descriptor);
41
41
  const ctx = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
3
- * Generated at: 2026-06-22T13:10:46.930Z
3
+ * Generated at: 2026-06-23T09:51:53.496Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const opinionApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.opinionApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
6
- * Generated at: 2026-06-22T13:10:46.930Z
6
+ * Generated at: 2026-06-23T09:51:53.496Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -82,7 +82,7 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
82
82
  if (credentials?.apiKey) {
83
83
  this.auth = new auth_1.OpinionAuth(credentials);
84
84
  }
85
- const opinionBaseUrl = credentials?.baseUrl || config_1.DEFAULT_OPINION_API_URL;
85
+ const opinionBaseUrl = credentials?.baseUrl || process.env.OPINION_BASE_URL || config_1.DEFAULT_OPINION_API_URL;
86
86
  const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.opinionApiSpec, opinionBaseUrl);
87
87
  this.defineImplicitApi(descriptor);
88
88
  const ctx = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
3
- * Generated at: 2026-06-22T13:10:46.882Z
3
+ * Generated at: 2026-06-23T09:51:53.448Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketClobSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketClobSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
6
- * Generated at: 2026-06-22T13:10:46.882Z
6
+ * Generated at: 2026-06-23T09:51:53.448Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketClobSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
3
- * Generated at: 2026-06-22T13:10:46.896Z
3
+ * Generated at: 2026-06-23T09:51:53.462Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketDataSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketDataSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
6
- * Generated at: 2026-06-22T13:10:46.896Z
6
+ * Generated at: 2026-06-23T09:51:53.462Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketDataSpec = {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
3
- * Generated at: 2026-06-22T13:10:46.893Z
3
+ * Generated at: 2026-06-23T09:51:53.459Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const polymarketGammaSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.polymarketGammaSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
6
- * Generated at: 2026-06-22T13:10:46.893Z
6
+ * Generated at: 2026-06-23T09:51:53.459Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -15,8 +15,10 @@ exports.POLYMARKET_US_GATEWAY_BASE_URL = process.env.POLYMARKET_US_GATEWAY_URL |
15
15
  * Return a typed config object for the Polymarket US API.
16
16
  */
17
17
  function getPolymarketUSConfig(baseUrlOverride) {
18
+ const apiUrl = baseUrlOverride || process.env.POLYMARKET_US_BASE_URL || exports.POLYMARKET_US_API_BASE_URL;
19
+ const gatewayUrl = process.env.POLYMARKET_US_GATEWAY_URL || exports.POLYMARKET_US_GATEWAY_BASE_URL;
18
20
  return {
19
- apiUrl: baseUrlOverride || exports.POLYMARKET_US_API_BASE_URL,
20
- gatewayUrl: exports.POLYMARKET_US_GATEWAY_BASE_URL,
21
+ apiUrl,
22
+ gatewayUrl,
21
23
  };
22
24
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
3
- * Generated at: 2026-06-22T13:10:46.919Z
3
+ * Generated at: 2026-06-23T09:51:53.484Z
4
4
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
5
5
  */
6
6
  export declare const probableApiSpec: {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.probableApiSpec = void 0;
4
4
  /**
5
5
  * Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
6
- * Generated at: 2026-06-22T13:10:46.919Z
6
+ * Generated at: 2026-06-23T09:51:53.484Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -31,7 +31,7 @@ class ProbableExchange extends BaseExchange_1.PredictionMarketExchange {
31
31
  if (credentials?.privateKey && credentials?.apiKey && credentials?.apiSecret && credentials?.passphrase) {
32
32
  this.auth = new auth_1.ProbableAuth(credentials);
33
33
  }
34
- const probableBaseUrl = credentials?.baseUrl || utils_1.DEFAULT_BASE_URL;
34
+ const probableBaseUrl = credentials?.baseUrl || process.env.PROBABLE_BASE_URL || utils_1.DEFAULT_BASE_URL;
35
35
  const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.probableApiSpec, probableBaseUrl);
36
36
  this.defineImplicitApi(descriptor);
37
37
  const ctx = {
@@ -42,7 +42,8 @@ exports.SMARKETS_PATHS = {
42
42
  * Return a typed config object for the Smarkets API.
43
43
  */
44
44
  function getSmarketsConfig(baseUrlOverride) {
45
+ const apiUrl = baseUrlOverride || process.env.SMARKETS_BASE_URL || exports.DEFAULT_SMARKETS_BASE_URL;
45
46
  return {
46
- apiUrl: baseUrlOverride || exports.DEFAULT_SMARKETS_BASE_URL,
47
+ apiUrl,
47
48
  };
48
49
  }
@@ -1,6 +1,7 @@
1
1
  import { Express } from "express";
2
2
  import { Server as HttpServer } from "http";
3
3
  import { createWebSocketHandler, CreateWebSocketHandlerOptions } from "./ws-handler";
4
+ import { PredictionMarketExchange } from "../BaseExchange";
4
5
  /**
5
6
  * Options accepted by {@link createApp}.
6
7
  */
@@ -25,6 +26,14 @@ export interface CreateAppOptions {
25
26
  * Defaults to false.
26
27
  */
27
28
  skipBaseMiddleware?: boolean;
29
+ /**
30
+ * Exchange instances scoped to this app.
31
+ *
32
+ * By default, unauthenticated local usage reuses process-wide singleton
33
+ * exchanges. Embedders and tests can provide explicit instances here when
34
+ * they need isolated state or non-default mock exchange options.
35
+ */
36
+ localExchanges?: Partial<Record<string, PredictionMarketExchange>>;
28
37
  }
29
38
  /**
30
39
  * Build an Express app that serves the PMXT sidecar API surface without
@@ -162,6 +162,15 @@ function normalizeDateFields(params, fields, exchange) {
162
162
  }
163
163
  return normalized;
164
164
  }
165
+ function normalizeArrayFields(params, fields) {
166
+ return fields.reduce((normalized, field) => {
167
+ const value = normalized[field];
168
+ if (typeof value === "string") {
169
+ return { ...normalized, [field]: [value] };
170
+ }
171
+ return normalized;
172
+ }, { ...params });
173
+ }
165
174
  function isMissing(value) {
166
175
  return value === undefined || value === null || value === "";
167
176
  }
@@ -200,9 +209,12 @@ function validateCreateOrderParams(params, exchange) {
200
209
  }
201
210
  function normalizeDispatchArgs(methodName, args, exchange) {
202
211
  const normalized = [...args];
203
- if (methodName === "fetchOHLCV" && isRecord(normalized[1])) {
212
+ if ((methodName === "fetchOHLCV" || methodName === "fetchTrades") && isRecord(normalized[1])) {
204
213
  normalized[1] = normalizeDateFields(normalized[1], ["start", "end"], exchange);
205
214
  }
215
+ if ((methodName === "fetchMarkets" || methodName === "fetchEvents") && isRecord(normalized[0])) {
216
+ normalized[0] = normalizeArrayFields(normalized[0], ["tags"]);
217
+ }
206
218
  if (methodName === "createOrder") {
207
219
  normalized[0] = validateCreateOrderParams(normalized[0], exchange);
208
220
  }
@@ -268,8 +280,9 @@ function getDefaultExchange(exchangeName) {
268
280
  * ```
269
281
  */
270
282
  function createApp(options = {}) {
271
- const { accessToken, skipBaseMiddleware = false } = options;
283
+ const { accessToken, skipBaseMiddleware = false, localExchanges = {}, } = options;
272
284
  const app = (0, express_1.default)();
285
+ const getAppExchange = (exchangeName) => (localExchanges[exchangeName] ?? getDefaultExchange(exchangeName));
273
286
  if (!skipBaseMiddleware) {
274
287
  app.use((0, cors_1.default)());
275
288
  app.use(express_1.default.json({ limit: "2mb" }));
@@ -307,16 +320,22 @@ function createApp(options = {}) {
307
320
  const exchangeName = req.params.exchange.toLowerCase();
308
321
  let exchange;
309
322
  if (exchangeName === "router") {
310
- // Router uses the caller's Bearer token for its internal /v0/
311
- // calls — not a server-side env var. Each request may carry a
312
- // different key, so Router is never cached as a singleton.
313
- const bearer = req.headers.authorization?.replace(/^Bearer\s+/i, "") || "";
314
- exchange = new router_1.Router({
315
- apiKey: bearer,
316
- localExchanges: {
317
- mock: getDefaultExchange("mock"),
318
- },
319
- });
323
+ const localRouter = localExchanges[exchangeName];
324
+ if (localRouter) {
325
+ exchange = localRouter;
326
+ }
327
+ else {
328
+ // Router uses the caller's Bearer token for its internal /v0/
329
+ // calls — not a server-side env var. Each request may carry a
330
+ // different key, so Router is never cached as a singleton.
331
+ const bearer = req.headers.authorization?.replace(/^Bearer\s+/i, "") || "";
332
+ exchange = new router_1.Router({
333
+ apiKey: bearer,
334
+ localExchanges: {
335
+ mock: getAppExchange("mock"),
336
+ },
337
+ });
338
+ }
320
339
  }
321
340
  else if (credentials &&
322
341
  (credentials.privateKey ||
@@ -325,7 +344,7 @@ function createApp(options = {}) {
325
344
  exchange = (0, exchange_factory_1.createExchange)(exchangeName, credentials);
326
345
  }
327
346
  else {
328
- exchange = getDefaultExchange(exchangeName);
347
+ exchange = getAppExchange(exchangeName);
329
348
  }
330
349
  if (req.headers["x-pmxt-verbose"] === "true") {
331
350
  exchange.verbose = true;
@@ -340,9 +359,8 @@ function createApp(options = {}) {
340
359
  });
341
360
  return;
342
361
  }
343
- if (exchange.has &&
344
- methodName in exchange.has &&
345
- exchange.has[methodName] === false) {
362
+ const capability = exchange.has?.[methodName];
363
+ if (capability === false && methodName !== "fetchSeries") {
346
364
  res.status(501).json({
347
365
  success: false,
348
366
  error: `Method '${methodName}' is not supported by '${exchangeName}'. ` +
@@ -54,8 +54,12 @@ function createExchange(name, credentials, bearerToken) {
54
54
  case "kalshi-demo":
55
55
  return new kalshi_demo_1.KalshiDemoExchange({
56
56
  credentials: {
57
- apiKey: credentials?.apiKey || process.env.KALSHI_API_KEY,
58
- privateKey: credentials?.privateKey || process.env.KALSHI_PRIVATE_KEY,
57
+ apiKey: credentials?.apiKey ||
58
+ process.env.KALSHI_DEMO_API_KEY ||
59
+ process.env.KALSHI_API_KEY,
60
+ privateKey: credentials?.privateKey ||
61
+ process.env.KALSHI_DEMO_PRIVATE_KEY ||
62
+ process.env.KALSHI_PRIVATE_KEY,
59
63
  },
60
64
  });
61
65
  case "probable":
@@ -80,7 +84,9 @@ function createExchange(name, credentials, bearerToken) {
80
84
  return new opinion_1.OpinionExchange({
81
85
  apiKey: credentials?.apiKey || process.env.OPINION_API_KEY,
82
86
  privateKey: credentials?.privateKey || process.env.OPINION_PRIVATE_KEY,
83
- funderAddress: credentials?.funderAddress,
87
+ funderAddress: credentials?.funderAddress ||
88
+ process.env.OPINION_FUNDER_ADDRESS ||
89
+ process.env.OPINION_PROXY_ADDRESS,
84
90
  });
85
91
  case "metaculus":
86
92
  return new metaculus_1.MetaculusExchange({
@@ -2636,6 +2636,7 @@ components:
2636
2636
  - hyperliquid
2637
2637
  - suibets
2638
2638
  - rain
2639
+ - hunch
2639
2640
  - mock
2640
2641
  - router
2641
2642
  required: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.51.0",
3
+ "version": "2.51.2",
4
4
  "description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,8 +29,8 @@
29
29
  "test": "jest -c jest.config.js",
30
30
  "server": "tsx watch src/server/index.ts",
31
31
  "server:prod": "node dist/server/index.js",
32
- "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.51.0,library=urllib3",
33
- "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.51.0,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
32
+ "generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.51.2,library=urllib3",
33
+ "generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.51.2,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
34
34
  "fetch:openapi": "node scripts/fetch-openapi-specs.js",
35
35
  "extract:jsdoc": "node ../scripts/extract-jsdoc.js",
36
36
  "generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",