pmxt-core 2.49.11 → 2.50.2

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 (35) 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/probable/api.d.ts +1 -1
  16. package/dist/exchanges/probable/api.js +1 -1
  17. package/dist/exchanges/rain/auth.d.ts +25 -0
  18. package/dist/exchanges/rain/auth.js +72 -0
  19. package/dist/exchanges/rain/errors.d.ts +5 -0
  20. package/dist/exchanges/rain/errors.js +11 -0
  21. package/dist/exchanges/rain/fetcher.d.ts +51 -0
  22. package/dist/exchanges/rain/fetcher.js +183 -0
  23. package/dist/exchanges/rain/index.d.ts +43 -0
  24. package/dist/exchanges/rain/index.js +515 -0
  25. package/dist/exchanges/rain/normalizer.d.ts +33 -0
  26. package/dist/exchanges/rain/normalizer.js +289 -0
  27. package/dist/exchanges/rain/utils.d.ts +20 -0
  28. package/dist/exchanges/rain/utils.js +91 -0
  29. package/dist/exchanges/rain/websocket.d.ts +21 -0
  30. package/dist/exchanges/rain/websocket.js +98 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +5 -1
  33. package/dist/server/exchange-factory.js +15 -0
  34. package/dist/server/openapi.yaml +12 -0
  35. package/package.json +7 -6
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RainNormalizer = void 0;
4
+ const market_utils_1 = require("../../utils/market-utils");
5
+ const metadata_1 = require("../../utils/metadata");
6
+ const utils_1 = require("./utils");
7
+ const PROMOTED_MARKET_KEYS = [
8
+ 'id', 'title', 'status', 'contractAddress', 'totalVolume', 'options',
9
+ 'totalLiquidity', 'startTime', 'endTime', 'baseToken', 'baseTokenDecimals',
10
+ ];
11
+ const PROMOTED_EVENT_KEYS = ['id', 'title', 'options'];
12
+ class RainNormalizer {
13
+ /**
14
+ * One Rain market with N options -> one UnifiedMarket when N<=2 (binary),
15
+ * or one UnifiedEvent with N synthetic binary UnifiedMarkets when N>2.
16
+ * The list-shape returns UnifiedMarket[]; callers wanting event grouping
17
+ * should call normalizeEvent.
18
+ */
19
+ normalizeMarket(raw) {
20
+ if (!raw?.market)
21
+ return null;
22
+ const { details } = raw;
23
+ const m = raw.market;
24
+ const marketId = (m._id ?? m.id);
25
+ if (!marketId)
26
+ return null;
27
+ const decimals = (0, utils_1.resolveDecimals)(details?.baseTokenDecimals ?? m.token?.tokenDecimals, utils_1.USDT_DECIMALS);
28
+ const contractAddress = (details?.contractAddress ?? m.contractAddress);
29
+ const title = (m.question ?? m.title ?? details?.title ?? '');
30
+ const tags = (m.tags ?? []);
31
+ // Outcomes: prefer on-chain (details.options with 1e18 currentPrice);
32
+ // fall back to the list-shape options (percentage 0-100).
33
+ const detailOpts = details?.options ?? [];
34
+ const listOpts = (m.options ?? []);
35
+ const outcomes = detailOpts.length
36
+ ? detailOpts.map((o) => ({
37
+ outcomeId: `rain:${marketId}:${o.choiceIndex}`,
38
+ marketId: `rain:${marketId}`,
39
+ label: o.optionName,
40
+ price: (0, utils_1.priceBigIntToNumber)(o.currentPrice),
41
+ }))
42
+ : listOpts.map((o) => ({
43
+ outcomeId: `rain:${marketId}:${o.choiceIndex}`,
44
+ marketId: `rain:${marketId}`,
45
+ label: o.optionName,
46
+ price: Math.min(1, Math.max(0, Number(o.percentage ?? 0) / 100)),
47
+ }));
48
+ const volume = details?.allFunds != null
49
+ ? (0, utils_1.weiToNumber)(details.allFunds, decimals)
50
+ : Number(m.totalVolumeUSD ?? m.totalVolume ?? 0);
51
+ const liquidity = details?.totalLiquidity != null
52
+ ? (0, utils_1.weiToNumber)(details.totalLiquidity, decimals)
53
+ : Number(m.totalLiquidityUSD ?? m.totalLiquidity ?? 0);
54
+ const endDate = details?.endTime
55
+ ? new Date(Number(details.endTime) * 1000)
56
+ : (m.endDate ? new Date(m.endDate) : undefined);
57
+ const um = {
58
+ marketId: `rain:${marketId}`,
59
+ title,
60
+ description: '',
61
+ outcomes,
62
+ resolutionDate: endDate,
63
+ volume24h: 0,
64
+ volume,
65
+ liquidity,
66
+ url: (0, utils_1.rainMarketUrl)(marketId),
67
+ status: (0, utils_1.mapRainStatus)((m.status ?? details?.status)),
68
+ contractAddress,
69
+ tags,
70
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)((0, utils_1.bigintsToStrings)({ ...(details ?? {}), ...m }), PROMOTED_MARKET_KEYS),
71
+ };
72
+ (0, market_utils_1.addBinaryOutcomes)(um);
73
+ return um;
74
+ }
75
+ /**
76
+ * Rain market -> UnifiedEvent. Multi-option markets are expanded into
77
+ * one synthetic binary market per option (matches Myriad/Polymarket pattern
78
+ * so the matching engine can find cross-venue identity matches).
79
+ */
80
+ normalizeEvent(raw) {
81
+ if (!raw?.market)
82
+ return null;
83
+ const { details } = raw;
84
+ const m = raw.market;
85
+ const eventId = (m._id ?? m.id);
86
+ if (!eventId)
87
+ return null;
88
+ const eventTitle = (m.question ?? m.title ?? details?.title ?? '');
89
+ const decimals = (0, utils_1.resolveDecimals)(details?.baseTokenDecimals ?? m.token?.tokenDecimals, utils_1.USDT_DECIMALS);
90
+ const options = details?.options?.length
91
+ ? details.options.map((o) => ({
92
+ choiceIndex: o.choiceIndex,
93
+ optionName: o.optionName,
94
+ price: (0, utils_1.priceBigIntToNumber)(o.currentPrice),
95
+ fundsBase: (0, utils_1.weiToNumber)(o.totalFunds, decimals),
96
+ }))
97
+ : (m.options ?? []).map((o) => ({
98
+ choiceIndex: o.choiceIndex,
99
+ optionName: o.optionName,
100
+ price: Math.min(1, Math.max(0, Number(o.percentage ?? 0) / 100)),
101
+ fundsBase: null,
102
+ }));
103
+ const markets = [];
104
+ const endDate = details?.endTime
105
+ ? new Date(Number(details.endTime) * 1000)
106
+ : (m.endDate ? new Date(m.endDate) : undefined);
107
+ const contractAddress = (details?.contractAddress ?? m.contractAddress);
108
+ const status = (0, utils_1.mapRainStatus)((m.status ?? details?.status));
109
+ const tags = (m.tags ?? []);
110
+ if (options.length <= 2) {
111
+ const um = this.normalizeMarket(raw);
112
+ if (um)
113
+ markets.push(um);
114
+ }
115
+ else {
116
+ for (const opt of options) {
117
+ const yesPrice = opt.price;
118
+ const syntheticOutcomes = [
119
+ {
120
+ outcomeId: `rain:${eventId}:${opt.choiceIndex}`,
121
+ marketId: `rain:${eventId}:${opt.choiceIndex}`,
122
+ label: opt.optionName,
123
+ price: yesPrice,
124
+ },
125
+ {
126
+ outcomeId: `rain:${eventId}:${opt.choiceIndex}:no`,
127
+ marketId: `rain:${eventId}:${opt.choiceIndex}`,
128
+ label: `Not ${opt.optionName}`,
129
+ price: Math.max(0, 1 - yesPrice),
130
+ },
131
+ ];
132
+ const um = {
133
+ marketId: `rain:${eventId}:${opt.choiceIndex}`,
134
+ eventId: `rain:${eventId}`,
135
+ title: `${eventTitle} - ${opt.optionName}`,
136
+ description: '',
137
+ outcomes: syntheticOutcomes,
138
+ resolutionDate: endDate,
139
+ volume24h: 0,
140
+ volume: opt.fundsBase ?? 0,
141
+ liquidity: opt.fundsBase ?? 0,
142
+ url: (0, utils_1.rainMarketUrl)(eventId),
143
+ status,
144
+ contractAddress,
145
+ tags,
146
+ };
147
+ (0, market_utils_1.addBinaryOutcomes)(um);
148
+ markets.push(um);
149
+ }
150
+ }
151
+ return {
152
+ id: `rain:${eventId}`,
153
+ title: eventTitle,
154
+ description: '',
155
+ slug: eventId,
156
+ markets,
157
+ volume24h: 0,
158
+ volume: markets.reduce((s, mk) => s + (mk.volume ?? 0), 0),
159
+ url: (0, utils_1.rainMarketUrl)(eventId),
160
+ tags,
161
+ sourceMetadata: (0, metadata_1.buildSourceMetadata)({ ...(details ?? {}), ...m }, PROMOTED_EVENT_KEYS),
162
+ };
163
+ }
164
+ /**
165
+ * Single-level emulated book using AMM spot. The plan was to use
166
+ * getMarketLiquidity.firstBuyOrderPrice / firstSellOrderPrice for real
167
+ * top-of-book; left for v2 because it needs an extra on-chain read per
168
+ * call. ponytail: 1-level AMM book is what Myriad ships and matches the
169
+ * router's expectations.
170
+ */
171
+ normalizeOrderBook(raw, outcomeId) {
172
+ const parts = outcomeId.split(':');
173
+ const choiceIndex = parts.length >= 3 ? Number(parts[2]) : NaN;
174
+ const isSyntheticNo = parts.length >= 4 && parts[3] === 'no';
175
+ const options = raw.details?.options ?? [];
176
+ if (!options.length || isNaN(choiceIndex)) {
177
+ return { bids: [], asks: [], timestamp: Date.now() };
178
+ }
179
+ const option = options.find((o) => o.choiceIndex === choiceIndex);
180
+ if (!option)
181
+ return { bids: [], asks: [], timestamp: Date.now() };
182
+ let price = (0, utils_1.priceBigIntToNumber)(option.currentPrice);
183
+ if (isSyntheticNo)
184
+ price = Math.max(0, 1 - price);
185
+ const decimals = (0, utils_1.resolveDecimals)(raw.details?.baseTokenDecimals, utils_1.USDT_DECIMALS);
186
+ const liquidity = raw.details?.totalLiquidity ? (0, utils_1.weiToNumber)(raw.details.totalLiquidity, decimals) : 0;
187
+ const size = liquidity > 0 ? liquidity : 1;
188
+ return {
189
+ bids: [{ price, size }],
190
+ asks: [{ price, size }],
191
+ timestamp: Date.now(),
192
+ };
193
+ }
194
+ normalizeOHLCV(raw, limit) {
195
+ if (!raw?.candles?.length)
196
+ return [];
197
+ const candles = raw.candles.map((c) => ({
198
+ timestamp: Number(c.timestamp) * 1000,
199
+ open: (0, utils_1.priceBigIntToNumber)(c.open),
200
+ high: (0, utils_1.priceBigIntToNumber)(c.high),
201
+ low: (0, utils_1.priceBigIntToNumber)(c.low),
202
+ close: (0, utils_1.priceBigIntToNumber)(c.close),
203
+ volume: (0, utils_1.weiToNumber)(c.volume),
204
+ }));
205
+ return limit && candles.length > limit ? candles.slice(-limit) : candles;
206
+ }
207
+ /** Map Rain SDK price-history intervals onto PMXT CandleInterval strings. */
208
+ static mapInterval(resolution) {
209
+ switch (resolution) {
210
+ case '1m': return '1m';
211
+ case '5m': return '5m';
212
+ case '15m': return '15m';
213
+ case '1h': return '1h';
214
+ case '4h': return '4h';
215
+ case '1d': return '1d';
216
+ case '1w': return '1w';
217
+ default: return '1h';
218
+ }
219
+ }
220
+ normalizeMarketTrades(raw) {
221
+ if (!raw?.transactions?.length)
222
+ return [];
223
+ return raw.transactions
224
+ .filter((t) => t.type === 'buy' || t.type === 'limit_buy_filled' || t.type === 'limit_sell_filled')
225
+ .map((t, i) => ({
226
+ id: t.transactionHash ?? `${t.blockNumber}-${i}`,
227
+ timestamp: Number(t.timestamp) * 1000,
228
+ price: (0, utils_1.priceBigIntToNumber)(t.price),
229
+ amount: t.optionAmount ? (0, utils_1.weiToNumber)(t.optionAmount) : 0,
230
+ side: t.type === 'buy' || t.type === 'limit_buy_filled' ? 'buy' : 'sell',
231
+ }));
232
+ }
233
+ normalizeUserTrades(raw) {
234
+ if (!raw?.transactions?.length)
235
+ return [];
236
+ return raw.transactions
237
+ .filter((t) => t.type === 'buy' || t.type === 'limit_buy_filled' || t.type === 'limit_sell_filled')
238
+ .map((t, i) => ({
239
+ id: t.transactionHash ?? `${t.blockNumber}-${i}`,
240
+ timestamp: Number(t.timestamp) * 1000,
241
+ price: (0, utils_1.priceBigIntToNumber)(t.price),
242
+ amount: t.optionAmount ? (0, utils_1.weiToNumber)(t.optionAmount) : 0,
243
+ side: t.type === 'buy' || t.type === 'limit_buy_filled' ? 'buy' : 'sell',
244
+ }));
245
+ }
246
+ normalizePositions(raw) {
247
+ const out = [];
248
+ for (const m of raw?.markets ?? []) {
249
+ for (const opt of m.options ?? []) {
250
+ const shares = (0, utils_1.weiToNumber)(opt.shares);
251
+ if (shares === 0)
252
+ continue;
253
+ out.push({
254
+ marketId: `rain:${m.marketId}`,
255
+ outcomeId: `rain:${m.marketId}:${opt.choiceIndex}`,
256
+ outcomeLabel: opt.optionName,
257
+ size: shares,
258
+ entryPrice: undefined,
259
+ currentPrice: (0, utils_1.priceBigIntToNumber)(opt.currentPrice),
260
+ currentValue: shares * (0, utils_1.priceBigIntToNumber)(opt.currentPrice),
261
+ });
262
+ }
263
+ }
264
+ return out;
265
+ }
266
+ normalizeBalance(raw) {
267
+ if (!raw?.tokenBalances?.length)
268
+ return [];
269
+ return raw.tokenBalances.map((b) => ({
270
+ currency: b.symbol,
271
+ total: (0, utils_1.weiToNumber)(b.balance, b.decimals),
272
+ available: (0, utils_1.weiToNumber)(b.balance, b.decimals),
273
+ locked: 0,
274
+ }));
275
+ }
276
+ normalizeMarketDetails(details) {
277
+ return this.normalizeMarket({
278
+ market: {
279
+ id: details.id,
280
+ title: details.title,
281
+ totalVolume: '0',
282
+ status: details.status,
283
+ contractAddress: details.contractAddress,
284
+ },
285
+ details,
286
+ });
287
+ }
288
+ }
289
+ exports.RainNormalizer = RainNormalizer;
@@ -0,0 +1,20 @@
1
+ export declare const RAIN_DEFAULT_ENVIRONMENT: "production";
2
+ export declare const RAIN_BASE_URL = "https://rain.one";
3
+ export declare const ARBITRUM_USDT = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9";
4
+ export declare const ARBITRUM_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
5
+ export declare const USDT_DECIMALS = 6;
6
+ export declare function priceBigIntToNumber(price: bigint | string | undefined | null): number;
7
+ export declare function weiToNumber(wei: bigint | string | undefined | null, decimals?: number): number;
8
+ /**
9
+ * Rain SDK returns `baseTokenDecimals` as the scale factor (e.g. 1000000n for
10
+ * a 6-decimal token), not the decimal count. Detect and normalize to a count.
11
+ */
12
+ export declare function resolveDecimals(raw: bigint | number | string | undefined | null, fallback?: number): number;
13
+ export declare function mapRainStatus(status?: string): string | undefined;
14
+ export declare function rainMarketUrl(marketId: string): string;
15
+ /**
16
+ * Recursively convert bigints to strings so the value is JSON-serialisable.
17
+ * Rain's SDK returns bigints all over (timestamps, fund totals, prices), and
18
+ * the PMXT server JSON.stringifies sourceMetadata before returning over HTTP.
19
+ */
20
+ export declare function bigintsToStrings<T>(value: T): T;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.USDT_DECIMALS = exports.ARBITRUM_USDC = exports.ARBITRUM_USDT = exports.RAIN_BASE_URL = exports.RAIN_DEFAULT_ENVIRONMENT = void 0;
4
+ exports.priceBigIntToNumber = priceBigIntToNumber;
5
+ exports.weiToNumber = weiToNumber;
6
+ exports.resolveDecimals = resolveDecimals;
7
+ exports.mapRainStatus = mapRainStatus;
8
+ exports.rainMarketUrl = rainMarketUrl;
9
+ exports.bigintsToStrings = bigintsToStrings;
10
+ exports.RAIN_DEFAULT_ENVIRONMENT = 'production';
11
+ exports.RAIN_BASE_URL = 'https://rain.one';
12
+ exports.ARBITRUM_USDT = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9';
13
+ exports.ARBITRUM_USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
14
+ exports.USDT_DECIMALS = 6;
15
+ const PRICE_SCALE = 10n ** 18n;
16
+ const PRECISION = 1_000_000;
17
+ function priceBigIntToNumber(price) {
18
+ if (price == null)
19
+ return 0;
20
+ const p = typeof price === 'string' ? BigInt(price) : price;
21
+ return Number((p * BigInt(PRECISION)) / PRICE_SCALE) / PRECISION;
22
+ }
23
+ function weiToNumber(wei, decimals = exports.USDT_DECIMALS) {
24
+ if (wei == null)
25
+ return 0;
26
+ const w = typeof wei === 'string' ? BigInt(wei) : wei;
27
+ const scale = 10n ** BigInt(decimals);
28
+ return Number((w * BigInt(PRECISION)) / scale) / PRECISION;
29
+ }
30
+ /**
31
+ * Rain SDK returns `baseTokenDecimals` as the scale factor (e.g. 1000000n for
32
+ * a 6-decimal token), not the decimal count. Detect and normalize to a count.
33
+ */
34
+ function resolveDecimals(raw, fallback = exports.USDT_DECIMALS) {
35
+ if (raw == null)
36
+ return fallback;
37
+ const n = typeof raw === 'bigint' ? Number(raw) : Number(raw);
38
+ if (!Number.isFinite(n) || n <= 0)
39
+ return fallback;
40
+ // Heuristic: values <=36 are decimal counts; anything larger is a scale.
41
+ if (n <= 36)
42
+ return Math.round(n);
43
+ const log = Math.log10(n);
44
+ return Math.round(log);
45
+ }
46
+ function mapRainStatus(status) {
47
+ if (!status)
48
+ return undefined;
49
+ switch (status) {
50
+ case 'Live':
51
+ case 'New':
52
+ case 'ClosingSoon':
53
+ case 'Trading':
54
+ return 'active';
55
+ case 'WaitingForResult':
56
+ case 'InReview':
57
+ case 'InEvaluation':
58
+ return 'resolving';
59
+ case 'UnderDispute':
60
+ case 'UnderAppeal':
61
+ return 'disputed';
62
+ case 'Closed':
63
+ return 'closed';
64
+ default:
65
+ return status.toLowerCase();
66
+ }
67
+ }
68
+ function rainMarketUrl(marketId) {
69
+ return `https://rain.one/markets/${marketId}`;
70
+ }
71
+ /**
72
+ * Recursively convert bigints to strings so the value is JSON-serialisable.
73
+ * Rain's SDK returns bigints all over (timestamps, fund totals, prices), and
74
+ * the PMXT server JSON.stringifies sourceMetadata before returning over HTTP.
75
+ */
76
+ function bigintsToStrings(value) {
77
+ if (typeof value === 'bigint') {
78
+ return value.toString();
79
+ }
80
+ if (Array.isArray(value)) {
81
+ return value.map(bigintsToStrings);
82
+ }
83
+ if (value && typeof value === 'object') {
84
+ const out = {};
85
+ for (const [k, v] of Object.entries(value)) {
86
+ out[k] = bigintsToStrings(v);
87
+ }
88
+ return out;
89
+ }
90
+ return value;
91
+ }
@@ -0,0 +1,21 @@
1
+ import { OrderBook, Trade } from '../../types';
2
+ export interface RainWebSocketConfig {
3
+ wsRpcUrl: string;
4
+ environment?: 'development' | 'stage' | 'production';
5
+ }
6
+ export declare class RainWebSocket {
7
+ private readonly config;
8
+ private client?;
9
+ private readonly subs;
10
+ private readonly normalizer;
11
+ constructor(config: RainWebSocketConfig);
12
+ private getClient;
13
+ /**
14
+ * Resolves to the next OrderBook snapshot after a price-affecting event.
15
+ * Stays subscribed for the connection lifetime; same caller can re-await
16
+ * to get subsequent snapshots.
17
+ */
18
+ watchOrderBook(marketAddress: string, outcomeId: string): Promise<OrderBook>;
19
+ watchTrades(marketAddress: string): Promise<Trade[]>;
20
+ close(): Promise<void>;
21
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ // Thin wrapper around Rain SDK's subscribePriceUpdates. Provides the
3
+ // CCXT-Pro-style "next snapshot" promise pattern PMXT expects.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.RainWebSocket = void 0;
6
+ const normalizer_1 = require("./normalizer");
7
+ const logger_1 = require("../../utils/logger");
8
+ // See note in fetcher.ts on why we Function-wrap the import().
9
+ const esmImportRainSdk = new Function('return import("@buidlrrr/rain-sdk")');
10
+ class RainWebSocket {
11
+ config;
12
+ client;
13
+ subs = new Map();
14
+ normalizer = new normalizer_1.RainNormalizer();
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+ async getClient() {
19
+ if (!this.client) {
20
+ const sdk = await esmImportRainSdk();
21
+ this.client = new sdk.Rain({
22
+ environment: this.config.environment ?? 'production',
23
+ wsRpcUrl: this.config.wsRpcUrl,
24
+ });
25
+ }
26
+ return this.client;
27
+ }
28
+ /**
29
+ * Resolves to the next OrderBook snapshot after a price-affecting event.
30
+ * Stays subscribed for the connection lifetime; same caller can re-await
31
+ * to get subsequent snapshots.
32
+ */
33
+ async watchOrderBook(marketAddress, outcomeId) {
34
+ const client = await this.getClient();
35
+ return new Promise((resolve, reject) => {
36
+ const key = `book:${marketAddress}:${outcomeId}`;
37
+ const existing = this.subs.get(key);
38
+ if (existing)
39
+ existing();
40
+ const unsubscribe = client.subscribePriceUpdates({
41
+ marketAddress: marketAddress,
42
+ onPriceUpdate: () => {
43
+ resolve(this.normalizer.normalizeOrderBook({ market: { contractAddress: marketAddress }, details: undefined }, outcomeId));
44
+ },
45
+ onError: (err) => {
46
+ logger_1.logger.warn('RainWebSocket: price update error', { err: String(err) });
47
+ reject(err);
48
+ },
49
+ });
50
+ this.subs.set(key, unsubscribe);
51
+ });
52
+ }
53
+ async watchTrades(marketAddress) {
54
+ const client = await this.getClient();
55
+ return new Promise((resolve, reject) => {
56
+ const key = `trades:${marketAddress}`;
57
+ const existing = this.subs.get(key);
58
+ if (existing)
59
+ existing();
60
+ const unsubscribe = client.subscribeToMarketEvents({
61
+ marketAddress: marketAddress,
62
+ eventNames: ['EnterOption', 'ExecuteBuyOrder', 'ExecuteSellOrder'],
63
+ onEvent: (event) => {
64
+ const isBuy = event.eventName === 'EnterOption' || event.eventName === 'ExecuteBuyOrder';
65
+ resolve([{
66
+ id: event.transactionHash,
67
+ timestamp: Number(event.blockNumber) * 1000,
68
+ price: 0,
69
+ amount: 0,
70
+ side: isBuy ? 'buy' : 'sell',
71
+ }]);
72
+ },
73
+ onError: (err) => {
74
+ logger_1.logger.warn('RainWebSocket: trade subscription error', { err: String(err) });
75
+ reject(err);
76
+ },
77
+ });
78
+ this.subs.set(key, unsubscribe);
79
+ });
80
+ }
81
+ async close() {
82
+ for (const unsub of this.subs.values()) {
83
+ try {
84
+ unsub();
85
+ }
86
+ catch { /* ignore */ }
87
+ }
88
+ this.subs.clear();
89
+ if (this.client && typeof this.client.destroyWebSocket === 'function') {
90
+ try {
91
+ await this.client.destroyWebSocket();
92
+ }
93
+ catch { /* ignore */ }
94
+ }
95
+ this.client = undefined;
96
+ }
97
+ }
98
+ exports.RainWebSocket = RainWebSocket;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from './exchanges/polymarket_us';
18
18
  export * from './exchanges/hyperliquid';
19
19
  export * from './exchanges/gemini-titan';
20
20
  export * from './exchanges/suibets';
21
+ export * from './exchanges/rain';
21
22
  export * from './router';
22
23
  export * from './feeds';
23
24
  export * from './server/app';
@@ -38,6 +39,7 @@ import { PolymarketUSExchange } from './exchanges/polymarket_us';
38
39
  import { HyperliquidExchange } from './exchanges/hyperliquid';
39
40
  import { GeminiTitanExchange } from './exchanges/gemini-titan';
40
41
  import { SuiBetsExchange } from './exchanges/suibets';
42
+ import { RainExchange } from './exchanges/rain';
41
43
  import { Router } from './router';
42
44
  declare const pmxt: {
43
45
  Mock: typeof MockExchange;
@@ -55,6 +57,7 @@ declare const pmxt: {
55
57
  Hyperliquid: typeof HyperliquidExchange;
56
58
  GeminiTitan: typeof GeminiTitanExchange;
57
59
  SuiBets: typeof SuiBetsExchange;
60
+ Rain: typeof RainExchange;
58
61
  Router: typeof Router;
59
62
  };
60
63
  export declare const Mock: typeof MockExchange;
@@ -72,4 +75,5 @@ export declare const PolymarketUS: typeof PolymarketUSExchange;
72
75
  export declare const Hyperliquid: typeof HyperliquidExchange;
73
76
  export declare const GeminiTitan: typeof GeminiTitanExchange;
74
77
  export declare const SuiBets: typeof SuiBetsExchange;
78
+ export declare const Rain: typeof RainExchange;
75
79
  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.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;
17
+ exports.Rain = 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);
@@ -36,6 +36,7 @@ __exportStar(require("./exchanges/polymarket_us"), exports);
36
36
  __exportStar(require("./exchanges/hyperliquid"), exports);
37
37
  __exportStar(require("./exchanges/gemini-titan"), exports);
38
38
  __exportStar(require("./exchanges/suibets"), exports);
39
+ __exportStar(require("./exchanges/rain"), exports);
39
40
  __exportStar(require("./router"), exports);
40
41
  __exportStar(require("./feeds"), exports);
41
42
  __exportStar(require("./server/app"), exports);
@@ -56,6 +57,7 @@ const polymarket_us_1 = require("./exchanges/polymarket_us");
56
57
  const hyperliquid_1 = require("./exchanges/hyperliquid");
57
58
  const gemini_titan_1 = require("./exchanges/gemini-titan");
58
59
  const suibets_1 = require("./exchanges/suibets");
60
+ const rain_1 = require("./exchanges/rain");
59
61
  const router_1 = require("./router");
60
62
  const pmxt = {
61
63
  Mock: mock_1.MockExchange,
@@ -73,6 +75,7 @@ const pmxt = {
73
75
  Hyperliquid: hyperliquid_1.HyperliquidExchange,
74
76
  GeminiTitan: gemini_titan_1.GeminiTitanExchange,
75
77
  SuiBets: suibets_1.SuiBetsExchange,
78
+ Rain: rain_1.RainExchange,
76
79
  Router: router_1.Router,
77
80
  };
78
81
  exports.Mock = mock_1.MockExchange;
@@ -90,4 +93,5 @@ exports.PolymarketUS = polymarket_us_1.PolymarketUSExchange;
90
93
  exports.Hyperliquid = hyperliquid_1.HyperliquidExchange;
91
94
  exports.GeminiTitan = gemini_titan_1.GeminiTitanExchange;
92
95
  exports.SuiBets = suibets_1.SuiBetsExchange;
96
+ exports.Rain = rain_1.RainExchange;
93
97
  exports.default = pmxt;
@@ -15,6 +15,7 @@ const polymarket_us_1 = require("../exchanges/polymarket_us");
15
15
  const hyperliquid_1 = require("../exchanges/hyperliquid");
16
16
  const gemini_titan_1 = require("../exchanges/gemini-titan");
17
17
  const suibets_1 = require("../exchanges/suibets");
18
+ const rain_1 = require("../exchanges/rain");
18
19
  const mock_1 = require("../exchanges/mock");
19
20
  const router_1 = require("../router");
20
21
  function createExchange(name, credentials, bearerToken) {
@@ -109,6 +110,20 @@ function createExchange(name, credentials, bearerToken) {
109
110
  walletAddress: credentials?.walletAddress || process.env.SUIBETS_WALLET_ADDRESS,
110
111
  baseUrl: credentials?.baseUrl || process.env.SUIBETS_BASE_URL,
111
112
  });
113
+ case "rain":
114
+ return new rain_1.RainExchange({
115
+ privateKey: credentials?.privateKey || process.env.RAIN_PRIVATE_KEY,
116
+ walletAddress: credentials?.walletAddress ||
117
+ process.env.RAIN_WALLET_ADDRESS,
118
+ subgraphUrl: credentials?.subgraphUrl ||
119
+ process.env.RAIN_SUBGRAPH_URL,
120
+ subgraphApiKey: credentials?.subgraphApiKey ||
121
+ process.env.RAIN_SUBGRAPH_API_KEY,
122
+ wsRpcUrl: credentials?.wsRpcUrl ||
123
+ process.env.RAIN_WS_RPC_URL,
124
+ environment: (credentials?.environment ||
125
+ process.env.RAIN_ENVIRONMENT),
126
+ });
112
127
  case "mock":
113
128
  return new mock_1.MockExchange();
114
129
  case "router":
@@ -2587,6 +2587,7 @@ components:
2587
2587
  - gemini-titan
2588
2588
  - hyperliquid
2589
2589
  - suibets
2590
+ - rain
2590
2591
  - mock
2591
2592
  - router
2592
2593
  required: true
@@ -4471,6 +4472,17 @@ x-sdk-constructors:
4471
4472
  tsName: pmxtApiKey
4472
4473
  type: string
4473
4474
  description: PMXT API key for hosted access
4475
+ rain:
4476
+ className: Rain
4477
+ params:
4478
+ - name: pmxt_api_key
4479
+ tsName: pmxtApiKey
4480
+ type: string
4481
+ description: PMXT API key for hosted access
4482
+ - name: private_key
4483
+ tsName: privateKey
4484
+ type: string
4485
+ description: Private key for authentication
4474
4486
  mock:
4475
4487
  className: Mock
4476
4488
  params: