@paraswap/dex-lib 3.4.2-balancer-v2-single-swap.0 → 3.4.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 (110) hide show
  1. package/README.md +377 -377
  2. package/build/config.js +9 -5
  3. package/build/config.js.map +1 -1
  4. package/build/dex/algebra/algebra.js +25 -25
  5. package/build/dex/balancer-v1/balancer-v1.js +34 -34
  6. package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
  7. package/build/dex/balancer-v2/balancer-v2.js +65 -78
  8. package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
  9. package/build/dex/camelot/camelot.js +27 -27
  10. package/build/dex/kyberdmm/kyberdmm.js +27 -27
  11. package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
  12. package/build/dex/solidly/solidly.js +27 -27
  13. package/build/dex/solidly-v3/solidly-v3.js +25 -25
  14. package/build/dex/swaap-v2/config.js +15 -0
  15. package/build/dex/swaap-v2/config.js.map +1 -1
  16. package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
  17. package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
  18. package/funding.json +5 -5
  19. package/package.json +1 -1
  20. package/src/config.ts +500 -496
  21. package/src/constants.ts +67 -67
  22. package/src/dex/aave-v2/aave-v2.ts +409 -409
  23. package/src/dex/aave-v3/aave-v3.ts +320 -320
  24. package/src/dex/algebra/algebra.ts +1180 -1180
  25. package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
  26. package/src/dex/augustus-rfq.ts +180 -180
  27. package/src/dex/bProtocol/bProtocol.ts +108 -108
  28. package/src/dex/balancer-v1/balancer-v1.ts +572 -572
  29. package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
  30. package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
  31. package/src/dex/bancor/bancor.ts +128 -128
  32. package/src/dex/camelot/camelot.ts +873 -873
  33. package/src/dex/compound/compound.ts +107 -107
  34. package/src/dex/curve-v1/config.ts +1179 -1179
  35. package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
  36. package/src/dex/curve-v1/curve-v1.ts +1315 -1315
  37. package/src/dex/curve-v1-factory/config.ts +1018 -1018
  38. package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
  39. package/src/dex/curve-v2/curve-v2.ts +440 -440
  40. package/src/dex/dexalot/dexalot.ts +1100 -1100
  41. package/src/dex/dodo-v1/dodo-v1.ts +132 -132
  42. package/src/dex/dodo-v2.ts +266 -266
  43. package/src/dex/etherfi.ts +239 -239
  44. package/src/dex/generic-rfq/generic-rfq.ts +431 -431
  45. package/src/dex/gmx/gmx.ts +321 -321
  46. package/src/dex/hashflow/hashflow.ts +1135 -1135
  47. package/src/dex/idex.ts +254 -254
  48. package/src/dex/index.ts +357 -357
  49. package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
  50. package/src/dex/jarvis.ts +262 -262
  51. package/src/dex/kyberdmm/kyberdmm.ts +649 -649
  52. package/src/dex/lido/lido.ts +110 -110
  53. package/src/dex/maker-psm/maker-psm.ts +592 -592
  54. package/src/dex/maverick-v1/maverick-v1.ts +537 -537
  55. package/src/dex/nerve/config.ts +635 -635
  56. package/src/dex/nerve/nerve-e2e.test.ts +731 -731
  57. package/src/dex/nerve/nerve-events.test.ts +911 -911
  58. package/src/dex/nerve/nerve.ts +467 -467
  59. package/src/dex/onebit/onebit.ts +113 -113
  60. package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
  61. package/src/dex/paraswap-limit-orders/config.ts +60 -60
  62. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
  63. package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
  64. package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
  65. package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
  66. package/src/dex/quick-perps/quick-perps.ts +337 -337
  67. package/src/dex/quickswap/config.ts +16 -16
  68. package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
  69. package/src/dex/smoothy/smoothy.ts +110 -110
  70. package/src/dex/solidly/config.ts +225 -225
  71. package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
  72. package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
  73. package/src/dex/solidly/solidly.ts +687 -687
  74. package/src/dex/solidly-v3/solidly-v3.ts +968 -968
  75. package/src/dex/spark/spark.ts +284 -284
  76. package/src/dex/stable-pool/stable-pool.ts +129 -129
  77. package/src/dex/swaap-v2/config.ts +15 -0
  78. package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
  79. package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
  80. package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
  81. package/src/dex/swell/swell.ts +312 -312
  82. package/src/dex/synthetix/synthetix.ts +420 -420
  83. package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
  84. package/src/dex/uniswap-v2/config.ts +637 -637
  85. package/src/dex/uniswap-v2/constants.ts +58 -58
  86. package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
  87. package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
  88. package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
  89. package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
  90. package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
  91. package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
  92. package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
  93. package/src/dex/uniswap-v3.ts +242 -242
  94. package/src/dex/weth/config.ts +73 -73
  95. package/src/dex/weth/weth.ts +230 -230
  96. package/src/dex/wombat/wombat.ts +467 -467
  97. package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
  98. package/src/dex/wsteth/wsteth.ts +304 -304
  99. package/src/dex/zerox/index.ts +328 -328
  100. package/src/executor/Executor01BytecodeBuilder.ts +474 -474
  101. package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
  102. package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
  103. package/src/executor/ExecutorDetector.ts +131 -131
  104. package/src/executor/constants.ts +66 -66
  105. package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
  106. package/src/executor/utils.ts +62 -62
  107. package/src/router/simpleswap.ts +366 -366
  108. package/src/types.ts +342 -342
  109. package/tests/constants-e2e.ts +1632 -1622
  110. package/tests/generate-new-limit-order.ts +107 -107
@@ -1,1100 +1,1100 @@
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
- TransferFeeParams,
14
- NumberAsString,
15
- DexExchangeParam,
16
- } from '../../types';
17
- import {
18
- SwapSide,
19
- Network,
20
- ETHER_ADDRESS,
21
- NULL_ADDRESS,
22
- } from '../../constants';
23
- import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
24
- import { getDexKeysWithNetwork, isAxiosError } from '../../utils';
25
- import { IDex } from '../idex';
26
- import { IDexHelper } from '../../dex-helper/idex-helper';
27
- import {
28
- ClobSide,
29
- DexalotData,
30
- PairData,
31
- PairDataMap,
32
- PriceDataMap,
33
- DexalotRfqError,
34
- DexalotAPIParameters,
35
- RFQResponse,
36
- RFQResponseError,
37
- TokenAddrDataMap,
38
- TokenDataMap,
39
- } from './types';
40
- import {
41
- SlippageCheckError,
42
- TooStrictSlippageCheckError,
43
- } from '../generic-rfq/types';
44
- import { SimpleExchange } from '../simple-exchange';
45
- import { Adapters, DexalotConfig } from './config';
46
- import { RateFetcher } from './rate-fetcher';
47
- import mainnetRFQAbi from '../../abi/dexalot/DexalotMainnetRFQ.json';
48
- import { Interface } from 'ethers/lib/utils';
49
- import { assert } from 'ts-essentials';
50
- import {
51
- DEXALOT_API_URL,
52
- DEXALOT_API_PRICES_POLLING_INTERVAL_MS,
53
- DEXALOT_PRICES_CACHES_TTL_S,
54
- DEXALOT_GAS_COST,
55
- DEXALOT_PAIRS_CACHES_TTL_S,
56
- DEXALOT_API_PAIRS_POLLING_INTERVAL_MS,
57
- DEXALOT_TOKENS_CACHES_TTL_S,
58
- DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS,
59
- DEXALOT_RATE_LIMITED_TTL_S,
60
- DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION,
61
- DEXALOT_RESTRICTED_CACHE_KEY,
62
- DEXALOT_RESTRICT_TTL_S,
63
- DEXALOT_RATELIMIT_CACHE_VALUE,
64
- DEXALOT_BLACKLIST_CACHES_TTL_S,
65
- DEXALOT_FIRM_QUOTE_TIMEOUT_MS,
66
- } from './constants';
67
- import { BI_MAX_UINT256 } from '../../bigint-constants';
68
- import { ethers } from 'ethers';
69
- import BigNumber from 'bignumber.js';
70
- import { Method } from '../../dex-helper/irequest-wrapper';
71
-
72
- export class Dexalot extends SimpleExchange implements IDex<DexalotData> {
73
- readonly isStatePollingDex = true;
74
- readonly hasConstantPriceLargeAmounts = false;
75
- readonly needWrapNative = false;
76
- readonly isFeeOnTransferSupported = false;
77
- private rateFetcher: RateFetcher;
78
-
79
- private dexalotAuthToken: string;
80
-
81
- private pricesCacheKey: string;
82
- private pairsCacheKey: string;
83
- private tokensAddrCacheKey: string;
84
- private tokensCacheKey: string;
85
- private blacklistCacheKey: string;
86
- private tokensMap: TokenDataMap = {};
87
-
88
- public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
89
- getDexKeysWithNetwork(DexalotConfig);
90
-
91
- logger: Logger;
92
-
93
- constructor(
94
- readonly network: Network,
95
- readonly dexKey: string,
96
- readonly dexHelper: IDexHelper,
97
- protected adapters = Adapters[network] || {},
98
- readonly mainnetRFQAddress: string = DexalotConfig['Dexalot'][network]
99
- .mainnetRFQAddress,
100
- protected rfqInterface = new Interface(mainnetRFQAbi),
101
- ) {
102
- super(dexHelper, dexKey);
103
- this.logger = dexHelper.getLogger(dexKey);
104
-
105
- const authToken = dexHelper.config.data.dexalotAuthToken;
106
- assert(
107
- authToken !== undefined,
108
- 'Dexalot auth token is not specified with env variable',
109
- );
110
- this.dexalotAuthToken = authToken;
111
-
112
- this.pricesCacheKey = 'prices';
113
- this.pairsCacheKey = 'pairs';
114
- this.tokensAddrCacheKey = 'tokens_addr';
115
- this.tokensCacheKey = 'tokens';
116
- this.blacklistCacheKey = 'blacklist';
117
-
118
- this.rateFetcher = new RateFetcher(
119
- this.dexHelper,
120
- this.dexKey,
121
- this.network,
122
- this.logger,
123
- {
124
- rateConfig: {
125
- pairsIntervalMs: DEXALOT_API_PAIRS_POLLING_INTERVAL_MS,
126
- pricesIntervalMs: DEXALOT_API_PRICES_POLLING_INTERVAL_MS,
127
- blacklistIntervalMs: DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS,
128
- pairsReqParams: this.getAPIReqParams('api/rfq/pairs', 'GET'),
129
- pricesReqParams: this.getAPIReqParams('api/rfq/prices', 'GET'),
130
- blacklistReqParams: this.getAPIReqParams('api/rfq/blacklist', 'GET'),
131
- pairsCacheKey: this.pairsCacheKey,
132
- pairsCacheTTLSecs: DEXALOT_PAIRS_CACHES_TTL_S,
133
- pricesCacheKey: this.pricesCacheKey,
134
- pricesCacheTTLSecs: DEXALOT_PRICES_CACHES_TTL_S,
135
- tokensAddrCacheKey: this.tokensAddrCacheKey,
136
- tokensCacheKey: this.tokensCacheKey,
137
- tokensCacheTTLSecs: DEXALOT_TOKENS_CACHES_TTL_S,
138
- blacklistCacheKey: this.blacklistCacheKey,
139
- blacklistCacheTTLSecs: DEXALOT_BLACKLIST_CACHES_TTL_S,
140
- },
141
- },
142
- );
143
- }
144
-
145
- async initializePricing(blockNumber: number): Promise<void> {
146
- if (!this.dexHelper.config.isSlave) {
147
- this.rateFetcher.start();
148
- }
149
-
150
- return;
151
- }
152
-
153
- getAdapters(side: SwapSide): { name: string; index: number }[] | null {
154
- return this.adapters[side] ? this.adapters[side] : null;
155
- }
156
-
157
- getPairString(baseToken: Token, quoteToken: Token): string {
158
- return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase();
159
- }
160
-
161
- async getPairData(
162
- srcToken: Token,
163
- destToken: Token,
164
- ): Promise<PairData | null> {
165
- const normalizedSrcToken = this.normalizeToken(srcToken);
166
- const normalizedDestToken = this.normalizeToken(destToken);
167
- if (normalizedSrcToken.address === normalizedDestToken.address) {
168
- return null;
169
- }
170
-
171
- const cachedTokens = (await this.getCachedTokens()) || {};
172
- if (
173
- !(normalizedSrcToken.address in cachedTokens) ||
174
- !(normalizedDestToken.address in cachedTokens)
175
- ) {
176
- return null;
177
- }
178
- normalizedSrcToken.symbol = cachedTokens[normalizedSrcToken.address].symbol;
179
- normalizedDestToken.symbol =
180
- cachedTokens[normalizedDestToken.address].symbol;
181
-
182
- const cachedPairs = (await this.getCachedPairs()) || {};
183
-
184
- const potentialPairs = [
185
- {
186
- identifier: this.getPairString(normalizedSrcToken, normalizedDestToken),
187
- isSrcBase: true,
188
- },
189
- {
190
- identifier: this.getPairString(normalizedDestToken, normalizedSrcToken),
191
- isSrcBase: false,
192
- },
193
- ];
194
-
195
- for (const pair of potentialPairs) {
196
- if (pair.identifier in cachedPairs) {
197
- const pairData = cachedPairs[pair.identifier];
198
- pairData.isSrcBase = pair.isSrcBase;
199
- return pairData;
200
- }
201
- }
202
- return null;
203
- }
204
-
205
- getIdentifier(srcAddress: Address, destAddress: Address) {
206
- const sortedAddresses =
207
- srcAddress < destAddress
208
- ? [srcAddress, destAddress]
209
- : [destAddress, srcAddress];
210
-
211
- return `${
212
- this.dexKey
213
- }_${sortedAddresses[0].toLowerCase()}_${sortedAddresses[1].toLowerCase()}`;
214
- }
215
-
216
- async getPoolIdentifiers(
217
- srcToken: Token,
218
- destToken: Token,
219
- side: SwapSide,
220
- blockNumber: number,
221
- ): Promise<string[]> {
222
- if (!srcToken || !destToken) {
223
- return [];
224
- }
225
- const pairData = await this.getPairData(srcToken, destToken);
226
- if (!pairData) {
227
- return [];
228
- }
229
-
230
- const tokensAddr = (await this.getCachedTokensAddr()) || {};
231
-
232
- return [
233
- this.getIdentifier(
234
- tokensAddr[pairData.base.toLowerCase()],
235
- tokensAddr[pairData.quote.toLowerCase()],
236
- ),
237
- ];
238
- }
239
-
240
- async getCachedPairs(): Promise<PairDataMap | null> {
241
- const cachedPairs = await this.dexHelper.cache.get(
242
- this.dexKey,
243
- this.network,
244
- this.pairsCacheKey,
245
- );
246
-
247
- if (cachedPairs) {
248
- return JSON.parse(cachedPairs) as PairDataMap;
249
- }
250
-
251
- return null;
252
- }
253
-
254
- async getCachedPrices(): Promise<PriceDataMap | null> {
255
- const cachedPrices = await this.dexHelper.cache.get(
256
- this.dexKey,
257
- this.network,
258
- this.pricesCacheKey,
259
- );
260
-
261
- if (cachedPrices) {
262
- return JSON.parse(cachedPrices) as PriceDataMap;
263
- }
264
-
265
- return null;
266
- }
267
-
268
- async getCachedTokensAddr(): Promise<TokenAddrDataMap | null> {
269
- const cachedTokensAddr = await this.dexHelper.cache.get(
270
- this.dexKey,
271
- this.network,
272
- this.tokensAddrCacheKey,
273
- );
274
-
275
- if (cachedTokensAddr) {
276
- return JSON.parse(cachedTokensAddr) as TokenAddrDataMap;
277
- }
278
-
279
- return null;
280
- }
281
-
282
- async getCachedTokens(): Promise<TokenDataMap | null> {
283
- const cachedTokens = await this.dexHelper.cache.get(
284
- this.dexKey,
285
- this.network,
286
- this.tokensCacheKey,
287
- );
288
-
289
- if (cachedTokens) {
290
- return JSON.parse(cachedTokens) as TokenDataMap;
291
- }
292
-
293
- return null;
294
- }
295
-
296
- normalizeAddress(address: string): string {
297
- return address.toLowerCase() === ETHER_ADDRESS
298
- ? NULL_ADDRESS
299
- : address.toLowerCase();
300
- }
301
-
302
- denormalizeAddress(address: string): string {
303
- return address.toLowerCase() === NULL_ADDRESS
304
- ? ETHER_ADDRESS
305
- : address.toLowerCase();
306
- }
307
-
308
- // Dexalot protocol for native token expects 0x00000... instead of 0xeeeee...
309
- normalizeToken(token: Token): Token {
310
- return {
311
- address: this.normalizeAddress(token.address),
312
- decimals: token.decimals,
313
- };
314
- }
315
-
316
- denormalizeToken(token: Token): Token {
317
- return {
318
- address: this.denormalizeAddress(token.address),
319
- decimals: token.decimals,
320
- };
321
- }
322
-
323
- calculateOrderPrice(
324
- amounts: bigint[],
325
- orderbook: string[][],
326
- baseToken: Token,
327
- quoteToken: Token,
328
- isInputQuote: boolean,
329
- ) {
330
- let result = [];
331
-
332
- for (let i = 0; i < amounts.length; i++) {
333
- let amt = amounts[i];
334
- if (amt === 0n) {
335
- result.push(amt);
336
- continue;
337
- }
338
-
339
- let left = 0,
340
- right = orderbook.length;
341
- let qty = BigInt(0);
342
-
343
- while (left < right) {
344
- let mid = Math.floor((left + right) / 2);
345
- qty = BigInt(
346
- ethers.utils
347
- .parseUnits(orderbook[mid][1], quoteToken.decimals)
348
- .toString(),
349
- );
350
- if (isInputQuote) {
351
- const price = BigInt(
352
- ethers.utils
353
- .parseUnits(orderbook[mid][0], baseToken.decimals)
354
- .toString(),
355
- );
356
- qty =
357
- (qty * BigInt(10 ** (baseToken.decimals * 2))) /
358
- (price * BigInt(10 ** quoteToken.decimals));
359
- }
360
- if (qty <= amt) {
361
- left = mid + 1;
362
- } else {
363
- right = mid;
364
- }
365
- }
366
-
367
- let price = BigInt(0),
368
- amount = BigInt(0);
369
- if (amounts[i] === qty) {
370
- price = BigInt(
371
- ethers.utils
372
- .parseUnits(orderbook[left][0], baseToken.decimals)
373
- .toString(),
374
- );
375
- amount = amounts[i];
376
- } else if (left === 0) {
377
- price = 0n;
378
- } else if (left < orderbook.length) {
379
- const lPrice = BigInt(
380
- ethers.utils
381
- .parseUnits(orderbook[left - 1][0], baseToken.decimals)
382
- .toString(),
383
- );
384
- const rPrice = BigInt(
385
- ethers.utils
386
- .parseUnits(orderbook[left][0], baseToken.decimals)
387
- .toString(),
388
- );
389
- let lQty = BigInt(
390
- ethers.utils
391
- .parseUnits(orderbook[left - 1][1], quoteToken.decimals)
392
- .toString(),
393
- );
394
- let rQty = BigInt(
395
- ethers.utils
396
- .parseUnits(orderbook[left][1], quoteToken.decimals)
397
- .toString(),
398
- );
399
- if (isInputQuote) {
400
- lQty =
401
- (lQty * BigInt(10 ** (baseToken.decimals * 2))) /
402
- (lPrice * BigInt(10 ** quoteToken.decimals));
403
- rQty =
404
- (rQty * BigInt(10 ** (baseToken.decimals * 2))) /
405
- (rPrice * BigInt(10 ** quoteToken.decimals));
406
- }
407
- price = lPrice + ((rPrice - lPrice) * (amt - lQty)) / (rQty - lQty);
408
- amount = amounts[i];
409
- }
410
-
411
- if (isInputQuote) {
412
- result.push(
413
- (price * amount * BigInt(10 ** quoteToken.decimals)) /
414
- BigInt(10 ** (baseToken.decimals * 2)),
415
- );
416
- } else {
417
- result.push(
418
- price !== 0n // To avoid division by zero error
419
- ? (amount * BigInt(10 ** (baseToken.decimals * 2))) /
420
- (price * BigInt(10 ** quoteToken.decimals))
421
- : 0n,
422
- );
423
- }
424
- }
425
- return result;
426
- }
427
-
428
- async getPricesVolume(
429
- srcToken: Token,
430
- destToken: Token,
431
- amounts: bigint[],
432
- side: SwapSide,
433
- blockNumber: number,
434
- limitPools?: string[],
435
- transferFees?: TransferFeeParams,
436
- ): Promise<null | ExchangePrices<DexalotData>> {
437
- try {
438
- const normalizedSrcToken = this.normalizeToken(srcToken);
439
- const normalizedDestToken = this.normalizeToken(destToken);
440
-
441
- this.tokensMap = (await this.getCachedTokens()) || {};
442
- if (normalizedSrcToken.address === normalizedDestToken.address) {
443
- return null;
444
- }
445
-
446
- let pools = limitPools
447
- ? limitPools.filter(
448
- p =>
449
- p ===
450
- this.getIdentifier(
451
- normalizedSrcToken.address,
452
- normalizedDestToken.address,
453
- ),
454
- )
455
- : await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber);
456
-
457
- pools = await Promise.all(
458
- pools.map(async p => !(await this.isRestrictedPool(p))),
459
- ).then(res => pools.filter((_v, i) => res[i]));
460
- if (pools.length === 0) {
461
- return null;
462
- }
463
-
464
- const pairData = await this.getPairData(
465
- normalizedSrcToken,
466
- normalizedDestToken,
467
- );
468
- if (!pairData) {
469
- return null;
470
- }
471
-
472
- const priceMap = await this.getCachedPrices();
473
- if (!priceMap) {
474
- return null;
475
- }
476
-
477
- const tokensAddr = (await this.getCachedTokensAddr()) || {};
478
- const pairKey = `${pairData.base}/${pairData.quote}`.toLowerCase();
479
- if (
480
- !(pairKey in priceMap) ||
481
- !pools.includes(
482
- this.getIdentifier(
483
- tokensAddr[pairData.base.toLowerCase()],
484
- tokensAddr[pairData.quote.toLowerCase()],
485
- ),
486
- )
487
- ) {
488
- return null;
489
- }
490
-
491
- const priceData = priceMap[pairKey];
492
- const baseToken = pairData.isSrcBase
493
- ? normalizedSrcToken
494
- : normalizedDestToken;
495
- const quoteToken = pairData.isSrcBase
496
- ? normalizedDestToken
497
- : normalizedSrcToken;
498
-
499
- // convert from swap to clob side
500
- let orderbook = priceData.asks;
501
- let clobSide = ClobSide.BID;
502
- if (pairData.isSrcBase) {
503
- orderbook = priceData.bids;
504
- clobSide = ClobSide.ASK;
505
- }
506
- if (orderbook.length === 0) {
507
- throw new Error(`Empty orderbook for ${pairKey}`);
508
- }
509
-
510
- const isInputQuote =
511
- (clobSide === ClobSide.ASK && side === SwapSide.SELL) ||
512
- (clobSide === ClobSide.BID && side === SwapSide.BUY);
513
-
514
- const prices = this.calculateOrderPrice(
515
- amounts,
516
- orderbook,
517
- baseToken,
518
- quoteToken,
519
- isInputQuote,
520
- );
521
- const outDecimals =
522
- clobSide === ClobSide.BID ? baseToken.decimals : quoteToken.decimals;
523
- const poolIdentifier = this.getIdentifier(pairData.base, pairData.quote);
524
-
525
- return [
526
- {
527
- prices,
528
- unit: BigInt(outDecimals),
529
- data: {},
530
- poolIdentifier: poolIdentifier,
531
- exchange: this.dexKey,
532
- gasCost: DEXALOT_GAS_COST,
533
- poolAddresses: [this.mainnetRFQAddress],
534
- },
535
- ];
536
- } catch (e: unknown) {
537
- this.logger.error(
538
- `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${
539
- destToken.address || destToken.symbol
540
- }, ${side}:`,
541
- e,
542
- );
543
- return null;
544
- }
545
- }
546
-
547
- generateRFQError(errorStr: string, swapIdentifier: string) {
548
- const message = `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}. ${errorStr}`;
549
- this.logger.warn(message);
550
- throw new DexalotRfqError(message);
551
- }
552
-
553
- async preProcessTransaction(
554
- optimalSwapExchange: OptimalSwapExchange<DexalotData>,
555
- srcToken: Token,
556
- destToken: Token,
557
- side: SwapSide,
558
- options: PreprocessTransactionOptions,
559
- ): Promise<[OptimalSwapExchange<DexalotData>, ExchangeTxInfo]> {
560
- if (await this.isBlacklisted(options.txOrigin)) {
561
- this.logger.warn(
562
- `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`,
563
- );
564
- throw new Error(
565
- `${this.dexKey}-${
566
- this.network
567
- }: user=${options.txOrigin.toLowerCase()} is blacklisted`,
568
- );
569
- }
570
-
571
- if (BigInt(optimalSwapExchange.srcAmount) === 0n) {
572
- throw new Error('getFirmRate failed with srcAmount === 0');
573
- }
574
-
575
- const normalizedSrcToken = this.normalizeToken(srcToken);
576
- const normalizedDestToken = this.normalizeToken(destToken);
577
- const swapIdentifier = `${this.getIdentifier(
578
- normalizedSrcToken.address,
579
- normalizedDestToken.address,
580
- )}_${side}`;
581
-
582
- try {
583
- const makerToken = normalizedDestToken;
584
- const takerToken = normalizedSrcToken;
585
-
586
- const isSell = side === SwapSide.SELL;
587
- const isBuy = side === SwapSide.BUY;
588
-
589
- const slippageBps = isSell
590
- ? BigNumber(1)
591
- .minus(options.slippageFactor)
592
- .multipliedBy(10000)
593
- .toFixed(0)
594
- : options.slippageFactor.minus(1).multipliedBy(10000).toFixed(0);
595
- const rfqParams = {
596
- makerAsset: ethers.utils.getAddress(makerToken.address),
597
- takerAsset: ethers.utils.getAddress(takerToken.address),
598
- makerAmount: isBuy ? optimalSwapExchange.destAmount : undefined,
599
- takerAmount: isSell ? optimalSwapExchange.srcAmount : undefined,
600
- userAddress: options.txOrigin,
601
- chainid: this.network,
602
- executor: this.augustusAddress,
603
- partner: options.partner,
604
- slippage: slippageBps,
605
- };
606
-
607
- const rfq: RFQResponse = await this.dexHelper.httpRequest.post(
608
- `${DEXALOT_API_URL}/api/rfq/firm`,
609
- rfqParams,
610
- DEXALOT_FIRM_QUOTE_TIMEOUT_MS,
611
- { 'x-apikey': this.dexalotAuthToken },
612
- );
613
- if (!rfq) {
614
- this.generateRFQError(
615
- 'Missing quote data',
616
- `RFQ ${swapIdentifier} ${JSON.stringify(rfq)}`,
617
- );
618
- } else if (!rfq.signature) {
619
- this.generateRFQError('Missing signature', swapIdentifier);
620
- }
621
- rfq.order.signature = rfq.signature;
622
-
623
- const { order } = rfq;
624
-
625
- assert(
626
- order.makerAsset.toLowerCase() === makerToken.address,
627
- `QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`,
628
- );
629
- assert(
630
- order.takerAsset.toLowerCase() === takerToken.address,
631
- `QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`,
632
- );
633
- if (isSell) {
634
- assert(
635
- order.takerAmount === optimalSwapExchange.srcAmount,
636
- `QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`,
637
- );
638
- } else {
639
- assert(
640
- order.makerAmount === optimalSwapExchange.destAmount,
641
- `QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`,
642
- );
643
- }
644
-
645
- const expiryAsBigInt = BigInt(order.expiry);
646
- const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256;
647
-
648
- const slippageFactor = options.slippageFactor;
649
- let isFailOnSlippage = false;
650
- let slippageErrorMessage = '';
651
-
652
- if (isSell) {
653
- if (
654
- BigInt(order.makerAmount) <
655
- BigInt(
656
- new BigNumber(optimalSwapExchange.destAmount.toString())
657
- .times(slippageFactor)
658
- .toFixed(0),
659
- )
660
- ) {
661
- isFailOnSlippage = true;
662
- const message = `${this.dexKey}-${this.network}: too much slippage on quote ${side} quoteTokenAmount ${order.makerAmount} / destAmount ${optimalSwapExchange.destAmount} < ${slippageFactor}`;
663
- slippageErrorMessage = message;
664
- this.logger.warn(message);
665
- }
666
- } else {
667
- if (
668
- BigInt(order.takerAmount) >
669
- BigInt(
670
- slippageFactor
671
- .times(optimalSwapExchange.srcAmount.toString())
672
- .toFixed(0),
673
- )
674
- ) {
675
- isFailOnSlippage = true;
676
- const message = `${this.dexKey}-${
677
- this.network
678
- }: too much slippage on quote ${side} baseTokenAmount ${
679
- order.takerAmount
680
- } / srcAmount ${
681
- optimalSwapExchange.srcAmount
682
- } > ${slippageFactor.toFixed()}`;
683
- slippageErrorMessage = message;
684
- this.logger.warn(message);
685
- }
686
- }
687
-
688
- let isTooStrictSlippage = false;
689
- if (
690
- isFailOnSlippage &&
691
- isSell &&
692
- new BigNumber(1)
693
- .minus(slippageFactor)
694
- .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION)
695
- ) {
696
- isTooStrictSlippage = true;
697
- } else if (
698
- isFailOnSlippage &&
699
- isBuy &&
700
- slippageFactor
701
- .minus(1)
702
- .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION)
703
- ) {
704
- isTooStrictSlippage = true;
705
- }
706
-
707
- if (isFailOnSlippage && isTooStrictSlippage) {
708
- throw new TooStrictSlippageCheckError(slippageErrorMessage);
709
- } else if (isFailOnSlippage && !isTooStrictSlippage) {
710
- throw new SlippageCheckError(slippageErrorMessage);
711
- }
712
-
713
- return [
714
- {
715
- ...optimalSwapExchange,
716
- data: {
717
- quoteData: order,
718
- },
719
- },
720
- { deadline: minDeadline },
721
- ];
722
- } catch (e) {
723
- if (isAxiosError(e) && e.response && e.response.data) {
724
- const errorData: RFQResponseError = e.response.data;
725
- if (errorData.ReasonCode === 'FQ-009') {
726
- this.logger.warn(
727
- `${this.dexKey}-${this.network}: Encountered rate limited user=${options.txOrigin}. Adding to local rate limit cache`,
728
- );
729
- await this.setRateLimited(options.txOrigin, errorData.RetryAfter);
730
- } else {
731
- await this.setBlacklist(options.txOrigin);
732
- this.logger.error(
733
- `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}: ${errorData.Reason}`,
734
- );
735
- }
736
- } else {
737
- if (e instanceof TooStrictSlippageCheckError) {
738
- this.logger.warn(
739
- `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`,
740
- );
741
- } else {
742
- const poolIdentifiers = await this.getPoolIdentifiers(
743
- srcToken,
744
- destToken,
745
- side,
746
- 0,
747
- );
748
- this.logger.warn(
749
- `${this.dexKey}-${this.network}: protocol is restricted for pools ${poolIdentifiers} due to swap: ${swapIdentifier}`,
750
- );
751
- await Promise.all(
752
- poolIdentifiers.map(async p => await this.restrictPool(p)),
753
- );
754
- }
755
- }
756
-
757
- throw e;
758
- }
759
- }
760
-
761
- getCalldataGasCost(poolPrices: PoolPrices<DexalotData>): number | number[] {
762
- return (
763
- CALLDATA_GAS_COST.DEX_OVERHEAD +
764
- // addresses: makerAsset, takerAsset, maker, taker
765
- CALLDATA_GAS_COST.ADDRESS * 4 +
766
- // uint256: expiry
767
- CALLDATA_GAS_COST.wordNonZeroBytes(16) +
768
- // uint256: nonceAndMeta, makerAmount, takerAmount
769
- CALLDATA_GAS_COST.AMOUNT * 3 +
770
- // bytes: _signature (65 bytes)
771
- CALLDATA_GAS_COST.FULL_WORD * 2 +
772
- CALLDATA_GAS_COST.OFFSET_SMALL
773
- );
774
- }
775
-
776
- getTokenFromAddress(address: Address): Token {
777
- return this.tokensMap[this.normalizeAddress(address)];
778
- }
779
-
780
- getAdapterParam(
781
- srcToken: string,
782
- destToken: string,
783
- srcAmount: string,
784
- destAmount: string,
785
- data: DexalotData,
786
- side: SwapSide,
787
- ): AdapterExchangeParam {
788
- const { quoteData } = data;
789
-
790
- assert(
791
- quoteData !== undefined,
792
- `${this.dexKey}-${this.network}: quoteData undefined`,
793
- );
794
-
795
- const params = [
796
- {
797
- nonceAndMeta: quoteData.nonceAndMeta,
798
- expiry: quoteData.expiry,
799
- makerAsset: quoteData.makerAsset,
800
- takerAsset: quoteData.takerAsset,
801
- maker: quoteData.maker,
802
- taker: quoteData.taker,
803
- makerAmount: quoteData.makerAmount,
804
- takerAmount: quoteData.takerAmount,
805
- },
806
- quoteData.signature,
807
- ];
808
-
809
- const payload = this.abiCoder.encodeParameter(
810
- {
811
- ParentStruct: {
812
- order: {
813
- nonceAndMeta: 'uint256',
814
- expiry: 'uint128',
815
- makerAsset: 'address',
816
- takerAsset: 'address',
817
- maker: 'address',
818
- taker: 'address',
819
- makerAmount: 'uint256',
820
- takerAmount: 'uint256',
821
- },
822
- signature: 'bytes',
823
- },
824
- },
825
- {
826
- order: params[0],
827
- signature: params[1],
828
- },
829
- );
830
-
831
- return {
832
- targetExchange: this.mainnetRFQAddress,
833
- payload,
834
- networkFee: '0',
835
- };
836
- }
837
-
838
- getRestrictedPoolKey(poolIdentifier: string): string {
839
- return `${DEXALOT_RESTRICTED_CACHE_KEY}-${poolIdentifier}`;
840
- }
841
-
842
- async restrictPool(
843
- poolIdentifier: string,
844
- ttl: number = DEXALOT_RESTRICT_TTL_S,
845
- ): Promise<boolean> {
846
- await this.dexHelper.cache.setex(
847
- this.dexKey,
848
- this.network,
849
- this.getRestrictedPoolKey(poolIdentifier),
850
- ttl,
851
- 'true',
852
- );
853
- return true;
854
- }
855
-
856
- async isRestrictedPool(poolIdentifier: string): Promise<boolean> {
857
- const result = await this.dexHelper.cache.get(
858
- this.dexKey,
859
- this.network,
860
- this.getRestrictedPoolKey(poolIdentifier),
861
- );
862
-
863
- return result === 'true';
864
- }
865
-
866
- async setBlacklist(
867
- txOrigin: Address,
868
- ttl: number = DEXALOT_BLACKLIST_CACHES_TTL_S,
869
- ): Promise<boolean> {
870
- const cachedBlacklist = await this.dexHelper.cache.get(
871
- this.dexKey,
872
- this.network,
873
- this.blacklistCacheKey,
874
- );
875
-
876
- let blacklist: string[] = [];
877
- if (cachedBlacklist) {
878
- blacklist = JSON.parse(cachedBlacklist);
879
- }
880
-
881
- blacklist.push(txOrigin.toLowerCase());
882
-
883
- this.dexHelper.cache.setex(
884
- this.dexKey,
885
- this.network,
886
- this.blacklistCacheKey,
887
- ttl,
888
- JSON.stringify(blacklist),
889
- );
890
-
891
- return true;
892
- }
893
-
894
- async isBlacklisted(txOrigin: Address): Promise<boolean> {
895
- const cachedBlacklist = await this.dexHelper.cache.get(
896
- this.dexKey,
897
- this.network,
898
- this.blacklistCacheKey,
899
- );
900
-
901
- if (cachedBlacklist) {
902
- const blacklist = JSON.parse(cachedBlacklist) as string[];
903
- return blacklist.includes(txOrigin.toLowerCase());
904
- }
905
-
906
- // To not show pricing for rate limited users
907
- if (await this.isRateLimited(txOrigin)) {
908
- return true;
909
- }
910
-
911
- return false;
912
- }
913
-
914
- getRateLimitedKey(address: Address) {
915
- return `rate_limited_${address}`.toLowerCase();
916
- }
917
-
918
- async isRateLimited(txOrigin: Address): Promise<boolean> {
919
- const result = await this.dexHelper.cache.get(
920
- this.dexKey,
921
- this.network,
922
- this.getRateLimitedKey(txOrigin),
923
- );
924
- return result === DEXALOT_RATELIMIT_CACHE_VALUE;
925
- }
926
-
927
- async setRateLimited(txOrigin: Address, ttl = DEXALOT_RATE_LIMITED_TTL_S) {
928
- await this.dexHelper.cache.setex(
929
- this.dexKey,
930
- this.network,
931
- this.getRateLimitedKey(txOrigin),
932
- ttl,
933
- DEXALOT_RATELIMIT_CACHE_VALUE,
934
- );
935
- return true;
936
- }
937
-
938
- async getSimpleParam(
939
- srcToken: string,
940
- destToken: string,
941
- srcAmount: string,
942
- destAmount: string,
943
- data: DexalotData,
944
- side: SwapSide,
945
- ): Promise<SimpleExchangeParam> {
946
- const { quoteData } = data;
947
-
948
- assert(
949
- quoteData !== undefined,
950
- `${this.dexKey}-${this.network}: quoteData undefined`,
951
- );
952
-
953
- const swapFunction = 'simpleSwap';
954
- const swapFunctionParams = [
955
- [
956
- quoteData.nonceAndMeta,
957
- quoteData.expiry,
958
- quoteData.makerAsset,
959
- quoteData.takerAsset,
960
- quoteData.maker,
961
- quoteData.taker,
962
- quoteData.makerAmount,
963
- quoteData.takerAmount,
964
- ],
965
- quoteData.signature,
966
- ];
967
-
968
- const swapData = this.rfqInterface.encodeFunctionData(
969
- swapFunction,
970
- swapFunctionParams,
971
- );
972
-
973
- return this.buildSimpleParamWithoutWETHConversion(
974
- srcToken,
975
- srcAmount,
976
- destToken,
977
- destAmount,
978
- swapData,
979
- this.mainnetRFQAddress,
980
- );
981
- }
982
-
983
- getDexParam(
984
- srcToken: Address,
985
- destToken: Address,
986
- srcAmount: NumberAsString,
987
- destAmount: NumberAsString,
988
- recipient: Address,
989
- data: DexalotData,
990
- side: SwapSide,
991
- ): DexExchangeParam {
992
- const { quoteData } = data;
993
-
994
- assert(
995
- quoteData !== undefined,
996
- `${this.dexKey}-${this.network}: quoteData undefined`,
997
- );
998
-
999
- const swapFunction = 'simpleSwap';
1000
- const swapFunctionParams = [
1001
- [
1002
- quoteData.nonceAndMeta,
1003
- quoteData.expiry,
1004
- quoteData.makerAsset,
1005
- quoteData.takerAsset,
1006
- quoteData.maker,
1007
- quoteData.taker,
1008
- quoteData.makerAmount,
1009
- quoteData.takerAmount,
1010
- ],
1011
- quoteData.signature,
1012
- ];
1013
-
1014
- const exchangeData = this.rfqInterface.encodeFunctionData(
1015
- swapFunction,
1016
- swapFunctionParams,
1017
- );
1018
-
1019
- return {
1020
- exchangeData,
1021
- needWrapNative: this.needWrapNative,
1022
- dexFuncHasRecipient: false,
1023
- targetExchange: this.mainnetRFQAddress,
1024
- returnAmountPos: undefined,
1025
- };
1026
- }
1027
-
1028
- async getTopPoolsForToken(
1029
- tokenAddress: Address,
1030
- limit: number,
1031
- ): Promise<PoolLiquidity[]> {
1032
- const normalizedTokenAddress = this.normalizeAddress(tokenAddress);
1033
- const pairs = (await this.getCachedPairs()) || {};
1034
- this.tokensMap = (await this.getCachedTokens()) || {};
1035
- const tokensAddr = (await this.getCachedTokensAddr()) || {};
1036
- const token = this.getTokenFromAddress(normalizedTokenAddress);
1037
- if (!token) {
1038
- return [];
1039
- }
1040
-
1041
- const tokenSymbol = token.symbol?.toLowerCase() || '';
1042
-
1043
- let pairsByLiquidity = [];
1044
- for (const pairName of Object.keys(pairs)) {
1045
- if (!pairName.includes(tokenSymbol)) {
1046
- continue;
1047
- }
1048
-
1049
- const tokensInPair = pairName.split('/');
1050
- if (tokensInPair.length !== 2) {
1051
- continue;
1052
- }
1053
-
1054
- const [baseToken, quoteToken] = tokensInPair;
1055
- const addr = tokensAddr[baseToken.toLowerCase()];
1056
- let outputToken = this.getTokenFromAddress(addr);
1057
- if (baseToken === tokenSymbol) {
1058
- const addr = tokensAddr[quoteToken.toLowerCase()];
1059
- outputToken = this.getTokenFromAddress(addr);
1060
- }
1061
-
1062
- const denormalizedToken = this.denormalizeToken(outputToken);
1063
-
1064
- pairsByLiquidity.push({
1065
- exchange: this.dexKey,
1066
- address: this.mainnetRFQAddress,
1067
- connectorTokens: [
1068
- {
1069
- address: denormalizedToken.address,
1070
- decimals: denormalizedToken.decimals,
1071
- },
1072
- ],
1073
- liquidityUSD: pairs[pairName].liquidityUSD,
1074
- });
1075
- }
1076
-
1077
- pairsByLiquidity.sort(
1078
- (a: PoolLiquidity, b: PoolLiquidity) => b.liquidityUSD - a.liquidityUSD,
1079
- );
1080
-
1081
- return pairsByLiquidity.slice(0, limit);
1082
- }
1083
-
1084
- getAPIReqParams(endpoint: string, method: Method): DexalotAPIParameters {
1085
- return {
1086
- url: `${DEXALOT_API_URL}/${endpoint}`,
1087
- headers: { 'x-apikey': this.dexalotAuthToken },
1088
- params: {
1089
- chainid: this.network,
1090
- },
1091
- method: method,
1092
- };
1093
- }
1094
-
1095
- releaseResources(): void {
1096
- if (this.rateFetcher) {
1097
- this.rateFetcher.stop();
1098
- }
1099
- }
1100
- }
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
+ TransferFeeParams,
14
+ NumberAsString,
15
+ DexExchangeParam,
16
+ } from '../../types';
17
+ import {
18
+ SwapSide,
19
+ Network,
20
+ ETHER_ADDRESS,
21
+ NULL_ADDRESS,
22
+ } from '../../constants';
23
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
24
+ import { getDexKeysWithNetwork, isAxiosError } from '../../utils';
25
+ import { IDex } from '../idex';
26
+ import { IDexHelper } from '../../dex-helper/idex-helper';
27
+ import {
28
+ ClobSide,
29
+ DexalotData,
30
+ PairData,
31
+ PairDataMap,
32
+ PriceDataMap,
33
+ DexalotRfqError,
34
+ DexalotAPIParameters,
35
+ RFQResponse,
36
+ RFQResponseError,
37
+ TokenAddrDataMap,
38
+ TokenDataMap,
39
+ } from './types';
40
+ import {
41
+ SlippageCheckError,
42
+ TooStrictSlippageCheckError,
43
+ } from '../generic-rfq/types';
44
+ import { SimpleExchange } from '../simple-exchange';
45
+ import { Adapters, DexalotConfig } from './config';
46
+ import { RateFetcher } from './rate-fetcher';
47
+ import mainnetRFQAbi from '../../abi/dexalot/DexalotMainnetRFQ.json';
48
+ import { Interface } from 'ethers/lib/utils';
49
+ import { assert } from 'ts-essentials';
50
+ import {
51
+ DEXALOT_API_URL,
52
+ DEXALOT_API_PRICES_POLLING_INTERVAL_MS,
53
+ DEXALOT_PRICES_CACHES_TTL_S,
54
+ DEXALOT_GAS_COST,
55
+ DEXALOT_PAIRS_CACHES_TTL_S,
56
+ DEXALOT_API_PAIRS_POLLING_INTERVAL_MS,
57
+ DEXALOT_TOKENS_CACHES_TTL_S,
58
+ DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS,
59
+ DEXALOT_RATE_LIMITED_TTL_S,
60
+ DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION,
61
+ DEXALOT_RESTRICTED_CACHE_KEY,
62
+ DEXALOT_RESTRICT_TTL_S,
63
+ DEXALOT_RATELIMIT_CACHE_VALUE,
64
+ DEXALOT_BLACKLIST_CACHES_TTL_S,
65
+ DEXALOT_FIRM_QUOTE_TIMEOUT_MS,
66
+ } from './constants';
67
+ import { BI_MAX_UINT256 } from '../../bigint-constants';
68
+ import { ethers } from 'ethers';
69
+ import BigNumber from 'bignumber.js';
70
+ import { Method } from '../../dex-helper/irequest-wrapper';
71
+
72
+ export class Dexalot extends SimpleExchange implements IDex<DexalotData> {
73
+ readonly isStatePollingDex = true;
74
+ readonly hasConstantPriceLargeAmounts = false;
75
+ readonly needWrapNative = false;
76
+ readonly isFeeOnTransferSupported = false;
77
+ private rateFetcher: RateFetcher;
78
+
79
+ private dexalotAuthToken: string;
80
+
81
+ private pricesCacheKey: string;
82
+ private pairsCacheKey: string;
83
+ private tokensAddrCacheKey: string;
84
+ private tokensCacheKey: string;
85
+ private blacklistCacheKey: string;
86
+ private tokensMap: TokenDataMap = {};
87
+
88
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
89
+ getDexKeysWithNetwork(DexalotConfig);
90
+
91
+ logger: Logger;
92
+
93
+ constructor(
94
+ readonly network: Network,
95
+ readonly dexKey: string,
96
+ readonly dexHelper: IDexHelper,
97
+ protected adapters = Adapters[network] || {},
98
+ readonly mainnetRFQAddress: string = DexalotConfig['Dexalot'][network]
99
+ .mainnetRFQAddress,
100
+ protected rfqInterface = new Interface(mainnetRFQAbi),
101
+ ) {
102
+ super(dexHelper, dexKey);
103
+ this.logger = dexHelper.getLogger(dexKey);
104
+
105
+ const authToken = dexHelper.config.data.dexalotAuthToken;
106
+ assert(
107
+ authToken !== undefined,
108
+ 'Dexalot auth token is not specified with env variable',
109
+ );
110
+ this.dexalotAuthToken = authToken;
111
+
112
+ this.pricesCacheKey = 'prices';
113
+ this.pairsCacheKey = 'pairs';
114
+ this.tokensAddrCacheKey = 'tokens_addr';
115
+ this.tokensCacheKey = 'tokens';
116
+ this.blacklistCacheKey = 'blacklist';
117
+
118
+ this.rateFetcher = new RateFetcher(
119
+ this.dexHelper,
120
+ this.dexKey,
121
+ this.network,
122
+ this.logger,
123
+ {
124
+ rateConfig: {
125
+ pairsIntervalMs: DEXALOT_API_PAIRS_POLLING_INTERVAL_MS,
126
+ pricesIntervalMs: DEXALOT_API_PRICES_POLLING_INTERVAL_MS,
127
+ blacklistIntervalMs: DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS,
128
+ pairsReqParams: this.getAPIReqParams('api/rfq/pairs', 'GET'),
129
+ pricesReqParams: this.getAPIReqParams('api/rfq/prices', 'GET'),
130
+ blacklistReqParams: this.getAPIReqParams('api/rfq/blacklist', 'GET'),
131
+ pairsCacheKey: this.pairsCacheKey,
132
+ pairsCacheTTLSecs: DEXALOT_PAIRS_CACHES_TTL_S,
133
+ pricesCacheKey: this.pricesCacheKey,
134
+ pricesCacheTTLSecs: DEXALOT_PRICES_CACHES_TTL_S,
135
+ tokensAddrCacheKey: this.tokensAddrCacheKey,
136
+ tokensCacheKey: this.tokensCacheKey,
137
+ tokensCacheTTLSecs: DEXALOT_TOKENS_CACHES_TTL_S,
138
+ blacklistCacheKey: this.blacklistCacheKey,
139
+ blacklistCacheTTLSecs: DEXALOT_BLACKLIST_CACHES_TTL_S,
140
+ },
141
+ },
142
+ );
143
+ }
144
+
145
+ async initializePricing(blockNumber: number): Promise<void> {
146
+ if (!this.dexHelper.config.isSlave) {
147
+ this.rateFetcher.start();
148
+ }
149
+
150
+ return;
151
+ }
152
+
153
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
154
+ return this.adapters[side] ? this.adapters[side] : null;
155
+ }
156
+
157
+ getPairString(baseToken: Token, quoteToken: Token): string {
158
+ return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase();
159
+ }
160
+
161
+ async getPairData(
162
+ srcToken: Token,
163
+ destToken: Token,
164
+ ): Promise<PairData | null> {
165
+ const normalizedSrcToken = this.normalizeToken(srcToken);
166
+ const normalizedDestToken = this.normalizeToken(destToken);
167
+ if (normalizedSrcToken.address === normalizedDestToken.address) {
168
+ return null;
169
+ }
170
+
171
+ const cachedTokens = (await this.getCachedTokens()) || {};
172
+ if (
173
+ !(normalizedSrcToken.address in cachedTokens) ||
174
+ !(normalizedDestToken.address in cachedTokens)
175
+ ) {
176
+ return null;
177
+ }
178
+ normalizedSrcToken.symbol = cachedTokens[normalizedSrcToken.address].symbol;
179
+ normalizedDestToken.symbol =
180
+ cachedTokens[normalizedDestToken.address].symbol;
181
+
182
+ const cachedPairs = (await this.getCachedPairs()) || {};
183
+
184
+ const potentialPairs = [
185
+ {
186
+ identifier: this.getPairString(normalizedSrcToken, normalizedDestToken),
187
+ isSrcBase: true,
188
+ },
189
+ {
190
+ identifier: this.getPairString(normalizedDestToken, normalizedSrcToken),
191
+ isSrcBase: false,
192
+ },
193
+ ];
194
+
195
+ for (const pair of potentialPairs) {
196
+ if (pair.identifier in cachedPairs) {
197
+ const pairData = cachedPairs[pair.identifier];
198
+ pairData.isSrcBase = pair.isSrcBase;
199
+ return pairData;
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+
205
+ getIdentifier(srcAddress: Address, destAddress: Address) {
206
+ const sortedAddresses =
207
+ srcAddress < destAddress
208
+ ? [srcAddress, destAddress]
209
+ : [destAddress, srcAddress];
210
+
211
+ return `${
212
+ this.dexKey
213
+ }_${sortedAddresses[0].toLowerCase()}_${sortedAddresses[1].toLowerCase()}`;
214
+ }
215
+
216
+ async getPoolIdentifiers(
217
+ srcToken: Token,
218
+ destToken: Token,
219
+ side: SwapSide,
220
+ blockNumber: number,
221
+ ): Promise<string[]> {
222
+ if (!srcToken || !destToken) {
223
+ return [];
224
+ }
225
+ const pairData = await this.getPairData(srcToken, destToken);
226
+ if (!pairData) {
227
+ return [];
228
+ }
229
+
230
+ const tokensAddr = (await this.getCachedTokensAddr()) || {};
231
+
232
+ return [
233
+ this.getIdentifier(
234
+ tokensAddr[pairData.base.toLowerCase()],
235
+ tokensAddr[pairData.quote.toLowerCase()],
236
+ ),
237
+ ];
238
+ }
239
+
240
+ async getCachedPairs(): Promise<PairDataMap | null> {
241
+ const cachedPairs = await this.dexHelper.cache.get(
242
+ this.dexKey,
243
+ this.network,
244
+ this.pairsCacheKey,
245
+ );
246
+
247
+ if (cachedPairs) {
248
+ return JSON.parse(cachedPairs) as PairDataMap;
249
+ }
250
+
251
+ return null;
252
+ }
253
+
254
+ async getCachedPrices(): Promise<PriceDataMap | null> {
255
+ const cachedPrices = await this.dexHelper.cache.get(
256
+ this.dexKey,
257
+ this.network,
258
+ this.pricesCacheKey,
259
+ );
260
+
261
+ if (cachedPrices) {
262
+ return JSON.parse(cachedPrices) as PriceDataMap;
263
+ }
264
+
265
+ return null;
266
+ }
267
+
268
+ async getCachedTokensAddr(): Promise<TokenAddrDataMap | null> {
269
+ const cachedTokensAddr = await this.dexHelper.cache.get(
270
+ this.dexKey,
271
+ this.network,
272
+ this.tokensAddrCacheKey,
273
+ );
274
+
275
+ if (cachedTokensAddr) {
276
+ return JSON.parse(cachedTokensAddr) as TokenAddrDataMap;
277
+ }
278
+
279
+ return null;
280
+ }
281
+
282
+ async getCachedTokens(): Promise<TokenDataMap | null> {
283
+ const cachedTokens = await this.dexHelper.cache.get(
284
+ this.dexKey,
285
+ this.network,
286
+ this.tokensCacheKey,
287
+ );
288
+
289
+ if (cachedTokens) {
290
+ return JSON.parse(cachedTokens) as TokenDataMap;
291
+ }
292
+
293
+ return null;
294
+ }
295
+
296
+ normalizeAddress(address: string): string {
297
+ return address.toLowerCase() === ETHER_ADDRESS
298
+ ? NULL_ADDRESS
299
+ : address.toLowerCase();
300
+ }
301
+
302
+ denormalizeAddress(address: string): string {
303
+ return address.toLowerCase() === NULL_ADDRESS
304
+ ? ETHER_ADDRESS
305
+ : address.toLowerCase();
306
+ }
307
+
308
+ // Dexalot protocol for native token expects 0x00000... instead of 0xeeeee...
309
+ normalizeToken(token: Token): Token {
310
+ return {
311
+ address: this.normalizeAddress(token.address),
312
+ decimals: token.decimals,
313
+ };
314
+ }
315
+
316
+ denormalizeToken(token: Token): Token {
317
+ return {
318
+ address: this.denormalizeAddress(token.address),
319
+ decimals: token.decimals,
320
+ };
321
+ }
322
+
323
+ calculateOrderPrice(
324
+ amounts: bigint[],
325
+ orderbook: string[][],
326
+ baseToken: Token,
327
+ quoteToken: Token,
328
+ isInputQuote: boolean,
329
+ ) {
330
+ let result = [];
331
+
332
+ for (let i = 0; i < amounts.length; i++) {
333
+ let amt = amounts[i];
334
+ if (amt === 0n) {
335
+ result.push(amt);
336
+ continue;
337
+ }
338
+
339
+ let left = 0,
340
+ right = orderbook.length;
341
+ let qty = BigInt(0);
342
+
343
+ while (left < right) {
344
+ let mid = Math.floor((left + right) / 2);
345
+ qty = BigInt(
346
+ ethers.utils
347
+ .parseUnits(orderbook[mid][1], quoteToken.decimals)
348
+ .toString(),
349
+ );
350
+ if (isInputQuote) {
351
+ const price = BigInt(
352
+ ethers.utils
353
+ .parseUnits(orderbook[mid][0], baseToken.decimals)
354
+ .toString(),
355
+ );
356
+ qty =
357
+ (qty * BigInt(10 ** (baseToken.decimals * 2))) /
358
+ (price * BigInt(10 ** quoteToken.decimals));
359
+ }
360
+ if (qty <= amt) {
361
+ left = mid + 1;
362
+ } else {
363
+ right = mid;
364
+ }
365
+ }
366
+
367
+ let price = BigInt(0),
368
+ amount = BigInt(0);
369
+ if (amounts[i] === qty) {
370
+ price = BigInt(
371
+ ethers.utils
372
+ .parseUnits(orderbook[left][0], baseToken.decimals)
373
+ .toString(),
374
+ );
375
+ amount = amounts[i];
376
+ } else if (left === 0) {
377
+ price = 0n;
378
+ } else if (left < orderbook.length) {
379
+ const lPrice = BigInt(
380
+ ethers.utils
381
+ .parseUnits(orderbook[left - 1][0], baseToken.decimals)
382
+ .toString(),
383
+ );
384
+ const rPrice = BigInt(
385
+ ethers.utils
386
+ .parseUnits(orderbook[left][0], baseToken.decimals)
387
+ .toString(),
388
+ );
389
+ let lQty = BigInt(
390
+ ethers.utils
391
+ .parseUnits(orderbook[left - 1][1], quoteToken.decimals)
392
+ .toString(),
393
+ );
394
+ let rQty = BigInt(
395
+ ethers.utils
396
+ .parseUnits(orderbook[left][1], quoteToken.decimals)
397
+ .toString(),
398
+ );
399
+ if (isInputQuote) {
400
+ lQty =
401
+ (lQty * BigInt(10 ** (baseToken.decimals * 2))) /
402
+ (lPrice * BigInt(10 ** quoteToken.decimals));
403
+ rQty =
404
+ (rQty * BigInt(10 ** (baseToken.decimals * 2))) /
405
+ (rPrice * BigInt(10 ** quoteToken.decimals));
406
+ }
407
+ price = lPrice + ((rPrice - lPrice) * (amt - lQty)) / (rQty - lQty);
408
+ amount = amounts[i];
409
+ }
410
+
411
+ if (isInputQuote) {
412
+ result.push(
413
+ (price * amount * BigInt(10 ** quoteToken.decimals)) /
414
+ BigInt(10 ** (baseToken.decimals * 2)),
415
+ );
416
+ } else {
417
+ result.push(
418
+ price !== 0n // To avoid division by zero error
419
+ ? (amount * BigInt(10 ** (baseToken.decimals * 2))) /
420
+ (price * BigInt(10 ** quoteToken.decimals))
421
+ : 0n,
422
+ );
423
+ }
424
+ }
425
+ return result;
426
+ }
427
+
428
+ async getPricesVolume(
429
+ srcToken: Token,
430
+ destToken: Token,
431
+ amounts: bigint[],
432
+ side: SwapSide,
433
+ blockNumber: number,
434
+ limitPools?: string[],
435
+ transferFees?: TransferFeeParams,
436
+ ): Promise<null | ExchangePrices<DexalotData>> {
437
+ try {
438
+ const normalizedSrcToken = this.normalizeToken(srcToken);
439
+ const normalizedDestToken = this.normalizeToken(destToken);
440
+
441
+ this.tokensMap = (await this.getCachedTokens()) || {};
442
+ if (normalizedSrcToken.address === normalizedDestToken.address) {
443
+ return null;
444
+ }
445
+
446
+ let pools = limitPools
447
+ ? limitPools.filter(
448
+ p =>
449
+ p ===
450
+ this.getIdentifier(
451
+ normalizedSrcToken.address,
452
+ normalizedDestToken.address,
453
+ ),
454
+ )
455
+ : await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber);
456
+
457
+ pools = await Promise.all(
458
+ pools.map(async p => !(await this.isRestrictedPool(p))),
459
+ ).then(res => pools.filter((_v, i) => res[i]));
460
+ if (pools.length === 0) {
461
+ return null;
462
+ }
463
+
464
+ const pairData = await this.getPairData(
465
+ normalizedSrcToken,
466
+ normalizedDestToken,
467
+ );
468
+ if (!pairData) {
469
+ return null;
470
+ }
471
+
472
+ const priceMap = await this.getCachedPrices();
473
+ if (!priceMap) {
474
+ return null;
475
+ }
476
+
477
+ const tokensAddr = (await this.getCachedTokensAddr()) || {};
478
+ const pairKey = `${pairData.base}/${pairData.quote}`.toLowerCase();
479
+ if (
480
+ !(pairKey in priceMap) ||
481
+ !pools.includes(
482
+ this.getIdentifier(
483
+ tokensAddr[pairData.base.toLowerCase()],
484
+ tokensAddr[pairData.quote.toLowerCase()],
485
+ ),
486
+ )
487
+ ) {
488
+ return null;
489
+ }
490
+
491
+ const priceData = priceMap[pairKey];
492
+ const baseToken = pairData.isSrcBase
493
+ ? normalizedSrcToken
494
+ : normalizedDestToken;
495
+ const quoteToken = pairData.isSrcBase
496
+ ? normalizedDestToken
497
+ : normalizedSrcToken;
498
+
499
+ // convert from swap to clob side
500
+ let orderbook = priceData.asks;
501
+ let clobSide = ClobSide.BID;
502
+ if (pairData.isSrcBase) {
503
+ orderbook = priceData.bids;
504
+ clobSide = ClobSide.ASK;
505
+ }
506
+ if (orderbook.length === 0) {
507
+ throw new Error(`Empty orderbook for ${pairKey}`);
508
+ }
509
+
510
+ const isInputQuote =
511
+ (clobSide === ClobSide.ASK && side === SwapSide.SELL) ||
512
+ (clobSide === ClobSide.BID && side === SwapSide.BUY);
513
+
514
+ const prices = this.calculateOrderPrice(
515
+ amounts,
516
+ orderbook,
517
+ baseToken,
518
+ quoteToken,
519
+ isInputQuote,
520
+ );
521
+ const outDecimals =
522
+ clobSide === ClobSide.BID ? baseToken.decimals : quoteToken.decimals;
523
+ const poolIdentifier = this.getIdentifier(pairData.base, pairData.quote);
524
+
525
+ return [
526
+ {
527
+ prices,
528
+ unit: BigInt(outDecimals),
529
+ data: {},
530
+ poolIdentifier: poolIdentifier,
531
+ exchange: this.dexKey,
532
+ gasCost: DEXALOT_GAS_COST,
533
+ poolAddresses: [this.mainnetRFQAddress],
534
+ },
535
+ ];
536
+ } catch (e: unknown) {
537
+ this.logger.error(
538
+ `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${
539
+ destToken.address || destToken.symbol
540
+ }, ${side}:`,
541
+ e,
542
+ );
543
+ return null;
544
+ }
545
+ }
546
+
547
+ generateRFQError(errorStr: string, swapIdentifier: string) {
548
+ const message = `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}. ${errorStr}`;
549
+ this.logger.warn(message);
550
+ throw new DexalotRfqError(message);
551
+ }
552
+
553
+ async preProcessTransaction(
554
+ optimalSwapExchange: OptimalSwapExchange<DexalotData>,
555
+ srcToken: Token,
556
+ destToken: Token,
557
+ side: SwapSide,
558
+ options: PreprocessTransactionOptions,
559
+ ): Promise<[OptimalSwapExchange<DexalotData>, ExchangeTxInfo]> {
560
+ if (await this.isBlacklisted(options.txOrigin)) {
561
+ this.logger.warn(
562
+ `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`,
563
+ );
564
+ throw new Error(
565
+ `${this.dexKey}-${
566
+ this.network
567
+ }: user=${options.txOrigin.toLowerCase()} is blacklisted`,
568
+ );
569
+ }
570
+
571
+ if (BigInt(optimalSwapExchange.srcAmount) === 0n) {
572
+ throw new Error('getFirmRate failed with srcAmount === 0');
573
+ }
574
+
575
+ const normalizedSrcToken = this.normalizeToken(srcToken);
576
+ const normalizedDestToken = this.normalizeToken(destToken);
577
+ const swapIdentifier = `${this.getIdentifier(
578
+ normalizedSrcToken.address,
579
+ normalizedDestToken.address,
580
+ )}_${side}`;
581
+
582
+ try {
583
+ const makerToken = normalizedDestToken;
584
+ const takerToken = normalizedSrcToken;
585
+
586
+ const isSell = side === SwapSide.SELL;
587
+ const isBuy = side === SwapSide.BUY;
588
+
589
+ const slippageBps = isSell
590
+ ? BigNumber(1)
591
+ .minus(options.slippageFactor)
592
+ .multipliedBy(10000)
593
+ .toFixed(0)
594
+ : options.slippageFactor.minus(1).multipliedBy(10000).toFixed(0);
595
+ const rfqParams = {
596
+ makerAsset: ethers.utils.getAddress(makerToken.address),
597
+ takerAsset: ethers.utils.getAddress(takerToken.address),
598
+ makerAmount: isBuy ? optimalSwapExchange.destAmount : undefined,
599
+ takerAmount: isSell ? optimalSwapExchange.srcAmount : undefined,
600
+ userAddress: options.txOrigin,
601
+ chainid: this.network,
602
+ executor: this.augustusAddress,
603
+ partner: options.partner,
604
+ slippage: slippageBps,
605
+ };
606
+
607
+ const rfq: RFQResponse = await this.dexHelper.httpRequest.post(
608
+ `${DEXALOT_API_URL}/api/rfq/firm`,
609
+ rfqParams,
610
+ DEXALOT_FIRM_QUOTE_TIMEOUT_MS,
611
+ { 'x-apikey': this.dexalotAuthToken },
612
+ );
613
+ if (!rfq) {
614
+ this.generateRFQError(
615
+ 'Missing quote data',
616
+ `RFQ ${swapIdentifier} ${JSON.stringify(rfq)}`,
617
+ );
618
+ } else if (!rfq.signature) {
619
+ this.generateRFQError('Missing signature', swapIdentifier);
620
+ }
621
+ rfq.order.signature = rfq.signature;
622
+
623
+ const { order } = rfq;
624
+
625
+ assert(
626
+ order.makerAsset.toLowerCase() === makerToken.address,
627
+ `QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`,
628
+ );
629
+ assert(
630
+ order.takerAsset.toLowerCase() === takerToken.address,
631
+ `QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`,
632
+ );
633
+ if (isSell) {
634
+ assert(
635
+ order.takerAmount === optimalSwapExchange.srcAmount,
636
+ `QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`,
637
+ );
638
+ } else {
639
+ assert(
640
+ order.makerAmount === optimalSwapExchange.destAmount,
641
+ `QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`,
642
+ );
643
+ }
644
+
645
+ const expiryAsBigInt = BigInt(order.expiry);
646
+ const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256;
647
+
648
+ const slippageFactor = options.slippageFactor;
649
+ let isFailOnSlippage = false;
650
+ let slippageErrorMessage = '';
651
+
652
+ if (isSell) {
653
+ if (
654
+ BigInt(order.makerAmount) <
655
+ BigInt(
656
+ new BigNumber(optimalSwapExchange.destAmount.toString())
657
+ .times(slippageFactor)
658
+ .toFixed(0),
659
+ )
660
+ ) {
661
+ isFailOnSlippage = true;
662
+ const message = `${this.dexKey}-${this.network}: too much slippage on quote ${side} quoteTokenAmount ${order.makerAmount} / destAmount ${optimalSwapExchange.destAmount} < ${slippageFactor}`;
663
+ slippageErrorMessage = message;
664
+ this.logger.warn(message);
665
+ }
666
+ } else {
667
+ if (
668
+ BigInt(order.takerAmount) >
669
+ BigInt(
670
+ slippageFactor
671
+ .times(optimalSwapExchange.srcAmount.toString())
672
+ .toFixed(0),
673
+ )
674
+ ) {
675
+ isFailOnSlippage = true;
676
+ const message = `${this.dexKey}-${
677
+ this.network
678
+ }: too much slippage on quote ${side} baseTokenAmount ${
679
+ order.takerAmount
680
+ } / srcAmount ${
681
+ optimalSwapExchange.srcAmount
682
+ } > ${slippageFactor.toFixed()}`;
683
+ slippageErrorMessage = message;
684
+ this.logger.warn(message);
685
+ }
686
+ }
687
+
688
+ let isTooStrictSlippage = false;
689
+ if (
690
+ isFailOnSlippage &&
691
+ isSell &&
692
+ new BigNumber(1)
693
+ .minus(slippageFactor)
694
+ .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION)
695
+ ) {
696
+ isTooStrictSlippage = true;
697
+ } else if (
698
+ isFailOnSlippage &&
699
+ isBuy &&
700
+ slippageFactor
701
+ .minus(1)
702
+ .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION)
703
+ ) {
704
+ isTooStrictSlippage = true;
705
+ }
706
+
707
+ if (isFailOnSlippage && isTooStrictSlippage) {
708
+ throw new TooStrictSlippageCheckError(slippageErrorMessage);
709
+ } else if (isFailOnSlippage && !isTooStrictSlippage) {
710
+ throw new SlippageCheckError(slippageErrorMessage);
711
+ }
712
+
713
+ return [
714
+ {
715
+ ...optimalSwapExchange,
716
+ data: {
717
+ quoteData: order,
718
+ },
719
+ },
720
+ { deadline: minDeadline },
721
+ ];
722
+ } catch (e) {
723
+ if (isAxiosError(e) && e.response && e.response.data) {
724
+ const errorData: RFQResponseError = e.response.data;
725
+ if (errorData.ReasonCode === 'FQ-009') {
726
+ this.logger.warn(
727
+ `${this.dexKey}-${this.network}: Encountered rate limited user=${options.txOrigin}. Adding to local rate limit cache`,
728
+ );
729
+ await this.setRateLimited(options.txOrigin, errorData.RetryAfter);
730
+ } else {
731
+ await this.setBlacklist(options.txOrigin);
732
+ this.logger.error(
733
+ `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}: ${errorData.Reason}`,
734
+ );
735
+ }
736
+ } else {
737
+ if (e instanceof TooStrictSlippageCheckError) {
738
+ this.logger.warn(
739
+ `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`,
740
+ );
741
+ } else {
742
+ const poolIdentifiers = await this.getPoolIdentifiers(
743
+ srcToken,
744
+ destToken,
745
+ side,
746
+ 0,
747
+ );
748
+ this.logger.warn(
749
+ `${this.dexKey}-${this.network}: protocol is restricted for pools ${poolIdentifiers} due to swap: ${swapIdentifier}`,
750
+ );
751
+ await Promise.all(
752
+ poolIdentifiers.map(async p => await this.restrictPool(p)),
753
+ );
754
+ }
755
+ }
756
+
757
+ throw e;
758
+ }
759
+ }
760
+
761
+ getCalldataGasCost(poolPrices: PoolPrices<DexalotData>): number | number[] {
762
+ return (
763
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
764
+ // addresses: makerAsset, takerAsset, maker, taker
765
+ CALLDATA_GAS_COST.ADDRESS * 4 +
766
+ // uint256: expiry
767
+ CALLDATA_GAS_COST.wordNonZeroBytes(16) +
768
+ // uint256: nonceAndMeta, makerAmount, takerAmount
769
+ CALLDATA_GAS_COST.AMOUNT * 3 +
770
+ // bytes: _signature (65 bytes)
771
+ CALLDATA_GAS_COST.FULL_WORD * 2 +
772
+ CALLDATA_GAS_COST.OFFSET_SMALL
773
+ );
774
+ }
775
+
776
+ getTokenFromAddress(address: Address): Token {
777
+ return this.tokensMap[this.normalizeAddress(address)];
778
+ }
779
+
780
+ getAdapterParam(
781
+ srcToken: string,
782
+ destToken: string,
783
+ srcAmount: string,
784
+ destAmount: string,
785
+ data: DexalotData,
786
+ side: SwapSide,
787
+ ): AdapterExchangeParam {
788
+ const { quoteData } = data;
789
+
790
+ assert(
791
+ quoteData !== undefined,
792
+ `${this.dexKey}-${this.network}: quoteData undefined`,
793
+ );
794
+
795
+ const params = [
796
+ {
797
+ nonceAndMeta: quoteData.nonceAndMeta,
798
+ expiry: quoteData.expiry,
799
+ makerAsset: quoteData.makerAsset,
800
+ takerAsset: quoteData.takerAsset,
801
+ maker: quoteData.maker,
802
+ taker: quoteData.taker,
803
+ makerAmount: quoteData.makerAmount,
804
+ takerAmount: quoteData.takerAmount,
805
+ },
806
+ quoteData.signature,
807
+ ];
808
+
809
+ const payload = this.abiCoder.encodeParameter(
810
+ {
811
+ ParentStruct: {
812
+ order: {
813
+ nonceAndMeta: 'uint256',
814
+ expiry: 'uint128',
815
+ makerAsset: 'address',
816
+ takerAsset: 'address',
817
+ maker: 'address',
818
+ taker: 'address',
819
+ makerAmount: 'uint256',
820
+ takerAmount: 'uint256',
821
+ },
822
+ signature: 'bytes',
823
+ },
824
+ },
825
+ {
826
+ order: params[0],
827
+ signature: params[1],
828
+ },
829
+ );
830
+
831
+ return {
832
+ targetExchange: this.mainnetRFQAddress,
833
+ payload,
834
+ networkFee: '0',
835
+ };
836
+ }
837
+
838
+ getRestrictedPoolKey(poolIdentifier: string): string {
839
+ return `${DEXALOT_RESTRICTED_CACHE_KEY}-${poolIdentifier}`;
840
+ }
841
+
842
+ async restrictPool(
843
+ poolIdentifier: string,
844
+ ttl: number = DEXALOT_RESTRICT_TTL_S,
845
+ ): Promise<boolean> {
846
+ await this.dexHelper.cache.setex(
847
+ this.dexKey,
848
+ this.network,
849
+ this.getRestrictedPoolKey(poolIdentifier),
850
+ ttl,
851
+ 'true',
852
+ );
853
+ return true;
854
+ }
855
+
856
+ async isRestrictedPool(poolIdentifier: string): Promise<boolean> {
857
+ const result = await this.dexHelper.cache.get(
858
+ this.dexKey,
859
+ this.network,
860
+ this.getRestrictedPoolKey(poolIdentifier),
861
+ );
862
+
863
+ return result === 'true';
864
+ }
865
+
866
+ async setBlacklist(
867
+ txOrigin: Address,
868
+ ttl: number = DEXALOT_BLACKLIST_CACHES_TTL_S,
869
+ ): Promise<boolean> {
870
+ const cachedBlacklist = await this.dexHelper.cache.get(
871
+ this.dexKey,
872
+ this.network,
873
+ this.blacklistCacheKey,
874
+ );
875
+
876
+ let blacklist: string[] = [];
877
+ if (cachedBlacklist) {
878
+ blacklist = JSON.parse(cachedBlacklist);
879
+ }
880
+
881
+ blacklist.push(txOrigin.toLowerCase());
882
+
883
+ this.dexHelper.cache.setex(
884
+ this.dexKey,
885
+ this.network,
886
+ this.blacklistCacheKey,
887
+ ttl,
888
+ JSON.stringify(blacklist),
889
+ );
890
+
891
+ return true;
892
+ }
893
+
894
+ async isBlacklisted(txOrigin: Address): Promise<boolean> {
895
+ const cachedBlacklist = await this.dexHelper.cache.get(
896
+ this.dexKey,
897
+ this.network,
898
+ this.blacklistCacheKey,
899
+ );
900
+
901
+ if (cachedBlacklist) {
902
+ const blacklist = JSON.parse(cachedBlacklist) as string[];
903
+ return blacklist.includes(txOrigin.toLowerCase());
904
+ }
905
+
906
+ // To not show pricing for rate limited users
907
+ if (await this.isRateLimited(txOrigin)) {
908
+ return true;
909
+ }
910
+
911
+ return false;
912
+ }
913
+
914
+ getRateLimitedKey(address: Address) {
915
+ return `rate_limited_${address}`.toLowerCase();
916
+ }
917
+
918
+ async isRateLimited(txOrigin: Address): Promise<boolean> {
919
+ const result = await this.dexHelper.cache.get(
920
+ this.dexKey,
921
+ this.network,
922
+ this.getRateLimitedKey(txOrigin),
923
+ );
924
+ return result === DEXALOT_RATELIMIT_CACHE_VALUE;
925
+ }
926
+
927
+ async setRateLimited(txOrigin: Address, ttl = DEXALOT_RATE_LIMITED_TTL_S) {
928
+ await this.dexHelper.cache.setex(
929
+ this.dexKey,
930
+ this.network,
931
+ this.getRateLimitedKey(txOrigin),
932
+ ttl,
933
+ DEXALOT_RATELIMIT_CACHE_VALUE,
934
+ );
935
+ return true;
936
+ }
937
+
938
+ async getSimpleParam(
939
+ srcToken: string,
940
+ destToken: string,
941
+ srcAmount: string,
942
+ destAmount: string,
943
+ data: DexalotData,
944
+ side: SwapSide,
945
+ ): Promise<SimpleExchangeParam> {
946
+ const { quoteData } = data;
947
+
948
+ assert(
949
+ quoteData !== undefined,
950
+ `${this.dexKey}-${this.network}: quoteData undefined`,
951
+ );
952
+
953
+ const swapFunction = 'simpleSwap';
954
+ const swapFunctionParams = [
955
+ [
956
+ quoteData.nonceAndMeta,
957
+ quoteData.expiry,
958
+ quoteData.makerAsset,
959
+ quoteData.takerAsset,
960
+ quoteData.maker,
961
+ quoteData.taker,
962
+ quoteData.makerAmount,
963
+ quoteData.takerAmount,
964
+ ],
965
+ quoteData.signature,
966
+ ];
967
+
968
+ const swapData = this.rfqInterface.encodeFunctionData(
969
+ swapFunction,
970
+ swapFunctionParams,
971
+ );
972
+
973
+ return this.buildSimpleParamWithoutWETHConversion(
974
+ srcToken,
975
+ srcAmount,
976
+ destToken,
977
+ destAmount,
978
+ swapData,
979
+ this.mainnetRFQAddress,
980
+ );
981
+ }
982
+
983
+ getDexParam(
984
+ srcToken: Address,
985
+ destToken: Address,
986
+ srcAmount: NumberAsString,
987
+ destAmount: NumberAsString,
988
+ recipient: Address,
989
+ data: DexalotData,
990
+ side: SwapSide,
991
+ ): DexExchangeParam {
992
+ const { quoteData } = data;
993
+
994
+ assert(
995
+ quoteData !== undefined,
996
+ `${this.dexKey}-${this.network}: quoteData undefined`,
997
+ );
998
+
999
+ const swapFunction = 'simpleSwap';
1000
+ const swapFunctionParams = [
1001
+ [
1002
+ quoteData.nonceAndMeta,
1003
+ quoteData.expiry,
1004
+ quoteData.makerAsset,
1005
+ quoteData.takerAsset,
1006
+ quoteData.maker,
1007
+ quoteData.taker,
1008
+ quoteData.makerAmount,
1009
+ quoteData.takerAmount,
1010
+ ],
1011
+ quoteData.signature,
1012
+ ];
1013
+
1014
+ const exchangeData = this.rfqInterface.encodeFunctionData(
1015
+ swapFunction,
1016
+ swapFunctionParams,
1017
+ );
1018
+
1019
+ return {
1020
+ exchangeData,
1021
+ needWrapNative: this.needWrapNative,
1022
+ dexFuncHasRecipient: false,
1023
+ targetExchange: this.mainnetRFQAddress,
1024
+ returnAmountPos: undefined,
1025
+ };
1026
+ }
1027
+
1028
+ async getTopPoolsForToken(
1029
+ tokenAddress: Address,
1030
+ limit: number,
1031
+ ): Promise<PoolLiquidity[]> {
1032
+ const normalizedTokenAddress = this.normalizeAddress(tokenAddress);
1033
+ const pairs = (await this.getCachedPairs()) || {};
1034
+ this.tokensMap = (await this.getCachedTokens()) || {};
1035
+ const tokensAddr = (await this.getCachedTokensAddr()) || {};
1036
+ const token = this.getTokenFromAddress(normalizedTokenAddress);
1037
+ if (!token) {
1038
+ return [];
1039
+ }
1040
+
1041
+ const tokenSymbol = token.symbol?.toLowerCase() || '';
1042
+
1043
+ let pairsByLiquidity = [];
1044
+ for (const pairName of Object.keys(pairs)) {
1045
+ if (!pairName.includes(tokenSymbol)) {
1046
+ continue;
1047
+ }
1048
+
1049
+ const tokensInPair = pairName.split('/');
1050
+ if (tokensInPair.length !== 2) {
1051
+ continue;
1052
+ }
1053
+
1054
+ const [baseToken, quoteToken] = tokensInPair;
1055
+ const addr = tokensAddr[baseToken.toLowerCase()];
1056
+ let outputToken = this.getTokenFromAddress(addr);
1057
+ if (baseToken === tokenSymbol) {
1058
+ const addr = tokensAddr[quoteToken.toLowerCase()];
1059
+ outputToken = this.getTokenFromAddress(addr);
1060
+ }
1061
+
1062
+ const denormalizedToken = this.denormalizeToken(outputToken);
1063
+
1064
+ pairsByLiquidity.push({
1065
+ exchange: this.dexKey,
1066
+ address: this.mainnetRFQAddress,
1067
+ connectorTokens: [
1068
+ {
1069
+ address: denormalizedToken.address,
1070
+ decimals: denormalizedToken.decimals,
1071
+ },
1072
+ ],
1073
+ liquidityUSD: pairs[pairName].liquidityUSD,
1074
+ });
1075
+ }
1076
+
1077
+ pairsByLiquidity.sort(
1078
+ (a: PoolLiquidity, b: PoolLiquidity) => b.liquidityUSD - a.liquidityUSD,
1079
+ );
1080
+
1081
+ return pairsByLiquidity.slice(0, limit);
1082
+ }
1083
+
1084
+ getAPIReqParams(endpoint: string, method: Method): DexalotAPIParameters {
1085
+ return {
1086
+ url: `${DEXALOT_API_URL}/${endpoint}`,
1087
+ headers: { 'x-apikey': this.dexalotAuthToken },
1088
+ params: {
1089
+ chainid: this.network,
1090
+ },
1091
+ method: method,
1092
+ };
1093
+ }
1094
+
1095
+ releaseResources(): void {
1096
+ if (this.rateFetcher) {
1097
+ this.rateFetcher.stop();
1098
+ }
1099
+ }
1100
+ }