@limitless-exchange/sdk 1.0.2 → 1.0.4

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,35 +31,47 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  APIError: () => APIError,
34
+ ApiTokenService: () => ApiTokenService,
34
35
  AuthenticationError: () => AuthenticationError,
35
36
  BASE_SEPOLIA_CHAIN_ID: () => BASE_SEPOLIA_CHAIN_ID,
36
37
  CONTRACT_ADDRESSES: () => CONTRACT_ADDRESSES,
38
+ Client: () => Client,
37
39
  ConsoleLogger: () => ConsoleLogger,
38
40
  DEFAULT_API_URL: () => DEFAULT_API_URL,
39
41
  DEFAULT_CHAIN_ID: () => DEFAULT_CHAIN_ID,
40
42
  DEFAULT_WS_URL: () => DEFAULT_WS_URL,
43
+ DelegatedOrderService: () => DelegatedOrderService,
41
44
  HttpClient: () => HttpClient,
42
45
  Market: () => Market,
43
46
  MarketFetcher: () => MarketFetcher,
47
+ MarketPageFetcher: () => MarketPageFetcher,
44
48
  NoOpLogger: () => NoOpLogger,
45
49
  OrderBuilder: () => OrderBuilder,
46
50
  OrderClient: () => OrderClient,
47
51
  OrderSigner: () => OrderSigner,
48
52
  OrderType: () => OrderType,
49
53
  OrderValidationError: () => OrderValidationError,
54
+ PartnerAccountService: () => PartnerAccountService,
50
55
  PortfolioFetcher: () => PortfolioFetcher,
51
56
  RateLimitError: () => RateLimitError,
52
57
  RetryConfig: () => RetryConfig,
53
58
  RetryableClient: () => RetryableClient,
54
59
  SIGNING_MESSAGE_TEMPLATE: () => SIGNING_MESSAGE_TEMPLATE,
60
+ ScopeAccountCreation: () => ScopeAccountCreation,
61
+ ScopeDelegatedSigning: () => ScopeDelegatedSigning,
62
+ ScopeTrading: () => ScopeTrading,
55
63
  Side: () => Side,
56
64
  SignatureType: () => SignatureType,
57
65
  ValidationError: () => ValidationError,
58
66
  WebSocketClient: () => WebSocketClient,
59
67
  WebSocketState: () => WebSocketState,
60
68
  ZERO_ADDRESS: () => ZERO_ADDRESS,
69
+ buildHMACMessage: () => buildHMACMessage,
70
+ computeHMACSignature: () => computeHMACSignature,
61
71
  getContractAddress: () => getContractAddress,
62
72
  retryOnErrors: () => retryOnErrors,
73
+ toFiniteInteger: () => toFiniteInteger,
74
+ toFiniteNumber: () => toFiniteNumber,
63
75
  validateOrderArgs: () => validateOrderArgs,
64
76
  validateSignedOrder: () => validateSignedOrder,
65
77
  validateUnsignedOrder: () => validateUnsignedOrder,
@@ -67,6 +79,11 @@ __export(index_exports, {
67
79
  });
68
80
  module.exports = __toCommonJS(index_exports);
69
81
 
82
+ // src/types/api-tokens.ts
83
+ var ScopeTrading = "trading";
84
+ var ScopeAccountCreation = "account_creation";
85
+ var ScopeDelegatedSigning = "delegated_signing";
86
+
70
87
  // src/types/logger.ts
71
88
  var NoOpLogger = class {
72
89
  debug() {
@@ -224,6 +241,40 @@ function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
224
241
  return address;
225
242
  }
226
243
 
244
+ // src/utils/sdk-tracking.ts
245
+ var SDK_ID = "lmts-sdk-ts";
246
+ function isNodeRuntime() {
247
+ return typeof process !== "undefined" && !!process.versions?.node;
248
+ }
249
+ function resolveSdkVersion() {
250
+ if ("1.0.4") {
251
+ return "1.0.4";
252
+ }
253
+ return "0.0.0";
254
+ }
255
+ function resolveRuntimeToken() {
256
+ if (isNodeRuntime()) {
257
+ return `node/${process.versions.node}`;
258
+ }
259
+ return "runtime/unknown";
260
+ }
261
+ function buildSdkTrackingHeaders() {
262
+ const sdkVersion = resolveSdkVersion();
263
+ const headers = {
264
+ "x-sdk-version": `${SDK_ID}/${sdkVersion}`
265
+ };
266
+ if (isNodeRuntime()) {
267
+ headers["user-agent"] = `${SDK_ID}/${sdkVersion} (${resolveRuntimeToken()})`;
268
+ }
269
+ return headers;
270
+ }
271
+ function buildWebSocketTrackingHeaders() {
272
+ if (!isNodeRuntime()) {
273
+ return {};
274
+ }
275
+ return buildSdkTrackingHeaders();
276
+ }
277
+
227
278
  // src/api/errors.ts
228
279
  var APIError = class _APIError extends Error {
229
280
  /**
@@ -296,6 +347,19 @@ var ValidationError = class _ValidationError extends APIError {
296
347
  }
297
348
  };
298
349
 
350
+ // src/api/hmac.ts
351
+ var import_crypto = require("crypto");
352
+ function buildHMACMessage(timestamp, method, path, body) {
353
+ return `${timestamp}
354
+ ${method.toUpperCase()}
355
+ ${path}
356
+ ${body}`;
357
+ }
358
+ function computeHMACSignature(secret, timestamp, method, path, body) {
359
+ const key = Buffer.from(secret, "base64");
360
+ return (0, import_crypto.createHmac)("sha256", key).update(buildHMACMessage(timestamp, method, path, body)).digest("base64");
361
+ }
362
+
299
363
  // src/api/http.ts
300
364
  var HttpClient = class {
301
365
  /**
@@ -305,10 +369,14 @@ var HttpClient = class {
305
369
  */
306
370
  constructor(config = {}) {
307
371
  this.apiKey = config.apiKey || process.env.LIMITLESS_API_KEY;
372
+ this.hmacCredentials = config.hmacCredentials ? {
373
+ tokenId: config.hmacCredentials.tokenId,
374
+ secret: config.hmacCredentials.secret
375
+ } : void 0;
308
376
  this.logger = config.logger || new NoOpLogger();
309
- if (!this.apiKey) {
377
+ if (!this.apiKey && !this.hmacCredentials) {
310
378
  this.logger.warn(
311
- "API key not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable or pass apiKey parameter."
379
+ "Authentication not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable, pass apiKey, or configure hmacCredentials."
312
380
  );
313
381
  }
314
382
  const keepAlive = config.keepAlive !== false;
@@ -335,6 +403,7 @@ var HttpClient = class {
335
403
  headers: {
336
404
  "Content-Type": "application/json",
337
405
  Accept: "application/json",
406
+ ...buildSdkTrackingHeaders(),
338
407
  ...config.additionalHeaders
339
408
  }
340
409
  });
@@ -352,21 +421,42 @@ var HttpClient = class {
352
421
  */
353
422
  setupInterceptors() {
354
423
  this.client.interceptors.request.use(
355
- (config) => {
356
- if (this.apiKey) {
357
- config.headers["X-API-Key"] = this.apiKey;
424
+ (rawConfig) => {
425
+ const config = rawConfig;
426
+ const headers = config.headers || (config.headers = {});
427
+ const identityToken = config.identityToken;
428
+ delete headers["X-API-Key"];
429
+ delete headers["lmts-api-key"];
430
+ delete headers["lmts-timestamp"];
431
+ delete headers["lmts-signature"];
432
+ delete headers.identity;
433
+ if (identityToken) {
434
+ headers.identity = `Bearer ${identityToken}`;
435
+ } else if (this.hmacCredentials) {
436
+ const requestPath = this.getRequestPath(config);
437
+ const requestBody = this.getRequestBodyForSignature(config.data);
438
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
439
+ const signature = computeHMACSignature(
440
+ this.hmacCredentials.secret,
441
+ timestamp,
442
+ config.method || "GET",
443
+ requestPath,
444
+ requestBody
445
+ );
446
+ headers["lmts-api-key"] = this.hmacCredentials.tokenId;
447
+ headers["lmts-timestamp"] = timestamp;
448
+ headers["lmts-signature"] = signature;
449
+ } else if (this.apiKey) {
450
+ headers["X-API-Key"] = this.apiKey;
358
451
  }
359
452
  const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
360
453
  const method = config.method?.toUpperCase() || "GET";
361
- const logHeaders = { ...config.headers };
362
- if (logHeaders["X-API-Key"]) {
363
- logHeaders["X-API-Key"] = "***";
364
- }
454
+ const logHeaders = this.maskSensitiveHeaders(headers);
365
455
  this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
366
456
  headers: logHeaders,
367
457
  body: config.data
368
458
  });
369
- return config;
459
+ return rawConfig;
370
460
  },
371
461
  (error) => Promise.reject(error)
372
462
  );
@@ -421,6 +511,42 @@ var HttpClient = class {
421
511
  }
422
512
  );
423
513
  }
514
+ /**
515
+ * Extracts a human-readable error message from API response payload.
516
+ * @internal
517
+ */
518
+ extractErrorMessage(data, fallback) {
519
+ if (!data) {
520
+ return fallback;
521
+ }
522
+ if (typeof data === "object") {
523
+ if (Array.isArray(data.message)) {
524
+ const messages = data.message.map((err) => {
525
+ const details = Object.entries(err || {}).filter(([_key, val]) => val !== "" && val !== null && val !== void 0).map(([key, val]) => `${key}: ${val}`).join(", ");
526
+ return details || JSON.stringify(err);
527
+ }).filter((msg) => msg.trim() !== "").join(" | ");
528
+ return messages || data.error || JSON.stringify(data);
529
+ }
530
+ return data.message || data.error || data.msg || data.errors && JSON.stringify(data.errors) || JSON.stringify(data);
531
+ }
532
+ return String(data);
533
+ }
534
+ /**
535
+ * Creates a typed API error class from status code.
536
+ * @internal
537
+ */
538
+ createTypedApiError(status, message, data, url, method) {
539
+ if (status === 429) {
540
+ return new RateLimitError(message, status, data, url, method);
541
+ }
542
+ if (status === 401 || status === 403) {
543
+ return new AuthenticationError(message, status, data, url, method);
544
+ }
545
+ if (status === 400) {
546
+ return new ValidationError(message, status, data, url, method);
547
+ }
548
+ return new APIError(message, status, data, url, method);
549
+ }
424
550
  /**
425
551
  * Sets the API key for authenticated requests.
426
552
  *
@@ -429,12 +555,86 @@ var HttpClient = class {
429
555
  setApiKey(apiKey) {
430
556
  this.apiKey = apiKey;
431
557
  }
558
+ /**
559
+ * Returns the configured API key, if any.
560
+ */
561
+ getApiKey() {
562
+ return this.apiKey;
563
+ }
432
564
  /**
433
565
  * Clears the API key.
434
566
  */
435
567
  clearApiKey() {
436
568
  this.apiKey = void 0;
437
569
  }
570
+ /**
571
+ * Sets HMAC credentials for scoped API-token authentication.
572
+ */
573
+ setHMACCredentials(credentials) {
574
+ this.hmacCredentials = {
575
+ tokenId: credentials.tokenId,
576
+ secret: credentials.secret
577
+ };
578
+ }
579
+ /**
580
+ * Clears HMAC credentials.
581
+ */
582
+ clearHMACCredentials() {
583
+ this.hmacCredentials = void 0;
584
+ }
585
+ /**
586
+ * Returns a copy of the configured HMAC credentials, if any.
587
+ */
588
+ getHMACCredentials() {
589
+ if (!this.hmacCredentials) {
590
+ return void 0;
591
+ }
592
+ return {
593
+ tokenId: this.hmacCredentials.tokenId,
594
+ secret: this.hmacCredentials.secret
595
+ };
596
+ }
597
+ /**
598
+ * Returns the logger attached to this HTTP client.
599
+ */
600
+ getLogger() {
601
+ return this.logger;
602
+ }
603
+ /**
604
+ * Returns true when cookie/header-based auth is configured on the underlying client.
605
+ * This is primarily used for custom authenticated flows that don't use API keys or HMAC.
606
+ */
607
+ hasConfiguredHeaderAuth() {
608
+ const defaultHeaders = this.client.defaults.headers;
609
+ const candidates = [defaultHeaders?.common, defaultHeaders];
610
+ for (const headers of candidates) {
611
+ if (!headers) {
612
+ continue;
613
+ }
614
+ const authValues = [
615
+ headers.Cookie,
616
+ headers.cookie,
617
+ headers.Authorization,
618
+ headers.authorization,
619
+ headers.identity
620
+ ];
621
+ if (authValues.some((value) => typeof value === "string" && value.trim() !== "")) {
622
+ return true;
623
+ }
624
+ }
625
+ return false;
626
+ }
627
+ /**
628
+ * Ensures the client has some authenticated transport configured.
629
+ */
630
+ requireAuth(operation) {
631
+ if (this.apiKey || this.hmacCredentials || this.hasConfiguredHeaderAuth()) {
632
+ return;
633
+ }
634
+ throw new Error(
635
+ `Authentication is required for ${operation}; pass apiKey, hmacCredentials, cookie/auth headers, or set LIMITLESS_API_KEY.`
636
+ );
637
+ }
438
638
  /**
439
639
  * Performs a GET request.
440
640
  *
@@ -446,6 +646,38 @@ var HttpClient = class {
446
646
  const response = await this.client.get(url, config);
447
647
  return response.data;
448
648
  }
649
+ /**
650
+ * Performs a GET request with identity-token authentication.
651
+ */
652
+ async getWithIdentity(url, identityToken, config) {
653
+ const response = await this.client.get(url, {
654
+ ...config,
655
+ identityToken
656
+ });
657
+ return response.data;
658
+ }
659
+ /**
660
+ * Performs a GET request and returns raw response metadata.
661
+ *
662
+ * @remarks
663
+ * Use this when callers need access to status code or headers (e.g. redirect `Location`).
664
+ *
665
+ * @param url - Request URL
666
+ * @param config - Additional request configuration
667
+ * @returns Promise resolving to status, headers, and response data
668
+ */
669
+ async getRaw(url, config) {
670
+ const response = await this.client.get(url, config);
671
+ if (response.status >= 400) {
672
+ const message = this.extractErrorMessage(response.data, `Request failed with status ${response.status}`);
673
+ throw this.createTypedApiError(response.status, message, response.data, url, "GET");
674
+ }
675
+ return {
676
+ status: response.status,
677
+ headers: response.headers,
678
+ data: response.data
679
+ };
680
+ }
449
681
  /**
450
682
  * Performs a POST request.
451
683
  *
@@ -458,6 +690,36 @@ var HttpClient = class {
458
690
  const response = await this.client.post(url, data, config);
459
691
  return response.data;
460
692
  }
693
+ /**
694
+ * Performs a POST request with identity-token authentication.
695
+ */
696
+ async postWithIdentity(url, identityToken, data, config) {
697
+ const response = await this.client.post(url, data, {
698
+ ...config,
699
+ identityToken
700
+ });
701
+ return response.data;
702
+ }
703
+ /**
704
+ * Performs a POST request with additional per-request headers.
705
+ */
706
+ async postWithHeaders(url, data, headers, config) {
707
+ const response = await this.client.post(url, data, {
708
+ ...config,
709
+ headers: {
710
+ ...config?.headers || {},
711
+ ...headers || {}
712
+ }
713
+ });
714
+ return response.data;
715
+ }
716
+ /**
717
+ * Performs a PATCH request.
718
+ */
719
+ async patch(url, data, config) {
720
+ const response = await this.client.patch(url, data, config);
721
+ return response.data;
722
+ }
461
723
  /**
462
724
  * Performs a DELETE request.
463
725
  *
@@ -480,6 +742,28 @@ var HttpClient = class {
480
742
  const response = await this.client.delete(url, deleteConfig);
481
743
  return response.data;
482
744
  }
745
+ getRequestPath(config) {
746
+ const resolved = new URL(config.url || "", config.baseURL || this.client.defaults.baseURL || DEFAULT_API_URL);
747
+ return `${resolved.pathname}${resolved.search}`;
748
+ }
749
+ getRequestBodyForSignature(data) {
750
+ if (data === void 0 || data === null || data === "") {
751
+ return "";
752
+ }
753
+ if (typeof data === "string") {
754
+ return data;
755
+ }
756
+ return JSON.stringify(data);
757
+ }
758
+ maskSensitiveHeaders(headers) {
759
+ const masked = { ...headers };
760
+ for (const key of ["X-API-Key", "lmts-api-key", "lmts-timestamp", "lmts-signature", "identity"]) {
761
+ if (masked[key] !== void 0) {
762
+ masked[key] = key === "identity" ? "Bearer ***" : "***";
763
+ }
764
+ }
765
+ return masked;
766
+ }
483
767
  };
484
768
 
485
769
  // src/api/retry.ts
@@ -663,6 +947,75 @@ var RetryableClient = class {
663
947
  }
664
948
  };
665
949
 
950
+ // src/api-tokens/service.ts
951
+ var ApiTokenService = class {
952
+ constructor(httpClient, logger) {
953
+ this.httpClient = httpClient;
954
+ this.logger = logger || new NoOpLogger();
955
+ }
956
+ async deriveToken(identityToken, input) {
957
+ if (!identityToken) {
958
+ throw new Error("Identity token is required for deriveToken");
959
+ }
960
+ this.logger.debug("Deriving API token", { scopes: input.scopes, label: input.label });
961
+ return this.httpClient.postWithIdentity("/auth/api-tokens/derive", identityToken, input);
962
+ }
963
+ async listTokens() {
964
+ this.httpClient.requireAuth("listTokens");
965
+ return this.httpClient.get("/auth/api-tokens");
966
+ }
967
+ async getCapabilities(identityToken) {
968
+ if (!identityToken) {
969
+ throw new Error("Identity token is required for getCapabilities");
970
+ }
971
+ return this.httpClient.getWithIdentity("/auth/api-tokens/capabilities", identityToken);
972
+ }
973
+ async revokeToken(tokenId) {
974
+ this.httpClient.requireAuth("revokeToken");
975
+ const response = await this.httpClient.delete(`/auth/api-tokens/${encodeURIComponent(tokenId)}`);
976
+ return response.message;
977
+ }
978
+ };
979
+
980
+ // src/partner-accounts/service.ts
981
+ var _PartnerAccountService = class _PartnerAccountService {
982
+ constructor(httpClient, logger) {
983
+ this.httpClient = httpClient;
984
+ this.logger = logger || new NoOpLogger();
985
+ }
986
+ async createAccount(input, eoaHeaders) {
987
+ this.httpClient.requireAuth("createPartnerAccount");
988
+ const serverWalletMode = input.createServerWallet === true;
989
+ if (!serverWalletMode && !eoaHeaders) {
990
+ throw new Error("EOA headers are required when createServerWallet is not true");
991
+ }
992
+ if (input.displayName && input.displayName.length > _PartnerAccountService.DISPLAY_NAME_MAX_LENGTH) {
993
+ throw new Error(
994
+ `displayName must be at most ${_PartnerAccountService.DISPLAY_NAME_MAX_LENGTH} characters`
995
+ );
996
+ }
997
+ this.logger.debug("Creating partner account", {
998
+ displayName: input.displayName,
999
+ createServerWallet: input.createServerWallet
1000
+ });
1001
+ const payload = {
1002
+ displayName: input.displayName,
1003
+ createServerWallet: input.createServerWallet
1004
+ };
1005
+ return this.httpClient.postWithHeaders(
1006
+ "/profiles/partner-accounts",
1007
+ payload,
1008
+ eoaHeaders ? {
1009
+ "x-account": eoaHeaders.account,
1010
+ "x-signing-message": eoaHeaders.signingMessage,
1011
+ "x-signature": eoaHeaders.signature
1012
+ } : void 0
1013
+ );
1014
+ }
1015
+ };
1016
+ _PartnerAccountService.DISPLAY_NAME_MAX_LENGTH = 44;
1017
+ var PartnerAccountService = _PartnerAccountService;
1018
+
666
1019
  // src/orders/builder.ts
667
1020
  var import_ethers = require("ethers");
668
1021
  var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
@@ -951,6 +1304,104 @@ var OrderBuilder = class {
951
1304
  }
952
1305
  };
953
1306
 
1307
+ // src/delegated-orders/service.ts
1308
+ var DEFAULT_DELEGATED_FEE_RATE_BPS = 300;
1309
+ var DelegatedOrderService = class {
1310
+ constructor(httpClient, logger) {
1311
+ this.httpClient = httpClient;
1312
+ this.logger = logger || new NoOpLogger();
1313
+ }
1314
+ async createOrder(params) {
1315
+ this.httpClient.requireAuth("createDelegatedOrder");
1316
+ if (!Number.isInteger(params.onBehalfOf) || params.onBehalfOf <= 0) {
1317
+ throw new Error("onBehalfOf must be a positive integer");
1318
+ }
1319
+ const feeRateBps = params.feeRateBps && params.feeRateBps > 0 ? params.feeRateBps : DEFAULT_DELEGATED_FEE_RATE_BPS;
1320
+ const builder = new OrderBuilder(ZERO_ADDRESS, feeRateBps);
1321
+ const unsignedOrder = builder.buildOrder(params.args);
1322
+ const payload = {
1323
+ order: {
1324
+ salt: unsignedOrder.salt,
1325
+ maker: unsignedOrder.maker,
1326
+ signer: unsignedOrder.signer,
1327
+ taker: unsignedOrder.taker,
1328
+ tokenId: unsignedOrder.tokenId,
1329
+ makerAmount: unsignedOrder.makerAmount,
1330
+ takerAmount: unsignedOrder.takerAmount,
1331
+ expiration: unsignedOrder.expiration,
1332
+ nonce: unsignedOrder.nonce,
1333
+ feeRateBps: unsignedOrder.feeRateBps,
1334
+ side: unsignedOrder.side,
1335
+ signatureType: 0 /* EOA */,
1336
+ ...unsignedOrder.price !== void 0 ? { price: unsignedOrder.price } : {}
1337
+ },
1338
+ orderType: params.orderType,
1339
+ marketSlug: params.marketSlug,
1340
+ ownerId: params.onBehalfOf,
1341
+ onBehalfOf: params.onBehalfOf
1342
+ };
1343
+ this.logger.debug("Creating delegated order", {
1344
+ marketSlug: params.marketSlug,
1345
+ onBehalfOf: params.onBehalfOf,
1346
+ feeRateBps
1347
+ });
1348
+ return this.httpClient.post("/orders", payload);
1349
+ }
1350
+ async cancel(orderId) {
1351
+ this.httpClient.requireAuth("cancelDelegatedOrder");
1352
+ const response = await this.httpClient.delete(`/orders/${encodeURIComponent(orderId)}`);
1353
+ return response.message;
1354
+ }
1355
+ async cancelOnBehalfOf(orderId, onBehalfOf) {
1356
+ this.httpClient.requireAuth("cancelDelegatedOrder");
1357
+ if (!Number.isInteger(onBehalfOf) || onBehalfOf <= 0) {
1358
+ throw new Error("onBehalfOf must be a positive integer");
1359
+ }
1360
+ const response = await this.httpClient.delete(
1361
+ `/orders/${encodeURIComponent(orderId)}?onBehalfOf=${onBehalfOf}`
1362
+ );
1363
+ return response.message;
1364
+ }
1365
+ async cancelAll(marketSlug) {
1366
+ this.httpClient.requireAuth("cancelAllDelegatedOrders");
1367
+ const response = await this.httpClient.delete(`/orders/all/${encodeURIComponent(marketSlug)}`);
1368
+ return response.message;
1369
+ }
1370
+ async cancelAllOnBehalfOf(marketSlug, onBehalfOf) {
1371
+ this.httpClient.requireAuth("cancelAllDelegatedOrders");
1372
+ if (!Number.isInteger(onBehalfOf) || onBehalfOf <= 0) {
1373
+ throw new Error("onBehalfOf must be a positive integer");
1374
+ }
1375
+ const response = await this.httpClient.delete(
1376
+ `/orders/all/${encodeURIComponent(marketSlug)}?onBehalfOf=${onBehalfOf}`
1377
+ );
1378
+ return response.message;
1379
+ }
1380
+ };
1381
+
1382
+ // src/utils/number-flex.ts
1383
+ function toFiniteNumber(value) {
1384
+ if (typeof value === "number") {
1385
+ return Number.isFinite(value) ? value : void 0;
1386
+ }
1387
+ if (typeof value === "string") {
1388
+ const trimmed = value.trim();
1389
+ if (trimmed.length === 0) {
1390
+ return void 0;
1391
+ }
1392
+ const parsed = Number(trimmed);
1393
+ return Number.isFinite(parsed) ? parsed : void 0;
1394
+ }
1395
+ return void 0;
1396
+ }
1397
+ function toFiniteInteger(value) {
1398
+ const parsed = toFiniteNumber(value);
1399
+ if (parsed === void 0) {
1400
+ return void 0;
1401
+ }
1402
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
1403
+ }
1404
+
954
1405
  // src/orders/signer.ts
955
1406
  var OrderSigner = class {
956
1407
  /**
@@ -1092,7 +1543,7 @@ var OrderValidationError = class extends Error {
1092
1543
  }
1093
1544
  };
1094
1545
  function isFOKOrder(args) {
1095
- return "amount" in args;
1546
+ return "makerAmount" in args;
1096
1547
  }
1097
1548
  function validateOrderArgs(args) {
1098
1549
  if (!args.tokenId) {
@@ -1614,9 +2065,14 @@ var OrderClient = class {
1614
2065
  });
1615
2066
  const portfolioFetcher = new PortfolioFetcher(this.httpClient);
1616
2067
  const profile = await portfolioFetcher.getProfile(this.wallet.address);
2068
+ const userId = toFiniteInteger(profile.id);
2069
+ const feeRateBps = toFiniteInteger(profile.rank?.feeRateBps) ?? 300;
2070
+ if (userId === void 0) {
2071
+ throw new Error(`Invalid user profile id: ${profile.id}`);
2072
+ }
1617
2073
  this.cachedUserData = {
1618
- userId: profile.id,
1619
- feeRateBps: profile.rank?.feeRateBps || 300
2074
+ userId,
2075
+ feeRateBps
1620
2076
  };
1621
2077
  this.orderBuilder = new OrderBuilder(
1622
2078
  this.wallet.address,
@@ -1728,26 +2184,27 @@ var OrderClient = class {
1728
2184
  * @internal
1729
2185
  */
1730
2186
  transformOrderResponse(apiResponse) {
2187
+ const order = apiResponse.order;
1731
2188
  const cleanOrder = {
1732
2189
  order: {
1733
- id: apiResponse.order.id,
1734
- createdAt: apiResponse.order.createdAt,
1735
- makerAmount: apiResponse.order.makerAmount,
1736
- takerAmount: apiResponse.order.takerAmount,
1737
- expiration: apiResponse.order.expiration,
1738
- signatureType: apiResponse.order.signatureType,
1739
- salt: apiResponse.order.salt,
1740
- maker: apiResponse.order.maker,
1741
- signer: apiResponse.order.signer,
1742
- taker: apiResponse.order.taker,
1743
- tokenId: apiResponse.order.tokenId,
1744
- side: apiResponse.order.side,
1745
- feeRateBps: apiResponse.order.feeRateBps,
1746
- nonce: apiResponse.order.nonce,
1747
- signature: apiResponse.order.signature,
1748
- orderType: apiResponse.order.orderType,
1749
- price: apiResponse.order.price,
1750
- marketId: apiResponse.order.marketId
2190
+ id: order.id,
2191
+ createdAt: order.createdAt,
2192
+ makerAmount: toFiniteNumber(order.makerAmount) ?? order.makerAmount,
2193
+ takerAmount: toFiniteNumber(order.takerAmount) ?? order.takerAmount,
2194
+ expiration: order.expiration,
2195
+ signatureType: order.signatureType,
2196
+ salt: toFiniteInteger(order.salt) ?? order.salt,
2197
+ maker: order.maker,
2198
+ signer: order.signer,
2199
+ taker: order.taker,
2200
+ tokenId: order.tokenId,
2201
+ side: order.side,
2202
+ feeRateBps: order.feeRateBps,
2203
+ nonce: order.nonce,
2204
+ signature: order.signature,
2205
+ orderType: order.orderType,
2206
+ price: order.price === void 0 || order.price === null ? order.price : toFiniteNumber(order.price) ?? order.price,
2207
+ marketId: order.marketId
1751
2208
  }
1752
2209
  };
1753
2210
  if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
@@ -1880,6 +2337,174 @@ var OrderClient = class {
1880
2337
  }
1881
2338
  };
1882
2339
 
2340
+ // src/market-pages/fetcher.ts
2341
+ var MAX_REDIRECT_DEPTH = 3;
2342
+ var MarketPageFetcher = class {
2343
+ /**
2344
+ * Creates a new market-pages fetcher.
2345
+ *
2346
+ * @param httpClient - HTTP client for API calls
2347
+ * @param logger - Optional logger
2348
+ */
2349
+ constructor(httpClient, logger) {
2350
+ this.httpClient = httpClient;
2351
+ this.logger = logger || new NoOpLogger();
2352
+ }
2353
+ /**
2354
+ * Gets the navigation tree.
2355
+ */
2356
+ async getNavigation() {
2357
+ this.logger.debug("Fetching navigation tree");
2358
+ return this.httpClient.get("/navigation");
2359
+ }
2360
+ /**
2361
+ * Resolves a market page by path.
2362
+ *
2363
+ * @remarks
2364
+ * Handles 301 redirects manually by re-requesting `/market-pages/by-path` with the
2365
+ * redirected path value from `Location` header.
2366
+ */
2367
+ async getMarketPageByPath(path) {
2368
+ return this.getMarketPageByPathInternal(path, 0);
2369
+ }
2370
+ async getMarketPageByPathInternal(path, depth) {
2371
+ const query = new URLSearchParams({ path }).toString();
2372
+ const endpoint = `/market-pages/by-path?${query}`;
2373
+ const requestConfig = {
2374
+ maxRedirects: 0,
2375
+ validateStatus: (status) => status === 200 || status === 301
2376
+ };
2377
+ this.logger.debug("Resolving market page by path", { path, depth });
2378
+ const response = await this.httpClient.getRaw(endpoint, requestConfig);
2379
+ if (response.status === 200) {
2380
+ return response.data;
2381
+ }
2382
+ if (response.status !== 301) {
2383
+ throw new Error(`Unexpected response status: ${response.status}`);
2384
+ }
2385
+ if (depth >= MAX_REDIRECT_DEPTH) {
2386
+ throw new Error(
2387
+ `Too many redirects while resolving market page path '${path}' (max ${MAX_REDIRECT_DEPTH})`
2388
+ );
2389
+ }
2390
+ const locationHeader = response.headers?.location;
2391
+ const location = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
2392
+ if (!location || typeof location !== "string") {
2393
+ throw new Error("Redirect response missing valid Location header");
2394
+ }
2395
+ const redirectedPath = this.extractRedirectPath(location);
2396
+ this.logger.info("Following market page redirect", {
2397
+ from: path,
2398
+ to: redirectedPath,
2399
+ depth: depth + 1
2400
+ });
2401
+ return this.getMarketPageByPathInternal(redirectedPath, depth + 1);
2402
+ }
2403
+ extractRedirectPath(location) {
2404
+ const directByPathPrefix = "/market-pages/by-path";
2405
+ if (location.startsWith(directByPathPrefix)) {
2406
+ const url = new URL(location, "https://api.limitless.exchange");
2407
+ const path = url.searchParams.get("path");
2408
+ if (!path) {
2409
+ throw new Error("Redirect location '/market-pages/by-path' is missing required 'path' query parameter");
2410
+ }
2411
+ return path;
2412
+ }
2413
+ if (/^https?:\/\//i.test(location)) {
2414
+ const url = new URL(location);
2415
+ if (url.pathname === directByPathPrefix) {
2416
+ const path = url.searchParams.get("path");
2417
+ if (!path) {
2418
+ throw new Error("Redirect location '/market-pages/by-path' is missing required 'path' query parameter");
2419
+ }
2420
+ return path;
2421
+ }
2422
+ return url.pathname || "/";
2423
+ }
2424
+ return location;
2425
+ }
2426
+ /**
2427
+ * Gets markets for a market page with optional filtering and pagination.
2428
+ */
2429
+ async getMarkets(pageId, params = {}) {
2430
+ if (params.cursor !== void 0 && params.page !== void 0) {
2431
+ throw new Error("Parameters `cursor` and `page` are mutually exclusive");
2432
+ }
2433
+ const query = new URLSearchParams();
2434
+ if (params.page !== void 0) {
2435
+ query.append("page", String(params.page));
2436
+ }
2437
+ if (params.limit !== void 0) {
2438
+ query.append("limit", String(params.limit));
2439
+ }
2440
+ if (params.sort) {
2441
+ query.append("sort", params.sort);
2442
+ }
2443
+ if (params.cursor !== void 0) {
2444
+ query.append("cursor", params.cursor);
2445
+ }
2446
+ if (params.filters) {
2447
+ for (const [key, value] of Object.entries(params.filters)) {
2448
+ if (Array.isArray(value)) {
2449
+ for (const item of value) {
2450
+ query.append(key, this.stringifyFilterValue(item));
2451
+ }
2452
+ } else {
2453
+ query.append(key, this.stringifyFilterValue(value));
2454
+ }
2455
+ }
2456
+ }
2457
+ const queryString = query.toString();
2458
+ const endpoint = `/market-pages/${pageId}/markets${queryString ? `?${queryString}` : ""}`;
2459
+ this.logger.debug("Fetching market-page markets", { pageId, params });
2460
+ const response = await this.httpClient.get(endpoint);
2461
+ const markets = (response.data || []).map((marketData) => new Market(marketData, this.httpClient));
2462
+ if (response.pagination) {
2463
+ return {
2464
+ data: markets,
2465
+ pagination: response.pagination
2466
+ };
2467
+ }
2468
+ if (response.cursor) {
2469
+ return {
2470
+ data: markets,
2471
+ cursor: response.cursor
2472
+ };
2473
+ }
2474
+ throw new Error("Invalid market-page response: expected `pagination` or `cursor` metadata");
2475
+ }
2476
+ /**
2477
+ * Lists all property keys with options.
2478
+ */
2479
+ async getPropertyKeys() {
2480
+ return this.httpClient.get("/property-keys");
2481
+ }
2482
+ /**
2483
+ * Gets a single property key by ID.
2484
+ */
2485
+ async getPropertyKey(id) {
2486
+ return this.httpClient.get(`/property-keys/${id}`);
2487
+ }
2488
+ /**
2489
+ * Lists options for a property key, optionally filtered by parent option ID.
2490
+ */
2491
+ async getPropertyOptions(keyId, parentId) {
2492
+ const query = new URLSearchParams();
2493
+ if (parentId) {
2494
+ query.append("parentId", parentId);
2495
+ }
2496
+ const queryString = query.toString();
2497
+ const endpoint = `/property-keys/${keyId}/options${queryString ? `?${queryString}` : ""}`;
2498
+ return this.httpClient.get(endpoint);
2499
+ }
2500
+ stringifyFilterValue(value) {
2501
+ if (typeof value === "boolean") {
2502
+ return value ? "true" : "false";
2503
+ }
2504
+ return String(value);
2505
+ }
2506
+ };
2507
+
1883
2508
  // src/websocket/client.ts
1884
2509
  var import_socket = require("socket.io-client");
1885
2510
  var WebSocketClient = class {
@@ -1898,6 +2523,7 @@ var WebSocketClient = class {
1898
2523
  this.config = {
1899
2524
  url: config.url || DEFAULT_WS_URL,
1900
2525
  apiKey: config.apiKey || process.env.LIMITLESS_API_KEY || "",
2526
+ hmacCredentials: config.hmacCredentials,
1901
2527
  autoReconnect: config.autoReconnect ?? true,
1902
2528
  reconnectDelay: config.reconnectDelay || 1e3,
1903
2529
  maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
@@ -1937,6 +2563,32 @@ var WebSocketClient = class {
1937
2563
  this.reconnectWithNewAuth();
1938
2564
  }
1939
2565
  }
2566
+ /**
2567
+ * Sets HMAC credentials for authenticated subscriptions.
2568
+ *
2569
+ * @remarks
2570
+ * When configured alongside `apiKey`, this client uses HMAC headers for authenticated subscriptions.
2571
+ */
2572
+ setHMACCredentials(hmacCredentials) {
2573
+ this.config.hmacCredentials = {
2574
+ tokenId: hmacCredentials.tokenId,
2575
+ secret: hmacCredentials.secret
2576
+ };
2577
+ if (this.socket?.connected) {
2578
+ this.logger.info("HMAC credentials updated, reconnecting...");
2579
+ this.reconnectWithNewAuth();
2580
+ }
2581
+ }
2582
+ /**
2583
+ * Clears HMAC credentials.
2584
+ */
2585
+ clearHMACCredentials() {
2586
+ this.config.hmacCredentials = void 0;
2587
+ if (this.socket?.connected) {
2588
+ this.logger.info("HMAC credentials cleared, reconnecting...");
2589
+ this.reconnectWithNewAuth();
2590
+ }
2591
+ }
1940
2592
  /**
1941
2593
  * Reconnects with new authentication credentials.
1942
2594
  * @internal
@@ -1982,10 +2634,29 @@ var WebSocketClient = class {
1982
2634
  // Add jitter to prevent thundering herd
1983
2635
  timeout: this.config.timeout
1984
2636
  };
1985
- if (this.config.apiKey) {
2637
+ const extraHeaders = buildWebSocketTrackingHeaders();
2638
+ if (this.config.hmacCredentials) {
2639
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2640
+ const signature = computeHMACSignature(
2641
+ this.config.hmacCredentials.secret,
2642
+ timestamp,
2643
+ "GET",
2644
+ "/socket.io/?EIO=4&transport=websocket",
2645
+ ""
2646
+ );
2647
+ socketOptions.extraHeaders = {
2648
+ ...extraHeaders,
2649
+ "lmts-api-key": this.config.hmacCredentials.tokenId,
2650
+ "lmts-timestamp": timestamp,
2651
+ "lmts-signature": signature
2652
+ };
2653
+ } else if (this.config.apiKey) {
1986
2654
  socketOptions.extraHeaders = {
2655
+ ...extraHeaders,
1987
2656
  "X-API-Key": this.config.apiKey
1988
2657
  };
2658
+ } else if (Object.keys(extraHeaders).length > 0) {
2659
+ socketOptions.extraHeaders = extraHeaders;
1989
2660
  }
1990
2661
  this.socket = (0, import_socket.io)(wsUrl + "/markets", socketOptions);
1991
2662
  this.attachPendingListeners();
@@ -2054,9 +2725,9 @@ var WebSocketClient = class {
2054
2725
  "subscribe_positions",
2055
2726
  "subscribe_transactions"
2056
2727
  ];
2057
- if (authenticatedChannels.includes(channel) && !this.config.apiKey) {
2728
+ if (authenticatedChannels.includes(channel) && !this.config.apiKey && !this.config.hmacCredentials) {
2058
2729
  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`
2730
+ `Authentication is required for '${channel}' subscription. Please provide either apiKey or hmacCredentials when creating the WebSocket client.`
2060
2731
  );
2061
2732
  }
2062
2733
  const subscriptionKey = this.getSubscriptionKey(channel, options);
@@ -2250,38 +2921,106 @@ var WebSocketClient = class {
2250
2921
  return key.split(":")[0];
2251
2922
  }
2252
2923
  };
2924
+
2925
+ // src/client.ts
2926
+ var import_ethers3 = require("ethers");
2927
+ var Client = class _Client {
2928
+ constructor(config = {}) {
2929
+ this.http = new HttpClient(config);
2930
+ const logger = this.http.getLogger?.() || new NoOpLogger();
2931
+ this.markets = new MarketFetcher(this.http, logger);
2932
+ this.portfolio = new PortfolioFetcher(this.http, logger);
2933
+ this.pages = new MarketPageFetcher(this.http, logger);
2934
+ this.apiTokens = new ApiTokenService(this.http, logger);
2935
+ this.partnerAccounts = new PartnerAccountService(this.http, logger);
2936
+ this.delegatedOrders = new DelegatedOrderService(this.http, logger);
2937
+ }
2938
+ /**
2939
+ * Creates a root client around an existing shared HTTP client.
2940
+ */
2941
+ static fromHttpClient(httpClient) {
2942
+ const client = Object.create(_Client.prototype);
2943
+ const logger = httpClient.getLogger?.() || new NoOpLogger();
2944
+ client.http = httpClient;
2945
+ client.markets = new MarketFetcher(httpClient, logger);
2946
+ client.portfolio = new PortfolioFetcher(httpClient, logger);
2947
+ client.pages = new MarketPageFetcher(httpClient, logger);
2948
+ client.apiTokens = new ApiTokenService(httpClient, logger);
2949
+ client.partnerAccounts = new PartnerAccountService(httpClient, logger);
2950
+ client.delegatedOrders = new DelegatedOrderService(httpClient, logger);
2951
+ return client;
2952
+ }
2953
+ /**
2954
+ * Creates a regular EIP-712 order client reusing the shared transport and market cache.
2955
+ */
2956
+ newOrderClient(walletOrPrivateKey, config = {}) {
2957
+ const wallet = typeof walletOrPrivateKey === "string" ? new import_ethers3.ethers.Wallet(walletOrPrivateKey) : walletOrPrivateKey;
2958
+ return new OrderClient({
2959
+ httpClient: this.http,
2960
+ wallet,
2961
+ marketFetcher: this.markets,
2962
+ logger: this.http.getLogger(),
2963
+ ...config
2964
+ });
2965
+ }
2966
+ /**
2967
+ * Creates a WebSocket client reusing shared auth where possible.
2968
+ */
2969
+ newWebSocketClient(config = {}) {
2970
+ return new WebSocketClient(
2971
+ {
2972
+ apiKey: config.apiKey || this.http.getApiKey(),
2973
+ hmacCredentials: config.hmacCredentials || this.http.getHMACCredentials(),
2974
+ ...config
2975
+ },
2976
+ this.http.getLogger()
2977
+ );
2978
+ }
2979
+ };
2253
2980
  // Annotate the CommonJS export names for ESM import in node:
2254
2981
  0 && (module.exports = {
2255
2982
  APIError,
2983
+ ApiTokenService,
2256
2984
  AuthenticationError,
2257
2985
  BASE_SEPOLIA_CHAIN_ID,
2258
2986
  CONTRACT_ADDRESSES,
2987
+ Client,
2259
2988
  ConsoleLogger,
2260
2989
  DEFAULT_API_URL,
2261
2990
  DEFAULT_CHAIN_ID,
2262
2991
  DEFAULT_WS_URL,
2992
+ DelegatedOrderService,
2263
2993
  HttpClient,
2264
2994
  Market,
2265
2995
  MarketFetcher,
2996
+ MarketPageFetcher,
2266
2997
  NoOpLogger,
2267
2998
  OrderBuilder,
2268
2999
  OrderClient,
2269
3000
  OrderSigner,
2270
3001
  OrderType,
2271
3002
  OrderValidationError,
3003
+ PartnerAccountService,
2272
3004
  PortfolioFetcher,
2273
3005
  RateLimitError,
2274
3006
  RetryConfig,
2275
3007
  RetryableClient,
2276
3008
  SIGNING_MESSAGE_TEMPLATE,
3009
+ ScopeAccountCreation,
3010
+ ScopeDelegatedSigning,
3011
+ ScopeTrading,
2277
3012
  Side,
2278
3013
  SignatureType,
2279
3014
  ValidationError,
2280
3015
  WebSocketClient,
2281
3016
  WebSocketState,
2282
3017
  ZERO_ADDRESS,
3018
+ buildHMACMessage,
3019
+ computeHMACSignature,
2283
3020
  getContractAddress,
2284
3021
  retryOnErrors,
3022
+ toFiniteInteger,
3023
+ toFiniteNumber,
2285
3024
  validateOrderArgs,
2286
3025
  validateSignedOrder,
2287
3026
  validateUnsignedOrder,