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.
Files changed (41) 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/mock/index.js +13 -2
  6. package/dist/exchanges/myriad/api.d.ts +1 -1
  7. package/dist/exchanges/myriad/api.js +1 -1
  8. package/dist/exchanges/opinion/api.d.ts +1 -1
  9. package/dist/exchanges/opinion/api.js +1 -1
  10. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  11. package/dist/exchanges/polymarket/api-clob.js +1 -1
  12. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  13. package/dist/exchanges/polymarket/api-data.js +1 -1
  14. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  15. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  16. package/dist/exchanges/polymarket/websocket.d.ts +12 -0
  17. package/dist/exchanges/polymarket/websocket.js +120 -14
  18. package/dist/exchanges/probable/api.d.ts +1 -1
  19. package/dist/exchanges/probable/api.js +1 -1
  20. package/dist/exchanges/suibets/api.d.ts +15 -0
  21. package/dist/exchanges/suibets/api.js +17 -0
  22. package/dist/exchanges/suibets/config.d.ts +16 -0
  23. package/dist/exchanges/suibets/config.js +34 -0
  24. package/dist/exchanges/suibets/errors.d.ts +16 -0
  25. package/dist/exchanges/suibets/errors.js +71 -0
  26. package/dist/exchanges/suibets/fetcher.d.ts +64 -0
  27. package/dist/exchanges/suibets/fetcher.js +128 -0
  28. package/dist/exchanges/suibets/index.d.ts +54 -0
  29. package/dist/exchanges/suibets/index.js +114 -0
  30. package/dist/exchanges/suibets/normalizer.d.ts +8 -0
  31. package/dist/exchanges/suibets/normalizer.js +102 -0
  32. package/dist/exchanges/suibets/utils.d.ts +63 -0
  33. package/dist/exchanges/suibets/utils.js +124 -0
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.js +5 -1
  36. package/dist/router/Router.js +12 -3
  37. package/dist/server/app.js +76 -1
  38. package/dist/server/exchange-factory.js +6 -0
  39. package/dist/server/openapi.yaml +7 -0
  40. package/dist/server/ws-handler.js +196 -23
  41. 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-25T13:34:08.496Z
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-25T13:34:08.496Z
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-25T13:34:08.535Z
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-25T13:34:08.535Z
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 ? params.end.getTime() : Date.now();
337
- const start = params.start ? params.start.getTime() : end - step * limit;
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-25T13:34:08.546Z
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-25T13:34:08.546Z
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-25T13:34:08.549Z
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-25T13:34:08.549Z
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-25T13:34:08.504Z
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-25T13:34:08.504Z
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-25T13:34:08.517Z
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-25T13:34:08.517Z
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-25T13:34:08.515Z
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-25T13:34:08.515Z
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, { resolve, reject }]);
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 msgs = JSON.parse(raw.toString());
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 bids = event.bids.map((b) => ({
342
- price: parseFloat(b.price),
343
- size: parseFloat(b.size),
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 = event.asks.map((a) => ({
346
- price: parseFloat(a.price),
347
- size: parseFloat(a.size),
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
- const orderBook = {
415
+ return {
350
416
  bids,
351
417
  asks,
352
- timestamp: event.timestamp ? (isNaN(Number(event.timestamp)) ? new Date(event.timestamp).getTime() : Number(event.timestamp)) : Date.now(),
418
+ timestamp: this.parseTimestamp(raw?.timestamp),
353
419
  };
354
- this.orderBooks.set(id, orderBook);
355
- this.resolveOrderBook(id, orderBook);
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-25T13:34:08.541Z
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-25T13:34:08.541Z
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
+ }