pmxt-core 2.44.6 → 2.45.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 (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 +6 -6
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuibetsFetcher = void 0;
4
+ const errors_1 = require("./errors");
5
+ class SuibetsFetcher {
6
+ ctx;
7
+ baseUrl;
8
+ constructor(ctx, baseUrl) {
9
+ this.ctx = ctx;
10
+ this.baseUrl = baseUrl.replace(/\/$/, '');
11
+ }
12
+ /**
13
+ * Performs a GET request via the rate-limited HTTP client provided by the
14
+ * base class. All errors are mapped to pmxt unified error types.
15
+ */
16
+ async get(path, params) {
17
+ try {
18
+ const url = new URL(path, this.baseUrl);
19
+ if (params) {
20
+ for (const [k, v] of Object.entries(params)) {
21
+ url.searchParams.set(k, v);
22
+ }
23
+ }
24
+ const response = await this.ctx.http.get(url.toString(), {
25
+ maxContentLength: 5 * 1024 * 1024,
26
+ });
27
+ return response.data;
28
+ }
29
+ catch (error) {
30
+ throw errors_1.suibetsErrorMapper.mapError(error);
31
+ }
32
+ }
33
+ /**
34
+ * Fetches raw P2P bet offers from the SuiBets API.
35
+ *
36
+ * When `params.query` is set, filtering is applied client-side after
37
+ * fetching because the API does not support full-text search.
38
+ */
39
+ async fetchRawMarkets(params) {
40
+ if (params?.marketId) {
41
+ const id = params.marketId.replace(/^suibets:/, '');
42
+ const data = await this.get(`/api/p2p/offers/${id}`);
43
+ const offer = data.offer ?? data;
44
+ return offer ? [offer] : [];
45
+ }
46
+ const baseParams = {
47
+ status: params?.status === 'all' ? 'all' : 'OPEN',
48
+ limit: String(params?.limit ?? 50),
49
+ offset: String(params?.offset ?? 0),
50
+ };
51
+ const queryParams = params?.eventId
52
+ ? { ...baseParams, matchId: params.eventId.replace(/^suibets:/, '') }
53
+ : { ...baseParams };
54
+ const data = await this.get('/api/p2p/offers', queryParams);
55
+ const offers = data.offers ??
56
+ (Array.isArray(data) ? data : []);
57
+ if (!params?.query) {
58
+ return offers;
59
+ }
60
+ // Client-side text filter: the API has no search endpoint.
61
+ const q = params.query.toLowerCase();
62
+ return offers.filter(o => o.matchName?.toLowerCase().includes(q) ||
63
+ o.homeTeam?.toLowerCase().includes(q) ||
64
+ o.awayTeam?.toLowerCase().includes(q) ||
65
+ o.sport?.toLowerCase().includes(q));
66
+ }
67
+ /**
68
+ * Fetches raw events by grouping active P2P offers by their matchId.
69
+ *
70
+ * SuiBets has no dedicated events endpoint; events are synthesised from
71
+ * the offers list so each unique match becomes one event.
72
+ */
73
+ async fetchRawEvents(params) {
74
+ const queryParams = {
75
+ status: 'OPEN',
76
+ limit: String(params.limit ?? 100),
77
+ };
78
+ const data = await this.get('/api/p2p/offers', queryParams);
79
+ const offers = data.offers ??
80
+ (Array.isArray(data) ? data : []);
81
+ // Group offers by matchId using a Map; each entry is built immutably.
82
+ const byMatch = new Map();
83
+ for (const offer of offers) {
84
+ if (!offer.matchId)
85
+ continue;
86
+ const existing = byMatch.get(offer.matchId) ?? [];
87
+ byMatch.set(offer.matchId, [...existing, offer]);
88
+ }
89
+ const q = params.query?.toLowerCase();
90
+ const events = [];
91
+ for (const [matchId, matchOffers] of byMatch) {
92
+ const first = matchOffers[0];
93
+ if (q) {
94
+ const matches = first.matchName?.toLowerCase().includes(q) ||
95
+ first.homeTeam?.toLowerCase().includes(q) ||
96
+ first.awayTeam?.toLowerCase().includes(q) ||
97
+ first.sport?.toLowerCase().includes(q);
98
+ if (!matches)
99
+ continue;
100
+ }
101
+ events.push({
102
+ id: matchId,
103
+ name: first.matchName || `${first.homeTeam} vs ${first.awayTeam}`,
104
+ homeTeam: first.homeTeam,
105
+ awayTeam: first.awayTeam,
106
+ sport: first.sport,
107
+ leagueName: first.leagueName,
108
+ matchDate: first.matchDate,
109
+ status: 'active',
110
+ offers: matchOffers,
111
+ });
112
+ }
113
+ return events;
114
+ }
115
+ /**
116
+ * Fetches raw positions (created offers, matched bets, parlays) for a
117
+ * given Sui wallet address.
118
+ */
119
+ async fetchRawPositions(walletAddress) {
120
+ const data = await this.get('/api/p2p/my', { wallet: walletAddress });
121
+ return [
122
+ ...(data.createdOffers ?? []),
123
+ ...(data.matchedBets ?? []),
124
+ ...(data.parlays ?? []),
125
+ ];
126
+ }
127
+ }
128
+ exports.SuibetsFetcher = SuibetsFetcher;
@@ -0,0 +1,54 @@
1
+ import { PredictionMarketExchange, MarketFilterParams, EventFetchParams, ExchangeCredentials } from '../../BaseExchange';
2
+ import { UnifiedMarket, UnifiedEvent, OrderBook, Position } from '../../types';
3
+ export interface SuibetsCredentials extends ExchangeCredentials {
4
+ /** Sui wallet address for fetching personal positions */
5
+ walletAddress?: string;
6
+ /** Override API base URL (default: https://suibets.replit.app) */
7
+ baseUrl?: string;
8
+ }
9
+ /**
10
+ * SuiBets — Decentralised P2P sports betting on Sui blockchain.
11
+ *
12
+ * Maps P2P bet offers to the pmxt unified market model:
13
+ * - Market = one P2P offer (creator side vs taker side)
14
+ * - Event = a sports match (groups all offers for that match)
15
+ * - Outcome = creator's pick (YES) or opposite (NO)
16
+ * - Price = implied probability derived from the offer odds
17
+ *
18
+ * Usage:
19
+ * ```ts
20
+ * import pmxt from 'pmxtjs';
21
+ * const exchange = new pmxt.SuiBets();
22
+ * const markets = await exchange.fetchMarkets({ limit: 20 });
23
+ * ```
24
+ */
25
+ export declare class SuiBetsExchange extends PredictionMarketExchange {
26
+ protected readonly capabilityOverrides: {
27
+ fetchOrderBook: "emulated";
28
+ createOrder: false;
29
+ cancelOrder: false;
30
+ fetchOrder: false;
31
+ fetchOpenOrders: false;
32
+ fetchBalance: false;
33
+ fetchPositions: true;
34
+ watchOrderBook: false;
35
+ watchTrades: false;
36
+ };
37
+ private readonly config;
38
+ private readonly fetcher;
39
+ private readonly normalizer;
40
+ private readonly walletAddress?;
41
+ constructor(credentials?: SuibetsCredentials);
42
+ get name(): string;
43
+ protected sign(): Record<string, string>;
44
+ protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
45
+ protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
46
+ /**
47
+ * Emulated order book derived from offer odds.
48
+ *
49
+ * Bid side: what buyers pay to back the creator's pick (YES price).
50
+ * Ask side: what sellers want to take the opposite side (NO price).
51
+ */
52
+ fetchOrderBook(outcomeId: string): Promise<OrderBook>;
53
+ fetchPositions(): Promise<Position[]>;
54
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuiBetsExchange = void 0;
4
+ const BaseExchange_1 = require("../../BaseExchange");
5
+ const errors_1 = require("../../errors");
6
+ const config_1 = require("./config");
7
+ const fetcher_1 = require("./fetcher");
8
+ const normalizer_1 = require("./normalizer");
9
+ const utils_1 = require("./utils");
10
+ /**
11
+ * SuiBets — Decentralised P2P sports betting on Sui blockchain.
12
+ *
13
+ * Maps P2P bet offers to the pmxt unified market model:
14
+ * - Market = one P2P offer (creator side vs taker side)
15
+ * - Event = a sports match (groups all offers for that match)
16
+ * - Outcome = creator's pick (YES) or opposite (NO)
17
+ * - Price = implied probability derived from the offer odds
18
+ *
19
+ * Usage:
20
+ * ```ts
21
+ * import pmxt from 'pmxtjs';
22
+ * const exchange = new pmxt.SuiBets();
23
+ * const markets = await exchange.fetchMarkets({ limit: 20 });
24
+ * ```
25
+ */
26
+ class SuiBetsExchange extends BaseExchange_1.PredictionMarketExchange {
27
+ capabilityOverrides = {
28
+ fetchOrderBook: 'emulated',
29
+ createOrder: false,
30
+ cancelOrder: false,
31
+ fetchOrder: false,
32
+ fetchOpenOrders: false,
33
+ fetchBalance: false,
34
+ fetchPositions: true,
35
+ watchOrderBook: false,
36
+ watchTrades: false,
37
+ };
38
+ config;
39
+ fetcher;
40
+ normalizer;
41
+ walletAddress;
42
+ constructor(credentials) {
43
+ super(credentials);
44
+ this.rateLimit = config_1.RATE_LIMIT_MS;
45
+ this.walletAddress = credentials?.walletAddress;
46
+ if (credentials?.baseUrl) {
47
+ (0, config_1.validateBaseUrl)(credentials.baseUrl);
48
+ }
49
+ this.config = (0, config_1.getSuibetsConfig)(credentials?.baseUrl);
50
+ const ctx = {
51
+ http: this.http,
52
+ callApi: this.callApi.bind(this),
53
+ getHeaders: () => ({}),
54
+ };
55
+ this.fetcher = new fetcher_1.SuibetsFetcher(ctx, this.config.baseUrl);
56
+ this.normalizer = new normalizer_1.SuibetsNormalizer();
57
+ }
58
+ get name() {
59
+ return 'SuiBets';
60
+ }
61
+ // SuiBets is a public API -- no request signing required
62
+ sign() {
63
+ return {};
64
+ }
65
+ // -------------------------------------------------------------------------
66
+ // Market Data
67
+ // -------------------------------------------------------------------------
68
+ async fetchMarketsImpl(params) {
69
+ const raw = await this.fetcher.fetchRawMarkets(params);
70
+ return raw
71
+ .map(r => this.normalizer.normalizeMarket(r))
72
+ .filter((m) => m !== null);
73
+ }
74
+ async fetchEventsImpl(params) {
75
+ const raw = await this.fetcher.fetchRawEvents(params);
76
+ return raw
77
+ .map(r => this.normalizer.normalizeEvent(r))
78
+ .filter((e) => e !== null);
79
+ }
80
+ /**
81
+ * Emulated order book derived from offer odds.
82
+ *
83
+ * Bid side: what buyers pay to back the creator's pick (YES price).
84
+ * Ask side: what sellers want to take the opposite side (NO price).
85
+ */
86
+ async fetchOrderBook(outcomeId) {
87
+ const { offerId } = (0, utils_1.fromOutcomeId)(outcomeId);
88
+ const markets = await this.fetchMarketsImpl({ marketId: `suibets:${offerId}` });
89
+ const market = markets[0];
90
+ if (!market)
91
+ return { bids: [], asks: [], timestamp: Date.now() };
92
+ const yes = market.outcomes[0];
93
+ const no = market.outcomes[1];
94
+ const size = market.liquidity;
95
+ return {
96
+ bids: [{ price: yes.price, size }],
97
+ asks: [{ price: no.price, size }],
98
+ timestamp: Date.now(),
99
+ };
100
+ }
101
+ // -------------------------------------------------------------------------
102
+ // Positions (read-only -- requires walletAddress)
103
+ // -------------------------------------------------------------------------
104
+ async fetchPositions() {
105
+ const wallet = this.walletAddress;
106
+ if (!wallet) {
107
+ throw new errors_1.AuthenticationError('fetchPositions() requires a walletAddress. ' +
108
+ 'Pass it via new SuiBetsExchange({ walletAddress: "0x..." }).', 'SuiBets');
109
+ }
110
+ const raw = await this.fetcher.fetchRawPositions(wallet);
111
+ return raw.map(r => this.normalizer.normalizePosition(r));
112
+ }
113
+ }
114
+ exports.SuiBetsExchange = SuiBetsExchange;
@@ -0,0 +1,8 @@
1
+ import { IExchangeNormalizer } from '../interfaces';
2
+ import { UnifiedMarket, UnifiedEvent, Position } from '../../types';
3
+ import { SuibetsRawOffer, SuibetsRawEvent } from './fetcher';
4
+ export declare class SuibetsNormalizer implements IExchangeNormalizer<SuibetsRawOffer, SuibetsRawEvent> {
5
+ normalizeMarket(raw: SuibetsRawOffer): UnifiedMarket | null;
6
+ normalizeEvent(raw: SuibetsRawEvent): UnifiedEvent | null;
7
+ normalizePosition(raw: SuibetsRawOffer): Position;
8
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SuibetsNormalizer = void 0;
4
+ const utils_1 = require("./utils");
5
+ function liquidity(offer) {
6
+ const remaining = offer.remainingStake ?? offer.creatorStake;
7
+ return (0, utils_1.mistToSui)(remaining);
8
+ }
9
+ class SuibetsNormalizer {
10
+ normalizeMarket(raw) {
11
+ if (!raw?.id)
12
+ return null;
13
+ const dateSource = raw.matchDate || raw.expiresAt;
14
+ if (!dateSource) {
15
+ throw new Error(`SuibetsNormalizer: offer ${raw.id} has neither matchDate nor expiresAt`);
16
+ }
17
+ const homeTeam = raw.homeTeam || 'Unknown Team';
18
+ const awayTeam = raw.awayTeam || 'Unknown Team';
19
+ const odds = Number(raw.creatorOdds) || 2;
20
+ const yesProb = (0, utils_1.impliedProbability)(odds);
21
+ const noProb = (0, utils_1.takerProbability)(odds);
22
+ const liq = liquidity(raw);
23
+ const volume24h = (0, utils_1.mistToSui)(raw.totalMatched ?? 0);
24
+ const marketId = (0, utils_1.toMarketId)(raw.id);
25
+ const creatorOutcome = {
26
+ outcomeId: (0, utils_1.toOutcomeId)(raw.id, 'creator'),
27
+ marketId,
28
+ label: (0, utils_1.sideLabel)(raw, 'creator'),
29
+ price: yesProb,
30
+ };
31
+ const takerOutcome = {
32
+ outcomeId: (0, utils_1.toOutcomeId)(raw.id, 'taker'),
33
+ marketId,
34
+ label: (0, utils_1.sideLabel)(raw, 'taker'),
35
+ price: noProb,
36
+ };
37
+ const market = {
38
+ marketId,
39
+ eventId: raw.matchId ? (0, utils_1.toMarketId)(raw.matchId) : undefined,
40
+ title: `${raw.matchName || `${homeTeam} vs ${awayTeam}`} \u2014 ${(0, utils_1.sideLabel)(raw, 'creator')} @ ${odds}x`,
41
+ description: [
42
+ `P2P offer on ${raw.sport || 'sports'} match.`,
43
+ `Creator bets ${(0, utils_1.sideLabel)(raw, 'creator')} at ${odds}x odds.`,
44
+ `Taker backs ${(0, utils_1.sideLabel)(raw, 'taker')} at ${(1 / noProb).toFixed(2)}x implied odds.`,
45
+ raw.leagueName ? `League: ${raw.leagueName}.` : '',
46
+ raw.isOnchain ? `On-chain escrow: ${raw.onchainOfferId ?? 'yes'}.` : 'Off-chain escrow.',
47
+ ].filter(Boolean).join(' '),
48
+ slug: raw.id,
49
+ outcomes: [creatorOutcome, takerOutcome],
50
+ resolutionDate: new Date(dateSource),
51
+ volume24h,
52
+ liquidity: liq,
53
+ url: 'https://suibets.replit.app/p2p',
54
+ status: (0, utils_1.mapStatus)(raw.status),
55
+ category: 'Sports',
56
+ tags: ['Sports', 'P2P', raw.sport, raw.leagueName].filter((t) => Boolean(t)),
57
+ contractAddress: raw.onchainOfferId,
58
+ yes: creatorOutcome,
59
+ no: takerOutcome,
60
+ };
61
+ return market;
62
+ }
63
+ normalizeEvent(raw) {
64
+ if (!raw?.id)
65
+ return null;
66
+ const homeTeam = raw.homeTeam || 'Unknown Team';
67
+ const awayTeam = raw.awayTeam || 'Unknown Team';
68
+ const markets = (raw.offers ?? [])
69
+ .map(o => this.normalizeMarket(o))
70
+ .filter((m) => m !== null);
71
+ const totalVolume = markets.reduce((s, m) => s + (m.volume24h ?? 0), 0);
72
+ return {
73
+ id: (0, utils_1.toMarketId)(raw.id),
74
+ title: raw.name || `${homeTeam} vs ${awayTeam}`,
75
+ description: [
76
+ raw.leagueName ? `${raw.leagueName} \u2014` : '',
77
+ raw.sport,
78
+ 'P2P betting on SuiBets.',
79
+ ].filter(Boolean).join(' '),
80
+ slug: raw.id,
81
+ markets,
82
+ volume24h: totalVolume,
83
+ volume: totalVolume,
84
+ url: 'https://suibets.replit.app/p2p',
85
+ category: 'Sports',
86
+ tags: ['Sports', 'P2P', 'Sui', raw.sport, raw.leagueName].filter((t) => Boolean(t)),
87
+ };
88
+ }
89
+ normalizePosition(raw) {
90
+ const odds = Number(raw.creatorOdds) || 2;
91
+ return {
92
+ marketId: (0, utils_1.toMarketId)(raw.matchId ?? raw.id),
93
+ outcomeId: (0, utils_1.toOutcomeId)(raw.id, 'creator'),
94
+ outcomeLabel: (0, utils_1.sideLabel)(raw, 'creator'),
95
+ size: (0, utils_1.mistToSui)(raw.creatorStake ?? 0),
96
+ entryPrice: (0, utils_1.impliedProbability)(odds),
97
+ currentPrice: (0, utils_1.impliedProbability)(odds),
98
+ unrealizedPnL: 0,
99
+ };
100
+ }
101
+ }
102
+ exports.SuibetsNormalizer = SuibetsNormalizer;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Build a unique market ID from a SuiBets offer ID.
3
+ * Format: "suibets:{offerId}"
4
+ */
5
+ export declare function toMarketId(offerId: string): string;
6
+ /**
7
+ * Extract the offer ID from a SuiBets market ID.
8
+ * Throws if the ID does not carry the expected prefix.
9
+ */
10
+ export declare function fromMarketId(marketId: string): string;
11
+ /**
12
+ * Build an outcome ID that encodes both the offer ID and the side.
13
+ * Format: "{offerId}:{side}"
14
+ */
15
+ export declare function toOutcomeId(offerId: string, side: 'creator' | 'taker'): string;
16
+ /**
17
+ * Decode an outcome ID back into offerId and side.
18
+ * Throws if the format is unrecognised or the side is invalid.
19
+ */
20
+ export declare function fromOutcomeId(outcomeId: string): {
21
+ offerId: string;
22
+ side: 'creator' | 'taker';
23
+ };
24
+ /**
25
+ * Map raw SuiBets offer statuses to the pmxt unified status vocabulary.
26
+ *
27
+ * OPEN -> active
28
+ * MATCHED -> matched
29
+ * SETTLED -> settled
30
+ * EXPIRED -> expired
31
+ * CANCELLED -> cancelled
32
+ * (other) -> inactive
33
+ */
34
+ export declare function mapStatus(rawStatus: string): string;
35
+ /**
36
+ * Convert decimal odds to an implied probability clamped to [0.01, 0.99].
37
+ *
38
+ * Throws if odds are zero or negative.
39
+ * Throws if odds are less than 1 (invalid — a payout below stake).
40
+ * When odds === 1 (evens), returns 0.99 (clamped maximum).
41
+ */
42
+ export declare function impliedProbability(decimalOdds: number): number;
43
+ /**
44
+ * Implied probability for the taker side: 1 - impliedProbability(odds),
45
+ * clamped to [0.01, 0.99].
46
+ */
47
+ export declare function takerProbability(decimalOdds: number): number;
48
+ /**
49
+ * Convert an amount denominated in MIST (the smallest SUI unit, 1e-9 SUI)
50
+ * to SUI by dividing by 1e9.
51
+ */
52
+ export declare function mistToSui(mist: number | string): number;
53
+ /**
54
+ * Return the human-readable team name for the given side of a P2P offer.
55
+ *
56
+ * Creator side: the team the creator bet on (creatorTeam, falling back to homeTeam).
57
+ * Taker side: the opposite team.
58
+ */
59
+ export declare function sideLabel(offer: {
60
+ creatorTeam?: string;
61
+ homeTeam?: string;
62
+ awayTeam?: string;
63
+ }, side: 'creator' | 'taker'): string;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toMarketId = toMarketId;
4
+ exports.fromMarketId = fromMarketId;
5
+ exports.toOutcomeId = toOutcomeId;
6
+ exports.fromOutcomeId = fromOutcomeId;
7
+ exports.mapStatus = mapStatus;
8
+ exports.impliedProbability = impliedProbability;
9
+ exports.takerProbability = takerProbability;
10
+ exports.mistToSui = mistToSui;
11
+ exports.sideLabel = sideLabel;
12
+ const MARKET_PREFIX = 'suibets:';
13
+ /**
14
+ * Build a unique market ID from a SuiBets offer ID.
15
+ * Format: "suibets:{offerId}"
16
+ */
17
+ function toMarketId(offerId) {
18
+ return `${MARKET_PREFIX}${offerId}`;
19
+ }
20
+ /**
21
+ * Extract the offer ID from a SuiBets market ID.
22
+ * Throws if the ID does not carry the expected prefix.
23
+ */
24
+ function fromMarketId(marketId) {
25
+ if (!marketId.startsWith(MARKET_PREFIX)) {
26
+ throw new Error(`Invalid SuiBets market ID: ${marketId}`);
27
+ }
28
+ return marketId.slice(MARKET_PREFIX.length);
29
+ }
30
+ /**
31
+ * Build an outcome ID that encodes both the offer ID and the side.
32
+ * Format: "{offerId}:{side}"
33
+ */
34
+ function toOutcomeId(offerId, side) {
35
+ return `${offerId}:${side}`;
36
+ }
37
+ /**
38
+ * Decode an outcome ID back into offerId and side.
39
+ * Throws if the format is unrecognised or the side is invalid.
40
+ */
41
+ function fromOutcomeId(outcomeId) {
42
+ const lastColon = outcomeId.lastIndexOf(':');
43
+ if (lastColon === -1) {
44
+ throw new Error(`Invalid SuiBets outcome ID: ${outcomeId}`);
45
+ }
46
+ const offerId = outcomeId.slice(0, lastColon);
47
+ const side = outcomeId.slice(lastColon + 1);
48
+ if (side !== 'creator' && side !== 'taker') {
49
+ throw new Error(`Invalid side in SuiBets outcome ID: ${outcomeId}`);
50
+ }
51
+ if (!offerId) {
52
+ throw new Error(`Invalid SuiBets outcome ID (empty offerId): ${outcomeId}`);
53
+ }
54
+ return { offerId, side };
55
+ }
56
+ /**
57
+ * Map raw SuiBets offer statuses to the pmxt unified status vocabulary.
58
+ *
59
+ * OPEN -> active
60
+ * MATCHED -> matched
61
+ * SETTLED -> settled
62
+ * EXPIRED -> expired
63
+ * CANCELLED -> cancelled
64
+ * (other) -> inactive
65
+ */
66
+ function mapStatus(rawStatus) {
67
+ switch (rawStatus) {
68
+ case 'OPEN': return 'active';
69
+ case 'MATCHED': return 'matched';
70
+ case 'SETTLED': return 'settled';
71
+ case 'EXPIRED': return 'expired';
72
+ case 'CANCELLED': return 'cancelled';
73
+ default: return 'inactive';
74
+ }
75
+ }
76
+ /**
77
+ * Convert decimal odds to an implied probability clamped to [0.01, 0.99].
78
+ *
79
+ * Throws if odds are zero or negative.
80
+ * Throws if odds are less than 1 (invalid — a payout below stake).
81
+ * When odds === 1 (evens), returns 0.99 (clamped maximum).
82
+ */
83
+ function impliedProbability(decimalOdds) {
84
+ if (decimalOdds <= 0) {
85
+ throw new Error(`Decimal odds must be positive, got: ${decimalOdds}`);
86
+ }
87
+ if (decimalOdds < 1) {
88
+ throw new Error(`Decimal odds below 1 are invalid, got: ${decimalOdds}`);
89
+ }
90
+ const raw = 1 / decimalOdds;
91
+ return Math.min(0.99, Math.max(0.01, raw));
92
+ }
93
+ /**
94
+ * Implied probability for the taker side: 1 - impliedProbability(odds),
95
+ * clamped to [0.01, 0.99].
96
+ */
97
+ function takerProbability(decimalOdds) {
98
+ return Math.min(0.99, Math.max(0.01, 1 - impliedProbability(decimalOdds)));
99
+ }
100
+ /**
101
+ * Convert an amount denominated in MIST (the smallest SUI unit, 1e-9 SUI)
102
+ * to SUI by dividing by 1e9.
103
+ */
104
+ function mistToSui(mist) {
105
+ return Number(mist) / 1e9;
106
+ }
107
+ /**
108
+ * Return the human-readable team name for the given side of a P2P offer.
109
+ *
110
+ * Creator side: the team the creator bet on (creatorTeam, falling back to homeTeam).
111
+ * Taker side: the opposite team.
112
+ */
113
+ function sideLabel(offer, side) {
114
+ const creator = offer.creatorTeam || offer.homeTeam || 'Home';
115
+ const away = offer.awayTeam || 'Away';
116
+ if (side === 'creator')
117
+ return creator;
118
+ // Taker takes the opposite side
119
+ if (creator.toLowerCase() === offer.homeTeam?.toLowerCase())
120
+ return away;
121
+ if (creator.toLowerCase() === offer.awayTeam?.toLowerCase())
122
+ return offer.homeTeam || 'Home';
123
+ return 'Opposite';
124
+ }
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export * from './exchanges/smarkets';
17
17
  export * from './exchanges/polymarket_us';
18
18
  export * from './exchanges/hyperliquid';
19
19
  export * from './exchanges/gemini-titan';
20
+ export * from './exchanges/suibets';
20
21
  export * from './router';
21
22
  export * from './feeds';
22
23
  export * from './server/app';
@@ -36,6 +37,7 @@ import { SmarketsExchange } from './exchanges/smarkets';
36
37
  import { PolymarketUSExchange } from './exchanges/polymarket_us';
37
38
  import { HyperliquidExchange } from './exchanges/hyperliquid';
38
39
  import { GeminiTitanExchange } from './exchanges/gemini-titan';
40
+ import { SuiBetsExchange } from './exchanges/suibets';
39
41
  import { Router } from './router';
40
42
  declare const pmxt: {
41
43
  Mock: typeof MockExchange;
@@ -52,6 +54,7 @@ declare const pmxt: {
52
54
  PolymarketUS: typeof PolymarketUSExchange;
53
55
  Hyperliquid: typeof HyperliquidExchange;
54
56
  GeminiTitan: typeof GeminiTitanExchange;
57
+ SuiBets: typeof SuiBetsExchange;
55
58
  Router: typeof Router;
56
59
  };
57
60
  export declare const Mock: typeof MockExchange;
@@ -68,4 +71,5 @@ export declare const Smarkets: typeof SmarketsExchange;
68
71
  export declare const PolymarketUS: typeof PolymarketUSExchange;
69
72
  export declare const Hyperliquid: typeof HyperliquidExchange;
70
73
  export declare const GeminiTitan: typeof GeminiTitanExchange;
74
+ export declare const SuiBets: typeof SuiBetsExchange;
71
75
  export default pmxt;
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.GeminiTitan = exports.Hyperliquid = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Myriad = exports.Baozi = exports.Probable = exports.KalshiDemo = exports.Kalshi = exports.Limitless = exports.Polymarket = exports.Mock = exports.parseOpenApiSpec = void 0;
17
+ exports.SuiBets = exports.GeminiTitan = exports.Hyperliquid = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Myriad = exports.Baozi = exports.Probable = exports.KalshiDemo = exports.Kalshi = exports.Limitless = exports.Polymarket = exports.Mock = exports.parseOpenApiSpec = void 0;
18
18
  __exportStar(require("./BaseExchange"), exports);
19
19
  __exportStar(require("./types"), exports);
20
20
  __exportStar(require("./utils/math"), exports);
@@ -35,6 +35,7 @@ __exportStar(require("./exchanges/smarkets"), exports);
35
35
  __exportStar(require("./exchanges/polymarket_us"), exports);
36
36
  __exportStar(require("./exchanges/hyperliquid"), exports);
37
37
  __exportStar(require("./exchanges/gemini-titan"), exports);
38
+ __exportStar(require("./exchanges/suibets"), exports);
38
39
  __exportStar(require("./router"), exports);
39
40
  __exportStar(require("./feeds"), exports);
40
41
  __exportStar(require("./server/app"), exports);
@@ -54,6 +55,7 @@ const smarkets_1 = require("./exchanges/smarkets");
54
55
  const polymarket_us_1 = require("./exchanges/polymarket_us");
55
56
  const hyperliquid_1 = require("./exchanges/hyperliquid");
56
57
  const gemini_titan_1 = require("./exchanges/gemini-titan");
58
+ const suibets_1 = require("./exchanges/suibets");
57
59
  const router_1 = require("./router");
58
60
  const pmxt = {
59
61
  Mock: mock_1.MockExchange,
@@ -70,6 +72,7 @@ const pmxt = {
70
72
  PolymarketUS: polymarket_us_1.PolymarketUSExchange,
71
73
  Hyperliquid: hyperliquid_1.HyperliquidExchange,
72
74
  GeminiTitan: gemini_titan_1.GeminiTitanExchange,
75
+ SuiBets: suibets_1.SuiBetsExchange,
73
76
  Router: router_1.Router,
74
77
  };
75
78
  exports.Mock = mock_1.MockExchange;
@@ -86,4 +89,5 @@ exports.Smarkets = smarkets_1.SmarketsExchange;
86
89
  exports.PolymarketUS = polymarket_us_1.PolymarketUSExchange;
87
90
  exports.Hyperliquid = hyperliquid_1.HyperliquidExchange;
88
91
  exports.GeminiTitan = gemini_titan_1.GeminiTitanExchange;
92
+ exports.SuiBets = suibets_1.SuiBetsExchange;
89
93
  exports.default = pmxt;