@yuants/vendor-hyperliquid 0.9.10 → 0.10.0

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.
Files changed (52) hide show
  1. package/dist/api/client.js +36 -2
  2. package/dist/api/client.js.map +1 -1
  3. package/dist/api/private-api.js +52 -41
  4. package/dist/api/private-api.js.map +1 -1
  5. package/dist/api/public-api.js +22 -11
  6. package/dist/api/public-api.js.map +1 -1
  7. package/dist/api/rate-limit.js +205 -0
  8. package/dist/api/rate-limit.js.map +1 -0
  9. package/dist/api/rate-limit.test.js +129 -0
  10. package/dist/api/rate-limit.test.js.map +1 -0
  11. package/dist/index.js +0 -2
  12. package/dist/index.js.map +1 -1
  13. package/lib/api/client.d.ts.map +1 -1
  14. package/lib/api/client.js +35 -1
  15. package/lib/api/client.js.map +1 -1
  16. package/lib/api/private-api.d.ts +65 -1
  17. package/lib/api/private-api.d.ts.map +1 -1
  18. package/lib/api/private-api.js +59 -43
  19. package/lib/api/private-api.js.map +1 -1
  20. package/lib/api/public-api.d.ts +54 -1
  21. package/lib/api/public-api.d.ts.map +1 -1
  22. package/lib/api/public-api.js +33 -12
  23. package/lib/api/public-api.js.map +1 -1
  24. package/lib/api/rate-limit.d.ts +21 -0
  25. package/lib/api/rate-limit.d.ts.map +1 -0
  26. package/lib/api/rate-limit.js +214 -0
  27. package/lib/api/rate-limit.js.map +1 -0
  28. package/lib/api/rate-limit.test.d.ts +2 -0
  29. package/lib/api/rate-limit.test.d.ts.map +1 -0
  30. package/lib/api/rate-limit.test.js +131 -0
  31. package/lib/api/rate-limit.test.js.map +1 -0
  32. package/lib/index.d.ts +0 -2
  33. package/lib/index.d.ts.map +1 -1
  34. package/lib/index.js +0 -2
  35. package/lib/index.js.map +1 -1
  36. package/package.json +5 -5
  37. package/temp/build/typescript/ts_HOjXkPSo.json +1 -1
  38. package/temp/package-deps.json +10 -10
  39. package/temp/test/jest/haste-map-1a3370ad4952cc1cbecf8fc42c6e9732-7b2990dcc44090c4eba2c52e971c544c-6a2a6e27ca3579da37751287ea19ab4c +0 -0
  40. package/temp/test/jest/perf-cache-1a3370ad4952cc1cbecf8fc42c6e9732-da39a3ee5e6b4b0d3255bfef95601890 +1 -0
  41. package/dist/services/markets/interest-rate.js +0 -61
  42. package/dist/services/markets/interest-rate.js.map +0 -1
  43. package/dist/services/markets/ohlc.js +0 -111
  44. package/dist/services/markets/ohlc.js.map +0 -1
  45. package/lib/services/markets/interest-rate.d.ts +0 -2
  46. package/lib/services/markets/interest-rate.d.ts.map +0 -1
  47. package/lib/services/markets/interest-rate.js +0 -63
  48. package/lib/services/markets/interest-rate.js.map +0 -1
  49. package/lib/services/markets/ohlc.d.ts +0 -2
  50. package/lib/services/markets/ohlc.d.ts.map +0 -1
  51. package/lib/services/markets/ohlc.js +0 -113
  52. package/lib/services/markets/ohlc.js.map +0 -1
@@ -0,0 +1,205 @@
1
+ import { newError, scopeError, tokenBucket } from '@yuants/utils';
2
+ const REST_IP_BUCKET_ID = 'HYPERLIQUID_REST_IP_WEIGHT_1200_PER_MIN';
3
+ const REST_IP_BUCKET_CAPACITY = 1200;
4
+ const REST_IP_WEIGHT_MAX = REST_IP_BUCKET_CAPACITY * 10;
5
+ tokenBucket(REST_IP_BUCKET_ID, {
6
+ capacity: REST_IP_BUCKET_CAPACITY,
7
+ refillInterval: 60000,
8
+ refillAmount: 1200,
9
+ });
10
+ const INFO_TYPE_TO_BASE_WEIGHT = {
11
+ l2Book: 2,
12
+ allMids: 2,
13
+ clearinghouseState: 2,
14
+ orderStatus: 2,
15
+ spotClearinghouseState: 2,
16
+ exchangeStatus: 2,
17
+ userRole: 60,
18
+ };
19
+ const INTERVAL_TO_MS = {
20
+ '1m': 60000,
21
+ '3m': 180000,
22
+ '5m': 300000,
23
+ '15m': 900000,
24
+ '30m': 1800000,
25
+ '1h': 3600000,
26
+ '2h': 7200000,
27
+ '4h': 14400000,
28
+ '8h': 28800000,
29
+ '12h': 43200000,
30
+ '1d': 86400000,
31
+ '3d': 259200000,
32
+ '1w': 604800000,
33
+ '1M': 2592000000,
34
+ };
35
+ const getObject = (value) => {
36
+ if (typeof value !== 'object' || value === null)
37
+ return;
38
+ return value;
39
+ };
40
+ const getString = (value) => {
41
+ if (typeof value !== 'string')
42
+ return;
43
+ return value;
44
+ };
45
+ const getNumber = (value) => {
46
+ if (typeof value !== 'number' || !Number.isFinite(value))
47
+ return;
48
+ return value;
49
+ };
50
+ const getArray = (value) => {
51
+ if (!Array.isArray(value))
52
+ return;
53
+ return value;
54
+ };
55
+ export const getRestRequestContext = (method, path, body) => {
56
+ var _a, _b;
57
+ if (method === 'POST' && path === 'info') {
58
+ const obj = getObject(body);
59
+ const infoType = getString(obj === null || obj === void 0 ? void 0 : obj.type);
60
+ return { method, path, body, kind: 'info', infoType };
61
+ }
62
+ if (method === 'POST' && path === 'exchange') {
63
+ const obj = getObject(body);
64
+ const action = getObject(obj === null || obj === void 0 ? void 0 : obj.action);
65
+ const exchangeActionType = getString(action === null || action === void 0 ? void 0 : action.type);
66
+ const orders = getArray(action === null || action === void 0 ? void 0 : action.orders);
67
+ const cancels = getArray(action === null || action === void 0 ? void 0 : action.cancels);
68
+ const exchangeBatchLength = Math.max(1, (_b = (_a = orders === null || orders === void 0 ? void 0 : orders.length) !== null && _a !== void 0 ? _a : cancels === null || cancels === void 0 ? void 0 : cancels.length) !== null && _b !== void 0 ? _b : 1);
69
+ return { method, path, body, kind: 'exchange', exchangeActionType, exchangeBatchLength };
70
+ }
71
+ if (path.startsWith('explorer')) {
72
+ return { method, path, body, kind: 'explorer' };
73
+ }
74
+ return { method, path, body, kind: 'other' };
75
+ };
76
+ export const getRestBaseWeight = (ctx) => {
77
+ var _a, _b, _c;
78
+ if (ctx.kind === 'exchange') {
79
+ const batchLength = Math.max(1, (_a = ctx.exchangeBatchLength) !== null && _a !== void 0 ? _a : 1);
80
+ return 1 + Math.floor(batchLength / 40);
81
+ }
82
+ if (ctx.kind === 'info') {
83
+ return (_c = INFO_TYPE_TO_BASE_WEIGHT[(_b = ctx.infoType) !== null && _b !== void 0 ? _b : '']) !== null && _c !== void 0 ? _c : 20;
84
+ }
85
+ if (ctx.kind === 'explorer') {
86
+ return 40;
87
+ }
88
+ return 20;
89
+ };
90
+ const estimateCandleSnapshotItems = (ctx) => {
91
+ var _a;
92
+ if (ctx.kind !== 'info' || ctx.infoType !== 'candleSnapshot')
93
+ return 0;
94
+ const bodyObj = getObject(ctx.body);
95
+ const reqObj = getObject(bodyObj === null || bodyObj === void 0 ? void 0 : bodyObj.req);
96
+ const interval = getString(reqObj === null || reqObj === void 0 ? void 0 : reqObj.interval);
97
+ const startTime = getNumber(reqObj === null || reqObj === void 0 ? void 0 : reqObj.startTime);
98
+ const endTime = getNumber(reqObj === null || reqObj === void 0 ? void 0 : reqObj.endTime);
99
+ if (!interval || startTime === undefined || endTime === undefined || endTime <= startTime)
100
+ return 0;
101
+ const intervalMs = (_a = INTERVAL_TO_MS[interval]) !== null && _a !== void 0 ? _a : 0;
102
+ if (!intervalMs)
103
+ return 0;
104
+ // Hyperliquid docs: Only the most recent 5000 candles are available
105
+ const estimated = Math.ceil((endTime - startTime) / intervalMs);
106
+ return Math.min(5000, Math.max(0, estimated));
107
+ };
108
+ const countArrayResponseItems = (response) => {
109
+ var _a;
110
+ const arr = getArray(response);
111
+ return (_a = arr === null || arr === void 0 ? void 0 : arr.length) !== null && _a !== void 0 ? _a : 0;
112
+ };
113
+ const countUserFillsItems = (response) => {
114
+ var _a;
115
+ const obj = getObject(response);
116
+ const fills = getArray(obj === null || obj === void 0 ? void 0 : obj.fills);
117
+ return (_a = fills === null || fills === void 0 ? void 0 : fills.length) !== null && _a !== void 0 ? _a : 0;
118
+ };
119
+ const extraWeighers = [
120
+ {
121
+ match: (ctx) => ctx.kind === 'info' && ctx.infoType === 'candleSnapshot',
122
+ divisor: 60,
123
+ estimateItems: estimateCandleSnapshotItems,
124
+ countItemsFromResponse: countArrayResponseItems,
125
+ },
126
+ {
127
+ match: (ctx) => ctx.kind === 'info' &&
128
+ (ctx.infoType === 'recentTrades' ||
129
+ ctx.infoType === 'historicalOrders' ||
130
+ ctx.infoType === 'userFills' ||
131
+ ctx.infoType === 'userFillsByTime' ||
132
+ ctx.infoType === 'fundingHistory' ||
133
+ ctx.infoType === 'userFunding' ||
134
+ ctx.infoType === 'nonUserFundingUpdates' ||
135
+ ctx.infoType === 'twapHistory' ||
136
+ ctx.infoType === 'userTwapSliceFills' ||
137
+ ctx.infoType === 'userTwapSliceFillsByTime' ||
138
+ ctx.infoType === 'delegatorHistory' ||
139
+ ctx.infoType === 'delegatorRewards' ||
140
+ ctx.infoType === 'validatorStats'),
141
+ divisor: 20,
142
+ countItemsFromResponse: (response) => countUserFillsItems(response) || countArrayResponseItems(response),
143
+ },
144
+ ];
145
+ export const getRestEstimatedExtraWeight = (ctx) => {
146
+ var _a, _b;
147
+ for (const weigher of extraWeighers) {
148
+ if (!weigher.match(ctx))
149
+ continue;
150
+ const items = (_b = (_a = weigher.estimateItems) === null || _a === void 0 ? void 0 : _a.call(weigher, ctx)) !== null && _b !== void 0 ? _b : 0;
151
+ if (items <= 0)
152
+ return 0;
153
+ return Math.ceil(items / weigher.divisor);
154
+ }
155
+ return 0;
156
+ };
157
+ const normalizeRestWeight = (meta, weight) => {
158
+ if (!Number.isFinite(weight)) {
159
+ throw newError('HYPERLIQUID_REST_WEIGHT_INVALID', Object.assign(Object.assign({}, meta), { weight }));
160
+ }
161
+ const normalized = Math.floor(weight);
162
+ if (normalized <= 0) {
163
+ throw newError('HYPERLIQUID_REST_WEIGHT_INVALID', Object.assign(Object.assign({}, meta), { weight: normalized }));
164
+ }
165
+ if (normalized > REST_IP_WEIGHT_MAX) {
166
+ throw newError('HYPERLIQUID_REST_WEIGHT_EXCESSIVE', Object.assign(Object.assign({}, meta), { weight: normalized, maxWeight: REST_IP_WEIGHT_MAX }));
167
+ }
168
+ return normalized;
169
+ };
170
+ export const acquireRestIpWeightSync = (meta, weight) => {
171
+ const normalized = normalizeRestWeight(meta, weight);
172
+ scopeError('HYPERLIQUID_API_RATE_LIMIT', Object.assign(Object.assign({}, meta), { bucketId: REST_IP_BUCKET_ID, weight: normalized }), () => tokenBucket(REST_IP_BUCKET_ID).acquireSync(normalized));
173
+ };
174
+ const acquireRestIpWeight = async (meta, weight) => {
175
+ let remaining = normalizeRestWeight(meta, weight);
176
+ while (remaining > 0) {
177
+ const chunk = Math.min(REST_IP_BUCKET_CAPACITY, remaining);
178
+ await scopeError('HYPERLIQUID_API_RATE_LIMIT', Object.assign(Object.assign({}, meta), { bucketId: REST_IP_BUCKET_ID, weight: chunk, remaining }), () => tokenBucket(REST_IP_BUCKET_ID).acquire(chunk));
179
+ remaining -= chunk;
180
+ }
181
+ };
182
+ export const beforeRestRequest = (meta, ctx) => {
183
+ const baseWeight = getRestBaseWeight(ctx);
184
+ const estimatedExtraWeight = getRestEstimatedExtraWeight(ctx);
185
+ acquireRestIpWeightSync(meta, baseWeight + estimatedExtraWeight);
186
+ return { baseWeight, estimatedExtraWeight };
187
+ };
188
+ export const afterRestResponse = async (meta, ctx, response, estimatedExtraWeight) => {
189
+ var _a, _b;
190
+ for (const weigher of extraWeighers) {
191
+ if (!weigher.match(ctx))
192
+ continue;
193
+ const items = (_b = (_a = weigher.countItemsFromResponse) === null || _a === void 0 ? void 0 : _a.call(weigher, response)) !== null && _b !== void 0 ? _b : 0;
194
+ if (items <= 0)
195
+ return;
196
+ const actualExtraWeight = Math.ceil(items / weigher.divisor);
197
+ const delta = actualExtraWeight - estimatedExtraWeight;
198
+ if (delta > 0) {
199
+ // 不使用 acquireSync:响应后只阻塞等待,不报错
200
+ await acquireRestIpWeight(meta, delta);
201
+ }
202
+ return;
203
+ }
204
+ };
205
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/api/rate-limit.ts"],"names":[],"mappings":"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","sourcesContent":["import { newError, scopeError, tokenBucket } from '@yuants/utils';\n\ntype HttpMethod = 'GET' | 'POST';\n\ntype RestRequestContext = Readonly<{\n method: HttpMethod;\n path: string;\n body?: unknown;\n kind: 'info' | 'exchange' | 'explorer' | 'other';\n infoType?: string;\n exchangeActionType?: string;\n exchangeBatchLength?: number;\n}>;\n\ntype ExtraWeigher = Readonly<{\n match: (ctx: RestRequestContext) => boolean;\n divisor: 20 | 60;\n estimateItems?: (ctx: RestRequestContext) => number;\n countItemsFromResponse?: (response: unknown) => number;\n}>;\n\nconst REST_IP_BUCKET_ID = 'HYPERLIQUID_REST_IP_WEIGHT_1200_PER_MIN';\nconst REST_IP_BUCKET_CAPACITY = 1200;\nconst REST_IP_WEIGHT_MAX = REST_IP_BUCKET_CAPACITY * 10;\n\ntokenBucket(REST_IP_BUCKET_ID, {\n capacity: REST_IP_BUCKET_CAPACITY,\n refillInterval: 60_000,\n refillAmount: 1200,\n});\n\nconst INFO_TYPE_TO_BASE_WEIGHT: Readonly<Record<string, number>> = {\n l2Book: 2,\n allMids: 2,\n clearinghouseState: 2,\n orderStatus: 2,\n spotClearinghouseState: 2,\n exchangeStatus: 2,\n userRole: 60,\n};\n\nconst INTERVAL_TO_MS: Readonly<Record<string, number>> = {\n '1m': 60_000,\n '3m': 180_000,\n '5m': 300_000,\n '15m': 900_000,\n '30m': 1_800_000,\n '1h': 3_600_000,\n '2h': 7_200_000,\n '4h': 14_400_000,\n '8h': 28_800_000,\n '12h': 43_200_000,\n '1d': 86_400_000,\n '3d': 259_200_000,\n '1w': 604_800_000,\n '1M': 2_592_000_000,\n};\n\nconst getObject = (value: unknown): Record<string, unknown> | undefined => {\n if (typeof value !== 'object' || value === null) return;\n return value as Record<string, unknown>;\n};\n\nconst getString = (value: unknown): string | undefined => {\n if (typeof value !== 'string') return;\n return value;\n};\n\nconst getNumber = (value: unknown): number | undefined => {\n if (typeof value !== 'number' || !Number.isFinite(value)) return;\n return value;\n};\n\nconst getArray = (value: unknown): unknown[] | undefined => {\n if (!Array.isArray(value)) return;\n return value;\n};\n\nexport const getRestRequestContext = (\n method: HttpMethod,\n path: string,\n body?: unknown,\n): RestRequestContext => {\n if (method === 'POST' && path === 'info') {\n const obj = getObject(body);\n const infoType = getString(obj?.type);\n return { method, path, body, kind: 'info', infoType };\n }\n if (method === 'POST' && path === 'exchange') {\n const obj = getObject(body);\n const action = getObject(obj?.action);\n const exchangeActionType = getString(action?.type);\n const orders = getArray(action?.orders);\n const cancels = getArray(action?.cancels);\n const exchangeBatchLength = Math.max(1, orders?.length ?? cancels?.length ?? 1);\n return { method, path, body, kind: 'exchange', exchangeActionType, exchangeBatchLength };\n }\n if (path.startsWith('explorer')) {\n return { method, path, body, kind: 'explorer' };\n }\n return { method, path, body, kind: 'other' };\n};\n\nexport const getRestBaseWeight = (ctx: RestRequestContext): number => {\n if (ctx.kind === 'exchange') {\n const batchLength = Math.max(1, ctx.exchangeBatchLength ?? 1);\n return 1 + Math.floor(batchLength / 40);\n }\n if (ctx.kind === 'info') {\n return INFO_TYPE_TO_BASE_WEIGHT[ctx.infoType ?? ''] ?? 20;\n }\n if (ctx.kind === 'explorer') {\n return 40;\n }\n return 20;\n};\n\nconst estimateCandleSnapshotItems = (ctx: RestRequestContext): number => {\n if (ctx.kind !== 'info' || ctx.infoType !== 'candleSnapshot') return 0;\n const bodyObj = getObject(ctx.body);\n const reqObj = getObject(bodyObj?.req);\n const interval = getString(reqObj?.interval);\n const startTime = getNumber(reqObj?.startTime);\n const endTime = getNumber(reqObj?.endTime);\n if (!interval || startTime === undefined || endTime === undefined || endTime <= startTime) return 0;\n\n const intervalMs = INTERVAL_TO_MS[interval] ?? 0;\n if (!intervalMs) return 0;\n\n // Hyperliquid docs: Only the most recent 5000 candles are available\n const estimated = Math.ceil((endTime - startTime) / intervalMs);\n return Math.min(5000, Math.max(0, estimated));\n};\n\nconst countArrayResponseItems = (response: unknown): number => {\n const arr = getArray(response);\n return arr?.length ?? 0;\n};\n\nconst countUserFillsItems = (response: unknown): number => {\n const obj = getObject(response);\n const fills = getArray(obj?.fills);\n return fills?.length ?? 0;\n};\n\nconst extraWeighers: ReadonlyArray<ExtraWeigher> = [\n {\n match: (ctx) => ctx.kind === 'info' && ctx.infoType === 'candleSnapshot',\n divisor: 60,\n estimateItems: estimateCandleSnapshotItems,\n countItemsFromResponse: countArrayResponseItems,\n },\n {\n match: (ctx) =>\n ctx.kind === 'info' &&\n (ctx.infoType === 'recentTrades' ||\n ctx.infoType === 'historicalOrders' ||\n ctx.infoType === 'userFills' ||\n ctx.infoType === 'userFillsByTime' ||\n ctx.infoType === 'fundingHistory' ||\n ctx.infoType === 'userFunding' ||\n ctx.infoType === 'nonUserFundingUpdates' ||\n ctx.infoType === 'twapHistory' ||\n ctx.infoType === 'userTwapSliceFills' ||\n ctx.infoType === 'userTwapSliceFillsByTime' ||\n ctx.infoType === 'delegatorHistory' ||\n ctx.infoType === 'delegatorRewards' ||\n ctx.infoType === 'validatorStats'),\n divisor: 20,\n countItemsFromResponse: (response) => countUserFillsItems(response) || countArrayResponseItems(response),\n },\n];\n\nexport const getRestEstimatedExtraWeight = (ctx: RestRequestContext): number => {\n for (const weigher of extraWeighers) {\n if (!weigher.match(ctx)) continue;\n const items = weigher.estimateItems?.(ctx) ?? 0;\n if (items <= 0) return 0;\n return Math.ceil(items / weigher.divisor);\n }\n return 0;\n};\n\nconst normalizeRestWeight = (meta: Record<string, unknown>, weight: number): number => {\n if (!Number.isFinite(weight)) {\n throw newError('HYPERLIQUID_REST_WEIGHT_INVALID', { ...meta, weight });\n }\n const normalized = Math.floor(weight);\n if (normalized <= 0) {\n throw newError('HYPERLIQUID_REST_WEIGHT_INVALID', { ...meta, weight: normalized });\n }\n if (normalized > REST_IP_WEIGHT_MAX) {\n throw newError('HYPERLIQUID_REST_WEIGHT_EXCESSIVE', {\n ...meta,\n weight: normalized,\n maxWeight: REST_IP_WEIGHT_MAX,\n });\n }\n return normalized;\n};\n\nexport const acquireRestIpWeightSync = (meta: Record<string, unknown>, weight: number) => {\n const normalized = normalizeRestWeight(meta, weight);\n scopeError('HYPERLIQUID_API_RATE_LIMIT', { ...meta, bucketId: REST_IP_BUCKET_ID, weight: normalized }, () =>\n tokenBucket(REST_IP_BUCKET_ID).acquireSync(normalized),\n );\n};\n\nconst acquireRestIpWeight = async (meta: Record<string, unknown>, weight: number) => {\n let remaining = normalizeRestWeight(meta, weight);\n while (remaining > 0) {\n const chunk = Math.min(REST_IP_BUCKET_CAPACITY, remaining);\n await scopeError(\n 'HYPERLIQUID_API_RATE_LIMIT',\n { ...meta, bucketId: REST_IP_BUCKET_ID, weight: chunk, remaining },\n () => tokenBucket(REST_IP_BUCKET_ID).acquire(chunk),\n );\n remaining -= chunk;\n }\n};\n\nexport const beforeRestRequest = (\n meta: Record<string, unknown>,\n ctx: RestRequestContext,\n): Readonly<{ baseWeight: number; estimatedExtraWeight: number }> => {\n const baseWeight = getRestBaseWeight(ctx);\n const estimatedExtraWeight = getRestEstimatedExtraWeight(ctx);\n acquireRestIpWeightSync(meta, baseWeight + estimatedExtraWeight);\n return { baseWeight, estimatedExtraWeight };\n};\n\nexport const afterRestResponse = async (\n meta: Record<string, unknown>,\n ctx: RestRequestContext,\n response: unknown,\n estimatedExtraWeight: number,\n) => {\n for (const weigher of extraWeighers) {\n if (!weigher.match(ctx)) continue;\n const items = weigher.countItemsFromResponse?.(response) ?? 0;\n if (items <= 0) return;\n const actualExtraWeight = Math.ceil(items / weigher.divisor);\n const delta = actualExtraWeight - estimatedExtraWeight;\n if (delta > 0) {\n // 不使用 acquireSync:响应后只阻塞等待,不报错\n await acquireRestIpWeight(meta, delta);\n }\n return;\n }\n};\n"]}
@@ -0,0 +1,129 @@
1
+ import { tokenBucket } from '@yuants/utils';
2
+ import { afterRestResponse, beforeRestRequest, getRestBaseWeight, getRestEstimatedExtraWeight, getRestRequestContext, } from './rate-limit';
3
+ describe('rate-limit', () => {
4
+ const bucketId = 'HYPERLIQUID_REST_IP_WEIGHT_1200_PER_MIN';
5
+ const readBucket = () => tokenBucket(bucketId).read();
6
+ afterAll(() => {
7
+ const bucket = tokenBucket(bucketId);
8
+ bucket[Symbol.dispose]();
9
+ });
10
+ it('parses rest request context', () => {
11
+ const infoCtx = getRestRequestContext('POST', 'info', { type: 'allMids' });
12
+ expect(infoCtx.kind).toBe('info');
13
+ expect(infoCtx.infoType).toBe('allMids');
14
+ const exchangeCtx = getRestRequestContext('POST', 'exchange', {
15
+ action: { type: 'order', orders: [{}, {}] },
16
+ });
17
+ expect(exchangeCtx.kind).toBe('exchange');
18
+ expect(exchangeCtx.exchangeActionType).toBe('order');
19
+ expect(exchangeCtx.exchangeBatchLength).toBe(2);
20
+ const explorerCtx = getRestRequestContext('GET', 'explorer/tx/0x', undefined);
21
+ expect(explorerCtx.kind).toBe('explorer');
22
+ });
23
+ it('calculates info base weight', () => {
24
+ const ctx = getRestRequestContext('POST', 'info', { type: 'allMids' });
25
+ expect(getRestBaseWeight(ctx)).toBe(2);
26
+ });
27
+ it('defaults base weight for unknown info type', () => {
28
+ const ctx = getRestRequestContext('POST', 'info', { type: 'unknownType' });
29
+ expect(getRestBaseWeight(ctx)).toBe(20);
30
+ });
31
+ it('assigns base weight for explorer and other requests', () => {
32
+ const explorerCtx = getRestRequestContext('GET', 'explorer/tx/0x', undefined);
33
+ expect(getRestBaseWeight(explorerCtx)).toBe(40);
34
+ const otherCtx = getRestRequestContext('GET', 'status', undefined);
35
+ expect(getRestBaseWeight(otherCtx)).toBe(20);
36
+ });
37
+ it('calculates exchange base weight across batch sizes', () => {
38
+ const cases = [
39
+ { batchLength: 0, expected: 1 },
40
+ { batchLength: 1, expected: 1 },
41
+ { batchLength: 40, expected: 2 },
42
+ { batchLength: 41, expected: 2 },
43
+ { batchLength: 80, expected: 3 },
44
+ ];
45
+ for (const { batchLength, expected } of cases) {
46
+ const ctx = getRestRequestContext('POST', 'exchange', {
47
+ action: { type: 'order', orders: new Array(batchLength).fill({}) },
48
+ });
49
+ expect(getRestBaseWeight(ctx)).toBe(expected);
50
+ }
51
+ });
52
+ it('estimates candleSnapshot extra weight with 5000 cap', () => {
53
+ const intervalMs = 60000;
54
+ const candles = 10000;
55
+ const ctx = getRestRequestContext('POST', 'info', {
56
+ type: 'candleSnapshot',
57
+ req: { interval: '1m', startTime: 0, endTime: candles * intervalMs },
58
+ });
59
+ expect(getRestEstimatedExtraWeight(ctx)).toBe(Math.ceil(5000 / 60));
60
+ });
61
+ it('returns zero extra weight for invalid candleSnapshot inputs', () => {
62
+ const invalidIntervalCtx = getRestRequestContext('POST', 'info', {
63
+ type: 'candleSnapshot',
64
+ req: { interval: '2m', startTime: 0, endTime: 60000 },
65
+ });
66
+ expect(getRestEstimatedExtraWeight(invalidIntervalCtx)).toBe(0);
67
+ const sameTimeCtx = getRestRequestContext('POST', 'info', {
68
+ type: 'candleSnapshot',
69
+ req: { interval: '1m', startTime: 0, endTime: 0 },
70
+ });
71
+ expect(getRestEstimatedExtraWeight(sameTimeCtx)).toBe(0);
72
+ const missingReqCtx = getRestRequestContext('POST', 'info', { type: 'candleSnapshot' });
73
+ expect(getRestEstimatedExtraWeight(missingReqCtx)).toBe(0);
74
+ });
75
+ it('consumes base and estimated weight before request', () => {
76
+ const ctx = getRestRequestContext('POST', 'info', {
77
+ type: 'candleSnapshot',
78
+ req: { interval: '1m', startTime: 0, endTime: 120 * 60000 },
79
+ });
80
+ const before = readBucket();
81
+ const { baseWeight, estimatedExtraWeight } = beforeRestRequest({ requestKey: 'test' }, ctx);
82
+ const after = readBucket();
83
+ expect(baseWeight).toBe(20);
84
+ expect(estimatedExtraWeight).toBe(2);
85
+ expect(after).toBe(before - baseWeight - estimatedExtraWeight);
86
+ });
87
+ it('throws on invalid or excessive weights before request', () => {
88
+ const invalidCtx = {
89
+ method: 'POST',
90
+ path: 'exchange',
91
+ kind: 'exchange',
92
+ exchangeBatchLength: Number.NaN,
93
+ };
94
+ expect(() => beforeRestRequest({ requestKey: 'test' }, invalidCtx)).toThrow(/HYPERLIQUID_REST_WEIGHT_INVALID/);
95
+ const maxWeight = 1200 * 10;
96
+ const excessiveCtx = {
97
+ method: 'POST',
98
+ path: 'exchange',
99
+ kind: 'exchange',
100
+ exchangeBatchLength: (maxWeight + 1) * 40,
101
+ };
102
+ expect(() => beforeRestRequest({ requestKey: 'test' }, excessiveCtx)).toThrow(/HYPERLIQUID_REST_WEIGHT_EXCESSIVE/);
103
+ });
104
+ it('applies extra weight based on response size', async () => {
105
+ const ctx = getRestRequestContext('POST', 'info', { type: 'userFills' });
106
+ const response = { fills: new Array(41).fill({}) };
107
+ const before = readBucket();
108
+ await afterRestResponse({ requestKey: 'test' }, ctx, response, 0);
109
+ const after = readBucket();
110
+ expect(before - after).toBe(Math.ceil(41 / 20));
111
+ });
112
+ it('applies delta extra weight for candleSnapshot responses', async () => {
113
+ const ctx = getRestRequestContext('POST', 'info', { type: 'candleSnapshot' });
114
+ const response = new Array(120).fill({});
115
+ const before = readBucket();
116
+ await afterRestResponse({ requestKey: 'test' }, ctx, response, 1);
117
+ const after = readBucket();
118
+ expect(before - after).toBe(1);
119
+ });
120
+ it('skips extra acquire when response weight is within estimate', async () => {
121
+ const ctx = getRestRequestContext('POST', 'info', { type: 'userFills' });
122
+ const response = { fills: [{}] };
123
+ const before = readBucket();
124
+ await afterRestResponse({ requestKey: 'test' }, ctx, response, 1);
125
+ const after = readBucket();
126
+ expect(after).toBe(before);
127
+ });
128
+ });
129
+ //# sourceMappingURL=rate-limit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../src/api/rate-limit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,cAAc,CAAC;AAEtB,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,MAAM,QAAQ,GAAG,yCAAyC,CAAC;IAC3D,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAEtD,QAAQ,CAAC,GAAG,EAAE;QACZ,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEzC,MAAM,WAAW,GAAG,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE;YAC5D,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE;SAC5C,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEhD,MAAM,WAAW,GAAG,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC;QAC9E,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,WAAW,GAAG,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC;QAC9E,MAAM,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEhD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QACnE,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG;YACZ,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;YAC/B,EAAE,WAAW,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;YAC/B,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;YAChC,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;YAChC,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;SACjC,CAAC;QAEF,KAAK,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,KAAK,EAAE,CAAC;YAC9C,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE;gBACpD,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;aACnE,CAAC,CAAC;YACH,MAAM,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,UAAU,GAAG,KAAM,CAAC;QAC1B,MAAM,OAAO,GAAG,KAAM,CAAC;QACvB,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE;YAChD,IAAI,EAAE,gBAAgB;YACtB,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,EAAE;SACrE,CAAC,CAAC;QACH,MAAM,CAAC,2BAA2B,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE;YAC/D,IAAI,EAAE,gBAAgB;YACtB,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAM,EAAE;SACvD,CAAC,CAAC;QACH,MAAM,CAAC,2BAA2B,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEhE,MAAM,WAAW,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE;YACxD,IAAI,EAAE,gBAAgB;YACtB,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;SAClD,CAAC,CAAC;QACH,MAAM,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEzD,MAAM,aAAa,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACxF,MAAM,CAAC,2BAA2B,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE;YAChD,IAAI,EAAE,gBAAgB;YACtB,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,KAAM,EAAE;SAC7D,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,EAAE,UAAU,EAAE,oBAAoB,EAAE,GAAG,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC;QAC5F,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5B,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,UAAU,GAAG,oBAAoB,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,UAAU,GAAG;YACjB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,UAAU;YAChB,mBAAmB,EAAE,MAAM,CAAC,GAAG;SACY,CAAC;QAC9C,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CACzE,iCAAiC,CAClC,CAAC;QAEF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG;YACnB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,UAAU;YAChB,mBAAmB,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,EAAE;SACE,CAAC;QAC9C,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAC3E,mCAAmC,CACpC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAEnD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC9E,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEzC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,GAAG,GAAG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QAEjC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,iBAAiB,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { tokenBucket } from '@yuants/utils';\nimport {\n afterRestResponse,\n beforeRestRequest,\n getRestBaseWeight,\n getRestEstimatedExtraWeight,\n getRestRequestContext,\n} from './rate-limit';\n\ndescribe('rate-limit', () => {\n const bucketId = 'HYPERLIQUID_REST_IP_WEIGHT_1200_PER_MIN';\n const readBucket = () => tokenBucket(bucketId).read();\n\n afterAll(() => {\n const bucket = tokenBucket(bucketId);\n bucket[Symbol.dispose]();\n });\n\n it('parses rest request context', () => {\n const infoCtx = getRestRequestContext('POST', 'info', { type: 'allMids' });\n expect(infoCtx.kind).toBe('info');\n expect(infoCtx.infoType).toBe('allMids');\n\n const exchangeCtx = getRestRequestContext('POST', 'exchange', {\n action: { type: 'order', orders: [{}, {}] },\n });\n expect(exchangeCtx.kind).toBe('exchange');\n expect(exchangeCtx.exchangeActionType).toBe('order');\n expect(exchangeCtx.exchangeBatchLength).toBe(2);\n\n const explorerCtx = getRestRequestContext('GET', 'explorer/tx/0x', undefined);\n expect(explorerCtx.kind).toBe('explorer');\n });\n\n it('calculates info base weight', () => {\n const ctx = getRestRequestContext('POST', 'info', { type: 'allMids' });\n expect(getRestBaseWeight(ctx)).toBe(2);\n });\n\n it('defaults base weight for unknown info type', () => {\n const ctx = getRestRequestContext('POST', 'info', { type: 'unknownType' });\n expect(getRestBaseWeight(ctx)).toBe(20);\n });\n\n it('assigns base weight for explorer and other requests', () => {\n const explorerCtx = getRestRequestContext('GET', 'explorer/tx/0x', undefined);\n expect(getRestBaseWeight(explorerCtx)).toBe(40);\n\n const otherCtx = getRestRequestContext('GET', 'status', undefined);\n expect(getRestBaseWeight(otherCtx)).toBe(20);\n });\n\n it('calculates exchange base weight across batch sizes', () => {\n const cases = [\n { batchLength: 0, expected: 1 },\n { batchLength: 1, expected: 1 },\n { batchLength: 40, expected: 2 },\n { batchLength: 41, expected: 2 },\n { batchLength: 80, expected: 3 },\n ];\n\n for (const { batchLength, expected } of cases) {\n const ctx = getRestRequestContext('POST', 'exchange', {\n action: { type: 'order', orders: new Array(batchLength).fill({}) },\n });\n expect(getRestBaseWeight(ctx)).toBe(expected);\n }\n });\n\n it('estimates candleSnapshot extra weight with 5000 cap', () => {\n const intervalMs = 60_000;\n const candles = 10_000;\n const ctx = getRestRequestContext('POST', 'info', {\n type: 'candleSnapshot',\n req: { interval: '1m', startTime: 0, endTime: candles * intervalMs },\n });\n expect(getRestEstimatedExtraWeight(ctx)).toBe(Math.ceil(5000 / 60));\n });\n\n it('returns zero extra weight for invalid candleSnapshot inputs', () => {\n const invalidIntervalCtx = getRestRequestContext('POST', 'info', {\n type: 'candleSnapshot',\n req: { interval: '2m', startTime: 0, endTime: 60_000 },\n });\n expect(getRestEstimatedExtraWeight(invalidIntervalCtx)).toBe(0);\n\n const sameTimeCtx = getRestRequestContext('POST', 'info', {\n type: 'candleSnapshot',\n req: { interval: '1m', startTime: 0, endTime: 0 },\n });\n expect(getRestEstimatedExtraWeight(sameTimeCtx)).toBe(0);\n\n const missingReqCtx = getRestRequestContext('POST', 'info', { type: 'candleSnapshot' });\n expect(getRestEstimatedExtraWeight(missingReqCtx)).toBe(0);\n });\n\n it('consumes base and estimated weight before request', () => {\n const ctx = getRestRequestContext('POST', 'info', {\n type: 'candleSnapshot',\n req: { interval: '1m', startTime: 0, endTime: 120 * 60_000 },\n });\n const before = readBucket();\n const { baseWeight, estimatedExtraWeight } = beforeRestRequest({ requestKey: 'test' }, ctx);\n const after = readBucket();\n expect(baseWeight).toBe(20);\n expect(estimatedExtraWeight).toBe(2);\n expect(after).toBe(before - baseWeight - estimatedExtraWeight);\n });\n\n it('throws on invalid or excessive weights before request', () => {\n const invalidCtx = {\n method: 'POST',\n path: 'exchange',\n kind: 'exchange',\n exchangeBatchLength: Number.NaN,\n } as ReturnType<typeof getRestRequestContext>;\n expect(() => beforeRestRequest({ requestKey: 'test' }, invalidCtx)).toThrow(\n /HYPERLIQUID_REST_WEIGHT_INVALID/,\n );\n\n const maxWeight = 1200 * 10;\n const excessiveCtx = {\n method: 'POST',\n path: 'exchange',\n kind: 'exchange',\n exchangeBatchLength: (maxWeight + 1) * 40,\n } as ReturnType<typeof getRestRequestContext>;\n expect(() => beforeRestRequest({ requestKey: 'test' }, excessiveCtx)).toThrow(\n /HYPERLIQUID_REST_WEIGHT_EXCESSIVE/,\n );\n });\n\n it('applies extra weight based on response size', async () => {\n const ctx = getRestRequestContext('POST', 'info', { type: 'userFills' });\n const response = { fills: new Array(41).fill({}) };\n\n const before = readBucket();\n await afterRestResponse({ requestKey: 'test' }, ctx, response, 0);\n const after = readBucket();\n expect(before - after).toBe(Math.ceil(41 / 20));\n });\n\n it('applies delta extra weight for candleSnapshot responses', async () => {\n const ctx = getRestRequestContext('POST', 'info', { type: 'candleSnapshot' });\n const response = new Array(120).fill({});\n\n const before = readBucket();\n await afterRestResponse({ requestKey: 'test' }, ctx, response, 1);\n const after = readBucket();\n expect(before - after).toBe(1);\n });\n\n it('skips extra acquire when response weight is within estimate', async () => {\n const ctx = getRestRequestContext('POST', 'info', { type: 'userFills' });\n const response = { fills: [{}] };\n\n const before = readBucket();\n await afterRestResponse({ requestKey: 'test' }, ctx, response, 1);\n const after = readBucket();\n expect(after).toBe(before);\n });\n});\n"]}
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
1
  import './services/exchange';
2
- import './services/markets/interest-rate';
3
- import './services/markets/ohlc';
4
2
  import './services/markets/product';
5
3
  import './services/markets/quote';
6
4
  import './services/quotes';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,OAAO,kCAAkC,CAAC;AAC1C,OAAO,yBAAyB,CAAC;AACjC,OAAO,4BAA4B,CAAC;AACpC,OAAO,0BAA0B,CAAC;AAClC,OAAO,mBAAmB,CAAC;AAC3B,OAAO,yBAAyB,CAAC;AACjC,OAAO,kCAAkC,CAAC","sourcesContent":["import './services/exchange';\nimport './services/markets/interest-rate';\nimport './services/markets/ohlc';\nimport './services/markets/product';\nimport './services/markets/quote';\nimport './services/quotes';\nimport './services/ohlc-service';\nimport './services/interest-rate-service';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAC7B,OAAO,4BAA4B,CAAC;AACpC,OAAO,0BAA0B,CAAC;AAClC,OAAO,mBAAmB,CAAC;AAC3B,OAAO,yBAAyB,CAAC;AACjC,OAAO,kCAAkC,CAAC","sourcesContent":["import './services/exchange';\nimport './services/markets/product';\nimport './services/markets/quote';\nimport './services/quotes';\nimport './services/ohlc-service';\nimport './services/interest-rate-service';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;AA8EjC,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,EAAE,SAAS,GAAG,KAC5C,OAAO,CAAC,CAAC,CAAC,CAAC;AAE9C,eAAO,MAAM,sBAAsB,GAAU,CAAC,GAAG,GAAG,EAClD,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,aAAa;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAC9C,SAAS,GAAG,eAYb,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAIA,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;AA8HjC,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,UAAU,EAAE,MAAM,MAAM,EAAE,SAAS,GAAG,KAC5C,OAAO,CAAC,CAAC,CAAC,CAAC;AAE9C,eAAO,MAAM,sBAAsB,GAAU,CAAC,GAAG,GAAG,EAClD,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,aAAa;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAC9C,SAAS,GAAG,eAYb,CAAC"}
package/lib/api/client.js CHANGED
@@ -3,7 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requestWithFlowControl = exports.request = void 0;
4
4
  const utils_1 = require("@yuants/utils");
5
5
  const rxjs_1 = require("rxjs");
6
+ const rate_limit_1 = require("./rate-limit");
6
7
  const BASE_URL = 'https://api.hyperliquid.xyz';
8
+ const getRequestKey = (ctx) => {
9
+ var _a, _b;
10
+ if (ctx.kind === 'info')
11
+ return `info:${(_a = ctx.infoType) !== null && _a !== void 0 ? _a : 'unknown'}`;
12
+ if (ctx.kind === 'exchange')
13
+ return `exchange:${(_b = ctx.exchangeActionType) !== null && _b !== void 0 ? _b : 'unknown'}`;
14
+ if (ctx.kind === 'explorer')
15
+ return `explorer:${ctx.path}`;
16
+ return `other:${ctx.path}`;
17
+ };
7
18
  const buildUrl = (path, method, params) => {
8
19
  const url = new URL(BASE_URL);
9
20
  url.pathname = path;
@@ -20,17 +31,40 @@ const callApi = async (method, path, params) => {
20
31
  const body = method === 'GET' ? '' : JSON.stringify(params !== null && params !== void 0 ? params : {});
21
32
  const headers = { 'Content-Type': 'application/json' };
22
33
  console.info((0, utils_1.formatTime)(Date.now()), method, url.href, body);
34
+ const requestContext = (0, rate_limit_1.getRestRequestContext)(method, path, params);
35
+ const requestKey = getRequestKey(requestContext);
36
+ const { estimatedExtraWeight } = (0, rate_limit_1.beforeRestRequest)({ method, url: url.href, path, kind: requestContext.kind, infoType: requestContext.infoType, requestKey }, requestContext);
23
37
  const res = await fetch(url.href, {
24
38
  method,
25
39
  headers,
26
40
  body: method === 'GET' ? undefined : body || undefined,
27
41
  });
28
42
  const retStr = await res.text();
43
+ if (res.status === 429) {
44
+ console.info((0, utils_1.formatTime)(Date.now()), 'HyperliquidResponse', method, url.href, `status=${res.status}`, retStr);
45
+ throw (0, utils_1.newError)('HYPERLIQUID_HTTP_429', {
46
+ status: res.status,
47
+ requestKey,
48
+ method,
49
+ path,
50
+ url: url.href,
51
+ response: retStr,
52
+ });
53
+ }
29
54
  try {
30
55
  if (process.env.LOG_LEVEL === 'DEBUG') {
31
56
  console.debug((0, utils_1.formatTime)(Date.now()), 'HyperliquidResponse', path, JSON.stringify(params), retStr);
32
57
  }
33
- return JSON.parse(retStr);
58
+ const response = JSON.parse(retStr);
59
+ await (0, rate_limit_1.afterRestResponse)({
60
+ method,
61
+ url: url.href,
62
+ path,
63
+ kind: requestContext.kind,
64
+ infoType: requestContext.infoType,
65
+ requestKey,
66
+ }, requestContext, response, estimatedExtraWeight);
67
+ return response;
34
68
  }
35
69
  catch (err) {
36
70
  console.error((0, utils_1.formatTime)(Date.now()), 'HyperliquidRequestFailed', path, JSON.stringify(params), retStr, err);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { UUID, formatTime } from '@yuants/utils';\nimport { Subject, filter, firstValueFrom, mergeMap, of, shareReplay, throwError, timeout, timer } from 'rxjs';\n\ntype HttpMethod = 'GET' | 'POST';\n\nconst BASE_URL = 'https://api.hyperliquid.xyz';\n\nconst buildUrl = (path: string, method: HttpMethod, params?: any) => {\n const url = new URL(BASE_URL);\n url.pathname = path;\n if (method === 'GET' && params) {\n const entries = Object.entries(params).sort(([a], [b]) => a.localeCompare(b));\n for (const [key, value] of entries) {\n url.searchParams.set(key, '' + value);\n }\n }\n return url;\n};\n\nconst callApi = async (method: HttpMethod, path: string, params?: any) => {\n const url = buildUrl(path, method, params);\n const body = method === 'GET' ? '' : JSON.stringify(params ?? {});\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n console.info(formatTime(Date.now()), method, url.href, body);\n const res = await fetch(url.href, {\n method,\n headers,\n body: method === 'GET' ? undefined : body || undefined,\n });\n const retStr = await res.text();\n try {\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'HyperliquidResponse', path, JSON.stringify(params), retStr);\n }\n return JSON.parse(retStr);\n } catch (err) {\n console.error(\n formatTime(Date.now()),\n 'HyperliquidRequestFailed',\n path,\n JSON.stringify(params),\n retStr,\n err,\n );\n throw err;\n }\n};\n\ntype FlowController = {\n requestQueue: Array<{ trace_id: string; method: HttpMethod; path: string; params?: any }>;\n responseChannel: Subject<{ trace_id: string; response?: any; error?: Error }>;\n};\n\nconst controllers = new Map<string, FlowController>();\n\nconst ensureController = (path: string, period: number, limit: number) => {\n if (controllers.has(path)) {\n return controllers.get(path)!;\n }\n const controller: FlowController = {\n requestQueue: [],\n responseChannel: new Subject(),\n };\n timer(0, period)\n .pipe(\n filter(() => controller.requestQueue.length > 0),\n mergeMap(() => controller.requestQueue.splice(0, limit)),\n mergeMap(async (request) => {\n try {\n const response = await callApi(request.method, request.path, request.params);\n return { trace_id: request.trace_id, response };\n } catch (error) {\n return { trace_id: request.trace_id, error: error as Error };\n }\n }),\n )\n .subscribe(controller.responseChannel);\n controllers.set(path, controller);\n return controller;\n};\n\nexport const request = <T = any>(method: HttpMethod, path: string, params?: any) =>\n callApi(method, path, params) as Promise<T>;\n\nexport const requestWithFlowControl = async <T = any>(\n method: HttpMethod,\n path: string,\n flowControl: { period: number; limit: number },\n params?: any,\n) => {\n const controller = ensureController(path, flowControl.period, flowControl.limit);\n const trace_id = UUID();\n const res$ = controller.responseChannel.pipe(\n filter((resp) => resp.trace_id === trace_id),\n mergeMap((resp) => (resp.error ? throwError(() => resp.error) : of(resp))),\n timeout(30_000),\n shareReplay(1),\n );\n controller.requestQueue.push({ trace_id, method, path, params });\n return ((await firstValueFrom(res$)).response ?? null) as T;\n};\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { UUID, formatTime, newError } from '@yuants/utils';\nimport { Subject, filter, firstValueFrom, mergeMap, of, shareReplay, throwError, timeout, timer } from 'rxjs';\nimport { afterRestResponse, beforeRestRequest, getRestRequestContext } from './rate-limit';\n\ntype HttpMethod = 'GET' | 'POST';\n\nconst BASE_URL = 'https://api.hyperliquid.xyz';\n\nconst getRequestKey = (ctx: ReturnType<typeof getRestRequestContext>) => {\n if (ctx.kind === 'info') return `info:${ctx.infoType ?? 'unknown'}`;\n if (ctx.kind === 'exchange') return `exchange:${ctx.exchangeActionType ?? 'unknown'}`;\n if (ctx.kind === 'explorer') return `explorer:${ctx.path}`;\n return `other:${ctx.path}`;\n};\n\nconst buildUrl = (path: string, method: HttpMethod, params?: any) => {\n const url = new URL(BASE_URL);\n url.pathname = path;\n if (method === 'GET' && params) {\n const entries = Object.entries(params).sort(([a], [b]) => a.localeCompare(b));\n for (const [key, value] of entries) {\n url.searchParams.set(key, '' + value);\n }\n }\n return url;\n};\n\nconst callApi = async (method: HttpMethod, path: string, params?: any) => {\n const url = buildUrl(path, method, params);\n const body = method === 'GET' ? '' : JSON.stringify(params ?? {});\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n console.info(formatTime(Date.now()), method, url.href, body);\n\n const requestContext = getRestRequestContext(method, path, params);\n const requestKey = getRequestKey(requestContext);\n\n const { estimatedExtraWeight } = beforeRestRequest(\n { method, url: url.href, path, kind: requestContext.kind, infoType: requestContext.infoType, requestKey },\n requestContext,\n );\n\n const res = await fetch(url.href, {\n method,\n headers,\n body: method === 'GET' ? undefined : body || undefined,\n });\n const retStr = await res.text();\n if (res.status === 429) {\n console.info(\n formatTime(Date.now()),\n 'HyperliquidResponse',\n method,\n url.href,\n `status=${res.status}`,\n retStr,\n );\n throw newError('HYPERLIQUID_HTTP_429', {\n status: res.status,\n requestKey,\n method,\n path,\n url: url.href,\n response: retStr,\n });\n }\n try {\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'HyperliquidResponse', path, JSON.stringify(params), retStr);\n }\n const response = JSON.parse(retStr);\n await afterRestResponse(\n {\n method,\n url: url.href,\n path,\n kind: requestContext.kind,\n infoType: requestContext.infoType,\n requestKey,\n },\n requestContext,\n response,\n estimatedExtraWeight,\n );\n return response;\n } catch (err) {\n console.error(\n formatTime(Date.now()),\n 'HyperliquidRequestFailed',\n path,\n JSON.stringify(params),\n retStr,\n err,\n );\n throw err;\n }\n};\n\ntype FlowController = {\n requestQueue: Array<{ trace_id: string; method: HttpMethod; path: string; params?: any }>;\n responseChannel: Subject<{ trace_id: string; response?: any; error?: Error }>;\n};\n\nconst controllers = new Map<string, FlowController>();\n\nconst ensureController = (path: string, period: number, limit: number) => {\n if (controllers.has(path)) {\n return controllers.get(path)!;\n }\n const controller: FlowController = {\n requestQueue: [],\n responseChannel: new Subject(),\n };\n timer(0, period)\n .pipe(\n filter(() => controller.requestQueue.length > 0),\n mergeMap(() => controller.requestQueue.splice(0, limit)),\n mergeMap(async (request) => {\n try {\n const response = await callApi(request.method, request.path, request.params);\n return { trace_id: request.trace_id, response };\n } catch (error) {\n return { trace_id: request.trace_id, error: error as Error };\n }\n }),\n )\n .subscribe(controller.responseChannel);\n controllers.set(path, controller);\n return controller;\n};\n\nexport const request = <T = any>(method: HttpMethod, path: string, params?: any) =>\n callApi(method, path, params) as Promise<T>;\n\nexport const requestWithFlowControl = async <T = any>(\n method: HttpMethod,\n path: string,\n flowControl: { period: number; limit: number },\n params?: any,\n) => {\n const controller = ensureController(path, flowControl.period, flowControl.limit);\n const trace_id = UUID();\n const res$ = controller.responseChannel.pipe(\n filter((resp) => resp.trace_id === trace_id),\n mergeMap((resp) => (resp.error ? throwError(() => resp.error) : of(resp))),\n timeout(30_000),\n shareReplay(1),\n );\n controller.requestQueue.push({ trace_id, method, path, params });\n return ((await firstValueFrom(res$)).response ?? null) as T;\n};\n"]}
@@ -41,6 +41,61 @@ type CancelPayload = {
41
41
  /** Order ID */
42
42
  o: number;
43
43
  };
44
+ type OrderAction = {
45
+ type: 'order';
46
+ orders: OrderPayload[];
47
+ grouping: 'na';
48
+ builder?: {
49
+ b: string;
50
+ f: number;
51
+ };
52
+ };
53
+ type CancelAction = {
54
+ type: 'cancel';
55
+ cancels: CancelPayload[];
56
+ };
57
+ type ModifyAction = {
58
+ type: 'modify';
59
+ oid: number | string;
60
+ order: OrderPayload;
61
+ };
62
+ type ExchangeAction = OrderAction | CancelAction | ModifyAction;
63
+ type L1Signature = {
64
+ r: string;
65
+ s: string;
66
+ v: number;
67
+ };
68
+ export declare const buildPlaceOrderAction: (params: {
69
+ orders: OrderPayload[];
70
+ builder?: {
71
+ b: string;
72
+ f: number;
73
+ };
74
+ }) => OrderAction;
75
+ export declare const buildCancelOrderAction: (params: {
76
+ cancels: CancelPayload[];
77
+ }) => {
78
+ type: "cancel";
79
+ cancels: CancelPayload[];
80
+ };
81
+ export declare const buildModifyOrderAction: (params: {
82
+ oid: number | string;
83
+ order: OrderPayload;
84
+ }) => {
85
+ type: "modify";
86
+ oid: string | number;
87
+ order: OrderPayload;
88
+ };
89
+ export declare const createSignedExchangeRequestBody: (credential: ICredential, action: ExchangeAction, options?: {
90
+ nonce?: number;
91
+ vaultAddress?: string;
92
+ expiresAfter?: number;
93
+ isMainnet?: boolean;
94
+ }) => Promise<{
95
+ action: ExchangeAction;
96
+ nonce: number;
97
+ signature: L1Signature;
98
+ }>;
44
99
  /**
45
100
  * Place orders on Hyperliquid exchange
46
101
  * API Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
@@ -84,6 +139,15 @@ export declare const modifyOrder: (credential: ICredential, params: {
84
139
  vaultAddress?: string;
85
140
  expiresAfter?: number;
86
141
  }) => Promise<any>;
142
+ export declare const buildUserFillsRequestBody: (credential: ICredential, params?: {
143
+ startTime?: number;
144
+ endTime?: number;
145
+ }) => {
146
+ type: "userFills";
147
+ user: string;
148
+ startTime?: number;
149
+ endTime?: number;
150
+ };
87
151
  /**
88
152
  * Get user's fill history (trade history) from Hyperliquid
89
153
  * API Endpoint: POST /info (type: userFills)
@@ -115,7 +179,7 @@ export declare const getUserFills: (credential: ICredential, params?: {
115
179
  positionAction: string;
116
180
  asset: number;
117
181
  tid: string;
118
- crossChain: any;
182
+ crossChain: unknown;
119
183
  }[];
120
184
  }>;
121
185
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"private-api.d.ts","sourceRoot":"","sources":["../../src/api/private-api.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC;;GAEG;AACH,KAAK,YAAY,GAAG;IAClB,6BAA6B;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,8CAA8C;IAC9C,CAAC,EAAE,OAAO,CAAC;IACX,yBAAyB;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,kBAAkB;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,uBAAuB;IACvB,CAAC,EAAE,OAAO,CAAC;IACX,+BAA+B;IAC/B,CAAC,EAAE;QACD,8GAA8G;QAC9G,KAAK,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QACxB,kCAAkC;QAClC,OAAO,CAAC,EAAE;YACR,8BAA8B;YAC9B,QAAQ,EAAE,OAAO,CAAC;YAClB,2BAA2B;YAC3B,SAAS,EAAE,MAAM,CAAC;YAClB,sCAAsC;YACtC,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;KACH,CAAC;IACF,4BAA4B;IAC5B,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF;;GAEG;AACH,KAAK,aAAa,GAAG;IACnB,6BAA6B;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,eAAe;IACf,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAqBF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GACrB,YAAY,WAAW,EACvB,QAAQ;IACN,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,iBAoBF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GACtB,YAAY,WAAW,EACvB,QAAQ;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,iBAgBnF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GACtB,YAAY,WAAW,EACvB,QAAQ;IACN,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,iBAiBF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,YAAY,WAAW,EACvB,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE;WAmBxC;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,cAAc,EAAE,MAAM,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,GAAG,CAAC;KACjB,EAAE;EAEN,CAAC"}
1
+ {"version":3,"file":"private-api.d.ts","sourceRoot":"","sources":["../../src/api/private-api.ts"],"names":[],"mappings":"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"}