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