pmxt-core 2.32.2 → 2.32.4

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.
@@ -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-22T08:35:38.154Z
3
+ * Generated at: 2026-04-22T16:11:36.646Z
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-22T08:35:38.154Z
6
+ * Generated at: 2026-04-22T16:11:36.646Z
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-22T08:35:38.202Z
3
+ * Generated at: 2026-04-22T16:11:36.689Z
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-22T08:35:38.202Z
6
+ * Generated at: 2026-04-22T16:11:36.689Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.limitlessApiSpec = {
@@ -38,6 +38,7 @@ function mapMarketToUnified(market) {
38
38
  eventId: market.slug,
39
39
  title: market.title || market.question,
40
40
  description: market.description,
41
+ slug: market.slug,
41
42
  outcomes: outcomes,
42
43
  resolutionDate: market.expirationTimestamp ? new Date(market.expirationTimestamp) : new Date(),
43
44
  volume24h: Number(market.volumeFormatted || 0),
@@ -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-22T08:35:38.219Z
3
+ * Generated at: 2026-04-22T16:11:36.707Z
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-22T08:35:38.219Z
6
+ * Generated at: 2026-04-22T16:11:36.707Z
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-22T08:35:38.228Z
3
+ * Generated at: 2026-04-22T16:11:36.715Z
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-22T08:35:38.228Z
6
+ * Generated at: 2026-04-22T16:11:36.715Z
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-22T08:35:38.165Z
3
+ * Generated at: 2026-04-22T16:11:36.654Z
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-22T08:35:38.165Z
6
+ * Generated at: 2026-04-22T16:11:36.654Z
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-22T08:35:38.180Z
3
+ * Generated at: 2026-04-22T16:11:36.669Z
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-22T08:35:38.180Z
6
+ * Generated at: 2026-04-22T16:11:36.669Z
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-22T08:35:38.178Z
3
+ * Generated at: 2026-04-22T16:11:36.666Z
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-22T08:35:38.178Z
6
+ * Generated at: 2026-04-22T16:11:36.666Z
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-22T08:35:38.213Z
3
+ * Generated at: 2026-04-22T16:11:36.695Z
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-22T08:35:38.213Z
6
+ * Generated at: 2026-04-22T16:11:36.695Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './exchanges/opinion';
14
14
  export * from './exchanges/metaculus';
15
15
  export * from './exchanges/smarkets';
16
16
  export * from './exchanges/polymarket_us';
17
+ export * from './router';
17
18
  export * from './server/app';
18
19
  export * from './server/utils/port-manager';
19
20
  export * from './server/utils/lock-file';
@@ -28,6 +29,7 @@ import { OpinionExchange } from './exchanges/opinion';
28
29
  import { MetaculusExchange } from './exchanges/metaculus';
29
30
  import { SmarketsExchange } from './exchanges/smarkets';
30
31
  import { PolymarketUSExchange } from './exchanges/polymarket_us';
32
+ import { Router } from './router';
31
33
  declare const pmxt: {
32
34
  Polymarket: typeof PolymarketExchange;
33
35
  Limitless: typeof LimitlessExchange;
@@ -40,6 +42,7 @@ declare const pmxt: {
40
42
  Metaculus: typeof MetaculusExchange;
41
43
  Smarkets: typeof SmarketsExchange;
42
44
  PolymarketUS: typeof PolymarketUSExchange;
45
+ Router: typeof Router;
43
46
  };
44
47
  export declare const Polymarket: typeof PolymarketExchange;
45
48
  export declare const Limitless: typeof LimitlessExchange;
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __exportStar(require("./exchanges/opinion"), exports);
32
32
  __exportStar(require("./exchanges/metaculus"), exports);
33
33
  __exportStar(require("./exchanges/smarkets"), exports);
34
34
  __exportStar(require("./exchanges/polymarket_us"), exports);
35
+ __exportStar(require("./router"), exports);
35
36
  __exportStar(require("./server/app"), exports);
36
37
  __exportStar(require("./server/utils/port-manager"), exports);
37
38
  __exportStar(require("./server/utils/lock-file"), exports);
@@ -46,6 +47,7 @@ const opinion_1 = require("./exchanges/opinion");
46
47
  const metaculus_1 = require("./exchanges/metaculus");
47
48
  const smarkets_1 = require("./exchanges/smarkets");
48
49
  const polymarket_us_1 = require("./exchanges/polymarket_us");
50
+ const router_1 = require("./router");
49
51
  const pmxt = {
50
52
  Polymarket: polymarket_1.PolymarketExchange,
51
53
  Limitless: limitless_1.LimitlessExchange,
@@ -58,6 +60,7 @@ const pmxt = {
58
60
  Metaculus: metaculus_1.MetaculusExchange,
59
61
  Smarkets: smarkets_1.SmarketsExchange,
60
62
  PolymarketUS: polymarket_us_1.PolymarketUSExchange,
63
+ Router: router_1.Router,
61
64
  };
62
65
  exports.Polymarket = polymarket_1.PolymarketExchange;
63
66
  exports.Limitless = limitless_1.LimitlessExchange;
@@ -0,0 +1,15 @@
1
+ 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";
5
+ private readonly client;
6
+ constructor(options: RouterOptions);
7
+ fetchMatches(params: FetchMatchesParams): Promise<MatchResult[]>;
8
+ fetchEventMatches(params: FetchEventMatchesParams): Promise<EventMatchResult[]>;
9
+ compareMarketPrices(params: FetchMatchesParams): Promise<PriceComparison[]>;
10
+ fetchHedges(params: FetchMatchesParams): Promise<PriceComparison[]>;
11
+ fetchArbitrage(params?: FetchArbitrageParams): Promise<ArbitrageOpportunity[]>;
12
+ fetchMarkets(params?: RouterMarketSearchParams): Promise<UnifiedMarket[]>;
13
+ fetchEvents(params?: RouterEventSearchParams): Promise<UnifiedEvent[]>;
14
+ createOrder(): Promise<never>;
15
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const client_1 = require("./client");
5
+ class Router {
6
+ name = 'Router';
7
+ client;
8
+ constructor(options) {
9
+ this.client = new client_1.PmxtApiClient(options.apiKey, options.baseUrl);
10
+ }
11
+ // -----------------------------------------------------------------------
12
+ // Core: Cross-exchange market matches
13
+ // -----------------------------------------------------------------------
14
+ async fetchMatches(params) {
15
+ const response = await this.client.getMarketMatches(params);
16
+ return response.matches ?? [];
17
+ }
18
+ // -----------------------------------------------------------------------
19
+ // Cross-exchange event matches
20
+ // -----------------------------------------------------------------------
21
+ async fetchEventMatches(params) {
22
+ const response = await this.client.getEventMatches(params);
23
+ return response.matches ?? [];
24
+ }
25
+ // -----------------------------------------------------------------------
26
+ // Price comparison: identity matches with live prices
27
+ // -----------------------------------------------------------------------
28
+ async compareMarketPrices(params) {
29
+ const matches = await this.fetchMatches({
30
+ ...params,
31
+ relation: 'identity',
32
+ includePrices: true,
33
+ });
34
+ return matches.map((m) => ({
35
+ market: m.market,
36
+ relation: m.relation,
37
+ confidence: m.confidence,
38
+ reasoning: m.reasoning,
39
+ bestBid: m.bestBid,
40
+ bestAsk: m.bestAsk,
41
+ venue: m.market.sourceExchange ?? '',
42
+ }));
43
+ }
44
+ // -----------------------------------------------------------------------
45
+ // Hedging: subset/superset matches with live prices
46
+ // -----------------------------------------------------------------------
47
+ async fetchHedges(params) {
48
+ const matches = await this.fetchMatches({
49
+ ...params,
50
+ includePrices: true,
51
+ });
52
+ return matches
53
+ .filter((m) => m.relation === 'subset' || m.relation === 'superset')
54
+ .map((m) => ({
55
+ market: m.market,
56
+ relation: m.relation,
57
+ confidence: m.confidence,
58
+ reasoning: m.reasoning,
59
+ bestBid: m.bestBid,
60
+ bestAsk: m.bestAsk,
61
+ venue: m.market.sourceExchange ?? '',
62
+ }));
63
+ }
64
+ // -----------------------------------------------------------------------
65
+ // Arbitrage: scan identity matches for price spreads
66
+ // -----------------------------------------------------------------------
67
+ async fetchArbitrage(params) {
68
+ const minSpread = params?.minSpread ?? 0;
69
+ const limit = params?.limit ?? 50;
70
+ const markets = await this.fetchMarkets({
71
+ category: params?.category,
72
+ limit,
73
+ });
74
+ const opportunities = [];
75
+ for (const market of markets) {
76
+ const matches = await this.fetchMatches({
77
+ marketId: market.marketId,
78
+ relation: 'identity',
79
+ includePrices: true,
80
+ });
81
+ if (matches.length === 0)
82
+ continue;
83
+ const sourceAsk = market.outcomes[0]?.price ?? null;
84
+ const sourceBid = sourceAsk; // Best approximation from last price
85
+ const sourceVenue = market.sourceExchange ?? '';
86
+ for (const match of matches) {
87
+ const matchBid = match.bestBid;
88
+ const matchAsk = match.bestAsk;
89
+ const matchVenue = match.market.sourceExchange ?? '';
90
+ if (sourceAsk !== null && matchBid !== null) {
91
+ const spread = matchBid - sourceAsk;
92
+ if (spread >= minSpread) {
93
+ opportunities.push({
94
+ marketA: market,
95
+ marketB: match.market,
96
+ spread,
97
+ buyVenue: sourceVenue,
98
+ sellVenue: matchVenue,
99
+ buyPrice: sourceAsk,
100
+ sellPrice: matchBid,
101
+ });
102
+ }
103
+ }
104
+ if (matchAsk !== null && sourceBid !== null) {
105
+ const spread = sourceBid - matchAsk;
106
+ if (spread >= minSpread) {
107
+ opportunities.push({
108
+ marketA: match.market,
109
+ marketB: market,
110
+ spread,
111
+ buyVenue: matchVenue,
112
+ sellVenue: sourceVenue,
113
+ buyPrice: matchAsk,
114
+ sellPrice: sourceBid,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+ opportunities.sort((a, b) => b.spread - a.spread);
121
+ return opportunities;
122
+ }
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
+ }
141
+ exports.Router = Router;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Router_1 = require("./Router");
4
+ const client_1 = require("./client");
5
+ jest.mock('./client');
6
+ const MockedClient = client_1.PmxtApiClient;
7
+ describe('Router', () => {
8
+ let router;
9
+ let clientInstance;
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ MockedClient.mockClear();
13
+ router = new Router_1.Router({ apiKey: 'test-key' });
14
+ clientInstance = MockedClient.mock.instances[0];
15
+ });
16
+ describe('constructor', () => {
17
+ it('has name "Router"', () => {
18
+ expect(router.name).toBe('Router');
19
+ });
20
+ it('does not require exchanges option', () => {
21
+ expect(() => new Router_1.Router({ apiKey: 'key' })).not.toThrow();
22
+ });
23
+ });
24
+ describe('fetchMatches', () => {
25
+ it('returns matches from the API using marketId', async () => {
26
+ const mockMatches = [
27
+ {
28
+ market: { marketId: 'k1', sourceExchange: 'kalshi' },
29
+ relation: 'identity',
30
+ confidence: 0.95,
31
+ reasoning: 'Same resolution condition.',
32
+ bestBid: 0.60,
33
+ bestAsk: 0.65,
34
+ },
35
+ ];
36
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
37
+ const result = await router.fetchMatches({ marketId: 'm1', relation: 'identity' });
38
+ expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({ marketId: 'm1', relation: 'identity' });
39
+ expect(result).toEqual(mockMatches);
40
+ });
41
+ it('accepts slug as identifier', async () => {
42
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
43
+ await router.fetchMatches({ slug: 'btc-100k' });
44
+ expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({ slug: 'btc-100k' });
45
+ });
46
+ it('returns empty array when no matches', async () => {
47
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({});
48
+ const result = await router.fetchMatches({ marketId: 'm1' });
49
+ expect(result).toEqual([]);
50
+ });
51
+ });
52
+ describe('fetchEventMatches', () => {
53
+ it('returns event matches from the API', async () => {
54
+ const mockMatches = [
55
+ { event: { id: 'e2' }, marketMatches: [] },
56
+ ];
57
+ clientInstance.getEventMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
58
+ const result = await router.fetchEventMatches({ eventId: 'e1' });
59
+ expect(clientInstance.getEventMatches).toHaveBeenCalledWith({ eventId: 'e1' });
60
+ expect(result).toEqual(mockMatches);
61
+ });
62
+ });
63
+ describe('compareMarketPrices', () => {
64
+ it('fetches identity matches with includePrices and maps to PriceComparison', async () => {
65
+ const mockMatches = [
66
+ {
67
+ market: { marketId: 'k1', sourceExchange: 'kalshi', outcomes: [] },
68
+ relation: 'identity',
69
+ confidence: 0.9,
70
+ reasoning: 'Same market.',
71
+ bestBid: 0.55,
72
+ bestAsk: 0.62,
73
+ },
74
+ ];
75
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
76
+ const result = await router.compareMarketPrices({ marketId: 'm1' });
77
+ expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({
78
+ marketId: 'm1',
79
+ relation: 'identity',
80
+ includePrices: true,
81
+ });
82
+ expect(result).toHaveLength(1);
83
+ expect(result[0].bestBid).toBe(0.55);
84
+ expect(result[0].bestAsk).toBe(0.62);
85
+ expect(result[0].venue).toBe('kalshi');
86
+ expect(result[0].reasoning).toBe('Same market.');
87
+ });
88
+ });
89
+ describe('fetchHedges', () => {
90
+ it('returns only subset/superset matches with reasoning', async () => {
91
+ const mockMatches = [
92
+ {
93
+ market: { marketId: 'k1', sourceExchange: 'kalshi' },
94
+ relation: 'identity',
95
+ confidence: 0.95,
96
+ reasoning: 'Same.',
97
+ bestBid: 0.60,
98
+ bestAsk: 0.65,
99
+ },
100
+ {
101
+ market: { marketId: 'k2', sourceExchange: 'kalshi' },
102
+ relation: 'subset',
103
+ confidence: 0.8,
104
+ reasoning: 'Narrower market — nomination implies candidacy.',
105
+ bestBid: 0.40,
106
+ bestAsk: 0.45,
107
+ },
108
+ {
109
+ market: { marketId: 'k3', sourceExchange: 'polymarket' },
110
+ relation: 'superset',
111
+ confidence: 0.7,
112
+ reasoning: 'Broader — popular vote does not guarantee election win.',
113
+ bestBid: 0.70,
114
+ bestAsk: 0.73,
115
+ },
116
+ ];
117
+ clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
118
+ const result = await router.fetchHedges({ marketId: 'm1' });
119
+ expect(result).toHaveLength(2);
120
+ expect(result[0].relation).toBe('subset');
121
+ expect(result[0].reasoning).toBe('Narrower market — nomination implies candidacy.');
122
+ expect(result[1].relation).toBe('superset');
123
+ });
124
+ });
125
+ describe('fetchMarkets', () => {
126
+ it('returns markets from the API', async () => {
127
+ const mockMarkets = [{ marketId: 'm1', title: 'BTC' }];
128
+ clientInstance.searchMarkets = jest.fn().mockResolvedValue(mockMarkets);
129
+ const result = await router.fetchMarkets({ query: 'bitcoin' });
130
+ expect(clientInstance.searchMarkets).toHaveBeenCalledWith({ query: 'bitcoin' });
131
+ expect(result).toEqual(mockMarkets);
132
+ });
133
+ });
134
+ describe('fetchEvents', () => {
135
+ it('returns events from the API', async () => {
136
+ const mockEvents = [{ id: 'e1', title: 'Election' }];
137
+ clientInstance.searchEvents = jest.fn().mockResolvedValue(mockEvents);
138
+ const result = await router.fetchEvents({ query: 'election' });
139
+ expect(clientInstance.searchEvents).toHaveBeenCalledWith({ query: 'election' });
140
+ expect(result).toEqual(mockEvents);
141
+ });
142
+ });
143
+ describe('createOrder', () => {
144
+ it('throws not implemented error', async () => {
145
+ await expect(router.createOrder()).rejects.toThrow('Router order routing is not yet implemented');
146
+ });
147
+ });
148
+ });
@@ -0,0 +1,11 @@
1
+ import type { FetchMatchesParams, FetchEventMatchesParams, RouterMarketSearchParams, RouterEventSearchParams } from './types';
2
+ export declare class PmxtApiClient {
3
+ private readonly http;
4
+ constructor(apiKey: string, baseUrl?: string);
5
+ getMarketMatches(params: FetchMatchesParams): Promise<any>;
6
+ getEventMatches(params: FetchEventMatchesParams): Promise<any>;
7
+ searchMarkets(params?: RouterMarketSearchParams): Promise<any>;
8
+ searchEvents(params?: RouterEventSearchParams): Promise<any>;
9
+ private request;
10
+ private mapError;
11
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PmxtApiClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const errors_1 = require("../errors");
9
+ const DEFAULT_BASE_URL = 'https://api.pmxt.dev';
10
+ class PmxtApiClient {
11
+ http;
12
+ constructor(apiKey, baseUrl) {
13
+ this.http = axios_1.default.create({
14
+ baseURL: baseUrl ?? DEFAULT_BASE_URL,
15
+ headers: {
16
+ Authorization: `Bearer ${apiKey}`,
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ timeout: 30_000,
20
+ });
21
+ }
22
+ async getMarketMatches(params) {
23
+ const id = params.marketId ?? params.slug ?? params.url;
24
+ if (!id)
25
+ throw new errors_1.BadRequest('One of marketId, slug, or url is required', 'Router');
26
+ const query = {};
27
+ if (params.relation)
28
+ query.relation = params.relation;
29
+ if (params.minConfidence !== undefined)
30
+ query.minConfidence = String(params.minConfidence);
31
+ if (params.limit !== undefined)
32
+ query.limit = String(params.limit);
33
+ if (params.includePrices)
34
+ query.includePrices = 'true';
35
+ const res = await this.request('GET', `/v0/markets/${encodeURIComponent(id)}/matches`, query);
36
+ return res.data;
37
+ }
38
+ async getEventMatches(params) {
39
+ const id = params.eventId ?? params.slug;
40
+ if (!id)
41
+ throw new errors_1.BadRequest('One of eventId or slug is required', 'Router');
42
+ const query = {};
43
+ if (params.relation)
44
+ query.relation = params.relation;
45
+ if (params.minConfidence !== undefined)
46
+ query.minConfidence = String(params.minConfidence);
47
+ if (params.limit !== undefined)
48
+ query.limit = String(params.limit);
49
+ if (params.includePrices)
50
+ query.includePrices = 'true';
51
+ const res = await this.request('GET', `/v0/events/${encodeURIComponent(id)}/matches`, query);
52
+ return res.data;
53
+ }
54
+ async searchMarkets(params) {
55
+ const query = {};
56
+ if (params?.query)
57
+ query.q = params.query;
58
+ if (params?.sourceExchange)
59
+ query.sourceExchange = params.sourceExchange;
60
+ if (params?.category)
61
+ query.category = params.category;
62
+ if (params?.limit !== undefined)
63
+ query.limit = String(params.limit);
64
+ if (params?.offset !== undefined)
65
+ query.offset = String(params.offset);
66
+ if (params?.closed)
67
+ query.closed = 'true';
68
+ const res = await this.request('GET', '/v0/markets', query);
69
+ return res.data;
70
+ }
71
+ async searchEvents(params) {
72
+ const query = {};
73
+ if (params?.query)
74
+ query.q = params.query;
75
+ if (params?.sourceExchange)
76
+ query.sourceExchange = params.sourceExchange;
77
+ if (params?.category)
78
+ query.category = params.category;
79
+ if (params?.limit !== undefined)
80
+ query.limit = String(params.limit);
81
+ if (params?.offset !== undefined)
82
+ query.offset = String(params.offset);
83
+ if (params?.closed)
84
+ query.closed = 'true';
85
+ const res = await this.request('GET', '/v0/events', query);
86
+ return res.data;
87
+ }
88
+ // -----------------------------------------------------------------------
89
+ // Internal
90
+ // -----------------------------------------------------------------------
91
+ async request(method, path, query) {
92
+ try {
93
+ const response = await this.http.request({
94
+ method,
95
+ url: path,
96
+ params: query,
97
+ });
98
+ return response.data;
99
+ }
100
+ catch (error) {
101
+ throw this.mapError(error);
102
+ }
103
+ }
104
+ mapError(error) {
105
+ if (axios_1.default.isAxiosError(error)) {
106
+ const status = error.response?.status;
107
+ const message = error.response?.data?.error ??
108
+ error.response?.data?.message ??
109
+ error.message;
110
+ switch (status) {
111
+ case 401:
112
+ return new errors_1.AuthenticationError(message, 'Router');
113
+ case 404:
114
+ return new errors_1.NotFound(message, 'Router');
115
+ case 429: {
116
+ const retryAfter = error.response?.headers?.['retry-after'];
117
+ return new errors_1.RateLimitExceeded(message, retryAfter ? parseInt(retryAfter, 10) : undefined, 'Router');
118
+ }
119
+ case 400:
120
+ return new errors_1.BadRequest(message, 'Router');
121
+ default:
122
+ if (status && status >= 500) {
123
+ return new errors_1.ExchangeNotAvailable(`Router API error (${status}): ${message}`, 'Router');
124
+ }
125
+ return new errors_1.BadRequest(message, 'Router');
126
+ }
127
+ }
128
+ if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') {
129
+ return new errors_1.NetworkError(`Network error: ${error.message}`, 'Router');
130
+ }
131
+ return error instanceof Error ? error : new Error(String(error));
132
+ }
133
+ }
134
+ exports.PmxtApiClient = PmxtApiClient;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const axios_1 = __importDefault(require("axios"));
7
+ const client_1 = require("./client");
8
+ const errors_1 = require("../errors");
9
+ jest.mock('axios', () => {
10
+ const mockInstance = {
11
+ request: jest.fn(),
12
+ defaults: { headers: { common: {} } },
13
+ };
14
+ const actualAxios = jest.requireActual('axios');
15
+ const mockAxios = {
16
+ create: jest.fn(() => mockInstance),
17
+ isAxiosError: actualAxios.isAxiosError,
18
+ };
19
+ return {
20
+ __esModule: true,
21
+ ...mockAxios,
22
+ default: mockAxios,
23
+ };
24
+ });
25
+ const mockAxiosInstance = axios_1.default.create();
26
+ describe('PmxtApiClient', () => {
27
+ let client;
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ mockAxiosInstance.request.mockReset();
31
+ client = new client_1.PmxtApiClient('test-api-key');
32
+ });
33
+ it('creates axios instance with Bearer token and default base URL', () => {
34
+ expect(axios_1.default.create).toHaveBeenCalledWith(expect.objectContaining({
35
+ baseURL: 'https://api.pmxt.dev',
36
+ headers: expect.objectContaining({
37
+ Authorization: 'Bearer test-api-key',
38
+ }),
39
+ }));
40
+ });
41
+ it('accepts a custom base URL', () => {
42
+ new client_1.PmxtApiClient('key', 'http://localhost:4111');
43
+ expect(axios_1.default.create).toHaveBeenCalledWith(expect.objectContaining({
44
+ baseURL: 'http://localhost:4111',
45
+ }));
46
+ });
47
+ describe('getMarketMatches', () => {
48
+ it('calls GET /v0/markets/:id/matches with marketId', async () => {
49
+ const mockData = { data: { market: {}, matches: [{ relation: 'identity' }] } };
50
+ mockAxiosInstance.request.mockResolvedValue({ data: mockData });
51
+ const result = await client.getMarketMatches({ marketId: 'abc-123' });
52
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
53
+ method: 'GET',
54
+ url: '/v0/markets/abc-123/matches',
55
+ params: {},
56
+ });
57
+ expect(result).toEqual(mockData.data);
58
+ });
59
+ it('accepts slug as identifier', async () => {
60
+ mockAxiosInstance.request.mockResolvedValue({
61
+ data: { data: { matches: [] } },
62
+ });
63
+ await client.getMarketMatches({ slug: 'will-btc-hit-100k' });
64
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
65
+ method: 'GET',
66
+ url: '/v0/markets/will-btc-hit-100k/matches',
67
+ params: {},
68
+ });
69
+ });
70
+ it('accepts url as identifier', async () => {
71
+ mockAxiosInstance.request.mockResolvedValue({
72
+ data: { data: { matches: [] } },
73
+ });
74
+ await client.getMarketMatches({ url: 'https://polymarket.com/event/btc' });
75
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
76
+ method: 'GET',
77
+ url: '/v0/markets/https%3A%2F%2Fpolymarket.com%2Fevent%2Fbtc/matches',
78
+ params: {},
79
+ });
80
+ });
81
+ it('throws BadRequest when no identifier provided', async () => {
82
+ await expect(client.getMarketMatches({})).rejects.toThrow(errors_1.BadRequest);
83
+ });
84
+ it('passes query params including includePrices', async () => {
85
+ mockAxiosInstance.request.mockResolvedValue({
86
+ data: { data: { matches: [] } },
87
+ });
88
+ await client.getMarketMatches({
89
+ marketId: 'abc',
90
+ relation: 'identity',
91
+ minConfidence: 0.8,
92
+ limit: 10,
93
+ includePrices: true,
94
+ });
95
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
96
+ method: 'GET',
97
+ url: '/v0/markets/abc/matches',
98
+ params: { relation: 'identity', minConfidence: '0.8', limit: '10', includePrices: 'true' },
99
+ });
100
+ });
101
+ });
102
+ describe('getEventMatches', () => {
103
+ it('calls GET /v0/events/:id/matches with eventId', async () => {
104
+ mockAxiosInstance.request.mockResolvedValue({
105
+ data: { data: { event: {}, matches: [] } },
106
+ });
107
+ await client.getEventMatches({ eventId: 'evt-1' });
108
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
109
+ method: 'GET',
110
+ url: '/v0/events/evt-1/matches',
111
+ params: {},
112
+ });
113
+ });
114
+ it('throws BadRequest when no identifier provided', async () => {
115
+ await expect(client.getEventMatches({})).rejects.toThrow(errors_1.BadRequest);
116
+ });
117
+ });
118
+ describe('searchMarkets', () => {
119
+ it('calls GET /v0/markets with query params', async () => {
120
+ mockAxiosInstance.request.mockResolvedValue({
121
+ data: { data: [] },
122
+ });
123
+ await client.searchMarkets({
124
+ query: 'bitcoin',
125
+ sourceExchange: 'polymarket',
126
+ limit: 20,
127
+ });
128
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
129
+ method: 'GET',
130
+ url: '/v0/markets',
131
+ params: { q: 'bitcoin', sourceExchange: 'polymarket', limit: '20' },
132
+ });
133
+ });
134
+ });
135
+ describe('searchEvents', () => {
136
+ it('calls GET /v0/events with query params', async () => {
137
+ mockAxiosInstance.request.mockResolvedValue({
138
+ data: { data: [] },
139
+ });
140
+ await client.searchEvents({ query: 'election', category: 'politics' });
141
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith({
142
+ method: 'GET',
143
+ url: '/v0/events',
144
+ params: { q: 'election', category: 'politics' },
145
+ });
146
+ });
147
+ });
148
+ describe('error mapping', () => {
149
+ function makeAxiosError(status, data) {
150
+ const error = new Error('Request failed');
151
+ error.isAxiosError = true;
152
+ error.response = { status, data: data ?? { error: `Error ${status}` }, headers: {} };
153
+ error.config = {};
154
+ error.toJSON = () => ({});
155
+ Object.defineProperty(error, '__CANCEL__', { value: false });
156
+ return error;
157
+ }
158
+ it('maps 401 to AuthenticationError', async () => {
159
+ mockAxiosInstance.request.mockRejectedValue(makeAxiosError(401, { error: 'Invalid API key' }));
160
+ await expect(client.getMarketMatches({ marketId: 'x' })).rejects.toThrow(errors_1.AuthenticationError);
161
+ });
162
+ it('maps 404 to NotFound', async () => {
163
+ mockAxiosInstance.request.mockRejectedValue(makeAxiosError(404, { error: 'market not found' }));
164
+ await expect(client.getMarketMatches({ marketId: 'x' })).rejects.toThrow(errors_1.NotFound);
165
+ });
166
+ it('maps 429 to RateLimitExceeded', async () => {
167
+ const err = makeAxiosError(429, { error: 'Too many requests' });
168
+ err.response.headers = { 'retry-after': '30' };
169
+ mockAxiosInstance.request.mockRejectedValue(err);
170
+ await expect(client.getMarketMatches({ marketId: 'x' })).rejects.toThrow(errors_1.RateLimitExceeded);
171
+ });
172
+ it('maps 400 to BadRequest', async () => {
173
+ mockAxiosInstance.request.mockRejectedValue(makeAxiosError(400, { error: 'Invalid relation' }));
174
+ await expect(client.getMarketMatches({ marketId: 'x' })).rejects.toThrow(errors_1.BadRequest);
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,3 @@
1
+ export { Router } from './Router';
2
+ export { PmxtApiClient } from './client';
3
+ export * from './types';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.PmxtApiClient = exports.Router = void 0;
18
+ var Router_1 = require("./Router");
19
+ Object.defineProperty(exports, "Router", { enumerable: true, get: function () { return Router_1.Router; } });
20
+ var client_1 = require("./client");
21
+ Object.defineProperty(exports, "PmxtApiClient", { enumerable: true, get: function () { return client_1.PmxtApiClient; } });
22
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,74 @@
1
+ import type { UnifiedMarket, UnifiedEvent } from '../types';
2
+ export type MatchRelation = 'identity' | 'subset' | 'superset' | 'overlap' | 'disjoint';
3
+ export interface RouterOptions {
4
+ apiKey: string;
5
+ baseUrl?: string;
6
+ }
7
+ export interface MatchResult {
8
+ market: UnifiedMarket;
9
+ relation: MatchRelation;
10
+ confidence: number;
11
+ reasoning: string | null;
12
+ bestBid: number | null;
13
+ bestAsk: number | null;
14
+ }
15
+ export interface EventMatchResult {
16
+ event: UnifiedEvent;
17
+ marketMatches: MatchResult[];
18
+ }
19
+ export interface PriceComparison {
20
+ market: UnifiedMarket;
21
+ relation: MatchRelation;
22
+ confidence: number;
23
+ reasoning: string | null;
24
+ bestBid: number | null;
25
+ bestAsk: number | null;
26
+ venue: string;
27
+ }
28
+ export interface ArbitrageOpportunity {
29
+ marketA: UnifiedMarket;
30
+ marketB: UnifiedMarket;
31
+ spread: number;
32
+ buyVenue: string;
33
+ sellVenue: string;
34
+ buyPrice: number;
35
+ sellPrice: number;
36
+ }
37
+ export interface FetchMatchesParams {
38
+ marketId?: string;
39
+ slug?: string;
40
+ url?: string;
41
+ relation?: MatchRelation;
42
+ minConfidence?: number;
43
+ limit?: number;
44
+ includePrices?: boolean;
45
+ }
46
+ export interface FetchEventMatchesParams {
47
+ eventId?: string;
48
+ slug?: string;
49
+ relation?: MatchRelation;
50
+ minConfidence?: number;
51
+ limit?: number;
52
+ includePrices?: boolean;
53
+ }
54
+ export interface FetchArbitrageParams {
55
+ minSpread?: number;
56
+ category?: string;
57
+ limit?: number;
58
+ }
59
+ export interface RouterMarketSearchParams {
60
+ query?: string;
61
+ sourceExchange?: string;
62
+ category?: string;
63
+ limit?: number;
64
+ offset?: number;
65
+ closed?: boolean;
66
+ }
67
+ export interface RouterEventSearchParams {
68
+ query?: string;
69
+ sourceExchange?: string;
70
+ category?: string;
71
+ limit?: number;
72
+ offset?: number;
73
+ closed?: boolean;
74
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "2.32.2",
3
+ "version": "2.32.4",
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.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.32.2,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.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",
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",