pmxt-core 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/exchanges/kalshi/api.d.ts +1 -1
  2. package/dist/exchanges/kalshi/api.js +1 -1
  3. package/dist/exchanges/limitless/api.d.ts +1 -1
  4. package/dist/exchanges/limitless/api.js +1 -1
  5. package/dist/exchanges/myriad/api.d.ts +1 -1
  6. package/dist/exchanges/myriad/api.js +1 -1
  7. package/dist/exchanges/opinion/api.d.ts +1 -1
  8. package/dist/exchanges/opinion/api.js +1 -1
  9. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  10. package/dist/exchanges/polymarket/api-clob.js +1 -1
  11. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  12. package/dist/exchanges/polymarket/api-data.js +1 -1
  13. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  14. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  15. package/dist/exchanges/polymarket/auth.js +42 -8
  16. package/dist/exchanges/polymarket/index.js +56 -19
  17. package/dist/exchanges/polymarket_us/config.d.ts +18 -0
  18. package/dist/exchanges/polymarket_us/config.js +22 -0
  19. package/dist/exchanges/polymarket_us/errors.d.ts +19 -0
  20. package/dist/exchanges/polymarket_us/errors.js +123 -0
  21. package/dist/exchanges/polymarket_us/errors.test.d.ts +1 -0
  22. package/dist/exchanges/polymarket_us/errors.test.js +54 -0
  23. package/dist/exchanges/polymarket_us/index.d.ts +90 -0
  24. package/dist/exchanges/polymarket_us/index.js +366 -0
  25. package/dist/exchanges/polymarket_us/index.test.d.ts +8 -0
  26. package/dist/exchanges/polymarket_us/index.test.js +237 -0
  27. package/dist/exchanges/polymarket_us/normalizer.d.ts +55 -0
  28. package/dist/exchanges/polymarket_us/normalizer.js +385 -0
  29. package/dist/exchanges/polymarket_us/normalizer.test.d.ts +1 -0
  30. package/dist/exchanges/polymarket_us/normalizer.test.js +224 -0
  31. package/dist/exchanges/polymarket_us/price.d.ts +94 -0
  32. package/dist/exchanges/polymarket_us/price.js +149 -0
  33. package/dist/exchanges/polymarket_us/price.test.d.ts +1 -0
  34. package/dist/exchanges/polymarket_us/price.test.js +131 -0
  35. package/dist/exchanges/polymarket_us/websocket.d.ts +39 -0
  36. package/dist/exchanges/polymarket_us/websocket.js +181 -0
  37. package/dist/exchanges/polymarket_us/websocket.test.d.ts +8 -0
  38. package/dist/exchanges/polymarket_us/websocket.test.js +162 -0
  39. package/dist/exchanges/probable/api.d.ts +1 -1
  40. package/dist/exchanges/probable/api.js +1 -1
  41. package/dist/exchanges/smarkets/api.d.ts +8067 -0
  42. package/dist/exchanges/smarkets/api.js +10698 -0
  43. package/dist/exchanges/smarkets/auth.d.ts +56 -0
  44. package/dist/exchanges/smarkets/auth.js +105 -0
  45. package/dist/exchanges/smarkets/config.d.ts +41 -0
  46. package/dist/exchanges/smarkets/config.js +47 -0
  47. package/dist/exchanges/smarkets/errors.d.ts +31 -0
  48. package/dist/exchanges/smarkets/errors.js +186 -0
  49. package/dist/exchanges/smarkets/fetcher.d.ts +177 -0
  50. package/dist/exchanges/smarkets/fetcher.js +342 -0
  51. package/dist/exchanges/smarkets/index.d.ts +54 -0
  52. package/dist/exchanges/smarkets/index.js +285 -0
  53. package/dist/exchanges/smarkets/normalizer.d.ts +18 -0
  54. package/dist/exchanges/smarkets/normalizer.js +267 -0
  55. package/dist/exchanges/smarkets/price.d.ts +26 -0
  56. package/dist/exchanges/smarkets/price.js +44 -0
  57. package/dist/exchanges/smarkets/price.test.d.ts +1 -0
  58. package/dist/exchanges/smarkets/price.test.js +50 -0
  59. package/dist/index.d.ts +8 -0
  60. package/dist/index.js +9 -1
  61. package/dist/server/app.js +18 -2
  62. package/package.json +4 -3
@@ -0,0 +1,366 @@
1
+ "use strict";
2
+ /**
3
+ * Polymarket US exchange adapter.
4
+ *
5
+ * Wraps the official `polymarket-us` SDK to expose Polymarket US under the
6
+ * unified PMXT `PredictionMarketExchange` interface.
7
+ *
8
+ * Notes:
9
+ * - PMXT `marketId` corresponds to the Polymarket US market `slug`.
10
+ * - Outcomes are encoded as `${slug}:long` and `${slug}:short`.
11
+ * - Polymarket US books and orders are quoted in long-side prices; helpers
12
+ * in `./price` perform the side-aware conversion.
13
+ * - The SDK requires `marketSlug` in the cancel body, but PMXT
14
+ * `cancelOrder(orderId)` does not. We maintain an in-memory cache mapping
15
+ * orderId -> marketSlug populated whenever we observe an order.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
29
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.PolymarketUSExchange = exports.PolymarketUSNormalizer = void 0;
33
+ const BaseExchange_1 = require("../../BaseExchange");
34
+ const errors_1 = require("../../errors");
35
+ const polymarket_us_1 = require("polymarket-us");
36
+ const config_1 = require("./config");
37
+ const normalizer_1 = require("./normalizer");
38
+ const errors_2 = require("./errors");
39
+ const websocket_1 = require("./websocket");
40
+ const price_1 = require("./price");
41
+ __exportStar(require("./config"), exports);
42
+ __exportStar(require("./price"), exports);
43
+ __exportStar(require("./errors"), exports);
44
+ var normalizer_2 = require("./normalizer");
45
+ Object.defineProperty(exports, "PolymarketUSNormalizer", { enumerable: true, get: function () { return normalizer_2.PolymarketUSNormalizer; } });
46
+ class PolymarketUSExchange extends BaseExchange_1.PredictionMarketExchange {
47
+ has = {
48
+ fetchMarkets: true,
49
+ fetchEvents: true,
50
+ fetchOHLCV: false,
51
+ fetchOrderBook: true,
52
+ fetchTrades: false,
53
+ createOrder: true,
54
+ cancelOrder: true,
55
+ fetchOrder: true,
56
+ fetchOpenOrders: true,
57
+ fetchPositions: true,
58
+ fetchBalance: true,
59
+ watchAddress: false,
60
+ unwatchAddress: false,
61
+ watchOrderBook: true,
62
+ watchTrades: true,
63
+ fetchMyTrades: true,
64
+ fetchClosedOrders: false,
65
+ fetchAllOrders: false,
66
+ buildOrder: true,
67
+ submitOrder: true,
68
+ };
69
+ client;
70
+ normalizer;
71
+ config;
72
+ /**
73
+ * Maps PMXT orderId -> Polymarket US marketSlug. Populated whenever we
74
+ * observe an order (create, fetch, list) so that `cancelOrder(orderId)`
75
+ * can supply the SDK-required `marketSlug` body field.
76
+ */
77
+ orderSlugCache = new Map();
78
+ wsWrapper;
79
+ constructor(credentials) {
80
+ super(credentials);
81
+ this.rateLimit = 100;
82
+ this.config = (0, config_1.getPolymarketUSConfig)();
83
+ this.normalizer = new normalizer_1.PolymarketUSNormalizer();
84
+ this.client = new polymarket_us_1.PolymarketUS({
85
+ keyId: credentials?.apiKey,
86
+ secretKey: credentials?.privateKey,
87
+ apiBaseUrl: this.config.apiUrl,
88
+ gatewayBaseUrl: this.config.gatewayUrl,
89
+ });
90
+ }
91
+ get name() {
92
+ return 'PolymarketUS';
93
+ }
94
+ // -------------------------------------------------------------------------
95
+ // Helpers
96
+ // -------------------------------------------------------------------------
97
+ requireAuth() {
98
+ if (!this.credentials?.apiKey || !this.credentials?.privateKey) {
99
+ throw new errors_1.AuthenticationError('PolymarketUS requires apiKey (keyId) and privateKey (secretKey) credentials.', 'PolymarketUS');
100
+ }
101
+ }
102
+ /**
103
+ * Wrap any SDK call so SDK errors get translated to PMXT error classes.
104
+ */
105
+ async run(fn) {
106
+ try {
107
+ return await fn();
108
+ }
109
+ catch (err) {
110
+ throw errors_2.polymarketUSErrorMapper.mapError(err);
111
+ }
112
+ }
113
+ cacheOrder(orderId, marketSlug) {
114
+ if (orderId && marketSlug) {
115
+ this.orderSlugCache.set(orderId, marketSlug);
116
+ }
117
+ }
118
+ /**
119
+ * Strip a trailing `:long` or `:short` suffix from a PMXT identifier
120
+ * to recover the bare Polymarket US market slug. If no suffix is
121
+ * present the input is returned unchanged.
122
+ */
123
+ slugFromId(id) {
124
+ if (id.endsWith(':long'))
125
+ return id.slice(0, -':long'.length);
126
+ if (id.endsWith(':short'))
127
+ return id.slice(0, -':short'.length);
128
+ return id;
129
+ }
130
+ // -------------------------------------------------------------------------
131
+ // Markets / Events
132
+ // -------------------------------------------------------------------------
133
+ async fetchMarketsImpl(params) {
134
+ return this.run(async () => {
135
+ // Direct slug / id / outcomeId lookup
136
+ const directSlug = params?.slug ||
137
+ params?.marketId ||
138
+ (params?.outcomeId ? this.slugFromId(params.outcomeId) : undefined);
139
+ if (directSlug) {
140
+ const resp = await this.client.markets.retrieveBySlug(directSlug);
141
+ return resp.market ? [this.normalizer.normalizeMarket(resp.market)] : [];
142
+ }
143
+ // Markets that belong to a specific event
144
+ if (params?.eventId) {
145
+ const eventResp = await this.client.events.retrieveBySlug(params.eventId);
146
+ return eventResp.event
147
+ ? this.normalizer.normalizeMarketsFromEvent(eventResp.event)
148
+ : [];
149
+ }
150
+ const resp = await this.client.markets.list({
151
+ active: true,
152
+ limit: params?.limit ?? 250,
153
+ offset: params?.offset ?? 0,
154
+ });
155
+ let markets = (resp.markets || []).map(m => this.normalizer.normalizeMarket(m));
156
+ if (params?.query) {
157
+ const q = params.query.toLowerCase();
158
+ markets = markets.filter(m => m.title.toLowerCase().includes(q) ||
159
+ (m.description || '').toLowerCase().includes(q));
160
+ }
161
+ return markets;
162
+ });
163
+ }
164
+ async fetchEventsImpl(params) {
165
+ return this.run(async () => {
166
+ const directSlug = params?.eventId || params?.slug;
167
+ if (directSlug) {
168
+ const resp = await this.client.events.retrieveBySlug(directSlug);
169
+ return resp.event ? [this.normalizer.normalizeEvent(resp.event)] : [];
170
+ }
171
+ const resp = await this.client.events.list({
172
+ active: true,
173
+ limit: params?.limit ?? 100,
174
+ offset: params?.offset ?? 0,
175
+ });
176
+ let events = (resp.events || []).map(e => this.normalizer.normalizeEvent(e));
177
+ if (params?.query) {
178
+ const q = params.query.toLowerCase();
179
+ events = events.filter(e => e.title.toLowerCase().includes(q) ||
180
+ (e.description || '').toLowerCase().includes(q));
181
+ }
182
+ return events;
183
+ });
184
+ }
185
+ async fetchOrderBook(id) {
186
+ return this.run(async () => {
187
+ const slug = this.slugFromId(id);
188
+ const book = await this.client.markets.book(slug);
189
+ return this.normalizer.normalizeOrderBook(book, id);
190
+ });
191
+ }
192
+ // -------------------------------------------------------------------------
193
+ // Account / Portfolio
194
+ // -------------------------------------------------------------------------
195
+ async fetchBalance(_address) {
196
+ this.requireAuth();
197
+ return this.run(async () => {
198
+ const resp = await this.client.account.balances();
199
+ if (!resp.balances || resp.balances.length === 0) {
200
+ return [];
201
+ }
202
+ return this.normalizer.normalizeBalance(resp.balances[0]);
203
+ });
204
+ }
205
+ async fetchPositions(_address) {
206
+ this.requireAuth();
207
+ return this.run(async () => {
208
+ const resp = await this.client.portfolio.positions({});
209
+ return this.normalizer.normalizePositions(resp.positions || {});
210
+ });
211
+ }
212
+ async fetchMyTrades(params) {
213
+ this.requireAuth();
214
+ return this.run(async () => {
215
+ const resp = await this.client.portfolio.activities({
216
+ types: ['ACTIVITY_TYPE_TRADE'],
217
+ limit: params?.limit ?? 100,
218
+ marketSlug: params?.marketId,
219
+ });
220
+ const activities = resp.activities || [];
221
+ const trades = [];
222
+ activities.forEach((activity, idx) => {
223
+ const trade = this.normalizer.normalizeUserTradeFromActivity(activity, idx);
224
+ if (trade)
225
+ trades.push(trade);
226
+ });
227
+ return trades;
228
+ });
229
+ }
230
+ // -------------------------------------------------------------------------
231
+ // Orders
232
+ // -------------------------------------------------------------------------
233
+ async fetchOpenOrders(marketId) {
234
+ this.requireAuth();
235
+ return this.run(async () => {
236
+ const resp = await this.client.orders.list({
237
+ slugs: marketId ? [marketId] : undefined,
238
+ });
239
+ const raws = resp.orders || [];
240
+ return raws.map(raw => {
241
+ const normalized = this.normalizer.normalizeOrder(raw);
242
+ this.cacheOrder(normalized.id, raw.marketSlug);
243
+ return normalized;
244
+ });
245
+ });
246
+ }
247
+ async fetchOrder(orderId) {
248
+ this.requireAuth();
249
+ return this.run(async () => {
250
+ const resp = await this.client.orders.retrieve(orderId);
251
+ const raw = resp.order;
252
+ const normalized = this.normalizer.normalizeOrder(raw);
253
+ this.cacheOrder(normalized.id, raw.marketSlug);
254
+ return normalized;
255
+ });
256
+ }
257
+ async buildOrder(params) {
258
+ const isShort = params.outcomeId.endsWith(':short');
259
+ let intent;
260
+ if (params.side === 'buy' && !isShort)
261
+ intent = 'ORDER_INTENT_BUY_LONG';
262
+ else if (params.side === 'sell' && !isShort)
263
+ intent = 'ORDER_INTENT_SELL_LONG';
264
+ else if (params.side === 'buy' && isShort)
265
+ intent = 'ORDER_INTENT_BUY_SHORT';
266
+ else
267
+ intent = 'ORDER_INTENT_SELL_SHORT';
268
+ const sdkType = params.type === 'market'
269
+ ? 'ORDER_TYPE_MARKET'
270
+ : 'ORDER_TYPE_LIMIT';
271
+ const sdkParams = {
272
+ marketSlug: params.marketId,
273
+ intent,
274
+ type: sdkType,
275
+ quantity: params.amount,
276
+ tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL',
277
+ };
278
+ if (params.type === 'limit') {
279
+ if (params.price === undefined) {
280
+ throw new Error('Limit order requires price');
281
+ }
282
+ const rounded = (0, price_1.roundToTickSize)(params.price);
283
+ (0, price_1.validatePriceBounds)(rounded);
284
+ const longPrice = (0, price_1.toLongSidePrice)(intent, rounded);
285
+ sdkParams.price = (0, price_1.toAmount)(longPrice);
286
+ }
287
+ return {
288
+ exchange: this.name,
289
+ params,
290
+ raw: sdkParams,
291
+ };
292
+ }
293
+ async submitOrder(built) {
294
+ this.requireAuth();
295
+ const sdkParams = built.raw;
296
+ const response = await this.run(() => this.client.orders.create(sdkParams));
297
+ const newId = response.id;
298
+ this.cacheOrder(newId, built.params.marketId);
299
+ try {
300
+ return await this.fetchOrder(newId);
301
+ }
302
+ catch {
303
+ // Order may not yet be visible via retrieve. Fall back to a
304
+ // synthetic Order built from the original params.
305
+ return {
306
+ id: newId,
307
+ marketId: built.params.marketId,
308
+ outcomeId: built.params.outcomeId,
309
+ side: built.params.side,
310
+ type: built.params.type,
311
+ price: built.params.price ?? 0,
312
+ amount: built.params.amount,
313
+ status: 'open',
314
+ filled: 0,
315
+ remaining: built.params.amount,
316
+ timestamp: Date.now(),
317
+ };
318
+ }
319
+ }
320
+ async createOrder(params) {
321
+ const built = await this.buildOrder(params);
322
+ return this.submitOrder(built);
323
+ }
324
+ async cancelOrder(orderId) {
325
+ this.requireAuth();
326
+ let slug = this.orderSlugCache.get(orderId);
327
+ if (!slug) {
328
+ // Populate the cache by fetching the order first
329
+ const fetched = await this.fetchOrder(orderId);
330
+ slug = this.orderSlugCache.get(orderId) || fetched.marketId;
331
+ }
332
+ await this.run(() => this.client.orders.cancel(orderId, { marketSlug: slug }));
333
+ return this.fetchOrder(orderId);
334
+ }
335
+ // -------------------------------------------------------------------------
336
+ // WebSocket Streaming
337
+ // -------------------------------------------------------------------------
338
+ /**
339
+ * Lazily construct the WebSocket wrapper. The underlying SDK factory
340
+ * requires credentials even for the public market socket, so this
341
+ * method calls `requireAuth()` up front.
342
+ */
343
+ ensureWs() {
344
+ this.requireAuth();
345
+ if (!this.wsWrapper) {
346
+ this.wsWrapper = new websocket_1.PolymarketUSWebSocket(this.client, this.normalizer);
347
+ }
348
+ return this.wsWrapper;
349
+ }
350
+ async watchOrderBook(id, _limit) {
351
+ return this.run(() => this.ensureWs().watchOrderBook(id));
352
+ }
353
+ async watchTrades(id, _address, _since, _limit) {
354
+ return this.run(() => this.ensureWs().watchTrades(id));
355
+ }
356
+ // -------------------------------------------------------------------------
357
+ // Lifecycle
358
+ // -------------------------------------------------------------------------
359
+ async close() {
360
+ if (this.wsWrapper) {
361
+ await this.wsWrapper.close();
362
+ this.wsWrapper = undefined;
363
+ }
364
+ }
365
+ }
366
+ exports.PolymarketUSExchange = PolymarketUSExchange;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Integration tests for PolymarketUSExchange.
3
+ *
4
+ * The `polymarket-us` SDK is fully mocked here so the tests run without
5
+ * touching the network. Each test instantiates a fresh exchange (and
6
+ * therefore a fresh mock client) so per-test mock setup is independent.
7
+ */
8
+ export {};
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ /**
3
+ * Integration tests for PolymarketUSExchange.
4
+ *
5
+ * The `polymarket-us` SDK is fully mocked here so the tests run without
6
+ * touching the network. Each test instantiates a fresh exchange (and
7
+ * therefore a fresh mock client) so per-test mock setup is independent.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ jest.mock('polymarket-us', () => {
11
+ class PolymarketUSError extends Error {
12
+ }
13
+ class APIError extends PolymarketUSError {
14
+ status;
15
+ constructor(status, message) {
16
+ super(message);
17
+ this.status = status;
18
+ }
19
+ }
20
+ class AuthenticationError extends APIError {
21
+ constructor(m = 'auth') { super(401, m); }
22
+ }
23
+ class BadRequestError extends APIError {
24
+ constructor(m = 'bad request') { super(400, m); }
25
+ }
26
+ class NotFoundError extends APIError {
27
+ constructor(m = 'not found') { super(404, m); }
28
+ }
29
+ class RateLimitError extends APIError {
30
+ constructor(m = 'rate limit') { super(429, m); }
31
+ }
32
+ class InternalServerError extends APIError {
33
+ constructor(m = 'server') { super(500, m); }
34
+ }
35
+ const PolymarketUS = jest.fn().mockImplementation(() => ({
36
+ markets: {
37
+ list: jest.fn(),
38
+ book: jest.fn(),
39
+ retrieveBySlug: jest.fn(),
40
+ },
41
+ events: {
42
+ list: jest.fn(),
43
+ retrieveBySlug: jest.fn(),
44
+ },
45
+ orders: {
46
+ list: jest.fn(),
47
+ retrieve: jest.fn(),
48
+ create: jest.fn(),
49
+ cancel: jest.fn(),
50
+ },
51
+ portfolio: {
52
+ positions: jest.fn(),
53
+ activities: jest.fn(),
54
+ },
55
+ account: {
56
+ balances: jest.fn(),
57
+ },
58
+ }));
59
+ return {
60
+ PolymarketUS,
61
+ PolymarketUSError,
62
+ APIError,
63
+ AuthenticationError,
64
+ BadRequestError,
65
+ NotFoundError,
66
+ RateLimitError,
67
+ InternalServerError,
68
+ };
69
+ });
70
+ const index_1 = require("./index");
71
+ const errors_1 = require("../../errors");
72
+ const polymarket_us_1 = require("polymarket-us");
73
+ const CREDS = { apiKey: 'k1', privateKey: 's1' };
74
+ function getClient(exchange) {
75
+ return exchange.client;
76
+ }
77
+ function makeMarketDetail(slug = 'btc-100k') {
78
+ return {
79
+ id: 1,
80
+ slug,
81
+ title: `Market ${slug}`,
82
+ outcome: 'binary',
83
+ description: 'desc',
84
+ active: true,
85
+ closed: false,
86
+ liquidity: 100,
87
+ volume: 1000,
88
+ eventSlug: 'evt-1',
89
+ };
90
+ }
91
+ function makeSdkOrder(overrides = {}) {
92
+ return {
93
+ id: 'order-1',
94
+ marketSlug: 'btc-100k',
95
+ side: 'ORDER_SIDE_BUY',
96
+ type: 'ORDER_TYPE_LIMIT',
97
+ price: { value: '0.55', currency: 'USD' },
98
+ quantity: 10,
99
+ cumQuantity: 0,
100
+ leavesQuantity: 10,
101
+ tif: 'TIME_IN_FORCE_GOOD_TILL_CANCEL',
102
+ intent: 'ORDER_INTENT_BUY_LONG',
103
+ state: 'ORDER_STATE_NEW',
104
+ ...overrides,
105
+ };
106
+ }
107
+ describe('PolymarketUSExchange', () => {
108
+ describe('construction', () => {
109
+ it('constructs without credentials but rejects auth-required calls', async () => {
110
+ const ex = new index_1.PolymarketUSExchange();
111
+ await expect(ex.fetchBalance()).rejects.toBeInstanceOf(errors_1.AuthenticationError);
112
+ });
113
+ it('constructs with credentials and exposes the unified name', () => {
114
+ const ex = new index_1.PolymarketUSExchange(CREDS);
115
+ expect(ex.name).toBe('PolymarketUS');
116
+ });
117
+ });
118
+ describe('fetchMarkets', () => {
119
+ it('returns normalized markets from markets.list', async () => {
120
+ const ex = new index_1.PolymarketUSExchange(CREDS);
121
+ const client = getClient(ex);
122
+ client.markets.list.mockResolvedValue({
123
+ markets: [makeMarketDetail('btc-100k'), makeMarketDetail('eth-5k')],
124
+ });
125
+ const result = await ex.fetchMarkets();
126
+ expect(client.markets.list).toHaveBeenCalledWith({
127
+ active: true,
128
+ limit: 250,
129
+ offset: 0,
130
+ });
131
+ expect(result).toHaveLength(2);
132
+ expect(result[0].marketId).toBe('btc-100k');
133
+ expect(result[1].marketId).toBe('eth-5k');
134
+ });
135
+ it('uses retrieveBySlug for direct slug lookup', async () => {
136
+ const ex = new index_1.PolymarketUSExchange(CREDS);
137
+ const client = getClient(ex);
138
+ client.markets.retrieveBySlug.mockResolvedValue({
139
+ market: makeMarketDetail('btc-100k'),
140
+ });
141
+ const result = await ex.fetchMarkets({ slug: 'btc-100k' });
142
+ expect(client.markets.retrieveBySlug).toHaveBeenCalledWith('btc-100k');
143
+ expect(result).toHaveLength(1);
144
+ expect(result[0].marketId).toBe('btc-100k');
145
+ });
146
+ });
147
+ describe('fetchOrderBook', () => {
148
+ it('normalizes a market book', async () => {
149
+ const ex = new index_1.PolymarketUSExchange(CREDS);
150
+ const client = getClient(ex);
151
+ client.markets.book.mockResolvedValue({
152
+ marketSlug: 'btc-100k',
153
+ bids: [{ px: { value: '0.55', currency: 'USD' }, qty: '10' }],
154
+ offers: [{ px: { value: '0.57', currency: 'USD' }, qty: '5' }],
155
+ state: 'MARKET_STATE_OPEN',
156
+ transactTime: '2024-01-01T00:00:00Z',
157
+ });
158
+ const book = await ex.fetchOrderBook('btc-100k');
159
+ expect(client.markets.book).toHaveBeenCalledWith('btc-100k');
160
+ expect(book.bids).toEqual([{ price: 0.55, size: 10 }]);
161
+ expect(book.asks).toEqual([{ price: 0.57, size: 5 }]);
162
+ });
163
+ });
164
+ describe('buildOrder', () => {
165
+ it('builds a BUY LONG limit order at the user price', async () => {
166
+ const ex = new index_1.PolymarketUSExchange(CREDS);
167
+ const built = await ex.buildOrder({
168
+ marketId: 'btc-100k',
169
+ outcomeId: 'btc-100k:long',
170
+ side: 'buy',
171
+ type: 'limit',
172
+ amount: 10,
173
+ price: 0.55,
174
+ });
175
+ const raw = built.raw;
176
+ expect(raw.intent).toBe('ORDER_INTENT_BUY_LONG');
177
+ expect(raw.type).toBe('ORDER_TYPE_LIMIT');
178
+ expect(raw.marketSlug).toBe('btc-100k');
179
+ expect(raw.quantity).toBe(10);
180
+ expect(raw.price.value).toBe('0.550');
181
+ });
182
+ it('builds a BUY SHORT limit order using long-side price conversion', async () => {
183
+ const ex = new index_1.PolymarketUSExchange(CREDS);
184
+ const built = await ex.buildOrder({
185
+ marketId: 'btc-100k',
186
+ outcomeId: 'btc-100k:short',
187
+ side: 'buy',
188
+ type: 'limit',
189
+ amount: 10,
190
+ price: 0.40,
191
+ });
192
+ const raw = built.raw;
193
+ expect(raw.intent).toBe('ORDER_INTENT_BUY_SHORT');
194
+ expect(raw.price.value).toBe('0.600');
195
+ });
196
+ });
197
+ describe('cancelOrder', () => {
198
+ it('uses cached marketSlug when available', async () => {
199
+ const ex = new index_1.PolymarketUSExchange(CREDS);
200
+ const client = getClient(ex);
201
+ // Pre-populate cache by listing open orders
202
+ client.orders.list.mockResolvedValue({
203
+ orders: [makeSdkOrder({ id: 'order-123', marketSlug: 'btc-100k' })],
204
+ });
205
+ await ex.fetchOpenOrders();
206
+ client.orders.cancel.mockResolvedValue(undefined);
207
+ client.orders.retrieve.mockResolvedValue({
208
+ order: makeSdkOrder({ id: 'order-123', state: 'ORDER_STATE_CANCELED' }),
209
+ });
210
+ await ex.cancelOrder('order-123');
211
+ expect(client.orders.cancel).toHaveBeenCalledWith('order-123', {
212
+ marketSlug: 'btc-100k',
213
+ });
214
+ });
215
+ it('fetches the order first when slug is not cached', async () => {
216
+ const ex = new index_1.PolymarketUSExchange(CREDS);
217
+ const client = getClient(ex);
218
+ client.orders.retrieve.mockResolvedValue({
219
+ order: makeSdkOrder({ id: 'order-456', marketSlug: 'eth-5k' }),
220
+ });
221
+ client.orders.cancel.mockResolvedValue(undefined);
222
+ await ex.cancelOrder('order-456');
223
+ expect(client.orders.retrieve).toHaveBeenCalledWith('order-456');
224
+ expect(client.orders.cancel).toHaveBeenCalledWith('order-456', {
225
+ marketSlug: 'eth-5k',
226
+ });
227
+ });
228
+ });
229
+ describe('error mapping', () => {
230
+ it('translates SDK AuthenticationError into PMXT AuthenticationError', async () => {
231
+ const ex = new index_1.PolymarketUSExchange(CREDS);
232
+ const client = getClient(ex);
233
+ client.account.balances.mockRejectedValue(new polymarket_us_1.AuthenticationError('bad creds'));
234
+ await expect(ex.fetchBalance()).rejects.toBeInstanceOf(errors_1.AuthenticationError);
235
+ });
236
+ });
237
+ });
@@ -0,0 +1,55 @@
1
+ import type { Event as SdkEvent, MarketDetail, MarketBook, Order as SdkOrder, UserPosition, UserBalance, Activity } from 'polymarket-us';
2
+ import { UnifiedMarket, UnifiedEvent, OrderBook, Order, Position, Balance, UserTrade } from '../../types';
3
+ export declare class PolymarketUSNormalizer {
4
+ /**
5
+ * Normalize a single MarketDetail into a UnifiedMarket.
6
+ * The slug is the canonical PMXT marketId for Polymarket US.
7
+ */
8
+ normalizeMarket(detail: MarketDetail): UnifiedMarket;
9
+ /**
10
+ * Flatten an SDK Event's markets into UnifiedMarkets.
11
+ * Each market inherits the parent event's metadata.
12
+ */
13
+ normalizeMarketsFromEvent(event: SdkEvent): UnifiedMarket[];
14
+ /**
15
+ * Normalize an SDK Event into a UnifiedEvent.
16
+ */
17
+ normalizeEvent(event: SdkEvent): UnifiedEvent;
18
+ /**
19
+ * Internal helper that builds a UnifiedMarket from the gateway's real
20
+ * runtime shape. Handles inheritance from a parent event when the market
21
+ * is nested (fields like category/tags/endDate fall back to the parent).
22
+ */
23
+ private buildUnifiedMarket;
24
+ /**
25
+ * Normalize a MarketBook into a PMXT OrderBook.
26
+ *
27
+ * IMPORTANT: Polymarket US books are quoted in long-side prices. PMXT
28
+ * exposes the book as the LONG side directly:
29
+ * - bids: levels where someone is bidding to BUY LONG (price = fromAmount(level.px))
30
+ * - asks: levels where someone is offering to SELL LONG (price = fromAmount(level.px))
31
+ * The implicit short-side book is `1 - longPrice` for each level. Callers
32
+ * needing the short-side view must invert prices themselves.
33
+ */
34
+ normalizeOrderBook(book: MarketBook, _marketId: string): OrderBook;
35
+ /**
36
+ * Normalize an SDK Order into a PMXT Order.
37
+ * Prices are converted from long-side to user-facing using the order's intent.
38
+ */
39
+ normalizeOrder(order: SdkOrder): Order;
40
+ /**
41
+ * Normalize the SDK's positions map into an array of PMXT Positions.
42
+ * Positive netPosition -> long outcome; negative -> short outcome.
43
+ */
44
+ normalizePositions(positions: Record<string, UserPosition>): Position[];
45
+ /**
46
+ * Normalize a UserBalance into PMXT Balance[].
47
+ * Locked = total - available (clamped to >= 0).
48
+ */
49
+ normalizeBalance(balance: UserBalance): Balance[];
50
+ /**
51
+ * Normalize a single Activity into a UserTrade.
52
+ * Returns null for non-trade activities.
53
+ */
54
+ normalizeUserTradeFromActivity(activity: Activity, _index: number): UserTrade | null;
55
+ }