@limitless-exchange/sdk 1.0.3 → 1.0.5

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