pmxt-core 2.39.1 → 2.40.1
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.
- package/dist/exchanges/baozi/fetcher.js +28 -8
- package/dist/exchanges/baozi/index.js +6 -4
- package/dist/exchanges/gemini-titan/auth.d.ts +34 -0
- package/dist/exchanges/gemini-titan/auth.js +80 -0
- package/dist/exchanges/gemini-titan/config.d.ts +15 -0
- package/dist/exchanges/gemini-titan/config.js +24 -0
- package/dist/exchanges/gemini-titan/errors.d.ts +20 -0
- package/dist/exchanges/gemini-titan/errors.js +75 -0
- package/dist/exchanges/gemini-titan/fetcher.d.ts +26 -0
- package/dist/exchanges/gemini-titan/fetcher.js +148 -0
- package/dist/exchanges/gemini-titan/index.d.ts +31 -0
- package/dist/exchanges/gemini-titan/index.js +188 -0
- package/dist/exchanges/gemini-titan/normalizer.d.ts +13 -0
- package/dist/exchanges/gemini-titan/normalizer.js +229 -0
- package/dist/exchanges/gemini-titan/types.d.ts +220 -0
- package/dist/exchanges/gemini-titan/types.js +6 -0
- package/dist/exchanges/gemini-titan/utils.d.ts +30 -0
- package/dist/exchanges/gemini-titan/utils.js +57 -0
- package/dist/exchanges/gemini-titan/websocket.d.ts +46 -0
- package/dist/exchanges/gemini-titan/websocket.js +295 -0
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/fetcher.js +6 -2
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/index.js +3 -6
- package/dist/exchanges/limitless/utils.js +9 -1
- package/dist/exchanges/metaculus/fetchEvents.js +7 -2
- package/dist/exchanges/mock/index.d.ts +55 -0
- package/dist/exchanges/mock/index.js +603 -0
- package/dist/exchanges/mock/seededRng.d.ts +10 -0
- package/dist/exchanges/mock/seededRng.js +48 -0
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/websocket.d.ts +4 -0
- package/dist/exchanges/myriad/websocket.js +51 -6
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/auth.js +5 -2
- package/dist/exchanges/polymarket/index.js +2 -1
- package/dist/exchanges/polymarket_us/normalizer.js +5 -1
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/index.js +9 -6
- package/dist/exchanges/smarkets/fetcher.js +6 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -1
- package/dist/router/Router.js +55 -21
- package/dist/server/exchange-factory.js +9 -0
- package/dist/server/openapi.yaml +22 -0
- package/dist/server/ws-handler.js +13 -3
- package/package.json +3 -3
- package/dist/exchanges/baozi/price.test.d.ts +0 -1
- package/dist/exchanges/baozi/price.test.js +0 -33
- package/dist/exchanges/kalshi/kalshi.test.d.ts +0 -1
- package/dist/exchanges/kalshi/kalshi.test.js +0 -641
- package/dist/exchanges/kalshi/price.test.d.ts +0 -1
- package/dist/exchanges/kalshi/price.test.js +0 -24
- package/dist/exchanges/myriad/price.test.d.ts +0 -1
- package/dist/exchanges/myriad/price.test.js +0 -17
- package/dist/exchanges/polymarket_us/errors.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/errors.test.js +0 -54
- package/dist/exchanges/polymarket_us/index.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/index.test.js +0 -237
- package/dist/exchanges/polymarket_us/normalizer.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/normalizer.test.js +0 -224
- package/dist/exchanges/polymarket_us/price.test.d.ts +0 -1
- package/dist/exchanges/polymarket_us/price.test.js +0 -131
- package/dist/exchanges/polymarket_us/websocket.test.d.ts +0 -8
- package/dist/exchanges/polymarket_us/websocket.test.js +0 -162
- package/dist/exchanges/smarkets/price.test.d.ts +0 -1
- package/dist/exchanges/smarkets/price.test.js +0 -50
- package/dist/router/Router.test.d.ts +0 -1
- package/dist/router/Router.test.js +0 -328
- package/dist/router/client.test.d.ts +0 -1
- package/dist/router/client.test.js +0 -177
|
@@ -1,328 +0,0 @@
|
|
|
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
|
-
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
|
-
}
|
|
15
|
-
describe('Router', () => {
|
|
16
|
-
let router;
|
|
17
|
-
let clientInstance;
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
jest.clearAllMocks();
|
|
20
|
-
MockedClient.mockClear();
|
|
21
|
-
router = new Router_1.Router({ apiKey: 'test-key' });
|
|
22
|
-
clientInstance = MockedClient.mock.instances[0];
|
|
23
|
-
});
|
|
24
|
-
describe('constructor', () => {
|
|
25
|
-
it('has name "Router"', () => {
|
|
26
|
-
expect(router.name).toBe('Router');
|
|
27
|
-
});
|
|
28
|
-
it('does not require exchanges option', () => {
|
|
29
|
-
expect(() => new Router_1.Router({ apiKey: 'key' })).not.toThrow();
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe('fetchMarketMatches', () => {
|
|
33
|
-
it('returns matches from the API using marketId', async () => {
|
|
34
|
-
const mockApiResponse = [
|
|
35
|
-
{
|
|
36
|
-
market: { marketId: 'k1', sourceExchange: 'kalshi', bestBid: 0.60, bestAsk: 0.65 },
|
|
37
|
-
relation: 'identity',
|
|
38
|
-
confidence: 0.95,
|
|
39
|
-
reasoning: 'Same resolution condition.',
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockApiResponse });
|
|
43
|
-
const result = await router.fetchMarketMatches({ marketId: 'm1', relation: 'identity' });
|
|
44
|
-
expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({ marketId: 'm1', relation: 'identity' });
|
|
45
|
-
expect(result[0].confidence).toBe(0.95);
|
|
46
|
-
expect(result[0].bestBid).toBe(0.60);
|
|
47
|
-
expect(result[0].bestAsk).toBe(0.65);
|
|
48
|
-
});
|
|
49
|
-
it('accepts slug as identifier', async () => {
|
|
50
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: [] });
|
|
51
|
-
await router.fetchMarketMatches({ slug: 'btc-100k' });
|
|
52
|
-
expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({ slug: 'btc-100k' });
|
|
53
|
-
});
|
|
54
|
-
it('returns empty array when no matches', async () => {
|
|
55
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({});
|
|
56
|
-
const result = await router.fetchMarketMatches({ marketId: 'm1' });
|
|
57
|
-
expect(result).toEqual([]);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe('fetchMatches (deprecated)', () => {
|
|
61
|
-
it('delegates to fetchMarketMatches and logs deprecation warning', async () => {
|
|
62
|
-
const mockApiResponse = [
|
|
63
|
-
{
|
|
64
|
-
market: { marketId: 'k1', sourceExchange: 'kalshi', bestBid: 0.60, bestAsk: 0.65 },
|
|
65
|
-
relation: 'identity',
|
|
66
|
-
confidence: 0.95,
|
|
67
|
-
reasoning: 'Same resolution condition.',
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockApiResponse });
|
|
71
|
-
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
72
|
-
const result = await router.fetchMatches({ marketId: 'm1' });
|
|
73
|
-
expect(warnSpy).toHaveBeenCalledWith('[pmxt] fetchMatches is deprecated, use fetchMarketMatches instead');
|
|
74
|
-
expect(result[0].confidence).toBe(0.95);
|
|
75
|
-
warnSpy.mockRestore();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe('fetchEventMatches', () => {
|
|
79
|
-
it('returns event matches from the API', async () => {
|
|
80
|
-
const mockMatches = [
|
|
81
|
-
{ event: { id: 'e2' }, marketMatches: [] },
|
|
82
|
-
];
|
|
83
|
-
clientInstance.getEventMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
|
|
84
|
-
const result = await router.fetchEventMatches({ eventId: 'e1' });
|
|
85
|
-
expect(clientInstance.getEventMatches).toHaveBeenCalledWith({ eventId: 'e1' });
|
|
86
|
-
expect(result).toEqual(mockMatches);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
describe('compareMarketPrices', () => {
|
|
90
|
-
it('fetches identity matches with includePrices and maps to PriceComparison', async () => {
|
|
91
|
-
const mockMatches = [
|
|
92
|
-
{
|
|
93
|
-
market: { marketId: 'k1', sourceExchange: 'kalshi', outcomes: [], bestBid: 0.55, bestAsk: 0.62 },
|
|
94
|
-
relation: 'identity',
|
|
95
|
-
confidence: 0.9,
|
|
96
|
-
reasoning: 'Same market.',
|
|
97
|
-
},
|
|
98
|
-
];
|
|
99
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
|
|
100
|
-
const result = await router.compareMarketPrices({ marketId: 'm1' });
|
|
101
|
-
expect(clientInstance.getMarketMatches).toHaveBeenCalledWith({
|
|
102
|
-
marketId: 'm1',
|
|
103
|
-
relation: 'identity',
|
|
104
|
-
includePrices: true,
|
|
105
|
-
});
|
|
106
|
-
expect(result).toHaveLength(1);
|
|
107
|
-
expect(result[0].bestBid).toBe(0.55);
|
|
108
|
-
expect(result[0].bestAsk).toBe(0.62);
|
|
109
|
-
expect(result[0].venue).toBe('kalshi');
|
|
110
|
-
expect(result[0].reasoning).toBe('Same market.');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe('fetchRelatedMarkets', () => {
|
|
114
|
-
it('returns only subset/superset matches with reasoning', async () => {
|
|
115
|
-
const mockMatches = [
|
|
116
|
-
{
|
|
117
|
-
market: { marketId: 'k1', sourceExchange: 'kalshi', bestBid: 0.60, bestAsk: 0.65 },
|
|
118
|
-
relation: 'identity',
|
|
119
|
-
confidence: 0.95,
|
|
120
|
-
reasoning: 'Same.',
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
market: { marketId: 'k2', sourceExchange: 'kalshi', bestBid: 0.40, bestAsk: 0.45 },
|
|
124
|
-
relation: 'subset',
|
|
125
|
-
confidence: 0.8,
|
|
126
|
-
reasoning: 'Narrower market — nomination implies candidacy.',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
market: { marketId: 'k3', sourceExchange: 'polymarket', bestBid: 0.70, bestAsk: 0.73 },
|
|
130
|
-
relation: 'superset',
|
|
131
|
-
confidence: 0.7,
|
|
132
|
-
reasoning: 'Broader — popular vote does not guarantee election win.',
|
|
133
|
-
},
|
|
134
|
-
];
|
|
135
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
|
|
136
|
-
const result = await router.fetchRelatedMarkets({ marketId: 'm1' });
|
|
137
|
-
expect(result).toHaveLength(2);
|
|
138
|
-
expect(result[0].relation).toBe('subset');
|
|
139
|
-
expect(result[0].reasoning).toBe('Narrower market — nomination implies candidacy.');
|
|
140
|
-
expect(result[1].relation).toBe('superset');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe('fetchHedges (deprecated)', () => {
|
|
144
|
-
it('delegates to fetchRelatedMarkets and logs deprecation warning', async () => {
|
|
145
|
-
const mockMatches = [
|
|
146
|
-
{
|
|
147
|
-
market: { marketId: 'k2', sourceExchange: 'kalshi', bestBid: 0.40, bestAsk: 0.45 },
|
|
148
|
-
relation: 'subset',
|
|
149
|
-
confidence: 0.8,
|
|
150
|
-
reasoning: 'Narrower market.',
|
|
151
|
-
},
|
|
152
|
-
];
|
|
153
|
-
clientInstance.getMarketMatches = jest.fn().mockResolvedValue({ matches: mockMatches });
|
|
154
|
-
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
155
|
-
const result = await router.fetchHedges({ marketId: 'm1' });
|
|
156
|
-
expect(warnSpy).toHaveBeenCalledWith('[pmxt] fetchHedges is deprecated, use fetchRelatedMarkets instead');
|
|
157
|
-
expect(result).toHaveLength(1);
|
|
158
|
-
warnSpy.mockRestore();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
describe('fetchMarkets', () => {
|
|
162
|
-
it('returns markets from the API', async () => {
|
|
163
|
-
const mockMarkets = [{ marketId: 'm1', title: 'BTC' }];
|
|
164
|
-
clientInstance.searchMarkets = jest.fn().mockResolvedValue(mockMarkets);
|
|
165
|
-
const result = await router.fetchMarkets({ query: 'bitcoin' });
|
|
166
|
-
expect(clientInstance.searchMarkets).toHaveBeenCalled();
|
|
167
|
-
expect(result).toEqual(mockMarkets);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
describe('fetchEvents', () => {
|
|
171
|
-
it('returns events from the API', async () => {
|
|
172
|
-
const mockEvents = [{ id: 'e1', title: 'Election' }];
|
|
173
|
-
clientInstance.searchEvents = jest.fn().mockResolvedValue(mockEvents);
|
|
174
|
-
const result = await router.fetchEvents({ query: 'election' });
|
|
175
|
-
expect(clientInstance.searchEvents).toHaveBeenCalled();
|
|
176
|
-
expect(result).toEqual(mockEvents);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
describe('createOrder', () => {
|
|
180
|
-
it('throws not implemented error', async () => {
|
|
181
|
-
await expect(router.createOrder({})).rejects.toThrow('not implemented');
|
|
182
|
-
});
|
|
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
|
-
});
|
|
304
|
-
describe('capabilities', () => {
|
|
305
|
-
it('reports matching methods as supported', () => {
|
|
306
|
-
expect(router.has.fetchMarketMatches).toBe(true);
|
|
307
|
-
expect(router.has.fetchMatches).toBe(true);
|
|
308
|
-
expect(router.has.fetchEventMatches).toBe(true);
|
|
309
|
-
expect(router.has.compareMarketPrices).toBe(true);
|
|
310
|
-
expect(router.has.fetchRelatedMarkets).toBe(true);
|
|
311
|
-
expect(router.has.fetchMatchedMarkets).toBe(true);
|
|
312
|
-
expect(router.has.fetchMatchedPrices).toBe(true);
|
|
313
|
-
expect(router.has.fetchHedges).toBe(true);
|
|
314
|
-
expect(router.has.fetchArbitrage).toBe(true);
|
|
315
|
-
});
|
|
316
|
-
it('reports fetchOrderBook as supported', () => {
|
|
317
|
-
expect(router.has.fetchOrderBook).toBe(true);
|
|
318
|
-
});
|
|
319
|
-
it('reports trading methods as unsupported', () => {
|
|
320
|
-
expect(router.has.createOrder).toBe(false);
|
|
321
|
-
expect(router.has.cancelOrder).toBe(false);
|
|
322
|
-
});
|
|
323
|
-
it('reports search methods as supported', () => {
|
|
324
|
-
expect(router.has.fetchMarkets).toBe(true);
|
|
325
|
-
expect(router.has.fetchEvents).toBe(true);
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,177 +0,0 @@
|
|
|
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
|
-
});
|