@provable-games/ekubo-sdk 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1533 @@
1
+ // src/errors/index.ts
2
+ var EkuboError = class _EkuboError extends Error {
3
+ constructor(message, code, cause) {
4
+ super(message);
5
+ this.code = code;
6
+ this.cause = cause;
7
+ this.name = "EkuboError";
8
+ Object.setPrototypeOf(this, _EkuboError.prototype);
9
+ }
10
+ };
11
+ var InsufficientLiquidityError = class _InsufficientLiquidityError extends EkuboError {
12
+ constructor(message = "Insufficient liquidity for swap") {
13
+ super(message, "INSUFFICIENT_LIQUIDITY");
14
+ this.name = "InsufficientLiquidityError";
15
+ Object.setPrototypeOf(this, _InsufficientLiquidityError.prototype);
16
+ }
17
+ };
18
+ var ApiError = class _ApiError extends EkuboError {
19
+ constructor(message, statusCode, cause) {
20
+ super(message, "API_ERROR", cause);
21
+ this.statusCode = statusCode;
22
+ this.name = "ApiError";
23
+ Object.setPrototypeOf(this, _ApiError.prototype);
24
+ }
25
+ };
26
+ var RateLimitError = class _RateLimitError extends EkuboError {
27
+ constructor(message = "Rate limited by API", retryAfter) {
28
+ super(message, "RATE_LIMIT");
29
+ this.retryAfter = retryAfter;
30
+ this.name = "RateLimitError";
31
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
32
+ }
33
+ };
34
+ var TimeoutError = class _TimeoutError extends EkuboError {
35
+ constructor(message = "Request timed out", timeout) {
36
+ super(message, "TIMEOUT");
37
+ this.timeout = timeout;
38
+ this.name = "TimeoutError";
39
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
40
+ }
41
+ };
42
+ var AbortError = class _AbortError extends EkuboError {
43
+ constructor(message = "Request aborted") {
44
+ super(message, "ABORTED");
45
+ this.name = "AbortError";
46
+ Object.setPrototypeOf(this, _AbortError.prototype);
47
+ }
48
+ };
49
+ var TokenNotFoundError = class _TokenNotFoundError extends EkuboError {
50
+ constructor(tokenIdentifier, message = `Token not found: ${tokenIdentifier}`) {
51
+ super(message, "TOKEN_NOT_FOUND");
52
+ this.tokenIdentifier = tokenIdentifier;
53
+ this.name = "TokenNotFoundError";
54
+ Object.setPrototypeOf(this, _TokenNotFoundError.prototype);
55
+ }
56
+ };
57
+ var InvalidChainError = class _InvalidChainError extends EkuboError {
58
+ constructor(chainId, message = `Invalid or unsupported chain: ${chainId}`) {
59
+ super(message, "INVALID_CHAIN");
60
+ this.chainId = chainId;
61
+ this.name = "InvalidChainError";
62
+ Object.setPrototypeOf(this, _InvalidChainError.prototype);
63
+ }
64
+ };
65
+ function isNonRetryableError(error) {
66
+ if (error instanceof InsufficientLiquidityError) return true;
67
+ if (error instanceof AbortError) return true;
68
+ if (error instanceof TokenNotFoundError) return true;
69
+ if (error instanceof InvalidChainError) return true;
70
+ return false;
71
+ }
72
+
73
+ // src/utils/retry.ts
74
+ var DEFAULT_FETCH_CONFIG = {
75
+ timeout: 1e4,
76
+ maxRetries: 3,
77
+ baseBackoff: 1e3,
78
+ maxBackoff: 5e3,
79
+ fetch: globalThis.fetch
80
+ };
81
+ function parseRetryAfter(retryAfter) {
82
+ if (!retryAfter) return null;
83
+ const trimmed = retryAfter.trim();
84
+ if (!trimmed) return null;
85
+ const seconds = parseInt(trimmed, 10);
86
+ if (!isNaN(seconds) && seconds >= 0) {
87
+ return seconds * 1e3;
88
+ }
89
+ try {
90
+ const date = new Date(trimmed);
91
+ const delay = date.getTime() - Date.now();
92
+ return delay > 0 ? delay : null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+ function calculateBackoff(attempt, baseBackoff, maxBackoff, retryAfter) {
98
+ if (retryAfter) {
99
+ const retryAfterDelay = parseRetryAfter(retryAfter);
100
+ if (retryAfterDelay !== null && retryAfterDelay > 0) {
101
+ return retryAfterDelay;
102
+ }
103
+ }
104
+ let delay = baseBackoff * Math.pow(2, attempt);
105
+ if (delay > maxBackoff) {
106
+ delay = maxBackoff;
107
+ }
108
+ const minDelay = delay / 2;
109
+ const jitter = Math.random() * (delay - minDelay);
110
+ return minDelay + jitter;
111
+ }
112
+ function sleep(ms) {
113
+ return new Promise((resolve) => setTimeout(resolve, ms));
114
+ }
115
+ async function withRetry(fn, options) {
116
+ const { maxRetries, baseBackoff, maxBackoff, signal, onRetry } = options;
117
+ let lastError = null;
118
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
119
+ if (signal?.aborted) {
120
+ throw new Error("Request aborted");
121
+ }
122
+ try {
123
+ return await fn();
124
+ } catch (error) {
125
+ lastError = error;
126
+ if (error instanceof Error && error.name === "AbortError") {
127
+ throw error;
128
+ }
129
+ if (isNonRetryableError(error)) {
130
+ throw error;
131
+ }
132
+ if (attempt === maxRetries - 1) {
133
+ break;
134
+ }
135
+ const delay = calculateBackoff(attempt, baseBackoff, maxBackoff);
136
+ onRetry?.(attempt, lastError, delay);
137
+ await sleep(delay);
138
+ }
139
+ }
140
+ throw lastError || new Error("Unknown error after retries");
141
+ }
142
+
143
+ // src/utils/bigint.ts
144
+ function parseTotalCalculated(totalCalculated) {
145
+ let value;
146
+ if (typeof totalCalculated === "string") {
147
+ value = BigInt(totalCalculated);
148
+ } else {
149
+ value = BigInt(Math.floor(totalCalculated));
150
+ }
151
+ return value < 0n ? -value : value;
152
+ }
153
+ function abs(value) {
154
+ return value < 0n ? -value : value;
155
+ }
156
+ function addSlippage(amount, slippagePercent) {
157
+ return amount + amount * slippagePercent / 100n;
158
+ }
159
+ function subtractSlippage(amount, slippagePercent) {
160
+ return amount - amount * slippagePercent / 100n;
161
+ }
162
+
163
+ // src/utils/hex.ts
164
+ function toHex(value) {
165
+ if (typeof value === "string") {
166
+ if (value.startsWith("0x") || value.startsWith("0X")) {
167
+ return "0x" + BigInt(value).toString(16);
168
+ }
169
+ return "0x" + BigInt(value).toString(16);
170
+ }
171
+ return "0x" + BigInt(value).toString(16);
172
+ }
173
+ function normalizeAddress(address) {
174
+ const normalized = toHex(address);
175
+ return normalized.toLowerCase();
176
+ }
177
+ function isAddress(value) {
178
+ return /^0x[0-9a-fA-F]+$/.test(value);
179
+ }
180
+ function splitU256(value) {
181
+ const low = value % 2n ** 128n;
182
+ const high = value >> 128n;
183
+ return {
184
+ low: toHex(low),
185
+ high: toHex(high)
186
+ };
187
+ }
188
+
189
+ // src/chains/constants.ts
190
+ var CHAIN_IDS = {
191
+ MAINNET: "23448594291968334",
192
+ SEPOLIA: "393402133025997798000961"
193
+ };
194
+ var STARKNET_CHAIN_IDS = {
195
+ SN_MAIN: "0x534e5f4d41494e",
196
+ SN_SEPOLIA: "0x534e5f5345504f4c4941"
197
+ };
198
+ var ROUTER_ADDRESSES = {
199
+ [CHAIN_IDS.MAINNET]: "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e",
200
+ [CHAIN_IDS.SEPOLIA]: "0x0045f933adf0607292468ad1c1dedaa74d5ad166392590e72676a34d01d7b763"
201
+ };
202
+ var USDC_ADDRESSES = {
203
+ [CHAIN_IDS.MAINNET]: "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb",
204
+ [CHAIN_IDS.SEPOLIA]: "0x053b40a647cedfca6ca84f542a0fe36736031905a9639a7f19a3c1e66bfd5080"
205
+ };
206
+ var API_URLS = {
207
+ /** Quoter API for swap quotes */
208
+ QUOTER: "https://prod-api-quoter.ekubo.org",
209
+ /** Main API for tokens, prices, stats, etc. */
210
+ API: "https://prod-api.ekubo.org"
211
+ };
212
+ var CHAIN_CONFIGS = {
213
+ mainnet: {
214
+ chainId: CHAIN_IDS.MAINNET,
215
+ quoterApiUrl: API_URLS.QUOTER,
216
+ apiUrl: API_URLS.API,
217
+ routerAddress: ROUTER_ADDRESSES[CHAIN_IDS.MAINNET],
218
+ usdcAddress: USDC_ADDRESSES[CHAIN_IDS.MAINNET]
219
+ },
220
+ sepolia: {
221
+ chainId: CHAIN_IDS.SEPOLIA,
222
+ quoterApiUrl: API_URLS.QUOTER,
223
+ apiUrl: API_URLS.API,
224
+ routerAddress: ROUTER_ADDRESSES[CHAIN_IDS.SEPOLIA],
225
+ usdcAddress: USDC_ADDRESSES[CHAIN_IDS.SEPOLIA]
226
+ }
227
+ };
228
+ var STARKNET_TO_EKUBO_CHAIN = {
229
+ [STARKNET_CHAIN_IDS.SN_MAIN]: CHAIN_IDS.MAINNET,
230
+ [STARKNET_CHAIN_IDS.SN_SEPOLIA]: CHAIN_IDS.SEPOLIA
231
+ };
232
+ function getEkuboChainId(starknetChainId) {
233
+ return STARKNET_TO_EKUBO_CHAIN[starknetChainId];
234
+ }
235
+ function getChainConfig(chainOrId) {
236
+ if (chainOrId === "mainnet" || chainOrId === "sepolia") {
237
+ return CHAIN_CONFIGS[chainOrId];
238
+ }
239
+ if (chainOrId === CHAIN_IDS.MAINNET) {
240
+ return CHAIN_CONFIGS.mainnet;
241
+ }
242
+ if (chainOrId === CHAIN_IDS.SEPOLIA) {
243
+ return CHAIN_CONFIGS.sepolia;
244
+ }
245
+ const ekuboChainId = getEkuboChainId(chainOrId);
246
+ if (ekuboChainId === CHAIN_IDS.MAINNET) {
247
+ return CHAIN_CONFIGS.mainnet;
248
+ }
249
+ if (ekuboChainId === CHAIN_IDS.SEPOLIA) {
250
+ return CHAIN_CONFIGS.sepolia;
251
+ }
252
+ return void 0;
253
+ }
254
+
255
+ // src/api/quote.ts
256
+ async function fetchSwapQuote(params) {
257
+ const {
258
+ amount,
259
+ tokenFrom,
260
+ tokenTo,
261
+ chainId = CHAIN_IDS.MAINNET,
262
+ signal,
263
+ fetchConfig = {}
264
+ } = params;
265
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
266
+ const fetchFn = config.fetch;
267
+ const receivedAmount = `-${amount.toString()}`;
268
+ const normalizedTokenFrom = normalizeAddress(tokenFrom);
269
+ const normalizedTokenTo = normalizeAddress(tokenTo);
270
+ const url = `${API_URLS.QUOTER}/${chainId}/${receivedAmount}/${normalizedTokenFrom}/${normalizedTokenTo}`;
271
+ let lastError = null;
272
+ for (let attempt = 0; attempt < config.maxRetries; attempt++) {
273
+ if (signal?.aborted) {
274
+ throw new AbortError("Request aborted");
275
+ }
276
+ try {
277
+ const controller = new AbortController();
278
+ const timeoutId = setTimeout(() => controller.abort(), config.timeout);
279
+ const combinedSignal = signal ? anySignal([signal, controller.signal]) : controller.signal;
280
+ const response = await fetchFn(url, {
281
+ method: "GET",
282
+ signal: combinedSignal,
283
+ mode: "cors",
284
+ credentials: "omit"
285
+ });
286
+ clearTimeout(timeoutId);
287
+ if (response.ok) {
288
+ const data = await response.json();
289
+ if ("error" in data) {
290
+ if (data.error.includes("Insufficient liquidity")) {
291
+ throw new InsufficientLiquidityError(data.error);
292
+ }
293
+ throw new ApiError(data.error);
294
+ }
295
+ return {
296
+ impact: data.price_impact,
297
+ total: parseTotalCalculated(data.total_calculated),
298
+ splits: data.splits
299
+ };
300
+ }
301
+ if (response.status === 404) {
302
+ try {
303
+ const data = await response.json();
304
+ if (data.error?.includes("Insufficient liquidity")) {
305
+ throw new InsufficientLiquidityError(data.error);
306
+ }
307
+ } catch (e) {
308
+ if (e instanceof InsufficientLiquidityError) {
309
+ throw e;
310
+ }
311
+ }
312
+ throw new ApiError(`Failed to fetch swap quote: 404 Not Found`, 404);
313
+ }
314
+ if (response.status === 429) {
315
+ if (attempt === config.maxRetries - 1) {
316
+ throw new RateLimitError("Rate limited (429) - max retries exceeded");
317
+ }
318
+ const retryAfter = response.headers.get("Retry-After");
319
+ const delay = calculateBackoff(
320
+ attempt,
321
+ config.baseBackoff,
322
+ config.maxBackoff,
323
+ retryAfter
324
+ );
325
+ await sleep(delay);
326
+ continue;
327
+ }
328
+ throw new ApiError(
329
+ `Failed to fetch swap quote: ${response.status}`,
330
+ response.status
331
+ );
332
+ } catch (error) {
333
+ lastError = error;
334
+ if (error instanceof Error && (error.name === "AbortError" || error instanceof AbortError)) {
335
+ throw new AbortError("Request aborted");
336
+ }
337
+ if (error instanceof InsufficientLiquidityError) {
338
+ throw error;
339
+ }
340
+ if (attempt === config.maxRetries - 1) {
341
+ break;
342
+ }
343
+ const delay = calculateBackoff(
344
+ attempt,
345
+ config.baseBackoff,
346
+ config.maxBackoff
347
+ );
348
+ await sleep(delay);
349
+ }
350
+ }
351
+ throw lastError || new ApiError("Failed to fetch swap quote: unknown error");
352
+ }
353
+ async function fetchSwapQuoteInUsdc(params) {
354
+ const { chainId = CHAIN_IDS.MAINNET, ...rest } = params;
355
+ const usdcAddress = USDC_ADDRESSES[chainId] ?? USDC_ADDRESSES[CHAIN_IDS.MAINNET];
356
+ const quote = await fetchSwapQuote({
357
+ ...rest,
358
+ tokenTo: usdcAddress,
359
+ chainId
360
+ });
361
+ return quote.total;
362
+ }
363
+ function anySignal(signals) {
364
+ const controller = new AbortController();
365
+ for (const signal of signals) {
366
+ if (signal.aborted) {
367
+ controller.abort();
368
+ return controller.signal;
369
+ }
370
+ signal.addEventListener("abort", () => controller.abort(), { once: true });
371
+ }
372
+ return controller.signal;
373
+ }
374
+
375
+ // src/api/price.ts
376
+ async function getPriceHistory(params) {
377
+ const {
378
+ token,
379
+ otherToken,
380
+ chainId = CHAIN_IDS.MAINNET,
381
+ interval = 7e3,
382
+ apiUrl = API_URLS.API,
383
+ fetchConfig = {}
384
+ } = params;
385
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
386
+ const fetchFn = config.fetch;
387
+ const normalizedToken = normalizeAddress(token);
388
+ const normalizedOtherToken = normalizeAddress(otherToken);
389
+ const url = `${apiUrl}/price/${chainId}/${normalizedToken}/${normalizedOtherToken}/history?interval=${interval}`;
390
+ try {
391
+ const response = await fetchFn(url, {
392
+ method: "GET",
393
+ mode: "cors",
394
+ credentials: "omit"
395
+ });
396
+ if (!response.ok) {
397
+ throw new ApiError(
398
+ `Failed to fetch price history: ${response.status}`,
399
+ response.status
400
+ );
401
+ }
402
+ const data = await response.json();
403
+ return {
404
+ data: data?.data || []
405
+ };
406
+ } catch (error) {
407
+ if (error instanceof ApiError) {
408
+ throw error;
409
+ }
410
+ throw new ApiError(
411
+ `Failed to fetch price history: ${error instanceof Error ? error.message : "unknown error"}`
412
+ );
413
+ }
414
+ }
415
+
416
+ // src/api/tokens.ts
417
+ async function fetchTokens(params = {}) {
418
+ const {
419
+ chainId = CHAIN_IDS.MAINNET,
420
+ apiUrl = API_URLS.API,
421
+ fetchConfig = {}
422
+ } = params;
423
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
424
+ const fetchFn = config.fetch;
425
+ const url = `${apiUrl}/tokens?chainId=${chainId}`;
426
+ try {
427
+ const response = await fetchFn(url, {
428
+ method: "GET",
429
+ mode: "cors",
430
+ credentials: "omit"
431
+ });
432
+ if (!response.ok) {
433
+ throw new ApiError(
434
+ `Failed to fetch tokens: ${response.status}`,
435
+ response.status
436
+ );
437
+ }
438
+ const data = await response.json();
439
+ return data;
440
+ } catch (error) {
441
+ if (error instanceof ApiError) {
442
+ throw error;
443
+ }
444
+ throw new ApiError(
445
+ `Failed to fetch tokens: ${error instanceof Error ? error.message : "unknown error"}`
446
+ );
447
+ }
448
+ }
449
+ async function fetchToken(params) {
450
+ const {
451
+ tokenAddress,
452
+ chainId = CHAIN_IDS.MAINNET,
453
+ apiUrl = API_URLS.API,
454
+ fetchConfig = {}
455
+ } = params;
456
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
457
+ const fetchFn = config.fetch;
458
+ const url = `${apiUrl}/tokens/${chainId}/${tokenAddress}`;
459
+ try {
460
+ const response = await fetchFn(url, {
461
+ method: "GET",
462
+ mode: "cors",
463
+ credentials: "omit"
464
+ });
465
+ if (response.status === 404) {
466
+ return null;
467
+ }
468
+ if (!response.ok) {
469
+ throw new ApiError(
470
+ `Failed to fetch token: ${response.status}`,
471
+ response.status
472
+ );
473
+ }
474
+ const data = await response.json();
475
+ return data;
476
+ } catch (error) {
477
+ if (error instanceof ApiError) {
478
+ throw error;
479
+ }
480
+ throw new ApiError(
481
+ `Failed to fetch token: ${error instanceof Error ? error.message : "unknown error"}`
482
+ );
483
+ }
484
+ }
485
+ async function fetchTokensBatch(params) {
486
+ const {
487
+ tokenAddresses,
488
+ chainId = CHAIN_IDS.MAINNET,
489
+ apiUrl = API_URLS.API,
490
+ fetchConfig = {}
491
+ } = params;
492
+ if (tokenAddresses.length === 0) {
493
+ return [];
494
+ }
495
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
496
+ const fetchFn = config.fetch;
497
+ const addressParams = tokenAddresses.map((addr) => `addresses=${encodeURIComponent(addr)}`).join("&");
498
+ const url = `${apiUrl}/tokens/batch?chainId=${chainId}&${addressParams}`;
499
+ try {
500
+ const response = await fetchFn(url, {
501
+ method: "GET",
502
+ mode: "cors",
503
+ credentials: "omit"
504
+ });
505
+ if (!response.ok) {
506
+ throw new ApiError(
507
+ `Failed to fetch tokens batch: ${response.status}`,
508
+ response.status
509
+ );
510
+ }
511
+ const data = await response.json();
512
+ return data;
513
+ } catch (error) {
514
+ if (error instanceof ApiError) {
515
+ throw error;
516
+ }
517
+ throw new ApiError(
518
+ `Failed to fetch tokens batch: ${error instanceof Error ? error.message : "unknown error"}`
519
+ );
520
+ }
521
+ }
522
+
523
+ // src/api/stats.ts
524
+ async function fetchTopPairs(params = {}) {
525
+ const {
526
+ chainId = CHAIN_IDS.MAINNET,
527
+ apiUrl = API_URLS.API,
528
+ fetchConfig = {}
529
+ } = params;
530
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
531
+ const fetchFn = config.fetch;
532
+ const url = `${apiUrl}/overview/pairs?chainId=${chainId}`;
533
+ try {
534
+ const response = await fetchFn(url, {
535
+ method: "GET",
536
+ mode: "cors",
537
+ credentials: "omit"
538
+ });
539
+ if (!response.ok) {
540
+ throw new ApiError(
541
+ `Failed to fetch top pairs: ${response.status}`,
542
+ response.status
543
+ );
544
+ }
545
+ return await response.json();
546
+ } catch (error) {
547
+ if (error instanceof ApiError) throw error;
548
+ throw new ApiError(
549
+ `Failed to fetch top pairs: ${error instanceof Error ? error.message : "unknown error"}`
550
+ );
551
+ }
552
+ }
553
+ async function fetchTvl(params = {}) {
554
+ const {
555
+ chainId = CHAIN_IDS.MAINNET,
556
+ apiUrl = API_URLS.API,
557
+ fetchConfig = {}
558
+ } = params;
559
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
560
+ const fetchFn = config.fetch;
561
+ const url = `${apiUrl}/overview/tvl?chainId=${chainId}`;
562
+ try {
563
+ const response = await fetchFn(url, {
564
+ method: "GET",
565
+ mode: "cors",
566
+ credentials: "omit"
567
+ });
568
+ if (!response.ok) {
569
+ throw new ApiError(
570
+ `Failed to fetch TVL: ${response.status}`,
571
+ response.status
572
+ );
573
+ }
574
+ return await response.json();
575
+ } catch (error) {
576
+ if (error instanceof ApiError) throw error;
577
+ throw new ApiError(
578
+ `Failed to fetch TVL: ${error instanceof Error ? error.message : "unknown error"}`
579
+ );
580
+ }
581
+ }
582
+ async function fetchVolume(params = {}) {
583
+ const {
584
+ chainId = CHAIN_IDS.MAINNET,
585
+ apiUrl = API_URLS.API,
586
+ fetchConfig = {}
587
+ } = params;
588
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
589
+ const fetchFn = config.fetch;
590
+ const url = `${apiUrl}/overview/volume?chainId=${chainId}`;
591
+ try {
592
+ const response = await fetchFn(url, {
593
+ method: "GET",
594
+ mode: "cors",
595
+ credentials: "omit"
596
+ });
597
+ if (!response.ok) {
598
+ throw new ApiError(
599
+ `Failed to fetch volume: ${response.status}`,
600
+ response.status
601
+ );
602
+ }
603
+ return await response.json();
604
+ } catch (error) {
605
+ if (error instanceof ApiError) throw error;
606
+ throw new ApiError(
607
+ `Failed to fetch volume: ${error instanceof Error ? error.message : "unknown error"}`
608
+ );
609
+ }
610
+ }
611
+ async function fetchPairTvl(params) {
612
+ const {
613
+ tokenA,
614
+ tokenB,
615
+ chainId = CHAIN_IDS.MAINNET,
616
+ apiUrl = API_URLS.API,
617
+ fetchConfig = {}
618
+ } = params;
619
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
620
+ const fetchFn = config.fetch;
621
+ const normalizedA = normalizeAddress(tokenA);
622
+ const normalizedB = normalizeAddress(tokenB);
623
+ const url = `${apiUrl}/pair/${chainId}/${normalizedA}/${normalizedB}/tvl`;
624
+ try {
625
+ const response = await fetchFn(url, {
626
+ method: "GET",
627
+ mode: "cors",
628
+ credentials: "omit"
629
+ });
630
+ if (!response.ok) {
631
+ throw new ApiError(
632
+ `Failed to fetch pair TVL: ${response.status}`,
633
+ response.status
634
+ );
635
+ }
636
+ return await response.json();
637
+ } catch (error) {
638
+ if (error instanceof ApiError) throw error;
639
+ throw new ApiError(
640
+ `Failed to fetch pair TVL: ${error instanceof Error ? error.message : "unknown error"}`
641
+ );
642
+ }
643
+ }
644
+ async function fetchPairVolume(params) {
645
+ const {
646
+ tokenA,
647
+ tokenB,
648
+ chainId = CHAIN_IDS.MAINNET,
649
+ apiUrl = API_URLS.API,
650
+ fetchConfig = {}
651
+ } = params;
652
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
653
+ const fetchFn = config.fetch;
654
+ const normalizedA = normalizeAddress(tokenA);
655
+ const normalizedB = normalizeAddress(tokenB);
656
+ const url = `${apiUrl}/pair/${chainId}/${normalizedA}/${normalizedB}/volume`;
657
+ try {
658
+ const response = await fetchFn(url, {
659
+ method: "GET",
660
+ mode: "cors",
661
+ credentials: "omit"
662
+ });
663
+ if (!response.ok) {
664
+ throw new ApiError(
665
+ `Failed to fetch pair volume: ${response.status}`,
666
+ response.status
667
+ );
668
+ }
669
+ return await response.json();
670
+ } catch (error) {
671
+ if (error instanceof ApiError) throw error;
672
+ throw new ApiError(
673
+ `Failed to fetch pair volume: ${error instanceof Error ? error.message : "unknown error"}`
674
+ );
675
+ }
676
+ }
677
+ async function fetchPairPools(params) {
678
+ const {
679
+ tokenA,
680
+ tokenB,
681
+ chainId = CHAIN_IDS.MAINNET,
682
+ apiUrl = API_URLS.API,
683
+ fetchConfig = {}
684
+ } = params;
685
+ const config = { ...DEFAULT_FETCH_CONFIG, ...fetchConfig };
686
+ const fetchFn = config.fetch;
687
+ const normalizedA = normalizeAddress(tokenA);
688
+ const normalizedB = normalizeAddress(tokenB);
689
+ const url = `${apiUrl}/pair/${chainId}/${normalizedA}/${normalizedB}/pools`;
690
+ try {
691
+ const response = await fetchFn(url, {
692
+ method: "GET",
693
+ mode: "cors",
694
+ credentials: "omit"
695
+ });
696
+ if (!response.ok) {
697
+ throw new ApiError(
698
+ `Failed to fetch pair pools: ${response.status}`,
699
+ response.status
700
+ );
701
+ }
702
+ return await response.json();
703
+ } catch (error) {
704
+ if (error instanceof ApiError) throw error;
705
+ throw new ApiError(
706
+ `Failed to fetch pair pools: ${error instanceof Error ? error.message : "unknown error"}`
707
+ );
708
+ }
709
+ }
710
+
711
+ // src/calls/encoder.ts
712
+ function encodeRouteNode(routeNode, currentToken) {
713
+ const isToken1 = BigInt(currentToken) === BigInt(routeNode.pool_key.token1);
714
+ const nextToken = isToken1 ? routeNode.pool_key.token0 : routeNode.pool_key.token1;
715
+ const sqrtRatioLimit = BigInt(routeNode.sqrt_ratio_limit);
716
+ const calldata = [
717
+ routeNode.pool_key.token0,
718
+ routeNode.pool_key.token1,
719
+ routeNode.pool_key.fee,
720
+ toHex(routeNode.pool_key.tick_spacing),
721
+ routeNode.pool_key.extension,
722
+ toHex(sqrtRatioLimit % 2n ** 128n),
723
+ // low
724
+ toHex(sqrtRatioLimit >> 128n),
725
+ // high
726
+ toHex(routeNode.skip_ahead)
727
+ ];
728
+ return { nextToken, calldata };
729
+ }
730
+ function encodeRoute(route, targetToken) {
731
+ return route.reduce(
732
+ (memo, routeNode) => {
733
+ const { nextToken, calldata } = encodeRouteNode(routeNode, memo.token);
734
+ return {
735
+ token: nextToken,
736
+ encoded: memo.encoded.concat(calldata)
737
+ };
738
+ },
739
+ {
740
+ token: targetToken,
741
+ encoded: []
742
+ }
743
+ );
744
+ }
745
+
746
+ // src/calls/generator.ts
747
+ var DEFAULT_SLIPPAGE_PERCENT = 5n;
748
+ function generateSwapCalls(params) {
749
+ const {
750
+ sellToken,
751
+ buyToken,
752
+ minimumReceived,
753
+ quote,
754
+ chainId = CHAIN_IDS.MAINNET,
755
+ routerAddress: customRouterAddress,
756
+ slippagePercent = DEFAULT_SLIPPAGE_PERCENT
757
+ } = params;
758
+ const routerAddress = customRouterAddress ?? ROUTER_ADDRESSES[chainId];
759
+ if (!routerAddress) {
760
+ throw new InvalidChainError(
761
+ chainId,
762
+ `Router address not found for chain ID: ${chainId}`
763
+ );
764
+ }
765
+ const normalizedSellToken = normalizeAddress(sellToken);
766
+ const normalizedBuyToken = normalizeAddress(buyToken);
767
+ const totalWithSlippage = addSlippage(quote.total, slippagePercent);
768
+ const transferCall = {
769
+ contractAddress: normalizedSellToken,
770
+ entrypoint: "transfer",
771
+ calldata: [routerAddress, toHex(totalWithSlippage), "0x0"]
772
+ };
773
+ const clearCall = {
774
+ contractAddress: routerAddress,
775
+ entrypoint: "clear",
776
+ calldata: [normalizedSellToken]
777
+ };
778
+ if (!quote || quote.splits.length === 0) {
779
+ return {
780
+ transferCall,
781
+ swapCalls: [],
782
+ clearCall,
783
+ allCalls: [transferCall, clearCall]
784
+ };
785
+ }
786
+ const { splits } = quote;
787
+ const clearMinimumCall = {
788
+ contractAddress: routerAddress,
789
+ entrypoint: "clear_minimum",
790
+ calldata: [normalizedBuyToken, toHex(minimumReceived), "0x0"]
791
+ };
792
+ let swapCalls;
793
+ if (splits.length === 1) {
794
+ swapCalls = generateSingleRouteSwap(
795
+ splits[0],
796
+ normalizedBuyToken,
797
+ routerAddress
798
+ );
799
+ } else {
800
+ swapCalls = generateMultiRouteSwap(
801
+ splits,
802
+ normalizedBuyToken,
803
+ routerAddress
804
+ );
805
+ }
806
+ swapCalls.push(clearMinimumCall);
807
+ return {
808
+ transferCall,
809
+ swapCalls,
810
+ clearCall,
811
+ allCalls: [transferCall, ...swapCalls, clearCall]
812
+ };
813
+ }
814
+ function generateSingleRouteSwap(split, targetToken, routerAddress) {
815
+ const routeCalldata = encodeRoute(split.route, targetToken);
816
+ const amountSpecified = BigInt(split.amount_specified);
817
+ const absAmount = abs(amountSpecified);
818
+ return [
819
+ {
820
+ contractAddress: routerAddress,
821
+ entrypoint: "multihop_swap",
822
+ calldata: [
823
+ toHex(split.route.length),
824
+ ...routeCalldata.encoded,
825
+ targetToken,
826
+ toHex(absAmount),
827
+ "0x1"
828
+ // is_exact_amount_received flag
829
+ ]
830
+ }
831
+ ];
832
+ }
833
+ function generateMultiRouteSwap(splits, targetToken, routerAddress) {
834
+ const multiRouteCalldata = splits.reduce((memo, split) => {
835
+ const routeCalldata = encodeRoute(split.route, targetToken);
836
+ const amountSpecified = BigInt(split.amount_specified);
837
+ const absAmount = abs(amountSpecified);
838
+ return memo.concat([
839
+ toHex(split.route.length),
840
+ ...routeCalldata.encoded,
841
+ targetToken,
842
+ toHex(absAmount),
843
+ "0x1"
844
+ // is_exact_amount_received flag
845
+ ]);
846
+ }, []);
847
+ return [
848
+ {
849
+ contractAddress: routerAddress,
850
+ entrypoint: "multi_multihop_swap",
851
+ calldata: [toHex(splits.length), ...multiRouteCalldata]
852
+ }
853
+ ];
854
+ }
855
+ function prepareSwapCalls(params) {
856
+ const result = generateSwapCalls(params);
857
+ const {
858
+ sellToken,
859
+ quote,
860
+ chainId = CHAIN_IDS.MAINNET,
861
+ routerAddress: customRouterAddress,
862
+ slippagePercent = DEFAULT_SLIPPAGE_PERCENT
863
+ } = params;
864
+ const routerAddress = customRouterAddress ?? ROUTER_ADDRESSES[chainId];
865
+ if (!routerAddress) {
866
+ throw new InvalidChainError(
867
+ chainId,
868
+ `Router address not found for chain ID: ${chainId}`
869
+ );
870
+ }
871
+ const normalizedSellToken = normalizeAddress(sellToken);
872
+ const totalWithSlippage = addSlippage(quote.total, slippagePercent);
873
+ const approveCall = {
874
+ contractAddress: normalizedSellToken,
875
+ entrypoint: "approve",
876
+ calldata: [routerAddress, toHex(totalWithSlippage), "0x0"]
877
+ };
878
+ return {
879
+ ...result,
880
+ approveCall,
881
+ allCalls: [approveCall, ...result.allCalls]
882
+ };
883
+ }
884
+
885
+ // src/tokens/mainnet.ts
886
+ var MAINNET_TOKENS = [
887
+ {
888
+ symbol: "ETH",
889
+ name: "Ether",
890
+ address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
891
+ decimals: 18
892
+ },
893
+ {
894
+ symbol: "STRK",
895
+ name: "Starknet Token",
896
+ address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
897
+ decimals: 18
898
+ },
899
+ {
900
+ symbol: "USDC",
901
+ name: "USD Coin",
902
+ address: "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb",
903
+ decimals: 6
904
+ },
905
+ {
906
+ symbol: "USDC.e",
907
+ name: "Bridged USDC",
908
+ address: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
909
+ decimals: 6
910
+ },
911
+ {
912
+ symbol: "USDT",
913
+ name: "Tether USD",
914
+ address: "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
915
+ decimals: 6
916
+ },
917
+ {
918
+ symbol: "DAI",
919
+ name: "Dai Stablecoin",
920
+ address: "0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3",
921
+ decimals: 18
922
+ },
923
+ {
924
+ symbol: "WBTC",
925
+ name: "Wrapped Bitcoin",
926
+ address: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
927
+ decimals: 8
928
+ },
929
+ {
930
+ symbol: "LORDS",
931
+ name: "Lords Token",
932
+ address: "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49",
933
+ decimals: 18
934
+ },
935
+ {
936
+ symbol: "wstETH",
937
+ name: "Wrapped stETH",
938
+ address: "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2",
939
+ decimals: 18
940
+ },
941
+ {
942
+ symbol: "EKUBO",
943
+ name: "Ekubo Protocol",
944
+ address: "0x075afe6402ad5a5c20dd25e10ec3b3986acaa647b77e4ae24b0cbc9a54a27a87",
945
+ decimals: 18
946
+ },
947
+ {
948
+ symbol: "ZEND",
949
+ name: "zkLend",
950
+ address: "0x00585c32b625999e6e5e78645ff8df7a9001cf5cf3eb6b80ccdd16cb64bd3a34",
951
+ decimals: 18
952
+ },
953
+ {
954
+ symbol: "NSTR",
955
+ name: "Nostra",
956
+ address: "0x04619e9ce4109590219c5263787050726be63382148538f3f936c22aa87d2fc2",
957
+ decimals: 18
958
+ }
959
+ ];
960
+ function getMainnetTokensMap() {
961
+ const map = /* @__PURE__ */ new Map();
962
+ for (const token of MAINNET_TOKENS) {
963
+ map.set(token.symbol.toUpperCase(), token);
964
+ }
965
+ return map;
966
+ }
967
+
968
+ // src/tokens/registry.ts
969
+ var TokenRegistry = class {
970
+ symbolMap = /* @__PURE__ */ new Map();
971
+ addressMap = /* @__PURE__ */ new Map();
972
+ constructor(tokens = []) {
973
+ for (const token of MAINNET_TOKENS) {
974
+ this.register(token);
975
+ }
976
+ for (const token of tokens) {
977
+ this.register(token);
978
+ }
979
+ }
980
+ /**
981
+ * Register a token
982
+ */
983
+ register(token) {
984
+ const upperSymbol = token.symbol.toUpperCase();
985
+ const normalizedAddress = normalizeAddress(token.address);
986
+ const normalizedToken = {
987
+ ...token,
988
+ address: normalizedAddress
989
+ };
990
+ this.symbolMap.set(upperSymbol, normalizedToken);
991
+ this.addressMap.set(normalizedAddress, normalizedToken);
992
+ }
993
+ /**
994
+ * Get token by symbol (case-insensitive)
995
+ */
996
+ getBySymbol(symbol) {
997
+ return this.symbolMap.get(symbol.toUpperCase());
998
+ }
999
+ /**
1000
+ * Get token by address (normalized)
1001
+ */
1002
+ getByAddress(address) {
1003
+ return this.addressMap.get(normalizeAddress(address));
1004
+ }
1005
+ /**
1006
+ * Check if a token exists by symbol
1007
+ */
1008
+ hasSymbol(symbol) {
1009
+ return this.symbolMap.has(symbol.toUpperCase());
1010
+ }
1011
+ /**
1012
+ * Check if a token exists by address
1013
+ */
1014
+ hasAddress(address) {
1015
+ return this.addressMap.has(normalizeAddress(address));
1016
+ }
1017
+ /**
1018
+ * Get all registered tokens
1019
+ */
1020
+ getAll() {
1021
+ return Array.from(this.symbolMap.values());
1022
+ }
1023
+ /**
1024
+ * Get all registered symbols
1025
+ */
1026
+ getSymbols() {
1027
+ return Array.from(this.symbolMap.keys());
1028
+ }
1029
+ };
1030
+ var defaultRegistry = null;
1031
+ function getDefaultRegistry() {
1032
+ if (!defaultRegistry) {
1033
+ defaultRegistry = new TokenRegistry();
1034
+ }
1035
+ return defaultRegistry;
1036
+ }
1037
+ function createTokenRegistry(customTokens = []) {
1038
+ return new TokenRegistry(customTokens);
1039
+ }
1040
+
1041
+ // src/tokens/resolver.ts
1042
+ function resolveToken(tokenIdentifier, registry) {
1043
+ const reg = registry ?? getDefaultRegistry();
1044
+ if (isAddress(tokenIdentifier)) {
1045
+ return normalizeAddress(tokenIdentifier);
1046
+ }
1047
+ const token = reg.getBySymbol(tokenIdentifier);
1048
+ if (token) {
1049
+ return token.address;
1050
+ }
1051
+ throw new TokenNotFoundError(tokenIdentifier);
1052
+ }
1053
+ function resolveTokenInfo(tokenIdentifier, registry) {
1054
+ const reg = registry ?? getDefaultRegistry();
1055
+ if (isAddress(tokenIdentifier)) {
1056
+ return reg.getByAddress(tokenIdentifier);
1057
+ }
1058
+ return reg.getBySymbol(tokenIdentifier);
1059
+ }
1060
+ function canResolveToken(tokenIdentifier, registry) {
1061
+ if (isAddress(tokenIdentifier)) {
1062
+ return true;
1063
+ }
1064
+ const reg = registry ?? getDefaultRegistry();
1065
+ return reg.hasSymbol(tokenIdentifier);
1066
+ }
1067
+
1068
+ // src/polling/poller.ts
1069
+ var DEFAULT_POLLING_CONFIG = {
1070
+ interval: 5e3,
1071
+ maxConsecutiveErrors: 3
1072
+ };
1073
+ var QuotePoller = class {
1074
+ intervalId = null;
1075
+ abortController = null;
1076
+ consecutiveErrors = 0;
1077
+ isRunning = false;
1078
+ params;
1079
+ callbacks;
1080
+ config;
1081
+ fetchConfig;
1082
+ constructor(params, callbacks, config = {}, fetchConfig) {
1083
+ this.params = params;
1084
+ this.callbacks = callbacks;
1085
+ this.config = { ...DEFAULT_POLLING_CONFIG, ...config };
1086
+ this.fetchConfig = fetchConfig;
1087
+ }
1088
+ /**
1089
+ * Start polling for quotes
1090
+ */
1091
+ start() {
1092
+ if (this.isRunning) {
1093
+ return;
1094
+ }
1095
+ this.isRunning = true;
1096
+ this.consecutiveErrors = 0;
1097
+ this.abortController = new AbortController();
1098
+ void this.fetchQuote();
1099
+ this.intervalId = setInterval(() => {
1100
+ void this.fetchQuote();
1101
+ }, this.config.interval);
1102
+ }
1103
+ /**
1104
+ * Stop polling
1105
+ */
1106
+ stop() {
1107
+ if (!this.isRunning) {
1108
+ return;
1109
+ }
1110
+ this.isRunning = false;
1111
+ if (this.intervalId) {
1112
+ clearInterval(this.intervalId);
1113
+ this.intervalId = null;
1114
+ }
1115
+ if (this.abortController) {
1116
+ this.abortController.abort();
1117
+ this.abortController = null;
1118
+ }
1119
+ this.callbacks.onStop?.("manual");
1120
+ }
1121
+ /**
1122
+ * Check if currently polling
1123
+ */
1124
+ get running() {
1125
+ return this.isRunning;
1126
+ }
1127
+ /**
1128
+ * Update polling parameters (requires restart)
1129
+ */
1130
+ updateParams(params) {
1131
+ Object.assign(this.params, params);
1132
+ if (this.isRunning) {
1133
+ this.stop();
1134
+ this.start();
1135
+ }
1136
+ }
1137
+ /**
1138
+ * Fetch a quote
1139
+ */
1140
+ async fetchQuote() {
1141
+ if (!this.isRunning || !this.abortController) {
1142
+ return;
1143
+ }
1144
+ try {
1145
+ const quote = await fetchSwapQuote({
1146
+ amount: this.params.amount,
1147
+ tokenFrom: this.params.tokenFrom,
1148
+ tokenTo: this.params.tokenTo,
1149
+ chainId: this.params.chainId,
1150
+ signal: this.abortController.signal,
1151
+ fetchConfig: this.fetchConfig
1152
+ });
1153
+ this.consecutiveErrors = 0;
1154
+ this.callbacks.onQuote(quote);
1155
+ } catch (error) {
1156
+ if (error instanceof Error && error.name === "AbortError") {
1157
+ return;
1158
+ }
1159
+ this.consecutiveErrors++;
1160
+ this.callbacks.onError?.(error);
1161
+ if (this.consecutiveErrors >= this.config.maxConsecutiveErrors) {
1162
+ this.stopDueToErrors();
1163
+ }
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Stop due to consecutive errors
1168
+ */
1169
+ stopDueToErrors() {
1170
+ this.isRunning = false;
1171
+ if (this.intervalId) {
1172
+ clearInterval(this.intervalId);
1173
+ this.intervalId = null;
1174
+ }
1175
+ if (this.abortController) {
1176
+ this.abortController.abort();
1177
+ this.abortController = null;
1178
+ }
1179
+ this.callbacks.onStop?.("errors");
1180
+ }
1181
+ };
1182
+ function createQuotePoller(params, callbacks, config, fetchConfig) {
1183
+ return new QuotePoller(params, callbacks, config, fetchConfig);
1184
+ }
1185
+
1186
+ // src/client.ts
1187
+ var DEFAULT_CONFIG = {
1188
+ chain: "mainnet",
1189
+ defaultSlippagePercent: 5n
1190
+ };
1191
+ var EkuboClient = class {
1192
+ config;
1193
+ tokenRegistry;
1194
+ constructor(config = {}) {
1195
+ this.config = this.resolveConfig(config);
1196
+ this.tokenRegistry = new TokenRegistry(config.customTokens);
1197
+ }
1198
+ /**
1199
+ * Resolve configuration with defaults
1200
+ */
1201
+ resolveConfig(config) {
1202
+ const chain = config.chain ?? DEFAULT_CONFIG.chain;
1203
+ const chainConfig = config.chainId ? getChainConfig(config.chainId) : getChainConfig(chain);
1204
+ if (!chainConfig && !config.chainId) {
1205
+ throw new InvalidChainError(chain);
1206
+ }
1207
+ return {
1208
+ chainId: config.chainId ?? chainConfig?.chainId ?? CHAIN_IDS.MAINNET,
1209
+ quoterApiUrl: config.quoterApiUrl ?? chainConfig?.quoterApiUrl ?? "https://prod-api-quoter.ekubo.org",
1210
+ apiUrl: config.apiUrl ?? chainConfig?.apiUrl ?? "https://prod-api.ekubo.org",
1211
+ routerAddress: config.routerAddress ?? chainConfig?.routerAddress ?? "",
1212
+ defaultSlippagePercent: config.defaultSlippagePercent ?? DEFAULT_CONFIG.defaultSlippagePercent,
1213
+ fetch: { ...DEFAULT_FETCH_CONFIG, ...config.fetch },
1214
+ polling: { ...DEFAULT_POLLING_CONFIG, ...config.polling }
1215
+ };
1216
+ }
1217
+ /**
1218
+ * Get the current chain ID
1219
+ */
1220
+ get chainId() {
1221
+ return this.config.chainId;
1222
+ }
1223
+ /**
1224
+ * Get the router contract address
1225
+ */
1226
+ get routerAddress() {
1227
+ return this.config.routerAddress;
1228
+ }
1229
+ /**
1230
+ * Get the token registry
1231
+ */
1232
+ get tokens() {
1233
+ return this.tokenRegistry;
1234
+ }
1235
+ /**
1236
+ * Resolve a token identifier (symbol or address) to an address
1237
+ */
1238
+ resolveToken(tokenIdentifier) {
1239
+ return resolveToken(tokenIdentifier, this.tokenRegistry);
1240
+ }
1241
+ // ==========================================================================
1242
+ // Swap Quotes
1243
+ // ==========================================================================
1244
+ /**
1245
+ * Fetch a swap quote
1246
+ *
1247
+ * @param params - Quote parameters (supports symbols or addresses)
1248
+ * @returns Swap quote
1249
+ */
1250
+ async getQuote(params) {
1251
+ const tokenFrom = this.resolveToken(params.tokenFrom);
1252
+ const tokenTo = this.resolveToken(params.tokenTo);
1253
+ return fetchSwapQuote({
1254
+ amount: params.amount,
1255
+ tokenFrom,
1256
+ tokenTo,
1257
+ chainId: this.config.chainId,
1258
+ signal: params.signal,
1259
+ fetchConfig: this.config.fetch
1260
+ });
1261
+ }
1262
+ /**
1263
+ * Fetch a token price in USDC
1264
+ *
1265
+ * @param tokenIdentifier - Token symbol or address
1266
+ * @param amount - Amount of token
1267
+ * @param signal - Optional abort signal
1268
+ * @returns Price in USDC (as bigint)
1269
+ */
1270
+ async getUsdcPrice(tokenIdentifier, amount, signal) {
1271
+ const tokenFrom = this.resolveToken(tokenIdentifier);
1272
+ return fetchSwapQuoteInUsdc({
1273
+ amount,
1274
+ tokenFrom,
1275
+ chainId: this.config.chainId,
1276
+ signal,
1277
+ fetchConfig: this.config.fetch
1278
+ });
1279
+ }
1280
+ /**
1281
+ * Fetch price history
1282
+ *
1283
+ * @param token - Token symbol or address
1284
+ * @param otherToken - Quote token symbol or address
1285
+ * @param interval - Time interval in seconds (default: 7000)
1286
+ * @returns Price history data
1287
+ */
1288
+ async getPriceHistory(token, otherToken, interval) {
1289
+ const resolvedToken = this.resolveToken(token);
1290
+ const resolvedOtherToken = this.resolveToken(otherToken);
1291
+ return getPriceHistory({
1292
+ token: resolvedToken,
1293
+ otherToken: resolvedOtherToken,
1294
+ chainId: this.config.chainId,
1295
+ interval,
1296
+ apiUrl: this.config.apiUrl,
1297
+ fetchConfig: this.config.fetch
1298
+ });
1299
+ }
1300
+ // ==========================================================================
1301
+ // Swap Call Generation
1302
+ // ==========================================================================
1303
+ /**
1304
+ * Generate swap calls from a quote
1305
+ *
1306
+ * @param params - Call generation parameters (supports symbols or addresses)
1307
+ * @returns Swap calls result
1308
+ */
1309
+ generateSwapCalls(params) {
1310
+ const sellToken = this.resolveToken(params.sellToken);
1311
+ const buyToken = this.resolveToken(params.buyToken);
1312
+ return generateSwapCalls({
1313
+ ...params,
1314
+ sellToken,
1315
+ buyToken,
1316
+ chainId: this.config.chainId,
1317
+ routerAddress: this.config.routerAddress,
1318
+ slippagePercent: params.slippagePercent ?? this.config.defaultSlippagePercent
1319
+ });
1320
+ }
1321
+ /**
1322
+ * Prepare swap calls with approval included
1323
+ *
1324
+ * @param params - Call generation parameters (supports symbols or addresses)
1325
+ * @returns Swap calls result with approve call
1326
+ */
1327
+ prepareSwapCalls(params) {
1328
+ const sellToken = this.resolveToken(params.sellToken);
1329
+ const buyToken = this.resolveToken(params.buyToken);
1330
+ return prepareSwapCalls({
1331
+ ...params,
1332
+ sellToken,
1333
+ buyToken,
1334
+ chainId: this.config.chainId,
1335
+ routerAddress: this.config.routerAddress,
1336
+ slippagePercent: params.slippagePercent ?? this.config.defaultSlippagePercent
1337
+ });
1338
+ }
1339
+ // ==========================================================================
1340
+ // Quote Polling
1341
+ // ==========================================================================
1342
+ /**
1343
+ * Create a quote poller for real-time updates
1344
+ *
1345
+ * @param params - Quote parameters (supports symbols or addresses)
1346
+ * @param callbacks - Poller callbacks
1347
+ * @param config - Optional polling configuration
1348
+ * @returns QuotePoller instance
1349
+ */
1350
+ createQuotePoller(params, callbacks, config) {
1351
+ const tokenFrom = this.resolveToken(params.tokenFrom);
1352
+ const tokenTo = this.resolveToken(params.tokenTo);
1353
+ return new QuotePoller(
1354
+ {
1355
+ amount: params.amount,
1356
+ tokenFrom,
1357
+ tokenTo,
1358
+ chainId: this.config.chainId
1359
+ },
1360
+ callbacks,
1361
+ { ...this.config.polling, ...config },
1362
+ this.config.fetch
1363
+ );
1364
+ }
1365
+ // ==========================================================================
1366
+ // Token Management
1367
+ // ==========================================================================
1368
+ /**
1369
+ * Register a custom token in the local registry
1370
+ */
1371
+ registerToken(token) {
1372
+ this.tokenRegistry.register(token);
1373
+ }
1374
+ /**
1375
+ * Fetch all tokens from the Ekubo API
1376
+ *
1377
+ * @returns Array of token metadata from the API
1378
+ */
1379
+ async fetchTokens() {
1380
+ return fetchTokens({
1381
+ chainId: this.config.chainId,
1382
+ apiUrl: this.config.apiUrl,
1383
+ fetchConfig: this.config.fetch
1384
+ });
1385
+ }
1386
+ /**
1387
+ * Fetch metadata for a single token from the API
1388
+ *
1389
+ * @param tokenAddress - Token contract address
1390
+ * @returns Token metadata or null if not found
1391
+ */
1392
+ async fetchToken(tokenAddress) {
1393
+ const resolved = this.resolveToken(tokenAddress);
1394
+ return fetchToken({
1395
+ tokenAddress: resolved,
1396
+ chainId: this.config.chainId,
1397
+ apiUrl: this.config.apiUrl,
1398
+ fetchConfig: this.config.fetch
1399
+ });
1400
+ }
1401
+ /**
1402
+ * Fetch metadata for multiple tokens at once
1403
+ *
1404
+ * @param tokenAddresses - Array of token addresses
1405
+ * @returns Array of token metadata
1406
+ */
1407
+ async fetchTokensBatch(tokenAddresses) {
1408
+ const resolved = tokenAddresses.map((addr) => this.resolveToken(addr));
1409
+ return fetchTokensBatch({
1410
+ tokenAddresses: resolved,
1411
+ chainId: this.config.chainId,
1412
+ apiUrl: this.config.apiUrl,
1413
+ fetchConfig: this.config.fetch
1414
+ });
1415
+ }
1416
+ /**
1417
+ * Fetch tokens from API and register them in the local registry
1418
+ *
1419
+ * @returns Number of tokens registered
1420
+ */
1421
+ async syncTokensFromApi() {
1422
+ const apiTokens = await this.fetchTokens();
1423
+ for (const token of apiTokens) {
1424
+ this.tokenRegistry.register({
1425
+ symbol: token.symbol,
1426
+ address: token.address,
1427
+ decimals: token.decimals,
1428
+ name: token.name
1429
+ });
1430
+ }
1431
+ return apiTokens.length;
1432
+ }
1433
+ // ==========================================================================
1434
+ // Protocol Statistics
1435
+ // ==========================================================================
1436
+ /**
1437
+ * Fetch top trading pairs by volume
1438
+ *
1439
+ * @returns Array of pair statistics
1440
+ */
1441
+ async getTopPairs() {
1442
+ return fetchTopPairs({
1443
+ chainId: this.config.chainId,
1444
+ apiUrl: this.config.apiUrl,
1445
+ fetchConfig: this.config.fetch
1446
+ });
1447
+ }
1448
+ /**
1449
+ * Fetch protocol TVL overview
1450
+ *
1451
+ * @returns TVL statistics
1452
+ */
1453
+ async getTvl() {
1454
+ return fetchTvl({
1455
+ chainId: this.config.chainId,
1456
+ apiUrl: this.config.apiUrl,
1457
+ fetchConfig: this.config.fetch
1458
+ });
1459
+ }
1460
+ /**
1461
+ * Fetch protocol volume overview
1462
+ *
1463
+ * @returns Volume statistics
1464
+ */
1465
+ async getVolume() {
1466
+ return fetchVolume({
1467
+ chainId: this.config.chainId,
1468
+ apiUrl: this.config.apiUrl,
1469
+ fetchConfig: this.config.fetch
1470
+ });
1471
+ }
1472
+ /**
1473
+ * Fetch TVL history for a trading pair
1474
+ *
1475
+ * @param tokenA - First token (symbol or address)
1476
+ * @param tokenB - Second token (symbol or address)
1477
+ * @returns TVL data points
1478
+ */
1479
+ async getPairTvl(tokenA, tokenB) {
1480
+ const resolvedA = this.resolveToken(tokenA);
1481
+ const resolvedB = this.resolveToken(tokenB);
1482
+ return fetchPairTvl({
1483
+ tokenA: resolvedA,
1484
+ tokenB: resolvedB,
1485
+ chainId: this.config.chainId,
1486
+ apiUrl: this.config.apiUrl,
1487
+ fetchConfig: this.config.fetch
1488
+ });
1489
+ }
1490
+ /**
1491
+ * Fetch volume history for a trading pair
1492
+ *
1493
+ * @param tokenA - First token (symbol or address)
1494
+ * @param tokenB - Second token (symbol or address)
1495
+ * @returns Volume data points
1496
+ */
1497
+ async getPairVolume(tokenA, tokenB) {
1498
+ const resolvedA = this.resolveToken(tokenA);
1499
+ const resolvedB = this.resolveToken(tokenB);
1500
+ return fetchPairVolume({
1501
+ tokenA: resolvedA,
1502
+ tokenB: resolvedB,
1503
+ chainId: this.config.chainId,
1504
+ apiUrl: this.config.apiUrl,
1505
+ fetchConfig: this.config.fetch
1506
+ });
1507
+ }
1508
+ /**
1509
+ * Fetch pools for a trading pair
1510
+ *
1511
+ * @param tokenA - First token (symbol or address)
1512
+ * @param tokenB - Second token (symbol or address)
1513
+ * @returns Pool information
1514
+ */
1515
+ async getPairPools(tokenA, tokenB) {
1516
+ const resolvedA = this.resolveToken(tokenA);
1517
+ const resolvedB = this.resolveToken(tokenB);
1518
+ return fetchPairPools({
1519
+ tokenA: resolvedA,
1520
+ tokenB: resolvedB,
1521
+ chainId: this.config.chainId,
1522
+ apiUrl: this.config.apiUrl,
1523
+ fetchConfig: this.config.fetch
1524
+ });
1525
+ }
1526
+ };
1527
+ function createEkuboClient(config) {
1528
+ return new EkuboClient(config);
1529
+ }
1530
+
1531
+ export { API_URLS, AbortError, ApiError, CHAIN_CONFIGS, CHAIN_IDS, DEFAULT_POLLING_CONFIG, EkuboClient, EkuboError, InsufficientLiquidityError, InvalidChainError, MAINNET_TOKENS, QuotePoller, ROUTER_ADDRESSES, RateLimitError, STARKNET_CHAIN_IDS, STARKNET_TO_EKUBO_CHAIN, TimeoutError, TokenNotFoundError, TokenRegistry, USDC_ADDRESSES, abs, addSlippage, calculateBackoff, canResolveToken, createEkuboClient, createQuotePoller, createTokenRegistry, encodeRoute, encodeRouteNode, fetchPairPools, fetchPairTvl, fetchPairVolume, fetchSwapQuote, fetchSwapQuoteInUsdc, fetchToken, fetchTokens, fetchTokensBatch, fetchTopPairs, fetchTvl, fetchVolume, generateSwapCalls, getChainConfig, getDefaultRegistry, getEkuboChainId, getMainnetTokensMap, getPriceHistory, isAddress, isNonRetryableError, normalizeAddress, parseTotalCalculated, prepareSwapCalls, resolveToken, resolveTokenInfo, sleep, splitU256, subtractSlippage, toHex, withRetry };
1532
+ //# sourceMappingURL=index.js.map
1533
+ //# sourceMappingURL=index.js.map