@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/README.md +78 -38
- package/dist/index.d.mts +728 -54
- package/dist/index.d.ts +728 -54
- package/dist/index.js +773 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +761 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
"
|
|
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
|
-
(
|
|
356
|
-
|
|
357
|
-
|
|
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 =
|
|
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
|
|
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 "
|
|
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
|
|
1619
|
-
feeRateBps
|
|
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:
|
|
1734
|
-
createdAt:
|
|
1735
|
-
makerAmount:
|
|
1736
|
-
takerAmount:
|
|
1737
|
-
expiration:
|
|
1738
|
-
signatureType:
|
|
1739
|
-
salt:
|
|
1740
|
-
maker:
|
|
1741
|
-
signer:
|
|
1742
|
-
taker:
|
|
1743
|
-
tokenId:
|
|
1744
|
-
side:
|
|
1745
|
-
feeRateBps:
|
|
1746
|
-
nonce:
|
|
1747
|
-
signature:
|
|
1748
|
-
orderType:
|
|
1749
|
-
price:
|
|
1750
|
-
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
|
-
|
|
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
|
-
`
|
|
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,
|