pmxt-core 2.44.6 → 2.45.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/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/mock/index.js +13 -2
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- 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/websocket.d.ts +12 -0
- package/dist/exchanges/polymarket/websocket.js +120 -14
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/suibets/api.d.ts +15 -0
- package/dist/exchanges/suibets/api.js +17 -0
- package/dist/exchanges/suibets/config.d.ts +16 -0
- package/dist/exchanges/suibets/config.js +34 -0
- package/dist/exchanges/suibets/errors.d.ts +16 -0
- package/dist/exchanges/suibets/errors.js +71 -0
- package/dist/exchanges/suibets/fetcher.d.ts +64 -0
- package/dist/exchanges/suibets/fetcher.js +128 -0
- package/dist/exchanges/suibets/index.d.ts +54 -0
- package/dist/exchanges/suibets/index.js +114 -0
- package/dist/exchanges/suibets/normalizer.d.ts +8 -0
- package/dist/exchanges/suibets/normalizer.js +102 -0
- package/dist/exchanges/suibets/utils.d.ts +63 -0
- package/dist/exchanges/suibets/utils.js +124 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/router/Router.js +12 -3
- package/dist/server/app.js +76 -1
- package/dist/server/exchange-factory.js +6 -0
- package/dist/server/openapi.yaml +7 -0
- package/dist/server/ws-handler.js +196 -23
- package/package.json +9 -9
|
@@ -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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.485Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.485Z
|
|
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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.524Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.524Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -5,6 +5,17 @@ const BaseExchange_1 = require("../../BaseExchange");
|
|
|
5
5
|
const seededRng_1 = require("./seededRng");
|
|
6
6
|
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
|
|
7
7
|
const round = (n, decimals = 3) => parseFloat(n.toFixed(decimals));
|
|
8
|
+
const toTimestamp = (value) => {
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (value instanceof Date)
|
|
12
|
+
return value.getTime();
|
|
13
|
+
const parsed = typeof value === 'number' ? value : new Date(value).getTime();
|
|
14
|
+
if (!Number.isFinite(parsed)) {
|
|
15
|
+
throw new Error(`Invalid date value: ${String(value)}`);
|
|
16
|
+
}
|
|
17
|
+
return parsed;
|
|
18
|
+
};
|
|
8
19
|
const CATEGORIES = ['Politics', 'Sports', 'Crypto', 'Finance', 'Science', 'Entertainment', 'Tech', 'World'];
|
|
9
20
|
const LOREM = `lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor
|
|
10
21
|
incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud
|
|
@@ -333,8 +344,8 @@ class MockExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
333
344
|
};
|
|
334
345
|
const step = resolutionMs[params.resolution] ?? 3_600_000;
|
|
335
346
|
const limit = params.limit ?? 100;
|
|
336
|
-
const end = params.end
|
|
337
|
-
const start = params.start
|
|
347
|
+
const end = toTimestamp(params.end) ?? Date.now();
|
|
348
|
+
const start = toTimestamp(params.start) ?? end - step * limit;
|
|
338
349
|
const candles = [];
|
|
339
350
|
let price = round(f.float(0.2, 0.8), 3);
|
|
340
351
|
let t = start;
|
|
@@ -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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.535Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.535Z
|
|
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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.538Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.538Z
|
|
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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.492Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.492Z
|
|
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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.505Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.505Z
|
|
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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.503Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.503Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketGammaSpec = {
|
|
@@ -49,6 +49,8 @@ export interface PolymarketWebSocketConfig {
|
|
|
49
49
|
userChannelCreds?: PolymarketUserChannelCreds;
|
|
50
50
|
/** Timeout in ms for WebSocket connections to open (default: 30000). */
|
|
51
51
|
connectionTimeoutMs?: number;
|
|
52
|
+
/** Time to wait for an initial market-channel book before using a REST snapshot fallback. */
|
|
53
|
+
snapshotFallbackMs?: number;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Native WebSocket implementation for Polymarket market data.
|
|
@@ -64,6 +66,8 @@ export declare class PolymarketWebSocket {
|
|
|
64
66
|
private orderBooks;
|
|
65
67
|
private config;
|
|
66
68
|
private initializationPromise?;
|
|
69
|
+
private marketPingInterval;
|
|
70
|
+
private readonly callApi;
|
|
67
71
|
constructor(callApi: (operationId: string, params?: Record<string, any>) => Promise<any>, config?: PolymarketWebSocketConfig);
|
|
68
72
|
watchOrderBook(outcomeId: string): Promise<OrderBook>;
|
|
69
73
|
unwatchOrderBook(outcomeId: string): Promise<void>;
|
|
@@ -95,6 +99,14 @@ export declare class PolymarketWebSocket {
|
|
|
95
99
|
private subscribe;
|
|
96
100
|
private ensureInitialized;
|
|
97
101
|
private handleBookSnapshot;
|
|
102
|
+
private withSnapshotFallback;
|
|
103
|
+
private removeOrderBookResolver;
|
|
104
|
+
private fetchOrderBookSnapshot;
|
|
105
|
+
private normalizeRawOrderBook;
|
|
106
|
+
private parseTimestamp;
|
|
107
|
+
private startMarketHeartbeat;
|
|
108
|
+
private stopMarketHeartbeat;
|
|
109
|
+
private rejectPendingMarketResolvers;
|
|
98
110
|
private handlePriceChange;
|
|
99
111
|
private handleTrade;
|
|
100
112
|
private resolveOrderBook;
|
|
@@ -46,6 +46,7 @@ const goldsky_1 = require("../../subscriber/external/goldsky");
|
|
|
46
46
|
const watcher_1 = require("../../subscriber/watcher");
|
|
47
47
|
const watch_timeout_1 = require("../../utils/watch-timeout");
|
|
48
48
|
const DEFAULT_CONNECTION_TIMEOUT_MS = 30_000;
|
|
49
|
+
const DEFAULT_SNAPSHOT_FALLBACK_MS = 3_000;
|
|
49
50
|
const MAX_PENDING_TRADES_PER_ASSET = 1000;
|
|
50
51
|
const MAX_USER_CALLBACKS = 100;
|
|
51
52
|
const POLYMARKET_MARKET_WS_URL = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
|
|
@@ -63,7 +64,10 @@ class PolymarketWebSocket {
|
|
|
63
64
|
orderBooks = new Map();
|
|
64
65
|
config;
|
|
65
66
|
initializationPromise;
|
|
67
|
+
marketPingInterval = null;
|
|
68
|
+
callApi;
|
|
66
69
|
constructor(callApi, config = {}) {
|
|
70
|
+
this.callApi = callApi;
|
|
67
71
|
this.config = config;
|
|
68
72
|
const watcherConfig = this.config.watcherConfig;
|
|
69
73
|
const subscriber = new goldsky_1.GoldSkySubscriber({
|
|
@@ -78,12 +82,20 @@ class PolymarketWebSocket {
|
|
|
78
82
|
async watchOrderBook(outcomeId) {
|
|
79
83
|
await this.ensureInitialized();
|
|
80
84
|
await this.subscribe([outcomeId]);
|
|
81
|
-
// Return a promise that resolves on the next orderbook update
|
|
85
|
+
// Return a promise that resolves on the next orderbook update.
|
|
86
|
+
// If the upstream market channel accepts the subscription but stays
|
|
87
|
+
// quiet, return a real REST snapshot instead of hanging indefinitely.
|
|
88
|
+
const resolverEntry = {
|
|
89
|
+
resolve: () => { },
|
|
90
|
+
reject: () => { },
|
|
91
|
+
};
|
|
82
92
|
const dataPromise = new Promise((resolve, reject) => {
|
|
93
|
+
resolverEntry.resolve = resolve;
|
|
94
|
+
resolverEntry.reject = reject;
|
|
83
95
|
const existing = this.orderBookResolvers.get(outcomeId) ?? [];
|
|
84
|
-
this.orderBookResolvers.set(outcomeId, [...existing,
|
|
96
|
+
this.orderBookResolvers.set(outcomeId, [...existing, resolverEntry]);
|
|
85
97
|
});
|
|
86
|
-
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${outcomeId}')`);
|
|
98
|
+
return (0, watch_timeout_1.withWatchTimeout)(this.withSnapshotFallback(outcomeId, dataPromise, resolverEntry), this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${outcomeId}')`);
|
|
87
99
|
}
|
|
88
100
|
async unwatchOrderBook(outcomeId) {
|
|
89
101
|
this.subscribedAssets.delete(outcomeId);
|
|
@@ -269,6 +281,7 @@ class PolymarketWebSocket {
|
|
|
269
281
|
this.ws.close();
|
|
270
282
|
this.ws = null;
|
|
271
283
|
}
|
|
284
|
+
this.stopMarketHeartbeat();
|
|
272
285
|
this.subscribedAssets.clear();
|
|
273
286
|
this.closeUserChannel();
|
|
274
287
|
this.watcher.close();
|
|
@@ -302,11 +315,15 @@ class PolymarketWebSocket {
|
|
|
302
315
|
}, timeoutMs);
|
|
303
316
|
this.ws.on('open', () => {
|
|
304
317
|
clearTimeout(timeout);
|
|
318
|
+
this.startMarketHeartbeat();
|
|
305
319
|
resolve();
|
|
306
320
|
});
|
|
307
321
|
this.ws.on('message', (raw) => {
|
|
308
322
|
try {
|
|
309
|
-
const
|
|
323
|
+
const text = raw.toString();
|
|
324
|
+
if (text === 'PONG' || text === 'PING')
|
|
325
|
+
return;
|
|
326
|
+
const msgs = JSON.parse(text);
|
|
310
327
|
const arr = Array.isArray(msgs) ? msgs : [msgs];
|
|
311
328
|
for (const msg of arr) {
|
|
312
329
|
const type = msg.event_type;
|
|
@@ -327,9 +344,12 @@ class PolymarketWebSocket {
|
|
|
327
344
|
this.ws.on('error', (err) => {
|
|
328
345
|
clearTimeout(timeout);
|
|
329
346
|
logger_1.logger.error('[polymarket-ws] WebSocket error', { error: err.message });
|
|
347
|
+
this.rejectPendingMarketResolvers(err);
|
|
330
348
|
reject(err);
|
|
331
349
|
});
|
|
332
350
|
this.ws.on('close', () => {
|
|
351
|
+
this.stopMarketHeartbeat();
|
|
352
|
+
this.rejectPendingMarketResolvers(new Error('Polymarket market channel closed'));
|
|
333
353
|
this.initializationPromise = undefined;
|
|
334
354
|
this.ws = null;
|
|
335
355
|
});
|
|
@@ -338,21 +358,107 @@ class PolymarketWebSocket {
|
|
|
338
358
|
}
|
|
339
359
|
handleBookSnapshot(event) {
|
|
340
360
|
const id = event.asset_id;
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
361
|
+
const orderBook = this.normalizeRawOrderBook(event);
|
|
362
|
+
this.orderBooks.set(id, orderBook);
|
|
363
|
+
this.resolveOrderBook(id, orderBook);
|
|
364
|
+
}
|
|
365
|
+
withSnapshotFallback(outcomeId, dataPromise, resolverEntry) {
|
|
366
|
+
const fallbackMs = this.config.snapshotFallbackMs ?? DEFAULT_SNAPSHOT_FALLBACK_MS;
|
|
367
|
+
if (fallbackMs <= 0)
|
|
368
|
+
return dataPromise;
|
|
369
|
+
let timer;
|
|
370
|
+
const fallbackPromise = new Promise((resolve, reject) => {
|
|
371
|
+
timer = setTimeout(async () => {
|
|
372
|
+
this.removeOrderBookResolver(outcomeId, resolverEntry);
|
|
373
|
+
try {
|
|
374
|
+
resolve(await this.fetchOrderBookSnapshot(outcomeId));
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
reject(error);
|
|
378
|
+
}
|
|
379
|
+
}, fallbackMs);
|
|
380
|
+
});
|
|
381
|
+
return Promise.race([dataPromise, fallbackPromise]).finally(() => {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
removeOrderBookResolver(outcomeId, resolverEntry) {
|
|
386
|
+
const resolvers = this.orderBookResolvers.get(outcomeId);
|
|
387
|
+
if (!resolvers)
|
|
388
|
+
return;
|
|
389
|
+
const filtered = resolvers.filter((entry) => entry !== resolverEntry);
|
|
390
|
+
if (filtered.length > 0) {
|
|
391
|
+
this.orderBookResolvers.set(outcomeId, filtered);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this.orderBookResolvers.delete(outcomeId);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async fetchOrderBookSnapshot(outcomeId) {
|
|
398
|
+
const raw = await this.callApi('getBook', { token_id: outcomeId });
|
|
399
|
+
const orderBook = this.normalizeRawOrderBook({
|
|
400
|
+
...raw,
|
|
401
|
+
asset_id: raw?.asset_id ?? outcomeId,
|
|
402
|
+
});
|
|
403
|
+
this.orderBooks.set(outcomeId, orderBook);
|
|
404
|
+
return orderBook;
|
|
405
|
+
}
|
|
406
|
+
normalizeRawOrderBook(raw) {
|
|
407
|
+
const bids = (raw?.bids || []).map((level) => ({
|
|
408
|
+
price: parseFloat(level.price),
|
|
409
|
+
size: parseFloat(level.size),
|
|
344
410
|
})).sort((a, b) => b.price - a.price);
|
|
345
|
-
const asks =
|
|
346
|
-
price: parseFloat(
|
|
347
|
-
size: parseFloat(
|
|
411
|
+
const asks = (raw?.asks || []).map((level) => ({
|
|
412
|
+
price: parseFloat(level.price),
|
|
413
|
+
size: parseFloat(level.size),
|
|
348
414
|
})).sort((a, b) => a.price - b.price);
|
|
349
|
-
|
|
415
|
+
return {
|
|
350
416
|
bids,
|
|
351
417
|
asks,
|
|
352
|
-
timestamp:
|
|
418
|
+
timestamp: this.parseTimestamp(raw?.timestamp),
|
|
353
419
|
};
|
|
354
|
-
|
|
355
|
-
|
|
420
|
+
}
|
|
421
|
+
parseTimestamp(value) {
|
|
422
|
+
if (typeof value === 'number')
|
|
423
|
+
return value;
|
|
424
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
425
|
+
const numeric = Number(value);
|
|
426
|
+
return Number.isNaN(numeric) ? new Date(value).getTime() : numeric;
|
|
427
|
+
}
|
|
428
|
+
return Date.now();
|
|
429
|
+
}
|
|
430
|
+
startMarketHeartbeat() {
|
|
431
|
+
this.stopMarketHeartbeat();
|
|
432
|
+
this.marketPingInterval = setInterval(() => {
|
|
433
|
+
if (this.ws && this.ws.readyState === 1) {
|
|
434
|
+
try {
|
|
435
|
+
this.ws.send('PING');
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
logger_1.logger.warn('[polymarket-ws] market heartbeat failed', {
|
|
439
|
+
error: error instanceof Error ? error.message : String(error),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}, 10_000);
|
|
444
|
+
}
|
|
445
|
+
stopMarketHeartbeat() {
|
|
446
|
+
if (this.marketPingInterval) {
|
|
447
|
+
clearInterval(this.marketPingInterval);
|
|
448
|
+
this.marketPingInterval = null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
rejectPendingMarketResolvers(error) {
|
|
452
|
+
for (const [, resolvers] of this.orderBookResolvers) {
|
|
453
|
+
for (const resolver of resolvers)
|
|
454
|
+
resolver.reject(error);
|
|
455
|
+
}
|
|
456
|
+
this.orderBookResolvers.clear();
|
|
457
|
+
for (const [, resolvers] of this.tradeResolvers) {
|
|
458
|
+
for (const resolver of resolvers)
|
|
459
|
+
resolver.reject(error);
|
|
460
|
+
}
|
|
461
|
+
this.tradeResolvers.clear();
|
|
356
462
|
}
|
|
357
463
|
handlePriceChange(event) {
|
|
358
464
|
const id = event.asset_id;
|
|
@@ -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-05-
|
|
3
|
+
* Generated at: 2026-05-25T16:17:01.530Z
|
|
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-05-
|
|
6
|
+
* Generated at: 2026-05-25T16:17:01.530Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.probableApiSpec = {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuiBets P2P Sports Betting API Reference
|
|
3
|
+
*
|
|
4
|
+
* This file documents the SuiBets REST API endpoints used by the fetcher.
|
|
5
|
+
* It is NOT wired into defineImplicitApi — the fetcher calls these endpoints
|
|
6
|
+
* directly via FetcherContext.http (the rate-limited HTTP client).
|
|
7
|
+
*
|
|
8
|
+
* Base URL: https://suibets.replit.app
|
|
9
|
+
*
|
|
10
|
+
* Endpoints:
|
|
11
|
+
* GET /api/p2p/offers - List open P2P offers (status, matchId, sport, limit, offset)
|
|
12
|
+
* GET /api/p2p/offers/:id - Get a single P2P offer by ID
|
|
13
|
+
* GET /api/p2p/my?wallet=... - Get user activity (created offers, matched bets, parlays)
|
|
14
|
+
* GET /api/events/upcoming - List upcoming sports events (sport, limit)
|
|
15
|
+
*/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SuiBets P2P Sports Betting API Reference
|
|
4
|
+
*
|
|
5
|
+
* This file documents the SuiBets REST API endpoints used by the fetcher.
|
|
6
|
+
* It is NOT wired into defineImplicitApi — the fetcher calls these endpoints
|
|
7
|
+
* directly via FetcherContext.http (the rate-limited HTTP client).
|
|
8
|
+
*
|
|
9
|
+
* Base URL: https://suibets.replit.app
|
|
10
|
+
*
|
|
11
|
+
* Endpoints:
|
|
12
|
+
* GET /api/p2p/offers - List open P2P offers (status, matchId, sport, limit, offset)
|
|
13
|
+
* GET /api/p2p/offers/:id - Get a single P2P offer by ID
|
|
14
|
+
* GET /api/p2p/my?wallet=... - Get user activity (created offers, matched bets, parlays)
|
|
15
|
+
* GET /api/events/upcoming - List upcoming sports events (sport, limit)
|
|
16
|
+
*/
|
|
17
|
+
// No runtime exports — this file serves as API documentation only.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SUIBETS_BASE_URL = "https://suibets.replit.app";
|
|
2
|
+
export declare const SUIBETS_PLATFORM_FEE = 0.02;
|
|
3
|
+
export declare const MIST_PER_SUI = 1000000000;
|
|
4
|
+
export declare const MIN_PRICE = 0.01;
|
|
5
|
+
export declare const MAX_PRICE = 0.99;
|
|
6
|
+
export declare const RATE_LIMIT_MS = 300;
|
|
7
|
+
export declare const ALLOWED_HOSTS: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Validates that the given URL's hostname is in the ALLOWED_HOSTS allowlist.
|
|
10
|
+
* Throws if the hostname is not permitted, to prevent SSRF.
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateBaseUrl(url: string): void;
|
|
13
|
+
export interface SuibetsApiConfig {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getSuibetsConfig(baseUrlOverride?: string): SuibetsApiConfig;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALLOWED_HOSTS = exports.RATE_LIMIT_MS = exports.MAX_PRICE = exports.MIN_PRICE = exports.MIST_PER_SUI = exports.SUIBETS_PLATFORM_FEE = exports.SUIBETS_BASE_URL = void 0;
|
|
4
|
+
exports.validateBaseUrl = validateBaseUrl;
|
|
5
|
+
exports.getSuibetsConfig = getSuibetsConfig;
|
|
6
|
+
exports.SUIBETS_BASE_URL = 'https://suibets.replit.app';
|
|
7
|
+
// SuiBets is a P2P sports betting platform on Sui blockchain.
|
|
8
|
+
// Platform takes a 2% fee on settled markets.
|
|
9
|
+
exports.SUIBETS_PLATFORM_FEE = 0.02;
|
|
10
|
+
// Sui uses MIST as its base unit; 1 SUI = 1,000,000,000 MIST
|
|
11
|
+
exports.MIST_PER_SUI = 1e9;
|
|
12
|
+
// Prices represent probabilities in the range [0.01, 0.99]
|
|
13
|
+
exports.MIN_PRICE = 0.01;
|
|
14
|
+
exports.MAX_PRICE = 0.99;
|
|
15
|
+
// Minimum delay between outbound requests (milliseconds)
|
|
16
|
+
exports.RATE_LIMIT_MS = 300;
|
|
17
|
+
// Allowlist of permitted hostnames for SSRF protection
|
|
18
|
+
exports.ALLOWED_HOSTS = ['suibets.replit.app'];
|
|
19
|
+
/**
|
|
20
|
+
* Validates that the given URL's hostname is in the ALLOWED_HOSTS allowlist.
|
|
21
|
+
* Throws if the hostname is not permitted, to prevent SSRF.
|
|
22
|
+
*/
|
|
23
|
+
function validateBaseUrl(url) {
|
|
24
|
+
const parsed = new URL(url);
|
|
25
|
+
if (!exports.ALLOWED_HOSTS.includes(parsed.hostname)) {
|
|
26
|
+
throw new Error(`Base URL hostname "${parsed.hostname}" is not in the SSRF allowlist. ` +
|
|
27
|
+
`Permitted hosts: ${exports.ALLOWED_HOSTS.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function getSuibetsConfig(baseUrlOverride) {
|
|
31
|
+
const baseUrl = baseUrlOverride ?? exports.SUIBETS_BASE_URL;
|
|
32
|
+
validateBaseUrl(baseUrl);
|
|
33
|
+
return { baseUrl };
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ErrorMapper } from '../../utils/error-mapper';
|
|
2
|
+
/**
|
|
3
|
+
* Maps SuiBets API errors to PMXT unified error classes.
|
|
4
|
+
*
|
|
5
|
+
* SuiBets is a read-only public API, so error mapping focuses on
|
|
6
|
+
* network errors and rate limits. Error responses are expected in the form:
|
|
7
|
+
* { error: string }
|
|
8
|
+
* or:
|
|
9
|
+
* { message: string }
|
|
10
|
+
*/
|
|
11
|
+
export declare class SuibetsErrorMapper extends ErrorMapper {
|
|
12
|
+
constructor();
|
|
13
|
+
protected extractErrorMessage(error: unknown): string;
|
|
14
|
+
mapError(error: unknown): ReturnType<ErrorMapper['mapError']>;
|
|
15
|
+
}
|
|
16
|
+
export declare const suibetsErrorMapper: SuibetsErrorMapper;
|
|
@@ -0,0 +1,71 @@
|
|
|
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.suibetsErrorMapper = exports.SuibetsErrorMapper = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const error_mapper_1 = require("../../utils/error-mapper");
|
|
9
|
+
const errors_1 = require("../../errors");
|
|
10
|
+
/**
|
|
11
|
+
* Maps SuiBets API errors to PMXT unified error classes.
|
|
12
|
+
*
|
|
13
|
+
* SuiBets is a read-only public API, so error mapping focuses on
|
|
14
|
+
* network errors and rate limits. Error responses are expected in the form:
|
|
15
|
+
* { error: string }
|
|
16
|
+
* or:
|
|
17
|
+
* { message: string }
|
|
18
|
+
*/
|
|
19
|
+
class SuibetsErrorMapper extends error_mapper_1.ErrorMapper {
|
|
20
|
+
constructor() {
|
|
21
|
+
super('SuiBets');
|
|
22
|
+
}
|
|
23
|
+
extractErrorMessage(error) {
|
|
24
|
+
if (axios_1.default.isAxiosError(error) && error.response?.data) {
|
|
25
|
+
const data = error.response.data;
|
|
26
|
+
if (typeof data === 'string') {
|
|
27
|
+
return `[${error.response.status}] ${data}`;
|
|
28
|
+
}
|
|
29
|
+
if (typeof data === 'object' && data !== null) {
|
|
30
|
+
const obj = data;
|
|
31
|
+
if (typeof obj.error === 'string') {
|
|
32
|
+
return `[${error.response.status}] ${obj.error}`;
|
|
33
|
+
}
|
|
34
|
+
if (typeof obj.message === 'string') {
|
|
35
|
+
return `[${error.response.status}] ${obj.message}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return super.extractErrorMessage(error);
|
|
40
|
+
}
|
|
41
|
+
mapError(error) {
|
|
42
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
43
|
+
const status = error.response?.status;
|
|
44
|
+
if (status === 429) {
|
|
45
|
+
const retryAfter = error.response?.headers?.['retry-after'];
|
|
46
|
+
const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : undefined;
|
|
47
|
+
return new errors_1.RateLimitExceeded(this.extractErrorMessage(error), retryAfterSeconds, this.exchangeName);
|
|
48
|
+
}
|
|
49
|
+
if (status === 401 || status === 403) {
|
|
50
|
+
return new errors_1.AuthenticationError(this.extractErrorMessage(error), this.exchangeName);
|
|
51
|
+
}
|
|
52
|
+
if (status !== undefined && status >= 500) {
|
|
53
|
+
return new errors_1.ExchangeNotAvailable(`Exchange error (${status}): ${this.extractErrorMessage(error)}`, this.exchangeName);
|
|
54
|
+
}
|
|
55
|
+
if (!status) {
|
|
56
|
+
return new errors_1.NetworkError(`Network error: ${this.extractErrorMessage(error)}`, this.exchangeName);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (error instanceof Error && !axios_1.default.isAxiosError(error)) {
|
|
60
|
+
const nodeErr = error;
|
|
61
|
+
if (nodeErr.code === 'ECONNREFUSED' ||
|
|
62
|
+
nodeErr.code === 'ENOTFOUND' ||
|
|
63
|
+
nodeErr.code === 'ETIMEDOUT') {
|
|
64
|
+
return new errors_1.NetworkError(`Network error: ${error.message}`, this.exchangeName);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return super.mapError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.SuibetsErrorMapper = SuibetsErrorMapper;
|
|
71
|
+
exports.suibetsErrorMapper = new SuibetsErrorMapper();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MarketFilterParams, EventFetchParams } from '../../BaseExchange';
|
|
2
|
+
import { IExchangeFetcher, FetcherContext } from '../interfaces';
|
|
3
|
+
export interface SuibetsRawOffer {
|
|
4
|
+
id: string;
|
|
5
|
+
matchId: string;
|
|
6
|
+
matchName: string;
|
|
7
|
+
sport: string;
|
|
8
|
+
homeTeam: string;
|
|
9
|
+
awayTeam: string;
|
|
10
|
+
creatorWallet: string;
|
|
11
|
+
creatorTeam: string;
|
|
12
|
+
creatorOdds: number;
|
|
13
|
+
creatorStake: number;
|
|
14
|
+
takerStake: number;
|
|
15
|
+
remainingStake?: number;
|
|
16
|
+
matchDate: string;
|
|
17
|
+
expiresAt: string;
|
|
18
|
+
status: string;
|
|
19
|
+
totalMatched?: number;
|
|
20
|
+
currency?: string;
|
|
21
|
+
isOnchain?: boolean;
|
|
22
|
+
onchainOfferId?: string;
|
|
23
|
+
leagueName?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SuibetsRawEvent {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
homeTeam: string;
|
|
29
|
+
awayTeam: string;
|
|
30
|
+
sport: string;
|
|
31
|
+
leagueName?: string;
|
|
32
|
+
matchDate: string;
|
|
33
|
+
status: string;
|
|
34
|
+
offers?: SuibetsRawOffer[];
|
|
35
|
+
}
|
|
36
|
+
export declare class SuibetsFetcher implements IExchangeFetcher<SuibetsRawOffer, SuibetsRawEvent> {
|
|
37
|
+
private readonly ctx;
|
|
38
|
+
private readonly baseUrl;
|
|
39
|
+
constructor(ctx: FetcherContext, baseUrl: string);
|
|
40
|
+
/**
|
|
41
|
+
* Performs a GET request via the rate-limited HTTP client provided by the
|
|
42
|
+
* base class. All errors are mapped to pmxt unified error types.
|
|
43
|
+
*/
|
|
44
|
+
private get;
|
|
45
|
+
/**
|
|
46
|
+
* Fetches raw P2P bet offers from the SuiBets API.
|
|
47
|
+
*
|
|
48
|
+
* When `params.query` is set, filtering is applied client-side after
|
|
49
|
+
* fetching because the API does not support full-text search.
|
|
50
|
+
*/
|
|
51
|
+
fetchRawMarkets(params?: MarketFilterParams): Promise<SuibetsRawOffer[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Fetches raw events by grouping active P2P offers by their matchId.
|
|
54
|
+
*
|
|
55
|
+
* SuiBets has no dedicated events endpoint; events are synthesised from
|
|
56
|
+
* the offers list so each unique match becomes one event.
|
|
57
|
+
*/
|
|
58
|
+
fetchRawEvents(params: EventFetchParams): Promise<SuibetsRawEvent[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Fetches raw positions (created offers, matched bets, parlays) for a
|
|
61
|
+
* given Sui wallet address.
|
|
62
|
+
*/
|
|
63
|
+
fetchRawPositions(walletAddress: string): Promise<unknown[]>;
|
|
64
|
+
}
|