@limitless-exchange/sdk 0.0.3 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,8 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  APIError: () => APIError,
34
- AuthenticatedClient: () => AuthenticatedClient,
35
- Authenticator: () => Authenticator,
34
+ AuthenticationError: () => AuthenticationError,
36
35
  BASE_SEPOLIA_CHAIN_ID: () => BASE_SEPOLIA_CHAIN_ID,
37
36
  CONTRACT_ADDRESSES: () => CONTRACT_ADDRESSES,
38
37
  ConsoleLogger: () => ConsoleLogger,
@@ -40,14 +39,16 @@ __export(index_exports, {
40
39
  DEFAULT_CHAIN_ID: () => DEFAULT_CHAIN_ID,
41
40
  DEFAULT_WS_URL: () => DEFAULT_WS_URL,
42
41
  HttpClient: () => HttpClient,
42
+ Market: () => Market,
43
43
  MarketFetcher: () => MarketFetcher,
44
- MessageSigner: () => MessageSigner,
45
44
  NoOpLogger: () => NoOpLogger,
46
45
  OrderBuilder: () => OrderBuilder,
47
46
  OrderClient: () => OrderClient,
48
47
  OrderSigner: () => OrderSigner,
49
48
  OrderType: () => OrderType,
49
+ OrderValidationError: () => OrderValidationError,
50
50
  PortfolioFetcher: () => PortfolioFetcher,
51
+ RateLimitError: () => RateLimitError,
51
52
  RetryConfig: () => RetryConfig,
52
53
  RetryableClient: () => RetryableClient,
53
54
  SIGNING_MESSAGE_TEMPLATE: () => SIGNING_MESSAGE_TEMPLATE,
@@ -136,250 +137,92 @@ var WebSocketState = /* @__PURE__ */ ((WebSocketState2) => {
136
137
  return WebSocketState2;
137
138
  })(WebSocketState || {});
138
139
 
139
- // src/auth/signer.ts
140
- var import_ethers = require("ethers");
141
- var MessageSigner = class {
142
- /**
143
- * Creates a new message signer instance.
144
- *
145
- * @param wallet - Ethers wallet instance for signing
146
- */
147
- constructor(wallet) {
148
- this.wallet = wallet;
149
- }
150
- /**
151
- * Creates authentication headers for API requests.
152
- *
153
- * @param signingMessage - Message to sign from the API
154
- * @returns Promise resolving to signature headers
155
- *
156
- * @example
157
- * ```typescript
158
- * const signer = new MessageSigner(wallet);
159
- * const headers = await signer.createAuthHeaders(message);
160
- * ```
161
- */
162
- async createAuthHeaders(signingMessage) {
163
- const hexMessage = this.stringToHex(signingMessage);
164
- const signature = await this.wallet.signMessage(signingMessage);
165
- const address = this.wallet.address;
166
- const recoveredAddress = import_ethers.ethers.verifyMessage(signingMessage, signature);
167
- if (address.toLowerCase() !== recoveredAddress.toLowerCase()) {
168
- throw new Error("Signature verification failed: address mismatch");
169
- }
170
- return {
171
- "x-account": address,
172
- "x-signing-message": hexMessage,
173
- "x-signature": signature
174
- };
175
- }
176
- /**
177
- * Signs EIP-712 typed data.
178
- *
179
- * @param domain - EIP-712 domain
180
- * @param types - EIP-712 types
181
- * @param value - Value to sign
182
- * @returns Promise resolving to signature string
183
- *
184
- * @example
185
- * ```typescript
186
- * const signature = await signer.signTypedData(domain, types, order);
187
- * ```
188
- */
189
- async signTypedData(domain, types, value) {
190
- return await this.wallet.signTypedData(domain, types, value);
191
- }
192
- /**
193
- * Gets the wallet address.
194
- *
195
- * @returns Ethereum address
196
- */
197
- getAddress() {
198
- return this.wallet.address;
199
- }
200
- /**
201
- * Converts a string to hex format.
202
- *
203
- * @param text - String to convert
204
- * @returns Hex string with 0x prefix
205
- * @internal
206
- */
207
- stringToHex(text) {
208
- return import_ethers.ethers.hexlify(import_ethers.ethers.toUtf8Bytes(text));
209
- }
210
- };
211
-
212
- // src/auth/authenticator.ts
213
- var Authenticator = class {
140
+ // src/types/market-class.ts
141
+ var Market = class _Market {
214
142
  /**
215
- * Creates a new authenticator instance.
216
- *
217
- * @param httpClient - HTTP client for API requests
218
- * @param signer - Message signer for wallet operations
219
- * @param logger - Optional logger for debugging and monitoring (default: no logging)
143
+ * Creates a Market instance.
220
144
  *
221
- * @example
222
- * ```typescript
223
- * // Without logging (default)
224
- * const authenticator = new Authenticator(httpClient, signer);
225
- *
226
- * // With logging
227
- * import { ConsoleLogger } from '@limitless-exchange/sdk';
228
- * const logger = new ConsoleLogger('debug');
229
- * const authenticator = new Authenticator(httpClient, signer, logger);
230
- * ```
145
+ * @param data - Market data from API
146
+ * @param httpClient - HTTP client for making requests
231
147
  */
232
- constructor(httpClient, signer, logger) {
148
+ constructor(data, httpClient) {
149
+ Object.assign(this, data);
233
150
  this.httpClient = httpClient;
234
- this.signer = signer;
235
- this.logger = logger || new NoOpLogger();
151
+ if (data.markets && Array.isArray(data.markets)) {
152
+ this.markets = data.markets.map((m) => new _Market(m, httpClient));
153
+ }
236
154
  }
237
155
  /**
238
- * Gets a signing message from the API.
239
- *
240
- * @returns Promise resolving to signing message string
241
- * @throws Error if API request fails
156
+ * Get user's orders for this market.
242
157
  *
243
- * @example
244
- * ```typescript
245
- * const message = await authenticator.getSigningMessage();
246
- * console.log(message);
247
- * ```
248
- */
249
- async getSigningMessage() {
250
- this.logger.debug("Requesting signing message from API");
251
- const message = await this.httpClient.get("/auth/signing-message");
252
- this.logger.debug("Received signing message", { length: message.length });
253
- return message;
254
- }
255
- /**
256
- * Authenticates with the API and obtains session cookie.
158
+ * @remarks
159
+ * Fetches all orders placed by the authenticated user for this specific market.
160
+ * Uses the http_client from the MarketFetcher that created this Market instance.
257
161
  *
258
- * @param options - Login options including client type and smart wallet
259
- * @returns Promise resolving to authentication result
260
- * @throws Error if authentication fails or smart wallet is required but not provided
162
+ * @returns Promise resolving to array of user orders
163
+ * @throws Error if market wasn't fetched via MarketFetcher
261
164
  *
262
165
  * @example
263
166
  * ```typescript
264
- * // EOA authentication
265
- * const result = await authenticator.authenticate({ client: 'eoa' });
266
- *
267
- * // ETHERSPOT with smart wallet
268
- * const result = await authenticator.authenticate({
269
- * client: 'etherspot',
270
- * smartWallet: '0x...'
271
- * });
167
+ * // Clean fluent API
168
+ * const market = await marketFetcher.getMarket("bitcoin-2024");
169
+ * const orders = await market.getUserOrders();
170
+ * console.log(`You have ${orders.length} orders in ${market.title}`);
272
171
  * ```
273
172
  */
274
- async authenticate(options = {}) {
275
- const client = options.client || "eoa";
276
- this.logger.info("Starting authentication", {
277
- client,
278
- hasSmartWallet: !!options.smartWallet
279
- });
280
- if (client === "etherspot" && !options.smartWallet) {
281
- this.logger.error("Smart wallet address required for ETHERSPOT client");
282
- throw new Error("Smart wallet address is required for ETHERSPOT client");
283
- }
284
- try {
285
- const signingMessage = await this.getSigningMessage();
286
- this.logger.debug("Creating signature headers");
287
- const headers = await this.signer.createAuthHeaders(signingMessage);
288
- this.logger.debug("Sending authentication request", { client });
289
- const response = await this.httpClient.postWithResponse(
290
- "/auth/login",
291
- { client, smartWallet: options.smartWallet },
292
- {
293
- headers,
294
- validateStatus: (status) => status < 500
295
- }
173
+ async getUserOrders() {
174
+ if (!this.httpClient) {
175
+ throw new Error(
176
+ "This Market instance has no httpClient attached. Make sure to fetch the market via MarketFetcher.getMarket() to use this method."
296
177
  );
297
- this.logger.debug("Extracting session cookie from response");
298
- const cookies = this.httpClient.extractCookies(response);
299
- const sessionCookie = cookies["limitless_session"];
300
- if (!sessionCookie) {
301
- this.logger.error("Session cookie not found in response headers");
302
- throw new Error("Failed to obtain session cookie from response");
303
- }
304
- this.httpClient.setSessionCookie(sessionCookie);
305
- this.logger.info("Authentication successful", {
306
- account: response.data.account,
307
- client: response.data.client
308
- });
309
- return {
310
- sessionCookie,
311
- profile: response.data
312
- };
313
- } catch (error) {
314
- this.logger.error("Authentication failed", error, {
315
- client
316
- });
317
- throw error;
318
- }
319
- }
320
- /**
321
- * Verifies the current authentication status.
322
- *
323
- * @param sessionCookie - Session cookie to verify
324
- * @returns Promise resolving to user's Ethereum address
325
- * @throws Error if session is invalid
326
- *
327
- * @example
328
- * ```typescript
329
- * const address = await authenticator.verifyAuth(sessionCookie);
330
- * console.log(`Authenticated as: ${address}`);
331
- * ```
332
- */
333
- async verifyAuth(sessionCookie) {
334
- this.logger.debug("Verifying authentication session");
335
- const originalCookie = this.httpClient["sessionCookie"];
336
- this.httpClient.setSessionCookie(sessionCookie);
337
- try {
338
- const address = await this.httpClient.get("/auth/verify-auth");
339
- this.logger.info("Session verified", { address });
340
- return address;
341
- } catch (error) {
342
- this.logger.error("Session verification failed", error);
343
- throw error;
344
- } finally {
345
- if (originalCookie) {
346
- this.httpClient.setSessionCookie(originalCookie);
347
- } else {
348
- this.httpClient.clearSessionCookie();
349
- }
350
178
  }
179
+ const response = await this.httpClient.get(`/markets/${this.slug}/user-orders`);
180
+ const orders = Array.isArray(response) ? response : response.orders || [];
181
+ return orders;
351
182
  }
352
- /**
353
- * Logs out and clears the session.
354
- *
355
- * @param sessionCookie - Session cookie to invalidate
356
- * @throws Error if logout request fails
357
- *
358
- * @example
359
- * ```typescript
360
- * await authenticator.logout(sessionCookie);
361
- * console.log('Logged out successfully');
362
- * ```
363
- */
364
- async logout(sessionCookie) {
365
- this.logger.debug("Logging out session");
366
- const originalCookie = this.httpClient["sessionCookie"];
367
- this.httpClient.setSessionCookie(sessionCookie);
368
- try {
369
- await this.httpClient.post("/auth/logout", {});
370
- this.logger.info("Logout successful");
371
- } catch (error) {
372
- this.logger.error("Logout failed", error);
373
- throw error;
374
- } finally {
375
- if (originalCookie) {
376
- this.httpClient.setSessionCookie(originalCookie);
377
- } else {
378
- this.httpClient.clearSessionCookie();
379
- }
380
- }
183
+ };
184
+
185
+ // src/api/http.ts
186
+ var import_axios = __toESM(require("axios"));
187
+ var import_http = __toESM(require("http"));
188
+ var import_https = __toESM(require("https"));
189
+
190
+ // src/utils/constants.ts
191
+ var DEFAULT_API_URL = "https://api.limitless.exchange";
192
+ var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
193
+ var DEFAULT_CHAIN_ID = 8453;
194
+ var BASE_SEPOLIA_CHAIN_ID = 84532;
195
+ var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
196
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
197
+ var CONTRACT_ADDRESSES = {
198
+ // Base mainnet (chainId: 8453)
199
+ 8453: {
200
+ USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
201
+ // Native USDC on Base
202
+ CTF: "0xC9c98965297Bc527861c898329Ee280632B76e18"
203
+ // Conditional Token Framework
204
+ },
205
+ // Base Sepolia testnet (chainId: 84532)
206
+ 84532: {
207
+ USDC: "0x...",
208
+ CTF: "0x..."
381
209
  }
382
210
  };
211
+ function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
212
+ const addresses = CONTRACT_ADDRESSES[chainId];
213
+ if (!addresses) {
214
+ throw new Error(
215
+ `No contract addresses configured for chainId ${chainId}. Supported chains: ${Object.keys(CONTRACT_ADDRESSES).join(", ")}`
216
+ );
217
+ }
218
+ const address = addresses[contractType];
219
+ if (!address || address === "0x...") {
220
+ throw new Error(
221
+ `Contract address for ${contractType} not available on chainId ${chainId}. Please configure the address in constants.ts or use environment variables.`
222
+ );
223
+ }
224
+ return address;
225
+ }
383
226
 
384
227
  // src/api/errors.ts
385
228
  var APIError = class _APIError extends Error {
@@ -425,118 +268,33 @@ var APIError = class _APIError extends Error {
425
268
  return this.status === 401 || this.status === 403;
426
269
  }
427
270
  };
428
-
429
- // src/auth/authenticated-client.ts
430
- var AuthenticatedClient = class {
431
- /**
432
- * Creates a new authenticated client with auto-retry capability.
433
- *
434
- * @param config - Configuration for authenticated client
435
- */
436
- constructor(config) {
437
- this.httpClient = config.httpClient;
438
- this.authenticator = config.authenticator;
439
- this.client = config.client;
440
- this.smartWallet = config.smartWallet;
441
- this.logger = config.logger || new NoOpLogger();
442
- this.maxRetries = config.maxRetries ?? 1;
443
- }
444
- /**
445
- * Executes a function with automatic retry on authentication errors.
446
- *
447
- * @param fn - Function to execute with auth retry
448
- * @returns Promise resolving to the function result
449
- * @throws Error if max retries exceeded or non-auth error occurs
450
- *
451
- * @example
452
- * ```typescript
453
- * // Automatic retry on 401/403
454
- * const positions = await authClient.withRetry(() =>
455
- * portfolioFetcher.getPositions()
456
- * );
457
- *
458
- * // Works with any async operation
459
- * const order = await authClient.withRetry(() =>
460
- * orderClient.createOrder({ ... })
461
- * );
462
- * ```
463
- */
464
- async withRetry(fn) {
465
- let attempts = 0;
466
- while (attempts <= this.maxRetries) {
467
- try {
468
- return await fn();
469
- } catch (error) {
470
- if (error instanceof APIError && error.isAuthError() && attempts < this.maxRetries) {
471
- this.logger.info("Authentication expired, re-authenticating...", {
472
- attempt: attempts + 1,
473
- maxRetries: this.maxRetries
474
- });
475
- await this.reauthenticate();
476
- attempts++;
477
- continue;
478
- }
479
- throw error;
480
- }
271
+ var RateLimitError = class _RateLimitError extends APIError {
272
+ constructor(message = "Rate limit exceeded", status = 429, data = null, url, method) {
273
+ super(message, status, data, url, method);
274
+ this.name = "RateLimitError";
275
+ if (Error.captureStackTrace) {
276
+ Error.captureStackTrace(this, _RateLimitError);
481
277
  }
482
- throw new Error("Unexpected error: exceeded max retries");
483
- }
484
- /**
485
- * Re-authenticates with the API.
486
- *
487
- * @internal
488
- */
489
- async reauthenticate() {
490
- this.logger.debug("Re-authenticating with API");
491
- await this.authenticator.authenticate({
492
- client: this.client,
493
- smartWallet: this.smartWallet
494
- });
495
- this.logger.info("Re-authentication successful");
496
278
  }
497
279
  };
498
-
499
- // src/api/http.ts
500
- var import_axios = __toESM(require("axios"));
501
- var import_http = __toESM(require("http"));
502
- var import_https = __toESM(require("https"));
503
-
504
- // src/utils/constants.ts
505
- var DEFAULT_API_URL = "https://api.limitless.exchange";
506
- var DEFAULT_WS_URL = "wss://ws.limitless.exchange";
507
- var DEFAULT_CHAIN_ID = 8453;
508
- var BASE_SEPOLIA_CHAIN_ID = 84532;
509
- var SIGNING_MESSAGE_TEMPLATE = "Welcome to Limitless.exchange! Please sign this message to verify your identity.\n\nNonce: {NONCE}";
510
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
511
- var CONTRACT_ADDRESSES = {
512
- // Base mainnet (chainId: 8453)
513
- 8453: {
514
- USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
515
- // Native USDC on Base
516
- CTF: "0xC9c98965297Bc527861c898329Ee280632B76e18"
517
- // Conditional Token Framework
518
- },
519
- // Base Sepolia testnet (chainId: 84532)
520
- 84532: {
521
- USDC: "0x...",
522
- CTF: "0x..."
280
+ var AuthenticationError = class _AuthenticationError extends APIError {
281
+ constructor(message = "Authentication failed", status = 401, data = null, url, method) {
282
+ super(message, status, data, url, method);
283
+ this.name = "AuthenticationError";
284
+ if (Error.captureStackTrace) {
285
+ Error.captureStackTrace(this, _AuthenticationError);
286
+ }
523
287
  }
524
288
  };
525
- function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
526
- const addresses = CONTRACT_ADDRESSES[chainId];
527
- if (!addresses) {
528
- throw new Error(
529
- `No contract addresses configured for chainId ${chainId}. Supported chains: ${Object.keys(CONTRACT_ADDRESSES).join(", ")}`
530
- );
531
- }
532
- const address = addresses[contractType];
533
- if (!address || address === "0x...") {
534
- throw new Error(
535
- `Contract address for ${contractType} not available on chainId ${chainId}. Please configure the address in constants.ts or use environment variables.`
536
- );
289
+ var ValidationError = class _ValidationError extends APIError {
290
+ constructor(message, status = 400, data = null, url, method) {
291
+ super(message, status, data, url, method);
292
+ this.name = "ValidationError";
293
+ if (Error.captureStackTrace) {
294
+ Error.captureStackTrace(this, _ValidationError);
295
+ }
537
296
  }
538
- return address;
539
- }
297
+ };
540
298
 
541
299
  // src/api/http.ts
542
300
  var HttpClient = class {
@@ -546,8 +304,13 @@ var HttpClient = class {
546
304
  * @param config - Configuration options for the HTTP client
547
305
  */
548
306
  constructor(config = {}) {
549
- this.sessionCookie = config.sessionCookie;
307
+ this.apiKey = config.apiKey || process.env.LIMITLESS_API_KEY;
550
308
  this.logger = config.logger || new NoOpLogger();
309
+ if (!this.apiKey) {
310
+ this.logger.warn(
311
+ "API key not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable or pass apiKey parameter."
312
+ );
313
+ }
551
314
  const keepAlive = config.keepAlive !== false;
552
315
  const maxSockets = config.maxSockets || 50;
553
316
  const maxFreeSockets = config.maxFreeSockets || 10;
@@ -590,13 +353,17 @@ var HttpClient = class {
590
353
  setupInterceptors() {
591
354
  this.client.interceptors.request.use(
592
355
  (config) => {
593
- if (this.sessionCookie) {
594
- config.headers["Cookie"] = `limitless_session=${this.sessionCookie}`;
356
+ if (this.apiKey) {
357
+ config.headers["X-API-Key"] = this.apiKey;
595
358
  }
596
359
  const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
597
360
  const method = config.method?.toUpperCase() || "GET";
361
+ const logHeaders = { ...config.headers };
362
+ if (logHeaders["X-API-Key"]) {
363
+ logHeaders["X-API-Key"] = "***";
364
+ }
598
365
  this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
599
- headers: config.headers,
366
+ headers: logHeaders,
600
367
  body: config.data
601
368
  });
602
369
  return config;
@@ -637,7 +404,15 @@ var HttpClient = class {
637
404
  message = String(data);
638
405
  }
639
406
  }
640
- throw new APIError(message, status, data, url, method);
407
+ if (status === 429) {
408
+ throw new RateLimitError(message, status, data, url, method);
409
+ } else if (status === 401 || status === 403) {
410
+ throw new AuthenticationError(message, status, data, url, method);
411
+ } else if (status === 400) {
412
+ throw new ValidationError(message, status, data, url, method);
413
+ } else {
414
+ throw new APIError(message, status, data, url, method);
415
+ }
641
416
  } else if (error.request) {
642
417
  throw new Error("No response received from API");
643
418
  } else {
@@ -647,18 +422,18 @@ var HttpClient = class {
647
422
  );
648
423
  }
649
424
  /**
650
- * Sets the session cookie for authenticated requests.
425
+ * Sets the API key for authenticated requests.
651
426
  *
652
- * @param cookie - Session cookie value
427
+ * @param apiKey - API key value
653
428
  */
654
- setSessionCookie(cookie) {
655
- this.sessionCookie = cookie;
429
+ setApiKey(apiKey) {
430
+ this.apiKey = apiKey;
656
431
  }
657
432
  /**
658
- * Clears the session cookie.
433
+ * Clears the API key.
659
434
  */
660
- clearSessionCookie() {
661
- this.sessionCookie = void 0;
435
+ clearApiKey() {
436
+ this.apiKey = void 0;
662
437
  }
663
438
  /**
664
439
  * Performs a GET request.
@@ -683,19 +458,6 @@ var HttpClient = class {
683
458
  const response = await this.client.post(url, data, config);
684
459
  return response.data;
685
460
  }
686
- /**
687
- * Performs a POST request and returns the full response object.
688
- * Useful when you need access to response headers (e.g., for cookie extraction).
689
- *
690
- * @param url - Request URL
691
- * @param data - Request body data
692
- * @param config - Additional request configuration
693
- * @returns Promise resolving to the full AxiosResponse object
694
- * @internal
695
- */
696
- async postWithResponse(url, data, config) {
697
- return await this.client.post(url, data, config);
698
- }
699
461
  /**
700
462
  * Performs a DELETE request.
701
463
  *
@@ -718,26 +480,6 @@ var HttpClient = class {
718
480
  const response = await this.client.delete(url, deleteConfig);
719
481
  return response.data;
720
482
  }
721
- /**
722
- * Extracts cookies from response headers.
723
- *
724
- * @param response - Axios response object
725
- * @returns Object containing parsed cookies
726
- * @internal
727
- */
728
- extractCookies(response) {
729
- const setCookie = response.headers["set-cookie"];
730
- if (!setCookie) return {};
731
- const cookies = {};
732
- const cookieStrings = Array.isArray(setCookie) ? setCookie : [setCookie];
733
- for (const cookieString of cookieStrings) {
734
- const parts = cookieString.split(";")[0].split("=");
735
- if (parts.length === 2) {
736
- cookies[parts[0].trim()] = parts[1].trim();
737
- }
738
- }
739
- return cookies;
740
- }
741
483
  };
742
484
 
743
485
  // src/api/retry.ts
@@ -922,7 +664,7 @@ var RetryableClient = class {
922
664
  };
923
665
 
924
666
  // src/orders/builder.ts
925
- var import_ethers2 = require("ethers");
667
+ var import_ethers = require("ethers");
926
668
  var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
927
669
  var DEFAULT_PRICE_TICK = 1e-3;
928
670
  var OrderBuilder = class {
@@ -1004,7 +746,7 @@ var OrderBuilder = class {
1004
746
  * @internal
1005
747
  */
1006
748
  isFOKOrder(args) {
1007
- return "amount" in args;
749
+ return "makerAmount" in args;
1008
750
  }
1009
751
  /**
1010
752
  * Generates a unique salt using timestamp + nano-offset pattern.
@@ -1158,7 +900,7 @@ var OrderBuilder = class {
1158
900
  }
1159
901
  }
1160
902
  const amountFormatted = makerAmount.toFixed(DECIMALS);
1161
- const amountScaled = import_ethers2.ethers.parseUnits(amountFormatted, DECIMALS);
903
+ const amountScaled = import_ethers.ethers.parseUnits(amountFormatted, DECIMALS);
1162
904
  const collateralAmount = Number(amountScaled);
1163
905
  return {
1164
906
  makerAmount: collateralAmount,
@@ -1178,7 +920,7 @@ var OrderBuilder = class {
1178
920
  if (!args.tokenId || args.tokenId === "0") {
1179
921
  throw new Error("Invalid tokenId: tokenId is required.");
1180
922
  }
1181
- if (args.taker && !import_ethers2.ethers.isAddress(args.taker)) {
923
+ if (args.taker && !import_ethers.ethers.isAddress(args.taker)) {
1182
924
  throw new Error(`Invalid taker address: ${args.taker}`);
1183
925
  }
1184
926
  if (this.isFOKOrder(args)) {
@@ -1189,7 +931,7 @@ var OrderBuilder = class {
1189
931
  throw new Error(`Invalid makerAmount: ${args.makerAmount}. Maker amount must be positive.`);
1190
932
  }
1191
933
  } else {
1192
- if (args.price < 0 || args.price > 1) {
934
+ if (args.price == null || args.price < 0 || args.price > 1) {
1193
935
  throw new Error(`Invalid price: ${args.price}. Price must be between 0 and 1.`);
1194
936
  }
1195
937
  if (args.size <= 0) {
@@ -1278,11 +1020,11 @@ var OrderSigner = class {
1278
1020
  signatureType: order.signatureType
1279
1021
  };
1280
1022
  this.logger.debug("EIP-712 Order Value", orderValue);
1281
- console.log("[OrderSigner] Full signing payload:", JSON.stringify({
1023
+ this.logger.debug("Full signing payload", {
1282
1024
  domain,
1283
1025
  types: this.getTypes(),
1284
1026
  value: orderValue
1285
- }, null, 2));
1027
+ });
1286
1028
  try {
1287
1029
  const signature = await this.wallet.signTypedData(domain, types, orderValue);
1288
1030
  this.logger.info("Successfully generated EIP-712 signature", {
@@ -1342,11 +1084,11 @@ var OrderSigner = class {
1342
1084
  };
1343
1085
 
1344
1086
  // src/orders/validator.ts
1345
- var import_ethers3 = require("ethers");
1346
- var ValidationError = class extends Error {
1087
+ var import_ethers2 = require("ethers");
1088
+ var OrderValidationError = class extends Error {
1347
1089
  constructor(message) {
1348
1090
  super(message);
1349
- this.name = "ValidationError";
1091
+ this.name = "OrderValidationError";
1350
1092
  }
1351
1093
  };
1352
1094
  function isFOKOrder(args) {
@@ -1354,126 +1096,126 @@ function isFOKOrder(args) {
1354
1096
  }
1355
1097
  function validateOrderArgs(args) {
1356
1098
  if (!args.tokenId) {
1357
- throw new ValidationError("TokenId is required");
1099
+ throw new OrderValidationError("TokenId is required");
1358
1100
  }
1359
1101
  if (args.tokenId === "0") {
1360
- throw new ValidationError("TokenId cannot be zero");
1102
+ throw new OrderValidationError("TokenId cannot be zero");
1361
1103
  }
1362
1104
  if (!/^\d+$/.test(args.tokenId)) {
1363
- throw new ValidationError(`Invalid tokenId format: ${args.tokenId}`);
1105
+ throw new OrderValidationError(`Invalid tokenId format: ${args.tokenId}`);
1364
1106
  }
1365
- if (args.taker && !import_ethers3.ethers.isAddress(args.taker)) {
1366
- throw new ValidationError(`Invalid taker address: ${args.taker}`);
1107
+ if (args.taker && !import_ethers2.ethers.isAddress(args.taker)) {
1108
+ throw new OrderValidationError(`Invalid taker address: ${args.taker}`);
1367
1109
  }
1368
1110
  if (args.expiration !== void 0) {
1369
1111
  if (!/^\d+$/.test(args.expiration)) {
1370
- throw new ValidationError(`Invalid expiration format: ${args.expiration}`);
1112
+ throw new OrderValidationError(`Invalid expiration format: ${args.expiration}`);
1371
1113
  }
1372
1114
  }
1373
1115
  if (args.nonce !== void 0) {
1374
1116
  if (!Number.isInteger(args.nonce) || args.nonce < 0) {
1375
- throw new ValidationError(`Invalid nonce: ${args.nonce}`);
1117
+ throw new OrderValidationError(`Invalid nonce: ${args.nonce}`);
1376
1118
  }
1377
1119
  }
1378
1120
  if (isFOKOrder(args)) {
1379
1121
  if (typeof args.makerAmount !== "number" || isNaN(args.makerAmount)) {
1380
- throw new ValidationError("Amount must be a valid number");
1122
+ throw new OrderValidationError("Amount must be a valid number");
1381
1123
  }
1382
1124
  if (args.makerAmount <= 0) {
1383
- throw new ValidationError(`Amount must be positive, got: ${args.makerAmount}`);
1125
+ throw new OrderValidationError(`Amount must be positive, got: ${args.makerAmount}`);
1384
1126
  }
1385
1127
  const amountStr = args.makerAmount.toString();
1386
1128
  const decimalIndex = amountStr.indexOf(".");
1387
1129
  if (decimalIndex !== -1) {
1388
1130
  const decimalPlaces = amountStr.length - decimalIndex - 1;
1389
1131
  if (decimalPlaces > 2) {
1390
- throw new ValidationError(
1132
+ throw new OrderValidationError(
1391
1133
  `Amount must have max 2 decimal places, got: ${args.makerAmount} (${decimalPlaces} decimals)`
1392
1134
  );
1393
1135
  }
1394
1136
  }
1395
1137
  } else {
1396
1138
  if (typeof args.price !== "number" || isNaN(args.price)) {
1397
- throw new ValidationError("Price must be a valid number");
1139
+ throw new OrderValidationError("Price must be a valid number");
1398
1140
  }
1399
1141
  if (args.price < 0 || args.price > 1) {
1400
- throw new ValidationError(`Price must be between 0 and 1, got: ${args.price}`);
1142
+ throw new OrderValidationError(`Price must be between 0 and 1, got: ${args.price}`);
1401
1143
  }
1402
1144
  if (typeof args.size !== "number" || isNaN(args.size)) {
1403
- throw new ValidationError("Size must be a valid number");
1145
+ throw new OrderValidationError("Size must be a valid number");
1404
1146
  }
1405
1147
  if (args.size <= 0) {
1406
- throw new ValidationError(`Size must be positive, got: ${args.size}`);
1148
+ throw new OrderValidationError(`Size must be positive, got: ${args.size}`);
1407
1149
  }
1408
1150
  }
1409
1151
  }
1410
1152
  function validateUnsignedOrder(order) {
1411
- if (!import_ethers3.ethers.isAddress(order.maker)) {
1412
- throw new ValidationError(`Invalid maker address: ${order.maker}`);
1153
+ if (!import_ethers2.ethers.isAddress(order.maker)) {
1154
+ throw new OrderValidationError(`Invalid maker address: ${order.maker}`);
1413
1155
  }
1414
- if (!import_ethers3.ethers.isAddress(order.signer)) {
1415
- throw new ValidationError(`Invalid signer address: ${order.signer}`);
1156
+ if (!import_ethers2.ethers.isAddress(order.signer)) {
1157
+ throw new OrderValidationError(`Invalid signer address: ${order.signer}`);
1416
1158
  }
1417
- if (!import_ethers3.ethers.isAddress(order.taker)) {
1418
- throw new ValidationError(`Invalid taker address: ${order.taker}`);
1159
+ if (!import_ethers2.ethers.isAddress(order.taker)) {
1160
+ throw new OrderValidationError(`Invalid taker address: ${order.taker}`);
1419
1161
  }
1420
1162
  if (!order.makerAmount || order.makerAmount === 0) {
1421
- throw new ValidationError("MakerAmount must be greater than zero");
1163
+ throw new OrderValidationError("MakerAmount must be greater than zero");
1422
1164
  }
1423
1165
  if (!order.takerAmount || order.takerAmount === 0) {
1424
- throw new ValidationError("TakerAmount must be greater than zero");
1166
+ throw new OrderValidationError("TakerAmount must be greater than zero");
1425
1167
  }
1426
1168
  if (typeof order.makerAmount !== "number" || order.makerAmount <= 0) {
1427
- throw new ValidationError(`Invalid makerAmount: ${order.makerAmount}`);
1169
+ throw new OrderValidationError(`Invalid makerAmount: ${order.makerAmount}`);
1428
1170
  }
1429
1171
  if (typeof order.takerAmount !== "number" || order.takerAmount <= 0) {
1430
- throw new ValidationError(`Invalid takerAmount: ${order.takerAmount}`);
1172
+ throw new OrderValidationError(`Invalid takerAmount: ${order.takerAmount}`);
1431
1173
  }
1432
1174
  if (!/^\d+$/.test(order.tokenId)) {
1433
- throw new ValidationError(`Invalid tokenId format: ${order.tokenId}`);
1175
+ throw new OrderValidationError(`Invalid tokenId format: ${order.tokenId}`);
1434
1176
  }
1435
1177
  if (!/^\d+$/.test(order.expiration)) {
1436
- throw new ValidationError(`Invalid expiration format: ${order.expiration}`);
1178
+ throw new OrderValidationError(`Invalid expiration format: ${order.expiration}`);
1437
1179
  }
1438
1180
  if (!Number.isInteger(order.salt) || order.salt <= 0) {
1439
- throw new ValidationError(`Invalid salt: ${order.salt}`);
1181
+ throw new OrderValidationError(`Invalid salt: ${order.salt}`);
1440
1182
  }
1441
1183
  if (!Number.isInteger(order.nonce) || order.nonce < 0) {
1442
- throw new ValidationError(`Invalid nonce: ${order.nonce}`);
1184
+ throw new OrderValidationError(`Invalid nonce: ${order.nonce}`);
1443
1185
  }
1444
1186
  if (!Number.isInteger(order.feeRateBps) || order.feeRateBps < 0) {
1445
- throw new ValidationError(`Invalid feeRateBps: ${order.feeRateBps}`);
1187
+ throw new OrderValidationError(`Invalid feeRateBps: ${order.feeRateBps}`);
1446
1188
  }
1447
1189
  if (order.side !== 0 && order.side !== 1) {
1448
- throw new ValidationError(`Invalid side: ${order.side}. Must be 0 (BUY) or 1 (SELL)`);
1190
+ throw new OrderValidationError(`Invalid side: ${order.side}. Must be 0 (BUY) or 1 (SELL)`);
1449
1191
  }
1450
1192
  if (!Number.isInteger(order.signatureType) || order.signatureType < 0) {
1451
- throw new ValidationError(`Invalid signatureType: ${order.signatureType}`);
1193
+ throw new OrderValidationError(`Invalid signatureType: ${order.signatureType}`);
1452
1194
  }
1453
1195
  if (order.price !== void 0) {
1454
1196
  if (typeof order.price !== "number" || isNaN(order.price)) {
1455
- throw new ValidationError("Price must be a valid number");
1197
+ throw new OrderValidationError("Price must be a valid number");
1456
1198
  }
1457
1199
  if (order.price < 0 || order.price > 1) {
1458
- throw new ValidationError(`Price must be between 0 and 1, got: ${order.price}`);
1200
+ throw new OrderValidationError(`Price must be between 0 and 1, got: ${order.price}`);
1459
1201
  }
1460
1202
  }
1461
1203
  }
1462
1204
  function validateSignedOrder(order) {
1463
1205
  validateUnsignedOrder(order);
1464
1206
  if (!order.signature) {
1465
- throw new ValidationError("Signature is required");
1207
+ throw new OrderValidationError("Signature is required");
1466
1208
  }
1467
1209
  if (!order.signature.startsWith("0x")) {
1468
- throw new ValidationError("Signature must start with 0x");
1210
+ throw new OrderValidationError("Signature must start with 0x");
1469
1211
  }
1470
1212
  if (order.signature.length !== 132) {
1471
- throw new ValidationError(
1213
+ throw new OrderValidationError(
1472
1214
  `Invalid signature length: ${order.signature.length}. Expected 132 characters.`
1473
1215
  );
1474
1216
  }
1475
1217
  if (!/^0x[0-9a-fA-F]{130}$/.test(order.signature)) {
1476
- throw new ValidationError("Signature must be valid hex string");
1218
+ throw new OrderValidationError("Signature must be valid hex string");
1477
1219
  }
1478
1220
  }
1479
1221
 
@@ -1535,13 +1277,18 @@ var MarketFetcher = class {
1535
1277
  this.logger.debug("Fetching active markets", { params });
1536
1278
  try {
1537
1279
  const response = await this.httpClient.get(endpoint);
1280
+ const markets = response.data.map((marketData) => new Market(marketData, this.httpClient));
1281
+ const result = {
1282
+ data: markets,
1283
+ totalMarketsCount: response.totalMarketsCount
1284
+ };
1538
1285
  this.logger.info("Active markets fetched successfully", {
1539
- count: response.data.length,
1286
+ count: markets.length,
1540
1287
  total: response.totalMarketsCount,
1541
1288
  sortBy: params?.sortBy,
1542
1289
  page: params?.page
1543
1290
  });
1544
- return response;
1291
+ return result;
1545
1292
  } catch (error) {
1546
1293
  this.logger.error("Failed to fetch active markets", error, { params });
1547
1294
  throw error;
@@ -1561,9 +1308,14 @@ var MarketFetcher = class {
1561
1308
  *
1562
1309
  * @example
1563
1310
  * ```typescript
1311
+ * // Get market
1564
1312
  * const market = await fetcher.getMarket('bitcoin-price-2024');
1565
1313
  * console.log(`Market: ${market.title}`);
1566
1314
  *
1315
+ * // Fluent API - get user orders for this market (clean!)
1316
+ * const orders = await market.getUserOrders();
1317
+ * console.log(`You have ${orders.length} orders`);
1318
+ *
1567
1319
  * // Venue is now cached for order signing
1568
1320
  * await orderClient.createOrder({
1569
1321
  * marketSlug: 'bitcoin-price-2024',
@@ -1574,7 +1326,8 @@ var MarketFetcher = class {
1574
1326
  async getMarket(slug) {
1575
1327
  this.logger.debug("Fetching market", { slug });
1576
1328
  try {
1577
- const market = await this.httpClient.get(`/markets/${slug}`);
1329
+ const response = await this.httpClient.get(`/markets/${slug}`);
1330
+ const market = new Market(response, this.httpClient);
1578
1331
  if (market.venue) {
1579
1332
  this.venueCache.set(slug, market.venue);
1580
1333
  this.logger.debug("Venue cached for order signing", {
@@ -1657,30 +1410,164 @@ var MarketFetcher = class {
1657
1410
  throw error;
1658
1411
  }
1659
1412
  }
1413
+ };
1414
+
1415
+ // src/portfolio/fetcher.ts
1416
+ var PortfolioFetcher = class {
1660
1417
  /**
1661
- * Gets the current price for a token.
1418
+ * Creates a new portfolio fetcher instance.
1662
1419
  *
1663
- * @param tokenId - Token ID
1664
- * @returns Promise resolving to price information
1665
- * @throws Error if API request fails
1420
+ * @param httpClient - Authenticated HTTP client for API requests
1421
+ * @param logger - Optional logger for debugging (default: no logging)
1666
1422
  *
1667
1423
  * @example
1668
1424
  * ```typescript
1669
- * const price = await fetcher.getPrice('123456');
1670
- * console.log(`Current price: ${price.price}`);
1425
+ * // Create authenticated client
1426
+ * const httpClient = new HttpClient({ baseURL: API_URL });
1427
+ * await authenticator.authenticate({ client: 'eoa' });
1428
+ *
1429
+ * // Create portfolio fetcher
1430
+ * const portfolioFetcher = new PortfolioFetcher(httpClient);
1671
1431
  * ```
1672
1432
  */
1673
- async getPrice(tokenId) {
1674
- this.logger.debug("Fetching price", { tokenId });
1675
- try {
1676
- const price = await this.httpClient.get(`/prices/${tokenId}`);
1677
- this.logger.info("Price fetched successfully", {
1678
- tokenId,
1679
- price: price.price
1680
- });
1681
- return price;
1433
+ constructor(httpClient, logger) {
1434
+ this.httpClient = httpClient;
1435
+ this.logger = logger || new NoOpLogger();
1436
+ }
1437
+ /**
1438
+ * Gets user profile for a specific wallet address.
1439
+ *
1440
+ * @remarks
1441
+ * Returns user profile data including user ID and fee rate.
1442
+ * Used internally by OrderClient to fetch user data.
1443
+ *
1444
+ * @param address - Wallet address to fetch profile for
1445
+ * @returns Promise resolving to user profile data
1446
+ * @throws Error if API request fails or user is not authenticated
1447
+ *
1448
+ * @example
1449
+ * ```typescript
1450
+ * const profile = await portfolioFetcher.getProfile('0x1234...');
1451
+ * console.log(`User ID: ${profile.id}`);
1452
+ * console.log(`Account: ${profile.account}`);
1453
+ * console.log(`Fee Rate: ${profile.rank?.feeRateBps}`);
1454
+ * ```
1455
+ */
1456
+ async getProfile(address) {
1457
+ this.logger.debug("Fetching user profile", { address });
1458
+ try {
1459
+ const response = await this.httpClient.get(`/profiles/${address}`);
1460
+ this.logger.info("User profile fetched successfully", { address });
1461
+ return response;
1682
1462
  } catch (error) {
1683
- this.logger.error("Failed to fetch price", error, { tokenId });
1463
+ this.logger.error("Failed to fetch user profile", error, { address });
1464
+ throw error;
1465
+ }
1466
+ }
1467
+ /**
1468
+ * Gets raw portfolio positions response from API.
1469
+ *
1470
+ * @returns Promise resolving to portfolio positions response with CLOB and AMM positions
1471
+ * @throws Error if API request fails or user is not authenticated
1472
+ *
1473
+ * @example
1474
+ * ```typescript
1475
+ * const response = await portfolioFetcher.getPositions();
1476
+ * console.log(`CLOB positions: ${response.clob.length}`);
1477
+ * console.log(`AMM positions: ${response.amm.length}`);
1478
+ * console.log(`Total points: ${response.accumulativePoints}`);
1479
+ * ```
1480
+ */
1481
+ async getPositions() {
1482
+ this.logger.debug("Fetching user positions");
1483
+ try {
1484
+ const response = await this.httpClient.get("/portfolio/positions");
1485
+ this.logger.info("Positions fetched successfully", {
1486
+ clobCount: response.clob?.length || 0,
1487
+ ammCount: response.amm?.length || 0
1488
+ });
1489
+ return response;
1490
+ } catch (error) {
1491
+ this.logger.error("Failed to fetch positions", error);
1492
+ throw error;
1493
+ }
1494
+ }
1495
+ /**
1496
+ * Gets CLOB positions only.
1497
+ *
1498
+ * @returns Promise resolving to array of CLOB positions
1499
+ * @throws Error if API request fails
1500
+ *
1501
+ * @example
1502
+ * ```typescript
1503
+ * const clobPositions = await portfolioFetcher.getCLOBPositions();
1504
+ * clobPositions.forEach(pos => {
1505
+ * console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
1506
+ * });
1507
+ * ```
1508
+ */
1509
+ async getCLOBPositions() {
1510
+ const response = await this.getPositions();
1511
+ return response.clob || [];
1512
+ }
1513
+ /**
1514
+ * Gets AMM positions only.
1515
+ *
1516
+ * @returns Promise resolving to array of AMM positions
1517
+ * @throws Error if API request fails
1518
+ *
1519
+ * @example
1520
+ * ```typescript
1521
+ * const ammPositions = await portfolioFetcher.getAMMPositions();
1522
+ * ammPositions.forEach(pos => {
1523
+ * console.log(`${pos.market.title}: ${pos.unrealizedPnl} P&L`);
1524
+ * });
1525
+ * ```
1526
+ */
1527
+ async getAMMPositions() {
1528
+ const response = await this.getPositions();
1529
+ return response.amm || [];
1530
+ }
1531
+ /**
1532
+ * Gets paginated history of user actions.
1533
+ *
1534
+ * Includes AMM trades, CLOB trades, Negrisk trades & conversions.
1535
+ *
1536
+ * @param page - Page number (starts at 1)
1537
+ * @param limit - Number of items per page
1538
+ * @returns Promise resolving to paginated history response
1539
+ * @throws Error if API request fails or user is not authenticated
1540
+ *
1541
+ * @example
1542
+ * ```typescript
1543
+ * // Get first page
1544
+ * const response = await portfolioFetcher.getUserHistory(1, 20);
1545
+ * console.log(`Found ${response.data.length} of ${response.totalCount} entries`);
1546
+ *
1547
+ * // Process history entries
1548
+ * for (const entry of response.data) {
1549
+ * console.log(`Type: ${entry.type}`);
1550
+ * console.log(`Market: ${entry.marketSlug}`);
1551
+ * }
1552
+ *
1553
+ * // Get next page
1554
+ * const page2 = await portfolioFetcher.getUserHistory(2, 20);
1555
+ * ```
1556
+ */
1557
+ async getUserHistory(page = 1, limit = 10) {
1558
+ this.logger.debug("Fetching user history", { page, limit });
1559
+ try {
1560
+ const params = new URLSearchParams({
1561
+ page: page.toString(),
1562
+ limit: limit.toString()
1563
+ });
1564
+ const response = await this.httpClient.get(
1565
+ `/portfolio/history?${params.toString()}`
1566
+ );
1567
+ this.logger.info("User history fetched successfully");
1568
+ return response;
1569
+ } catch (error) {
1570
+ this.logger.error("Failed to fetch user history", error, { page, limit });
1684
1571
  throw error;
1685
1572
  }
1686
1573
  }
@@ -1695,10 +1582,8 @@ var OrderClient = class {
1695
1582
  */
1696
1583
  constructor(config) {
1697
1584
  this.httpClient = config.httpClient;
1585
+ this.wallet = config.wallet;
1698
1586
  this.logger = config.logger || new NoOpLogger();
1699
- this.ownerId = config.userData.userId;
1700
- const feeRateBps = config.userData.feeRateBps;
1701
- this.orderBuilder = new OrderBuilder(config.wallet.address, feeRateBps, 1e-3);
1702
1587
  this.orderSigner = new OrderSigner(config.wallet, this.logger);
1703
1588
  this.marketFetcher = config.marketFetcher || new MarketFetcher(config.httpClient, this.logger);
1704
1589
  if (config.signingConfig) {
@@ -1715,6 +1600,37 @@ var OrderClient = class {
1715
1600
  });
1716
1601
  }
1717
1602
  }
1603
+ /**
1604
+ * Ensures user data is loaded and cached.
1605
+ * Fetches from profile API on first call, then caches for subsequent calls.
1606
+ *
1607
+ * @returns Promise resolving to cached user data
1608
+ * @internal
1609
+ */
1610
+ async ensureUserData() {
1611
+ if (!this.cachedUserData) {
1612
+ this.logger.info("Fetching user profile for order client initialization...", {
1613
+ walletAddress: this.wallet.address
1614
+ });
1615
+ const portfolioFetcher = new PortfolioFetcher(this.httpClient);
1616
+ const profile = await portfolioFetcher.getProfile(this.wallet.address);
1617
+ this.cachedUserData = {
1618
+ userId: profile.id,
1619
+ feeRateBps: profile.rank?.feeRateBps || 300
1620
+ };
1621
+ this.orderBuilder = new OrderBuilder(
1622
+ this.wallet.address,
1623
+ this.cachedUserData.feeRateBps,
1624
+ 1e-3
1625
+ );
1626
+ this.logger.info("Order Client initialized", {
1627
+ walletAddress: profile.account,
1628
+ userId: this.cachedUserData.userId,
1629
+ feeRate: `${this.cachedUserData.feeRateBps / 100}%`
1630
+ });
1631
+ }
1632
+ return this.cachedUserData;
1633
+ }
1718
1634
  /**
1719
1635
  * Creates and submits a new order.
1720
1636
  *
@@ -1751,6 +1667,7 @@ var OrderClient = class {
1751
1667
  * ```
1752
1668
  */
1753
1669
  async createOrder(params) {
1670
+ const userData = await this.ensureUserData();
1754
1671
  this.logger.info("Creating order", {
1755
1672
  side: params.side,
1756
1673
  orderType: params.orderType,
@@ -1785,10 +1702,7 @@ var OrderClient = class {
1785
1702
  makerAmount: unsignedOrder.makerAmount,
1786
1703
  takerAmount: unsignedOrder.takerAmount
1787
1704
  });
1788
- const signature = await this.orderSigner.signOrder(
1789
- unsignedOrder,
1790
- dynamicSigningConfig
1791
- );
1705
+ const signature = await this.orderSigner.signOrder(unsignedOrder, dynamicSigningConfig);
1792
1706
  const payload = {
1793
1707
  order: {
1794
1708
  ...unsignedOrder,
@@ -1796,14 +1710,10 @@ var OrderClient = class {
1796
1710
  },
1797
1711
  orderType: params.orderType,
1798
1712
  marketSlug: params.marketSlug,
1799
- ownerId: this.ownerId
1713
+ ownerId: userData.userId
1800
1714
  };
1801
- this.logger.debug("Submitting order to API");
1802
- console.log("[OrderClient] Full API request payload:", JSON.stringify(payload, null, 2));
1803
- const apiResponse = await this.httpClient.post(
1804
- "/orders",
1805
- payload
1806
- );
1715
+ this.logger.debug("Submitting order to API", payload);
1716
+ const apiResponse = await this.httpClient.post("/orders", payload);
1807
1717
  this.logger.info("Order created successfully", {
1808
1718
  orderId: apiResponse.order.id
1809
1719
  });
@@ -1866,9 +1776,7 @@ var OrderClient = class {
1866
1776
  */
1867
1777
  async cancel(orderId) {
1868
1778
  this.logger.info("Cancelling order", { orderId });
1869
- const response = await this.httpClient.delete(
1870
- `/orders/${orderId}`
1871
- );
1779
+ const response = await this.httpClient.delete(`/orders/${orderId}`);
1872
1780
  this.logger.info("Order cancellation response", {
1873
1781
  orderId,
1874
1782
  message: response.message
@@ -1891,42 +1799,13 @@ var OrderClient = class {
1891
1799
  */
1892
1800
  async cancelAll(marketSlug) {
1893
1801
  this.logger.info("Cancelling all orders for market", { marketSlug });
1894
- const response = await this.httpClient.delete(
1895
- `/orders/all/${marketSlug}`
1896
- );
1802
+ const response = await this.httpClient.delete(`/orders/all/${marketSlug}`);
1897
1803
  this.logger.info("All orders cancellation response", {
1898
1804
  marketSlug,
1899
1805
  message: response.message
1900
1806
  });
1901
1807
  return response;
1902
1808
  }
1903
- /**
1904
- * @deprecated Use `cancel()` instead
1905
- */
1906
- async cancelOrder(orderId) {
1907
- await this.cancel(orderId);
1908
- }
1909
- /**
1910
- * Gets an order by ID.
1911
- *
1912
- * @param orderId - Order ID to fetch
1913
- * @returns Promise resolving to order details
1914
- *
1915
- * @throws Error if order not found
1916
- *
1917
- * @example
1918
- * ```typescript
1919
- * const order = await orderClient.getOrder('order-id-123');
1920
- * console.log(order.order.side);
1921
- * ```
1922
- */
1923
- async getOrder(orderId) {
1924
- this.logger.debug("Fetching order", { orderId });
1925
- const response = await this.httpClient.get(
1926
- `/orders/${orderId}`
1927
- );
1928
- return response;
1929
- }
1930
1809
  /**
1931
1810
  * Builds an unsigned order without submitting.
1932
1811
  *
@@ -1935,11 +1814,11 @@ var OrderClient = class {
1935
1814
  * before signing and submission.
1936
1815
  *
1937
1816
  * @param params - Order parameters
1938
- * @returns Unsigned order
1817
+ * @returns Promise resolving to unsigned order
1939
1818
  *
1940
1819
  * @example
1941
1820
  * ```typescript
1942
- * const unsignedOrder = orderClient.buildUnsignedOrder({
1821
+ * const unsignedOrder = await orderClient.buildUnsignedOrder({
1943
1822
  * tokenId: '123456',
1944
1823
  * price: 0.65,
1945
1824
  * size: 100,
@@ -1947,7 +1826,8 @@ var OrderClient = class {
1947
1826
  * });
1948
1827
  * ```
1949
1828
  */
1950
- buildUnsignedOrder(params) {
1829
+ async buildUnsignedOrder(params) {
1830
+ await this.ensureUserData();
1951
1831
  return this.orderBuilder.buildOrder(params);
1952
1832
  }
1953
1833
  /**
@@ -1968,308 +1848,35 @@ var OrderClient = class {
1968
1848
  async signOrder(order) {
1969
1849
  return await this.orderSigner.signOrder(order, this.signingConfig);
1970
1850
  }
1971
- };
1972
-
1973
- // src/portfolio/fetcher.ts
1974
- var PortfolioFetcher = class {
1975
- /**
1976
- * Creates a new portfolio fetcher instance.
1977
- *
1978
- * @param httpClient - Authenticated HTTP client for API requests
1979
- * @param logger - Optional logger for debugging (default: no logging)
1980
- *
1981
- * @example
1982
- * ```typescript
1983
- * // Create authenticated client
1984
- * const httpClient = new HttpClient({ baseURL: API_URL });
1985
- * await authenticator.authenticate({ client: 'eoa' });
1986
- *
1987
- * // Create portfolio fetcher
1988
- * const portfolioFetcher = new PortfolioFetcher(httpClient);
1989
- * ```
1990
- */
1991
- constructor(httpClient, logger) {
1992
- this.httpClient = httpClient;
1993
- this.logger = logger || new NoOpLogger();
1994
- }
1995
1851
  /**
1996
- * Gets raw portfolio positions response from API.
1997
- *
1998
- * @returns Promise resolving to portfolio positions response with CLOB and AMM positions
1999
- * @throws Error if API request fails or user is not authenticated
2000
- *
2001
- * @example
2002
- * ```typescript
2003
- * const response = await portfolioFetcher.getPositions();
2004
- * console.log(`CLOB positions: ${response.clob.length}`);
2005
- * console.log(`AMM positions: ${response.amm.length}`);
2006
- * console.log(`Total points: ${response.accumulativePoints}`);
2007
- * ```
2008
- */
2009
- async getPositions() {
2010
- this.logger.debug("Fetching user positions");
2011
- try {
2012
- const response = await this.httpClient.get(
2013
- "/portfolio/positions"
2014
- );
2015
- this.logger.info("Positions fetched successfully", {
2016
- clobCount: response.clob?.length || 0,
2017
- ammCount: response.amm?.length || 0
2018
- });
2019
- return response;
2020
- } catch (error) {
2021
- this.logger.error("Failed to fetch positions", error);
2022
- throw error;
2023
- }
2024
- }
2025
- /**
2026
- * Gets CLOB positions only.
2027
- *
2028
- * @returns Promise resolving to array of CLOB positions
2029
- * @throws Error if API request fails
2030
- *
2031
- * @example
2032
- * ```typescript
2033
- * const clobPositions = await portfolioFetcher.getCLOBPositions();
2034
- * clobPositions.forEach(pos => {
2035
- * console.log(`${pos.market.title}: YES ${pos.positions.yes.unrealizedPnl} P&L`);
2036
- * });
2037
- * ```
2038
- */
2039
- async getCLOBPositions() {
2040
- const response = await this.getPositions();
2041
- return response.clob || [];
2042
- }
2043
- /**
2044
- * Gets AMM positions only.
2045
- *
2046
- * @returns Promise resolving to array of AMM positions
2047
- * @throws Error if API request fails
2048
- *
2049
- * @example
2050
- * ```typescript
2051
- * const ammPositions = await portfolioFetcher.getAMMPositions();
2052
- * ammPositions.forEach(pos => {
2053
- * console.log(`${pos.market.title}: ${pos.unrealizedPnl} P&L`);
2054
- * });
2055
- * ```
2056
- */
2057
- async getAMMPositions() {
2058
- const response = await this.getPositions();
2059
- return response.amm || [];
2060
- }
2061
- /**
2062
- * Flattens positions into a unified format for easier consumption.
2063
- *
2064
- * @remarks
2065
- * Converts CLOB positions (which have YES/NO sides) and AMM positions
2066
- * into a unified Position array. Only includes positions with non-zero values.
2067
- *
2068
- * @returns Promise resolving to array of flattened positions
2069
- * @throws Error if API request fails
2070
- *
2071
- * @example
2072
- * ```typescript
2073
- * const positions = await portfolioFetcher.getFlattenedPositions();
2074
- * positions.forEach(pos => {
2075
- * const pnlPercent = (pos.unrealizedPnl / pos.costBasis) * 100;
2076
- * console.log(`${pos.market.title} (${pos.side}): ${pnlPercent.toFixed(2)}% P&L`);
2077
- * });
2078
- * ```
2079
- */
2080
- async getFlattenedPositions() {
2081
- const response = await this.getPositions();
2082
- const positions = [];
2083
- for (const clobPos of response.clob || []) {
2084
- const yesCost = parseFloat(clobPos.positions.yes.cost);
2085
- const yesValue = parseFloat(clobPos.positions.yes.marketValue);
2086
- if (yesCost > 0 || yesValue > 0) {
2087
- positions.push({
2088
- type: "CLOB",
2089
- market: clobPos.market,
2090
- side: "YES",
2091
- costBasis: yesCost,
2092
- marketValue: yesValue,
2093
- unrealizedPnl: parseFloat(clobPos.positions.yes.unrealizedPnl),
2094
- realizedPnl: parseFloat(clobPos.positions.yes.realisedPnl),
2095
- currentPrice: clobPos.latestTrade?.latestYesPrice ?? 0,
2096
- avgPrice: yesCost > 0 ? parseFloat(clobPos.positions.yes.fillPrice) / 1e6 : 0,
2097
- tokenBalance: parseFloat(clobPos.tokensBalance.yes)
2098
- });
2099
- }
2100
- const noCost = parseFloat(clobPos.positions.no.cost);
2101
- const noValue = parseFloat(clobPos.positions.no.marketValue);
2102
- if (noCost > 0 || noValue > 0) {
2103
- positions.push({
2104
- type: "CLOB",
2105
- market: clobPos.market,
2106
- side: "NO",
2107
- costBasis: noCost,
2108
- marketValue: noValue,
2109
- unrealizedPnl: parseFloat(clobPos.positions.no.unrealizedPnl),
2110
- realizedPnl: parseFloat(clobPos.positions.no.realisedPnl),
2111
- currentPrice: clobPos.latestTrade?.latestNoPrice ?? 0,
2112
- avgPrice: noCost > 0 ? parseFloat(clobPos.positions.no.fillPrice) / 1e6 : 0,
2113
- tokenBalance: parseFloat(clobPos.tokensBalance.no)
2114
- });
2115
- }
2116
- }
2117
- for (const ammPos of response.amm || []) {
2118
- const cost = parseFloat(ammPos.totalBuysCost);
2119
- const value = parseFloat(ammPos.collateralAmount);
2120
- if (cost > 0 || value > 0) {
2121
- positions.push({
2122
- type: "AMM",
2123
- market: ammPos.market,
2124
- side: ammPos.outcomeIndex === 0 ? "YES" : "NO",
2125
- costBasis: cost,
2126
- marketValue: value,
2127
- unrealizedPnl: parseFloat(ammPos.unrealizedPnl),
2128
- realizedPnl: parseFloat(ammPos.realizedPnl),
2129
- currentPrice: ammPos.latestTrade ? parseFloat(ammPos.latestTrade.outcomeTokenPrice) : 0,
2130
- avgPrice: parseFloat(ammPos.averageFillPrice),
2131
- tokenBalance: parseFloat(ammPos.outcomeTokenAmount)
2132
- });
2133
- }
2134
- }
2135
- this.logger.debug("Flattened positions", { count: positions.length });
2136
- return positions;
2137
- }
2138
- /**
2139
- * Calculates portfolio summary statistics from raw API response.
1852
+ * Gets the wallet address.
2140
1853
  *
2141
- * @param response - Portfolio positions response from API
2142
- * @returns Portfolio summary with totals and statistics
1854
+ * @returns Ethereum address of the wallet
2143
1855
  *
2144
1856
  * @example
2145
1857
  * ```typescript
2146
- * const response = await portfolioFetcher.getPositions();
2147
- * const summary = portfolioFetcher.calculateSummary(response);
2148
- *
2149
- * console.log(`Total Portfolio Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
2150
- * console.log(`Total P&L: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
2151
- * console.log(`CLOB Positions: ${summary.breakdown.clob.positions}`);
2152
- * console.log(`AMM Positions: ${summary.breakdown.amm.positions}`);
1858
+ * const address = orderClient.walletAddress;
1859
+ * console.log(`Wallet: ${address}`);
2153
1860
  * ```
2154
1861
  */
2155
- calculateSummary(response) {
2156
- this.logger.debug("Calculating portfolio summary", {
2157
- clobCount: response.clob?.length || 0,
2158
- ammCount: response.amm?.length || 0
2159
- });
2160
- let totalValue = 0;
2161
- let totalCostBasis = 0;
2162
- let totalUnrealizedPnl = 0;
2163
- let totalRealizedPnl = 0;
2164
- let clobPositions = 0;
2165
- let clobValue = 0;
2166
- let clobPnl = 0;
2167
- let ammPositions = 0;
2168
- let ammValue = 0;
2169
- let ammPnl = 0;
2170
- for (const clobPos of response.clob || []) {
2171
- const yesCost = parseFloat(clobPos.positions.yes.cost);
2172
- const yesValue = parseFloat(clobPos.positions.yes.marketValue);
2173
- const yesUnrealizedPnl = parseFloat(clobPos.positions.yes.unrealizedPnl);
2174
- const yesRealizedPnl = parseFloat(clobPos.positions.yes.realisedPnl);
2175
- if (yesCost > 0 || yesValue > 0) {
2176
- clobPositions++;
2177
- totalCostBasis += yesCost;
2178
- totalValue += yesValue;
2179
- totalUnrealizedPnl += yesUnrealizedPnl;
2180
- totalRealizedPnl += yesRealizedPnl;
2181
- clobValue += yesValue;
2182
- clobPnl += yesUnrealizedPnl;
2183
- }
2184
- const noCost = parseFloat(clobPos.positions.no.cost);
2185
- const noValue = parseFloat(clobPos.positions.no.marketValue);
2186
- const noUnrealizedPnl = parseFloat(clobPos.positions.no.unrealizedPnl);
2187
- const noRealizedPnl = parseFloat(clobPos.positions.no.realisedPnl);
2188
- if (noCost > 0 || noValue > 0) {
2189
- clobPositions++;
2190
- totalCostBasis += noCost;
2191
- totalValue += noValue;
2192
- totalUnrealizedPnl += noUnrealizedPnl;
2193
- totalRealizedPnl += noRealizedPnl;
2194
- clobValue += noValue;
2195
- clobPnl += noUnrealizedPnl;
2196
- }
2197
- }
2198
- for (const ammPos of response.amm || []) {
2199
- const cost = parseFloat(ammPos.totalBuysCost);
2200
- const value = parseFloat(ammPos.collateralAmount);
2201
- const unrealizedPnl = parseFloat(ammPos.unrealizedPnl);
2202
- const realizedPnl = parseFloat(ammPos.realizedPnl);
2203
- if (cost > 0 || value > 0) {
2204
- ammPositions++;
2205
- totalCostBasis += cost;
2206
- totalValue += value;
2207
- totalUnrealizedPnl += unrealizedPnl;
2208
- totalRealizedPnl += realizedPnl;
2209
- ammValue += value;
2210
- ammPnl += unrealizedPnl;
2211
- }
2212
- }
2213
- const totalUnrealizedPnlPercent = totalCostBasis > 0 ? totalUnrealizedPnl / totalCostBasis * 100 : 0;
2214
- const uniqueMarkets = /* @__PURE__ */ new Set();
2215
- for (const pos of response.clob || []) {
2216
- uniqueMarkets.add(pos.market.id);
2217
- }
2218
- for (const pos of response.amm || []) {
2219
- uniqueMarkets.add(pos.market.id);
2220
- }
2221
- const summary = {
2222
- totalValue,
2223
- totalCostBasis,
2224
- totalUnrealizedPnl,
2225
- totalRealizedPnl,
2226
- totalUnrealizedPnlPercent,
2227
- positionCount: clobPositions + ammPositions,
2228
- marketCount: uniqueMarkets.size,
2229
- breakdown: {
2230
- clob: {
2231
- positions: clobPositions,
2232
- value: clobValue,
2233
- pnl: clobPnl
2234
- },
2235
- amm: {
2236
- positions: ammPositions,
2237
- value: ammValue,
2238
- pnl: ammPnl
2239
- }
2240
- }
2241
- };
2242
- this.logger.debug("Portfolio summary calculated", summary);
2243
- return summary;
1862
+ get walletAddress() {
1863
+ return this.wallet.address;
2244
1864
  }
2245
1865
  /**
2246
- * Gets positions and calculates summary in a single call.
1866
+ * Gets the owner ID (user ID from profile).
2247
1867
  *
2248
- * @returns Promise resolving to response and summary
2249
- * @throws Error if API request fails or user is not authenticated
1868
+ * @returns Owner ID from user profile, or undefined if not yet loaded
2250
1869
  *
2251
1870
  * @example
2252
1871
  * ```typescript
2253
- * const { response, summary } = await portfolioFetcher.getPortfolio();
2254
- *
2255
- * console.log('Portfolio Summary:');
2256
- * console.log(` Total Value: $${(summary.totalValue / 1e6).toFixed(2)}`);
2257
- * console.log(` Total P&L: $${(summary.totalUnrealizedPnl / 1e6).toFixed(2)}`);
2258
- * console.log(` P&L %: ${summary.totalUnrealizedPnlPercent.toFixed(2)}%`);
2259
- * console.log(`\nCLOB Positions: ${response.clob.length}`);
2260
- * console.log(`AMM Positions: ${response.amm.length}`);
1872
+ * const ownerId = orderClient.ownerId;
1873
+ * if (ownerId) {
1874
+ * console.log(`Owner ID: ${ownerId}`);
1875
+ * }
2261
1876
  * ```
2262
1877
  */
2263
- async getPortfolio() {
2264
- this.logger.debug("Fetching portfolio with summary");
2265
- const response = await this.getPositions();
2266
- const summary = this.calculateSummary(response);
2267
- this.logger.info("Portfolio fetched with summary", {
2268
- positionCount: summary.positionCount,
2269
- totalValueUSDC: summary.totalValue / 1e6,
2270
- pnlPercent: summary.totalUnrealizedPnlPercent
2271
- });
2272
- return { response, summary };
1878
+ get ownerId() {
1879
+ return this.cachedUserData?.userId;
2273
1880
  }
2274
1881
  };
2275
1882
 
@@ -2290,7 +1897,7 @@ var WebSocketClient = class {
2290
1897
  this.pendingListeners = [];
2291
1898
  this.config = {
2292
1899
  url: config.url || DEFAULT_WS_URL,
2293
- sessionCookie: config.sessionCookie || "",
1900
+ apiKey: config.apiKey || process.env.LIMITLESS_API_KEY || "",
2294
1901
  autoReconnect: config.autoReconnect ?? true,
2295
1902
  reconnectDelay: config.reconnectDelay || 1e3,
2296
1903
  maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
@@ -2315,18 +1922,29 @@ var WebSocketClient = class {
2315
1922
  return this.state === "connected" /* CONNECTED */ && this.socket?.connected === true;
2316
1923
  }
2317
1924
  /**
2318
- * Sets the session cookie for authentication.
1925
+ * Sets the API key for authentication.
2319
1926
  *
2320
- * @param sessionCookie - Session cookie value
1927
+ * @param apiKey - API key value
1928
+ *
1929
+ * @remarks
1930
+ * API key is required for authenticated subscriptions (positions, transactions).
1931
+ * If already connected, this will trigger a reconnection with the new API key.
2321
1932
  */
2322
- setSessionCookie(sessionCookie) {
2323
- this.config.sessionCookie = sessionCookie;
1933
+ setApiKey(apiKey) {
1934
+ this.config.apiKey = apiKey;
2324
1935
  if (this.socket?.connected) {
2325
- this.logger.info("Session cookie updated, reconnecting...");
2326
- this.disconnect();
2327
- this.connect();
1936
+ this.logger.info("API key updated, reconnecting...");
1937
+ this.reconnectWithNewAuth();
2328
1938
  }
2329
1939
  }
1940
+ /**
1941
+ * Reconnects with new authentication credentials.
1942
+ * @internal
1943
+ */
1944
+ async reconnectWithNewAuth() {
1945
+ await this.disconnect();
1946
+ await this.connect();
1947
+ }
2330
1948
  /**
2331
1949
  * Connects to the WebSocket server.
2332
1950
  *
@@ -2340,8 +1958,8 @@ var WebSocketClient = class {
2340
1958
  * ```
2341
1959
  */
2342
1960
  async connect() {
2343
- if (this.socket?.connected) {
2344
- this.logger.info("Already connected");
1961
+ if (this.socket?.connected || this.state === "connecting" /* CONNECTING */) {
1962
+ this.logger.info("Already connected or connecting");
2345
1963
  return;
2346
1964
  }
2347
1965
  this.logger.info("Connecting to WebSocket", { url: this.config.url });
@@ -2350,18 +1968,26 @@ var WebSocketClient = class {
2350
1968
  const timeout = setTimeout(() => {
2351
1969
  reject(new Error(`Connection timeout after ${this.config.timeout}ms`));
2352
1970
  }, this.config.timeout);
2353
- const wsUrl = this.config.url.endsWith("/markets") ? this.config.url : `${this.config.url}/markets`;
2354
- this.socket = (0, import_socket.io)(wsUrl, {
1971
+ const wsUrl = this.config.url;
1972
+ const socketOptions = {
2355
1973
  transports: ["websocket"],
2356
1974
  // Use WebSocket transport only
2357
- extraHeaders: {
2358
- cookie: `limitless_session=${this.config.sessionCookie}`
2359
- },
2360
1975
  reconnection: this.config.autoReconnect,
2361
1976
  reconnectionDelay: this.config.reconnectDelay,
2362
- reconnectionAttempts: this.config.maxReconnectAttempts,
1977
+ reconnectionDelayMax: Math.min(this.config.reconnectDelay * 32, 6e4),
1978
+ // Max 60s
1979
+ reconnectionAttempts: this.config.maxReconnectAttempts === Infinity ? 0 : this.config.maxReconnectAttempts,
1980
+ // 0 = infinite
1981
+ randomizationFactor: 0.2,
1982
+ // Add jitter to prevent thundering herd
2363
1983
  timeout: this.config.timeout
2364
- });
1984
+ };
1985
+ if (this.config.apiKey) {
1986
+ socketOptions.extraHeaders = {
1987
+ "X-API-Key": this.config.apiKey
1988
+ };
1989
+ }
1990
+ this.socket = (0, import_socket.io)(wsUrl + "/markets", socketOptions);
2365
1991
  this.attachPendingListeners();
2366
1992
  this.setupEventHandlers();
2367
1993
  this.socket.once("connect", () => {
@@ -2383,12 +2009,14 @@ var WebSocketClient = class {
2383
2009
  /**
2384
2010
  * Disconnects from the WebSocket server.
2385
2011
  *
2012
+ * @returns Promise that resolves when disconnected
2013
+ *
2386
2014
  * @example
2387
2015
  * ```typescript
2388
- * wsClient.disconnect();
2016
+ * await wsClient.disconnect();
2389
2017
  * ```
2390
2018
  */
2391
- disconnect() {
2019
+ async disconnect() {
2392
2020
  if (!this.socket) {
2393
2021
  return;
2394
2022
  }
@@ -2403,13 +2031,13 @@ var WebSocketClient = class {
2403
2031
  *
2404
2032
  * @param channel - Channel to subscribe to
2405
2033
  * @param options - Subscription options
2406
- * @returns Promise that resolves when subscribed
2034
+ * @returns Promise that resolves immediately (kept async for API compatibility)
2407
2035
  * @throws Error if not connected
2408
2036
  *
2409
2037
  * @example
2410
2038
  * ```typescript
2411
2039
  * // Subscribe to orderbook for a specific market
2412
- * await wsClient.subscribe('orderbook', { marketSlug: 'market-123' });
2040
+ * await wsClient.subscribe('orderbook', { marketSlugs: ['market-123'] });
2413
2041
  *
2414
2042
  * // Subscribe to all trades
2415
2043
  * await wsClient.subscribe('trades');
@@ -2422,21 +2050,20 @@ var WebSocketClient = class {
2422
2050
  if (!this.isConnected()) {
2423
2051
  throw new Error("WebSocket not connected. Call connect() first.");
2424
2052
  }
2053
+ const authenticatedChannels = [
2054
+ "subscribe_positions",
2055
+ "subscribe_transactions"
2056
+ ];
2057
+ if (authenticatedChannels.includes(channel) && !this.config.apiKey) {
2058
+ throw new Error(
2059
+ `API key is required for '${channel}' subscription. Please provide an API key in the constructor or set LIMITLESS_API_KEY environment variable. You can generate an API key at https://limitless.exchange`
2060
+ );
2061
+ }
2425
2062
  const subscriptionKey = this.getSubscriptionKey(channel, options);
2426
2063
  this.subscriptions.set(subscriptionKey, options);
2427
2064
  this.logger.info("Subscribing to channel", { channel, options });
2428
- return new Promise((resolve, reject) => {
2429
- this.socket.emit(channel, options, (response) => {
2430
- if (response?.error) {
2431
- this.logger.error("Subscription failed", response.error);
2432
- this.subscriptions.delete(subscriptionKey);
2433
- reject(new Error(response.error));
2434
- } else {
2435
- this.logger.info("Subscribed successfully", { channel, options });
2436
- resolve();
2437
- }
2438
- });
2439
- });
2065
+ this.socket.emit(channel, options);
2066
+ this.logger.info("Subscription request sent", { channel, options });
2440
2067
  }
2441
2068
  /**
2442
2069
  * Unsubscribes from a channel.
@@ -2444,10 +2071,11 @@ var WebSocketClient = class {
2444
2071
  * @param channel - Channel to unsubscribe from
2445
2072
  * @param options - Subscription options (must match subscribe call)
2446
2073
  * @returns Promise that resolves when unsubscribed
2074
+ * @throws Error if not connected or unsubscribe fails
2447
2075
  *
2448
2076
  * @example
2449
2077
  * ```typescript
2450
- * await wsClient.unsubscribe('orderbook', { marketSlug: 'market-123' });
2078
+ * await wsClient.unsubscribe('orderbook', { marketSlugs: ['market-123'] });
2451
2079
  * ```
2452
2080
  */
2453
2081
  async unsubscribe(channel, options = {}) {
@@ -2457,17 +2085,19 @@ var WebSocketClient = class {
2457
2085
  const subscriptionKey = this.getSubscriptionKey(channel, options);
2458
2086
  this.subscriptions.delete(subscriptionKey);
2459
2087
  this.logger.info("Unsubscribing from channel", { channel, options });
2460
- return new Promise((resolve, reject) => {
2461
- this.socket.emit("unsubscribe", { channel, ...options }, (response) => {
2462
- if (response?.error) {
2463
- this.logger.error("Unsubscribe failed", response.error);
2464
- reject(new Error(response.error));
2465
- } else {
2466
- this.logger.info("Unsubscribed successfully", { channel, options });
2467
- resolve();
2468
- }
2469
- });
2470
- });
2088
+ try {
2089
+ const unsubscribeData = { channel, ...options };
2090
+ const response = await this.socket.timeout(5e3).emitWithAck("unsubscribe", unsubscribeData);
2091
+ if (response && typeof response === "object" && "error" in response) {
2092
+ const errorMsg = response.error;
2093
+ this.logger.error("Unsubscribe failed", new Error(errorMsg), { error: errorMsg });
2094
+ throw new Error(`Unsubscribe failed: ${errorMsg}`);
2095
+ }
2096
+ this.logger.info("Unsubscribed successfully", { channel, options });
2097
+ } catch (error) {
2098
+ this.logger.error("Unsubscribe error", error, { channel });
2099
+ throw error;
2100
+ }
2471
2101
  }
2472
2102
  /**
2473
2103
  * Registers an event listener.
@@ -2510,14 +2140,27 @@ var WebSocketClient = class {
2510
2140
  * Removes an event listener.
2511
2141
  *
2512
2142
  * @param event - Event name
2513
- * @param handler - Event handler to remove
2143
+ * @param handler - Event handler to remove (if undefined, removes all handlers for event)
2514
2144
  * @returns This client for chaining
2145
+ *
2146
+ * @example
2147
+ * ```typescript
2148
+ * // Remove specific handler
2149
+ * wsClient.off('orderbookUpdate', myHandler);
2150
+ *
2151
+ * // Remove all handlers for event
2152
+ * wsClient.off('orderbookUpdate');
2153
+ * ```
2515
2154
  */
2516
2155
  off(event, handler) {
2517
2156
  if (!this.socket) {
2518
2157
  return this;
2519
2158
  }
2520
- this.socket.off(event, handler);
2159
+ if (handler === void 0) {
2160
+ this.socket.removeAllListeners(event);
2161
+ } else {
2162
+ this.socket.off(event, handler);
2163
+ }
2521
2164
  return this;
2522
2165
  }
2523
2166
  /**
@@ -2610,8 +2253,7 @@ var WebSocketClient = class {
2610
2253
  // Annotate the CommonJS export names for ESM import in node:
2611
2254
  0 && (module.exports = {
2612
2255
  APIError,
2613
- AuthenticatedClient,
2614
- Authenticator,
2256
+ AuthenticationError,
2615
2257
  BASE_SEPOLIA_CHAIN_ID,
2616
2258
  CONTRACT_ADDRESSES,
2617
2259
  ConsoleLogger,
@@ -2619,14 +2261,16 @@ var WebSocketClient = class {
2619
2261
  DEFAULT_CHAIN_ID,
2620
2262
  DEFAULT_WS_URL,
2621
2263
  HttpClient,
2264
+ Market,
2622
2265
  MarketFetcher,
2623
- MessageSigner,
2624
2266
  NoOpLogger,
2625
2267
  OrderBuilder,
2626
2268
  OrderClient,
2627
2269
  OrderSigner,
2628
2270
  OrderType,
2271
+ OrderValidationError,
2629
2272
  PortfolioFetcher,
2273
+ RateLimitError,
2630
2274
  RetryConfig,
2631
2275
  RetryableClient,
2632
2276
  SIGNING_MESSAGE_TEMPLATE,