@limitless-exchange/sdk 0.0.1

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,2555 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ APIError: () => APIError,
34
+ AuthenticatedClient: () => AuthenticatedClient,
35
+ Authenticator: () => Authenticator,
36
+ BASE_SEPOLIA_CHAIN_ID: () => BASE_SEPOLIA_CHAIN_ID,
37
+ CONTRACT_ADDRESSES: () => CONTRACT_ADDRESSES,
38
+ ConsoleLogger: () => ConsoleLogger,
39
+ DEFAULT_API_URL: () => DEFAULT_API_URL,
40
+ DEFAULT_CHAIN_ID: () => DEFAULT_CHAIN_ID,
41
+ DEFAULT_WS_URL: () => DEFAULT_WS_URL,
42
+ HttpClient: () => HttpClient,
43
+ MarketFetcher: () => MarketFetcher,
44
+ MarketType: () => MarketType,
45
+ MessageSigner: () => MessageSigner,
46
+ NoOpLogger: () => NoOpLogger,
47
+ OrderBuilder: () => OrderBuilder,
48
+ OrderClient: () => OrderClient,
49
+ OrderSigner: () => OrderSigner,
50
+ OrderType: () => OrderType,
51
+ PortfolioFetcher: () => PortfolioFetcher,
52
+ RetryConfig: () => RetryConfig,
53
+ RetryableClient: () => RetryableClient,
54
+ SIGNING_MESSAGE_TEMPLATE: () => SIGNING_MESSAGE_TEMPLATE,
55
+ Side: () => Side,
56
+ SignatureType: () => SignatureType,
57
+ ValidationError: () => ValidationError,
58
+ WebSocketClient: () => WebSocketClient,
59
+ WebSocketState: () => WebSocketState,
60
+ ZERO_ADDRESS: () => ZERO_ADDRESS,
61
+ getContractAddress: () => getContractAddress,
62
+ retryOnErrors: () => retryOnErrors,
63
+ validateOrderArgs: () => validateOrderArgs,
64
+ validateSignedOrder: () => validateSignedOrder,
65
+ validateUnsignedOrder: () => validateUnsignedOrder,
66
+ withRetry: () => withRetry
67
+ });
68
+ module.exports = __toCommonJS(index_exports);
69
+
70
+ // src/types/logger.ts
71
+ var NoOpLogger = class {
72
+ debug() {
73
+ }
74
+ info() {
75
+ }
76
+ warn() {
77
+ }
78
+ error() {
79
+ }
80
+ };
81
+ var ConsoleLogger = class {
82
+ constructor(level = "info") {
83
+ this.level = level;
84
+ }
85
+ shouldLog(messageLevel) {
86
+ const levels = ["debug", "info", "warn", "error"];
87
+ return levels.indexOf(messageLevel) >= levels.indexOf(this.level);
88
+ }
89
+ debug(message, meta) {
90
+ if (this.shouldLog("debug")) {
91
+ console.debug("[Limitless SDK]", message, meta || "");
92
+ }
93
+ }
94
+ info(message, meta) {
95
+ if (this.shouldLog("info")) {
96
+ console.info("[Limitless SDK]", message, meta || "");
97
+ }
98
+ }
99
+ warn(message, meta) {
100
+ if (this.shouldLog("warn")) {
101
+ console.warn("[Limitless SDK]", message, meta || "");
102
+ }
103
+ }
104
+ error(message, error, meta) {
105
+ if (this.shouldLog("error")) {
106
+ const errorMsg = error ? error.message : "";
107
+ console.error("[Limitless SDK]", message, errorMsg ? `- ${errorMsg}` : "", meta || "");
108
+ }
109
+ }
110
+ };
111
+
112
+ // src/types/orders.ts
113
+ var Side = /* @__PURE__ */ ((Side2) => {
114
+ Side2[Side2["BUY"] = 0] = "BUY";
115
+ Side2[Side2["SELL"] = 1] = "SELL";
116
+ return Side2;
117
+ })(Side || {});
118
+ var OrderType = /* @__PURE__ */ ((OrderType3) => {
119
+ OrderType3["FOK"] = "FOK";
120
+ OrderType3["GTC"] = "GTC";
121
+ return OrderType3;
122
+ })(OrderType || {});
123
+ var MarketType = /* @__PURE__ */ ((MarketType3) => {
124
+ MarketType3["CLOB"] = "CLOB";
125
+ MarketType3["NEGRISK"] = "NEGRISK";
126
+ return MarketType3;
127
+ })(MarketType || {});
128
+ var SignatureType = /* @__PURE__ */ ((SignatureType2) => {
129
+ SignatureType2[SignatureType2["EOA"] = 0] = "EOA";
130
+ SignatureType2[SignatureType2["POLY_PROXY"] = 1] = "POLY_PROXY";
131
+ SignatureType2[SignatureType2["POLY_GNOSIS_SAFE"] = 2] = "POLY_GNOSIS_SAFE";
132
+ return SignatureType2;
133
+ })(SignatureType || {});
134
+
135
+ // src/types/websocket.ts
136
+ var WebSocketState = /* @__PURE__ */ ((WebSocketState2) => {
137
+ WebSocketState2["DISCONNECTED"] = "disconnected";
138
+ WebSocketState2["CONNECTING"] = "connecting";
139
+ WebSocketState2["CONNECTED"] = "connected";
140
+ WebSocketState2["RECONNECTING"] = "reconnecting";
141
+ WebSocketState2["ERROR"] = "error";
142
+ return WebSocketState2;
143
+ })(WebSocketState || {});
144
+
145
+ // src/auth/signer.ts
146
+ var import_ethers = require("ethers");
147
+ var MessageSigner = class {
148
+ /**
149
+ * Creates a new message signer instance.
150
+ *
151
+ * @param wallet - Ethers wallet instance for signing
152
+ */
153
+ constructor(wallet) {
154
+ this.wallet = wallet;
155
+ }
156
+ /**
157
+ * Creates authentication headers for API requests.
158
+ *
159
+ * @param signingMessage - Message to sign from the API
160
+ * @returns Promise resolving to signature headers
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * const signer = new MessageSigner(wallet);
165
+ * const headers = await signer.createAuthHeaders(message);
166
+ * ```
167
+ */
168
+ async createAuthHeaders(signingMessage) {
169
+ const hexMessage = this.stringToHex(signingMessage);
170
+ const signature = await this.wallet.signMessage(signingMessage);
171
+ const address = this.wallet.address;
172
+ const recoveredAddress = import_ethers.ethers.verifyMessage(signingMessage, signature);
173
+ if (address.toLowerCase() !== recoveredAddress.toLowerCase()) {
174
+ throw new Error("Signature verification failed: address mismatch");
175
+ }
176
+ return {
177
+ "x-account": address,
178
+ "x-signing-message": hexMessage,
179
+ "x-signature": signature
180
+ };
181
+ }
182
+ /**
183
+ * Signs EIP-712 typed data.
184
+ *
185
+ * @param domain - EIP-712 domain
186
+ * @param types - EIP-712 types
187
+ * @param value - Value to sign
188
+ * @returns Promise resolving to signature string
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * const signature = await signer.signTypedData(domain, types, order);
193
+ * ```
194
+ */
195
+ async signTypedData(domain, types, value) {
196
+ return await this.wallet.signTypedData(domain, types, value);
197
+ }
198
+ /**
199
+ * Gets the wallet address.
200
+ *
201
+ * @returns Ethereum address
202
+ */
203
+ getAddress() {
204
+ return this.wallet.address;
205
+ }
206
+ /**
207
+ * Converts a string to hex format.
208
+ *
209
+ * @param text - String to convert
210
+ * @returns Hex string with 0x prefix
211
+ * @internal
212
+ */
213
+ stringToHex(text) {
214
+ return import_ethers.ethers.hexlify(import_ethers.ethers.toUtf8Bytes(text));
215
+ }
216
+ };
217
+
218
+ // src/auth/authenticator.ts
219
+ var Authenticator = class {
220
+ /**
221
+ * Creates a new authenticator instance.
222
+ *
223
+ * @param httpClient - HTTP client for API requests
224
+ * @param signer - Message signer for wallet operations
225
+ * @param logger - Optional logger for debugging and monitoring (default: no logging)
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * // Without logging (default)
230
+ * const authenticator = new Authenticator(httpClient, signer);
231
+ *
232
+ * // With logging
233
+ * import { ConsoleLogger } from '@limitless-exchange/sdk';
234
+ * const logger = new ConsoleLogger('debug');
235
+ * const authenticator = new Authenticator(httpClient, signer, logger);
236
+ * ```
237
+ */
238
+ constructor(httpClient, signer, logger) {
239
+ this.httpClient = httpClient;
240
+ this.signer = signer;
241
+ this.logger = logger || new NoOpLogger();
242
+ }
243
+ /**
244
+ * Gets a signing message from the API.
245
+ *
246
+ * @returns Promise resolving to signing message string
247
+ * @throws Error if API request fails
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const message = await authenticator.getSigningMessage();
252
+ * console.log(message);
253
+ * ```
254
+ */
255
+ async getSigningMessage() {
256
+ this.logger.debug("Requesting signing message from API");
257
+ const message = await this.httpClient.get("/auth/signing-message");
258
+ this.logger.debug("Received signing message", { length: message.length });
259
+ return message;
260
+ }
261
+ /**
262
+ * Authenticates with the API and obtains session cookie.
263
+ *
264
+ * @param options - Login options including client type and smart wallet
265
+ * @returns Promise resolving to authentication result
266
+ * @throws Error if authentication fails or smart wallet is required but not provided
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * // EOA authentication
271
+ * const result = await authenticator.authenticate({ client: 'eoa' });
272
+ *
273
+ * // ETHERSPOT with smart wallet
274
+ * const result = await authenticator.authenticate({
275
+ * client: 'etherspot',
276
+ * smartWallet: '0x...'
277
+ * });
278
+ * ```
279
+ */
280
+ async authenticate(options = {}) {
281
+ const client = options.client || "eoa";
282
+ this.logger.info("Starting authentication", {
283
+ client,
284
+ hasSmartWallet: !!options.smartWallet
285
+ });
286
+ if (client === "etherspot" && !options.smartWallet) {
287
+ this.logger.error("Smart wallet address required for ETHERSPOT client");
288
+ throw new Error("Smart wallet address is required for ETHERSPOT client");
289
+ }
290
+ try {
291
+ const signingMessage = await this.getSigningMessage();
292
+ this.logger.debug("Creating signature headers");
293
+ const headers = await this.signer.createAuthHeaders(signingMessage);
294
+ this.logger.debug("Sending authentication request", { client });
295
+ const response = await this.httpClient.postWithResponse(
296
+ "/auth/login",
297
+ { client, smartWallet: options.smartWallet },
298
+ {
299
+ headers,
300
+ validateStatus: (status) => status < 500
301
+ }
302
+ );
303
+ this.logger.debug("Extracting session cookie from response");
304
+ const cookies = this.httpClient.extractCookies(response);
305
+ const sessionCookie = cookies["limitless_session"];
306
+ if (!sessionCookie) {
307
+ this.logger.error("Session cookie not found in response headers");
308
+ throw new Error("Failed to obtain session cookie from response");
309
+ }
310
+ this.httpClient.setSessionCookie(sessionCookie);
311
+ this.logger.info("Authentication successful", {
312
+ account: response.data.account,
313
+ client: response.data.client
314
+ });
315
+ return {
316
+ sessionCookie,
317
+ profile: response.data
318
+ };
319
+ } catch (error) {
320
+ this.logger.error("Authentication failed", error, {
321
+ client
322
+ });
323
+ throw error;
324
+ }
325
+ }
326
+ /**
327
+ * Verifies the current authentication status.
328
+ *
329
+ * @param sessionCookie - Session cookie to verify
330
+ * @returns Promise resolving to user's Ethereum address
331
+ * @throws Error if session is invalid
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * const address = await authenticator.verifyAuth(sessionCookie);
336
+ * console.log(`Authenticated as: ${address}`);
337
+ * ```
338
+ */
339
+ async verifyAuth(sessionCookie) {
340
+ this.logger.debug("Verifying authentication session");
341
+ const originalCookie = this.httpClient["sessionCookie"];
342
+ this.httpClient.setSessionCookie(sessionCookie);
343
+ try {
344
+ const address = await this.httpClient.get("/auth/verify-auth");
345
+ this.logger.info("Session verified", { address });
346
+ return address;
347
+ } catch (error) {
348
+ this.logger.error("Session verification failed", error);
349
+ throw error;
350
+ } finally {
351
+ if (originalCookie) {
352
+ this.httpClient.setSessionCookie(originalCookie);
353
+ } else {
354
+ this.httpClient.clearSessionCookie();
355
+ }
356
+ }
357
+ }
358
+ /**
359
+ * Logs out and clears the session.
360
+ *
361
+ * @param sessionCookie - Session cookie to invalidate
362
+ * @throws Error if logout request fails
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * await authenticator.logout(sessionCookie);
367
+ * console.log('Logged out successfully');
368
+ * ```
369
+ */
370
+ async logout(sessionCookie) {
371
+ this.logger.debug("Logging out session");
372
+ const originalCookie = this.httpClient["sessionCookie"];
373
+ this.httpClient.setSessionCookie(sessionCookie);
374
+ try {
375
+ await this.httpClient.post("/auth/logout", {});
376
+ this.logger.info("Logout successful");
377
+ } catch (error) {
378
+ this.logger.error("Logout failed", error);
379
+ throw error;
380
+ } finally {
381
+ if (originalCookie) {
382
+ this.httpClient.setSessionCookie(originalCookie);
383
+ } else {
384
+ this.httpClient.clearSessionCookie();
385
+ }
386
+ }
387
+ }
388
+ };
389
+
390
+ // src/api/errors.ts
391
+ var APIError = class _APIError extends Error {
392
+ /**
393
+ * Creates a new API error.
394
+ *
395
+ * @param message - Human-readable error message
396
+ * @param status - HTTP status code
397
+ * @param data - Raw API response data
398
+ * @param url - Request URL
399
+ * @param method - Request method
400
+ */
401
+ constructor(message, status, data, url, method) {
402
+ super(message);
403
+ this.name = "APIError";
404
+ this.status = status;
405
+ this.data = data;
406
+ this.url = url;
407
+ this.method = method;
408
+ if (Error.captureStackTrace) {
409
+ Error.captureStackTrace(this, _APIError);
410
+ }
411
+ }
412
+ /**
413
+ * Checks if this error is an authentication/authorization error.
414
+ *
415
+ * @returns True if the error is due to expired or invalid authentication
416
+ *
417
+ * @example
418
+ * ```typescript
419
+ * try {
420
+ * await portfolioFetcher.getPositions();
421
+ * } catch (error) {
422
+ * if (error instanceof APIError && error.isAuthError()) {
423
+ * // Re-authenticate and retry
424
+ * await authenticator.authenticate({ client: 'eoa' });
425
+ * await portfolioFetcher.getPositions();
426
+ * }
427
+ * }
428
+ * ```
429
+ */
430
+ isAuthError() {
431
+ return this.status === 401 || this.status === 403;
432
+ }
433
+ };
434
+
435
+ // src/auth/authenticated-client.ts
436
+ var AuthenticatedClient = class {
437
+ /**
438
+ * Creates a new authenticated client with auto-retry capability.
439
+ *
440
+ * @param config - Configuration for authenticated client
441
+ */
442
+ constructor(config) {
443
+ this.httpClient = config.httpClient;
444
+ this.authenticator = config.authenticator;
445
+ this.client = config.client;
446
+ this.smartWallet = config.smartWallet;
447
+ this.logger = config.logger || new NoOpLogger();
448
+ this.maxRetries = config.maxRetries ?? 1;
449
+ }
450
+ /**
451
+ * Executes a function with automatic retry on authentication errors.
452
+ *
453
+ * @param fn - Function to execute with auth retry
454
+ * @returns Promise resolving to the function result
455
+ * @throws Error if max retries exceeded or non-auth error occurs
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * // Automatic retry on 401/403
460
+ * const positions = await authClient.withRetry(() =>
461
+ * portfolioFetcher.getPositions()
462
+ * );
463
+ *
464
+ * // Works with any async operation
465
+ * const order = await authClient.withRetry(() =>
466
+ * orderClient.createOrder({ ... })
467
+ * );
468
+ * ```
469
+ */
470
+ async withRetry(fn) {
471
+ let attempts = 0;
472
+ while (attempts <= this.maxRetries) {
473
+ try {
474
+ return await fn();
475
+ } catch (error) {
476
+ if (error instanceof APIError && error.isAuthError() && attempts < this.maxRetries) {
477
+ this.logger.info("Authentication expired, re-authenticating...", {
478
+ attempt: attempts + 1,
479
+ maxRetries: this.maxRetries
480
+ });
481
+ await this.reauthenticate();
482
+ attempts++;
483
+ continue;
484
+ }
485
+ throw error;
486
+ }
487
+ }
488
+ throw new Error("Unexpected error: exceeded max retries");
489
+ }
490
+ /**
491
+ * Re-authenticates with the API.
492
+ *
493
+ * @internal
494
+ */
495
+ async reauthenticate() {
496
+ this.logger.debug("Re-authenticating with API");
497
+ await this.authenticator.authenticate({
498
+ client: this.client,
499
+ smartWallet: this.smartWallet
500
+ });
501
+ this.logger.info("Re-authentication successful");
502
+ }
503
+ };
504
+
505
+ // src/api/http.ts
506
+ var import_axios = __toESM(require("axios"));
507
+
508
+ // src/utils/constants.ts
509
+ var DEFAULT_API_URL = "https://api.limitless.exchange";
510
+ var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
511
+ var DEFAULT_CHAIN_ID = 8453;
512
+ var BASE_SEPOLIA_CHAIN_ID = 84532;
513
+ var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
514
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
515
+ var CONTRACT_ADDRESSES = {
516
+ // Base mainnet (chainId: 8453)
517
+ 8453: {
518
+ CLOB: "0xa4409D988CA2218d956BeEFD3874100F444f0DC3",
519
+ NEGRISK: "0x5a38afc17F7E97ad8d6C547ddb837E40B4aEDfC6"
520
+ },
521
+ // Base Sepolia testnet (chainId: 84532)
522
+ 84532: {
523
+ CLOB: "0x...",
524
+ // Add testnet addresses when available
525
+ NEGRISK: "0x..."
526
+ }
527
+ };
528
+ function getContractAddress(marketType, chainId = DEFAULT_CHAIN_ID) {
529
+ const addresses = CONTRACT_ADDRESSES[chainId];
530
+ if (!addresses) {
531
+ throw new Error(
532
+ `No contract addresses configured for chainId ${chainId}. Supported chains: ${Object.keys(CONTRACT_ADDRESSES).join(", ")}`
533
+ );
534
+ }
535
+ return addresses[marketType];
536
+ }
537
+
538
+ // src/api/http.ts
539
+ var HttpClient = class {
540
+ /**
541
+ * Creates a new HTTP client instance.
542
+ *
543
+ * @param config - Configuration options for the HTTP client
544
+ */
545
+ constructor(config = {}) {
546
+ this.sessionCookie = config.sessionCookie;
547
+ this.logger = config.logger || new NoOpLogger();
548
+ this.client = import_axios.default.create({
549
+ baseURL: config.baseURL || DEFAULT_API_URL,
550
+ timeout: config.timeout || 3e4,
551
+ headers: {
552
+ "Content-Type": "application/json",
553
+ Accept: "application/json"
554
+ }
555
+ });
556
+ this.setupInterceptors();
557
+ }
558
+ /**
559
+ * Sets up request and response interceptors.
560
+ * @internal
561
+ */
562
+ setupInterceptors() {
563
+ this.client.interceptors.request.use(
564
+ (config) => {
565
+ if (this.sessionCookie) {
566
+ config.headers["Cookie"] = `limitless_session=${this.sessionCookie}`;
567
+ }
568
+ const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
569
+ const method = config.method?.toUpperCase() || "GET";
570
+ this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
571
+ headers: config.headers,
572
+ body: config.data
573
+ });
574
+ return config;
575
+ },
576
+ (error) => Promise.reject(error)
577
+ );
578
+ this.client.interceptors.response.use(
579
+ (response) => {
580
+ const method = response.config.method?.toUpperCase() || "GET";
581
+ const url = response.config.url || "";
582
+ this.logger.debug(`\u2713 ${response.status} ${method} ${url}`, {
583
+ data: response.data
584
+ });
585
+ return response;
586
+ },
587
+ (error) => {
588
+ if (error.response) {
589
+ const status = error.response.status;
590
+ const data = error.response.data;
591
+ const url = error.config?.url;
592
+ const method = error.config?.method?.toUpperCase();
593
+ let message = error.message;
594
+ if (data) {
595
+ this.logger.debug(`\u2717 ${status} ${method} ${url}`, {
596
+ error: data
597
+ });
598
+ if (typeof data === "object") {
599
+ if (Array.isArray(data.message)) {
600
+ const messages = data.message.map((err) => {
601
+ const details = Object.entries(err).filter(([_key, val]) => val !== "" && val !== null && val !== void 0).map(([key, val]) => `${key}: ${val}`).join(", ");
602
+ return details || JSON.stringify(err);
603
+ }).filter((msg) => msg.trim() !== "").join(" | ");
604
+ message = messages || data.error || JSON.stringify(data);
605
+ } else {
606
+ message = data.message || data.error || data.msg || data.errors && JSON.stringify(data.errors) || JSON.stringify(data);
607
+ }
608
+ } else {
609
+ message = String(data);
610
+ }
611
+ }
612
+ throw new APIError(message, status, data, url, method);
613
+ } else if (error.request) {
614
+ throw new Error("No response received from API");
615
+ } else {
616
+ throw new Error(`Request failed: ${error.message}`);
617
+ }
618
+ }
619
+ );
620
+ }
621
+ /**
622
+ * Sets the session cookie for authenticated requests.
623
+ *
624
+ * @param cookie - Session cookie value
625
+ */
626
+ setSessionCookie(cookie) {
627
+ this.sessionCookie = cookie;
628
+ }
629
+ /**
630
+ * Clears the session cookie.
631
+ */
632
+ clearSessionCookie() {
633
+ this.sessionCookie = void 0;
634
+ }
635
+ /**
636
+ * Performs a GET request.
637
+ *
638
+ * @param url - Request URL
639
+ * @param config - Additional request configuration
640
+ * @returns Promise resolving to the response data
641
+ */
642
+ async get(url, config) {
643
+ const response = await this.client.get(url, config);
644
+ return response.data;
645
+ }
646
+ /**
647
+ * Performs a POST request.
648
+ *
649
+ * @param url - Request URL
650
+ * @param data - Request body data
651
+ * @param config - Additional request configuration
652
+ * @returns Promise resolving to the response data
653
+ */
654
+ async post(url, data, config) {
655
+ const response = await this.client.post(url, data, config);
656
+ return response.data;
657
+ }
658
+ /**
659
+ * Performs a POST request and returns the full response object.
660
+ * Useful when you need access to response headers (e.g., for cookie extraction).
661
+ *
662
+ * @param url - Request URL
663
+ * @param data - Request body data
664
+ * @param config - Additional request configuration
665
+ * @returns Promise resolving to the full AxiosResponse object
666
+ * @internal
667
+ */
668
+ async postWithResponse(url, data, config) {
669
+ return await this.client.post(url, data, config);
670
+ }
671
+ /**
672
+ * Performs a DELETE request.
673
+ *
674
+ * @remarks
675
+ * DELETE requests typically don't have a body, so we remove the Content-Type header
676
+ * to avoid "Body cannot be empty" errors from the API.
677
+ *
678
+ * @param url - Request URL
679
+ * @param config - Additional request configuration
680
+ * @returns Promise resolving to the response data
681
+ */
682
+ async delete(url, config) {
683
+ const deleteConfig = {
684
+ ...config,
685
+ headers: {
686
+ ...config?.headers,
687
+ "Content-Type": void 0
688
+ }
689
+ };
690
+ const response = await this.client.delete(url, deleteConfig);
691
+ return response.data;
692
+ }
693
+ /**
694
+ * Extracts cookies from response headers.
695
+ *
696
+ * @param response - Axios response object
697
+ * @returns Object containing parsed cookies
698
+ * @internal
699
+ */
700
+ extractCookies(response) {
701
+ const setCookie = response.headers["set-cookie"];
702
+ if (!setCookie) return {};
703
+ const cookies = {};
704
+ const cookieStrings = Array.isArray(setCookie) ? setCookie : [setCookie];
705
+ for (const cookieString of cookieStrings) {
706
+ const parts = cookieString.split(";")[0].split("=");
707
+ if (parts.length === 2) {
708
+ cookies[parts[0].trim()] = parts[1].trim();
709
+ }
710
+ }
711
+ return cookies;
712
+ }
713
+ };
714
+
715
+ // src/api/retry.ts
716
+ var RetryConfig = class {
717
+ /**
718
+ * Creates a new retry configuration.
719
+ *
720
+ * @param options - Configuration options
721
+ */
722
+ constructor(options = {}) {
723
+ this.statusCodes = new Set(options.statusCodes || [429, 500, 502, 503, 504]);
724
+ this.maxRetries = options.maxRetries ?? 3;
725
+ this.delays = options.delays;
726
+ this.exponentialBase = options.exponentialBase ?? 2;
727
+ this.maxDelay = options.maxDelay ?? 60;
728
+ this.onRetry = options.onRetry;
729
+ }
730
+ /**
731
+ * Calculates delay for a given retry attempt.
732
+ *
733
+ * @param attempt - Retry attempt number (0-based)
734
+ * @returns Delay in seconds
735
+ */
736
+ getDelay(attempt) {
737
+ if (this.delays) {
738
+ return this.delays[Math.min(attempt, this.delays.length - 1)];
739
+ } else {
740
+ return Math.min(Math.pow(this.exponentialBase, attempt), this.maxDelay);
741
+ }
742
+ }
743
+ };
744
+ function sleep(seconds) {
745
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
746
+ }
747
+ function retryOnErrors(options = {}) {
748
+ const config = new RetryConfig(options);
749
+ return function(_target, _propertyKey, descriptor) {
750
+ const originalMethod = descriptor.value;
751
+ descriptor.value = async function(...args) {
752
+ let lastError;
753
+ try {
754
+ return await originalMethod.apply(this, args);
755
+ } catch (error) {
756
+ if (error instanceof APIError && config.statusCodes.has(error.status)) {
757
+ lastError = error;
758
+ } else {
759
+ throw error;
760
+ }
761
+ }
762
+ for (let attempt = 0; attempt < config.maxRetries; attempt++) {
763
+ try {
764
+ const delay = config.getDelay(attempt);
765
+ if (config.onRetry && lastError) {
766
+ config.onRetry(attempt, lastError, delay);
767
+ }
768
+ await sleep(delay);
769
+ return await originalMethod.apply(this, args);
770
+ } catch (error) {
771
+ if (error instanceof APIError && config.statusCodes.has(error.status)) {
772
+ lastError = error;
773
+ } else {
774
+ throw error;
775
+ }
776
+ }
777
+ }
778
+ throw lastError;
779
+ };
780
+ return descriptor;
781
+ };
782
+ }
783
+ async function withRetry(fn, options = {}, logger = new NoOpLogger()) {
784
+ const config = new RetryConfig(options);
785
+ let lastError;
786
+ try {
787
+ return await fn();
788
+ } catch (error) {
789
+ if (error instanceof APIError && config.statusCodes.has(error.status)) {
790
+ lastError = error;
791
+ logger.warn("API error, starting retries", { status: error.status });
792
+ } else {
793
+ throw error;
794
+ }
795
+ }
796
+ for (let attempt = 0; attempt < config.maxRetries; attempt++) {
797
+ try {
798
+ const delay = config.getDelay(attempt);
799
+ if (config.onRetry && lastError) {
800
+ config.onRetry(attempt, lastError, delay);
801
+ }
802
+ logger.info("Retrying operation", { attempt: attempt + 1, delay });
803
+ await sleep(delay);
804
+ return await fn();
805
+ } catch (error) {
806
+ if (error instanceof APIError && config.statusCodes.has(error.status)) {
807
+ lastError = error;
808
+ logger.warn("Retry failed", { attempt: attempt + 1, status: error.status });
809
+ } else {
810
+ throw error;
811
+ }
812
+ }
813
+ }
814
+ logger.error("All retries exhausted");
815
+ throw lastError;
816
+ }
817
+ var RetryableClient = class {
818
+ /**
819
+ * Creates a new retryable client wrapper.
820
+ *
821
+ * @param httpClient - HTTP client to wrap
822
+ * @param retryConfig - Retry configuration
823
+ * @param logger - Optional logger
824
+ */
825
+ constructor(httpClient, retryConfig = new RetryConfig(), logger = new NoOpLogger()) {
826
+ this.httpClient = httpClient;
827
+ this.retryConfig = retryConfig;
828
+ this.logger = logger;
829
+ }
830
+ /**
831
+ * Performs a GET request with retry logic.
832
+ *
833
+ * @param url - Request URL
834
+ * @param config - Additional request configuration
835
+ * @returns Promise resolving to the response data
836
+ */
837
+ async get(url, config) {
838
+ return withRetry(
839
+ async () => this.httpClient.get(url, config),
840
+ {
841
+ statusCodes: Array.from(this.retryConfig.statusCodes),
842
+ maxRetries: this.retryConfig.maxRetries,
843
+ delays: this.retryConfig.delays,
844
+ exponentialBase: this.retryConfig.exponentialBase,
845
+ maxDelay: this.retryConfig.maxDelay,
846
+ onRetry: this.retryConfig.onRetry
847
+ },
848
+ this.logger
849
+ );
850
+ }
851
+ /**
852
+ * Performs a POST request with retry logic.
853
+ *
854
+ * @param url - Request URL
855
+ * @param data - Request body data
856
+ * @param config - Additional request configuration
857
+ * @returns Promise resolving to the response data
858
+ */
859
+ async post(url, data, config) {
860
+ return withRetry(
861
+ async () => this.httpClient.post(url, data, config),
862
+ {
863
+ statusCodes: Array.from(this.retryConfig.statusCodes),
864
+ maxRetries: this.retryConfig.maxRetries,
865
+ delays: this.retryConfig.delays,
866
+ exponentialBase: this.retryConfig.exponentialBase,
867
+ maxDelay: this.retryConfig.maxDelay,
868
+ onRetry: this.retryConfig.onRetry
869
+ },
870
+ this.logger
871
+ );
872
+ }
873
+ /**
874
+ * Performs a DELETE request with retry logic.
875
+ *
876
+ * @param url - Request URL
877
+ * @param config - Additional request configuration
878
+ * @returns Promise resolving to the response data
879
+ */
880
+ async delete(url, config) {
881
+ return withRetry(
882
+ async () => this.httpClient.delete(url, config),
883
+ {
884
+ statusCodes: Array.from(this.retryConfig.statusCodes),
885
+ maxRetries: this.retryConfig.maxRetries,
886
+ delays: this.retryConfig.delays,
887
+ exponentialBase: this.retryConfig.exponentialBase,
888
+ maxDelay: this.retryConfig.maxDelay,
889
+ onRetry: this.retryConfig.onRetry
890
+ },
891
+ this.logger
892
+ );
893
+ }
894
+ };
895
+
896
+ // src/orders/builder.ts
897
+ var import_ethers2 = require("ethers");
898
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
899
+ var DEFAULT_PRICE_TICK = 1e-3;
900
+ var OrderBuilder = class {
901
+ /**
902
+ * Creates a new order builder instance.
903
+ *
904
+ * @param makerAddress - Ethereum address of the order maker
905
+ * @param feeRateBps - Fee rate in basis points (e.g., 100 = 1%)
906
+ * @param priceTick - Price tick size (default: 0.001 for 3 decimals)
907
+ *
908
+ * @example
909
+ * ```typescript
910
+ * const builder = new OrderBuilder(
911
+ * '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
912
+ * 300, // 3% fee
913
+ * 0.001 // 3 decimal price precision
914
+ * );
915
+ * ```
916
+ */
917
+ constructor(makerAddress, feeRateBps, priceTick = DEFAULT_PRICE_TICK) {
918
+ this.makerAddress = makerAddress;
919
+ this.feeRateBps = feeRateBps;
920
+ this.priceTick = priceTick;
921
+ }
922
+ /**
923
+ * Builds an unsigned order payload.
924
+ *
925
+ * @param args - Order arguments (FOK or GTC)
926
+ * @returns Unsigned order ready for signing
927
+ *
928
+ * @throws Error if validation fails or tick alignment fails
929
+ *
930
+ * @example
931
+ * ```typescript
932
+ * // FOK order (market order)
933
+ * const fokOrder = builder.buildOrder({
934
+ * tokenId: '123456',
935
+ * makerAmount: 50, // 50 USDC to spend
936
+ * side: Side.BUY,
937
+ * marketType: MarketType.CLOB
938
+ * });
939
+ *
940
+ * // GTC order (price + size)
941
+ * const gtcOrder = builder.buildOrder({
942
+ * tokenId: '123456',
943
+ * price: 0.38,
944
+ * size: 22.123, // Will be rounded to tick-aligned: 22.123 shares
945
+ * side: Side.BUY,
946
+ * marketType: MarketType.CLOB
947
+ * });
948
+ * ```
949
+ */
950
+ buildOrder(args) {
951
+ this.validateOrderArgs(args);
952
+ const { makerAmount, takerAmount, price } = this.isFOKOrder(args) ? this.calculateFOKAmounts(args.makerAmount) : this.calculateGTCAmountsTickAligned(args.price, args.size, args.side);
953
+ const order = {
954
+ salt: this.generateSalt(),
955
+ maker: this.makerAddress,
956
+ signer: this.makerAddress,
957
+ taker: args.taker || ZERO_ADDRESS2,
958
+ tokenId: args.tokenId,
959
+ makerAmount,
960
+ takerAmount,
961
+ expiration: args.expiration || "0",
962
+ nonce: args.nonce || 0,
963
+ feeRateBps: this.feeRateBps,
964
+ side: args.side,
965
+ signatureType: 0 /* EOA */
966
+ };
967
+ if (price !== void 0) {
968
+ order.price = price;
969
+ }
970
+ return order;
971
+ }
972
+ /**
973
+ * Type guard to check if order arguments are for FOK order.
974
+ *
975
+ * @param args - Order arguments
976
+ * @returns True if FOK order arguments
977
+ *
978
+ * @internal
979
+ */
980
+ isFOKOrder(args) {
981
+ return "amount" in args;
982
+ }
983
+ /**
984
+ * Generates a unique salt using timestamp + nano-offset pattern.
985
+ *
986
+ * @remarks
987
+ * This follows the reference implementation pattern:
988
+ * salt = timestamp * 1000 + nanoOffset + 24h
989
+ *
990
+ * This ensures uniqueness even when creating orders rapidly.
991
+ *
992
+ * @returns Unique salt value
993
+ *
994
+ * @internal
995
+ */
996
+ generateSalt() {
997
+ const timestamp = Date.now();
998
+ const nanoOffset = Math.floor(performance.now() * 1e3) % 1e6;
999
+ const oneDayMs = 1e3 * 60 * 60 * 24;
1000
+ return timestamp * 1e3 + nanoOffset + oneDayMs;
1001
+ }
1002
+ /**
1003
+ * Parses decimal string to scaled BigInt.
1004
+ *
1005
+ * @param value - Decimal string (e.g., "0.38")
1006
+ * @param scale - Scale factor (e.g., 1_000_000n for 6 decimals)
1007
+ * @returns Scaled BigInt value
1008
+ *
1009
+ * @internal
1010
+ */
1011
+ parseDecToInt(value, scale) {
1012
+ const s = value.trim();
1013
+ const [intPart, fracPart = ""] = s.split(".");
1014
+ const decimals = scale.toString().length - 1;
1015
+ const frac = (fracPart + "0".repeat(decimals)).slice(0, decimals);
1016
+ const sign = intPart.startsWith("-") ? -1n : 1n;
1017
+ const intPartAbs = intPart.replace("-", "");
1018
+ return sign * (BigInt(intPartAbs || "0") * scale + BigInt(frac || "0"));
1019
+ }
1020
+ /**
1021
+ * Ceiling division for BigInt.
1022
+ *
1023
+ * @param numerator - Numerator
1024
+ * @param denominator - Denominator
1025
+ * @returns Ceiling of numerator / denominator
1026
+ *
1027
+ * @internal
1028
+ */
1029
+ divCeil(numerator, denominator) {
1030
+ if (denominator === 0n) {
1031
+ throw new Error("Division by zero");
1032
+ }
1033
+ return (numerator + denominator - 1n) / denominator;
1034
+ }
1035
+ /**
1036
+ * Calculates maker and taker amounts for GTC orders with tick alignment validation.
1037
+ *
1038
+ * @remarks
1039
+ * Validates and calculates amounts to ensure:
1040
+ * 1. Price aligns to tick size (e.g., 0.001 for 3 decimals)
1041
+ * 2. Size produces takerAmount divisible by sharesStep
1042
+ * 3. No auto-rounding - fails fast if values are invalid
1043
+ * 4. Transparent error messages guide users to valid values
1044
+ *
1045
+ * **Algorithm**:
1046
+ * - sharesStep = priceScale / tickInt (e.g., 1000 for 0.001 tick)
1047
+ * - Validates shares are divisible by sharesStep
1048
+ * - Calculates collateral from shares × price (ceil for BUY, floor for SELL)
1049
+ * - Assigns maker/taker based on side:
1050
+ * - BUY: maker = collateral, taker = shares
1051
+ * - SELL: maker = shares, taker = collateral
1052
+ * - Throws clear error if size is not tick-aligned
1053
+ *
1054
+ * @param price - Price per share (0.0 to 1.0, max 3 decimals)
1055
+ * @param size - Number of shares (must be tick-aligned)
1056
+ * @param side - Order side (BUY or SELL)
1057
+ * @returns Object with validated makerAmount, takerAmount, and price
1058
+ *
1059
+ * @throws Error if price or size not tick-aligned
1060
+ *
1061
+ * @internal
1062
+ */
1063
+ calculateGTCAmountsTickAligned(price, size, side) {
1064
+ const sharesScale = 1000000n;
1065
+ const collateralScale = 1000000n;
1066
+ const priceScale = 1000000n;
1067
+ const shares = this.parseDecToInt(size.toString(), sharesScale);
1068
+ const priceInt = this.parseDecToInt(price.toString(), priceScale);
1069
+ const tickInt = this.parseDecToInt(this.priceTick.toString(), priceScale);
1070
+ if (tickInt <= 0n) {
1071
+ throw new Error(`Invalid priceTick: ${this.priceTick}`);
1072
+ }
1073
+ if (priceInt <= 0n) {
1074
+ throw new Error(`Invalid price: ${price}`);
1075
+ }
1076
+ if (priceInt % tickInt !== 0n) {
1077
+ throw new Error(
1078
+ `Price ${price} is not tick-aligned. Must be multiple of ${this.priceTick} (e.g., 0.380, 0.381, etc.)`
1079
+ );
1080
+ }
1081
+ const sharesStep = priceScale / tickInt;
1082
+ if (shares % sharesStep !== 0n) {
1083
+ const validSizeDown = Number(shares / sharesStep * sharesStep) / 1e6;
1084
+ const validSizeUp = Number(this.divCeil(shares, sharesStep) * sharesStep) / 1e6;
1085
+ throw new Error(
1086
+ `Invalid size: ${size}. Size must produce contracts divisible by ${sharesStep} (sharesStep). Try ${validSizeDown} (rounded down) or ${validSizeUp} (rounded up) instead.`
1087
+ );
1088
+ }
1089
+ const numerator = shares * priceInt * collateralScale;
1090
+ const denominator = sharesScale * priceScale;
1091
+ const collateral = side === 0 /* BUY */ ? this.divCeil(numerator, denominator) : numerator / denominator;
1092
+ let makerAmount;
1093
+ let takerAmount;
1094
+ if (side === 0 /* BUY */) {
1095
+ makerAmount = collateral;
1096
+ takerAmount = shares;
1097
+ } else {
1098
+ makerAmount = shares;
1099
+ takerAmount = collateral;
1100
+ }
1101
+ return {
1102
+ makerAmount: Number(makerAmount),
1103
+ takerAmount: Number(takerAmount),
1104
+ price
1105
+ };
1106
+ }
1107
+ /**
1108
+ * Calculates maker and taker amounts for FOK (market) orders.
1109
+ *
1110
+ * @remarks
1111
+ * FOK orders use makerAmount for both BUY and SELL:
1112
+ * - BUY: makerAmount = USDC amount to spend (e.g., 50 = $50 USDC)
1113
+ * - SELL: makerAmount = number of shares to sell (e.g., 18.64 shares)
1114
+ *
1115
+ * takerAmount is always 1 (constant for FOK orders)
1116
+ *
1117
+ * @param makerAmount - Amount in human-readable format (max 6 decimals)
1118
+ * @returns Object with makerAmount (scaled), takerAmount (always 1), and undefined price
1119
+ *
1120
+ * @internal
1121
+ */
1122
+ calculateFOKAmounts(makerAmount) {
1123
+ const DECIMALS = 6;
1124
+ const amountStr = makerAmount.toString();
1125
+ const decimalIndex = amountStr.indexOf(".");
1126
+ if (decimalIndex !== -1) {
1127
+ const decimalPlaces = amountStr.length - decimalIndex - 1;
1128
+ if (decimalPlaces > DECIMALS) {
1129
+ throw new Error(
1130
+ `Invalid makerAmount: ${makerAmount}. Can have max ${DECIMALS} decimal places. Try ${makerAmount.toFixed(DECIMALS)} instead.`
1131
+ );
1132
+ }
1133
+ }
1134
+ const amountFormatted = makerAmount.toFixed(DECIMALS);
1135
+ const amountScaled = import_ethers2.ethers.parseUnits(amountFormatted, DECIMALS);
1136
+ const collateralAmount = Number(amountScaled);
1137
+ return {
1138
+ makerAmount: collateralAmount,
1139
+ takerAmount: 1,
1140
+ price: void 0
1141
+ };
1142
+ }
1143
+ /**
1144
+ * Validates order arguments.
1145
+ *
1146
+ * @param args - Order arguments to validate
1147
+ * @throws Error if validation fails
1148
+ *
1149
+ * @internal
1150
+ */
1151
+ validateOrderArgs(args) {
1152
+ if (!args.tokenId || args.tokenId === "0") {
1153
+ throw new Error("Invalid tokenId: tokenId is required.");
1154
+ }
1155
+ if (args.taker && !import_ethers2.ethers.isAddress(args.taker)) {
1156
+ throw new Error(`Invalid taker address: ${args.taker}`);
1157
+ }
1158
+ if (this.isFOKOrder(args)) {
1159
+ if (!args.makerAmount) {
1160
+ throw new Error("FOK orders require makerAmount");
1161
+ }
1162
+ if (args.makerAmount <= 0) {
1163
+ throw new Error(`Invalid makerAmount: ${args.makerAmount}. Maker amount must be positive.`);
1164
+ }
1165
+ } else {
1166
+ if (args.price < 0 || args.price > 1) {
1167
+ throw new Error(`Invalid price: ${args.price}. Price must be between 0 and 1.`);
1168
+ }
1169
+ if (args.size <= 0) {
1170
+ throw new Error(`Invalid size: ${args.size}. Size must be positive.`);
1171
+ }
1172
+ const priceStr = args.price.toString();
1173
+ const decimalIndex = priceStr.indexOf(".");
1174
+ if (decimalIndex !== -1) {
1175
+ const decimalPlaces = priceStr.length - decimalIndex - 1;
1176
+ if (decimalPlaces > 3) {
1177
+ throw new Error(
1178
+ `Invalid price: ${args.price}. Price must have max 3 decimal places (e.g., 0.380, 0.001).`
1179
+ );
1180
+ }
1181
+ }
1182
+ }
1183
+ }
1184
+ };
1185
+
1186
+ // src/orders/signer.ts
1187
+ var OrderSigner = class {
1188
+ /**
1189
+ * Creates a new order signer instance.
1190
+ *
1191
+ * @param wallet - Ethers wallet for signing
1192
+ * @param logger - Optional logger for debugging (default: no logging)
1193
+ *
1194
+ * @example
1195
+ * ```typescript
1196
+ * import { ethers } from 'ethers';
1197
+ * import { OrderSigner } from '@limitless-exchange/sdk';
1198
+ *
1199
+ * const wallet = new ethers.Wallet(privateKey);
1200
+ * const signer = new OrderSigner(wallet);
1201
+ * ```
1202
+ */
1203
+ constructor(wallet, logger) {
1204
+ this.wallet = wallet;
1205
+ this.logger = logger || new NoOpLogger();
1206
+ }
1207
+ /**
1208
+ * Signs an order with EIP-712.
1209
+ *
1210
+ * @param order - Unsigned order to sign
1211
+ * @param config - Signing configuration (chainId, contract address, market type)
1212
+ * @returns Promise resolving to EIP-712 signature
1213
+ *
1214
+ * @throws Error if wallet address doesn't match order signer
1215
+ * @throws Error if signing fails
1216
+ *
1217
+ * @example
1218
+ * ```typescript
1219
+ * const signature = await signer.signOrder(unsignedOrder, {
1220
+ * chainId: 8453,
1221
+ * contractAddress: '0x...',
1222
+ * marketType: MarketType.CLOB
1223
+ * });
1224
+ * ```
1225
+ */
1226
+ async signOrder(order, config) {
1227
+ this.logger.debug("Signing order with EIP-712", {
1228
+ tokenId: order.tokenId,
1229
+ side: order.side,
1230
+ marketType: config.marketType
1231
+ });
1232
+ const walletAddress = await this.wallet.getAddress();
1233
+ if (walletAddress.toLowerCase() !== order.signer.toLowerCase()) {
1234
+ const error = `Wallet address mismatch! Signing with: ${walletAddress}, but order signer is: ${order.signer}`;
1235
+ this.logger.error(error);
1236
+ throw new Error(error);
1237
+ }
1238
+ const domain = this.getDomain(config);
1239
+ this.logger.debug("EIP-712 Domain", domain);
1240
+ const types = this.getTypes();
1241
+ const orderValue = {
1242
+ salt: order.salt,
1243
+ maker: order.maker,
1244
+ signer: order.signer,
1245
+ taker: order.taker,
1246
+ tokenId: order.tokenId,
1247
+ makerAmount: order.makerAmount,
1248
+ takerAmount: order.takerAmount,
1249
+ expiration: order.expiration,
1250
+ nonce: order.nonce,
1251
+ feeRateBps: order.feeRateBps,
1252
+ side: order.side,
1253
+ signatureType: order.signatureType
1254
+ };
1255
+ this.logger.debug("EIP-712 Order Value", orderValue);
1256
+ console.log("[OrderSigner] Full signing payload:", JSON.stringify({
1257
+ domain,
1258
+ types: this.getTypes(),
1259
+ value: orderValue
1260
+ }, null, 2));
1261
+ try {
1262
+ const signature = await this.wallet.signTypedData(domain, types, orderValue);
1263
+ this.logger.info("Successfully generated EIP-712 signature", {
1264
+ signature: signature.slice(0, 10) + "..."
1265
+ });
1266
+ return signature;
1267
+ } catch (error) {
1268
+ this.logger.error("Failed to sign order", error);
1269
+ throw error;
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Gets the EIP-712 domain for signing.
1274
+ *
1275
+ * @param config - Signing configuration
1276
+ * @returns EIP-712 domain object
1277
+ *
1278
+ * @internal
1279
+ */
1280
+ getDomain(config) {
1281
+ return {
1282
+ name: "Limitless CTF Exchange",
1283
+ version: "1",
1284
+ chainId: config.chainId,
1285
+ verifyingContract: config.contractAddress
1286
+ };
1287
+ }
1288
+ /**
1289
+ * Gets the EIP-712 type definitions.
1290
+ *
1291
+ * @remarks
1292
+ * This matches the order structure expected by the Limitless Exchange
1293
+ * smart contracts.
1294
+ *
1295
+ * @returns EIP-712 types definition
1296
+ *
1297
+ * @internal
1298
+ */
1299
+ getTypes() {
1300
+ return {
1301
+ Order: [
1302
+ { name: "salt", type: "uint256" },
1303
+ { name: "maker", type: "address" },
1304
+ { name: "signer", type: "address" },
1305
+ { name: "taker", type: "address" },
1306
+ { name: "tokenId", type: "uint256" },
1307
+ { name: "makerAmount", type: "uint256" },
1308
+ { name: "takerAmount", type: "uint256" },
1309
+ { name: "expiration", type: "uint256" },
1310
+ { name: "nonce", type: "uint256" },
1311
+ { name: "feeRateBps", type: "uint256" },
1312
+ { name: "side", type: "uint8" },
1313
+ { name: "signatureType", type: "uint8" }
1314
+ ]
1315
+ };
1316
+ }
1317
+ };
1318
+
1319
+ // src/orders/validator.ts
1320
+ var import_ethers3 = require("ethers");
1321
+ var ValidationError = class extends Error {
1322
+ constructor(message) {
1323
+ super(message);
1324
+ this.name = "ValidationError";
1325
+ }
1326
+ };
1327
+ function isFOKOrder(args) {
1328
+ return "amount" in args;
1329
+ }
1330
+ function validateOrderArgs(args) {
1331
+ if (!args.tokenId) {
1332
+ throw new ValidationError("TokenId is required");
1333
+ }
1334
+ if (args.tokenId === "0") {
1335
+ throw new ValidationError("TokenId cannot be zero");
1336
+ }
1337
+ if (!/^\d+$/.test(args.tokenId)) {
1338
+ throw new ValidationError(`Invalid tokenId format: ${args.tokenId}`);
1339
+ }
1340
+ if (args.taker && !import_ethers3.ethers.isAddress(args.taker)) {
1341
+ throw new ValidationError(`Invalid taker address: ${args.taker}`);
1342
+ }
1343
+ if (args.expiration !== void 0) {
1344
+ if (!/^\d+$/.test(args.expiration)) {
1345
+ throw new ValidationError(`Invalid expiration format: ${args.expiration}`);
1346
+ }
1347
+ }
1348
+ if (args.nonce !== void 0) {
1349
+ if (!Number.isInteger(args.nonce) || args.nonce < 0) {
1350
+ throw new ValidationError(`Invalid nonce: ${args.nonce}`);
1351
+ }
1352
+ }
1353
+ if (isFOKOrder(args)) {
1354
+ if (typeof args.makerAmount !== "number" || isNaN(args.makerAmount)) {
1355
+ throw new ValidationError("Amount must be a valid number");
1356
+ }
1357
+ if (args.makerAmount <= 0) {
1358
+ throw new ValidationError(`Amount must be positive, got: ${args.makerAmount}`);
1359
+ }
1360
+ const amountStr = args.makerAmount.toString();
1361
+ const decimalIndex = amountStr.indexOf(".");
1362
+ if (decimalIndex !== -1) {
1363
+ const decimalPlaces = amountStr.length - decimalIndex - 1;
1364
+ if (decimalPlaces > 2) {
1365
+ throw new ValidationError(
1366
+ `Amount must have max 2 decimal places, got: ${args.makerAmount} (${decimalPlaces} decimals)`
1367
+ );
1368
+ }
1369
+ }
1370
+ } else {
1371
+ if (typeof args.price !== "number" || isNaN(args.price)) {
1372
+ throw new ValidationError("Price must be a valid number");
1373
+ }
1374
+ if (args.price < 0 || args.price > 1) {
1375
+ throw new ValidationError(`Price must be between 0 and 1, got: ${args.price}`);
1376
+ }
1377
+ if (typeof args.size !== "number" || isNaN(args.size)) {
1378
+ throw new ValidationError("Size must be a valid number");
1379
+ }
1380
+ if (args.size <= 0) {
1381
+ throw new ValidationError(`Size must be positive, got: ${args.size}`);
1382
+ }
1383
+ }
1384
+ }
1385
+ function validateUnsignedOrder(order) {
1386
+ if (!import_ethers3.ethers.isAddress(order.maker)) {
1387
+ throw new ValidationError(`Invalid maker address: ${order.maker}`);
1388
+ }
1389
+ if (!import_ethers3.ethers.isAddress(order.signer)) {
1390
+ throw new ValidationError(`Invalid signer address: ${order.signer}`);
1391
+ }
1392
+ if (!import_ethers3.ethers.isAddress(order.taker)) {
1393
+ throw new ValidationError(`Invalid taker address: ${order.taker}`);
1394
+ }
1395
+ if (!order.makerAmount || order.makerAmount === 0) {
1396
+ throw new ValidationError("MakerAmount must be greater than zero");
1397
+ }
1398
+ if (!order.takerAmount || order.takerAmount === 0) {
1399
+ throw new ValidationError("TakerAmount must be greater than zero");
1400
+ }
1401
+ if (typeof order.makerAmount !== "number" || order.makerAmount <= 0) {
1402
+ throw new ValidationError(`Invalid makerAmount: ${order.makerAmount}`);
1403
+ }
1404
+ if (typeof order.takerAmount !== "number" || order.takerAmount <= 0) {
1405
+ throw new ValidationError(`Invalid takerAmount: ${order.takerAmount}`);
1406
+ }
1407
+ if (!/^\d+$/.test(order.tokenId)) {
1408
+ throw new ValidationError(`Invalid tokenId format: ${order.tokenId}`);
1409
+ }
1410
+ if (!/^\d+$/.test(order.expiration)) {
1411
+ throw new ValidationError(`Invalid expiration format: ${order.expiration}`);
1412
+ }
1413
+ if (!Number.isInteger(order.salt) || order.salt <= 0) {
1414
+ throw new ValidationError(`Invalid salt: ${order.salt}`);
1415
+ }
1416
+ if (!Number.isInteger(order.nonce) || order.nonce < 0) {
1417
+ throw new ValidationError(`Invalid nonce: ${order.nonce}`);
1418
+ }
1419
+ if (!Number.isInteger(order.feeRateBps) || order.feeRateBps < 0) {
1420
+ throw new ValidationError(`Invalid feeRateBps: ${order.feeRateBps}`);
1421
+ }
1422
+ if (order.side !== 0 && order.side !== 1) {
1423
+ throw new ValidationError(`Invalid side: ${order.side}. Must be 0 (BUY) or 1 (SELL)`);
1424
+ }
1425
+ if (!Number.isInteger(order.signatureType) || order.signatureType < 0) {
1426
+ throw new ValidationError(`Invalid signatureType: ${order.signatureType}`);
1427
+ }
1428
+ if (order.price !== void 0) {
1429
+ if (typeof order.price !== "number" || isNaN(order.price)) {
1430
+ throw new ValidationError("Price must be a valid number");
1431
+ }
1432
+ if (order.price < 0 || order.price > 1) {
1433
+ throw new ValidationError(`Price must be between 0 and 1, got: ${order.price}`);
1434
+ }
1435
+ }
1436
+ }
1437
+ function validateSignedOrder(order) {
1438
+ validateUnsignedOrder(order);
1439
+ if (!order.signature) {
1440
+ throw new ValidationError("Signature is required");
1441
+ }
1442
+ if (!order.signature.startsWith("0x")) {
1443
+ throw new ValidationError("Signature must start with 0x");
1444
+ }
1445
+ if (order.signature.length !== 132) {
1446
+ throw new ValidationError(
1447
+ `Invalid signature length: ${order.signature.length}. Expected 132 characters.`
1448
+ );
1449
+ }
1450
+ if (!/^0x[0-9a-fA-F]{130}$/.test(order.signature)) {
1451
+ throw new ValidationError("Signature must be valid hex string");
1452
+ }
1453
+ }
1454
+
1455
+ // src/orders/client.ts
1456
+ var OrderClient = class {
1457
+ /**
1458
+ * Creates a new order client instance.
1459
+ *
1460
+ * @param config - Order client configuration
1461
+ *
1462
+ * @throws Error if neither marketType nor signingConfig is provided
1463
+ */
1464
+ constructor(config) {
1465
+ this.httpClient = config.httpClient;
1466
+ this.logger = config.logger || new NoOpLogger();
1467
+ this.ownerId = config.userData.userId;
1468
+ const feeRateBps = config.userData.feeRateBps;
1469
+ this.orderBuilder = new OrderBuilder(config.wallet.address, feeRateBps, 1e-3);
1470
+ this.orderSigner = new OrderSigner(config.wallet, this.logger);
1471
+ if (config.signingConfig) {
1472
+ this.signingConfig = config.signingConfig;
1473
+ } else if (config.marketType) {
1474
+ const chainId = parseInt(process.env.CHAIN_ID || "8453");
1475
+ const contractAddress = config.marketType === "NEGRISK" /* NEGRISK */ ? process.env.NEGRISK_CONTRACT_ADDRESS || getContractAddress("NEGRISK", chainId) : process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
1476
+ this.signingConfig = {
1477
+ chainId,
1478
+ contractAddress,
1479
+ marketType: config.marketType
1480
+ };
1481
+ this.logger.info("Auto-configured signing", {
1482
+ chainId,
1483
+ contractAddress,
1484
+ marketType: config.marketType
1485
+ });
1486
+ } else {
1487
+ const chainId = parseInt(process.env.CHAIN_ID || "8453");
1488
+ const contractAddress = process.env.CLOB_CONTRACT_ADDRESS || getContractAddress("CLOB", chainId);
1489
+ this.signingConfig = {
1490
+ chainId,
1491
+ contractAddress,
1492
+ marketType: "CLOB" /* CLOB */
1493
+ };
1494
+ this.logger.debug("Using default CLOB configuration", {
1495
+ chainId,
1496
+ contractAddress
1497
+ });
1498
+ }
1499
+ }
1500
+ /**
1501
+ * Creates and submits a new order.
1502
+ *
1503
+ * @remarks
1504
+ * This method handles the complete order creation flow:
1505
+ * 1. Build unsigned order
1506
+ * 2. Sign with EIP-712
1507
+ * 3. Submit to API
1508
+ *
1509
+ * @param params - Order parameters
1510
+ * @returns Promise resolving to order response
1511
+ *
1512
+ * @throws Error if order creation fails
1513
+ *
1514
+ * @example
1515
+ * ```typescript
1516
+ * const order = await orderClient.createOrder({
1517
+ * tokenId: '123456',
1518
+ * price: 0.65,
1519
+ * size: 100,
1520
+ * side: Side.BUY,
1521
+ * orderType: OrderType.GTC,
1522
+ * marketSlug: 'market-slug'
1523
+ * });
1524
+ *
1525
+ * console.log(`Order created: ${order.order.id}`);
1526
+ * ```
1527
+ */
1528
+ async createOrder(params) {
1529
+ this.logger.info("Creating order", {
1530
+ side: params.side,
1531
+ orderType: params.orderType,
1532
+ marketSlug: params.marketSlug
1533
+ });
1534
+ const unsignedOrder = this.orderBuilder.buildOrder(params);
1535
+ this.logger.debug("Built unsigned order", {
1536
+ salt: unsignedOrder.salt,
1537
+ makerAmount: unsignedOrder.makerAmount,
1538
+ takerAmount: unsignedOrder.takerAmount
1539
+ });
1540
+ const signature = await this.orderSigner.signOrder(
1541
+ unsignedOrder,
1542
+ this.signingConfig
1543
+ );
1544
+ const payload = {
1545
+ order: {
1546
+ ...unsignedOrder,
1547
+ signature
1548
+ },
1549
+ orderType: params.orderType,
1550
+ marketSlug: params.marketSlug,
1551
+ ownerId: this.ownerId
1552
+ };
1553
+ this.logger.debug("Submitting order to API");
1554
+ console.log("[OrderClient] Full API request payload:", JSON.stringify(payload, null, 2));
1555
+ const apiResponse = await this.httpClient.post(
1556
+ "/orders",
1557
+ payload
1558
+ );
1559
+ this.logger.info("Order created successfully", {
1560
+ orderId: apiResponse.order.id
1561
+ });
1562
+ return this.transformOrderResponse(apiResponse);
1563
+ }
1564
+ /**
1565
+ * Transforms raw API response to clean OrderResponse DTO.
1566
+ *
1567
+ * @param apiResponse - Raw API response with nested objects
1568
+ * @returns Clean OrderResponse with only essential fields
1569
+ *
1570
+ * @internal
1571
+ */
1572
+ transformOrderResponse(apiResponse) {
1573
+ const cleanOrder = {
1574
+ order: {
1575
+ id: apiResponse.order.id,
1576
+ createdAt: apiResponse.order.createdAt,
1577
+ makerAmount: apiResponse.order.makerAmount,
1578
+ takerAmount: apiResponse.order.takerAmount,
1579
+ expiration: apiResponse.order.expiration,
1580
+ signatureType: apiResponse.order.signatureType,
1581
+ salt: apiResponse.order.salt,
1582
+ maker: apiResponse.order.maker,
1583
+ signer: apiResponse.order.signer,
1584
+ taker: apiResponse.order.taker,
1585
+ tokenId: apiResponse.order.tokenId,
1586
+ side: apiResponse.order.side,
1587
+ feeRateBps: apiResponse.order.feeRateBps,
1588
+ nonce: apiResponse.order.nonce,
1589
+ signature: apiResponse.order.signature,
1590
+ orderType: apiResponse.order.orderType,
1591
+ price: apiResponse.order.price,
1592
+ marketId: apiResponse.order.marketId
1593
+ }
1594
+ };
1595
+ if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
1596
+ cleanOrder.makerMatches = apiResponse.makerMatches.map((match) => ({
1597
+ id: match.id,
1598
+ createdAt: match.createdAt,
1599
+ matchedSize: match.matchedSize,
1600
+ orderId: match.orderId
1601
+ }));
1602
+ }
1603
+ return cleanOrder;
1604
+ }
1605
+ /**
1606
+ * Cancels an existing order by ID.
1607
+ *
1608
+ * @param orderId - Order ID to cancel
1609
+ * @returns Promise resolving to cancellation message
1610
+ *
1611
+ * @throws Error if cancellation fails
1612
+ *
1613
+ * @example
1614
+ * ```typescript
1615
+ * const result = await orderClient.cancel('order-id-123');
1616
+ * console.log(result.message); // "Order canceled successfully"
1617
+ * ```
1618
+ */
1619
+ async cancel(orderId) {
1620
+ this.logger.info("Cancelling order", { orderId });
1621
+ const response = await this.httpClient.delete(
1622
+ `/orders/${orderId}`
1623
+ );
1624
+ this.logger.info("Order cancellation response", {
1625
+ orderId,
1626
+ message: response.message
1627
+ });
1628
+ return response;
1629
+ }
1630
+ /**
1631
+ * Cancels all orders for a specific market.
1632
+ *
1633
+ * @param marketSlug - Market slug to cancel all orders for
1634
+ * @returns Promise resolving to cancellation message
1635
+ *
1636
+ * @throws Error if cancellation fails
1637
+ *
1638
+ * @example
1639
+ * ```typescript
1640
+ * const result = await orderClient.cancelAll('market-slug-123');
1641
+ * console.log(result.message); // "Orders canceled successfully"
1642
+ * ```
1643
+ */
1644
+ async cancelAll(marketSlug) {
1645
+ this.logger.info("Cancelling all orders for market", { marketSlug });
1646
+ const response = await this.httpClient.delete(
1647
+ `/orders/all/${marketSlug}`
1648
+ );
1649
+ this.logger.info("All orders cancellation response", {
1650
+ marketSlug,
1651
+ message: response.message
1652
+ });
1653
+ return response;
1654
+ }
1655
+ /**
1656
+ * @deprecated Use `cancel()` instead
1657
+ */
1658
+ async cancelOrder(orderId) {
1659
+ await this.cancel(orderId);
1660
+ }
1661
+ /**
1662
+ * Gets an order by ID.
1663
+ *
1664
+ * @param orderId - Order ID to fetch
1665
+ * @returns Promise resolving to order details
1666
+ *
1667
+ * @throws Error if order not found
1668
+ *
1669
+ * @example
1670
+ * ```typescript
1671
+ * const order = await orderClient.getOrder('order-id-123');
1672
+ * console.log(order.order.side);
1673
+ * ```
1674
+ */
1675
+ async getOrder(orderId) {
1676
+ this.logger.debug("Fetching order", { orderId });
1677
+ const response = await this.httpClient.get(
1678
+ `/orders/${orderId}`
1679
+ );
1680
+ return response;
1681
+ }
1682
+ /**
1683
+ * Builds an unsigned order without submitting.
1684
+ *
1685
+ * @remarks
1686
+ * Useful for advanced use cases where you need the unsigned order
1687
+ * before signing and submission.
1688
+ *
1689
+ * @param params - Order parameters
1690
+ * @returns Unsigned order
1691
+ *
1692
+ * @example
1693
+ * ```typescript
1694
+ * const unsignedOrder = orderClient.buildUnsignedOrder({
1695
+ * tokenId: '123456',
1696
+ * price: 0.65,
1697
+ * size: 100,
1698
+ * side: Side.BUY
1699
+ * });
1700
+ * ```
1701
+ */
1702
+ buildUnsignedOrder(params) {
1703
+ return this.orderBuilder.buildOrder(params);
1704
+ }
1705
+ /**
1706
+ * Signs an unsigned order without submitting.
1707
+ *
1708
+ * @remarks
1709
+ * Useful for advanced use cases where you need to inspect
1710
+ * the signature before submission.
1711
+ *
1712
+ * @param order - Unsigned order to sign
1713
+ * @returns Promise resolving to signature
1714
+ *
1715
+ * @example
1716
+ * ```typescript
1717
+ * const signature = await orderClient.signOrder(unsignedOrder);
1718
+ * ```
1719
+ */
1720
+ async signOrder(order) {
1721
+ return await this.orderSigner.signOrder(order, this.signingConfig);
1722
+ }
1723
+ };
1724
+
1725
+ // src/markets/fetcher.ts
1726
+ var MarketFetcher = class {
1727
+ /**
1728
+ * Creates a new market fetcher instance.
1729
+ *
1730
+ * @param httpClient - HTTP client for API requests
1731
+ * @param logger - Optional logger for debugging (default: no logging)
1732
+ *
1733
+ * @example
1734
+ * ```typescript
1735
+ * const fetcher = new MarketFetcher(httpClient);
1736
+ * ```
1737
+ */
1738
+ constructor(httpClient, logger) {
1739
+ this.httpClient = httpClient;
1740
+ this.logger = logger || new NoOpLogger();
1741
+ }
1742
+ /**
1743
+ * Gets active markets with query parameters and pagination support.
1744
+ *
1745
+ * @param params - Query parameters for filtering and pagination
1746
+ * @returns Promise resolving to active markets response
1747
+ * @throws Error if API request fails
1748
+ *
1749
+ * @example
1750
+ * ```typescript
1751
+ * // Get 8 markets sorted by LP rewards
1752
+ * const response = await fetcher.getActiveMarkets({
1753
+ * limit: 8,
1754
+ * sortBy: 'lp_rewards'
1755
+ * });
1756
+ * console.log(`Found ${response.data.length} of ${response.totalMarketsCount} markets`);
1757
+ *
1758
+ * // Get page 2
1759
+ * const page2 = await fetcher.getActiveMarkets({
1760
+ * limit: 8,
1761
+ * page: 2,
1762
+ * sortBy: 'ending_soon'
1763
+ * });
1764
+ * ```
1765
+ */
1766
+ async getActiveMarkets(params) {
1767
+ const queryParams = new URLSearchParams();
1768
+ if (params?.limit !== void 0) {
1769
+ queryParams.append("limit", params.limit.toString());
1770
+ }
1771
+ if (params?.page !== void 0) {
1772
+ queryParams.append("page", params.page.toString());
1773
+ }
1774
+ if (params?.sortBy) {
1775
+ queryParams.append("sortBy", params.sortBy);
1776
+ }
1777
+ const queryString = queryParams.toString();
1778
+ const endpoint = `/markets/active${queryString ? `?${queryString}` : ""}`;
1779
+ this.logger.debug("Fetching active markets", { params });
1780
+ try {
1781
+ const response = await this.httpClient.get(endpoint);
1782
+ this.logger.info("Active markets fetched successfully", {
1783
+ count: response.data.length,
1784
+ total: response.totalMarketsCount,
1785
+ sortBy: params?.sortBy,
1786
+ page: params?.page
1787
+ });
1788
+ return response;
1789
+ } catch (error) {
1790
+ this.logger.error("Failed to fetch active markets", error, { params });
1791
+ throw error;
1792
+ }
1793
+ }
1794
+ /**
1795
+ * Gets a single market by slug.
1796
+ *
1797
+ * @param slug - Market slug identifier
1798
+ * @returns Promise resolving to market details
1799
+ * @throws Error if API request fails or market not found
1800
+ *
1801
+ * @example
1802
+ * ```typescript
1803
+ * const market = await fetcher.getMarket('bitcoin-price-2024');
1804
+ * console.log(`Market: ${market.title}`);
1805
+ * ```
1806
+ */
1807
+ async getMarket(slug) {
1808
+ this.logger.debug("Fetching market", { slug });
1809
+ try {
1810
+ const market = await this.httpClient.get(`/markets/${slug}`);
1811
+ this.logger.info("Market fetched successfully", {
1812
+ slug,
1813
+ title: market.title
1814
+ });
1815
+ return market;
1816
+ } catch (error) {
1817
+ this.logger.error("Failed to fetch market", error, { slug });
1818
+ throw error;
1819
+ }
1820
+ }
1821
+ /**
1822
+ * Gets the orderbook for a CLOB market.
1823
+ *
1824
+ * @param slug - Market slug identifier
1825
+ * @returns Promise resolving to orderbook data
1826
+ * @throws Error if API request fails
1827
+ *
1828
+ * @example
1829
+ * ```typescript
1830
+ * const orderbook = await fetcher.getOrderBook('bitcoin-price-2024');
1831
+ * console.log(`Bids: ${orderbook.bids.length}, Asks: ${orderbook.asks.length}`);
1832
+ * ```
1833
+ */
1834
+ async getOrderBook(slug) {
1835
+ this.logger.debug("Fetching orderbook", { slug });
1836
+ try {
1837
+ const orderbook = await this.httpClient.get(
1838
+ `/markets/${slug}/orderbook`
1839
+ );
1840
+ this.logger.info("Orderbook fetched successfully", {
1841
+ slug,
1842
+ bids: orderbook.bids.length,
1843
+ asks: orderbook.asks.length,
1844
+ tokenId: orderbook.tokenId
1845
+ });
1846
+ return orderbook;
1847
+ } catch (error) {
1848
+ this.logger.error("Failed to fetch orderbook", error, { slug });
1849
+ throw error;
1850
+ }
1851
+ }
1852
+ /**
1853
+ * Gets the current price for a token.
1854
+ *
1855
+ * @param tokenId - Token ID
1856
+ * @returns Promise resolving to price information
1857
+ * @throws Error if API request fails
1858
+ *
1859
+ * @example
1860
+ * ```typescript
1861
+ * const price = await fetcher.getPrice('123456');
1862
+ * console.log(`Current price: ${price.price}`);
1863
+ * ```
1864
+ */
1865
+ async getPrice(tokenId) {
1866
+ this.logger.debug("Fetching price", { tokenId });
1867
+ try {
1868
+ const price = await this.httpClient.get(`/prices/${tokenId}`);
1869
+ this.logger.info("Price fetched successfully", {
1870
+ tokenId,
1871
+ price: price.price
1872
+ });
1873
+ return price;
1874
+ } catch (error) {
1875
+ this.logger.error("Failed to fetch price", error, { tokenId });
1876
+ throw error;
1877
+ }
1878
+ }
1879
+ };
1880
+
1881
+ // src/portfolio/fetcher.ts
1882
+ var PortfolioFetcher = class {
1883
+ /**
1884
+ * Creates a new portfolio fetcher instance.
1885
+ *
1886
+ * @param httpClient - Authenticated HTTP client for API requests
1887
+ * @param logger - Optional logger for debugging (default: no logging)
1888
+ *
1889
+ * @example
1890
+ * ```typescript
1891
+ * // Create authenticated client
1892
+ * const httpClient = new HttpClient({ baseURL: API_URL });
1893
+ * await authenticator.authenticate({ client: 'eoa' });
1894
+ *
1895
+ * // Create portfolio fetcher
1896
+ * const portfolioFetcher = new PortfolioFetcher(httpClient);
1897
+ * ```
1898
+ */
1899
+ constructor(httpClient, logger) {
1900
+ this.httpClient = httpClient;
1901
+ this.logger = logger || new NoOpLogger();
1902
+ }
1903
+ /**
1904
+ * Gets raw portfolio positions response from API.
1905
+ *
1906
+ * @returns Promise resolving to portfolio positions response with CLOB and AMM positions
1907
+ * @throws Error if API request fails or user is not authenticated
1908
+ *
1909
+ * @example
1910
+ * ```typescript
1911
+ * const response = await portfolioFetcher.getPositions();
1912
+ * console.log(`CLOB positions: ${response.clob.length}`);
1913
+ * console.log(`AMM positions: ${response.amm.length}`);
1914
+ * console.log(`Total points: ${response.accumulativePoints}`);
1915
+ * ```
1916
+ */
1917
+ async getPositions() {
1918
+ this.logger.debug("Fetching user positions");
1919
+ try {
1920
+ const response = await this.httpClient.get(
1921
+ "/portfolio/positions"
1922
+ );
1923
+ this.logger.info("Positions fetched successfully", {
1924
+ clobCount: response.clob?.length || 0,
1925
+ ammCount: response.amm?.length || 0
1926
+ });
1927
+ return response;
1928
+ } catch (error) {
1929
+ this.logger.error("Failed to fetch positions", error);
1930
+ throw error;
1931
+ }
1932
+ }
1933
+ /**
1934
+ * Gets CLOB positions only.
1935
+ *
1936
+ * @returns Promise resolving to array of CLOB positions
1937
+ * @throws Error if API request fails
1938
+ *
1939
+ * @example
1940
+ * ```typescript
1941
+ * const clobPositions = await portfolioFetcher.getCLOBPositions();
1942
+ * clobPositions.forEach(pos => {
1943
+ * console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
1944
+ * });
1945
+ * ```
1946
+ */
1947
+ async getCLOBPositions() {
1948
+ const response = await this.getPositions();
1949
+ return response.clob || [];
1950
+ }
1951
+ /**
1952
+ * Gets AMM positions only.
1953
+ *
1954
+ * @returns Promise resolving to array of AMM positions
1955
+ * @throws Error if API request fails
1956
+ *
1957
+ * @example
1958
+ * ```typescript
1959
+ * const ammPositions = await portfolioFetcher.getAMMPositions();
1960
+ * ammPositions.forEach(pos => {
1961
+ * console.log(`${pos.market.title}: ${pos.unrealizedPnl} P&L`);
1962
+ * });
1963
+ * ```
1964
+ */
1965
+ async getAMMPositions() {
1966
+ const response = await this.getPositions();
1967
+ return response.amm || [];
1968
+ }
1969
+ /**
1970
+ * Flattens positions into a unified format for easier consumption.
1971
+ *
1972
+ * @remarks
1973
+ * Converts CLOB positions (which have YES/NO sides) and AMM positions
1974
+ * into a unified Position array. Only includes positions with non-zero values.
1975
+ *
1976
+ * @returns Promise resolving to array of flattened positions
1977
+ * @throws Error if API request fails
1978
+ *
1979
+ * @example
1980
+ * ```typescript
1981
+ * const positions = await portfolioFetcher.getFlattenedPositions();
1982
+ * positions.forEach(pos => {
1983
+ * const pnlPercent = (pos.unrealizedPnl / pos.costBasis) * 100;
1984
+ * console.log(`${pos.market.title} (${pos.side}): ${pnlPercent.toFixed(2)}% P&L`);
1985
+ * });
1986
+ * ```
1987
+ */
1988
+ async getFlattenedPositions() {
1989
+ const response = await this.getPositions();
1990
+ const positions = [];
1991
+ for (const clobPos of response.clob || []) {
1992
+ const yesCost = parseFloat(clobPos.positions.yes.cost);
1993
+ const yesValue = parseFloat(clobPos.positions.yes.marketValue);
1994
+ if (yesCost > 0 || yesValue > 0) {
1995
+ positions.push({
1996
+ type: "CLOB",
1997
+ market: clobPos.market,
1998
+ side: "YES",
1999
+ costBasis: yesCost,
2000
+ marketValue: yesValue,
2001
+ unrealizedPnl: parseFloat(clobPos.positions.yes.unrealizedPnl),
2002
+ realizedPnl: parseFloat(clobPos.positions.yes.realisedPnl),
2003
+ currentPrice: clobPos.latestTrade?.latestYesPrice ?? 0,
2004
+ avgPrice: yesCost > 0 ? parseFloat(clobPos.positions.yes.fillPrice) / 1e6 : 0,
2005
+ tokenBalance: parseFloat(clobPos.tokensBalance.yes)
2006
+ });
2007
+ }
2008
+ const noCost = parseFloat(clobPos.positions.no.cost);
2009
+ const noValue = parseFloat(clobPos.positions.no.marketValue);
2010
+ if (noCost > 0 || noValue > 0) {
2011
+ positions.push({
2012
+ type: "CLOB",
2013
+ market: clobPos.market,
2014
+ side: "NO",
2015
+ costBasis: noCost,
2016
+ marketValue: noValue,
2017
+ unrealizedPnl: parseFloat(clobPos.positions.no.unrealizedPnl),
2018
+ realizedPnl: parseFloat(clobPos.positions.no.realisedPnl),
2019
+ currentPrice: clobPos.latestTrade?.latestNoPrice ?? 0,
2020
+ avgPrice: noCost > 0 ? parseFloat(clobPos.positions.no.fillPrice) / 1e6 : 0,
2021
+ tokenBalance: parseFloat(clobPos.tokensBalance.no)
2022
+ });
2023
+ }
2024
+ }
2025
+ for (const ammPos of response.amm || []) {
2026
+ const cost = parseFloat(ammPos.totalBuysCost);
2027
+ const value = parseFloat(ammPos.collateralAmount);
2028
+ if (cost > 0 || value > 0) {
2029
+ positions.push({
2030
+ type: "AMM",
2031
+ market: ammPos.market,
2032
+ side: ammPos.outcomeIndex === 0 ? "YES" : "NO",
2033
+ costBasis: cost,
2034
+ marketValue: value,
2035
+ unrealizedPnl: parseFloat(ammPos.unrealizedPnl),
2036
+ realizedPnl: parseFloat(ammPos.realizedPnl),
2037
+ currentPrice: ammPos.latestTrade ? parseFloat(ammPos.latestTrade.outcomeTokenPrice) : 0,
2038
+ avgPrice: parseFloat(ammPos.averageFillPrice),
2039
+ tokenBalance: parseFloat(ammPos.outcomeTokenAmount)
2040
+ });
2041
+ }
2042
+ }
2043
+ this.logger.debug("Flattened positions", { count: positions.length });
2044
+ return positions;
2045
+ }
2046
+ /**
2047
+ * Calculates portfolio summary statistics from raw API response.
2048
+ *
2049
+ * @param response - Portfolio positions response from API
2050
+ * @returns Portfolio summary with totals and statistics
2051
+ *
2052
+ * @example
2053
+ * ```typescript
2054
+ * const response = await portfolioFetcher.getPositions();
2055
+ * const summary = portfolioFetcher.calculateSummary(response);
2056
+ *
2057
+ * console.log(`Total Portfolio Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
2058
+ * console.log(`Total P&L: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
2059
+ * console.log(`CLOB Positions: ${summary.breakdown.clob.positions}`);
2060
+ * console.log(`AMM Positions: ${summary.breakdown.amm.positions}`);
2061
+ * ```
2062
+ */
2063
+ calculateSummary(response) {
2064
+ this.logger.debug("Calculating portfolio summary", {
2065
+ clobCount: response.clob?.length || 0,
2066
+ ammCount: response.amm?.length || 0
2067
+ });
2068
+ let totalValue = 0;
2069
+ let totalCostBasis = 0;
2070
+ let totalUnrealizedPnl = 0;
2071
+ let totalRealizedPnl = 0;
2072
+ let clobPositions = 0;
2073
+ let clobValue = 0;
2074
+ let clobPnl = 0;
2075
+ let ammPositions = 0;
2076
+ let ammValue = 0;
2077
+ let ammPnl = 0;
2078
+ for (const clobPos of response.clob || []) {
2079
+ const yesCost = parseFloat(clobPos.positions.yes.cost);
2080
+ const yesValue = parseFloat(clobPos.positions.yes.marketValue);
2081
+ const yesUnrealizedPnl = parseFloat(clobPos.positions.yes.unrealizedPnl);
2082
+ const yesRealizedPnl = parseFloat(clobPos.positions.yes.realisedPnl);
2083
+ if (yesCost > 0 || yesValue > 0) {
2084
+ clobPositions++;
2085
+ totalCostBasis += yesCost;
2086
+ totalValue += yesValue;
2087
+ totalUnrealizedPnl += yesUnrealizedPnl;
2088
+ totalRealizedPnl += yesRealizedPnl;
2089
+ clobValue += yesValue;
2090
+ clobPnl += yesUnrealizedPnl;
2091
+ }
2092
+ const noCost = parseFloat(clobPos.positions.no.cost);
2093
+ const noValue = parseFloat(clobPos.positions.no.marketValue);
2094
+ const noUnrealizedPnl = parseFloat(clobPos.positions.no.unrealizedPnl);
2095
+ const noRealizedPnl = parseFloat(clobPos.positions.no.realisedPnl);
2096
+ if (noCost > 0 || noValue > 0) {
2097
+ clobPositions++;
2098
+ totalCostBasis += noCost;
2099
+ totalValue += noValue;
2100
+ totalUnrealizedPnl += noUnrealizedPnl;
2101
+ totalRealizedPnl += noRealizedPnl;
2102
+ clobValue += noValue;
2103
+ clobPnl += noUnrealizedPnl;
2104
+ }
2105
+ }
2106
+ for (const ammPos of response.amm || []) {
2107
+ const cost = parseFloat(ammPos.totalBuysCost);
2108
+ const value = parseFloat(ammPos.collateralAmount);
2109
+ const unrealizedPnl = parseFloat(ammPos.unrealizedPnl);
2110
+ const realizedPnl = parseFloat(ammPos.realizedPnl);
2111
+ if (cost > 0 || value > 0) {
2112
+ ammPositions++;
2113
+ totalCostBasis += cost;
2114
+ totalValue += value;
2115
+ totalUnrealizedPnl += unrealizedPnl;
2116
+ totalRealizedPnl += realizedPnl;
2117
+ ammValue += value;
2118
+ ammPnl += unrealizedPnl;
2119
+ }
2120
+ }
2121
+ const totalUnrealizedPnlPercent = totalCostBasis > 0 ? totalUnrealizedPnl / totalCostBasis * 100 : 0;
2122
+ const uniqueMarkets = /* @__PURE__ */ new Set();
2123
+ for (const pos of response.clob || []) {
2124
+ uniqueMarkets.add(pos.market.id);
2125
+ }
2126
+ for (const pos of response.amm || []) {
2127
+ uniqueMarkets.add(pos.market.id);
2128
+ }
2129
+ const summary = {
2130
+ totalValue,
2131
+ totalCostBasis,
2132
+ totalUnrealizedPnl,
2133
+ totalRealizedPnl,
2134
+ totalUnrealizedPnlPercent,
2135
+ positionCount: clobPositions + ammPositions,
2136
+ marketCount: uniqueMarkets.size,
2137
+ breakdown: {
2138
+ clob: {
2139
+ positions: clobPositions,
2140
+ value: clobValue,
2141
+ pnl: clobPnl
2142
+ },
2143
+ amm: {
2144
+ positions: ammPositions,
2145
+ value: ammValue,
2146
+ pnl: ammPnl
2147
+ }
2148
+ }
2149
+ };
2150
+ this.logger.debug("Portfolio summary calculated", summary);
2151
+ return summary;
2152
+ }
2153
+ /**
2154
+ * Gets positions and calculates summary in a single call.
2155
+ *
2156
+ * @returns Promise resolving to response and summary
2157
+ * @throws Error if API request fails or user is not authenticated
2158
+ *
2159
+ * @example
2160
+ * ```typescript
2161
+ * const { response, summary } = await portfolioFetcher.getPortfolio();
2162
+ *
2163
+ * console.log('Portfolio Summary:');
2164
+ * console.log(` Total Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
2165
+ * console.log(` Total P&L: $${(summary.totalUnrealizedPnl / 1e6).toFixed(2)}`);
2166
+ * console.log(` P&L %: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
2167
+ * console.log(`\nCLOB Positions: ${response.clob.length}`);
2168
+ * console.log(`AMM Positions: ${response.amm.length}`);
2169
+ * ```
2170
+ */
2171
+ async getPortfolio() {
2172
+ this.logger.debug("Fetching portfolio with summary");
2173
+ const response = await this.getPositions();
2174
+ const summary = this.calculateSummary(response);
2175
+ this.logger.info("Portfolio fetched with summary", {
2176
+ positionCount: summary.positionCount,
2177
+ totalValueUSDC: summary.totalValue / 1e6,
2178
+ pnlPercent: summary.totalUnrealizedPnlPercent
2179
+ });
2180
+ return { response, summary };
2181
+ }
2182
+ };
2183
+
2184
+ // src/websocket/client.ts
2185
+ var import_socket = require("socket.io-client");
2186
+ var WebSocketClient = class {
2187
+ /**
2188
+ * Creates a new WebSocket client.
2189
+ *
2190
+ * @param config - WebSocket configuration
2191
+ * @param logger - Optional logger for debugging
2192
+ */
2193
+ constructor(config = {}, logger) {
2194
+ this.socket = null;
2195
+ this.state = "disconnected" /* DISCONNECTED */;
2196
+ this.reconnectAttempts = 0;
2197
+ this.subscriptions = /* @__PURE__ */ new Map();
2198
+ this.pendingListeners = [];
2199
+ this.config = {
2200
+ url: config.url || DEFAULT_WS_URL,
2201
+ sessionCookie: config.sessionCookie || "",
2202
+ autoReconnect: config.autoReconnect ?? true,
2203
+ reconnectDelay: config.reconnectDelay || 1e3,
2204
+ maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
2205
+ timeout: config.timeout || 1e4
2206
+ };
2207
+ this.logger = logger || new NoOpLogger();
2208
+ }
2209
+ /**
2210
+ * Gets current connection state.
2211
+ *
2212
+ * @returns Current WebSocket state
2213
+ */
2214
+ getState() {
2215
+ return this.state;
2216
+ }
2217
+ /**
2218
+ * Checks if client is connected.
2219
+ *
2220
+ * @returns True if connected
2221
+ */
2222
+ isConnected() {
2223
+ return this.state === "connected" /* CONNECTED */ && this.socket?.connected === true;
2224
+ }
2225
+ /**
2226
+ * Sets the session cookie for authentication.
2227
+ *
2228
+ * @param sessionCookie - Session cookie value
2229
+ */
2230
+ setSessionCookie(sessionCookie) {
2231
+ this.config.sessionCookie = sessionCookie;
2232
+ if (this.socket?.connected) {
2233
+ this.logger.info("Session cookie updated, reconnecting...");
2234
+ this.disconnect();
2235
+ this.connect();
2236
+ }
2237
+ }
2238
+ /**
2239
+ * Connects to the WebSocket server.
2240
+ *
2241
+ * @returns Promise that resolves when connected
2242
+ * @throws Error if connection fails
2243
+ *
2244
+ * @example
2245
+ * ```typescript
2246
+ * await wsClient.connect();
2247
+ * console.log('Connected!');
2248
+ * ```
2249
+ */
2250
+ async connect() {
2251
+ if (this.socket?.connected) {
2252
+ this.logger.info("Already connected");
2253
+ return;
2254
+ }
2255
+ this.logger.info("Connecting to WebSocket", { url: this.config.url });
2256
+ this.state = "connecting" /* CONNECTING */;
2257
+ return new Promise((resolve, reject) => {
2258
+ const timeout = setTimeout(() => {
2259
+ reject(new Error(`Connection timeout after ${this.config.timeout}ms`));
2260
+ }, this.config.timeout);
2261
+ const wsUrl = this.config.url.endsWith("/markets") ? this.config.url : `${this.config.url}/markets`;
2262
+ this.socket = (0, import_socket.io)(wsUrl, {
2263
+ transports: ["websocket"],
2264
+ // Use WebSocket transport only
2265
+ extraHeaders: {
2266
+ cookie: `limitless_session=${this.config.sessionCookie}`
2267
+ },
2268
+ reconnection: this.config.autoReconnect,
2269
+ reconnectionDelay: this.config.reconnectDelay,
2270
+ reconnectionAttempts: this.config.maxReconnectAttempts,
2271
+ timeout: this.config.timeout
2272
+ });
2273
+ this.attachPendingListeners();
2274
+ this.setupEventHandlers();
2275
+ this.socket.once("connect", () => {
2276
+ clearTimeout(timeout);
2277
+ this.state = "connected" /* CONNECTED */;
2278
+ this.reconnectAttempts = 0;
2279
+ this.logger.info("WebSocket connected");
2280
+ this.resubscribeAll();
2281
+ resolve();
2282
+ });
2283
+ this.socket.once("connect_error", (error) => {
2284
+ clearTimeout(timeout);
2285
+ this.state = "error" /* ERROR */;
2286
+ this.logger.error("WebSocket connection error", error);
2287
+ reject(error);
2288
+ });
2289
+ });
2290
+ }
2291
+ /**
2292
+ * Disconnects from the WebSocket server.
2293
+ *
2294
+ * @example
2295
+ * ```typescript
2296
+ * wsClient.disconnect();
2297
+ * ```
2298
+ */
2299
+ disconnect() {
2300
+ if (!this.socket) {
2301
+ return;
2302
+ }
2303
+ this.logger.info("Disconnecting from WebSocket");
2304
+ this.socket.disconnect();
2305
+ this.socket = null;
2306
+ this.state = "disconnected" /* DISCONNECTED */;
2307
+ this.subscriptions.clear();
2308
+ }
2309
+ /**
2310
+ * Subscribes to a channel.
2311
+ *
2312
+ * @param channel - Channel to subscribe to
2313
+ * @param options - Subscription options
2314
+ * @returns Promise that resolves when subscribed
2315
+ * @throws Error if not connected
2316
+ *
2317
+ * @example
2318
+ * ```typescript
2319
+ * // Subscribe to orderbook for a specific market
2320
+ * await wsClient.subscribe('orderbook', { marketSlug: 'market-123' });
2321
+ *
2322
+ * // Subscribe to all trades
2323
+ * await wsClient.subscribe('trades');
2324
+ *
2325
+ * // Subscribe to your orders
2326
+ * await wsClient.subscribe('orders');
2327
+ * ```
2328
+ */
2329
+ async subscribe(channel, options = {}) {
2330
+ if (!this.isConnected()) {
2331
+ throw new Error("WebSocket not connected. Call connect() first.");
2332
+ }
2333
+ const subscriptionKey = this.getSubscriptionKey(channel, options);
2334
+ this.subscriptions.set(subscriptionKey, options);
2335
+ this.logger.info("Subscribing to channel", { channel, options });
2336
+ return new Promise((resolve, reject) => {
2337
+ this.socket.emit(channel, options, (response) => {
2338
+ if (response?.error) {
2339
+ this.logger.error("Subscription failed", response.error);
2340
+ this.subscriptions.delete(subscriptionKey);
2341
+ reject(new Error(response.error));
2342
+ } else {
2343
+ this.logger.info("Subscribed successfully", { channel, options });
2344
+ resolve();
2345
+ }
2346
+ });
2347
+ });
2348
+ }
2349
+ /**
2350
+ * Unsubscribes from a channel.
2351
+ *
2352
+ * @param channel - Channel to unsubscribe from
2353
+ * @param options - Subscription options (must match subscribe call)
2354
+ * @returns Promise that resolves when unsubscribed
2355
+ *
2356
+ * @example
2357
+ * ```typescript
2358
+ * await wsClient.unsubscribe('orderbook', { marketSlug: 'market-123' });
2359
+ * ```
2360
+ */
2361
+ async unsubscribe(channel, options = {}) {
2362
+ if (!this.isConnected()) {
2363
+ throw new Error("WebSocket not connected");
2364
+ }
2365
+ const subscriptionKey = this.getSubscriptionKey(channel, options);
2366
+ this.subscriptions.delete(subscriptionKey);
2367
+ this.logger.info("Unsubscribing from channel", { channel, options });
2368
+ return new Promise((resolve, reject) => {
2369
+ this.socket.emit("unsubscribe", { channel, ...options }, (response) => {
2370
+ if (response?.error) {
2371
+ this.logger.error("Unsubscribe failed", response.error);
2372
+ reject(new Error(response.error));
2373
+ } else {
2374
+ this.logger.info("Unsubscribed successfully", { channel, options });
2375
+ resolve();
2376
+ }
2377
+ });
2378
+ });
2379
+ }
2380
+ /**
2381
+ * Registers an event listener.
2382
+ *
2383
+ * @param event - Event name
2384
+ * @param handler - Event handler
2385
+ * @returns This client for chaining
2386
+ *
2387
+ * @example
2388
+ * ```typescript
2389
+ * wsClient
2390
+ * .on('orderbook', (data) => console.log('Orderbook:', data))
2391
+ * .on('trade', (data) => console.log('Trade:', data))
2392
+ * .on('error', (error) => console.error('Error:', error));
2393
+ * ```
2394
+ */
2395
+ on(event, handler) {
2396
+ if (!this.socket) {
2397
+ this.pendingListeners.push({ event, handler });
2398
+ return this;
2399
+ }
2400
+ this.socket.on(event, handler);
2401
+ return this;
2402
+ }
2403
+ /**
2404
+ * Registers a one-time event listener.
2405
+ *
2406
+ * @param event - Event name
2407
+ * @param handler - Event handler
2408
+ * @returns This client for chaining
2409
+ */
2410
+ once(event, handler) {
2411
+ if (!this.socket) {
2412
+ throw new Error("WebSocket not initialized. Call connect() first.");
2413
+ }
2414
+ this.socket.once(event, handler);
2415
+ return this;
2416
+ }
2417
+ /**
2418
+ * Removes an event listener.
2419
+ *
2420
+ * @param event - Event name
2421
+ * @param handler - Event handler to remove
2422
+ * @returns This client for chaining
2423
+ */
2424
+ off(event, handler) {
2425
+ if (!this.socket) {
2426
+ return this;
2427
+ }
2428
+ this.socket.off(event, handler);
2429
+ return this;
2430
+ }
2431
+ /**
2432
+ * Attach any pending event listeners that were added before connect().
2433
+ * @internal
2434
+ */
2435
+ attachPendingListeners() {
2436
+ if (!this.socket || this.pendingListeners.length === 0) {
2437
+ return;
2438
+ }
2439
+ for (const { event, handler } of this.pendingListeners) {
2440
+ this.socket.on(event, handler);
2441
+ }
2442
+ this.pendingListeners = [];
2443
+ }
2444
+ /**
2445
+ * Setup internal event handlers for connection management.
2446
+ * @internal
2447
+ */
2448
+ setupEventHandlers() {
2449
+ if (!this.socket) {
2450
+ return;
2451
+ }
2452
+ this.socket.on("connect", () => {
2453
+ this.state = "connected" /* CONNECTED */;
2454
+ this.reconnectAttempts = 0;
2455
+ this.logger.info("WebSocket connected");
2456
+ });
2457
+ this.socket.on("disconnect", (reason) => {
2458
+ this.state = "disconnected" /* DISCONNECTED */;
2459
+ this.logger.info("WebSocket disconnected", { reason });
2460
+ });
2461
+ this.socket.on("error", (error) => {
2462
+ this.state = "error" /* ERROR */;
2463
+ this.logger.error("WebSocket error", error);
2464
+ });
2465
+ this.socket.io.on("reconnect_attempt", (attempt) => {
2466
+ this.state = "reconnecting" /* RECONNECTING */;
2467
+ this.reconnectAttempts = attempt;
2468
+ this.logger.info("Reconnecting...", { attempt });
2469
+ });
2470
+ this.socket.io.on("reconnect", (attempt) => {
2471
+ this.state = "connected" /* CONNECTED */;
2472
+ this.logger.info("Reconnected", { attempts: attempt });
2473
+ this.resubscribeAll();
2474
+ });
2475
+ this.socket.io.on("reconnect_error", (error) => {
2476
+ this.logger.error("Reconnection error", error);
2477
+ });
2478
+ this.socket.io.on("reconnect_failed", () => {
2479
+ this.state = "error" /* ERROR */;
2480
+ this.logger.error("Reconnection failed");
2481
+ });
2482
+ }
2483
+ /**
2484
+ * Re-subscribes to all previous subscriptions after reconnection.
2485
+ * @internal
2486
+ */
2487
+ async resubscribeAll() {
2488
+ if (this.subscriptions.size === 0) {
2489
+ return;
2490
+ }
2491
+ this.logger.info("Re-subscribing to channels", {
2492
+ count: this.subscriptions.size
2493
+ });
2494
+ for (const [key, options] of this.subscriptions.entries()) {
2495
+ const channel = this.getChannelFromKey(key);
2496
+ try {
2497
+ await this.subscribe(channel, options);
2498
+ } catch (error) {
2499
+ this.logger.error("Failed to re-subscribe", error, { channel, options });
2500
+ }
2501
+ }
2502
+ }
2503
+ /**
2504
+ * Creates a unique subscription key.
2505
+ * @internal
2506
+ */
2507
+ getSubscriptionKey(channel, options) {
2508
+ return `${channel}:${options.marketSlug || "global"}`;
2509
+ }
2510
+ /**
2511
+ * Extracts channel from subscription key.
2512
+ * @internal
2513
+ */
2514
+ getChannelFromKey(key) {
2515
+ return key.split(":")[0];
2516
+ }
2517
+ };
2518
+ // Annotate the CommonJS export names for ESM import in node:
2519
+ 0 && (module.exports = {
2520
+ APIError,
2521
+ AuthenticatedClient,
2522
+ Authenticator,
2523
+ BASE_SEPOLIA_CHAIN_ID,
2524
+ CONTRACT_ADDRESSES,
2525
+ ConsoleLogger,
2526
+ DEFAULT_API_URL,
2527
+ DEFAULT_CHAIN_ID,
2528
+ DEFAULT_WS_URL,
2529
+ HttpClient,
2530
+ MarketFetcher,
2531
+ MarketType,
2532
+ MessageSigner,
2533
+ NoOpLogger,
2534
+ OrderBuilder,
2535
+ OrderClient,
2536
+ OrderSigner,
2537
+ OrderType,
2538
+ PortfolioFetcher,
2539
+ RetryConfig,
2540
+ RetryableClient,
2541
+ SIGNING_MESSAGE_TEMPLATE,
2542
+ Side,
2543
+ SignatureType,
2544
+ ValidationError,
2545
+ WebSocketClient,
2546
+ WebSocketState,
2547
+ ZERO_ADDRESS,
2548
+ getContractAddress,
2549
+ retryOnErrors,
2550
+ validateOrderArgs,
2551
+ validateSignedOrder,
2552
+ validateUnsignedOrder,
2553
+ withRetry
2554
+ });
2555
+ //# sourceMappingURL=index.js.map