@alango/dr-manhattan 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,2537 @@
1
+ import { EventEmitter } from 'events';
2
+ import WebSocket from 'ws';
3
+ import { Wallet } from 'ethers';
4
+ import 'socket.io-client';
5
+ import { ClobClient, Side, AssetType } from '@polymarket/clob-client';
6
+ import pino from 'pino';
7
+
8
+ // src/types/order.ts
9
+ var OrderSide = {
10
+ BUY: "buy",
11
+ SELL: "sell"
12
+ };
13
+ var OrderStatus = {
14
+ PENDING: "pending",
15
+ OPEN: "open",
16
+ FILLED: "filled",
17
+ PARTIALLY_FILLED: "partially_filled",
18
+ CANCELLED: "cancelled",
19
+ REJECTED: "rejected"
20
+ };
21
+ var OrderUtils = {
22
+ /** Get remaining unfilled amount */
23
+ remaining(order) {
24
+ return order.size - order.filled;
25
+ },
26
+ /** Check if order is still active */
27
+ isActive(order) {
28
+ return order.status === OrderStatus.OPEN || order.status === OrderStatus.PARTIALLY_FILLED;
29
+ },
30
+ /** Check if order is completely filled */
31
+ isFilled(order) {
32
+ return order.status === OrderStatus.FILLED || order.filled >= order.size;
33
+ },
34
+ /** Get fill percentage (0-1) */
35
+ fillPercentage(order) {
36
+ if (order.size === 0) return 0;
37
+ return order.filled / order.size;
38
+ }
39
+ };
40
+
41
+ // src/types/market.ts
42
+ var MarketUtils = {
43
+ /** Check if market is binary (Yes/No) */
44
+ isBinary(market) {
45
+ return market.outcomes.length === 2;
46
+ },
47
+ /** Check if market is still open for trading */
48
+ isOpen(market) {
49
+ if ("closed" in market.metadata && market.metadata.closed) {
50
+ return false;
51
+ }
52
+ if (!market.closeTime) return true;
53
+ return /* @__PURE__ */ new Date() < market.closeTime;
54
+ },
55
+ /** Get bid-ask spread for binary markets */
56
+ spread(market) {
57
+ if (!MarketUtils.isBinary(market) || market.outcomes.length !== 2) {
58
+ return null;
59
+ }
60
+ const prices = Object.values(market.prices);
61
+ if (prices.length !== 2) return null;
62
+ return Math.abs(1 - prices.reduce((a, b) => a + b, 0));
63
+ },
64
+ /** Get token IDs from market metadata */
65
+ getTokenIds(market) {
66
+ const tokenIds = market.metadata.clobTokenIds;
67
+ if (!tokenIds) return [];
68
+ if (typeof tokenIds === "string") {
69
+ try {
70
+ return JSON.parse(tokenIds);
71
+ } catch {
72
+ return [];
73
+ }
74
+ }
75
+ if (Array.isArray(tokenIds)) {
76
+ return tokenIds.map(String);
77
+ }
78
+ return [];
79
+ },
80
+ /** Create OutcomeToken array from market */
81
+ getOutcomeTokens(market) {
82
+ const tokenIds = MarketUtils.getTokenIds(market);
83
+ return market.outcomes.map((outcome, i) => ({
84
+ outcome,
85
+ tokenId: tokenIds[i] ?? ""
86
+ }));
87
+ }
88
+ };
89
+
90
+ // src/types/position.ts
91
+ var PositionUtils = {
92
+ /** Get total cost basis */
93
+ costBasis(position) {
94
+ return position.size * position.averagePrice;
95
+ },
96
+ /** Get current market value */
97
+ currentValue(position) {
98
+ return position.size * position.currentPrice;
99
+ },
100
+ /** Get unrealized profit/loss */
101
+ unrealizedPnl(position) {
102
+ return PositionUtils.currentValue(position) - PositionUtils.costBasis(position);
103
+ },
104
+ /** Get unrealized P&L as percentage */
105
+ unrealizedPnlPercent(position) {
106
+ const costBasis = PositionUtils.costBasis(position);
107
+ if (costBasis === 0) return 0;
108
+ return PositionUtils.unrealizedPnl(position) / costBasis * 100;
109
+ }
110
+ };
111
+ function calculateDelta(positions) {
112
+ const entries = Object.entries(positions);
113
+ if (entries.length === 0) {
114
+ return { delta: 0, maxOutcome: null, maxPosition: 0 };
115
+ }
116
+ let maxOutcome = null;
117
+ let maxPosition = 0;
118
+ for (const [outcome, size] of entries) {
119
+ if (size > maxPosition) {
120
+ maxPosition = size;
121
+ maxOutcome = outcome;
122
+ }
123
+ }
124
+ if (entries.length === 2) {
125
+ const [first, second] = entries;
126
+ if (first && second) {
127
+ const delta = Math.abs(first[1] - second[1]);
128
+ return { delta, maxOutcome, maxPosition };
129
+ }
130
+ }
131
+ return { delta: maxPosition, maxOutcome, maxPosition };
132
+ }
133
+
134
+ // src/types/orderbook.ts
135
+ var OrderbookUtils = {
136
+ /** Get best bid price */
137
+ bestBid(orderbook) {
138
+ return orderbook.bids[0]?.[0] ?? null;
139
+ },
140
+ /** Get best ask price */
141
+ bestAsk(orderbook) {
142
+ return orderbook.asks[0]?.[0] ?? null;
143
+ },
144
+ /** Get mid price */
145
+ midPrice(orderbook) {
146
+ const bid = OrderbookUtils.bestBid(orderbook);
147
+ const ask = OrderbookUtils.bestAsk(orderbook);
148
+ if (bid === null || ask === null) return null;
149
+ return (bid + ask) / 2;
150
+ },
151
+ /** Get bid-ask spread */
152
+ spread(orderbook) {
153
+ const bid = OrderbookUtils.bestBid(orderbook);
154
+ const ask = OrderbookUtils.bestAsk(orderbook);
155
+ if (bid === null || ask === null) return null;
156
+ return ask - bid;
157
+ },
158
+ /** Create orderbook from REST API response */
159
+ fromRestResponse(data, tokenId = "") {
160
+ const bids = [];
161
+ const asks = [];
162
+ for (const bid of data.bids ?? []) {
163
+ const price = Number.parseFloat(bid.price);
164
+ const size = Number.parseFloat(bid.size);
165
+ if (price > 0 && size > 0) {
166
+ bids.push([price, size]);
167
+ }
168
+ }
169
+ for (const ask of data.asks ?? []) {
170
+ const price = Number.parseFloat(ask.price);
171
+ const size = Number.parseFloat(ask.size);
172
+ if (price > 0 && size > 0) {
173
+ asks.push([price, size]);
174
+ }
175
+ }
176
+ bids.sort((a, b) => b[0] - a[0]);
177
+ asks.sort((a, b) => a[0] - b[0]);
178
+ return {
179
+ bids,
180
+ asks,
181
+ timestamp: Date.now(),
182
+ assetId: tokenId,
183
+ marketId: ""
184
+ };
185
+ }
186
+ };
187
+ var OrderbookManager = class {
188
+ orderbooks = /* @__PURE__ */ new Map();
189
+ /** Update orderbook for a token */
190
+ update(tokenId, orderbook) {
191
+ this.orderbooks.set(tokenId, orderbook);
192
+ }
193
+ /** Get orderbook for a token */
194
+ get(tokenId) {
195
+ return this.orderbooks.get(tokenId);
196
+ }
197
+ /** Get best bid and ask for a token */
198
+ getBestBidAsk(tokenId) {
199
+ const orderbook = this.get(tokenId);
200
+ if (!orderbook) return [null, null];
201
+ return [OrderbookUtils.bestBid(orderbook), OrderbookUtils.bestAsk(orderbook)];
202
+ }
203
+ /** Check if we have data for a token */
204
+ hasData(tokenId) {
205
+ const orderbook = this.get(tokenId);
206
+ if (!orderbook) return false;
207
+ return orderbook.bids.length > 0 && orderbook.asks.length > 0;
208
+ }
209
+ /** Check if we have data for all tokens */
210
+ hasAllData(tokenIds) {
211
+ return tokenIds.every((id) => this.hasData(id));
212
+ }
213
+ /** Clear all orderbooks */
214
+ clear() {
215
+ this.orderbooks.clear();
216
+ }
217
+ };
218
+
219
+ // src/types/crypto-hourly.ts
220
+ var TOKEN_ALIASES = {
221
+ BITCOIN: "BTC",
222
+ ETHEREUM: "ETH",
223
+ SOLANA: "SOL"
224
+ };
225
+ function normalizeTokenSymbol(token) {
226
+ const upper = token.toUpperCase();
227
+ return TOKEN_ALIASES[upper] ?? upper;
228
+ }
229
+
230
+ // src/errors/index.ts
231
+ var DrManhattanError = class extends Error {
232
+ constructor(message) {
233
+ super(message);
234
+ this.name = "DrManhattanError";
235
+ Object.setPrototypeOf(this, new.target.prototype);
236
+ }
237
+ };
238
+ var ExchangeError = class extends DrManhattanError {
239
+ constructor(message) {
240
+ super(message);
241
+ this.name = "ExchangeError";
242
+ }
243
+ };
244
+ var NetworkError = class extends DrManhattanError {
245
+ constructor(message) {
246
+ super(message);
247
+ this.name = "NetworkError";
248
+ }
249
+ };
250
+ var RateLimitError = class extends DrManhattanError {
251
+ retryAfter;
252
+ constructor(message, retryAfter) {
253
+ super(message);
254
+ this.name = "RateLimitError";
255
+ this.retryAfter = retryAfter;
256
+ }
257
+ };
258
+ var AuthenticationError = class extends DrManhattanError {
259
+ constructor(message) {
260
+ super(message);
261
+ this.name = "AuthenticationError";
262
+ }
263
+ };
264
+ var InsufficientFunds = class extends DrManhattanError {
265
+ constructor(message) {
266
+ super(message);
267
+ this.name = "InsufficientFunds";
268
+ }
269
+ };
270
+ var InvalidOrder = class extends DrManhattanError {
271
+ constructor(message) {
272
+ super(message);
273
+ this.name = "InvalidOrder";
274
+ }
275
+ };
276
+ var MarketNotFound = class extends DrManhattanError {
277
+ constructor(message) {
278
+ super(message);
279
+ this.name = "MarketNotFound";
280
+ }
281
+ };
282
+
283
+ // src/core/exchange.ts
284
+ var Exchange = class {
285
+ config;
286
+ requestTimes = [];
287
+ lastRequestTime = 0;
288
+ constructor(config = {}) {
289
+ this.config = {
290
+ timeout: 3e4,
291
+ verbose: false,
292
+ rateLimit: 10,
293
+ maxRetries: 3,
294
+ retryDelay: 1e3,
295
+ retryBackoff: 2,
296
+ ...config
297
+ };
298
+ }
299
+ get verbose() {
300
+ return this.config.verbose ?? false;
301
+ }
302
+ get timeout() {
303
+ return this.config.timeout ?? 3e4;
304
+ }
305
+ describe() {
306
+ return {
307
+ id: this.id,
308
+ name: this.name,
309
+ has: {
310
+ fetchMarkets: true,
311
+ fetchMarket: true,
312
+ createOrder: true,
313
+ cancelOrder: true,
314
+ fetchOrder: true,
315
+ fetchOpenOrders: true,
316
+ fetchPositions: true,
317
+ fetchBalance: true,
318
+ websocket: false
319
+ }
320
+ };
321
+ }
322
+ checkRateLimit() {
323
+ const currentTime = Date.now();
324
+ const rateLimit = this.config.rateLimit ?? 10;
325
+ this.requestTimes = this.requestTimes.filter((t) => currentTime - t < 1e3);
326
+ if (this.requestTimes.length >= rateLimit) {
327
+ const oldestRequest = this.requestTimes[0];
328
+ if (oldestRequest) {
329
+ const sleepTime = 1e3 - (currentTime - oldestRequest);
330
+ if (sleepTime > 0) {
331
+ throw new RateLimitError(`Rate limit reached, wait ${sleepTime}ms`, sleepTime);
332
+ }
333
+ }
334
+ }
335
+ this.requestTimes.push(currentTime);
336
+ }
337
+ async withRetry(fn) {
338
+ const maxRetries = this.config.maxRetries ?? 3;
339
+ const retryDelay = this.config.retryDelay ?? 1e3;
340
+ const retryBackoff = this.config.retryBackoff ?? 2;
341
+ let lastError = null;
342
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
343
+ try {
344
+ this.checkRateLimit();
345
+ return await fn();
346
+ } catch (error) {
347
+ lastError = error;
348
+ if (error instanceof NetworkError || error instanceof RateLimitError) {
349
+ if (attempt < maxRetries) {
350
+ const delay = retryDelay * retryBackoff ** attempt + Math.random() * 1e3;
351
+ if (this.verbose) {
352
+ console.log(
353
+ `Attempt ${attempt + 1} failed, retrying in ${delay.toFixed(0)}ms: ${error.message}`
354
+ );
355
+ }
356
+ await this.sleep(delay);
357
+ continue;
358
+ }
359
+ }
360
+ throw error;
361
+ }
362
+ }
363
+ throw lastError;
364
+ }
365
+ sleep(ms) {
366
+ return new Promise((resolve) => setTimeout(resolve, ms));
367
+ }
368
+ parseDateTime(timestamp) {
369
+ if (!timestamp) return void 0;
370
+ if (timestamp instanceof Date) return timestamp;
371
+ if (typeof timestamp === "number") return new Date(timestamp);
372
+ if (typeof timestamp === "string") {
373
+ const parsed = new Date(timestamp);
374
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
375
+ }
376
+ return void 0;
377
+ }
378
+ async findTradeableMarket(options = {}) {
379
+ const { binary = true, limit = 100, minLiquidity = 0 } = options;
380
+ const markets = await this.fetchMarkets({ limit });
381
+ const suitable = [];
382
+ for (const market of markets) {
383
+ if (binary && !MarketUtils.isBinary(market)) continue;
384
+ if (!MarketUtils.isOpen(market)) continue;
385
+ if (market.liquidity < minLiquidity) continue;
386
+ const tokenIds = MarketUtils.getTokenIds(market);
387
+ if (tokenIds.length === 0) continue;
388
+ suitable.push(market);
389
+ }
390
+ if (suitable.length === 0) return null;
391
+ const randomIndex = Math.floor(Math.random() * suitable.length);
392
+ return suitable[randomIndex] ?? null;
393
+ }
394
+ calculateSpread(market) {
395
+ return MarketUtils.spread(market);
396
+ }
397
+ calculateImpliedProbability(price) {
398
+ return Math.max(0, Math.min(1, price));
399
+ }
400
+ calculateExpectedValue(market, outcome, price) {
401
+ if (!MarketUtils.isBinary(market)) return 0;
402
+ const probability = this.calculateImpliedProbability(price);
403
+ const payoff = outcome === market.outcomes[0] ? 1 : 0;
404
+ const cost = price;
405
+ return probability * payoff - cost;
406
+ }
407
+ getOptimalOrderSize(market, maxPositionSize) {
408
+ const liquidityBasedSize = market.liquidity * 0.1;
409
+ return Math.min(maxPositionSize, liquidityBasedSize);
410
+ }
411
+ };
412
+ var WebSocketState = {
413
+ DISCONNECTED: "disconnected",
414
+ CONNECTING: "connecting",
415
+ CONNECTED: "connected",
416
+ RECONNECTING: "reconnecting",
417
+ CLOSED: "closed"
418
+ };
419
+ var OrderBookWebSocket = class extends EventEmitter {
420
+ config;
421
+ ws = null;
422
+ state = WebSocketState.DISCONNECTED;
423
+ reconnectAttempts = 0;
424
+ subscriptions = /* @__PURE__ */ new Map();
425
+ pingTimer = null;
426
+ lastMessageTime = 0;
427
+ constructor(config = {}) {
428
+ super();
429
+ this.config = {
430
+ verbose: false,
431
+ autoReconnect: true,
432
+ maxReconnectAttempts: 999,
433
+ reconnectDelay: 3e3,
434
+ pingInterval: 2e4,
435
+ pingTimeout: 1e4,
436
+ ...config
437
+ };
438
+ }
439
+ get isConnected() {
440
+ return this.state === WebSocketState.CONNECTED;
441
+ }
442
+ async connect() {
443
+ if (this.state === WebSocketState.CONNECTED) return;
444
+ this.state = WebSocketState.CONNECTING;
445
+ return new Promise((resolve, reject) => {
446
+ this.ws = new WebSocket(this.wsUrl);
447
+ this.ws.on("open", async () => {
448
+ this.state = WebSocketState.CONNECTED;
449
+ this.reconnectAttempts = 0;
450
+ this.lastMessageTime = Date.now();
451
+ if (this.config.verbose) {
452
+ console.log(`WebSocket connected to ${this.wsUrl}`);
453
+ }
454
+ try {
455
+ await this.authenticate();
456
+ for (const marketId of this.subscriptions.keys()) {
457
+ await this.subscribeOrderbook(marketId);
458
+ }
459
+ this.startPingTimer();
460
+ resolve();
461
+ } catch (error) {
462
+ reject(error);
463
+ }
464
+ });
465
+ this.ws.on("message", (data) => this.handleMessage(data));
466
+ this.ws.on("error", (error) => this.handleError(error));
467
+ this.ws.on("close", () => this.handleClose());
468
+ });
469
+ }
470
+ async disconnect() {
471
+ this.state = WebSocketState.CLOSED;
472
+ this.config.autoReconnect = false;
473
+ this.stopPingTimer();
474
+ if (this.ws) {
475
+ this.ws.close();
476
+ this.ws = null;
477
+ }
478
+ }
479
+ async watchOrderbook(marketId, callback) {
480
+ this.subscriptions.set(marketId, callback);
481
+ if (this.state !== WebSocketState.CONNECTED) {
482
+ await this.connect();
483
+ }
484
+ await this.subscribeOrderbook(marketId);
485
+ }
486
+ async unwatchOrderbook(marketId) {
487
+ if (!this.subscriptions.has(marketId)) return;
488
+ this.subscriptions.delete(marketId);
489
+ if (this.state === WebSocketState.CONNECTED) {
490
+ await this.unsubscribeOrderbook(marketId);
491
+ }
492
+ }
493
+ handleMessage(data) {
494
+ this.lastMessageTime = Date.now();
495
+ try {
496
+ const message = data.toString();
497
+ if (message === "PONG" || message === "PING" || message === "") return;
498
+ const parsed = JSON.parse(message);
499
+ const orderbook = this.parseOrderbookMessage(parsed);
500
+ if (orderbook) {
501
+ const callback = this.subscriptions.get(orderbook.marketId);
502
+ if (callback) {
503
+ Promise.resolve(callback(orderbook.marketId, orderbook)).catch((error) => {
504
+ if (this.config.verbose) {
505
+ console.error("Orderbook callback error:", error);
506
+ }
507
+ });
508
+ }
509
+ }
510
+ } catch (error) {
511
+ if (this.config.verbose) {
512
+ console.error("Failed to parse WebSocket message:", error);
513
+ }
514
+ }
515
+ }
516
+ handleError(error) {
517
+ if (this.config.verbose) {
518
+ console.error("WebSocket error:", error);
519
+ }
520
+ this.emit("error", error);
521
+ }
522
+ handleClose() {
523
+ this.stopPingTimer();
524
+ if (this.config.verbose) {
525
+ console.log("WebSocket connection closed");
526
+ }
527
+ if (this.config.autoReconnect && this.state !== WebSocketState.CLOSED) {
528
+ this.reconnect();
529
+ }
530
+ }
531
+ async reconnect() {
532
+ const maxAttempts = this.config.maxReconnectAttempts ?? 999;
533
+ if (this.reconnectAttempts >= maxAttempts) {
534
+ this.state = WebSocketState.CLOSED;
535
+ return;
536
+ }
537
+ this.state = WebSocketState.RECONNECTING;
538
+ this.reconnectAttempts++;
539
+ const delay = Math.min(
540
+ 6e4,
541
+ (this.config.reconnectDelay ?? 3e3) * 1.5 ** (this.reconnectAttempts - 1)
542
+ );
543
+ if (this.config.verbose) {
544
+ console.log(`Reconnecting in ${delay.toFixed(0)}ms (attempt ${this.reconnectAttempts})`);
545
+ }
546
+ await this.sleep(delay);
547
+ try {
548
+ await this.connect();
549
+ } catch {
550
+ const currentState = this.state;
551
+ if (this.config.autoReconnect && currentState !== WebSocketState.CLOSED) {
552
+ this.reconnect();
553
+ }
554
+ }
555
+ }
556
+ startPingTimer() {
557
+ this.stopPingTimer();
558
+ const interval = this.config.pingInterval ?? 2e4;
559
+ this.pingTimer = setInterval(() => {
560
+ if (this.ws?.readyState === WebSocket.OPEN) {
561
+ this.ws.ping();
562
+ }
563
+ }, interval);
564
+ }
565
+ stopPingTimer() {
566
+ if (this.pingTimer) {
567
+ clearInterval(this.pingTimer);
568
+ this.pingTimer = null;
569
+ }
570
+ }
571
+ sleep(ms) {
572
+ return new Promise((resolve) => setTimeout(resolve, ms));
573
+ }
574
+ send(data) {
575
+ if (this.ws?.readyState === WebSocket.OPEN) {
576
+ this.ws.send(JSON.stringify(data));
577
+ }
578
+ }
579
+ };
580
+ var StrategyState = {
581
+ STOPPED: "stopped",
582
+ RUNNING: "running",
583
+ PAUSED: "paused"
584
+ };
585
+ var Strategy = class extends EventEmitter {
586
+ exchange;
587
+ marketId;
588
+ market = null;
589
+ state = StrategyState.STOPPED;
590
+ config;
591
+ tickTimer = null;
592
+ positions = [];
593
+ openOrders = [];
594
+ constructor(exchange, marketId, config = {}) {
595
+ super();
596
+ this.exchange = exchange;
597
+ this.marketId = marketId;
598
+ this.config = {
599
+ tickInterval: 1e3,
600
+ maxPositionSize: 100,
601
+ spreadBps: 100,
602
+ verbose: false,
603
+ ...config
604
+ };
605
+ }
606
+ async start() {
607
+ if (this.state === StrategyState.RUNNING) return;
608
+ this.market = await this.exchange.fetchMarket(this.marketId);
609
+ this.state = StrategyState.RUNNING;
610
+ this.tickTimer = setInterval(async () => {
611
+ if (this.state !== StrategyState.RUNNING) return;
612
+ try {
613
+ await this.refreshState();
614
+ await this.onTick();
615
+ } catch (error) {
616
+ this.emit("error", error);
617
+ if (this.config.verbose) {
618
+ console.error("Strategy tick error:", error);
619
+ }
620
+ }
621
+ }, this.config.tickInterval);
622
+ this.emit("started");
623
+ }
624
+ async stop() {
625
+ if (this.state === StrategyState.STOPPED) return;
626
+ this.state = StrategyState.STOPPED;
627
+ if (this.tickTimer) {
628
+ clearInterval(this.tickTimer);
629
+ this.tickTimer = null;
630
+ }
631
+ await this.cancelAllOrders();
632
+ this.emit("stopped");
633
+ }
634
+ pause() {
635
+ if (this.state === StrategyState.RUNNING) {
636
+ this.state = StrategyState.PAUSED;
637
+ this.emit("paused");
638
+ }
639
+ }
640
+ resume() {
641
+ if (this.state === StrategyState.PAUSED) {
642
+ this.state = StrategyState.RUNNING;
643
+ this.emit("resumed");
644
+ }
645
+ }
646
+ async refreshState() {
647
+ const [positions, orders] = await Promise.all([
648
+ this.exchange.fetchPositions(this.marketId),
649
+ this.exchange.fetchOpenOrders(this.marketId)
650
+ ]);
651
+ this.positions = positions;
652
+ this.openOrders = orders;
653
+ }
654
+ async cancelAllOrders() {
655
+ for (const order of this.openOrders) {
656
+ try {
657
+ await this.exchange.cancelOrder(order.id, this.marketId);
658
+ } catch {
659
+ }
660
+ }
661
+ this.openOrders = [];
662
+ }
663
+ getPosition(outcome) {
664
+ return this.positions.find((p) => p.outcome === outcome);
665
+ }
666
+ getNetPosition() {
667
+ if (!this.market || this.market.outcomes.length !== 2) return 0;
668
+ const outcome1 = this.market.outcomes[0];
669
+ const outcome2 = this.market.outcomes[1];
670
+ if (!outcome1 || !outcome2) return 0;
671
+ const pos1 = this.getPosition(outcome1)?.size ?? 0;
672
+ const pos2 = this.getPosition(outcome2)?.size ?? 0;
673
+ return pos1 - pos2;
674
+ }
675
+ async placeOrder(outcome, side, price, size, tokenId) {
676
+ try {
677
+ const order = await this.exchange.createOrder({
678
+ marketId: this.marketId,
679
+ outcome,
680
+ side,
681
+ price,
682
+ size,
683
+ tokenId
684
+ });
685
+ this.openOrders.push(order);
686
+ this.emit("order", order);
687
+ return order;
688
+ } catch (error) {
689
+ this.emit("error", error);
690
+ return null;
691
+ }
692
+ }
693
+ log(message) {
694
+ if (this.config.verbose) {
695
+ console.log(`[${this.exchange.id}:${this.marketId}] ${message}`);
696
+ }
697
+ }
698
+ get isRunning() {
699
+ return this.state === StrategyState.RUNNING;
700
+ }
701
+ get currentState() {
702
+ return this.state;
703
+ }
704
+ };
705
+
706
+ // src/exchanges/limitless/index.ts
707
+ var BASE_URL = "https://api.limitless.exchange";
708
+ var CHAIN_ID = 8453;
709
+ var Limitless = class extends Exchange {
710
+ id = "limitless";
711
+ name = "Limitless";
712
+ host;
713
+ chainId;
714
+ wallet = null;
715
+ address = null;
716
+ authenticated = false;
717
+ ownerId = null;
718
+ sessionCookie = null;
719
+ tokenToSlug = /* @__PURE__ */ new Map();
720
+ noTokens = /* @__PURE__ */ new Set();
721
+ constructor(config = {}) {
722
+ super(config);
723
+ this.host = config.host ?? BASE_URL;
724
+ this.chainId = config.chainId ?? CHAIN_ID;
725
+ if (config.privateKey) {
726
+ this.wallet = new Wallet(config.privateKey);
727
+ this.address = this.wallet.address;
728
+ }
729
+ }
730
+ async authenticate() {
731
+ if (!this.wallet || !this.address) {
732
+ throw new AuthenticationError("Private key required for authentication");
733
+ }
734
+ const msgResponse = await fetch(`${this.host}/auth/signing-message`, {
735
+ method: "GET",
736
+ headers: { "Content-Type": "application/json" }
737
+ });
738
+ if (!msgResponse.ok) {
739
+ throw new AuthenticationError("Failed to get signing message");
740
+ }
741
+ const message = await msgResponse.text();
742
+ if (!message) {
743
+ throw new AuthenticationError("Empty signing message");
744
+ }
745
+ const signature = await this.wallet.signMessage(message);
746
+ const messageHex = `0x${Buffer.from(message, "utf-8").toString("hex")}`;
747
+ const loginResponse = await fetch(`${this.host}/auth/login`, {
748
+ method: "POST",
749
+ headers: {
750
+ "Content-Type": "application/json",
751
+ "x-account": this.address,
752
+ "x-signing-message": messageHex,
753
+ "x-signature": signature
754
+ },
755
+ body: JSON.stringify({ client: "eoa" })
756
+ });
757
+ if (!loginResponse.ok) {
758
+ throw new AuthenticationError("Login failed");
759
+ }
760
+ const setCookie = loginResponse.headers.get("set-cookie");
761
+ if (setCookie) {
762
+ const match = /limitless_session=([^;]+)/.exec(setCookie);
763
+ if (match?.[1]) {
764
+ this.sessionCookie = match[1];
765
+ }
766
+ }
767
+ const loginData = await loginResponse.json();
768
+ const userData = loginData.user ?? loginData;
769
+ this.ownerId = userData.id ?? null;
770
+ this.authenticated = true;
771
+ }
772
+ async ensureAuth() {
773
+ if (!this.authenticated) {
774
+ if (!this.wallet) {
775
+ throw new AuthenticationError("Private key required for this operation");
776
+ }
777
+ await this.authenticate();
778
+ }
779
+ }
780
+ async request(method, endpoint, params, requireAuth = false) {
781
+ if (requireAuth) {
782
+ await this.ensureAuth();
783
+ }
784
+ const url = new URL(`${this.host}${endpoint}`);
785
+ if (method === "GET" && params) {
786
+ for (const [key, value] of Object.entries(params)) {
787
+ if (value !== void 0 && value !== null) {
788
+ url.searchParams.set(key, String(value));
789
+ }
790
+ }
791
+ }
792
+ const headers = {
793
+ "Content-Type": "application/json"
794
+ };
795
+ if (this.sessionCookie) {
796
+ headers.Cookie = `limitless_session=${this.sessionCookie}`;
797
+ }
798
+ const fetchOptions = {
799
+ method,
800
+ headers
801
+ };
802
+ if (method !== "GET" && method !== "DELETE" && params) {
803
+ fetchOptions.body = JSON.stringify(params);
804
+ }
805
+ const response = await fetch(url.toString(), fetchOptions);
806
+ if (!response.ok) {
807
+ if (response.status === 429) {
808
+ throw new NetworkError("Rate limited");
809
+ }
810
+ if (response.status === 401 || response.status === 403) {
811
+ this.authenticated = false;
812
+ throw new AuthenticationError("Authentication failed");
813
+ }
814
+ if (response.status === 404) {
815
+ throw new ExchangeError(`Resource not found: ${endpoint}`);
816
+ }
817
+ throw new NetworkError(`HTTP ${response.status}`);
818
+ }
819
+ return response.json();
820
+ }
821
+ parseMarket(data) {
822
+ const slug = data.slug ?? data.address ?? "";
823
+ const question = data.title ?? data.question ?? "";
824
+ const tokens = data.tokens ?? {};
825
+ const yesTokenId = String(tokens.yes ?? "");
826
+ const noTokenId = String(tokens.no ?? "");
827
+ const outcomes = ["Yes", "No"];
828
+ const tokenIds = yesTokenId && noTokenId ? [yesTokenId, noTokenId] : [];
829
+ const prices = {};
830
+ if (data.yesPrice !== void 0) {
831
+ const yesPrice = data.yesPrice ?? 0;
832
+ const noPrice = data.noPrice ?? 0;
833
+ prices.Yes = yesPrice > 1 ? yesPrice / 100 : yesPrice;
834
+ prices.No = noPrice > 1 ? noPrice / 100 : noPrice;
835
+ } else if (data.prices) {
836
+ if (Array.isArray(data.prices)) {
837
+ const yesPrice = data.prices[0] ?? 0;
838
+ const noPrice = data.prices[1] ?? 0;
839
+ prices.Yes = yesPrice > 1 ? yesPrice / 100 : yesPrice;
840
+ prices.No = noPrice > 1 ? noPrice / 100 : noPrice;
841
+ } else {
842
+ const yesPrice = data.prices.yes ?? 0;
843
+ const noPrice = data.prices.no ?? 0;
844
+ prices.Yes = yesPrice > 1 ? yesPrice / 100 : yesPrice;
845
+ prices.No = noPrice > 1 ? noPrice / 100 : noPrice;
846
+ }
847
+ }
848
+ let closeTime;
849
+ const deadline = data.deadline ?? data.closeDate ?? data.expirationDate;
850
+ if (deadline) {
851
+ closeTime = this.parseDateTime(deadline);
852
+ }
853
+ const volume = data.volumeFormatted ?? data.volume ?? 0;
854
+ const liquidity = data.liquidityFormatted ?? data.liquidity ?? 0;
855
+ const tickSize = 1e-3;
856
+ for (const tokenId of tokenIds) {
857
+ if (tokenId) {
858
+ this.tokenToSlug.set(tokenId, slug);
859
+ }
860
+ }
861
+ if (noTokenId) {
862
+ this.noTokens.add(noTokenId);
863
+ }
864
+ const status = data.status ?? "";
865
+ const closed = status.toLowerCase() === "resolved" || status.toLowerCase() === "closed";
866
+ const metadata = {
867
+ ...data,
868
+ slug,
869
+ clobTokenIds: tokenIds,
870
+ token_ids: tokenIds,
871
+ tokens: { Yes: yesTokenId, No: noTokenId },
872
+ minimum_tick_size: tickSize,
873
+ closed
874
+ };
875
+ return {
876
+ id: slug,
877
+ question,
878
+ outcomes,
879
+ closeTime,
880
+ volume,
881
+ liquidity,
882
+ prices,
883
+ tickSize,
884
+ description: data.description ?? "",
885
+ metadata
886
+ };
887
+ }
888
+ parseOrder(data, tokenToOutcome) {
889
+ const orderId = String(data.id ?? data.orderId ?? "");
890
+ const marketId = data.marketSlug ?? data.market_id ?? "";
891
+ let side;
892
+ if (typeof data.side === "number") {
893
+ side = data.side === 0 ? OrderSide.BUY : OrderSide.SELL;
894
+ } else {
895
+ side = String(data.side).toLowerCase() === "buy" ? OrderSide.BUY : OrderSide.SELL;
896
+ }
897
+ const status = this.parseOrderStatus(data.status);
898
+ const price = data.price ?? 0;
899
+ let size = data.size ?? data.amount ?? 0;
900
+ if (!size) {
901
+ const makerAmount = data.makerAmount ?? 0;
902
+ const takerAmount = data.takerAmount ?? 0;
903
+ if (makerAmount || takerAmount) {
904
+ size = side === OrderSide.BUY ? takerAmount / 1e6 : makerAmount / 1e6;
905
+ }
906
+ }
907
+ const filled = data.filled ?? data.matchedAmount ?? 0;
908
+ let createdAt = /* @__PURE__ */ new Date();
909
+ if (data.createdAt) {
910
+ const parsed = this.parseDateTime(data.createdAt);
911
+ if (parsed) createdAt = parsed;
912
+ }
913
+ let outcome = data.outcome ?? "";
914
+ if (!outcome && tokenToOutcome) {
915
+ const tokenId = String(data.token ?? data.tokenId ?? "");
916
+ outcome = tokenToOutcome.get(tokenId) ?? "";
917
+ }
918
+ return {
919
+ id: orderId,
920
+ marketId,
921
+ outcome,
922
+ side,
923
+ price,
924
+ size,
925
+ filled,
926
+ status,
927
+ createdAt
928
+ };
929
+ }
930
+ parseOrderStatus(status) {
931
+ if (!status) return OrderStatus.OPEN;
932
+ const statusStr = String(status).toLowerCase();
933
+ const statusMap = {
934
+ pending: OrderStatus.PENDING,
935
+ open: OrderStatus.OPEN,
936
+ live: OrderStatus.OPEN,
937
+ active: OrderStatus.OPEN,
938
+ filled: OrderStatus.FILLED,
939
+ matched: OrderStatus.FILLED,
940
+ partially_filled: OrderStatus.PARTIALLY_FILLED,
941
+ partial: OrderStatus.PARTIALLY_FILLED,
942
+ cancelled: OrderStatus.CANCELLED,
943
+ canceled: OrderStatus.CANCELLED
944
+ };
945
+ return statusMap[statusStr] ?? OrderStatus.OPEN;
946
+ }
947
+ parsePortfolioPositions(data) {
948
+ const positions = [];
949
+ const marketData = data.market ?? {};
950
+ const marketId = marketData.slug ?? data.marketSlug ?? "";
951
+ const tokensBalance = data.tokensBalance ?? {};
952
+ const positionDetails = data.positions ?? {};
953
+ const latestTrade = data.latestTrade ?? {};
954
+ const yesBalance = Number(tokensBalance.yes ?? 0);
955
+ if (yesBalance > 0) {
956
+ const yesDetails = positionDetails.yes ?? {};
957
+ const fillPrice = yesDetails.fillPrice ?? 0;
958
+ const avgPrice = fillPrice > 1 ? fillPrice / 1e6 : fillPrice;
959
+ const currentPrice = latestTrade.latestYesPrice ?? 0;
960
+ const size = yesBalance / 1e6;
961
+ positions.push({
962
+ marketId,
963
+ outcome: "Yes",
964
+ size,
965
+ averagePrice: avgPrice,
966
+ currentPrice
967
+ });
968
+ }
969
+ const noBalance = Number(tokensBalance.no ?? 0);
970
+ if (noBalance > 0) {
971
+ const noDetails = positionDetails.no ?? {};
972
+ const fillPrice = noDetails.fillPrice ?? 0;
973
+ const avgPrice = fillPrice > 1 ? fillPrice / 1e6 : fillPrice;
974
+ const currentPrice = latestTrade.latestNoPrice ?? 0;
975
+ const size = noBalance / 1e6;
976
+ positions.push({
977
+ marketId,
978
+ outcome: "No",
979
+ size,
980
+ averagePrice: avgPrice,
981
+ currentPrice
982
+ });
983
+ }
984
+ return positions;
985
+ }
986
+ async fetchMarkets(params) {
987
+ return this.withRetry(async () => {
988
+ const queryParams = {
989
+ page: params?.offset ? Math.floor(params.offset / 25) + 1 : 1,
990
+ limit: Math.min(params?.limit ?? 25, 25)
991
+ };
992
+ const response = await this.request(
993
+ "GET",
994
+ "/markets/active",
995
+ queryParams
996
+ );
997
+ const marketsData = Array.isArray(response) ? response : response.data ?? [];
998
+ let markets = marketsData.map((m) => this.parseMarket(m));
999
+ if (params?.active !== false) {
1000
+ markets = markets.filter((m) => !m.metadata?.closed);
1001
+ }
1002
+ if (params?.limit) {
1003
+ return markets.slice(0, params.limit);
1004
+ }
1005
+ return markets;
1006
+ });
1007
+ }
1008
+ async fetchMarket(marketId) {
1009
+ return this.withRetry(async () => {
1010
+ try {
1011
+ const data = await this.request("GET", `/markets/${marketId}`);
1012
+ return this.parseMarket(data);
1013
+ } catch (error) {
1014
+ if (error instanceof ExchangeError && error.message.includes("not found")) {
1015
+ throw new MarketNotFound(`Market ${marketId} not found`);
1016
+ }
1017
+ throw error;
1018
+ }
1019
+ });
1020
+ }
1021
+ async getOrderbook(marketSlugOrTokenId) {
1022
+ const isNoToken = this.noTokens.has(marketSlugOrTokenId);
1023
+ const slug = this.tokenToSlug.get(marketSlugOrTokenId) ?? marketSlugOrTokenId;
1024
+ return this.withRetry(async () => {
1025
+ const response = await this.request("GET", `/markets/${slug}/orderbook`);
1026
+ const bids = [];
1027
+ const asks = [];
1028
+ const orders = response.orders ?? response.data ?? [];
1029
+ for (const order of orders) {
1030
+ const side = String(order.side ?? "").toLowerCase();
1031
+ const price = Number(order.price ?? 0);
1032
+ const size = Number(order.size ?? 0);
1033
+ if (price > 0 && size > 0) {
1034
+ const entry = { price: String(price), size: String(size) };
1035
+ if (side === "buy") {
1036
+ bids.push(entry);
1037
+ } else {
1038
+ asks.push(entry);
1039
+ }
1040
+ }
1041
+ }
1042
+ if (response.bids) {
1043
+ for (const bid of response.bids) {
1044
+ bids.push({
1045
+ price: String(bid.price ?? 0),
1046
+ size: String(bid.size ?? 0)
1047
+ });
1048
+ }
1049
+ }
1050
+ if (response.asks) {
1051
+ for (const ask of response.asks) {
1052
+ asks.push({
1053
+ price: String(ask.price ?? 0),
1054
+ size: String(ask.size ?? 0)
1055
+ });
1056
+ }
1057
+ }
1058
+ bids.sort((a, b) => Number(b.price) - Number(a.price));
1059
+ asks.sort((a, b) => Number(a.price) - Number(b.price));
1060
+ if (isNoToken) {
1061
+ const invertedBids = asks.map((a) => ({
1062
+ price: String(Math.round((1 - Number(a.price)) * 1e3) / 1e3),
1063
+ size: a.size
1064
+ }));
1065
+ const invertedAsks = bids.map((b) => ({
1066
+ price: String(Math.round((1 - Number(b.price)) * 1e3) / 1e3),
1067
+ size: b.size
1068
+ }));
1069
+ invertedBids.sort((a, b) => Number(b.price) - Number(a.price));
1070
+ invertedAsks.sort((a, b) => Number(a.price) - Number(b.price));
1071
+ return { bids: invertedBids, asks: invertedAsks };
1072
+ }
1073
+ return { bids, asks };
1074
+ });
1075
+ }
1076
+ async createOrder(params) {
1077
+ await this.ensureAuth();
1078
+ if (!this.wallet || !this.address) {
1079
+ throw new AuthenticationError("Wallet not initialized");
1080
+ }
1081
+ const market = await this.fetchMarket(params.marketId);
1082
+ const tokens = market.metadata?.tokens ?? {};
1083
+ const tokenId = params.tokenId ?? tokens[params.outcome];
1084
+ if (!tokenId) {
1085
+ throw new InvalidOrder(`Could not find token_id for outcome '${params.outcome}'`);
1086
+ }
1087
+ if (params.price <= 0 || params.price >= 1) {
1088
+ throw new InvalidOrder(`Price must be between 0 and 1, got: ${params.price}`);
1089
+ }
1090
+ const venue = market.metadata?.venue;
1091
+ const exchangeAddress = venue?.exchange;
1092
+ if (!exchangeAddress) {
1093
+ throw new InvalidOrder("Market does not have venue.exchange address");
1094
+ }
1095
+ const orderType = params.params?.order_type?.toUpperCase() ?? "GTC";
1096
+ const feeRateBps = 300;
1097
+ const signedOrder = await this.buildSignedOrder(
1098
+ tokenId,
1099
+ params.price,
1100
+ params.size,
1101
+ params.side,
1102
+ orderType,
1103
+ exchangeAddress,
1104
+ feeRateBps
1105
+ );
1106
+ const payload = {
1107
+ order: signedOrder,
1108
+ orderType,
1109
+ marketSlug: params.marketId
1110
+ };
1111
+ if (this.ownerId) {
1112
+ payload.ownerId = this.ownerId;
1113
+ }
1114
+ return this.withRetry(async () => {
1115
+ const result = await this.request(
1116
+ "POST",
1117
+ "/orders",
1118
+ payload,
1119
+ true
1120
+ );
1121
+ const orderData = result.order ?? result;
1122
+ const orderId = orderData.id ?? orderData.orderId ?? "";
1123
+ const statusStr = orderData.status ?? "LIVE";
1124
+ return {
1125
+ id: String(orderId),
1126
+ marketId: params.marketId,
1127
+ outcome: params.outcome,
1128
+ side: params.side,
1129
+ price: params.price,
1130
+ size: params.size,
1131
+ filled: Number(orderData.filled ?? 0),
1132
+ status: this.parseOrderStatus(statusStr),
1133
+ createdAt: /* @__PURE__ */ new Date()
1134
+ };
1135
+ });
1136
+ }
1137
+ async buildSignedOrder(tokenId, price, size, side, orderType, exchangeAddress, feeRateBps) {
1138
+ if (!this.wallet || !this.address) {
1139
+ throw new AuthenticationError("Wallet not initialized");
1140
+ }
1141
+ const timestampMs = Date.now();
1142
+ const nanoOffset = Math.floor(Math.random() * 1e6);
1143
+ const oneDayMs = 1e3 * 60 * 60 * 24;
1144
+ const salt = timestampMs * 1e3 + nanoOffset + oneDayMs;
1145
+ const sharesScale = 1e6;
1146
+ const collateralScale = 1e6;
1147
+ const priceScale = 1e6;
1148
+ const priceTick = 1e-3;
1149
+ let shares = Math.floor(size * sharesScale);
1150
+ const priceInt = Math.floor(price * priceScale);
1151
+ const tickInt = Math.floor(priceTick * priceScale);
1152
+ const sharesStep = Math.floor(priceScale / tickInt);
1153
+ if (shares % sharesStep !== 0) {
1154
+ shares = Math.floor(shares / sharesStep) * sharesStep;
1155
+ }
1156
+ const numerator = shares * priceInt * collateralScale;
1157
+ const denominator = sharesScale * priceScale;
1158
+ const sideInt = side === OrderSide.BUY ? 0 : 1;
1159
+ let makerAmount;
1160
+ let takerAmount;
1161
+ if (side === OrderSide.BUY) {
1162
+ const collateral = Math.ceil(numerator / denominator);
1163
+ makerAmount = collateral;
1164
+ takerAmount = shares;
1165
+ } else {
1166
+ const collateral = Math.floor(numerator / denominator);
1167
+ makerAmount = shares;
1168
+ takerAmount = collateral;
1169
+ }
1170
+ const orderForSigning = {
1171
+ salt,
1172
+ maker: this.address,
1173
+ signer: this.address,
1174
+ taker: "0x0000000000000000000000000000000000000000",
1175
+ tokenId: Number.parseInt(tokenId, 10),
1176
+ makerAmount,
1177
+ takerAmount,
1178
+ expiration: 0,
1179
+ nonce: 0,
1180
+ feeRateBps,
1181
+ side: sideInt,
1182
+ signatureType: 0
1183
+ };
1184
+ const signature = await this.signOrderEip712(orderForSigning, exchangeAddress);
1185
+ const order = {
1186
+ salt,
1187
+ maker: this.address,
1188
+ signer: this.address,
1189
+ taker: "0x0000000000000000000000000000000000000000",
1190
+ tokenId,
1191
+ makerAmount,
1192
+ takerAmount,
1193
+ expiration: "0",
1194
+ nonce: 0,
1195
+ feeRateBps,
1196
+ side: sideInt,
1197
+ signatureType: 0,
1198
+ signature
1199
+ };
1200
+ if (orderType === "GTC") {
1201
+ order.price = Math.round(price * 1e3) / 1e3;
1202
+ }
1203
+ return order;
1204
+ }
1205
+ async signOrderEip712(order, exchangeAddress) {
1206
+ if (!this.wallet) {
1207
+ throw new AuthenticationError("Wallet not initialized");
1208
+ }
1209
+ const domain = {
1210
+ name: "Limitless CTF Exchange",
1211
+ version: "1",
1212
+ chainId: this.chainId,
1213
+ verifyingContract: exchangeAddress
1214
+ };
1215
+ const types = {
1216
+ Order: [
1217
+ { name: "salt", type: "uint256" },
1218
+ { name: "maker", type: "address" },
1219
+ { name: "signer", type: "address" },
1220
+ { name: "taker", type: "address" },
1221
+ { name: "tokenId", type: "uint256" },
1222
+ { name: "makerAmount", type: "uint256" },
1223
+ { name: "takerAmount", type: "uint256" },
1224
+ { name: "expiration", type: "uint256" },
1225
+ { name: "nonce", type: "uint256" },
1226
+ { name: "feeRateBps", type: "uint256" },
1227
+ { name: "side", type: "uint8" },
1228
+ { name: "signatureType", type: "uint8" }
1229
+ ]
1230
+ };
1231
+ const value = {
1232
+ salt: order.salt,
1233
+ maker: order.maker,
1234
+ signer: order.signer,
1235
+ taker: order.taker,
1236
+ tokenId: order.tokenId,
1237
+ makerAmount: order.makerAmount,
1238
+ takerAmount: order.takerAmount,
1239
+ expiration: order.expiration,
1240
+ nonce: order.nonce,
1241
+ feeRateBps: order.feeRateBps,
1242
+ side: order.side,
1243
+ signatureType: order.signatureType
1244
+ };
1245
+ const signature = await this.wallet._signTypedData(domain, types, value);
1246
+ return signature;
1247
+ }
1248
+ async cancelOrder(orderId, marketId) {
1249
+ await this.ensureAuth();
1250
+ return this.withRetry(async () => {
1251
+ await this.request("DELETE", `/orders/${orderId}`, void 0, true);
1252
+ return {
1253
+ id: orderId,
1254
+ marketId: marketId ?? "",
1255
+ outcome: "",
1256
+ side: OrderSide.BUY,
1257
+ price: 0,
1258
+ size: 0,
1259
+ filled: 0,
1260
+ status: OrderStatus.CANCELLED,
1261
+ createdAt: /* @__PURE__ */ new Date()
1262
+ };
1263
+ });
1264
+ }
1265
+ async fetchOrder(orderId, _marketId) {
1266
+ await this.ensureAuth();
1267
+ return this.withRetry(async () => {
1268
+ const data = await this.request("GET", `/orders/${orderId}`, void 0, true);
1269
+ return this.parseOrder(data);
1270
+ });
1271
+ }
1272
+ async fetchOpenOrders(marketId) {
1273
+ await this.ensureAuth();
1274
+ const queryParams = { statuses: "LIVE" };
1275
+ let endpoint;
1276
+ if (marketId) {
1277
+ endpoint = `/markets/${marketId}/user-orders`;
1278
+ } else {
1279
+ endpoint = "/orders";
1280
+ }
1281
+ let tokenToOutcome;
1282
+ if (marketId) {
1283
+ try {
1284
+ const market = await this.fetchMarket(marketId);
1285
+ const tokens = market.metadata?.tokens ?? {};
1286
+ tokenToOutcome = /* @__PURE__ */ new Map();
1287
+ for (const [outcome, tokenId] of Object.entries(tokens)) {
1288
+ if (tokenId) {
1289
+ tokenToOutcome.set(tokenId, outcome);
1290
+ }
1291
+ }
1292
+ } catch {
1293
+ tokenToOutcome = void 0;
1294
+ }
1295
+ }
1296
+ return this.withRetry(async () => {
1297
+ const response = await this.request(
1298
+ "GET",
1299
+ endpoint,
1300
+ queryParams,
1301
+ true
1302
+ );
1303
+ const ordersData = Array.isArray(response) ? response : response.data ?? [];
1304
+ return ordersData.map((o) => this.parseOrder(o, tokenToOutcome));
1305
+ });
1306
+ }
1307
+ async fetchPositions(marketId) {
1308
+ await this.ensureAuth();
1309
+ return this.withRetry(async () => {
1310
+ const response = await this.request(
1311
+ "GET",
1312
+ "/portfolio/positions",
1313
+ void 0,
1314
+ true
1315
+ );
1316
+ const clobPositions = response.clob ?? [];
1317
+ const positions = [];
1318
+ for (const posData of clobPositions) {
1319
+ const parsed = this.parsePortfolioPositions(posData);
1320
+ for (const pos of parsed) {
1321
+ if (marketId && pos.marketId !== marketId) {
1322
+ continue;
1323
+ }
1324
+ positions.push(pos);
1325
+ }
1326
+ }
1327
+ return positions;
1328
+ });
1329
+ }
1330
+ async fetchBalance() {
1331
+ await this.ensureAuth();
1332
+ if (!this.address) {
1333
+ throw new AuthenticationError("Wallet address not available");
1334
+ }
1335
+ const usdcAddress = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1336
+ const baseRpc = "https://mainnet.base.org";
1337
+ try {
1338
+ const data = `0x70a08231000000000000000000000000${this.address.slice(2).toLowerCase()}`;
1339
+ const response = await fetch(baseRpc, {
1340
+ method: "POST",
1341
+ headers: { "Content-Type": "application/json" },
1342
+ body: JSON.stringify({
1343
+ jsonrpc: "2.0",
1344
+ method: "eth_call",
1345
+ params: [{ to: usdcAddress, data }, "latest"],
1346
+ id: 1
1347
+ })
1348
+ });
1349
+ const result = await response.json();
1350
+ const balanceHex = result.result ?? "0x0";
1351
+ const balanceWei = Number.parseInt(balanceHex, 16);
1352
+ const usdcBalance = balanceWei / 10 ** 6;
1353
+ return { USDC: usdcBalance };
1354
+ } catch {
1355
+ try {
1356
+ const response = await this.request(
1357
+ "GET",
1358
+ "/portfolio/trading/allowance",
1359
+ { type: "clob" },
1360
+ true
1361
+ );
1362
+ const balance = response.balance ?? response.allowance ?? 0;
1363
+ return { USDC: balance };
1364
+ } catch {
1365
+ return { USDC: 0 };
1366
+ }
1367
+ }
1368
+ }
1369
+ describe() {
1370
+ return {
1371
+ id: this.id,
1372
+ name: this.name,
1373
+ has: {
1374
+ fetchMarkets: true,
1375
+ fetchMarket: true,
1376
+ createOrder: true,
1377
+ cancelOrder: true,
1378
+ fetchOrder: true,
1379
+ fetchOpenOrders: true,
1380
+ fetchPositions: true,
1381
+ fetchBalance: true,
1382
+ websocket: true
1383
+ }
1384
+ };
1385
+ }
1386
+ };
1387
+ var BASE_URL2 = "https://proxy.opinion.trade:8443";
1388
+ var CHAIN_ID2 = 56;
1389
+ var Opinion = class extends Exchange {
1390
+ id = "opinion";
1391
+ name = "Opinion";
1392
+ apiKey;
1393
+ multiSigAddr;
1394
+ chainId;
1395
+ host;
1396
+ wallet = null;
1397
+ constructor(config = {}) {
1398
+ super(config);
1399
+ this.apiKey = config.apiKey ?? "";
1400
+ this.multiSigAddr = config.multiSigAddr ?? "";
1401
+ this.chainId = config.chainId ?? CHAIN_ID2;
1402
+ this.host = config.host ?? BASE_URL2;
1403
+ if (config.privateKey) {
1404
+ this.wallet = new Wallet(config.privateKey);
1405
+ }
1406
+ }
1407
+ async request(method, endpoint, params) {
1408
+ const url = new URL(`${this.host}${endpoint}`);
1409
+ if (method === "GET" && params) {
1410
+ for (const [key, value] of Object.entries(params)) {
1411
+ if (value !== void 0 && value !== null) {
1412
+ url.searchParams.set(key, String(value));
1413
+ }
1414
+ }
1415
+ }
1416
+ const headers = {
1417
+ "Content-Type": "application/json"
1418
+ };
1419
+ if (this.apiKey) {
1420
+ headers.Authorization = `Bearer ${this.apiKey}`;
1421
+ headers["X-API-Key"] = this.apiKey;
1422
+ }
1423
+ const fetchOptions = {
1424
+ method,
1425
+ headers
1426
+ };
1427
+ if (method !== "GET" && params) {
1428
+ fetchOptions.body = JSON.stringify(params);
1429
+ }
1430
+ const response = await fetch(url.toString(), fetchOptions);
1431
+ if (!response.ok) {
1432
+ if (response.status === 429) {
1433
+ throw new NetworkError("Rate limited");
1434
+ }
1435
+ if (response.status === 401 || response.status === 403) {
1436
+ throw new AuthenticationError("Authentication failed");
1437
+ }
1438
+ throw new NetworkError(`HTTP ${response.status}`);
1439
+ }
1440
+ return response.json();
1441
+ }
1442
+ ensureAuth() {
1443
+ if (!this.apiKey || !this.wallet || !this.multiSigAddr) {
1444
+ throw new AuthenticationError("API key, private key, and multiSigAddr required");
1445
+ }
1446
+ }
1447
+ parseMarket(data, _fetchPrices = false) {
1448
+ const marketId = String(data.market_id ?? data.topic_id ?? data.id ?? "");
1449
+ const question = data.market_title ?? data.title ?? data.question ?? "";
1450
+ let outcomes = [];
1451
+ const tokenIds = [];
1452
+ const prices = {};
1453
+ const yesTokenId = String(data.yes_token_id ?? "");
1454
+ const noTokenId = String(data.no_token_id ?? "");
1455
+ const yesLabel = data.yes_label ?? "Yes";
1456
+ const noLabel = data.no_label ?? "No";
1457
+ const childMarkets = data.child_markets ?? [];
1458
+ if (yesTokenId && noTokenId) {
1459
+ outcomes = [yesLabel, noLabel];
1460
+ tokenIds.push(yesTokenId, noTokenId);
1461
+ } else if (childMarkets.length > 0) {
1462
+ for (const child of childMarkets) {
1463
+ const childTitle = child.market_title ?? "";
1464
+ const childYesToken = String(child.yes_token_id ?? "");
1465
+ if (childTitle && childYesToken) {
1466
+ outcomes.push(childTitle);
1467
+ tokenIds.push(childYesToken);
1468
+ }
1469
+ }
1470
+ }
1471
+ if (outcomes.length === 0) {
1472
+ outcomes = ["Yes", "No"];
1473
+ }
1474
+ let closeTime;
1475
+ const cutoffTime = data.cutoff_at ?? data.cutoff_time;
1476
+ if (cutoffTime && typeof cutoffTime === "number" && cutoffTime > 0) {
1477
+ closeTime = new Date(cutoffTime * 1e3);
1478
+ }
1479
+ const volume = typeof data.volume === "string" ? Number.parseFloat(data.volume) : data.volume ?? 0;
1480
+ const liquidity = data.liquidity ?? 0;
1481
+ const tickSize = 1e-3;
1482
+ const metadata = {
1483
+ topic_id: marketId,
1484
+ market_id: marketId,
1485
+ condition_id: data.condition_id ?? "",
1486
+ status: data.status ?? "",
1487
+ chain_id: this.chainId,
1488
+ clobTokenIds: tokenIds,
1489
+ token_ids: tokenIds,
1490
+ tokens: Object.fromEntries(outcomes.map((o, i) => [o, tokenIds[i] ?? ""])),
1491
+ description: data.description ?? data.rules ?? "",
1492
+ category: data.category ?? "",
1493
+ image_url: data.image_url ?? "",
1494
+ minimum_tick_size: tickSize,
1495
+ closed: data.status === "RESOLVED"
1496
+ };
1497
+ return {
1498
+ id: marketId,
1499
+ question,
1500
+ outcomes,
1501
+ closeTime,
1502
+ volume,
1503
+ liquidity,
1504
+ prices,
1505
+ tickSize,
1506
+ description: String(metadata.description ?? ""),
1507
+ metadata
1508
+ };
1509
+ }
1510
+ parseOrder(data) {
1511
+ const orderId = String(data.order_id ?? data.id ?? data.orderID ?? "");
1512
+ const marketId = String(data.topic_id ?? data.market_id ?? "");
1513
+ let side;
1514
+ if (data.side_enum) {
1515
+ side = data.side_enum.toLowerCase() === "buy" ? OrderSide.BUY : OrderSide.SELL;
1516
+ } else if (typeof data.side === "string") {
1517
+ side = data.side.toLowerCase() === "buy" ? OrderSide.BUY : OrderSide.SELL;
1518
+ } else {
1519
+ side = data.side === 1 ? OrderSide.BUY : OrderSide.SELL;
1520
+ }
1521
+ const statusVal = data.status;
1522
+ let status;
1523
+ if (typeof statusVal === "number") {
1524
+ const statusMap = {
1525
+ 0: OrderStatus.PENDING,
1526
+ 1: OrderStatus.OPEN,
1527
+ 2: OrderStatus.FILLED,
1528
+ 3: OrderStatus.PARTIALLY_FILLED,
1529
+ 4: OrderStatus.CANCELLED
1530
+ };
1531
+ status = statusMap[statusVal] ?? OrderStatus.OPEN;
1532
+ } else {
1533
+ const strStatus = String(statusVal).toLowerCase();
1534
+ if (strStatus === "filled" || strStatus === "matched") {
1535
+ status = OrderStatus.FILLED;
1536
+ } else if (strStatus === "cancelled" || strStatus === "canceled") {
1537
+ status = OrderStatus.CANCELLED;
1538
+ } else if (strStatus === "partially_filled") {
1539
+ status = OrderStatus.PARTIALLY_FILLED;
1540
+ } else if (strStatus === "pending") {
1541
+ status = OrderStatus.PENDING;
1542
+ } else {
1543
+ status = OrderStatus.OPEN;
1544
+ }
1545
+ }
1546
+ const price = data.price ?? 0;
1547
+ const size = data.order_shares ?? data.maker_amount ?? data.size ?? 0;
1548
+ const filled = data.filled_shares ?? data.matched_amount ?? data.filled ?? 0;
1549
+ let createdAt = /* @__PURE__ */ new Date();
1550
+ if (data.created_at) {
1551
+ createdAt = typeof data.created_at === "number" ? new Date(data.created_at * 1e3) : new Date(data.created_at);
1552
+ }
1553
+ return {
1554
+ id: orderId,
1555
+ marketId,
1556
+ outcome: data.outcome ?? "",
1557
+ side,
1558
+ price,
1559
+ size,
1560
+ filled,
1561
+ status,
1562
+ createdAt
1563
+ };
1564
+ }
1565
+ parsePosition(data) {
1566
+ return {
1567
+ marketId: String(data.topic_id ?? data.market_id ?? ""),
1568
+ outcome: data.outcome ?? data.token_name ?? "",
1569
+ size: data.shares_owned ?? data.size ?? data.balance ?? 0,
1570
+ averagePrice: data.avg_entry_price ?? data.average_price ?? 0,
1571
+ currentPrice: data.current_price ?? data.price ?? 0
1572
+ };
1573
+ }
1574
+ async fetchMarkets(params) {
1575
+ return this.withRetry(async () => {
1576
+ const queryParams = {
1577
+ topic_type: "ALL",
1578
+ status: params?.active === false ? "ALL" : "ACTIVATED",
1579
+ page: params?.offset ? Math.floor(params.offset / 20) + 1 : 1,
1580
+ limit: Math.min(params?.limit ?? 20, 20)
1581
+ };
1582
+ const response = await this.request("GET", "/api/v1/markets", queryParams);
1583
+ if (response.errno !== 0) {
1584
+ throw new ExchangeError(`Failed to fetch markets: ${response.errmsg}`);
1585
+ }
1586
+ const marketsList = response.result?.list ?? [];
1587
+ const markets = marketsList.map((m) => this.parseMarket(m));
1588
+ if (params?.limit) {
1589
+ return markets.slice(0, params.limit);
1590
+ }
1591
+ return markets;
1592
+ });
1593
+ }
1594
+ async fetchMarket(marketId) {
1595
+ return this.withRetry(async () => {
1596
+ const response = await this.request("GET", `/api/v1/markets/${marketId}`);
1597
+ if (response.errno !== 0 || !response.result?.data) {
1598
+ throw new MarketNotFound(`Market ${marketId} not found`);
1599
+ }
1600
+ return this.parseMarket(response.result.data, true);
1601
+ });
1602
+ }
1603
+ async getOrderbook(tokenId) {
1604
+ return this.withRetry(async () => {
1605
+ const response = await this.request("GET", "/api/v1/orderbook", { token_id: tokenId });
1606
+ const bids = [];
1607
+ const asks = [];
1608
+ if (response.errno === 0 && response.result) {
1609
+ const result = response.result;
1610
+ for (const bid of result.bids ?? []) {
1611
+ const price = Number(bid.price);
1612
+ const size = Number(bid.size);
1613
+ if (price > 0 && size > 0) {
1614
+ bids.push({ price: String(price), size: String(size) });
1615
+ }
1616
+ }
1617
+ for (const ask of result.asks ?? []) {
1618
+ const price = Number(ask.price);
1619
+ const size = Number(ask.size);
1620
+ if (price > 0 && size > 0) {
1621
+ asks.push({ price: String(price), size: String(size) });
1622
+ }
1623
+ }
1624
+ bids.sort((a, b) => Number(b.price) - Number(a.price));
1625
+ asks.sort((a, b) => Number(a.price) - Number(b.price));
1626
+ }
1627
+ return { bids, asks };
1628
+ });
1629
+ }
1630
+ async createOrder(params) {
1631
+ this.ensureAuth();
1632
+ const tokenId = params.tokenId ?? params.params?.token_id;
1633
+ if (!tokenId) {
1634
+ throw new InvalidOrder("token_id required in params");
1635
+ }
1636
+ if (params.price <= 0 || params.price >= 1) {
1637
+ throw new InvalidOrder("Price must be between 0 and 1");
1638
+ }
1639
+ return this.withRetry(async () => {
1640
+ const orderData = {
1641
+ market_id: Number(params.marketId),
1642
+ token_id: tokenId,
1643
+ side: params.side === OrderSide.BUY ? 1 : 2,
1644
+ price: String(params.price),
1645
+ size: String(params.size),
1646
+ order_type: "LIMIT"
1647
+ };
1648
+ const response = await this.request(
1649
+ "POST",
1650
+ "/api/v1/orders",
1651
+ orderData
1652
+ );
1653
+ if (response.errno !== 0) {
1654
+ throw new InvalidOrder(`Order failed: ${response.errmsg}`);
1655
+ }
1656
+ const orderId = response.result?.data?.order_id ?? "";
1657
+ return {
1658
+ id: orderId,
1659
+ marketId: params.marketId,
1660
+ outcome: params.outcome,
1661
+ side: params.side,
1662
+ price: params.price,
1663
+ size: params.size,
1664
+ filled: 0,
1665
+ status: OrderStatus.OPEN,
1666
+ createdAt: /* @__PURE__ */ new Date()
1667
+ };
1668
+ });
1669
+ }
1670
+ async cancelOrder(orderId, marketId) {
1671
+ this.ensureAuth();
1672
+ return this.withRetry(async () => {
1673
+ const response = await this.request("POST", `/api/v1/orders/${orderId}/cancel`);
1674
+ if (response.errno !== 0) {
1675
+ throw new ExchangeError(`Failed to cancel order: ${response.errmsg}`);
1676
+ }
1677
+ return {
1678
+ id: orderId,
1679
+ marketId: marketId ?? "",
1680
+ outcome: "",
1681
+ side: OrderSide.BUY,
1682
+ price: 0,
1683
+ size: 0,
1684
+ filled: 0,
1685
+ status: OrderStatus.CANCELLED,
1686
+ createdAt: /* @__PURE__ */ new Date()
1687
+ };
1688
+ });
1689
+ }
1690
+ async fetchOrder(orderId, _marketId) {
1691
+ this.ensureAuth();
1692
+ return this.withRetry(async () => {
1693
+ const response = await this.request("GET", `/api/v1/orders/${orderId}`);
1694
+ if (response.errno !== 0 || !response.result?.data) {
1695
+ throw new ExchangeError(`Order ${orderId} not found`);
1696
+ }
1697
+ return this.parseOrder(response.result.data);
1698
+ });
1699
+ }
1700
+ async fetchOpenOrders(marketId) {
1701
+ this.ensureAuth();
1702
+ return this.withRetry(async () => {
1703
+ const params = {
1704
+ status: "1",
1705
+ page: 1,
1706
+ limit: 100
1707
+ };
1708
+ if (marketId) {
1709
+ params.market_id = Number(marketId);
1710
+ }
1711
+ const response = await this.request("GET", "/api/v1/orders", params);
1712
+ if (response.errno !== 0) {
1713
+ return [];
1714
+ }
1715
+ const ordersList = response.result?.list ?? [];
1716
+ return ordersList.map((o) => this.parseOrder(o));
1717
+ });
1718
+ }
1719
+ async fetchPositions(marketId) {
1720
+ this.ensureAuth();
1721
+ return this.withRetry(async () => {
1722
+ const params = {
1723
+ page: 1,
1724
+ limit: 100
1725
+ };
1726
+ if (marketId) {
1727
+ params.market_id = Number(marketId);
1728
+ }
1729
+ const response = await this.request("GET", "/api/v1/positions", params);
1730
+ if (response.errno !== 0) {
1731
+ return [];
1732
+ }
1733
+ const positionsList = response.result?.list ?? [];
1734
+ return positionsList.map((p) => this.parsePosition(p));
1735
+ });
1736
+ }
1737
+ async fetchBalance() {
1738
+ this.ensureAuth();
1739
+ return this.withRetry(async () => {
1740
+ const response = await this.request(
1741
+ "GET",
1742
+ "/api/v1/balances"
1743
+ );
1744
+ if (response.errno !== 0) {
1745
+ throw new ExchangeError(`Failed to fetch balance: ${response.errmsg}`);
1746
+ }
1747
+ const result = response.result;
1748
+ const balances = result?.balances ?? [];
1749
+ if (balances.length > 0) {
1750
+ const balance = balances[0]?.available_balance ?? 0;
1751
+ return { USDC: balance };
1752
+ }
1753
+ return { USDC: 0 };
1754
+ });
1755
+ }
1756
+ describe() {
1757
+ return {
1758
+ id: this.id,
1759
+ name: this.name,
1760
+ has: {
1761
+ fetchMarkets: true,
1762
+ fetchMarket: true,
1763
+ createOrder: true,
1764
+ cancelOrder: true,
1765
+ fetchOrder: true,
1766
+ fetchOpenOrders: true,
1767
+ fetchPositions: true,
1768
+ fetchBalance: true,
1769
+ websocket: false
1770
+ }
1771
+ };
1772
+ }
1773
+ };
1774
+ var BASE_URL3 = "https://gamma-api.polymarket.com";
1775
+ var CLOB_URL = "https://clob.polymarket.com";
1776
+ var Polymarket = class extends Exchange {
1777
+ id = "polymarket";
1778
+ name = "Polymarket";
1779
+ clobClient = null;
1780
+ wallet = null;
1781
+ address = null;
1782
+ constructor(config = {}) {
1783
+ super(config);
1784
+ if (config.privateKey) {
1785
+ this.initializeClobClient(config);
1786
+ }
1787
+ }
1788
+ describe() {
1789
+ const base = super.describe();
1790
+ return { ...base, has: { ...base.has, websocket: true } };
1791
+ }
1792
+ initializeClobClient(config) {
1793
+ try {
1794
+ const chainId = config.chainId ?? 137;
1795
+ const signatureType = config.signatureType ?? 2;
1796
+ if (!config.privateKey) {
1797
+ return;
1798
+ }
1799
+ this.wallet = new Wallet(config.privateKey);
1800
+ this.clobClient = new ClobClient(
1801
+ CLOB_URL,
1802
+ chainId,
1803
+ this.wallet,
1804
+ void 0,
1805
+ signatureType,
1806
+ config.funder
1807
+ );
1808
+ this.address = this.wallet.address;
1809
+ } catch (error) {
1810
+ throw new AuthenticationError(`Failed to initialize CLOB client: ${error}`);
1811
+ }
1812
+ }
1813
+ async fetchMarkets(params) {
1814
+ return this.withRetry(async () => {
1815
+ const response = await fetch(`${CLOB_URL}/sampling-markets`, {
1816
+ signal: AbortSignal.timeout(this.timeout)
1817
+ });
1818
+ if (!response.ok) {
1819
+ throw new NetworkError(`Failed to fetch markets: ${response.status}`);
1820
+ }
1821
+ const result = await response.json();
1822
+ const marketsData = result.data ?? (Array.isArray(result) ? result : []);
1823
+ let markets = marketsData.map((item) => this.parseSamplingMarket(item)).filter((m) => m !== null);
1824
+ if (params?.active || !params?.closed) {
1825
+ markets = markets.filter((m) => this.isMarketOpen(m));
1826
+ }
1827
+ if (params?.limit) {
1828
+ markets = markets.slice(0, params.limit);
1829
+ }
1830
+ return markets;
1831
+ });
1832
+ }
1833
+ async fetchMarket(marketId) {
1834
+ return this.withRetry(async () => {
1835
+ const response = await fetch(`${BASE_URL3}/markets/${marketId}`, {
1836
+ signal: AbortSignal.timeout(this.timeout)
1837
+ });
1838
+ if (response.status === 404) {
1839
+ throw new MarketNotFound(`Market ${marketId} not found`);
1840
+ }
1841
+ if (!response.ok) {
1842
+ throw new NetworkError(`Failed to fetch market: ${response.status}`);
1843
+ }
1844
+ const data = await response.json();
1845
+ return this.parseGammaMarket(data);
1846
+ });
1847
+ }
1848
+ async fetchMarketsBySlug(slugOrUrl) {
1849
+ const slug = this.parseMarketIdentifier(slugOrUrl);
1850
+ if (!slug) throw new Error("Empty slug provided");
1851
+ return this.withRetry(async () => {
1852
+ const response = await fetch(`${BASE_URL3}/events?slug=${slug}`, {
1853
+ signal: AbortSignal.timeout(this.timeout)
1854
+ });
1855
+ if (response.status === 404) {
1856
+ throw new MarketNotFound(`Event not found: ${slug}`);
1857
+ }
1858
+ if (!response.ok) {
1859
+ throw new ExchangeError(`Failed to fetch event: ${response.status}`);
1860
+ }
1861
+ const eventData = await response.json();
1862
+ if (!eventData.length) {
1863
+ throw new MarketNotFound(`Event not found: ${slug}`);
1864
+ }
1865
+ const event = eventData[0];
1866
+ const marketsData = event?.markets ?? [];
1867
+ return marketsData.map((m) => this.parseGammaMarket(m));
1868
+ });
1869
+ }
1870
+ async createOrder(params) {
1871
+ const client = this.clobClient;
1872
+ if (!client) {
1873
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1874
+ }
1875
+ const tokenId = params.tokenId ?? params.params?.token_id;
1876
+ if (!tokenId) {
1877
+ throw new InvalidOrder("token_id required in params");
1878
+ }
1879
+ return this.withRetry(async () => {
1880
+ const signedOrder = await client.createOrder({
1881
+ tokenID: tokenId,
1882
+ price: params.price,
1883
+ size: params.size,
1884
+ side: params.side === OrderSide.BUY ? Side.BUY : Side.SELL
1885
+ });
1886
+ const result = await client.postOrder(signedOrder);
1887
+ const orderId = result.orderID ?? "";
1888
+ const statusStr = result.status ?? "LIVE";
1889
+ return {
1890
+ id: orderId,
1891
+ marketId: params.marketId,
1892
+ outcome: params.outcome,
1893
+ side: params.side,
1894
+ price: params.price,
1895
+ size: params.size,
1896
+ filled: 0,
1897
+ status: this.parseOrderStatus(statusStr),
1898
+ createdAt: /* @__PURE__ */ new Date(),
1899
+ updatedAt: /* @__PURE__ */ new Date()
1900
+ };
1901
+ });
1902
+ }
1903
+ async cancelOrder(orderId, marketId) {
1904
+ const client = this.clobClient;
1905
+ if (!client) {
1906
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1907
+ }
1908
+ return this.withRetry(async () => {
1909
+ await client.cancelOrder({ orderID: orderId });
1910
+ return {
1911
+ id: orderId,
1912
+ marketId: marketId ?? "",
1913
+ outcome: "",
1914
+ side: OrderSide.BUY,
1915
+ price: 0,
1916
+ size: 0,
1917
+ filled: 0,
1918
+ status: OrderStatus.CANCELLED,
1919
+ createdAt: /* @__PURE__ */ new Date(),
1920
+ updatedAt: /* @__PURE__ */ new Date()
1921
+ };
1922
+ });
1923
+ }
1924
+ async fetchOrder(orderId, _marketId) {
1925
+ const client = this.clobClient;
1926
+ if (!client) {
1927
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1928
+ }
1929
+ return this.withRetry(async () => {
1930
+ const data = await client.getOrder(orderId);
1931
+ return this.parseOrder(data);
1932
+ });
1933
+ }
1934
+ async fetchOpenOrders(marketId) {
1935
+ const client = this.clobClient;
1936
+ if (!client) {
1937
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1938
+ }
1939
+ return this.withRetry(async () => {
1940
+ const response = await client.getOpenOrders();
1941
+ let orders = response;
1942
+ if (marketId) {
1943
+ orders = orders.filter((o) => o.market === marketId);
1944
+ }
1945
+ return orders.map((o) => this.parseOrder(o));
1946
+ });
1947
+ }
1948
+ async fetchPositions(_marketId) {
1949
+ if (!this.clobClient) {
1950
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1951
+ }
1952
+ return [];
1953
+ }
1954
+ async fetchBalance() {
1955
+ const client = this.clobClient;
1956
+ if (!client) {
1957
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
1958
+ }
1959
+ return this.withRetry(async () => {
1960
+ const balanceData = await client.getBalanceAllowance({
1961
+ asset_type: AssetType.COLLATERAL
1962
+ });
1963
+ const balance = balanceData.balance ? Number.parseFloat(balanceData.balance) / 1e6 : 0;
1964
+ return { USDC: balance };
1965
+ });
1966
+ }
1967
+ async getOrderbook(tokenId) {
1968
+ return this.withRetry(async () => {
1969
+ const response = await fetch(`${CLOB_URL}/book?token_id=${tokenId}`, {
1970
+ signal: AbortSignal.timeout(this.timeout)
1971
+ });
1972
+ if (!response.ok) {
1973
+ return { bids: [], asks: [] };
1974
+ }
1975
+ return response.json();
1976
+ });
1977
+ }
1978
+ parseMarketIdentifier(identifier) {
1979
+ if (!identifier) return "";
1980
+ if (identifier.startsWith("http")) {
1981
+ const url = identifier.split("?")[0] ?? "";
1982
+ const parts = url.replace(/\/$/, "").split("/");
1983
+ const eventIndex = parts.indexOf("event");
1984
+ if (eventIndex !== -1 && eventIndex + 1 < parts.length) {
1985
+ return parts[eventIndex + 1] ?? "";
1986
+ }
1987
+ return parts[parts.length - 1] ?? "";
1988
+ }
1989
+ return identifier;
1990
+ }
1991
+ parseSamplingMarket(data) {
1992
+ const conditionId = data.condition_id;
1993
+ if (!conditionId) return null;
1994
+ const tokens = data.tokens ?? [];
1995
+ const tokenIds = [];
1996
+ const outcomes = [];
1997
+ const prices = {};
1998
+ for (const token of tokens) {
1999
+ if (token.token_id) tokenIds.push(String(token.token_id));
2000
+ if (token.outcome) outcomes.push(String(token.outcome));
2001
+ if (token.outcome && token.price != null) {
2002
+ prices[String(token.outcome)] = Number(token.price);
2003
+ }
2004
+ }
2005
+ const tickSize = data.minimum_tick_size ?? 0.01;
2006
+ return {
2007
+ id: conditionId,
2008
+ question: data.question ?? "",
2009
+ outcomes: outcomes.length ? outcomes : ["Yes", "No"],
2010
+ closeTime: void 0,
2011
+ volume: 0,
2012
+ liquidity: 0,
2013
+ prices,
2014
+ tickSize,
2015
+ description: data.description ?? "",
2016
+ metadata: {
2017
+ ...data,
2018
+ clobTokenIds: tokenIds,
2019
+ conditionId,
2020
+ minimumTickSize: tickSize
2021
+ }
2022
+ };
2023
+ }
2024
+ parseGammaMarket(data) {
2025
+ let outcomes = data.outcomes ?? [];
2026
+ if (typeof data.outcomes === "string") {
2027
+ try {
2028
+ outcomes = JSON.parse(data.outcomes);
2029
+ } catch {
2030
+ outcomes = [];
2031
+ }
2032
+ }
2033
+ let pricesList = [];
2034
+ if (data.outcomePrices != null) {
2035
+ if (typeof data.outcomePrices === "string") {
2036
+ try {
2037
+ pricesList = JSON.parse(data.outcomePrices);
2038
+ } catch {
2039
+ pricesList = [];
2040
+ }
2041
+ } else if (Array.isArray(data.outcomePrices)) {
2042
+ pricesList = data.outcomePrices;
2043
+ }
2044
+ }
2045
+ const prices = {};
2046
+ for (let i = 0; i < outcomes.length && i < pricesList.length; i++) {
2047
+ const outcome = outcomes[i];
2048
+ const price = pricesList[i];
2049
+ if (outcome && price != null) {
2050
+ const priceVal = Number(price);
2051
+ if (priceVal > 0) {
2052
+ prices[outcome] = priceVal;
2053
+ }
2054
+ }
2055
+ }
2056
+ const closeTime = this.parseDateTime(data.endDate);
2057
+ const volume = Number(data.volumeNum ?? data.volume ?? 0);
2058
+ const liquidity = Number(data.liquidityNum ?? data.liquidity ?? 0);
2059
+ const tickSize = data.minimum_tick_size ?? 0.01;
2060
+ let clobTokenIds = data.clobTokenIds;
2061
+ if (typeof clobTokenIds === "string") {
2062
+ try {
2063
+ clobTokenIds = JSON.parse(clobTokenIds);
2064
+ } catch {
2065
+ clobTokenIds = void 0;
2066
+ }
2067
+ }
2068
+ return {
2069
+ id: data.id ?? "",
2070
+ question: data.question ?? "",
2071
+ outcomes,
2072
+ closeTime,
2073
+ volume,
2074
+ liquidity,
2075
+ prices,
2076
+ tickSize,
2077
+ description: data.description ?? "",
2078
+ metadata: {
2079
+ ...data,
2080
+ clobTokenIds,
2081
+ minimumTickSize: tickSize
2082
+ }
2083
+ };
2084
+ }
2085
+ parseOrder(data) {
2086
+ const orderId = data.id ?? data.orderID ?? "";
2087
+ const size = Number(data.size ?? data.original_size ?? data.amount ?? 0);
2088
+ const filled = Number(data.filled ?? data.matched ?? 0);
2089
+ return {
2090
+ id: orderId,
2091
+ marketId: data.market_id ?? data.market ?? "",
2092
+ outcome: data.outcome ?? "",
2093
+ side: (data.side ?? "buy").toLowerCase() === "buy" ? OrderSide.BUY : OrderSide.SELL,
2094
+ price: Number(data.price ?? 0),
2095
+ size,
2096
+ filled,
2097
+ status: this.parseOrderStatus(data.status),
2098
+ createdAt: this.parseDateTime(data.created_at) ?? /* @__PURE__ */ new Date(),
2099
+ updatedAt: this.parseDateTime(data.updated_at)
2100
+ };
2101
+ }
2102
+ parseOrderStatus(status) {
2103
+ const statusMap = {
2104
+ pending: OrderStatus.PENDING,
2105
+ open: OrderStatus.OPEN,
2106
+ live: OrderStatus.OPEN,
2107
+ filled: OrderStatus.FILLED,
2108
+ matched: OrderStatus.FILLED,
2109
+ partially_filled: OrderStatus.PARTIALLY_FILLED,
2110
+ cancelled: OrderStatus.CANCELLED,
2111
+ canceled: OrderStatus.CANCELLED,
2112
+ rejected: OrderStatus.REJECTED
2113
+ };
2114
+ return statusMap[(status ?? "").toLowerCase()] ?? OrderStatus.OPEN;
2115
+ }
2116
+ isMarketOpen(market) {
2117
+ if (market.metadata.closed) return false;
2118
+ if (!market.closeTime) return true;
2119
+ return /* @__PURE__ */ new Date() < market.closeTime;
2120
+ }
2121
+ get walletAddress() {
2122
+ return this.address;
2123
+ }
2124
+ async fetchTokenIds(conditionId) {
2125
+ return this.withRetry(async () => {
2126
+ const response = await fetch(`${CLOB_URL}/simplified-markets`, {
2127
+ signal: AbortSignal.timeout(this.timeout)
2128
+ });
2129
+ if (!response.ok) {
2130
+ throw new ExchangeError(`Failed to fetch markets: ${response.status}`);
2131
+ }
2132
+ const result = await response.json();
2133
+ const markets = result.data ?? [];
2134
+ for (const market of markets) {
2135
+ const marketId = market.condition_id ?? market.id;
2136
+ if (marketId === conditionId) {
2137
+ const tokens = market.tokens ?? [];
2138
+ return tokens.map((t) => String(t.token_id ?? "")).filter(Boolean);
2139
+ }
2140
+ }
2141
+ throw new ExchangeError(`Token IDs not found for market ${conditionId}`);
2142
+ });
2143
+ }
2144
+ async fetchPositionsForMarket(market) {
2145
+ const client = this.clobClient;
2146
+ if (!client) {
2147
+ throw new AuthenticationError("CLOB client not initialized. Private key required.");
2148
+ }
2149
+ const positions = [];
2150
+ const tokenIds = MarketUtils.getTokenIds(market);
2151
+ if (tokenIds.length < 2) return positions;
2152
+ for (let i = 0; i < tokenIds.length; i++) {
2153
+ const tokenId = tokenIds[i];
2154
+ if (!tokenId) continue;
2155
+ try {
2156
+ const balanceData = await client.getBalanceAllowance({
2157
+ asset_type: AssetType.CONDITIONAL,
2158
+ token_id: tokenId
2159
+ });
2160
+ const balance = balanceData.balance ? Number.parseFloat(balanceData.balance) / 1e6 : 0;
2161
+ if (balance > 0) {
2162
+ const outcome = market.outcomes[i] ?? (i === 0 ? "Yes" : "No");
2163
+ const currentPrice = market.prices[outcome] ?? 0;
2164
+ positions.push({
2165
+ marketId: market.id,
2166
+ outcome,
2167
+ size: balance,
2168
+ averagePrice: 0,
2169
+ currentPrice
2170
+ });
2171
+ }
2172
+ } catch {
2173
+ }
2174
+ }
2175
+ return positions;
2176
+ }
2177
+ async fetchPriceHistory(marketOrId, options = {}) {
2178
+ const { outcome = 0, interval = "1m", fidelity = 10 } = options;
2179
+ const market = typeof marketOrId === "string" ? await this.fetchMarket(marketOrId) : marketOrId;
2180
+ const tokenIds = MarketUtils.getTokenIds(market);
2181
+ let outcomeIndex;
2182
+ if (typeof outcome === "number") {
2183
+ outcomeIndex = outcome;
2184
+ } else {
2185
+ outcomeIndex = market.outcomes.indexOf(outcome);
2186
+ if (outcomeIndex === -1) outcomeIndex = 0;
2187
+ }
2188
+ const tokenId = tokenIds[outcomeIndex];
2189
+ if (!tokenId) {
2190
+ throw new ExchangeError("Cannot fetch price history without token ID");
2191
+ }
2192
+ return this.withRetry(async () => {
2193
+ const url = `${CLOB_URL}/prices-history?market=${tokenId}&interval=${interval}&fidelity=${fidelity}`;
2194
+ const response = await fetch(url, { signal: AbortSignal.timeout(this.timeout) });
2195
+ if (!response.ok) {
2196
+ throw new NetworkError(`Failed to fetch price history: ${response.status}`);
2197
+ }
2198
+ const data = await response.json();
2199
+ const history = data.history ?? [];
2200
+ return history.filter((h) => h.t != null && h.p != null).map((h) => ({
2201
+ timestamp: new Date((h.t ?? 0) * 1e3),
2202
+ price: h.p ?? 0,
2203
+ raw: h
2204
+ })).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
2205
+ });
2206
+ }
2207
+ async searchMarkets(options = {}) {
2208
+ const {
2209
+ limit = 200,
2210
+ offset = 0,
2211
+ order = "volume",
2212
+ ascending = false,
2213
+ closed = false,
2214
+ tagId,
2215
+ query,
2216
+ binary,
2217
+ minLiquidity = 0
2218
+ } = options;
2219
+ return this.withRetry(async () => {
2220
+ const params = new URLSearchParams({
2221
+ limit: String(limit),
2222
+ offset: String(offset),
2223
+ order,
2224
+ ascending: String(ascending),
2225
+ closed: String(closed)
2226
+ });
2227
+ if (tagId) params.set("tag_id", tagId);
2228
+ const response = await fetch(`${BASE_URL3}/markets?${params}`, {
2229
+ signal: AbortSignal.timeout(this.timeout)
2230
+ });
2231
+ if (!response.ok) {
2232
+ throw new NetworkError(`Failed to search markets: ${response.status}`);
2233
+ }
2234
+ const data = await response.json();
2235
+ let markets = data.map((m) => this.parseGammaMarket(m));
2236
+ if (binary !== void 0) {
2237
+ markets = markets.filter((m) => MarketUtils.isBinary(m) === binary);
2238
+ }
2239
+ if (minLiquidity > 0) {
2240
+ markets = markets.filter((m) => m.liquidity >= minLiquidity);
2241
+ }
2242
+ if (query) {
2243
+ const q = query.toLowerCase();
2244
+ markets = markets.filter(
2245
+ (m) => m.question.toLowerCase().includes(q) || m.description.toLowerCase().includes(q)
2246
+ );
2247
+ }
2248
+ return markets;
2249
+ });
2250
+ }
2251
+ async fetchPublicTrades(options = {}) {
2252
+ const { limit = 100, offset = 0, side, user } = options;
2253
+ let conditionId;
2254
+ if (options.market) {
2255
+ conditionId = typeof options.market === "string" ? options.market : options.market.metadata.conditionId ?? options.market.id;
2256
+ }
2257
+ return this.withRetry(async () => {
2258
+ const params = new URLSearchParams({
2259
+ limit: String(limit),
2260
+ offset: String(offset),
2261
+ takerOnly: "true"
2262
+ });
2263
+ if (conditionId) params.set("market", conditionId);
2264
+ if (side) params.set("side", side);
2265
+ if (user) params.set("user", user);
2266
+ const response = await fetch(`https://data-api.polymarket.com/trades?${params}`, {
2267
+ signal: AbortSignal.timeout(this.timeout)
2268
+ });
2269
+ if (!response.ok) {
2270
+ throw new NetworkError(`Failed to fetch trades: ${response.status}`);
2271
+ }
2272
+ const data = await response.json();
2273
+ return data.map((row) => {
2274
+ const ts = row.timestamp;
2275
+ let timestamp;
2276
+ if (typeof ts === "number") {
2277
+ timestamp = new Date(ts * 1e3);
2278
+ } else if (typeof ts === "string" && /^\d+$/.test(ts)) {
2279
+ timestamp = new Date(Number.parseInt(ts, 10) * 1e3);
2280
+ } else {
2281
+ timestamp = /* @__PURE__ */ new Date(0);
2282
+ }
2283
+ return {
2284
+ proxyWallet: String(row.proxyWallet ?? ""),
2285
+ side: String(row.side ?? ""),
2286
+ asset: String(row.asset ?? ""),
2287
+ conditionId: String(row.conditionId ?? ""),
2288
+ size: Number(row.size ?? 0),
2289
+ price: Number(row.price ?? 0),
2290
+ timestamp,
2291
+ title: row.title,
2292
+ slug: row.slug,
2293
+ icon: row.icon,
2294
+ eventSlug: row.eventSlug,
2295
+ outcome: row.outcome,
2296
+ outcomeIndex: row.outcomeIndex,
2297
+ name: row.name,
2298
+ pseudonym: row.pseudonym,
2299
+ bio: row.bio,
2300
+ profileImage: row.profileImage,
2301
+ profileImageOptimized: row.profileImageOptimized,
2302
+ transactionHash: row.transactionHash
2303
+ };
2304
+ });
2305
+ });
2306
+ }
2307
+ async getTagBySlug(slug) {
2308
+ if (!slug) throw new Error("slug must be a non-empty string");
2309
+ return this.withRetry(async () => {
2310
+ const response = await fetch(`${BASE_URL3}/tags/slug/${slug}`, {
2311
+ signal: AbortSignal.timeout(this.timeout)
2312
+ });
2313
+ if (!response.ok) {
2314
+ throw new ExchangeError(`Failed to fetch tag: ${response.status}`);
2315
+ }
2316
+ const data = await response.json();
2317
+ return {
2318
+ id: String(data.id ?? ""),
2319
+ label: data.label,
2320
+ slug: data.slug,
2321
+ forceShow: data.forceShow,
2322
+ forceHide: data.forceHide,
2323
+ isCarousel: data.isCarousel,
2324
+ publishedAt: data.publishedAt,
2325
+ createdAt: data.createdAt,
2326
+ updatedAt: data.UpdatedAt ?? data.updatedAt,
2327
+ raw: data
2328
+ };
2329
+ });
2330
+ }
2331
+ async findCryptoHourlyMarket(options = {}) {
2332
+ const {
2333
+ tokenSymbol,
2334
+ minLiquidity = 0,
2335
+ limit = 100,
2336
+ isActive = true,
2337
+ isExpired = false
2338
+ } = options;
2339
+ const TAG_1H = "102175";
2340
+ const markets = await this.searchMarkets({
2341
+ limit,
2342
+ tagId: TAG_1H,
2343
+ closed: false,
2344
+ minLiquidity
2345
+ });
2346
+ const upDownPattern = /(?<token>Bitcoin|Ethereum|Solana|BTC|ETH|SOL)\s+Up or Down/i;
2347
+ const strikePattern = /(?:(?<token1>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA)\s+.*?(?<direction>above|below|over|under|reach)\s+[\$]?(?<price1>[\d,]+(?:\.\d+)?))|(?:[\$]?(?<price2>[\d,]+(?:\.\d+)?)\s+.*?(?<token2>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA))/i;
2348
+ for (const market of markets) {
2349
+ if (!MarketUtils.isBinary(market) || !MarketUtils.isOpen(market)) continue;
2350
+ if (market.closeTime) {
2351
+ const now = /* @__PURE__ */ new Date();
2352
+ const timeUntilExpiry = market.closeTime.getTime() - now.getTime();
2353
+ if (isExpired && timeUntilExpiry > 0) continue;
2354
+ if (!isExpired && timeUntilExpiry <= 0) continue;
2355
+ if (isActive && !isExpired && timeUntilExpiry > 36e5) continue;
2356
+ }
2357
+ const upDownMatch = upDownPattern.exec(market.question);
2358
+ if (upDownMatch?.groups) {
2359
+ const parsedToken = normalizeTokenSymbol(upDownMatch.groups.token ?? "");
2360
+ if (tokenSymbol && parsedToken !== normalizeTokenSymbol(tokenSymbol)) continue;
2361
+ return {
2362
+ market,
2363
+ crypto: {
2364
+ tokenSymbol: parsedToken,
2365
+ expiryTime: market.closeTime ?? new Date(Date.now() + 36e5),
2366
+ strikePrice: null,
2367
+ marketType: "up_down"
2368
+ }
2369
+ };
2370
+ }
2371
+ const strikeMatch = strikePattern.exec(market.question);
2372
+ if (strikeMatch?.groups) {
2373
+ const parsedToken = normalizeTokenSymbol(
2374
+ strikeMatch.groups.token1 ?? strikeMatch.groups.token2 ?? ""
2375
+ );
2376
+ const priceStr = strikeMatch.groups.price1 ?? strikeMatch.groups.price2 ?? "0";
2377
+ const parsedPrice = Number.parseFloat(priceStr.replace(/,/g, ""));
2378
+ if (tokenSymbol && parsedToken !== normalizeTokenSymbol(tokenSymbol)) continue;
2379
+ return {
2380
+ market,
2381
+ crypto: {
2382
+ tokenSymbol: parsedToken,
2383
+ expiryTime: market.closeTime ?? new Date(Date.now() + 36e5),
2384
+ strikePrice: parsedPrice,
2385
+ marketType: "strike_price"
2386
+ }
2387
+ };
2388
+ }
2389
+ }
2390
+ return null;
2391
+ }
2392
+ };
2393
+
2394
+ // src/exchanges/polymarket/polymarket-ws.ts
2395
+ var WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
2396
+ var PolymarketWebSocket = class extends OrderBookWebSocket {
2397
+ wsUrl = WS_URL;
2398
+ apiKey;
2399
+ assetSubscriptions = /* @__PURE__ */ new Map();
2400
+ constructor(config = {}) {
2401
+ super(config);
2402
+ this.apiKey = config.apiKey;
2403
+ }
2404
+ async authenticate() {
2405
+ if (this.apiKey) {
2406
+ this.send({
2407
+ type: "auth",
2408
+ apiKey: this.apiKey
2409
+ });
2410
+ }
2411
+ }
2412
+ async subscribeOrderbook(marketId) {
2413
+ const assetIds = this.assetSubscriptions.get(marketId);
2414
+ if (!assetIds) return;
2415
+ this.send({
2416
+ type: "subscribe",
2417
+ channel: "book",
2418
+ assets_id: assetIds
2419
+ });
2420
+ }
2421
+ async unsubscribeOrderbook(marketId) {
2422
+ const assetIds = this.assetSubscriptions.get(marketId);
2423
+ if (!assetIds) return;
2424
+ this.send({
2425
+ type: "unsubscribe",
2426
+ channel: "book",
2427
+ assets_id: assetIds
2428
+ });
2429
+ }
2430
+ parseOrderbookMessage(message) {
2431
+ if (message.event_type !== "book") return null;
2432
+ const assetId = message.asset_id;
2433
+ if (!assetId) return null;
2434
+ const marketId = this.findMarketIdByAsset(assetId);
2435
+ if (!marketId) return null;
2436
+ const bids = [];
2437
+ const asks = [];
2438
+ const rawBids = message.bids;
2439
+ const rawAsks = message.asks;
2440
+ if (rawBids) {
2441
+ for (const bid of rawBids) {
2442
+ const price = Number.parseFloat(bid.price);
2443
+ const size = Number.parseFloat(bid.size);
2444
+ if (price > 0 && size > 0) {
2445
+ bids.push([price, size]);
2446
+ }
2447
+ }
2448
+ }
2449
+ if (rawAsks) {
2450
+ for (const ask of rawAsks) {
2451
+ const price = Number.parseFloat(ask.price);
2452
+ const size = Number.parseFloat(ask.size);
2453
+ if (price > 0 && size > 0) {
2454
+ asks.push([price, size]);
2455
+ }
2456
+ }
2457
+ }
2458
+ bids.sort((a, b) => b[0] - a[0]);
2459
+ asks.sort((a, b) => a[0] - b[0]);
2460
+ return {
2461
+ marketId,
2462
+ bids,
2463
+ asks,
2464
+ timestamp: Date.now()
2465
+ };
2466
+ }
2467
+ async watchOrderbookWithAsset(marketId, assetId, callback) {
2468
+ this.assetSubscriptions.set(marketId, assetId);
2469
+ await this.watchOrderbook(marketId, callback);
2470
+ }
2471
+ findMarketIdByAsset(assetId) {
2472
+ for (const [marketId, asset] of this.assetSubscriptions) {
2473
+ if (asset === assetId) {
2474
+ return marketId;
2475
+ }
2476
+ }
2477
+ return void 0;
2478
+ }
2479
+ };
2480
+
2481
+ // src/exchanges/index.ts
2482
+ var exchanges = {
2483
+ polymarket: Polymarket,
2484
+ opinion: Opinion,
2485
+ limitless: Limitless
2486
+ };
2487
+ function listExchanges() {
2488
+ return Object.keys(exchanges);
2489
+ }
2490
+ function createExchange(exchangeId, config) {
2491
+ const ExchangeClass = exchanges[exchangeId.toLowerCase()];
2492
+ if (!ExchangeClass) {
2493
+ throw new Error(`Exchange '${exchangeId}' not found. Available: ${listExchanges().join(", ")}`);
2494
+ }
2495
+ return new ExchangeClass(config);
2496
+ }
2497
+ var logger = pino({
2498
+ transport: {
2499
+ target: "pino-pretty",
2500
+ options: {
2501
+ colorize: true,
2502
+ translateTime: "HH:MM:ss",
2503
+ ignore: "pid,hostname"
2504
+ }
2505
+ }
2506
+ });
2507
+ function createLogger(name) {
2508
+ return logger.child({ name });
2509
+ }
2510
+ var Colors = {
2511
+ bold: (text) => `\x1B[1m${text}\x1B[0m`,
2512
+ red: (text) => `\x1B[31m${text}\x1B[0m`,
2513
+ green: (text) => `\x1B[32m${text}\x1B[0m`,
2514
+ yellow: (text) => `\x1B[33m${text}\x1B[0m`,
2515
+ blue: (text) => `\x1B[34m${text}\x1B[0m`,
2516
+ magenta: (text) => `\x1B[35m${text}\x1B[0m`,
2517
+ cyan: (text) => `\x1B[36m${text}\x1B[0m`,
2518
+ gray: (text) => `\x1B[90m${text}\x1B[0m`
2519
+ };
2520
+
2521
+ // src/utils/price.ts
2522
+ function roundToTickSize(price, tickSize) {
2523
+ return Math.round(price / tickSize) * tickSize;
2524
+ }
2525
+ function clampPrice(price, min = 0, max = 1) {
2526
+ return Math.max(min, Math.min(max, price));
2527
+ }
2528
+ function formatPrice(price, decimals = 4) {
2529
+ return price.toFixed(decimals);
2530
+ }
2531
+ function formatUsd(amount) {
2532
+ return `$${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
2533
+ }
2534
+
2535
+ export { AuthenticationError, Colors, DrManhattanError, Exchange, ExchangeError, InsufficientFunds, InvalidOrder, Limitless, MarketNotFound, MarketUtils, NetworkError, Opinion, OrderBookWebSocket, OrderSide, OrderStatus, OrderUtils, OrderbookManager, OrderbookUtils, Polymarket, PolymarketWebSocket, PositionUtils, RateLimitError, Strategy, StrategyState, WebSocketState, calculateDelta, clampPrice, createExchange, createLogger, formatPrice, formatUsd, listExchanges, logger, roundToTickSize };
2536
+ //# sourceMappingURL=index.js.map
2537
+ //# sourceMappingURL=index.js.map