@limitless-exchange/sdk 1.0.3 → 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.mjs CHANGED
@@ -1,3 +1,8 @@
1
+ // src/types/api-tokens.ts
2
+ var ScopeTrading = "trading";
3
+ var ScopeAccountCreation = "account_creation";
4
+ var ScopeDelegatedSigning = "delegated_signing";
5
+
1
6
  // src/types/logger.ts
2
7
  var NoOpLogger = class {
3
8
  debug() {
@@ -155,6 +160,40 @@ function getContractAddress(contractType, chainId = DEFAULT_CHAIN_ID) {
155
160
  return address;
156
161
  }
157
162
 
163
+ // src/utils/sdk-tracking.ts
164
+ var SDK_ID = "lmts-sdk-ts";
165
+ function isNodeRuntime() {
166
+ return typeof process !== "undefined" && !!process.versions?.node;
167
+ }
168
+ function resolveSdkVersion() {
169
+ if ("1.0.4") {
170
+ return "1.0.4";
171
+ }
172
+ return "0.0.0";
173
+ }
174
+ function resolveRuntimeToken() {
175
+ if (isNodeRuntime()) {
176
+ return `node/${process.versions.node}`;
177
+ }
178
+ return "runtime/unknown";
179
+ }
180
+ function buildSdkTrackingHeaders() {
181
+ const sdkVersion = resolveSdkVersion();
182
+ const headers = {
183
+ "x-sdk-version": `${SDK_ID}/${sdkVersion}`
184
+ };
185
+ if (isNodeRuntime()) {
186
+ headers["user-agent"] = `${SDK_ID}/${sdkVersion} (${resolveRuntimeToken()})`;
187
+ }
188
+ return headers;
189
+ }
190
+ function buildWebSocketTrackingHeaders() {
191
+ if (!isNodeRuntime()) {
192
+ return {};
193
+ }
194
+ return buildSdkTrackingHeaders();
195
+ }
196
+
158
197
  // src/api/errors.ts
159
198
  var APIError = class _APIError extends Error {
160
199
  /**
@@ -227,6 +266,19 @@ var ValidationError = class _ValidationError extends APIError {
227
266
  }
228
267
  };
229
268
 
269
+ // src/api/hmac.ts
270
+ import { createHmac } from "crypto";
271
+ function buildHMACMessage(timestamp, method, path, body) {
272
+ return `${timestamp}
273
+ ${method.toUpperCase()}
274
+ ${path}
275
+ ${body}`;
276
+ }
277
+ function computeHMACSignature(secret, timestamp, method, path, body) {
278
+ const key = Buffer.from(secret, "base64");
279
+ return createHmac("sha256", key).update(buildHMACMessage(timestamp, method, path, body)).digest("base64");
280
+ }
281
+
230
282
  // src/api/http.ts
231
283
  var HttpClient = class {
232
284
  /**
@@ -236,10 +288,14 @@ var HttpClient = class {
236
288
  */
237
289
  constructor(config = {}) {
238
290
  this.apiKey = config.apiKey || process.env.LIMITLESS_API_KEY;
291
+ this.hmacCredentials = config.hmacCredentials ? {
292
+ tokenId: config.hmacCredentials.tokenId,
293
+ secret: config.hmacCredentials.secret
294
+ } : void 0;
239
295
  this.logger = config.logger || new NoOpLogger();
240
- if (!this.apiKey) {
296
+ if (!this.apiKey && !this.hmacCredentials) {
241
297
  this.logger.warn(
242
- "API key not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable or pass apiKey parameter."
298
+ "Authentication not set. Authenticated endpoints will fail. Set LIMITLESS_API_KEY environment variable, pass apiKey, or configure hmacCredentials."
243
299
  );
244
300
  }
245
301
  const keepAlive = config.keepAlive !== false;
@@ -266,6 +322,7 @@ var HttpClient = class {
266
322
  headers: {
267
323
  "Content-Type": "application/json",
268
324
  Accept: "application/json",
325
+ ...buildSdkTrackingHeaders(),
269
326
  ...config.additionalHeaders
270
327
  }
271
328
  });
@@ -283,21 +340,42 @@ var HttpClient = class {
283
340
  */
284
341
  setupInterceptors() {
285
342
  this.client.interceptors.request.use(
286
- (config) => {
287
- if (this.apiKey) {
288
- config.headers["X-API-Key"] = this.apiKey;
343
+ (rawConfig) => {
344
+ const config = rawConfig;
345
+ const headers = config.headers || (config.headers = {});
346
+ const identityToken = config.identityToken;
347
+ delete headers["X-API-Key"];
348
+ delete headers["lmts-api-key"];
349
+ delete headers["lmts-timestamp"];
350
+ delete headers["lmts-signature"];
351
+ delete headers.identity;
352
+ if (identityToken) {
353
+ headers.identity = `Bearer ${identityToken}`;
354
+ } else if (this.hmacCredentials) {
355
+ const requestPath = this.getRequestPath(config);
356
+ const requestBody = this.getRequestBodyForSignature(config.data);
357
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
358
+ const signature = computeHMACSignature(
359
+ this.hmacCredentials.secret,
360
+ timestamp,
361
+ config.method || "GET",
362
+ requestPath,
363
+ requestBody
364
+ );
365
+ headers["lmts-api-key"] = this.hmacCredentials.tokenId;
366
+ headers["lmts-timestamp"] = timestamp;
367
+ headers["lmts-signature"] = signature;
368
+ } else if (this.apiKey) {
369
+ headers["X-API-Key"] = this.apiKey;
289
370
  }
290
371
  const fullUrl = `${config.baseURL || ""}${config.url || ""}`;
291
372
  const method = config.method?.toUpperCase() || "GET";
292
- const logHeaders = { ...config.headers };
293
- if (logHeaders["X-API-Key"]) {
294
- logHeaders["X-API-Key"] = "***";
295
- }
373
+ const logHeaders = this.maskSensitiveHeaders(headers);
296
374
  this.logger.debug(`\u2192 ${method} ${fullUrl}`, {
297
375
  headers: logHeaders,
298
376
  body: config.data
299
377
  });
300
- return config;
378
+ return rawConfig;
301
379
  },
302
380
  (error) => Promise.reject(error)
303
381
  );
@@ -396,12 +474,86 @@ var HttpClient = class {
396
474
  setApiKey(apiKey) {
397
475
  this.apiKey = apiKey;
398
476
  }
477
+ /**
478
+ * Returns the configured API key, if any.
479
+ */
480
+ getApiKey() {
481
+ return this.apiKey;
482
+ }
399
483
  /**
400
484
  * Clears the API key.
401
485
  */
402
486
  clearApiKey() {
403
487
  this.apiKey = void 0;
404
488
  }
489
+ /**
490
+ * Sets HMAC credentials for scoped API-token authentication.
491
+ */
492
+ setHMACCredentials(credentials) {
493
+ this.hmacCredentials = {
494
+ tokenId: credentials.tokenId,
495
+ secret: credentials.secret
496
+ };
497
+ }
498
+ /**
499
+ * Clears HMAC credentials.
500
+ */
501
+ clearHMACCredentials() {
502
+ this.hmacCredentials = void 0;
503
+ }
504
+ /**
505
+ * Returns a copy of the configured HMAC credentials, if any.
506
+ */
507
+ getHMACCredentials() {
508
+ if (!this.hmacCredentials) {
509
+ return void 0;
510
+ }
511
+ return {
512
+ tokenId: this.hmacCredentials.tokenId,
513
+ secret: this.hmacCredentials.secret
514
+ };
515
+ }
516
+ /**
517
+ * Returns the logger attached to this HTTP client.
518
+ */
519
+ getLogger() {
520
+ return this.logger;
521
+ }
522
+ /**
523
+ * Returns true when cookie/header-based auth is configured on the underlying client.
524
+ * This is primarily used for custom authenticated flows that don't use API keys or HMAC.
525
+ */
526
+ hasConfiguredHeaderAuth() {
527
+ const defaultHeaders = this.client.defaults.headers;
528
+ const candidates = [defaultHeaders?.common, defaultHeaders];
529
+ for (const headers of candidates) {
530
+ if (!headers) {
531
+ continue;
532
+ }
533
+ const authValues = [
534
+ headers.Cookie,
535
+ headers.cookie,
536
+ headers.Authorization,
537
+ headers.authorization,
538
+ headers.identity
539
+ ];
540
+ if (authValues.some((value) => typeof value === "string" && value.trim() !== "")) {
541
+ return true;
542
+ }
543
+ }
544
+ return false;
545
+ }
546
+ /**
547
+ * Ensures the client has some authenticated transport configured.
548
+ */
549
+ requireAuth(operation) {
550
+ if (this.apiKey || this.hmacCredentials || this.hasConfiguredHeaderAuth()) {
551
+ return;
552
+ }
553
+ throw new Error(
554
+ `Authentication is required for ${operation}; pass apiKey, hmacCredentials, cookie/auth headers, or set LIMITLESS_API_KEY.`
555
+ );
556
+ }
405
557
  /**
406
558
  * Performs a GET request.
407
559
  *
@@ -413,6 +565,16 @@ var HttpClient = class {
413
565
  const response = await this.client.get(url, config);
414
566
  return response.data;
415
567
  }
568
+ /**
569
+ * Performs a GET request with identity-token authentication.
570
+ */
571
+ async getWithIdentity(url, identityToken, config) {
572
+ const response = await this.client.get(url, {
573
+ ...config,
574
+ identityToken
575
+ });
576
+ return response.data;
577
+ }
416
578
  /**
417
579
  * Performs a GET request and returns raw response metadata.
418
580
  *
@@ -447,6 +609,36 @@ var HttpClient = class {
447
609
  const response = await this.client.post(url, data, config);
448
610
  return response.data;
449
611
  }
612
+ /**
613
+ * Performs a POST request with identity-token authentication.
614
+ */
615
+ async postWithIdentity(url, identityToken, data, config) {
616
+ const response = await this.client.post(url, data, {
617
+ ...config,
618
+ identityToken
619
+ });
620
+ return response.data;
621
+ }
622
+ /**
623
+ * Performs a POST request with additional per-request headers.
624
+ */
625
+ async postWithHeaders(url, data, headers, config) {
626
+ const response = await this.client.post(url, data, {
627
+ ...config,
628
+ headers: {
629
+ ...config?.headers || {},
630
+ ...headers || {}
631
+ }
632
+ });
633
+ return response.data;
634
+ }
635
+ /**
636
+ * Performs a PATCH request.
637
+ */
638
+ async patch(url, data, config) {
639
+ const response = await this.client.patch(url, data, config);
640
+ return response.data;
641
+ }
450
642
  /**
451
643
  * Performs a DELETE request.
452
644
  *
@@ -469,6 +661,28 @@ var HttpClient = class {
469
661
  const response = await this.client.delete(url, deleteConfig);
470
662
  return response.data;
471
663
  }
664
+ getRequestPath(config) {
665
+ const resolved = new URL(config.url || "", config.baseURL || this.client.defaults.baseURL || DEFAULT_API_URL);
666
+ return `${resolved.pathname}${resolved.search}`;
667
+ }
668
+ getRequestBodyForSignature(data) {
669
+ if (data === void 0 || data === null || data === "") {
670
+ return "";
671
+ }
672
+ if (typeof data === "string") {
673
+ return data;
674
+ }
675
+ return JSON.stringify(data);
676
+ }
677
+ maskSensitiveHeaders(headers) {
678
+ const masked = { ...headers };
679
+ for (const key of ["X-API-Key", "lmts-api-key", "lmts-timestamp", "lmts-signature", "identity"]) {
680
+ if (masked[key] !== void 0) {
681
+ masked[key] = key === "identity" ? "Bearer ***" : "***";
682
+ }
683
+ }
684
+ return masked;
685
+ }
472
686
  };
473
687
 
474
688
  // src/api/retry.ts
@@ -652,6 +866,75 @@ var RetryableClient = class {
652
866
  }
653
867
  };
654
868
 
869
+ // src/api-tokens/service.ts
870
+ var ApiTokenService = class {
871
+ constructor(httpClient, logger) {
872
+ this.httpClient = httpClient;
873
+ this.logger = logger || new NoOpLogger();
874
+ }
875
+ async deriveToken(identityToken, input) {
876
+ if (!identityToken) {
877
+ throw new Error("Identity token is required for deriveToken");
878
+ }
879
+ this.logger.debug("Deriving API token", { scopes: input.scopes, label: input.label });
880
+ return this.httpClient.postWithIdentity("/auth/api-tokens/derive", identityToken, input);
881
+ }
882
+ async listTokens() {
883
+ this.httpClient.requireAuth("listTokens");
884
+ return this.httpClient.get("/auth/api-tokens");
885
+ }
886
+ async getCapabilities(identityToken) {
887
+ if (!identityToken) {
888
+ throw new Error("Identity token is required for getCapabilities");
889
+ }
890
+ return this.httpClient.getWithIdentity("/auth/api-tokens/capabilities", identityToken);
891
+ }
892
+ async revokeToken(tokenId) {
893
+ this.httpClient.requireAuth("revokeToken");
894
+ const response = await this.httpClient.delete(`/auth/api-tokens/${encodeURIComponent(tokenId)}`);
895
+ return response.message;
896
+ }
897
+ };
898
+
899
+ // src/partner-accounts/service.ts
900
+ var _PartnerAccountService = class _PartnerAccountService {
901
+ constructor(httpClient, logger) {
902
+ this.httpClient = httpClient;
903
+ this.logger = logger || new NoOpLogger();
904
+ }
905
+ async createAccount(input, eoaHeaders) {
906
+ this.httpClient.requireAuth("createPartnerAccount");
907
+ const serverWalletMode = input.createServerWallet === true;
908
+ if (!serverWalletMode && !eoaHeaders) {
909
+ throw new Error("EOA headers are required when createServerWallet is not true");
910
+ }
911
+ if (input.displayName && input.displayName.length > _PartnerAccountService.DISPLAY_NAME_MAX_LENGTH) {
912
+ throw new Error(
913
+ `displayName must be at most ${_PartnerAccountService.DISPLAY_NAME_MAX_LENGTH} characters`
914
+ );
915
+ }
916
+ this.logger.debug("Creating partner account", {
917
+ displayName: input.displayName,
918
+ createServerWallet: input.createServerWallet
919
+ });
920
+ const payload = {
921
+ displayName: input.displayName,
922
+ createServerWallet: input.createServerWallet
923
+ };
924
+ return this.httpClient.postWithHeaders(
925
+ "/profiles/partner-accounts",
926
+ payload,
927
+ eoaHeaders ? {
928
+ "x-account": eoaHeaders.account,
929
+ "x-signing-message": eoaHeaders.signingMessage,
930
+ "x-signature": eoaHeaders.signature
931
+ } : void 0
932
+ );
933
+ }
934
+ };
935
+ _PartnerAccountService.DISPLAY_NAME_MAX_LENGTH = 44;
936
+ var PartnerAccountService = _PartnerAccountService;
937
+
655
938
  // src/orders/builder.ts
656
939
  import { ethers } from "ethers";
657
940
  var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
@@ -940,6 +1223,104 @@ var OrderBuilder = class {
940
1223
  }
941
1224
  };
942
1225
 
1226
+ // src/delegated-orders/service.ts
1227
+ var DEFAULT_DELEGATED_FEE_RATE_BPS = 300;
1228
+ var DelegatedOrderService = class {
1229
+ constructor(httpClient, logger) {
1230
+ this.httpClient = httpClient;
1231
+ this.logger = logger || new NoOpLogger();
1232
+ }
1233
+ async createOrder(params) {
1234
+ this.httpClient.requireAuth("createDelegatedOrder");
1235
+ if (!Number.isInteger(params.onBehalfOf) || params.onBehalfOf <= 0) {
1236
+ throw new Error("onBehalfOf must be a positive integer");
1237
+ }
1238
+ const feeRateBps = params.feeRateBps && params.feeRateBps > 0 ? params.feeRateBps : DEFAULT_DELEGATED_FEE_RATE_BPS;
1239
+ const builder = new OrderBuilder(ZERO_ADDRESS, feeRateBps);
1240
+ const unsignedOrder = builder.buildOrder(params.args);
1241
+ const payload = {
1242
+ order: {
1243
+ salt: unsignedOrder.salt,
1244
+ maker: unsignedOrder.maker,
1245
+ signer: unsignedOrder.signer,
1246
+ taker: unsignedOrder.taker,
1247
+ tokenId: unsignedOrder.tokenId,
1248
+ makerAmount: unsignedOrder.makerAmount,
1249
+ takerAmount: unsignedOrder.takerAmount,
1250
+ expiration: unsignedOrder.expiration,
1251
+ nonce: unsignedOrder.nonce,
1252
+ feeRateBps: unsignedOrder.feeRateBps,
1253
+ side: unsignedOrder.side,
1254
+ signatureType: 0 /* EOA */,
1255
+ ...unsignedOrder.price !== void 0 ? { price: unsignedOrder.price } : {}
1256
+ },
1257
+ orderType: params.orderType,
1258
+ marketSlug: params.marketSlug,
1259
+ ownerId: params.onBehalfOf,
1260
+ onBehalfOf: params.onBehalfOf
1261
+ };
1262
+ this.logger.debug("Creating delegated order", {
1263
+ marketSlug: params.marketSlug,
1264
+ onBehalfOf: params.onBehalfOf,
1265
+ feeRateBps
1266
+ });
1267
+ return this.httpClient.post("/orders", payload);
1268
+ }
1269
+ async cancel(orderId) {
1270
+ this.httpClient.requireAuth("cancelDelegatedOrder");
1271
+ const response = await this.httpClient.delete(`/orders/${encodeURIComponent(orderId)}`);
1272
+ return response.message;
1273
+ }
1274
+ async cancelOnBehalfOf(orderId, onBehalfOf) {
1275
+ this.httpClient.requireAuth("cancelDelegatedOrder");
1276
+ if (!Number.isInteger(onBehalfOf) || onBehalfOf <= 0) {
1277
+ throw new Error("onBehalfOf must be a positive integer");
1278
+ }
1279
+ const response = await this.httpClient.delete(
1280
+ `/orders/${encodeURIComponent(orderId)}?onBehalfOf=${onBehalfOf}`
1281
+ );
1282
+ return response.message;
1283
+ }
1284
+ async cancelAll(marketSlug) {
1285
+ this.httpClient.requireAuth("cancelAllDelegatedOrders");
1286
+ const response = await this.httpClient.delete(`/orders/all/${encodeURIComponent(marketSlug)}`);
1287
+ return response.message;
1288
+ }
1289
+ async cancelAllOnBehalfOf(marketSlug, onBehalfOf) {
1290
+ this.httpClient.requireAuth("cancelAllDelegatedOrders");
1291
+ if (!Number.isInteger(onBehalfOf) || onBehalfOf <= 0) {
1292
+ throw new Error("onBehalfOf must be a positive integer");
1293
+ }
1294
+ const response = await this.httpClient.delete(
1295
+ `/orders/all/${encodeURIComponent(marketSlug)}?onBehalfOf=${onBehalfOf}`
1296
+ );
1297
+ return response.message;
1298
+ }
1299
+ };
1300
+
1301
+ // src/utils/number-flex.ts
1302
+ function toFiniteNumber(value) {
1303
+ if (typeof value === "number") {
1304
+ return Number.isFinite(value) ? value : void 0;
1305
+ }
1306
+ if (typeof value === "string") {
1307
+ const trimmed = value.trim();
1308
+ if (trimmed.length === 0) {
1309
+ return void 0;
1310
+ }
1311
+ const parsed = Number(trimmed);
1312
+ return Number.isFinite(parsed) ? parsed : void 0;
1313
+ }
1314
+ return void 0;
1315
+ }
1316
+ function toFiniteInteger(value) {
1317
+ const parsed = toFiniteNumber(value);
1318
+ if (parsed === void 0) {
1319
+ return void 0;
1320
+ }
1321
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
1322
+ }
1323
+
943
1324
  // src/orders/signer.ts
944
1325
  var OrderSigner = class {
945
1326
  /**
@@ -1603,9 +1984,14 @@ var OrderClient = class {
1603
1984
  });
1604
1985
  const portfolioFetcher = new PortfolioFetcher(this.httpClient);
1605
1986
  const profile = await portfolioFetcher.getProfile(this.wallet.address);
1987
+ const userId = toFiniteInteger(profile.id);
1988
+ const feeRateBps = toFiniteInteger(profile.rank?.feeRateBps) ?? 300;
1989
+ if (userId === void 0) {
1990
+ throw new Error(`Invalid user profile id: ${profile.id}`);
1991
+ }
1606
1992
  this.cachedUserData = {
1607
- userId: profile.id,
1608
- feeRateBps: profile.rank?.feeRateBps || 300
1993
+ userId,
1994
+ feeRateBps
1609
1995
  };
1610
1996
  this.orderBuilder = new OrderBuilder(
1611
1997
  this.wallet.address,
@@ -1717,26 +2103,27 @@ var OrderClient = class {
1717
2103
  * @internal
1718
2104
  */
1719
2105
  transformOrderResponse(apiResponse) {
2106
+ const order = apiResponse.order;
1720
2107
  const cleanOrder = {
1721
2108
  order: {
1722
- id: apiResponse.order.id,
1723
- createdAt: apiResponse.order.createdAt,
1724
- makerAmount: apiResponse.order.makerAmount,
1725
- takerAmount: apiResponse.order.takerAmount,
1726
- expiration: apiResponse.order.expiration,
1727
- signatureType: apiResponse.order.signatureType,
1728
- salt: apiResponse.order.salt,
1729
- maker: apiResponse.order.maker,
1730
- signer: apiResponse.order.signer,
1731
- taker: apiResponse.order.taker,
1732
- tokenId: apiResponse.order.tokenId,
1733
- side: apiResponse.order.side,
1734
- feeRateBps: apiResponse.order.feeRateBps,
1735
- nonce: apiResponse.order.nonce,
1736
- signature: apiResponse.order.signature,
1737
- orderType: apiResponse.order.orderType,
1738
- price: apiResponse.order.price,
1739
- marketId: apiResponse.order.marketId
2109
+ id: order.id,
2110
+ createdAt: order.createdAt,
2111
+ makerAmount: toFiniteNumber(order.makerAmount) ?? order.makerAmount,
2112
+ takerAmount: toFiniteNumber(order.takerAmount) ?? order.takerAmount,
2113
+ expiration: order.expiration,
2114
+ signatureType: order.signatureType,
2115
+ salt: toFiniteInteger(order.salt) ?? order.salt,
2116
+ maker: order.maker,
2117
+ signer: order.signer,
2118
+ taker: order.taker,
2119
+ tokenId: order.tokenId,
2120
+ side: order.side,
2121
+ feeRateBps: order.feeRateBps,
2122
+ nonce: order.nonce,
2123
+ signature: order.signature,
2124
+ orderType: order.orderType,
2125
+ price: order.price === void 0 || order.price === null ? order.price : toFiniteNumber(order.price) ?? order.price,
2126
+ marketId: order.marketId
1740
2127
  }
1741
2128
  };
1742
2129
  if (apiResponse.makerMatches && apiResponse.makerMatches.length > 0) {
@@ -2055,6 +2442,7 @@ var WebSocketClient = class {
2055
2442
  this.config = {
2056
2443
  url: config.url || DEFAULT_WS_URL,
2057
2444
  apiKey: config.apiKey || process.env.LIMITLESS_API_KEY || "",
2445
+ hmacCredentials: config.hmacCredentials,
2058
2446
  autoReconnect: config.autoReconnect ?? true,
2059
2447
  reconnectDelay: config.reconnectDelay || 1e3,
2060
2448
  maxReconnectAttempts: config.maxReconnectAttempts || Infinity,
@@ -2094,6 +2482,32 @@ var WebSocketClient = class {
2094
2482
  this.reconnectWithNewAuth();
2095
2483
  }
2096
2484
  }
2485
+ /**
2486
+ * Sets HMAC credentials for authenticated subscriptions.
2487
+ *
2488
+ * @remarks
2489
+ * When configured alongside `apiKey`, this client uses HMAC headers for authenticated subscriptions.
2490
+ */
2491
+ setHMACCredentials(hmacCredentials) {
2492
+ this.config.hmacCredentials = {
2493
+ tokenId: hmacCredentials.tokenId,
2494
+ secret: hmacCredentials.secret
2495
+ };
2496
+ if (this.socket?.connected) {
2497
+ this.logger.info("HMAC credentials updated, reconnecting...");
2498
+ this.reconnectWithNewAuth();
2499
+ }
2500
+ }
2501
+ /**
2502
+ * Clears HMAC credentials.
2503
+ */
2504
+ clearHMACCredentials() {
2505
+ this.config.hmacCredentials = void 0;
2506
+ if (this.socket?.connected) {
2507
+ this.logger.info("HMAC credentials cleared, reconnecting...");
2508
+ this.reconnectWithNewAuth();
2509
+ }
2510
+ }
2097
2511
  /**
2098
2512
  * Reconnects with new authentication credentials.
2099
2513
  * @internal
@@ -2139,10 +2553,29 @@ var WebSocketClient = class {
2139
2553
  // Add jitter to prevent thundering herd
2140
2554
  timeout: this.config.timeout
2141
2555
  };
2142
- if (this.config.apiKey) {
2556
+ const extraHeaders = buildWebSocketTrackingHeaders();
2557
+ if (this.config.hmacCredentials) {
2558
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2559
+ const signature = computeHMACSignature(
2560
+ this.config.hmacCredentials.secret,
2561
+ timestamp,
2562
+ "GET",
2563
+ "/socket.io/?EIO=4&transport=websocket",
2564
+ ""
2565
+ );
2143
2566
  socketOptions.extraHeaders = {
2567
+ ...extraHeaders,
2568
+ "lmts-api-key": this.config.hmacCredentials.tokenId,
2569
+ "lmts-timestamp": timestamp,
2570
+ "lmts-signature": signature
2571
+ };
2572
+ } else if (this.config.apiKey) {
2573
+ socketOptions.extraHeaders = {
2574
+ ...extraHeaders,
2144
2575
  "X-API-Key": this.config.apiKey
2145
2576
  };
2577
+ } else if (Object.keys(extraHeaders).length > 0) {
2578
+ socketOptions.extraHeaders = extraHeaders;
2146
2579
  }
2147
2580
  this.socket = io(wsUrl + "/markets", socketOptions);
2148
2581
  this.attachPendingListeners();
@@ -2211,9 +2644,9 @@ var WebSocketClient = class {
2211
2644
  "subscribe_positions",
2212
2645
  "subscribe_transactions"
2213
2646
  ];
2214
- if (authenticatedChannels.includes(channel) && !this.config.apiKey) {
2647
+ if (authenticatedChannels.includes(channel) && !this.config.apiKey && !this.config.hmacCredentials) {
2215
2648
  throw new Error(
2216
- `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`
2649
+ `Authentication is required for '${channel}' subscription. Please provide either apiKey or hmacCredentials when creating the WebSocket client.`
2217
2650
  );
2218
2651
  }
2219
2652
  const subscriptionKey = this.getSubscriptionKey(channel, options);
@@ -2407,15 +2840,74 @@ var WebSocketClient = class {
2407
2840
  return key.split(":")[0];
2408
2841
  }
2409
2842
  };
2843
+
2844
+ // src/client.ts
2845
+ import { ethers as ethers3 } from "ethers";
2846
+ var Client = class _Client {
2847
+ constructor(config = {}) {
2848
+ this.http = new HttpClient(config);
2849
+ const logger = this.http.getLogger?.() || new NoOpLogger();
2850
+ this.markets = new MarketFetcher(this.http, logger);
2851
+ this.portfolio = new PortfolioFetcher(this.http, logger);
2852
+ this.pages = new MarketPageFetcher(this.http, logger);
2853
+ this.apiTokens = new ApiTokenService(this.http, logger);
2854
+ this.partnerAccounts = new PartnerAccountService(this.http, logger);
2855
+ this.delegatedOrders = new DelegatedOrderService(this.http, logger);
2856
+ }
2857
+ /**
2858
+ * Creates a root client around an existing shared HTTP client.
2859
+ */
2860
+ static fromHttpClient(httpClient) {
2861
+ const client = Object.create(_Client.prototype);
2862
+ const logger = httpClient.getLogger?.() || new NoOpLogger();
2863
+ client.http = httpClient;
2864
+ client.markets = new MarketFetcher(httpClient, logger);
2865
+ client.portfolio = new PortfolioFetcher(httpClient, logger);
2866
+ client.pages = new MarketPageFetcher(httpClient, logger);
2867
+ client.apiTokens = new ApiTokenService(httpClient, logger);
2868
+ client.partnerAccounts = new PartnerAccountService(httpClient, logger);
2869
+ client.delegatedOrders = new DelegatedOrderService(httpClient, logger);
2870
+ return client;
2871
+ }
2872
+ /**
2873
+ * Creates a regular EIP-712 order client reusing the shared transport and market cache.
2874
+ */
2875
+ newOrderClient(walletOrPrivateKey, config = {}) {
2876
+ const wallet = typeof walletOrPrivateKey === "string" ? new ethers3.Wallet(walletOrPrivateKey) : walletOrPrivateKey;
2877
+ return new OrderClient({
2878
+ httpClient: this.http,
2879
+ wallet,
2880
+ marketFetcher: this.markets,
2881
+ logger: this.http.getLogger(),
2882
+ ...config
2883
+ });
2884
+ }
2885
+ /**
2886
+ * Creates a WebSocket client reusing shared auth where possible.
2887
+ */
2888
+ newWebSocketClient(config = {}) {
2889
+ return new WebSocketClient(
2890
+ {
2891
+ apiKey: config.apiKey || this.http.getApiKey(),
2892
+ hmacCredentials: config.hmacCredentials || this.http.getHMACCredentials(),
2893
+ ...config
2894
+ },
2895
+ this.http.getLogger()
2896
+ );
2897
+ }
2898
+ };
2410
2899
  export {
2411
2900
  APIError,
2901
+ ApiTokenService,
2412
2902
  AuthenticationError,
2413
2903
  BASE_SEPOLIA_CHAIN_ID,
2414
2904
  CONTRACT_ADDRESSES,
2905
+ Client,
2415
2906
  ConsoleLogger,
2416
2907
  DEFAULT_API_URL,
2417
2908
  DEFAULT_CHAIN_ID,
2418
2909
  DEFAULT_WS_URL,
2910
+ DelegatedOrderService,
2419
2911
  HttpClient,
2420
2912
  Market,
2421
2913
  MarketFetcher,
@@ -2426,19 +2918,27 @@ export {
2426
2918
  OrderSigner,
2427
2919
  OrderType,
2428
2920
  OrderValidationError,
2921
+ PartnerAccountService,
2429
2922
  PortfolioFetcher,
2430
2923
  RateLimitError,
2431
2924
  RetryConfig,
2432
2925
  RetryableClient,
2433
2926
  SIGNING_MESSAGE_TEMPLATE,
2927
+ ScopeAccountCreation,
2928
+ ScopeDelegatedSigning,
2929
+ ScopeTrading,
2434
2930
  Side,
2435
2931
  SignatureType,
2436
2932
  ValidationError,
2437
2933
  WebSocketClient,
2438
2934
  WebSocketState,
2439
2935
  ZERO_ADDRESS,
2936
+ buildHMACMessage,
2937
+ computeHMACSignature,
2440
2938
  getContractAddress,
2441
2939
  retryOnErrors,
2940
+ toFiniteInteger,
2941
+ toFiniteNumber,
2442
2942
  validateOrderArgs,
2443
2943
  validateSignedOrder,
2444
2944
  validateUnsignedOrder,