@paraswap/dex-lib 2.19.0 → 2.19.1-swaap-v2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.idea/aws.xml +17 -0
  2. package/.idea/codeStyles/Project.xml +19 -0
  3. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  4. package/.idea/misc.xml +6 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/paraswap-dex-lib.iml +9 -0
  7. package/.idea/vcs.xml +6 -0
  8. package/build/abi/swaap-v2/vault.json +918 -0
  9. package/build/config.js +4 -2
  10. package/build/config.js.map +1 -1
  11. package/build/dex/aave-v3/tokens/arbitrum.json +50 -0
  12. package/build/dex/aave-v3/tokens/avalanche.json +74 -0
  13. package/build/dex/aave-v3/tokens/fantom.json +62 -0
  14. package/build/dex/aave-v3/tokens/optimism.json +50 -0
  15. package/build/dex/aave-v3/tokens/polygon.json +116 -0
  16. package/build/dex/index.js +2 -0
  17. package/build/dex/index.js.map +1 -1
  18. package/build/dex/pancakeswap-v3/types.d.ts +14 -0
  19. package/build/dex/pancakeswap-v3/types.js +3 -0
  20. package/build/dex/pancakeswap-v3/types.js.map +1 -0
  21. package/build/dex/swaap-v2/config.d.ts +4 -0
  22. package/build/dex/swaap-v2/config.js +19 -0
  23. package/build/dex/swaap-v2/config.js.map +1 -0
  24. package/build/dex/swaap-v2/constants.d.ts +13 -0
  25. package/build/dex/swaap-v2/constants.js +17 -0
  26. package/build/dex/swaap-v2/constants.js.map +1 -0
  27. package/build/dex/swaap-v2/rate-fetcher.d.ts +16 -0
  28. package/build/dex/swaap-v2/rate-fetcher.js +101 -0
  29. package/build/dex/swaap-v2/rate-fetcher.js.map +1 -0
  30. package/build/dex/swaap-v2/swaap-v2.d.ts +61 -0
  31. package/build/dex/swaap-v2/swaap-v2.js +550 -0
  32. package/build/dex/swaap-v2/swaap-v2.js.map +1 -0
  33. package/build/dex/swaap-v2/types.d.ts +76 -0
  34. package/build/dex/swaap-v2/types.js +7 -0
  35. package/build/dex/swaap-v2/types.js.map +1 -0
  36. package/build/dex/swaap-v2/utils.d.ts +7 -0
  37. package/build/dex/swaap-v2/utils.js +34 -0
  38. package/build/dex/swaap-v2/utils.js.map +1 -0
  39. package/build/dex/swaap-v2/validators.d.ts +6 -0
  40. package/build/dex/swaap-v2/validators.js +125 -0
  41. package/build/dex/swaap-v2/validators.js.map +1 -0
  42. package/build/types.d.ts +1 -0
  43. package/package.json +1 -1
  44. package/src/abi/swaap-v2/vault.json +918 -0
  45. package/src/config.ts +5 -2
  46. package/src/dex/index.ts +2 -0
  47. package/src/dex/swaap-v2/config.ts +19 -0
  48. package/src/dex/swaap-v2/constants.ts +25 -0
  49. package/src/dex/swaap-v2/rate-fetcher.ts +165 -0
  50. package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +199 -0
  51. package/src/dex/swaap-v2/swaap-v2-integration.test.ts +145 -0
  52. package/src/dex/swaap-v2/swaap-v2.ts +871 -0
  53. package/src/dex/swaap-v2/types.ts +88 -0
  54. package/src/dex/swaap-v2/utils.ts +44 -0
  55. package/src/dex/swaap-v2/validators.ts +130 -0
  56. package/src/types.ts +1 -0
  57. package/tests/constants-e2e.ts +1 -1
  58. package/tests/utils-e2e.ts +5 -0
  59. package/build/abi/DirectSwap.json +0 -1008
@@ -0,0 +1,871 @@
1
+ import {
2
+ Token,
3
+ Address,
4
+ ExchangePrices,
5
+ PoolPrices,
6
+ AdapterExchangeParam,
7
+ SimpleExchangeParam,
8
+ PoolLiquidity,
9
+ Logger,
10
+ ExchangeTxInfo,
11
+ OptimalSwapExchange,
12
+ PreprocessTransactionOptions,
13
+ } from '../../types';
14
+ import { SwapSide, Network, MAX_INT, MAX_UINT } from '../../constants';
15
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
16
+ import { getBigIntPow, getDexKeysWithNetwork } from '../../utils';
17
+ import { IDex } from '../idex';
18
+ import { IDexHelper } from '../../dex-helper/idex-helper';
19
+ import {
20
+ SwaapV2Data,
21
+ SwaapV2PriceLevel,
22
+ SwaapV2PriceLevels,
23
+ SwaapV2APIParameters,
24
+ SwaapV2QuoteError,
25
+ TokensMap,
26
+ SwaapV2TokensResponse,
27
+ } from './types';
28
+ import { getLocalDeadlineAsFriendlyPlaceholder, SimpleExchange } from '../simple-exchange';
29
+ import { Adapters, SwaapV2Config } from './config';
30
+ import { RateFetcher } from './rate-fetcher';
31
+ import routerAbi from '../../abi/swaap-v2/vault.json';
32
+ import BigNumber from 'bignumber.js';
33
+ import { BN_0, BN_1, getBigNumberPow } from '../../bignumber-constants';
34
+ import { Interface } from 'ethers/lib/utils';
35
+ import { assert } from 'ts-essentials';
36
+ import {
37
+ SWAAP_RFQ_API_URL,
38
+ SWAAP_RFQ_PRICES_ENDPOINT,
39
+ SWAAP_RFQ_QUOTE_ENDPOINT,
40
+ SWAAP_RFQ_API_PRICES_POLLING_INTERVAL_MS,
41
+ SWAAP_RFQ_PRICES_CACHES_TTL_S,
42
+ GAS_COST_ESTIMATION,
43
+ BATCH_SWAP_SELECTOR,
44
+ CALLER_SLOT,
45
+ SWAAP_BLACKLIST_TTL_S,
46
+ SWAAP_RFQ_TOKENS_ENDPOINT,
47
+ SWAAP_RESTRICT_TTL_S,
48
+ SWAAP_RESTRICTED_CACHE_KEY,
49
+ } from './constants';
50
+ import {
51
+ getPoolIdentifier,
52
+ getPriceLevelsCacheKey,
53
+ normalizeTokenAddress,
54
+ getPairName,
55
+ } from './utils';
56
+ import { Method } from '../../dex-helper/irequest-wrapper';
57
+ import { validateAndCast } from '../../lib/validators';
58
+ import { getTokensResponseValidator } from './validators';
59
+ import { SlippageCheckError } from '../generic-rfq/types';
60
+
61
+ const BLACKLISTED = 'blacklisted';
62
+
63
+ export class SwaapV2 extends SimpleExchange implements IDex<SwaapV2Data> {
64
+ readonly isStatePollingDex = true;
65
+ readonly hasConstantPriceLargeAmounts = false;
66
+ readonly needWrapNative = false;
67
+ readonly isFeeOnTransferSupported = false;
68
+ private rateFetcher: RateFetcher;
69
+ private tokensMap: TokensMap = {};
70
+ private swaapV2AuthToken: string;
71
+
72
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
73
+ getDexKeysWithNetwork(SwaapV2Config);
74
+
75
+ logger: Logger;
76
+
77
+ constructor(
78
+ readonly network: Network,
79
+ readonly dexKey: string,
80
+ readonly dexHelper: IDexHelper,
81
+ protected adapters = Adapters[network] || {},
82
+ protected routerInterface = new Interface(routerAbi),
83
+ ) {
84
+ super(dexHelper, dexKey);
85
+ this.logger = dexHelper.getLogger(dexKey);
86
+ const token = dexHelper.config.data.swaapV2AuthToken;
87
+ assert(
88
+ token !== undefined,
89
+ 'SwaapV2 auth token is not specified with env variable',
90
+ );
91
+
92
+ this.swaapV2AuthToken = token;
93
+
94
+ this.rateFetcher = new RateFetcher(
95
+ this.dexHelper,
96
+ this.dexKey,
97
+ this.logger,
98
+ {
99
+ rateConfig: {
100
+ pricesIntervalMs: SWAAP_RFQ_API_PRICES_POLLING_INTERVAL_MS,
101
+ pricesReqParams: this.getPriceLevelsReqParams(),
102
+ pricesCacheTTLSecs: SWAAP_RFQ_PRICES_CACHES_TTL_S,
103
+ },
104
+ },
105
+ );
106
+ }
107
+
108
+ async initializePricing(blockNumber: number): Promise<void> {
109
+ const { data } = await this.dexHelper.httpRequest.request<unknown>(
110
+ this.getTokensReqParams(),
111
+ );
112
+
113
+ const tokensResp = validateAndCast<SwaapV2TokensResponse>(
114
+ data,
115
+ getTokensResponseValidator,
116
+ );
117
+
118
+ this.tokensMap = Object.keys(tokensResp.tokens).reduce((acc, key: string) => {
119
+ acc[key.toLowerCase()] = tokensResp.tokens[key];
120
+ return acc;
121
+ }, {} as TokensMap);
122
+
123
+ if (!this.dexHelper.config.isSlave) {
124
+ await this.rateFetcher.start();
125
+ }
126
+
127
+ return;
128
+ }
129
+
130
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
131
+ return this.adapters[side] ? this.adapters[side] : null;
132
+ }
133
+
134
+ async getPoolIdentifiers(
135
+ srcToken: Token,
136
+ destToken: Token,
137
+ side: SwapSide,
138
+ blockNumber: number,
139
+ ): Promise<string[]> {
140
+ const normalizedSrcToken = this.normalizeToken(srcToken);
141
+ const normalizedDestToken = this.normalizeToken(destToken);
142
+
143
+ if (normalizedSrcToken.address === normalizedDestToken.address) {
144
+ return [];
145
+ }
146
+
147
+ const poolIdentifier = getPoolIdentifier(
148
+ this.dexKey,
149
+ normalizedSrcToken.address,
150
+ normalizedDestToken.address,
151
+ );
152
+
153
+ const levels = await this.getCachedLevels();
154
+ if (levels === null) {
155
+ return [];
156
+ }
157
+
158
+ return Object.keys(levels)
159
+ .map((pair: string) => {
160
+ return getPoolIdentifier(
161
+ this.dexKey,
162
+ levels[pair].base!,
163
+ levels[pair].quote!,
164
+ );
165
+ })
166
+ .filter((pi: string) => pi === poolIdentifier);
167
+ }
168
+
169
+ computePricesFromLevelsBids(
170
+ amounts: BigNumber[],
171
+ asksAndBids: SwaapV2PriceLevels,
172
+ srcToken: Token,
173
+ destToken: Token,
174
+ side: SwapSide,
175
+ ): bigint[] {
176
+ let levels: SwaapV2PriceLevel[] = [];
177
+ if (side === SwapSide.BUY) {
178
+ if (destToken.address === asksAndBids.base!) {
179
+ levels = asksAndBids.asks;
180
+ } else {
181
+ levels = this.invertPrices(asksAndBids.bids);
182
+ }
183
+ } else {
184
+ if (srcToken.address === asksAndBids.base!) {
185
+ levels = asksAndBids.bids;
186
+ } else {
187
+ levels = this.invertPrices(asksAndBids.asks);
188
+ }
189
+ }
190
+ return this.computeLevelsQuote(amounts, levels, srcToken, destToken, side);
191
+ }
192
+
193
+ inversePrice(priceLevel: SwaapV2PriceLevel): SwaapV2PriceLevel {
194
+ return {
195
+ level: priceLevel.level * priceLevel.price,
196
+ price: 1 / priceLevel.price,
197
+ };
198
+ }
199
+
200
+ invertPrices(levels: SwaapV2PriceLevel[]): SwaapV2PriceLevel[] {
201
+ return levels.map(pl => this.inversePrice(pl));
202
+ }
203
+
204
+ computeLevelsQuote(
205
+ amounts: BigNumber[],
206
+ levels: SwaapV2PriceLevel[],
207
+ srcToken: Token,
208
+ destToken: Token,
209
+ side: SwapSide,
210
+ ): bigint[] {
211
+ const size = levels.length;
212
+ if (size === 0) {
213
+ return amounts.map(_ => BigInt(0));
214
+ }
215
+ return amounts.map((amount: BigNumber) => {
216
+ if (amount.isZero() || amount.lt(BigNumber(levels[0].level))) {
217
+ return BigInt(0);
218
+ }
219
+
220
+ let i = 0;
221
+ let output = BN_0;
222
+ let previousLevel = BN_0;
223
+ let remaininig = BigNumber(amount);
224
+ let enoughLiquidity = false;
225
+ while (i < size) {
226
+ const levelDelta = BigNumber(levels[i].level).minus(previousLevel);
227
+ if (levelDelta.lte(remaininig)) {
228
+ output = output.plus(levelDelta.times(levels[i].price));
229
+ remaininig = remaininig.minus(levelDelta);
230
+ previousLevel = BigNumber(levels[i].level);
231
+ i += 1;
232
+ } else {
233
+ output = output.plus(remaininig.times(levels[i].price));
234
+ enoughLiquidity = true;
235
+ break;
236
+ }
237
+ }
238
+ if (enoughLiquidity) {
239
+ return BigInt(
240
+ output
241
+ .multipliedBy(
242
+ getBigNumberPow(
243
+ side === SwapSide.BUY ? srcToken.decimals : destToken.decimals,
244
+ ),
245
+ )
246
+ .toFixed(0),
247
+ );
248
+ }
249
+ return BigInt(0); // not enough liquidity
250
+ });
251
+ }
252
+
253
+ async getCachedLevels(): Promise<Record<string, SwaapV2PriceLevels> | null> {
254
+ const cachedLevels = await this.dexHelper.cache.get(
255
+ this.dexKey,
256
+ this.network,
257
+ getPriceLevelsCacheKey(this.dexKey),
258
+ );
259
+
260
+ if (cachedLevels) {
261
+ return JSON.parse(cachedLevels) as Record<string, SwaapV2PriceLevels>;
262
+ }
263
+
264
+ return null;
265
+ }
266
+
267
+ normalizeToken(token: Token): Token {
268
+ return {
269
+ address: normalizeTokenAddress(token.address),
270
+ decimals: token.decimals,
271
+ };
272
+ }
273
+
274
+ async getPricesVolume(
275
+ srcToken: Token,
276
+ destToken: Token,
277
+ amounts: bigint[],
278
+ side: SwapSide,
279
+ blockNumber: number,
280
+ limitPools?: string[],
281
+ ): Promise<null | ExchangePrices<SwaapV2Data>> {
282
+ try {
283
+ if(await this.isRestricted()) {
284
+ return null;
285
+ }
286
+
287
+ const normalizedSrcToken = this.normalizeToken(srcToken);
288
+ const normalizedDestToken = this.normalizeToken(destToken);
289
+
290
+ const requestedPoolIdentifier: string = getPoolIdentifier(
291
+ this.dexKey,
292
+ normalizedSrcToken.address,
293
+ normalizedDestToken.address,
294
+ );
295
+
296
+ if (normalizedSrcToken.address === normalizedDestToken.address) {
297
+ return null;
298
+ }
299
+
300
+ const pools =
301
+ limitPools ??
302
+ (await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber));
303
+
304
+ const levels = await this.getCachedLevels();
305
+ if (levels === null) {
306
+ return null;
307
+ }
308
+
309
+ const poolIdentifier = getPoolIdentifier(
310
+ this.dexKey,
311
+ normalizedSrcToken.address,
312
+ normalizedDestToken.address,
313
+ );
314
+
315
+ const levelEntries: SwaapV2PriceLevels[] = Object.keys(levels)
316
+ .map((pair: string) => {
317
+ if (pools.includes(poolIdentifier)) {
318
+ return levels[pair];
319
+ }
320
+ return undefined;
321
+ })
322
+ .filter((o): o is SwaapV2PriceLevels => o !== undefined);
323
+
324
+ const unitVolume = getBigNumberPow(
325
+ (side === SwapSide.SELL ? normalizedSrcToken : normalizedDestToken)
326
+ .decimals,
327
+ );
328
+
329
+ const amountsFloat = amounts.map(a =>
330
+ new BigNumber(a.toString()).dividedBy(unitVolume),
331
+ );
332
+
333
+ const prices = levelEntries.map((askAndBids: SwaapV2PriceLevels) => {
334
+
335
+ const unitPrice = this.computePricesFromLevelsBids(
336
+ [unitVolume],
337
+ askAndBids,
338
+ normalizedSrcToken,
339
+ normalizedDestToken,
340
+ side,
341
+ )[0];
342
+ const prices = this.computePricesFromLevelsBids(
343
+ amountsFloat,
344
+ askAndBids,
345
+ normalizedSrcToken,
346
+ normalizedDestToken,
347
+ side,
348
+ );
349
+
350
+ return {
351
+ gasCost: GAS_COST_ESTIMATION,
352
+ exchange: this.dexKey,
353
+ data: {},
354
+ prices,
355
+ unit: unitPrice,
356
+ poolIdentifier: requestedPoolIdentifier,
357
+ } as PoolPrices<SwaapV2Data>;
358
+ });
359
+
360
+ return prices;
361
+ } catch (e) {
362
+ this.logger.error(
363
+ `Error_getPrices ${srcToken}, ${destToken}, ${side}:`, e,
364
+ );
365
+ return null;
366
+ }
367
+ }
368
+
369
+ getBaseToken(poolIdentifier: string): string {
370
+ return poolIdentifier.split('_')[1];
371
+ }
372
+
373
+ getLevels(
374
+ askAndBids: SwaapV2PriceLevels,
375
+ side: SwapSide,
376
+ poolIdentifier: string,
377
+ ) {
378
+ const baseToken: string = this.getBaseToken(poolIdentifier);
379
+ if (side === SwapSide.SELL) {
380
+ return askAndBids.asks;
381
+ }
382
+ return askAndBids.asks;
383
+ }
384
+
385
+ async preProcessTransaction(
386
+ optimalSwapExchange: OptimalSwapExchange<SwaapV2Data>,
387
+ srcToken: Token,
388
+ destToken: Token,
389
+ side: SwapSide,
390
+ options: PreprocessTransactionOptions,
391
+ ): Promise<[OptimalSwapExchange<SwaapV2Data>, ExchangeTxInfo]> {
392
+
393
+ if(await this.isRestricted()) {
394
+ this.logger.warn(
395
+ `${this.dexKey}-${this.network}: protocol is restricted.`,
396
+ );
397
+ throw new Error(
398
+ `${this.dexKey}-${
399
+ this.network
400
+ }: protocol is restricted`,
401
+ );
402
+ }
403
+
404
+ if (await this.isBlacklisted(options.txOrigin)) {
405
+ this.logger.warn(
406
+ `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`,
407
+ );
408
+ throw new Error(
409
+ `${this.dexKey}-${
410
+ this.network
411
+ }: user=${options.txOrigin.toLowerCase()} is blacklisted`,
412
+ );
413
+ }
414
+
415
+ const isSell = side === SwapSide.SELL;
416
+
417
+ const normalizedSrcToken = this.normalizeToken(srcToken);
418
+ const normalizedDestToken = this.normalizeToken(destToken);
419
+
420
+ const tolerance = (
421
+ options.slippageFactor > BN_1
422
+ ? options.slippageFactor.minus(BN_1)
423
+ : BN_1.minus(options.slippageFactor)
424
+ ).toNumber();
425
+
426
+ try {
427
+ const quote = await this.rateFetcher.getQuote(
428
+ this.network,
429
+ normalizedSrcToken,
430
+ normalizedDestToken,
431
+ isSell ? optimalSwapExchange.srcAmount : optimalSwapExchange.destAmount,
432
+ isSell ? 1 : 2,
433
+ options.txOrigin,
434
+ this.augustusAddress,
435
+ tolerance,
436
+ this.getQuoteReqParams(),
437
+ );
438
+
439
+ if (!quote.success) {
440
+ const message = `${this.dexKey}-${
441
+ this.network
442
+ }: Failed to fetch RFQ for ${getPairName(
443
+ normalizedSrcToken.address,
444
+ normalizedDestToken.address,
445
+ )}: ${JSON.stringify(quote)}`;
446
+ this.logger.warn(message);
447
+ throw new SwaapV2QuoteError(message);
448
+ } else if (!quote.calldata) {
449
+ const message = `${this.dexKey}-${
450
+ this.network
451
+ }: Failed to fetch RFQ for ${getPairName(
452
+ normalizedSrcToken.address,
453
+ normalizedDestToken.address,
454
+ )}. Missing quote data`;
455
+ this.logger.warn(message);
456
+ throw new SwaapV2QuoteError(message);
457
+ } else if (!quote.router) {
458
+ const message = `${this.dexKey}-${
459
+ this.network
460
+ }: Failed to fetch RFQ for ${getPairName(
461
+ normalizedSrcToken.address,
462
+ normalizedDestToken.address,
463
+ )}. Missing router address`;
464
+ this.logger.warn(message);
465
+ throw new SwaapV2QuoteError(message);
466
+ }
467
+
468
+ const srcAmount = BigInt(optimalSwapExchange.srcAmount);
469
+ const destAmount = BigInt(optimalSwapExchange.destAmount);
470
+ const quoteTokenAmount = BigInt(quote.amount);
471
+ const slippageFactor = options.slippageFactor;
472
+
473
+ if(side === SwapSide.SELL) {
474
+ if (
475
+ quoteTokenAmount <
476
+ BigInt(
477
+ new BigNumber(destAmount.toString())
478
+ .times(slippageFactor)
479
+ .toFixed(0),
480
+ )
481
+ ) {
482
+ const message = `${this.dexKey}-${this.network}: too much slippage on quote ${side} quoteTokenAmount ${quoteTokenAmount} / destAmount ${destAmount} < ${slippageFactor}`;
483
+ this.logger.warn(message);
484
+ throw new SlippageCheckError(message);
485
+ }
486
+ } else {
487
+ if (
488
+ quoteTokenAmount >
489
+ BigInt(slippageFactor.times(srcAmount.toString()).toFixed(0))
490
+ ) {
491
+ const message = `${this.dexKey}-${
492
+ this.network
493
+ }: too much slippage on quote ${side} baseTokenAmount ${srcAmount} / srcAmount ${srcAmount} > ${slippageFactor.toFixed()}`;
494
+ this.logger.warn(message);
495
+ throw new SlippageCheckError(message);
496
+ }
497
+ }
498
+
499
+ return [
500
+ {
501
+ ...optimalSwapExchange,
502
+ data: {
503
+ router: quote.router,
504
+ callData: quote.calldata,
505
+ },
506
+ },
507
+ { deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()) },
508
+ ];
509
+ } catch (e) {
510
+ if (
511
+ e instanceof Error &&
512
+ e.message.endsWith('Request failed with status code 403')
513
+ ) {
514
+ await this.setBlacklist(options.txOrigin);
515
+ this.logger.warn(
516
+ `${this.dexKey}-${this.network}: Encountered restricted user=${options.txOrigin}. Adding to local blacklist cache`,
517
+ );
518
+ } else {
519
+ await this.restrict();
520
+ }
521
+
522
+ throw e;
523
+ }
524
+ }
525
+
526
+ async restrict(ttl: number = SWAAP_RESTRICT_TTL_S): Promise<boolean> {
527
+ await this.dexHelper.cache.setex(
528
+ this.dexKey,
529
+ this.network,
530
+ SWAAP_RESTRICTED_CACHE_KEY,
531
+ ttl,
532
+ 'true',
533
+ );
534
+ return true;
535
+ }
536
+
537
+ async isRestricted(): Promise<boolean> {
538
+ const result = await this.dexHelper.cache.get(
539
+ this.dexKey,
540
+ this.network,
541
+ SWAAP_RESTRICTED_CACHE_KEY,
542
+ );
543
+
544
+ return result === 'true';
545
+ }
546
+
547
+ async isBlacklisted(txOrigin: Address): Promise<boolean> {
548
+ const result = await this.dexHelper.cache.get(
549
+ this.dexKey,
550
+ this.network,
551
+ this.getBlackListKey(txOrigin),
552
+ );
553
+ return result === BLACKLISTED;
554
+ }
555
+
556
+ getBlackListKey(address: Address) {
557
+ return `blacklist_${address}`.toLowerCase();
558
+ }
559
+
560
+ async setBlacklist(
561
+ txOrigin: Address,
562
+ ttl: number = SWAAP_BLACKLIST_TTL_S,
563
+ ): Promise<boolean> {
564
+ await this.dexHelper.cache.setex(
565
+ this.dexKey,
566
+ this.network,
567
+ this.getBlackListKey(txOrigin),
568
+ ttl,
569
+ BLACKLISTED,
570
+ );
571
+ return true;
572
+ }
573
+
574
+ // Returns estimated gas cost of calldata for this DEX in multiSwap
575
+ getCalldataGasCost(poolPrices: PoolPrices<SwaapV2Data>): number | number[] {
576
+ return (
577
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
578
+ CALLDATA_GAS_COST.LENGTH_LARGE +
579
+ // ParentStruct header
580
+ CALLDATA_GAS_COST.OFFSET_SMALL +
581
+ // ParentStruct -> swaps[] header
582
+ CALLDATA_GAS_COST.OFFSET_LARGE +
583
+ // ParentStruct -> assets[] header
584
+ CALLDATA_GAS_COST.OFFSET_LARGE +
585
+ // ParentStruct -> funds
586
+ CALLDATA_GAS_COST.ADDRESS +
587
+ CALLDATA_GAS_COST.BOOL +
588
+ CALLDATA_GAS_COST.ADDRESS +
589
+ CALLDATA_GAS_COST.BOOL +
590
+ // ParentStruct -> limits[] header
591
+ CALLDATA_GAS_COST.OFFSET_LARGE +
592
+ // ParentStruct -> deadline
593
+ CALLDATA_GAS_COST.TIMESTAMP +
594
+ // ParentStruct -> swaps[]
595
+ CALLDATA_GAS_COST.LENGTH_SMALL +
596
+ // ParentStruct -> swaps[0] header
597
+ CALLDATA_GAS_COST.OFFSET_SMALL +
598
+ // ParentStruct -> swaps[0] -> poolId
599
+ CALLDATA_GAS_COST.FULL_WORD +
600
+ // ParentStruct -> swaps[0] -> assetInIndex
601
+ CALLDATA_GAS_COST.INDEX +
602
+ // ParentStruct -> swaps[0] -> assetOutIndex
603
+ CALLDATA_GAS_COST.INDEX +
604
+ // ParentStruct -> swaps[0] -> amount
605
+ CALLDATA_GAS_COST.AMOUNT +
606
+ // ParentStruct -> swaps[0] -> userData header
607
+ CALLDATA_GAS_COST.OFFSET_SMALL +
608
+ // ParentStruct -> swaps[0] -> userData
609
+ CALLDATA_GAS_COST.ZERO +
610
+ // ParentStruct -> assets[]
611
+ CALLDATA_GAS_COST.LENGTH_SMALL +
612
+ // ParentStruct -> assets[0:2]
613
+ CALLDATA_GAS_COST.ADDRESS * 2 +
614
+ // ParentStruct -> limits[]
615
+ CALLDATA_GAS_COST.LENGTH_SMALL +
616
+ // ParentStruct -> limits[0:2]
617
+ CALLDATA_GAS_COST.FULL_WORD * 2 +
618
+ // Bool -> IsBatchSwap
619
+ CALLDATA_GAS_COST.BOOL
620
+ );
621
+ }
622
+
623
+ getAdapterParam(
624
+ srcToken: string,
625
+ destToken: string,
626
+ srcAmount: string,
627
+ destAmount: string,
628
+ data: SwaapV2Data,
629
+ side: SwapSide,
630
+ ): AdapterExchangeParam {
631
+ const { router, callData } = data;
632
+
633
+ assert(
634
+ router !== undefined,
635
+ `${this.dexKey}-${this.network}: router undefined`,
636
+ );
637
+
638
+ assert(
639
+ callData !== undefined,
640
+ `${this.dexKey}-${this.network}: callData undefined`,
641
+ );
642
+
643
+ let payload: string;
644
+ const truncatedCalldata = '0x' + callData.slice(10); // droping the function selector
645
+ const isBatchSwap = callData.slice(0, 10) === BATCH_SWAP_SELECTOR;
646
+
647
+ if (isBatchSwap) {
648
+ const batchswapAbi = routerAbi.filter(abi => abi.name === 'batchSwap');
649
+
650
+ const callDataStruct = this.abiCoder.decodeParameters(
651
+ batchswapAbi[0].inputs!,
652
+ truncatedCalldata,
653
+ );
654
+
655
+ const callerSlots = Array(callDataStruct.swaps.length).fill(160);
656
+
657
+ payload = this.abiCoder.encodeParameters(
658
+ [
659
+ 'bool',
660
+ 'uint256[]',
661
+ {
662
+ ParentStruct: {
663
+ 'swaps[]': {
664
+ poolId: 'bytes32',
665
+ assetInIndex: 'uint256',
666
+ assetOutIndex: 'uint256',
667
+ amount: 'uint256',
668
+ userData: 'bytes',
669
+ },
670
+ assets: 'address[]',
671
+ funds: {
672
+ sender: 'address',
673
+ fromInternalBalance: 'bool',
674
+ recipient: 'address',
675
+ toInternalBalance: 'bool',
676
+ },
677
+ limits: 'int256[]',
678
+ },
679
+ },
680
+ ],
681
+ [
682
+ isBatchSwap,
683
+ callerSlots,
684
+ {
685
+ swaps: callDataStruct.swaps,
686
+ assets: callDataStruct.assets,
687
+ funds: callDataStruct.funds,
688
+ limits: callDataStruct.limits.map((_: any) => MAX_INT),
689
+ },
690
+ ],
691
+ );
692
+ } else {
693
+ const swapAbi = routerAbi.filter(abi => abi.name === 'swap');
694
+
695
+ const callDataStruct = this.abiCoder.decodeParameters(
696
+ swapAbi[0].inputs!,
697
+ truncatedCalldata,
698
+ );
699
+
700
+ const callerSlot = CALLER_SLOT;
701
+
702
+ payload = this.abiCoder.encodeParameters(
703
+ [
704
+ 'bool',
705
+ 'uint256',
706
+ {
707
+ ParentStruct: {
708
+ swap: {
709
+ poolId: 'bytes32',
710
+ kind: 'uint8',
711
+ assetIn: 'address',
712
+ assetOut: 'address',
713
+ amount: 'uint256',
714
+ userData: 'bytes',
715
+ },
716
+ funds: {
717
+ sender: 'address',
718
+ fromInternalBalance: 'bool',
719
+ recipient: 'address',
720
+ toInternalBalance: 'bool',
721
+ },
722
+ limit: 'uint256',
723
+ },
724
+ },
725
+ ],
726
+ [
727
+ isBatchSwap,
728
+ callerSlot,
729
+ {
730
+ swap: {
731
+ poolId: callDataStruct.singleSwap.poolId,
732
+ kind: callDataStruct.singleSwap.kind,
733
+ assetIn: callDataStruct.singleSwap.assetIn,
734
+ assetOut: callDataStruct.singleSwap.assetOut,
735
+ amount: callDataStruct.singleSwap.amount,
736
+ userData: callDataStruct.singleSwap.userData,
737
+ },
738
+ funds: {
739
+ sender: callDataStruct.funds.sender,
740
+ fromInternalBalance: callDataStruct.funds.fromInternalBalance,
741
+ recipient: callDataStruct.funds.recipient,
742
+ toInternalBalance: callDataStruct.funds.toInternalBalance,
743
+ },
744
+ limit: side === SwapSide.BUY ? MAX_UINT : '0',
745
+ },
746
+ ],
747
+ );
748
+ }
749
+
750
+ return {
751
+ targetExchange: router,
752
+ payload: payload,
753
+ networkFee: '0',
754
+ };
755
+ }
756
+
757
+ async getSimpleParam(
758
+ srcToken: string,
759
+ destToken: string,
760
+ srcAmount: string,
761
+ destAmount: string,
762
+ data: SwaapV2Data,
763
+ side: SwapSide,
764
+ ): Promise<SimpleExchangeParam> {
765
+ const { router, callData } = data;
766
+
767
+ assert(
768
+ router !== undefined,
769
+ `${this.dexKey}-${this.network}: router undefined`,
770
+ );
771
+
772
+ assert(
773
+ callData !== undefined,
774
+ `${this.dexKey}-${this.network}: callData undefined`,
775
+ );
776
+
777
+ return this.buildSimpleParamWithoutWETHConversion(
778
+ srcToken,
779
+ srcAmount,
780
+ destToken,
781
+ destAmount,
782
+ callData,
783
+ router,
784
+ );
785
+ }
786
+
787
+ computeMaxLiquidity = (
788
+ levels: SwaapV2PriceLevels,
789
+ tokenAddress: string,
790
+ ): number => {
791
+ if (tokenAddress === levels.base!) {
792
+ return (
793
+ (levels.asks[levels.asks.length - 1]?.level ?? 0) *
794
+ levels.asks[0]?.price
795
+ );
796
+ }
797
+ return (
798
+ (levels.asks[levels.bids.length - 1]?.level ?? 0) * levels.asks[0]?.price
799
+ );
800
+ };
801
+
802
+ async getTopPoolsForToken(
803
+ tokenAddress: Address,
804
+ limit: number,
805
+ ): Promise<PoolLiquidity[]> {
806
+
807
+ if(await this.isRestricted()) {
808
+ return [];
809
+ }
810
+
811
+ const normalizedTokenAddress = normalizeTokenAddress(tokenAddress);
812
+
813
+ const pLevels = await this.getCachedLevels();
814
+
815
+ if (pLevels === null) {
816
+ return [];
817
+ }
818
+
819
+ return Object.keys(pLevels)
820
+ .filter((pair: string) => {
821
+ return (
822
+ normalizedTokenAddress === pLevels[pair].base ||
823
+ normalizedTokenAddress === pLevels[pair].quote
824
+ );
825
+ })
826
+ .map((pair: string) => {
827
+ return {
828
+ exchange: this.dexKey,
829
+ connectorTokens: [this.getTokenFromAddress(normalizedTokenAddress === pLevels[pair].base ? pLevels[pair].quote : pLevels[pair].base)],
830
+ liquidityUSD: this.computeMaxLiquidity(pLevels[pair], tokenAddress),
831
+ } as PoolLiquidity;
832
+ })
833
+ .sort((pl: PoolLiquidity) => {
834
+ return -pl.liquidityUSD;
835
+ })
836
+ .slice(0, limit);
837
+ }
838
+
839
+ getTokenFromAddress(address: Address): Token {
840
+ return this.tokensMap[address];
841
+ }
842
+
843
+ releaseResources(): void {
844
+ if (this.rateFetcher) {
845
+ this.rateFetcher.stop();
846
+ }
847
+ }
848
+
849
+ getPriceLevelsReqParams(): SwaapV2APIParameters {
850
+ return this.getAPIReqParams(SWAAP_RFQ_PRICES_ENDPOINT, 'GET');
851
+ }
852
+
853
+ getQuoteReqParams(): SwaapV2APIParameters {
854
+ return this.getAPIReqParams(SWAAP_RFQ_QUOTE_ENDPOINT, 'POST');
855
+ }
856
+
857
+ getTokensReqParams(): SwaapV2APIParameters {
858
+ return this.getAPIReqParams(SWAAP_RFQ_TOKENS_ENDPOINT, 'GET');
859
+ }
860
+
861
+ getAPIReqParams(endpoint: string, method: Method): SwaapV2APIParameters {
862
+ return {
863
+ url: `${SWAAP_RFQ_API_URL}/${endpoint}`,
864
+ params: {
865
+ networkId: this.network,
866
+ },
867
+ headers: { 'x-api-key': this.swaapV2AuthToken },
868
+ method: method,
869
+ };
870
+ }
871
+ }