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,515 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RainExchange = void 0;
4
+ const BaseExchange_1 = require("../../BaseExchange");
5
+ const errors_1 = require("../../errors");
6
+ const auth_1 = require("./auth");
7
+ const fetcher_1 = require("./fetcher");
8
+ const normalizer_1 = require("./normalizer");
9
+ const websocket_1 = require("./websocket");
10
+ const errors_2 = require("./errors");
11
+ const utils_1 = require("./utils");
12
+ const viem_1 = require("viem");
13
+ const ARBITRUM_CHAIN_ID = 42161;
14
+ const PRICE_SCALE = 10n ** 18n;
15
+ const MAX_UINT256 = (1n << 256n) - 1n;
16
+ const ERC20_ABI = (0, viem_1.parseAbi)([
17
+ 'function allowance(address owner, address spender) view returns (uint256)',
18
+ ]);
19
+ function encodeOrderId(ref) {
20
+ return `rain:${ref.marketContract}:${ref.side}:${ref.option}:${ref.pricePerShare1e18}:${ref.rainOrderId}:${ref.txHash ?? ''}`;
21
+ }
22
+ function decodeOrderId(orderId) {
23
+ const parts = orderId.split(':');
24
+ if (parts.length < 7 || parts[0] !== 'rain') {
25
+ throw new errors_1.BadRequest(`Invalid Rain order id: "${orderId}". Expected "rain:{contract}:{side}:{option}:{price1e18}:{rainOrderId}:{txHash}".`, 'Rain');
26
+ }
27
+ return {
28
+ marketContract: parts[1],
29
+ side: parts[2],
30
+ option: Number(parts[3]),
31
+ pricePerShare1e18: parts[4],
32
+ rainOrderId: parts[5],
33
+ txHash: parts[6] || undefined,
34
+ };
35
+ }
36
+ class RainExchange extends BaseExchange_1.PredictionMarketExchange {
37
+ capabilityOverrides = {
38
+ fetchSeries: false,
39
+ fetchOrderBook: 'emulated',
40
+ watchOrderBook: 'emulated',
41
+ watchTrades: 'emulated',
42
+ // Trading is on-chain via Rain SDK + viem. open/closed orders + fetchOrder
43
+ // need subgraph; without it, they throw at call-time with a clear message.
44
+ fetchOpenOrders: 'emulated',
45
+ fetchClosedOrders: 'emulated',
46
+ fetchOrder: 'emulated',
47
+ };
48
+ auth;
49
+ fetcher;
50
+ normalizer;
51
+ ws;
52
+ constructor(credentials) {
53
+ super(credentials);
54
+ this.rateLimit = 250;
55
+ this.auth = new auth_1.RainAuth(credentials ?? {});
56
+ this.fetcher = new fetcher_1.RainFetcher({
57
+ environment: credentials?.environment,
58
+ subgraphUrl: credentials?.subgraphUrl,
59
+ subgraphApiKey: credentials?.subgraphApiKey,
60
+ wsRpcUrl: credentials?.wsRpcUrl,
61
+ });
62
+ this.normalizer = new normalizer_1.RainNormalizer();
63
+ }
64
+ get name() {
65
+ return 'Rain';
66
+ }
67
+ // ------------------------------------------------------------------------
68
+ // Market data
69
+ // ------------------------------------------------------------------------
70
+ async fetchMarketsImpl(params) {
71
+ const limit = params?.limit ?? 25;
72
+ const status = (params?.status === 'closed' ? 'Closed' : params?.status === 'all' ? undefined : 'Live');
73
+ const sortBy = params?.sort === 'volume' ? 'Volumn' : params?.sort === 'newest' ? 'latest' : 'Liquidity';
74
+ const rawMarkets = await this.fetcher.fetchRawMarkets({ limit, status, sortBy });
75
+ return rawMarkets
76
+ .map((raw) => this.normalizer.normalizeMarket(raw))
77
+ .filter((m) => m !== null);
78
+ }
79
+ async fetchEventsImpl(params) {
80
+ if (params.series !== undefined)
81
+ return []; // Rain has no series concept
82
+ const limit = params.limit ?? 25;
83
+ const rawMarkets = await this.fetcher.fetchRawMarkets({
84
+ limit,
85
+ status: params.status === 'closed' ? 'Closed' : 'Live',
86
+ });
87
+ return rawMarkets
88
+ .map((raw) => this.normalizer.normalizeEvent(raw))
89
+ .filter((e) => e !== null);
90
+ }
91
+ async fetchOHLCV(outcomeId, params) {
92
+ const parts = outcomeId.split(':');
93
+ if (parts.length < 3 || parts[0] !== 'rain') {
94
+ throw new Error(`Invalid Rain outcomeId format: "${outcomeId}". Expected "rain:{marketId}:{choiceIndex}".`);
95
+ }
96
+ const marketId = parts[1];
97
+ const choiceIndex = Number(parts[2]);
98
+ const interval = normalizer_1.RainNormalizer.mapInterval(params.resolution);
99
+ const raw = await this.fetcher.fetchRawOHLCV(marketId, choiceIndex, interval, params.limit);
100
+ return this.normalizer.normalizeOHLCV(raw, params.limit);
101
+ }
102
+ async fetchOrderBook(outcomeId, _limit, _params) {
103
+ const resolved = await this.resolveOutcomeAlias(outcomeId, _params);
104
+ outcomeId = resolved.outcomeId;
105
+ const parts = outcomeId.split(':');
106
+ if (parts.length < 3) {
107
+ throw new Error(`Invalid Rain outcomeId format: "${outcomeId}". Expected "rain:{marketId}:{choiceIndex}".`);
108
+ }
109
+ const raw = await this.fetcher.fetchRawMarket(parts[1]);
110
+ if (!raw)
111
+ return { bids: [], asks: [], timestamp: Date.now() };
112
+ return this.normalizer.normalizeOrderBook(raw, outcomeId);
113
+ }
114
+ async fetchTrades(outcomeId, params) {
115
+ const parts = outcomeId.split(':');
116
+ if (parts.length < 2) {
117
+ throw new Error(`Invalid Rain id format: "${outcomeId}". Expected "rain:{marketId}".`);
118
+ }
119
+ const market = await this.fetcher.fetchRawMarket(parts[1]);
120
+ const contract = market?.details?.contractAddress;
121
+ if (!contract)
122
+ return [];
123
+ const raw = await this.fetcher.fetchRawMarketTrades(contract, params.limit);
124
+ return this.normalizer.normalizeMarketTrades(raw);
125
+ }
126
+ async fetchMyTrades(params) {
127
+ const wallet = this.auth.requireWalletAddress('fetchMyTrades');
128
+ const marketParts = params?.marketId ? params.marketId.split(':') : [];
129
+ const marketId = marketParts.length >= 2 ? marketParts[1] : undefined;
130
+ let marketAddress;
131
+ if (marketId) {
132
+ const m = await this.fetcher.fetchRawMarket(marketId);
133
+ marketAddress = m?.details?.contractAddress;
134
+ }
135
+ const raw = await this.fetcher.fetchRawUserTrades(wallet, marketAddress, params?.limit);
136
+ return this.normalizer.normalizeUserTrades(raw);
137
+ }
138
+ async fetchPositions(address) {
139
+ const wallet = address ?? this.auth.requireWalletAddress('fetchPositions');
140
+ const raw = await this.fetcher.fetchRawPositions(wallet);
141
+ return this.normalizer.normalizePositions(raw);
142
+ }
143
+ async fetchBalance(address) {
144
+ const wallet = address ?? this.auth.requireWalletAddress('fetchBalance');
145
+ const raw = await this.fetcher.fetchRawBalance(wallet, [utils_1.ARBITRUM_USDT, utils_1.ARBITRUM_USDC]);
146
+ return this.normalizer.normalizeBalance(raw);
147
+ }
148
+ // ------------------------------------------------------------------------
149
+ // Trading -- on-chain via Rain SDK + viem.
150
+ // ------------------------------------------------------------------------
151
+ async buildOrder(params) {
152
+ try {
153
+ const { marketContract, baseToken, baseDecimals } = await this.resolveContractFor(params);
154
+ const choice = this.parseChoiceIndex(params.outcomeId);
155
+ const sdk = await this.fetcher.sdkClient();
156
+ let rawTx;
157
+ let pricePerShare1e18 = '0';
158
+ if (params.side === 'buy' && params.type === 'market') {
159
+ rawTx = sdk.buildBuyOptionRawTx({
160
+ marketContractAddress: marketContract,
161
+ selectedOption: BigInt(choice),
162
+ buyAmountInWei: this.toBaseWei(params.amount, baseDecimals),
163
+ });
164
+ }
165
+ else if (params.side === 'buy' && params.type === 'limit') {
166
+ if (params.price == null) {
167
+ throw new errors_1.BadRequest('Limit buy requires a price (0 < price < 1).', 'Rain');
168
+ }
169
+ const price1e18 = this.toPrice1e18(params.price);
170
+ pricePerShare1e18 = price1e18.toString();
171
+ rawTx = sdk.buildLimitBuyOptionTx({
172
+ marketContractAddress: marketContract,
173
+ selectedOption: choice,
174
+ pricePerShare: price1e18,
175
+ buyAmountInWei: this.toBaseWei(params.amount, baseDecimals),
176
+ tokenDecimals: baseDecimals,
177
+ });
178
+ }
179
+ else if (params.side === 'sell' && params.type === 'limit') {
180
+ if (params.price == null) {
181
+ throw new errors_1.BadRequest('Limit sell requires a price (0 < price < 1).', 'Rain');
182
+ }
183
+ pricePerShare1e18 = this.toPrice1e18(params.price).toString();
184
+ rawTx = sdk.buildSellOptionTx({
185
+ marketContractAddress: marketContract,
186
+ selectedOption: choice,
187
+ pricePerShare: params.price,
188
+ shares: this.toBaseWei(params.amount, baseDecimals),
189
+ tokenDecimals: baseDecimals,
190
+ });
191
+ }
192
+ else {
193
+ // params.side === 'sell' && params.type === 'market'
194
+ throw new errors_1.NotSupported('Rain has no AMM market-sell. Sells go through the orderbook as limit orders — pass type:"limit" with a price.', 'Rain');
195
+ }
196
+ const ref = {
197
+ marketContract,
198
+ side: params.side,
199
+ option: choice,
200
+ pricePerShare1e18,
201
+ rainOrderId: '0',
202
+ };
203
+ return {
204
+ exchange: 'Rain',
205
+ params,
206
+ tx: {
207
+ to: rawTx.to,
208
+ data: rawTx.data,
209
+ value: (rawTx.value ?? 0n).toString(),
210
+ chainId: ARBITRUM_CHAIN_ID,
211
+ },
212
+ raw: { rawTx, ref, baseToken, baseDecimals },
213
+ };
214
+ }
215
+ catch (error) {
216
+ throw errors_2.rainErrorMapper.mapError(error);
217
+ }
218
+ }
219
+ async submitOrder(built) {
220
+ try {
221
+ const wallet = this.auth.ensureWalletClient();
222
+ const account = wallet.account;
223
+ if (!account)
224
+ throw new Error('Wallet client has no account.');
225
+ const raw = built.raw;
226
+ if (built.params.side === 'buy') {
227
+ await this.ensureApproval(raw.baseToken, raw.ref.marketContract, this.toBaseWei(built.params.amount, raw.baseDecimals));
228
+ }
229
+ const txHash = await wallet.sendTransaction({
230
+ account,
231
+ chain: wallet.chain,
232
+ to: raw.rawTx.to,
233
+ data: raw.rawTx.data,
234
+ value: raw.rawTx.value ?? 0n,
235
+ });
236
+ const ref = { ...raw.ref, txHash };
237
+ const isMarket = built.params.type === 'market';
238
+ return {
239
+ id: encodeOrderId(ref),
240
+ marketId: built.params.marketId,
241
+ outcomeId: built.params.outcomeId,
242
+ side: built.params.side,
243
+ type: built.params.type,
244
+ price: built.params.price ?? (raw.ref.pricePerShare1e18 !== '0'
245
+ ? (0, utils_1.priceBigIntToNumber)(BigInt(raw.ref.pricePerShare1e18))
246
+ : undefined),
247
+ amount: built.params.amount,
248
+ status: isMarket ? 'filled' : 'open',
249
+ filled: isMarket ? built.params.amount : 0,
250
+ remaining: isMarket ? 0 : built.params.amount,
251
+ timestamp: Date.now(),
252
+ txHash,
253
+ chain: 'arbitrum',
254
+ };
255
+ }
256
+ catch (error) {
257
+ throw errors_2.rainErrorMapper.mapError(error);
258
+ }
259
+ }
260
+ async createOrder(params) {
261
+ const built = await this.buildOrder(params);
262
+ return this.submitOrder(built);
263
+ }
264
+ async cancelOrder(orderId) {
265
+ try {
266
+ const ref = decodeOrderId(orderId);
267
+ if (ref.rainOrderId === '0') {
268
+ throw new errors_1.BadRequest('Cannot cancel a Rain order without a Rain orderID. Market AMM swaps are atomic; only resting limit orders are cancellable.', 'Rain');
269
+ }
270
+ const sdk = await this.fetcher.sdkClient();
271
+ const builder = ref.side === 'buy' ? sdk.buildCancelBuyOrdersTx : sdk.buildCancelSellOrdersTx;
272
+ const rawTx = builder({
273
+ marketContractAddress: ref.marketContract,
274
+ orders: [{
275
+ option: ref.option,
276
+ price: (0, utils_1.priceBigIntToNumber)(BigInt(ref.pricePerShare1e18)),
277
+ orderID: BigInt(ref.rainOrderId),
278
+ }],
279
+ });
280
+ const wallet = this.auth.ensureWalletClient();
281
+ const account = wallet.account;
282
+ if (!account)
283
+ throw new Error('Wallet client has no account.');
284
+ const txHash = await wallet.sendTransaction({
285
+ account,
286
+ chain: wallet.chain,
287
+ to: rawTx.to,
288
+ data: rawTx.data,
289
+ value: rawTx.value ?? 0n,
290
+ });
291
+ return {
292
+ id: orderId,
293
+ marketId: `rain:unknown`,
294
+ outcomeId: `rain:unknown:${ref.option}`,
295
+ side: ref.side,
296
+ type: 'limit',
297
+ amount: 0,
298
+ status: 'canceled',
299
+ filled: 0,
300
+ remaining: 0,
301
+ timestamp: Date.now(),
302
+ txHash,
303
+ chain: 'arbitrum',
304
+ };
305
+ }
306
+ catch (error) {
307
+ throw errors_2.rainErrorMapper.mapError(error);
308
+ }
309
+ }
310
+ async fetchOrder(orderId) {
311
+ const ref = decodeOrderId(orderId);
312
+ if (!ref.txHash) {
313
+ throw new errors_1.BadRequest('Rain order id has no tx hash to look up.', 'Rain');
314
+ }
315
+ const sdk = await this.fetcher.sdkClient();
316
+ const details = await sdk.getTransactionDetails({ transactionHash: ref.txHash });
317
+ return {
318
+ id: orderId,
319
+ marketId: 'rain:unknown',
320
+ outcomeId: `rain:unknown:${ref.option}`,
321
+ side: ref.side,
322
+ type: ref.rainOrderId === '0' ? 'market' : 'limit',
323
+ amount: 0,
324
+ status: details?.status === 'success' ? 'filled' : 'rejected',
325
+ filled: 0,
326
+ remaining: 0,
327
+ timestamp: details?.timestamp ? Number(details.timestamp) * 1000 : Date.now(),
328
+ txHash: ref.txHash,
329
+ chain: 'arbitrum',
330
+ };
331
+ }
332
+ async fetchOpenOrders(marketId) {
333
+ // Open orders = limit_*_placed transactions whose orderID has not been
334
+ // cancelled or filled. Needs subgraph; without it we honestly return [].
335
+ const wallet = this.auth.resolveAddress();
336
+ if (!wallet)
337
+ return [];
338
+ const raw = await this.fetcher.fetchRawUserTrades(wallet, undefined, 200);
339
+ if (!raw)
340
+ return [];
341
+ const open = new Map();
342
+ for (const tx of raw.transactions ?? []) {
343
+ const key = `${tx.marketAddress}:${tx.orderId}:${tx.type.startsWith('limit_buy') ? 'buy' : 'sell'}`;
344
+ if (tx.type === 'limit_buy_placed' || tx.type === 'limit_sell_placed') {
345
+ open.set(key, {
346
+ side: tx.type === 'limit_buy_placed' ? 'buy' : 'sell',
347
+ option: tx.option ?? 0,
348
+ price: BigInt(tx.price ?? 0),
349
+ orderId: BigInt(tx.orderId ?? 0),
350
+ market: tx.marketAddress,
351
+ ts: Number(tx.timestamp) * 1000,
352
+ });
353
+ }
354
+ else if (tx.type === 'limit_buy_filled' || tx.type === 'limit_sell_filled' ||
355
+ tx.type === 'cancel_buy' || tx.type === 'cancel_sell') {
356
+ const side = tx.type.includes('buy') ? 'buy' : 'sell';
357
+ open.delete(`${tx.marketAddress}:${tx.orderId}:${side}`);
358
+ }
359
+ }
360
+ const want = marketId ? marketId.replace(/^rain:/, '') : undefined;
361
+ const out = [];
362
+ for (const o of open.values()) {
363
+ const ref = {
364
+ marketContract: o.market,
365
+ side: o.side,
366
+ option: o.option,
367
+ pricePerShare1e18: o.price.toString(),
368
+ rainOrderId: o.orderId.toString(),
369
+ };
370
+ const oMarketId = `rain:${o.market}`;
371
+ if (want && oMarketId !== `rain:${want}`)
372
+ continue;
373
+ out.push({
374
+ id: encodeOrderId(ref),
375
+ marketId: oMarketId,
376
+ outcomeId: `rain:${o.market}:${o.option}`,
377
+ side: o.side,
378
+ type: 'limit',
379
+ price: (0, utils_1.priceBigIntToNumber)(o.price),
380
+ amount: 0,
381
+ status: 'open',
382
+ filled: 0,
383
+ remaining: 0,
384
+ timestamp: o.ts,
385
+ chain: 'arbitrum',
386
+ });
387
+ }
388
+ return out;
389
+ }
390
+ // ------------------------------------------------------------------------
391
+ // Trading helpers
392
+ // ------------------------------------------------------------------------
393
+ parseChoiceIndex(outcomeId) {
394
+ const parts = outcomeId.split(':');
395
+ if (parts.length < 3) {
396
+ throw new errors_1.BadRequest(`Invalid Rain outcomeId: "${outcomeId}". Expected "rain:{marketId}:{choiceIndex}".`, 'Rain');
397
+ }
398
+ const idx = Number(parts[2]);
399
+ if (!Number.isInteger(idx) || idx < 0) {
400
+ throw new errors_1.BadRequest(`Invalid choice index in outcomeId: "${outcomeId}".`, 'Rain');
401
+ }
402
+ return idx;
403
+ }
404
+ toBaseWei(amount, decimals) {
405
+ // amount is the user-facing decimal token amount (e.g., 10 USDT). Convert
406
+ // to base-token wei. For sells (shares) we accept a decimal share count.
407
+ const scale = 10n ** BigInt(decimals);
408
+ const PRECISION = 1_000_000;
409
+ return (BigInt(Math.round(amount * PRECISION)) * scale) / BigInt(PRECISION);
410
+ }
411
+ toPrice1e18(price) {
412
+ if (price <= 0 || price >= 1) {
413
+ throw new errors_1.BadRequest(`Rain price must satisfy 0 < price < 1, got ${price}.`, 'Rain');
414
+ }
415
+ const PRECISION = 1000000n;
416
+ return (BigInt(Math.round(price * Number(PRECISION))) * PRICE_SCALE) / PRECISION;
417
+ }
418
+ async resolveContractFor(params) {
419
+ const marketParts = params.marketId.split(':');
420
+ if (marketParts.length < 2 || marketParts[0] !== 'rain') {
421
+ throw new errors_1.BadRequest(`Invalid Rain marketId: "${params.marketId}". Expected "rain:{marketId}".`, 'Rain');
422
+ }
423
+ const m = await this.fetcher.fetchRawMarket(marketParts[1]);
424
+ if (!m?.details?.contractAddress) {
425
+ throw new errors_1.BadRequest(`Could not resolve contract address for Rain market ${params.marketId}.`, 'Rain');
426
+ }
427
+ return {
428
+ marketContract: m.details.contractAddress,
429
+ baseToken: m.details.baseToken ?? utils_1.ARBITRUM_USDT,
430
+ baseDecimals: (0, utils_1.resolveDecimals)(m.details.baseTokenDecimals, utils_1.USDT_DECIMALS),
431
+ };
432
+ }
433
+ async ensureApproval(token, spender, neededAmount) {
434
+ const pub = this.auth.ensurePublicClient();
435
+ const owner = this.auth.resolveAddress();
436
+ if (!owner) {
437
+ throw new Error('Cannot check allowance without a wallet address.');
438
+ }
439
+ const allowance = await pub.readContract({
440
+ address: token,
441
+ abi: ERC20_ABI,
442
+ functionName: 'allowance',
443
+ args: [owner, spender],
444
+ });
445
+ if (allowance >= neededAmount)
446
+ return;
447
+ const sdk = await this.fetcher.sdkClient();
448
+ const approveTx = sdk.buildApprovalTx({
449
+ tokenAddress: token,
450
+ spender,
451
+ amount: MAX_UINT256,
452
+ });
453
+ if (approveTx instanceof Error) {
454
+ throw approveTx;
455
+ }
456
+ const wallet = this.auth.ensureWalletClient();
457
+ const account = wallet.account;
458
+ if (!account)
459
+ throw new Error('Wallet client has no account.');
460
+ const hash = await wallet.sendTransaction({
461
+ account,
462
+ chain: wallet.chain,
463
+ to: approveTx.to,
464
+ data: approveTx.data,
465
+ value: approveTx.value ?? 0n,
466
+ });
467
+ await pub.waitForTransactionReceipt({ hash });
468
+ }
469
+ // ------------------------------------------------------------------------
470
+ // WebSocket
471
+ // ------------------------------------------------------------------------
472
+ ensureWebSocket() {
473
+ if (!this.ws) {
474
+ const wsRpcUrl = this.auth.creds?.wsRpcUrl;
475
+ if (!wsRpcUrl) {
476
+ throw new Error('Rain WebSocket requires wsRpcUrl in credentials.');
477
+ }
478
+ this.ws = new websocket_1.RainWebSocket({
479
+ wsRpcUrl,
480
+ environment: this.auth.creds?.environment,
481
+ });
482
+ }
483
+ return this.ws;
484
+ }
485
+ async watchOrderBook(outcomeId, _limit, _params = {}) {
486
+ const parts = outcomeId.split(':');
487
+ if (parts.length < 3) {
488
+ throw new Error(`Invalid Rain outcomeId for watchOrderBook: "${outcomeId}".`);
489
+ }
490
+ const market = await this.fetcher.fetchRawMarket(parts[1]);
491
+ const contract = market?.details?.contractAddress;
492
+ if (!contract)
493
+ throw new Error(`No contract address for Rain market ${parts[1]}.`);
494
+ return this.ensureWebSocket().watchOrderBook(contract, outcomeId);
495
+ }
496
+ async watchTrades(outcomeId) {
497
+ const parts = outcomeId.split(':');
498
+ if (parts.length < 2) {
499
+ throw new Error(`Invalid Rain outcomeId for watchTrades: "${outcomeId}".`);
500
+ }
501
+ const market = await this.fetcher.fetchRawMarket(parts[1]);
502
+ const contract = market?.details?.contractAddress;
503
+ if (!contract)
504
+ throw new Error(`No contract address for Rain market ${parts[1]}.`);
505
+ return this.ensureWebSocket().watchTrades(contract);
506
+ }
507
+ async close() {
508
+ if (this.ws) {
509
+ await this.ws.close();
510
+ this.ws = undefined;
511
+ }
512
+ await this.fetcher.close();
513
+ }
514
+ }
515
+ exports.RainExchange = RainExchange;
@@ -0,0 +1,33 @@
1
+ import { UnifiedMarket, UnifiedEvent, PriceCandle, OrderBook, Trade, UserTrade, Position, Balance, CandleInterval } from '../../types';
2
+ import { RainMarketWithDetails, RainRawMarketDetails, RainRawPositions, RainRawBalance, RainRawPriceHistory, RainRawTransactions, RainRawMarketTransactions } from './fetcher';
3
+ export declare class RainNormalizer {
4
+ /**
5
+ * One Rain market with N options -> one UnifiedMarket when N<=2 (binary),
6
+ * or one UnifiedEvent with N synthetic binary UnifiedMarkets when N>2.
7
+ * The list-shape returns UnifiedMarket[]; callers wanting event grouping
8
+ * should call normalizeEvent.
9
+ */
10
+ normalizeMarket(raw: RainMarketWithDetails): UnifiedMarket | null;
11
+ /**
12
+ * Rain market -> UnifiedEvent. Multi-option markets are expanded into
13
+ * one synthetic binary market per option (matches Myriad/Polymarket pattern
14
+ * so the matching engine can find cross-venue identity matches).
15
+ */
16
+ normalizeEvent(raw: RainMarketWithDetails): UnifiedEvent | null;
17
+ /**
18
+ * Single-level emulated book using AMM spot. The plan was to use
19
+ * getMarketLiquidity.firstBuyOrderPrice / firstSellOrderPrice for real
20
+ * top-of-book; left for v2 because it needs an extra on-chain read per
21
+ * call. ponytail: 1-level AMM book is what Myriad ships and matches the
22
+ * router's expectations.
23
+ */
24
+ normalizeOrderBook(raw: RainMarketWithDetails, outcomeId: string): OrderBook;
25
+ normalizeOHLCV(raw: RainRawPriceHistory | null, limit?: number): PriceCandle[];
26
+ /** Map Rain SDK price-history intervals onto PMXT CandleInterval strings. */
27
+ static mapInterval(resolution: CandleInterval): '1m' | '5m' | '15m' | '1h' | '4h' | '1d' | '1w';
28
+ normalizeMarketTrades(raw: RainRawMarketTransactions | null): Trade[];
29
+ normalizeUserTrades(raw: RainRawTransactions | null): UserTrade[];
30
+ normalizePositions(raw: RainRawPositions): Position[];
31
+ normalizeBalance(raw: RainRawBalance): Balance[];
32
+ normalizeMarketDetails(details: RainRawMarketDetails): UnifiedMarket | null;
33
+ }