pmxt-core 2.32.4 → 2.33.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.
@@ -2,6 +2,7 @@ import { AxiosInstance } from 'axios';
2
2
  import { SubscribedAddressSnapshot, SubscriptionOption } from './subscriber/base';
3
3
  import { Balance, BuiltOrder, CandleInterval, CreateOrderParams, Order, OrderBook, Position, PriceCandle, Trade, UnifiedEvent, UnifiedMarket, UserTrade } from './types';
4
4
  import { ExecutionPriceResult } from './utils/math';
5
+ import type { FetchMatchesParams, FetchEventMatchesParams, FetchArbitrageParams, MatchResult, EventMatchResult, PriceComparison, ArbitrageOpportunity } from './router/types';
5
6
  export interface ApiEndpoint {
6
7
  /** HTTP verb for the endpoint (e.g. GET, POST). */
7
8
  method: string;
@@ -227,6 +228,16 @@ export interface ExchangeHas {
227
228
  buildOrder: ExchangeCapability;
228
229
  /** Whether this exchange supports submitting a pre-built order. */
229
230
  submitOrder: ExchangeCapability;
231
+ /** Whether this exchange supports fetching cross-venue market matches. */
232
+ fetchMatches: ExchangeCapability;
233
+ /** Whether this exchange supports fetching cross-venue event matches. */
234
+ fetchEventMatches: ExchangeCapability;
235
+ /** Whether this exchange supports comparing prices across venues. */
236
+ compareMarketPrices: ExchangeCapability;
237
+ /** Whether this exchange supports finding hedging opportunities across venues. */
238
+ fetchHedges: ExchangeCapability;
239
+ /** Whether this exchange supports scanning for arbitrage opportunities. */
240
+ fetchArbitrage: ExchangeCapability;
230
241
  }
231
242
  /**
232
243
  * Optional authentication credentials for exchange operations.
@@ -559,6 +570,41 @@ export declare abstract class PredictionMarketExchange {
559
570
  * Call this when you're done streaming to properly release connections.
560
571
  */
561
572
  close(): Promise<void>;
573
+ /**
574
+ * Fetch cross-venue matches for a given market.
575
+ *
576
+ * @param params - Match filter parameters (marketId, relation, minConfidence, etc.)
577
+ * @returns Array of matched markets with relation and confidence
578
+ */
579
+ fetchMatches(params: FetchMatchesParams): Promise<MatchResult[]>;
580
+ /**
581
+ * Fetch cross-venue matches for a given event.
582
+ *
583
+ * @param params - Event match filter parameters (eventId, relation, etc.)
584
+ * @returns Array of matched events with market-level match details
585
+ */
586
+ fetchEventMatches(params: FetchEventMatchesParams): Promise<EventMatchResult[]>;
587
+ /**
588
+ * Compare prices across venues for identity matches of a market.
589
+ *
590
+ * @param params - Match filter parameters (uses relation: 'identity' internally)
591
+ * @returns Array of price comparisons across venues
592
+ */
593
+ compareMarketPrices(params: FetchMatchesParams): Promise<PriceComparison[]>;
594
+ /**
595
+ * Find hedging opportunities via subset/superset matches across venues.
596
+ *
597
+ * @param params - Match filter parameters
598
+ * @returns Array of subset/superset matches with live prices
599
+ */
600
+ fetchHedges(params: FetchMatchesParams): Promise<PriceComparison[]>;
601
+ /**
602
+ * Scan for arbitrage opportunities across identity matches.
603
+ *
604
+ * @param params - Arbitrage scan parameters (minSpread, category, limit)
605
+ * @returns Array of arbitrage opportunities sorted by spread
606
+ */
607
+ fetchArbitrage(params?: FetchArbitrageParams): Promise<ArbitrageOpportunity[]>;
562
608
  /**
563
609
  * @internal
564
610
  * Implementation for fetching/searching markets.
@@ -760,6 +760,54 @@ class PredictionMarketExchange {
760
760
  // Default implementation: no-op
761
761
  // Exchanges with WebSocket support should override this
762
762
  }
763
+ // ----------------------------------------------------------------------------
764
+ // Matching Methods (Router-only; stubs throw for standard exchanges)
765
+ // ----------------------------------------------------------------------------
766
+ /**
767
+ * Fetch cross-venue matches for a given market.
768
+ *
769
+ * @param params - Match filter parameters (marketId, relation, minConfidence, etc.)
770
+ * @returns Array of matched markets with relation and confidence
771
+ */
772
+ async fetchMatches(params) {
773
+ throw new Error("Method fetchMatches not implemented.");
774
+ }
775
+ /**
776
+ * Fetch cross-venue matches for a given event.
777
+ *
778
+ * @param params - Event match filter parameters (eventId, relation, etc.)
779
+ * @returns Array of matched events with market-level match details
780
+ */
781
+ async fetchEventMatches(params) {
782
+ throw new Error("Method fetchEventMatches not implemented.");
783
+ }
784
+ /**
785
+ * Compare prices across venues for identity matches of a market.
786
+ *
787
+ * @param params - Match filter parameters (uses relation: 'identity' internally)
788
+ * @returns Array of price comparisons across venues
789
+ */
790
+ async compareMarketPrices(params) {
791
+ throw new Error("Method compareMarketPrices not implemented.");
792
+ }
793
+ /**
794
+ * Find hedging opportunities via subset/superset matches across venues.
795
+ *
796
+ * @param params - Match filter parameters
797
+ * @returns Array of subset/superset matches with live prices
798
+ */
799
+ async fetchHedges(params) {
800
+ throw new Error("Method fetchHedges not implemented.");
801
+ }
802
+ /**
803
+ * Scan for arbitrage opportunities across identity matches.
804
+ *
805
+ * @param params - Arbitrage scan parameters (minSpread, category, limit)
806
+ * @returns Array of arbitrage opportunities sorted by spread
807
+ */
808
+ async fetchArbitrage(params) {
809
+ throw new Error("Method fetchArbitrage not implemented.");
810
+ }
763
811
  /**
764
812
  * @internal
765
813
  * Implementation for fetching/searching markets.
@@ -886,6 +934,8 @@ class PredictionMarketExchange {
886
934
  'watchAddress', 'unwatchAddress', 'watchOrderBook',
887
935
  'unwatchOrderBook', 'watchTrades', 'fetchMyTrades',
888
936
  'fetchClosedOrders', 'fetchAllOrders', 'buildOrder', 'submitOrder',
937
+ 'fetchMatches', 'fetchEventMatches', 'compareMarketPrices',
938
+ 'fetchHedges', 'fetchArbitrage',
889
939
  ];
890
940
  // Compile-time exhaustiveness check: fails tsc if a key exists in
891
941
  // ExchangeHas but is missing from _capabilityKeys above.
@@ -897,6 +947,8 @@ class PredictionMarketExchange {
897
947
  unwatchAddress: true, watchOrderBook: true, unwatchOrderBook: true,
898
948
  watchTrades: true, fetchMyTrades: true, fetchClosedOrders: true,
899
949
  fetchAllOrders: true, buildOrder: true, submitOrder: true,
950
+ fetchMatches: true, fetchEventMatches: true, compareMarketPrices: true,
951
+ fetchHedges: true, fetchArbitrage: true,
900
952
  };
901
953
  /**
902
954
  * Map from capability keys to the actual method(s) whose override status
@@ -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-04-22T16:11:36.646Z
3
+ * Generated at: 2026-04-23T07:50:04.153Z
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-04-22T16:11:36.646Z
6
+ * Generated at: 2026-04-23T07:50:04.153Z
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-04-22T16:11:36.689Z
3
+ * Generated at: 2026-04-23T07:50:04.190Z
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-04-22T16:11:36.689Z
6
+ * Generated at: 2026-04-23T07:50:04.190Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -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-04-22T16:11:36.707Z
3
+ * Generated at: 2026-04-23T07:50:04.201Z
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-04-22T16:11:36.707Z
6
+ * Generated at: 2026-04-23T07:50:04.201Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.myriadApiSpec = {
@@ -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-04-22T16:11:36.715Z
3
+ * Generated at: 2026-04-23T07:50:04.206Z
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-04-22T16:11:36.715Z
6
+ * Generated at: 2026-04-23T07:50:04.206Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.opinionApiSpec = {
@@ -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-04-22T16:11:36.654Z
3
+ * Generated at: 2026-04-23T07:50:04.161Z
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-04-22T16:11:36.654Z
6
+ * Generated at: 2026-04-23T07:50:04.161Z
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-04-22T16:11:36.669Z
3
+ * Generated at: 2026-04-23T07:50:04.172Z
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-04-22T16:11:36.669Z
6
+ * Generated at: 2026-04-23T07:50:04.172Z
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-04-22T16:11:36.666Z
3
+ * Generated at: 2026-04-23T07:50:04.170Z
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-04-22T16:11:36.666Z
6
+ * Generated at: 2026-04-23T07:50:04.170Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.polymarketGammaSpec = {
@@ -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-04-22T16:11:36.695Z
3
+ * Generated at: 2026-04-23T07:50:04.194Z
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-04-22T16:11:36.695Z
6
+ * Generated at: 2026-04-23T07:50:04.194Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -1,15 +1,15 @@
1
+ import { PredictionMarketExchange, type MarketFetchParams, type EventFetchParams } from '../BaseExchange';
1
2
  import type { UnifiedMarket, UnifiedEvent } from '../types';
2
- import type { RouterOptions, MatchResult, EventMatchResult, PriceComparison, ArbitrageOpportunity, FetchMatchesParams, FetchEventMatchesParams, FetchArbitrageParams, RouterMarketSearchParams, RouterEventSearchParams } from './types';
3
- export declare class Router {
4
- readonly name = "Router";
3
+ import type { RouterOptions, MatchResult, EventMatchResult, PriceComparison, ArbitrageOpportunity, FetchMatchesParams, FetchEventMatchesParams, FetchArbitrageParams } from './types';
4
+ export declare class Router extends PredictionMarketExchange {
5
5
  private readonly client;
6
6
  constructor(options: RouterOptions);
7
+ get name(): string;
8
+ protected fetchMarketsImpl(params?: MarketFetchParams): Promise<UnifiedMarket[]>;
9
+ protected fetchEventsImpl(params?: EventFetchParams): Promise<UnifiedEvent[]>;
7
10
  fetchMatches(params: FetchMatchesParams): Promise<MatchResult[]>;
8
11
  fetchEventMatches(params: FetchEventMatchesParams): Promise<EventMatchResult[]>;
9
12
  compareMarketPrices(params: FetchMatchesParams): Promise<PriceComparison[]>;
10
13
  fetchHedges(params: FetchMatchesParams): Promise<PriceComparison[]>;
11
14
  fetchArbitrage(params?: FetchArbitrageParams): Promise<ArbitrageOpportunity[]>;
12
- fetchMarkets(params?: RouterMarketSearchParams): Promise<UnifiedMarket[]>;
13
- fetchEvents(params?: RouterEventSearchParams): Promise<UnifiedEvent[]>;
14
- createOrder(): Promise<never>;
15
15
  }
@@ -1,19 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Router = void 0;
4
+ const BaseExchange_1 = require("../BaseExchange");
4
5
  const client_1 = require("./client");
5
- class Router {
6
- name = 'Router';
6
+ class Router extends BaseExchange_1.PredictionMarketExchange {
7
7
  client;
8
8
  constructor(options) {
9
+ super({ apiKey: options.apiKey });
9
10
  this.client = new client_1.PmxtApiClient(options.apiKey, options.baseUrl);
11
+ this.rateLimit = 100;
12
+ }
13
+ get name() {
14
+ return 'Router';
10
15
  }
11
16
  // -----------------------------------------------------------------------
12
- // Core: Cross-exchange market matches
17
+ // BaseExchange implementation delegates
18
+ // -----------------------------------------------------------------------
19
+ async fetchMarketsImpl(params) {
20
+ const response = await this.client.searchMarkets({
21
+ query: params?.query,
22
+ category: params?.category,
23
+ limit: params?.limit,
24
+ offset: params?.offset,
25
+ closed: params?.status === 'closed' || params?.status === 'inactive',
26
+ });
27
+ return response ?? [];
28
+ }
29
+ async fetchEventsImpl(params) {
30
+ const response = await this.client.searchEvents({
31
+ query: params?.query,
32
+ category: params?.category,
33
+ limit: params?.limit,
34
+ offset: params?.offset,
35
+ });
36
+ return response ?? [];
37
+ }
38
+ // -----------------------------------------------------------------------
39
+ // Cross-exchange market matches
13
40
  // -----------------------------------------------------------------------
14
41
  async fetchMatches(params) {
15
42
  const response = await this.client.getMarketMatches(params);
16
- return response.matches ?? [];
43
+ const matches = response.matches ?? [];
44
+ return matches.map((m) => ({
45
+ market: m.market,
46
+ relation: m.relation,
47
+ confidence: m.confidence,
48
+ reasoning: m.reasoning ?? null,
49
+ bestBid: m.market?.bestBid ?? null,
50
+ bestAsk: m.market?.bestAsk ?? null,
51
+ }));
17
52
  }
18
53
  // -----------------------------------------------------------------------
19
54
  // Cross-exchange event matches
@@ -81,7 +116,7 @@ class Router {
81
116
  if (matches.length === 0)
82
117
  continue;
83
118
  const sourceAsk = market.outcomes[0]?.price ?? null;
84
- const sourceBid = sourceAsk; // Best approximation from last price
119
+ const sourceBid = sourceAsk;
85
120
  const sourceVenue = market.sourceExchange ?? '';
86
121
  for (const match of matches) {
87
122
  const matchBid = match.bestBid;
@@ -120,22 +155,5 @@ class Router {
120
155
  opportunities.sort((a, b) => b.spread - a.spread);
121
156
  return opportunities;
122
157
  }
123
- // -----------------------------------------------------------------------
124
- // Cross-venue search
125
- // -----------------------------------------------------------------------
126
- async fetchMarkets(params) {
127
- const response = await this.client.searchMarkets(params);
128
- return response ?? [];
129
- }
130
- async fetchEvents(params) {
131
- const response = await this.client.searchEvents(params);
132
- return response ?? [];
133
- }
134
- // -----------------------------------------------------------------------
135
- // Disabled: order routing (future)
136
- // -----------------------------------------------------------------------
137
- async createOrder() {
138
- throw new Error('Router order routing is not yet implemented');
139
- }
140
158
  }
141
159
  exports.Router = Router;
@@ -23,20 +23,20 @@ describe('Router', () => {
23
23
  });
24
24
  describe('fetchMatches', () => {
25
25
  it('returns matches from the API using marketId', async () => {
26
- const mockMatches = [
26
+ const mockApiResponse = [
27
27
  {
28
- market: { marketId: 'k1', sourceExchange: 'kalshi' },
28
+ market: { marketId: 'k1', sourceExchange: 'kalshi', bestBid: 0.60, bestAsk: 0.65 },
29
29
  relation: 'identity',
30
30
  confidence: 0.95,
31
31
  reasoning: 'Same resolution condition.',
32
- bestBid: 0.60,
33
- bestAsk: 0.65,
34
32
  },
35
33
  ];
36
- clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
34
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockApiResponse });
37
35
  const result = await router.fetchMatches({ marketId: 'm1', relation: 'identity' });
38
36
  expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({ marketId: 'm1', relation: 'identity' });
39
- expect(result).toEqual(mockMatches);
37
+ expect(result[0].confidence).toBe(0.95);
38
+ expect(result[0].bestBid).toBe(0.60);
39
+ expect(result[0].bestAsk).toBe(0.65);
40
40
  });
41
41
  it('accepts slug as identifier', async () => {
42
42
  clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
@@ -64,12 +64,10 @@ describe('Router', () => {
64
64
  it('fetches identity matches with includePrices and maps to PriceComparison', async () => {
65
65
  const mockMatches = [
66
66
  {
67
- market: { marketId: 'k1', sourceExchange: 'kalshi', outcomes: [] },
67
+ market: { marketId: 'k1', sourceExchange: 'kalshi', outcomes: [], bestBid: 0.55, bestAsk: 0.62 },
68
68
  relation: 'identity',
69
69
  confidence: 0.9,
70
70
  reasoning: 'Same market.',
71
- bestBid: 0.55,
72
- bestAsk: 0.62,
73
71
  },
74
72
  ];
75
73
  clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
@@ -90,28 +88,22 @@ describe('Router', () => {
90
88
  it('returns only subset/superset matches with reasoning', async () => {
91
89
  const mockMatches = [
92
90
  {
93
- market: { marketId: 'k1', sourceExchange: 'kalshi' },
91
+ market: { marketId: 'k1', sourceExchange: 'kalshi', bestBid: 0.60, bestAsk: 0.65 },
94
92
  relation: 'identity',
95
93
  confidence: 0.95,
96
94
  reasoning: 'Same.',
97
- bestBid: 0.60,
98
- bestAsk: 0.65,
99
95
  },
100
96
  {
101
- market: { marketId: 'k2', sourceExchange: 'kalshi' },
97
+ market: { marketId: 'k2', sourceExchange: 'kalshi', bestBid: 0.40, bestAsk: 0.45 },
102
98
  relation: 'subset',
103
99
  confidence: 0.8,
104
100
  reasoning: 'Narrower market — nomination implies candidacy.',
105
- bestBid: 0.40,
106
- bestAsk: 0.45,
107
101
  },
108
102
  {
109
- market: { marketId: 'k3', sourceExchange: 'polymarket' },
103
+ market: { marketId: 'k3', sourceExchange: 'polymarket', bestBid: 0.70, bestAsk: 0.73 },
110
104
  relation: 'superset',
111
105
  confidence: 0.7,
112
106
  reasoning: 'Broader — popular vote does not guarantee election win.',
113
- bestBid: 0.70,
114
- bestAsk: 0.73,
115
107
  },
116
108
  ];
117
109
  clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
@@ -127,7 +119,7 @@ describe('Router', () => {
127
119
  const mockMarkets = [{ marketId: 'm1', title: 'BTC' }];
128
120
  clientInstance.searchMarkets = jest.fn().mockResolvedValue(mockMarkets);
129
121
  const result = await router.fetchMarkets({ query: 'bitcoin' });
130
- expect(clientInstance.searchMarkets).toHaveBeenCalledWith({ query: 'bitcoin' });
122
+ expect(clientInstance.searchMarkets).toHaveBeenCalled();
131
123
  expect(result).toEqual(mockMarkets);
132
124
  });
133
125
  });
@@ -136,13 +128,31 @@ describe('Router', () => {
136
128
  const mockEvents = [{ id: 'e1', title: 'Election' }];
137
129
  clientInstance.searchEvents = jest.fn().mockResolvedValue(mockEvents);
138
130
  const result = await router.fetchEvents({ query: 'election' });
139
- expect(clientInstance.searchEvents).toHaveBeenCalledWith({ query: 'election' });
131
+ expect(clientInstance.searchEvents).toHaveBeenCalled();
140
132
  expect(result).toEqual(mockEvents);
141
133
  });
142
134
  });
143
135
  describe('createOrder', () => {
144
136
  it('throws not implemented error', async () => {
145
- await expect(router.createOrder()).rejects.toThrow('Router order routing is not yet implemented');
137
+ await expect(router.createOrder({})).rejects.toThrow('not implemented');
138
+ });
139
+ });
140
+ describe('capabilities', () => {
141
+ it('reports matching methods as supported', () => {
142
+ expect(router.has.fetchMatches).toBe(true);
143
+ expect(router.has.fetchEventMatches).toBe(true);
144
+ expect(router.has.compareMarketPrices).toBe(true);
145
+ expect(router.has.fetchHedges).toBe(true);
146
+ expect(router.has.fetchArbitrage).toBe(true);
147
+ });
148
+ it('reports trading methods as unsupported', () => {
149
+ expect(router.has.createOrder).toBe(false);
150
+ expect(router.has.cancelOrder).toBe(false);
151
+ expect(router.has.fetchOrderBook).toBe(false);
152
+ });
153
+ it('reports search methods as supported', () => {
154
+ expect(router.has.fetchMarkets).toBe(true);
155
+ expect(router.has.fetchEvents).toBe(true);
146
156
  });
147
157
  });
148
158
  });
@@ -20,6 +20,7 @@ const opinion_1 = require("../exchanges/opinion");
20
20
  const metaculus_1 = require("../exchanges/metaculus");
21
21
  const smarkets_1 = require("../exchanges/smarkets");
22
22
  const polymarket_us_1 = require("../exchanges/polymarket_us");
23
+ const router_1 = require("../router");
23
24
  const errors_1 = require("../errors");
24
25
  function loadMethodVerbs() {
25
26
  const candidates = [
@@ -410,6 +411,10 @@ function createExchange(name, credentials) {
410
411
  apiKey: credentials?.apiKey || process.env.POLYMARKET_US_KEY_ID,
411
412
  privateKey: credentials?.privateKey || process.env.POLYMARKET_US_SECRET_KEY,
412
413
  });
414
+ case "router":
415
+ return new router_1.Router({
416
+ apiKey: process.env.PMXT_API_KEY || '',
417
+ });
413
418
  default:
414
419
  throw new Error(`Unknown exchange: ${name}`);
415
420
  }
@@ -357,5 +357,55 @@
357
357
  "close": {
358
358
  "verb": "post",
359
359
  "args": []
360
+ },
361
+ "fetchMatches": {
362
+ "verb": "get",
363
+ "args": [
364
+ {
365
+ "name": "params",
366
+ "kind": "object",
367
+ "optional": false
368
+ }
369
+ ]
370
+ },
371
+ "fetchEventMatches": {
372
+ "verb": "get",
373
+ "args": [
374
+ {
375
+ "name": "params",
376
+ "kind": "object",
377
+ "optional": false
378
+ }
379
+ ]
380
+ },
381
+ "compareMarketPrices": {
382
+ "verb": "post",
383
+ "args": [
384
+ {
385
+ "name": "params",
386
+ "kind": "object",
387
+ "optional": false
388
+ }
389
+ ]
390
+ },
391
+ "fetchHedges": {
392
+ "verb": "get",
393
+ "args": [
394
+ {
395
+ "name": "params",
396
+ "kind": "object",
397
+ "optional": false
398
+ }
399
+ ]
400
+ },
401
+ "fetchArbitrage": {
402
+ "verb": "get",
403
+ "args": [
404
+ {
405
+ "name": "params",
406
+ "kind": "object",
407
+ "optional": true
408
+ }
409
+ ]
360
410
  }
361
411
  }
@@ -1464,6 +1464,261 @@ paths:
1464
1464
  description: >-
1465
1465
  Close all WebSocket connections and clean up resources. Call this when you're done streaming to properly release
1466
1466
  connections.
1467
+ '/api/{exchange}/fetchMatches':
1468
+ get:
1469
+ summary: Fetch Matches
1470
+ operationId: fetchMatches
1471
+ parameters:
1472
+ - $ref: '#/components/parameters/ExchangeParam'
1473
+ - in: query
1474
+ name: marketId
1475
+ required: false
1476
+ schema:
1477
+ type: string
1478
+ - in: query
1479
+ name: slug
1480
+ required: false
1481
+ schema:
1482
+ type: string
1483
+ - in: query
1484
+ name: url
1485
+ required: false
1486
+ schema:
1487
+ type: string
1488
+ - in: query
1489
+ name: relation
1490
+ required: false
1491
+ schema:
1492
+ type: string
1493
+ enum:
1494
+ - identity
1495
+ - subset
1496
+ - superset
1497
+ - overlap
1498
+ - disjoint
1499
+ - in: query
1500
+ name: minConfidence
1501
+ required: false
1502
+ schema:
1503
+ type: number
1504
+ - in: query
1505
+ name: limit
1506
+ required: false
1507
+ schema:
1508
+ type: number
1509
+ - in: query
1510
+ name: includePrices
1511
+ required: false
1512
+ schema:
1513
+ type: boolean
1514
+ responses:
1515
+ '200':
1516
+ description: Fetch Matches response
1517
+ content:
1518
+ application/json:
1519
+ schema:
1520
+ allOf:
1521
+ - $ref: '#/components/schemas/BaseResponse'
1522
+ - type: object
1523
+ properties:
1524
+ data:
1525
+ type: array
1526
+ items:
1527
+ $ref: '#/components/schemas/MatchResult'
1528
+ description: Fetch cross-venue matches for a given market.
1529
+ '/api/{exchange}/fetchEventMatches':
1530
+ get:
1531
+ summary: Fetch Event Matches
1532
+ operationId: fetchEventMatches
1533
+ parameters:
1534
+ - $ref: '#/components/parameters/ExchangeParam'
1535
+ - in: query
1536
+ name: eventId
1537
+ required: false
1538
+ schema:
1539
+ type: string
1540
+ - in: query
1541
+ name: slug
1542
+ required: false
1543
+ schema:
1544
+ type: string
1545
+ - in: query
1546
+ name: relation
1547
+ required: false
1548
+ schema:
1549
+ type: string
1550
+ enum:
1551
+ - identity
1552
+ - subset
1553
+ - superset
1554
+ - overlap
1555
+ - disjoint
1556
+ - in: query
1557
+ name: minConfidence
1558
+ required: false
1559
+ schema:
1560
+ type: number
1561
+ - in: query
1562
+ name: limit
1563
+ required: false
1564
+ schema:
1565
+ type: number
1566
+ - in: query
1567
+ name: includePrices
1568
+ required: false
1569
+ schema:
1570
+ type: boolean
1571
+ responses:
1572
+ '200':
1573
+ description: Fetch Event Matches response
1574
+ content:
1575
+ application/json:
1576
+ schema:
1577
+ allOf:
1578
+ - $ref: '#/components/schemas/BaseResponse'
1579
+ - type: object
1580
+ properties:
1581
+ data:
1582
+ type: array
1583
+ items:
1584
+ $ref: '#/components/schemas/EventMatchResult'
1585
+ description: Fetch cross-venue matches for a given event.
1586
+ '/api/{exchange}/compareMarketPrices':
1587
+ post:
1588
+ summary: Compare Market Prices
1589
+ operationId: compareMarketPrices
1590
+ parameters:
1591
+ - $ref: '#/components/parameters/ExchangeParam'
1592
+ requestBody:
1593
+ content:
1594
+ application/json:
1595
+ schema:
1596
+ title: CompareMarketPricesRequest
1597
+ type: object
1598
+ properties:
1599
+ args:
1600
+ type: array
1601
+ maxItems: 1
1602
+ items:
1603
+ $ref: '#/components/schemas/FetchMatchesParams'
1604
+ minItems: 1
1605
+ credentials:
1606
+ $ref: '#/components/schemas/ExchangeCredentials'
1607
+ required:
1608
+ - args
1609
+ responses:
1610
+ '200':
1611
+ description: Compare Market Prices response
1612
+ content:
1613
+ application/json:
1614
+ schema:
1615
+ allOf:
1616
+ - $ref: '#/components/schemas/BaseResponse'
1617
+ - type: object
1618
+ properties:
1619
+ data:
1620
+ type: array
1621
+ items:
1622
+ $ref: '#/components/schemas/PriceComparison'
1623
+ description: Compare prices across venues for identity matches of a market.
1624
+ '/api/{exchange}/fetchHedges':
1625
+ get:
1626
+ summary: Fetch Hedges
1627
+ operationId: fetchHedges
1628
+ parameters:
1629
+ - $ref: '#/components/parameters/ExchangeParam'
1630
+ - in: query
1631
+ name: marketId
1632
+ required: false
1633
+ schema:
1634
+ type: string
1635
+ - in: query
1636
+ name: slug
1637
+ required: false
1638
+ schema:
1639
+ type: string
1640
+ - in: query
1641
+ name: url
1642
+ required: false
1643
+ schema:
1644
+ type: string
1645
+ - in: query
1646
+ name: relation
1647
+ required: false
1648
+ schema:
1649
+ type: string
1650
+ enum:
1651
+ - identity
1652
+ - subset
1653
+ - superset
1654
+ - overlap
1655
+ - disjoint
1656
+ - in: query
1657
+ name: minConfidence
1658
+ required: false
1659
+ schema:
1660
+ type: number
1661
+ - in: query
1662
+ name: limit
1663
+ required: false
1664
+ schema:
1665
+ type: number
1666
+ - in: query
1667
+ name: includePrices
1668
+ required: false
1669
+ schema:
1670
+ type: boolean
1671
+ responses:
1672
+ '200':
1673
+ description: Fetch Hedges response
1674
+ content:
1675
+ application/json:
1676
+ schema:
1677
+ allOf:
1678
+ - $ref: '#/components/schemas/BaseResponse'
1679
+ - type: object
1680
+ properties:
1681
+ data:
1682
+ type: array
1683
+ items:
1684
+ $ref: '#/components/schemas/PriceComparison'
1685
+ description: Find hedging opportunities via subset/superset matches across venues.
1686
+ '/api/{exchange}/fetchArbitrage':
1687
+ get:
1688
+ summary: Fetch Arbitrage
1689
+ operationId: fetchArbitrage
1690
+ parameters:
1691
+ - $ref: '#/components/parameters/ExchangeParam'
1692
+ - in: query
1693
+ name: minSpread
1694
+ required: false
1695
+ schema:
1696
+ type: number
1697
+ - in: query
1698
+ name: category
1699
+ required: false
1700
+ schema:
1701
+ type: string
1702
+ - in: query
1703
+ name: limit
1704
+ required: false
1705
+ schema:
1706
+ type: number
1707
+ responses:
1708
+ '200':
1709
+ description: Fetch Arbitrage response
1710
+ content:
1711
+ application/json:
1712
+ schema:
1713
+ allOf:
1714
+ - $ref: '#/components/schemas/BaseResponse'
1715
+ - type: object
1716
+ properties:
1717
+ data:
1718
+ type: array
1719
+ items:
1720
+ $ref: '#/components/schemas/ArbitrageOpportunity'
1721
+ description: Scan for arbitrage opportunities across identity matches.
1467
1722
  components:
1468
1723
  parameters:
1469
1724
  ExchangeParam:
@@ -1483,6 +1738,7 @@ components:
1483
1738
  - metaculus
1484
1739
  - smarkets
1485
1740
  - polymarket_us
1741
+ - router
1486
1742
  required: true
1487
1743
  description: The prediction market exchange to target.
1488
1744
  schemas:
@@ -2372,6 +2628,167 @@ components:
2372
2628
  max:
2373
2629
  type: number
2374
2630
  description: Sum of market volumes
2631
+ FetchMatchesParams:
2632
+ type: object
2633
+ properties:
2634
+ marketId:
2635
+ type: string
2636
+ slug:
2637
+ type: string
2638
+ url:
2639
+ type: string
2640
+ relation:
2641
+ type: string
2642
+ enum:
2643
+ - identity
2644
+ - subset
2645
+ - superset
2646
+ - overlap
2647
+ - disjoint
2648
+ minConfidence:
2649
+ type: number
2650
+ limit:
2651
+ type: number
2652
+ includePrices:
2653
+ type: boolean
2654
+ FetchEventMatchesParams:
2655
+ type: object
2656
+ properties:
2657
+ eventId:
2658
+ type: string
2659
+ slug:
2660
+ type: string
2661
+ relation:
2662
+ type: string
2663
+ enum:
2664
+ - identity
2665
+ - subset
2666
+ - superset
2667
+ - overlap
2668
+ - disjoint
2669
+ minConfidence:
2670
+ type: number
2671
+ limit:
2672
+ type: number
2673
+ includePrices:
2674
+ type: boolean
2675
+ FetchArbitrageParams:
2676
+ type: object
2677
+ properties:
2678
+ minSpread:
2679
+ type: number
2680
+ category:
2681
+ type: string
2682
+ limit:
2683
+ type: number
2684
+ MatchResult:
2685
+ type: object
2686
+ properties:
2687
+ market:
2688
+ $ref: '#/components/schemas/UnifiedMarket'
2689
+ relation:
2690
+ type: string
2691
+ enum:
2692
+ - identity
2693
+ - subset
2694
+ - superset
2695
+ - overlap
2696
+ - disjoint
2697
+ confidence:
2698
+ type: number
2699
+ reasoning:
2700
+ oneOf:
2701
+ - type: string
2702
+ - {}
2703
+ bestBid:
2704
+ oneOf:
2705
+ - type: number
2706
+ - {}
2707
+ bestAsk:
2708
+ oneOf:
2709
+ - type: number
2710
+ - {}
2711
+ required:
2712
+ - market
2713
+ - relation
2714
+ - confidence
2715
+ - reasoning
2716
+ - bestBid
2717
+ - bestAsk
2718
+ EventMatchResult:
2719
+ type: object
2720
+ properties:
2721
+ event:
2722
+ $ref: '#/components/schemas/UnifiedEvent'
2723
+ marketMatches:
2724
+ type: array
2725
+ items:
2726
+ $ref: '#/components/schemas/MatchResult'
2727
+ required:
2728
+ - event
2729
+ - marketMatches
2730
+ PriceComparison:
2731
+ type: object
2732
+ properties:
2733
+ market:
2734
+ $ref: '#/components/schemas/UnifiedMarket'
2735
+ relation:
2736
+ type: string
2737
+ enum:
2738
+ - identity
2739
+ - subset
2740
+ - superset
2741
+ - overlap
2742
+ - disjoint
2743
+ confidence:
2744
+ type: number
2745
+ reasoning:
2746
+ oneOf:
2747
+ - type: string
2748
+ - {}
2749
+ bestBid:
2750
+ oneOf:
2751
+ - type: number
2752
+ - {}
2753
+ bestAsk:
2754
+ oneOf:
2755
+ - type: number
2756
+ - {}
2757
+ venue:
2758
+ type: string
2759
+ required:
2760
+ - market
2761
+ - relation
2762
+ - confidence
2763
+ - reasoning
2764
+ - bestBid
2765
+ - bestAsk
2766
+ - venue
2767
+ ArbitrageOpportunity:
2768
+ type: object
2769
+ properties:
2770
+ marketA:
2771
+ $ref: '#/components/schemas/UnifiedMarket'
2772
+ marketB:
2773
+ $ref: '#/components/schemas/UnifiedMarket'
2774
+ spread:
2775
+ type: number
2776
+ buyVenue:
2777
+ type: string
2778
+ sellVenue:
2779
+ type: string
2780
+ buyPrice:
2781
+ type: number
2782
+ sellPrice:
2783
+ type: number
2784
+ required:
2785
+ - marketA
2786
+ - marketB
2787
+ - spread
2788
+ - buyVenue
2789
+ - sellVenue
2790
+ - buyPrice
2791
+ - sellPrice
2375
2792
  ExchangeCredentials:
2376
2793
  type: object
2377
2794
  description: Optional authentication credentials for exchange operations.
@@ -2593,3 +3010,10 @@ x-sdk-constructors:
2593
3010
  tsName: privateKey
2594
3011
  type: string
2595
3012
  description: Private key for authentication
3013
+ router:
3014
+ className: Router
3015
+ params:
3016
+ - name: pmxt_api_key
3017
+ tsName: pmxtApiKey
3018
+ type: string
3019
+ description: PMXT API key for hosted access
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.32.4",
3
+ "version": "2.33.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.32.4,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.32.4,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.33.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.33.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",