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.
- 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/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/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/rain/auth.d.ts +25 -0
- package/dist/exchanges/rain/auth.js +72 -0
- package/dist/exchanges/rain/errors.d.ts +5 -0
- package/dist/exchanges/rain/errors.js +11 -0
- package/dist/exchanges/rain/fetcher.d.ts +51 -0
- package/dist/exchanges/rain/fetcher.js +183 -0
- package/dist/exchanges/rain/index.d.ts +43 -0
- package/dist/exchanges/rain/index.js +515 -0
- package/dist/exchanges/rain/normalizer.d.ts +33 -0
- package/dist/exchanges/rain/normalizer.js +289 -0
- package/dist/exchanges/rain/utils.d.ts +20 -0
- package/dist/exchanges/rain/utils.js +91 -0
- package/dist/exchanges/rain/websocket.d.ts +21 -0
- package/dist/exchanges/rain/websocket.js +98 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -1
- package/dist/server/exchange-factory.js +15 -0
- package/dist/server/openapi.yaml +12 -0
- 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":
|
package/dist/server/openapi.yaml
CHANGED
|
@@ -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:
|