pmxt-core 2.37.10 → 2.37.12

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 (72) hide show
  1. package/dist/BaseExchange.d.ts +5 -0
  2. package/dist/BaseExchange.js +6 -0
  3. package/dist/exchanges/kalshi/api.d.ts +1 -1
  4. package/dist/exchanges/kalshi/api.js +1 -1
  5. package/dist/exchanges/kalshi/config.d.ts +3 -3
  6. package/dist/exchanges/kalshi/config.js +4 -4
  7. package/dist/exchanges/kalshi/index.js +1 -1
  8. package/dist/exchanges/limitless/api.d.ts +1 -1
  9. package/dist/exchanges/limitless/api.js +1 -1
  10. package/dist/exchanges/limitless/auth.d.ts +1 -0
  11. package/dist/exchanges/limitless/auth.js +4 -2
  12. package/dist/exchanges/limitless/client.d.ts +1 -1
  13. package/dist/exchanges/limitless/client.js +3 -3
  14. package/dist/exchanges/limitless/fetcher.d.ts +7 -1
  15. package/dist/exchanges/limitless/fetcher.js +61 -16
  16. package/dist/exchanges/limitless/index.js +2 -2
  17. package/dist/exchanges/limitless/utils.d.ts +2 -1
  18. package/dist/exchanges/limitless/utils.js +3 -2
  19. package/dist/exchanges/metaculus/cancelOrder.d.ts +2 -0
  20. package/dist/exchanges/metaculus/cancelOrder.js +1 -1
  21. package/dist/exchanges/metaculus/createOrder.d.ts +2 -0
  22. package/dist/exchanges/metaculus/createOrder.js +1 -1
  23. package/dist/exchanges/metaculus/index.d.ts +1 -0
  24. package/dist/exchanges/metaculus/index.js +5 -1
  25. package/dist/exchanges/metaculus/utils.d.ts +2 -1
  26. package/dist/exchanges/metaculus/utils.js +3 -2
  27. package/dist/exchanges/myriad/api.d.ts +1 -1
  28. package/dist/exchanges/myriad/api.js +1 -1
  29. package/dist/exchanges/myriad/fetcher.d.ts +2 -1
  30. package/dist/exchanges/myriad/fetcher.js +9 -7
  31. package/dist/exchanges/myriad/index.js +3 -2
  32. package/dist/exchanges/myriad/utils.d.ts +2 -1
  33. package/dist/exchanges/myriad/utils.js +3 -2
  34. package/dist/exchanges/opinion/api.d.ts +1 -1
  35. package/dist/exchanges/opinion/api.js +1 -1
  36. package/dist/exchanges/opinion/config.d.ts +2 -1
  37. package/dist/exchanges/opinion/config.js +3 -2
  38. package/dist/exchanges/opinion/fetcher.d.ts +2 -1
  39. package/dist/exchanges/opinion/fetcher.js +10 -8
  40. package/dist/exchanges/opinion/index.js +3 -2
  41. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  42. package/dist/exchanges/polymarket/api-clob.js +1 -1
  43. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  44. package/dist/exchanges/polymarket/api-data.js +1 -1
  45. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  46. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  47. package/dist/exchanges/polymarket/auth.d.ts +1 -0
  48. package/dist/exchanges/polymarket/auth.js +7 -4
  49. package/dist/exchanges/polymarket/fetcher.js +1 -1
  50. package/dist/exchanges/polymarket/utils.d.ts +4 -4
  51. package/dist/exchanges/polymarket/utils.js +4 -4
  52. package/dist/exchanges/polymarket_us/config.d.ts +3 -3
  53. package/dist/exchanges/polymarket_us/config.js +4 -4
  54. package/dist/exchanges/polymarket_us/index.js +1 -1
  55. package/dist/exchanges/probable/api.d.ts +1 -1
  56. package/dist/exchanges/probable/api.js +1 -1
  57. package/dist/exchanges/probable/fetcher.d.ts +2 -1
  58. package/dist/exchanges/probable/fetcher.js +9 -7
  59. package/dist/exchanges/probable/index.js +3 -2
  60. package/dist/exchanges/probable/utils.d.ts +2 -1
  61. package/dist/exchanges/probable/utils.js +3 -2
  62. package/dist/exchanges/smarkets/config.d.ts +3 -2
  63. package/dist/exchanges/smarkets/config.js +5 -4
  64. package/dist/exchanges/smarkets/fetcher.js +2 -1
  65. package/dist/exchanges/smarkets/index.js +1 -1
  66. package/dist/router/Router.d.ts +3 -1
  67. package/dist/router/Router.js +65 -0
  68. package/dist/router/Router.test.js +131 -1
  69. package/dist/router/types.d.ts +3 -0
  70. package/dist/server/method-verbs.json +10 -0
  71. package/dist/server/openapi.yaml +35 -3
  72. package/package.json +3 -3
@@ -3,11 +3,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Router = void 0;
4
4
  const BaseExchange_1 = require("../BaseExchange");
5
5
  const client_1 = require("./client");
6
+ // ---------------------------------------------------------------------------
7
+ // Orderbook merge utilities
8
+ // ---------------------------------------------------------------------------
9
+ function findOutcomeForSide(market, side) {
10
+ return market.outcomes.find((o) => o.label.toLowerCase() === side)
11
+ ?? market.outcomes[side === 'yes' ? 0 : 1];
12
+ }
13
+ function mergeLevels(levels) {
14
+ const byPrice = new Map();
15
+ for (const level of levels) {
16
+ byPrice.set(level.price, (byPrice.get(level.price) ?? 0) + level.size);
17
+ }
18
+ return Array.from(byPrice.entries()).map(([price, size]) => ({ price, size }));
19
+ }
20
+ function mergeOrderBooks(books) {
21
+ const allBids = books.flatMap((b) => b.bids);
22
+ const allAsks = books.flatMap((b) => b.asks);
23
+ return {
24
+ bids: mergeLevels(allBids).sort((a, b) => b.price - a.price),
25
+ asks: mergeLevels(allAsks).sort((a, b) => a.price - b.price),
26
+ timestamp: Date.now(),
27
+ };
28
+ }
29
+ // ---------------------------------------------------------------------------
6
30
  class Router extends BaseExchange_1.PredictionMarketExchange {
7
31
  client;
32
+ exchanges;
8
33
  constructor(options) {
9
34
  super({ apiKey: options.apiKey });
10
35
  this.client = new client_1.PmxtApiClient(options.apiKey, options.baseUrl);
36
+ this.exchanges = options.exchanges ?? {};
11
37
  this.rateLimit = 100;
12
38
  }
13
39
  get name() {
@@ -36,6 +62,45 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
36
62
  return response ?? [];
37
63
  }
38
64
  // -----------------------------------------------------------------------
65
+ // Unified orderbook (cross-exchange merge)
66
+ // -----------------------------------------------------------------------
67
+ async fetchOrderBook(id, side) {
68
+ const exchangeNames = Object.keys(this.exchanges);
69
+ if (exchangeNames.length === 0) {
70
+ throw new Error('Router requires exchange instances for fetchOrderBook. Pass exchanges in RouterOptions.');
71
+ }
72
+ const resolvedSide = side ?? 'yes';
73
+ // Find identity matches across venues
74
+ const matches = await this.fetchMarketMatches({
75
+ marketId: id,
76
+ relation: 'identity',
77
+ });
78
+ const fetchPromises = [];
79
+ const matchedVenues = new Set(matches.map((m) => m.market.sourceExchange).filter(Boolean));
80
+ // Fetch from matched markets (we know their exchange + outcome IDs)
81
+ for (const match of matches) {
82
+ const venueName = match.market.sourceExchange ?? '';
83
+ const exchange = this.exchanges[venueName];
84
+ if (!exchange)
85
+ continue;
86
+ const outcome = findOutcomeForSide(match.market, resolvedSide);
87
+ if (!outcome)
88
+ continue;
89
+ fetchPromises.push(exchange.fetchOrderBook(outcome.outcomeId, resolvedSide).catch(() => null));
90
+ }
91
+ // Fetch the source market's orderbook (try remaining exchanges with the raw ID)
92
+ for (const [name, exchange] of Object.entries(this.exchanges)) {
93
+ if (matchedVenues.has(name))
94
+ continue;
95
+ fetchPromises.push(exchange.fetchOrderBook(id, resolvedSide).catch(() => null));
96
+ }
97
+ const books = (await Promise.all(fetchPromises)).filter((b) => b !== null);
98
+ if (books.length === 0) {
99
+ return { bids: [], asks: [], timestamp: Date.now() };
100
+ }
101
+ return mergeOrderBooks(books);
102
+ }
103
+ // -----------------------------------------------------------------------
39
104
  // Cross-exchange market matches
40
105
  // -----------------------------------------------------------------------
41
106
  async fetchMarketMatches(params = {}) {
@@ -4,6 +4,14 @@ const Router_1 = require("./Router");
4
4
  const client_1 = require("./client");
5
5
  jest.mock('./client');
6
6
  const MockedClient = client_1.PmxtApiClient;
7
+ function mockExchange(name, orderBook) {
8
+ return {
9
+ name,
10
+ fetchOrderBook: orderBook
11
+ ? jest.fn().mockResolvedValue(orderBook)
12
+ : jest.fn().mockRejectedValue(new Error('Not found')),
13
+ };
14
+ }
7
15
  describe('Router', () => {
8
16
  let router;
9
17
  let clientInstance;
@@ -173,6 +181,126 @@ describe('Router', () => {
173
181
  await expect(router.createOrder({})).rejects.toThrow('not implemented');
174
182
  });
175
183
  });
184
+ describe('fetchOrderBook', () => {
185
+ it('throws when no exchanges are configured', async () => {
186
+ await expect(router.fetchOrderBook('m1')).rejects.toThrow('Router requires exchange instances for fetchOrderBook');
187
+ });
188
+ it('merges orderbooks from matched markets', async () => {
189
+ const polyBook = {
190
+ bids: [{ price: 0.45, size: 100 }, { price: 0.44, size: 50 }],
191
+ asks: [{ price: 0.47, size: 80 }, { price: 0.48, size: 60 }],
192
+ timestamp: 1000,
193
+ };
194
+ const kalshiBook = {
195
+ bids: [{ price: 0.46, size: 200 }, { price: 0.44, size: 75 }],
196
+ asks: [{ price: 0.47, size: 120 }, { price: 0.49, size: 90 }],
197
+ timestamp: 2000,
198
+ };
199
+ const polyExchange = mockExchange('polymarket', polyBook);
200
+ const kalshiExchange = mockExchange('kalshi', kalshiBook);
201
+ MockedClient.mockClear();
202
+ const routerWithExchanges = new Router_1.Router({
203
+ apiKey: 'test-key',
204
+ exchanges: { polymarket: polyExchange, kalshi: kalshiExchange },
205
+ });
206
+ const client = MockedClient.mock.instances[0];
207
+ // Match API returns kalshi as a match (source is polymarket)
208
+ client.getMarketMatches = jest.fn().mockResolvedValue({
209
+ matches: [{
210
+ market: {
211
+ marketId: 'k1',
212
+ sourceExchange: 'kalshi',
213
+ outcomes: [
214
+ { outcomeId: 'k1-yes', label: 'Yes', price: 0.46 },
215
+ { outcomeId: 'k1-no', label: 'No', price: 0.54 },
216
+ ],
217
+ },
218
+ relation: 'identity',
219
+ confidence: 0.95,
220
+ reasoning: null,
221
+ }],
222
+ });
223
+ const result = await routerWithExchanges.fetchOrderBook('poly-token-yes', 'yes');
224
+ // Bids merged: 0.46 (200), 0.45 (100), 0.44 (50+75=125)
225
+ expect(result.bids).toEqual([
226
+ { price: 0.46, size: 200 },
227
+ { price: 0.45, size: 100 },
228
+ { price: 0.44, size: 125 },
229
+ ]);
230
+ // Asks merged: 0.47 (80+120=200), 0.48 (60), 0.49 (90)
231
+ expect(result.asks).toEqual([
232
+ { price: 0.47, size: 200 },
233
+ { price: 0.48, size: 60 },
234
+ { price: 0.49, size: 90 },
235
+ ]);
236
+ // Kalshi was called with the matched outcome ID
237
+ expect(kalshiExchange.fetchOrderBook).toHaveBeenCalledWith('k1-yes', 'yes');
238
+ // Polymarket was called with the raw ID (source market)
239
+ expect(polyExchange.fetchOrderBook).toHaveBeenCalledWith('poly-token-yes', 'yes');
240
+ });
241
+ it('returns single exchange book when no matches exist', async () => {
242
+ const book = {
243
+ bids: [{ price: 0.50, size: 100 }],
244
+ asks: [{ price: 0.52, size: 80 }],
245
+ timestamp: 1000,
246
+ };
247
+ const exchange = mockExchange('polymarket', book);
248
+ MockedClient.mockClear();
249
+ const routerWithExchanges = new Router_1.Router({
250
+ apiKey: 'test-key',
251
+ exchanges: { polymarket: exchange },
252
+ });
253
+ const client = MockedClient.mock.instances[0];
254
+ client.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
255
+ const result = await routerWithExchanges.fetchOrderBook('token-id');
256
+ expect(result.bids).toEqual([{ price: 0.50, size: 100 }]);
257
+ expect(result.asks).toEqual([{ price: 0.52, size: 80 }]);
258
+ });
259
+ it('defaults side to yes when not specified', async () => {
260
+ const exchange = mockExchange('kalshi', { bids: [], asks: [], timestamp: 0 });
261
+ MockedClient.mockClear();
262
+ const routerWithExchanges = new Router_1.Router({
263
+ apiKey: 'test-key',
264
+ exchanges: { kalshi: exchange },
265
+ });
266
+ const client = MockedClient.mock.instances[0];
267
+ client.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
268
+ await routerWithExchanges.fetchOrderBook('ticker');
269
+ expect(exchange.fetchOrderBook).toHaveBeenCalledWith('ticker', 'yes');
270
+ });
271
+ it('skips exchanges that fail and returns partial results', async () => {
272
+ const book = {
273
+ bids: [{ price: 0.50, size: 100 }],
274
+ asks: [{ price: 0.55, size: 50 }],
275
+ timestamp: 1000,
276
+ };
277
+ const goodExchange = mockExchange('kalshi', book);
278
+ const badExchange = mockExchange('limitless'); // rejects
279
+ MockedClient.mockClear();
280
+ const routerWithExchanges = new Router_1.Router({
281
+ apiKey: 'test-key',
282
+ exchanges: { kalshi: goodExchange, limitless: badExchange },
283
+ });
284
+ const client = MockedClient.mock.instances[0];
285
+ client.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
286
+ const result = await routerWithExchanges.fetchOrderBook('ticker', 'yes');
287
+ expect(result.bids).toEqual([{ price: 0.50, size: 100 }]);
288
+ expect(result.asks).toEqual([{ price: 0.55, size: 50 }]);
289
+ });
290
+ it('returns empty orderbook when all exchanges fail', async () => {
291
+ const badExchange = mockExchange('polymarket');
292
+ MockedClient.mockClear();
293
+ const routerWithExchanges = new Router_1.Router({
294
+ apiKey: 'test-key',
295
+ exchanges: { polymarket: badExchange },
296
+ });
297
+ const client = MockedClient.mock.instances[0];
298
+ client.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
299
+ const result = await routerWithExchanges.fetchOrderBook('bad-id');
300
+ expect(result.bids).toEqual([]);
301
+ expect(result.asks).toEqual([]);
302
+ });
303
+ });
176
304
  describe('capabilities', () => {
177
305
  it('reports matching methods as supported', () => {
178
306
  expect(router.has.fetchMarketMatches).toBe(true);
@@ -185,10 +313,12 @@ describe('Router', () => {
185
313
  expect(router.has.fetchHedges).toBe(true);
186
314
  expect(router.has.fetchArbitrage).toBe(true);
187
315
  });
316
+ it('reports fetchOrderBook as supported', () => {
317
+ expect(router.has.fetchOrderBook).toBe(true);
318
+ });
188
319
  it('reports trading methods as unsupported', () => {
189
320
  expect(router.has.createOrder).toBe(false);
190
321
  expect(router.has.cancelOrder).toBe(false);
191
- expect(router.has.fetchOrderBook).toBe(false);
192
322
  });
193
323
  it('reports search methods as supported', () => {
194
324
  expect(router.has.fetchMarkets).toBe(true);
@@ -1,8 +1,11 @@
1
+ import type { PredictionMarketExchange } from '../BaseExchange';
1
2
  import type { UnifiedMarket, UnifiedEvent } from '../types';
2
3
  export type MatchRelation = 'identity' | 'subset' | 'superset' | 'overlap' | 'disjoint';
3
4
  export interface RouterOptions {
4
5
  apiKey: string;
5
6
  baseUrl?: string;
7
+ /** Exchange instances for cross-venue orderbook aggregation. Keyed by exchange name (e.g. 'polymarket', 'kalshi'). */
8
+ exchanges?: Record<string, PredictionMarketExchange>;
6
9
  }
7
10
  export interface MatchResult {
8
11
  market: UnifiedMarket;
@@ -384,6 +384,16 @@
384
384
  }
385
385
  ]
386
386
  },
387
+ "testDummyMethod": {
388
+ "verb": "post",
389
+ "args": [
390
+ {
391
+ "name": "param",
392
+ "kind": "string",
393
+ "optional": true
394
+ }
395
+ ]
396
+ },
387
397
  "close": {
388
398
  "verb": "post",
389
399
  "args": []
@@ -1527,6 +1527,39 @@ paths:
1527
1527
  schema:
1528
1528
  $ref: '#/components/schemas/BaseResponse'
1529
1529
  description: Stop watching a previously registered wallet address and release its resource updates.
1530
+ '/api/{exchange}/testDummyMethod':
1531
+ post:
1532
+ summary: Test Dummy Method
1533
+ operationId: testDummyMethod
1534
+ parameters:
1535
+ - $ref: '#/components/parameters/ExchangeParam'
1536
+ requestBody:
1537
+ content:
1538
+ application/json:
1539
+ schema:
1540
+ title: TestDummyMethodRequest
1541
+ type: object
1542
+ properties:
1543
+ args:
1544
+ type: array
1545
+ maxItems: 1
1546
+ items:
1547
+ type: string
1548
+ credentials:
1549
+ $ref: '#/components/schemas/ExchangeCredentials'
1550
+ responses:
1551
+ '200':
1552
+ description: Test Dummy Method response
1553
+ content:
1554
+ application/json:
1555
+ schema:
1556
+ allOf:
1557
+ - $ref: '#/components/schemas/BaseResponse'
1558
+ - type: object
1559
+ properties:
1560
+ data:
1561
+ type: string
1562
+ description: Test method for auto-generation verification.
1530
1563
  '/api/{exchange}/close':
1531
1564
  post:
1532
1565
  summary: Close
@@ -1553,9 +1586,6 @@ paths:
1553
1586
  application/json:
1554
1587
  schema:
1555
1588
  $ref: '#/components/schemas/BaseResponse'
1556
- description: >-
1557
- Close all WebSocket connections and clean up resources. Call this when you're done streaming to properly release
1558
- connections.
1559
1589
  '/api/{exchange}/fetchMarketMatches':
1560
1590
  get:
1561
1591
  summary: Market Matches
@@ -3442,6 +3472,8 @@ components:
3442
3472
  description: The address funding the trades (defaults to signer address)
3443
3473
  walletAddress:
3444
3474
  type: string
3475
+ baseUrl:
3476
+ type: string
3445
3477
  x-sdk-constructors:
3446
3478
  polymarket:
3447
3479
  className: Polymarket
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.37.10",
3
+ "version": "2.37.12",
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.37.10,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.37.10,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.37.12,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.37.12,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",