@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,1180 +1,1180 @@
1
- import { defaultAbiCoder } from '@ethersproject/abi';
2
- import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
3
- import { AbiItem } from 'web3-utils';
4
- import { pack } from '@ethersproject/solidity';
5
- import _ from 'lodash';
6
- import {
7
- ExchangePrices,
8
- PoolPrices,
9
- AdapterExchangeParam,
10
- SimpleExchangeParam,
11
- Logger,
12
- TransferFeeParams,
13
- Address,
14
- PoolLiquidity,
15
- Token,
16
- DexExchangeParam,
17
- NumberAsString,
18
- } from '../../types';
19
- import { SwapSide, Network, CACHE_PREFIX } from '../../constants';
20
- import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
21
- import {
22
- _require,
23
- getBigIntPow,
24
- getDexKeysWithNetwork,
25
- interpolate,
26
- isDestTokenTransferFeeToBeExchanged,
27
- isSrcTokenTransferFeeToBeExchanged,
28
- } from '../../utils';
29
- import { IDex } from '../../dex/idex';
30
- import { IDexHelper } from '../../dex-helper/idex-helper';
31
- import {
32
- AlgebraData,
33
- AlgebraFunctions,
34
- DexParams,
35
- IAlgebraPoolState,
36
- } from './types';
37
- import {
38
- SimpleExchange,
39
- getLocalDeadlineAsFriendlyPlaceholder,
40
- } from '../simple-exchange';
41
- import { AlgebraConfig, Adapters } from './config';
42
- import { Contract } from 'web3-eth-contract';
43
- import { Interface } from 'ethers/lib/utils';
44
- import SwapRouter from '../../abi/algebra/SwapRouter.json';
45
- import AlgebraQuoterABI from '../../abi/algebra/AlgebraQuoter.abi.json';
46
- import UniswapV3MultiABI from '../../abi/uniswap-v3/UniswapMulti.abi.json';
47
- import AlgebraStateMulticallABI from '../../abi/algebra/AlgebraStateMulticall.abi.json';
48
- import { OutputResult } from '../uniswap-v3/types';
49
- import { AlgebraMath } from './lib/AlgebraMath';
50
- import { AlgebraEventPoolV1_1 } from './algebra-pool-v1_1';
51
- import { AlgebraEventPoolV1_9 } from './algebra-pool-v1_9';
52
- import { AlgebraFactory, OnPoolCreatedCallback } from './algebra-factory';
53
- import { applyTransferFee } from '../../lib/token-transfer-fee';
54
- import { AlgebraEventPoolV1_9_bidirectional_fee } from './algebra-pool-v1_9_bidirectional_fee';
55
- import { extractReturnAmountPosition } from '../../executor/utils';
56
-
57
- type PoolPairsInfo = {
58
- token0: Address;
59
- token1: Address;
60
- };
61
-
62
- const PoolsRegistryHashKey = `${CACHE_PREFIX}_poolsRegistry`;
63
-
64
- // const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days
65
- // const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day
66
- const ALGEBRA_EFFICIENCY_FACTOR = 3;
67
- const ALGEBRA_TICK_GAS_COST = 24_000; // Ceiled
68
- const ALGEBRA_TICK_BASE_OVERHEAD = 75_000;
69
- const ALGEBRA_POOL_SEARCH_OVERHEAD = 10_000;
70
- const ALGEBRA_QUOTE_GASLIMIT = 2_000_000;
71
-
72
- const MAX_STALE_STATE_BLOCK_AGE = {
73
- [Network.ZKEVM]: 150, // approximately 3min
74
- };
75
-
76
- type IAlgebraEventPool =
77
- | AlgebraEventPoolV1_1
78
- | AlgebraEventPoolV1_9
79
- | AlgebraEventPoolV1_9_bidirectional_fee;
80
-
81
- export class Algebra extends SimpleExchange implements IDex<AlgebraData> {
82
- private readonly factory: AlgebraFactory;
83
- readonly isFeeOnTransferSupported: boolean = true;
84
- protected eventPools: Record<string, IAlgebraEventPool | null> = {};
85
-
86
- private newlyCreatedPoolKeys: Set<string> = new Set();
87
-
88
- readonly hasConstantPriceLargeAmounts = false;
89
- readonly needWrapNative = true;
90
-
91
- intervalTask?: NodeJS.Timeout;
92
-
93
- public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
94
- getDexKeysWithNetwork(AlgebraConfig);
95
-
96
- logger: Logger;
97
-
98
- private uniswapMulti: Contract;
99
- private stateMultiContract: Contract;
100
-
101
- private notExistingPoolSetKey: string;
102
-
103
- private AlgebraPoolImplem:
104
- | typeof AlgebraEventPoolV1_1
105
- | typeof AlgebraEventPoolV1_9
106
- | typeof AlgebraEventPoolV1_9_bidirectional_fee;
107
-
108
- readonly SRC_TOKEN_DEX_TRANSFERS = 1;
109
- readonly DEST_TOKEN_DEX_TRANSFERS = 1;
110
-
111
- constructor(
112
- protected network: Network,
113
- dexKey: string,
114
- protected dexHelper: IDexHelper,
115
- protected adapters = Adapters[network] || {},
116
- readonly routerIface = new Interface(SwapRouter),
117
- readonly quoterIface = new Interface(AlgebraQuoterABI),
118
- readonly config = AlgebraConfig[dexKey][network],
119
- ) {
120
- super(dexHelper, dexKey);
121
- this.logger = dexHelper.getLogger(dexKey + '-' + network);
122
- this.uniswapMulti = new this.dexHelper.web3Provider.eth.Contract(
123
- UniswapV3MultiABI as AbiItem[],
124
- this.config.uniswapMulticall,
125
- );
126
- this.stateMultiContract = new this.dexHelper.web3Provider.eth.Contract(
127
- AlgebraStateMulticallABI as AbiItem[],
128
- this.config.algebraStateMulticall,
129
- );
130
-
131
- this.dexHelper.web3Provider.eth.handleRevert = false;
132
-
133
- this.config = this._toLowerForAllConfigAddresses();
134
- // External configuration has priority over internal
135
- this.config.forceRPC = dexHelper.config.data.forceRpcFallbackDexs.includes(
136
- dexKey.toLowerCase(),
137
- );
138
-
139
- this.notExistingPoolSetKey =
140
- `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase();
141
-
142
- this.AlgebraPoolImplem =
143
- config.version === 'v1.1'
144
- ? AlgebraEventPoolV1_1
145
- : config.version === 'v1.9-bidirectional-fee'
146
- ? AlgebraEventPoolV1_9_bidirectional_fee
147
- : AlgebraEventPoolV1_9;
148
-
149
- this.factory = new AlgebraFactory(
150
- dexHelper,
151
- dexKey,
152
- this.config.factory,
153
- this.logger,
154
- this.onPoolCreatedDeleteFromNonExistingSet,
155
- );
156
- }
157
-
158
- getAdapters(side: SwapSide): { name: string; index: number }[] | null {
159
- return this.adapters[side] ? this.adapters[side] : null;
160
- }
161
-
162
- getPoolIdentifier(srcAddress: Address, destAddress: Address) {
163
- const tokenAddresses = this._sortTokens(srcAddress, destAddress).join('_');
164
- return `${this.dexKey}_${tokenAddresses}`;
165
- }
166
-
167
- async initializePricing(blockNumber: number) {
168
- // Init listening to new pools creation
169
- await this.factory.initialize(blockNumber);
170
-
171
- //// COMMENTING DEPRECATED LOGIC: as we now invalidate pools on creation this is not needed anymore
172
- // if (!this.dexHelper.config.isSlave) {
173
- // const cleanExpiredNotExistingPoolsKeys = async () => {
174
- // const maxTimestamp =
175
- // Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS;
176
- // await this.dexHelper.cache.zremrangebyscore(
177
- // this.notExistingPoolSetKey,
178
- // 0,
179
- // maxTimestamp,
180
- // );
181
- // };
182
-
183
- // this.intervalTask = setInterval(
184
- // cleanExpiredNotExistingPoolsKeys.bind(this),
185
- // ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS,
186
- // );
187
- // }
188
- }
189
-
190
- /*
191
- * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls
192
- * Once the pool is created, it gets immediately flagged
193
- */
194
- onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({
195
- token0,
196
- token1,
197
- }) => {
198
- const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]';
199
- const [_token0, _token1] = this._sortTokens(token0, token1);
200
- const poolKey = `${_token0}_${_token1}`.toLowerCase();
201
-
202
- this.newlyCreatedPoolKeys.add(poolKey);
203
-
204
- // consider doing it only from master pool for less calls to distant cache
205
-
206
- // delete entry locally to let local instance discover the pool
207
- delete this.eventPools[this.getPoolIdentifier(_token0, _token1)];
208
-
209
- try {
210
- this.logger.info(
211
- `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`,
212
- );
213
- // delete pool record from set
214
- const result = await this.dexHelper.cache.zrem(
215
- this.notExistingPoolSetKey,
216
- [poolKey],
217
- );
218
- this.logger.info(
219
- `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`,
220
- );
221
- } catch (error) {
222
- this.logger.error(
223
- `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`,
224
- error,
225
- );
226
- }
227
- };
228
-
229
- async getPool(
230
- srcAddress: Address,
231
- destAddress: Address,
232
- blockNumber: number,
233
- ): Promise<IAlgebraEventPool | null> {
234
- let pool = this.eventPools[
235
- this.getPoolIdentifier(srcAddress, destAddress)
236
- ] as IAlgebraEventPool | null | undefined;
237
-
238
- if (pool === null) return null;
239
-
240
- if (pool) {
241
- if (!pool.initFailed) {
242
- if (this.network !== Network.ZKEVM) return pool;
243
-
244
- if (
245
- pool.getStaleState() === null ||
246
- (pool.getState(blockNumber) === null &&
247
- blockNumber - pool.getStateBlockNumber() >
248
- MAX_STALE_STATE_BLOCK_AGE[this.network])
249
- ) {
250
- /* reload state, on zkEVM this would most likely timeout during request life
251
- * but would allow to rely on staleState for couple of min for next requests
252
- */
253
- await pool.initialize(blockNumber, { forceRegenerate: true });
254
- }
255
-
256
- return pool;
257
- } else {
258
- // if init failed then prefer to early return pool with empty state to fallback to rpc call
259
- if (
260
- ++pool.initRetryAttemptCount % this.config.initRetryFrequency !==
261
- 0
262
- ) {
263
- return pool;
264
- }
265
- // else pursue with re-try initialization
266
- }
267
- }
268
-
269
- const [token0, token1] = this._sortTokens(srcAddress, destAddress);
270
-
271
- const key = `${token0}_${token1}`.toLowerCase();
272
-
273
- // no need to run this logic on retry initialisation scenario
274
- if (!pool) {
275
- const notExistingPoolScore = await this.dexHelper.cache.zscore(
276
- this.notExistingPoolSetKey,
277
- key,
278
- );
279
-
280
- const poolDoesNotExist = notExistingPoolScore !== null;
281
-
282
- if (poolDoesNotExist) {
283
- this.eventPools[this.getPoolIdentifier(srcAddress, destAddress)] = null;
284
- return null;
285
- }
286
- }
287
-
288
- this.logger.trace(`starting to listen to new pool: ${key}`);
289
- pool =
290
- pool ||
291
- new this.AlgebraPoolImplem(
292
- this.dexHelper,
293
- this.dexKey,
294
- this.stateMultiContract,
295
- this.erc20Interface,
296
- this.config.factory,
297
- token0,
298
- token1,
299
- this.logger,
300
- this.cacheStateKey,
301
- this.config.initHash,
302
- this.config.deployer,
303
- this.config.forceManualStateGenerate,
304
- );
305
-
306
- try {
307
- await pool.initialize(blockNumber, {
308
- initCallback: state => {
309
- //really hacky, we need to push poolAddress so that we subscribeToLogs in StatefulEventSubscriber
310
- pool!.addressesSubscribed[0] = state.pool;
311
- pool!.poolAddress = state.pool;
312
- pool!.initFailed = false;
313
- pool!.initRetryAttemptCount = 0;
314
- },
315
- });
316
-
317
- if (this.newlyCreatedPoolKeys.has(key)) {
318
- this.newlyCreatedPoolKeys.delete(key);
319
- }
320
- } catch (e) {
321
- if (e instanceof Error && e.message.endsWith('Pool does not exist')) {
322
- /*
323
- protection against 2 race conditions
324
- 1/ if pool.initialize() promise rejects after the Pool creation event got treated
325
- 2/ if the rpc node we hit on the http request is lagging behind the one we got event from (websocket)
326
- */
327
- if (this.newlyCreatedPoolKeys.has(key)) {
328
- this.logger.warn(
329
- `[block=${blockNumber}][Pool=${key}] newly created pool failed to initialise, trying on next request`,
330
- );
331
- } else {
332
- this.logger.info(
333
- `[block=${blockNumber}][Pool=${key}] pool failed to initialize so it's marked as non existing`,
334
- e,
335
- );
336
-
337
- // no need to await we want the set to have the pool key but it's not blocking
338
- this.dexHelper.cache.zadd(
339
- this.notExistingPoolSetKey,
340
- [Date.now(), key],
341
- 'NX',
342
- );
343
-
344
- // Pool does not exist for this pair, so we can set it to null
345
- // to prevent more requests for this pool
346
- pool = null;
347
- }
348
- } else {
349
- // on unknown error mark as failed and increase retryCount for retry init strategy
350
- // note: state would be null by default which allows to fallback
351
- this.logger.warn(
352
- `[block=${blockNumber}][Pool=${key}] Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`,
353
- e,
354
- );
355
- pool.initFailed = true;
356
- }
357
- }
358
-
359
- if (pool !== null) {
360
- const allEventPools = Object.values(this.eventPools);
361
- // if pool was created, delete pool record from non existing set
362
- this.dexHelper.cache
363
- .zrem(this.notExistingPoolSetKey, [key])
364
- .catch(() => {});
365
- this.logger.info(
366
- `starting to listen to new non-null pool: ${key}. Already following ${allEventPools
367
- // Not that I like this reduce, but since it is done only on initialization, expect this to be ok
368
- .reduce(
369
- (acc, curr) => (curr !== null ? ++acc : acc),
370
- 0,
371
- )} non-null pools or ${allEventPools.length} total pools`,
372
- );
373
- }
374
-
375
- this.eventPools[this.getPoolIdentifier(srcAddress, destAddress)] = pool;
376
- return pool;
377
- }
378
-
379
- async addMasterPool(poolKey: string, blockNumber: number): Promise<boolean> {
380
- const _pairs = await this.dexHelper.cache.hget(
381
- PoolsRegistryHashKey,
382
- `${this.cacheStateKey}_${poolKey}`,
383
- );
384
- if (!_pairs) {
385
- this.logger.warn(
386
- `did not find poolConfig in for key ${PoolsRegistryHashKey} ${this.cacheStateKey}_${poolKey}`,
387
- );
388
- return false;
389
- }
390
-
391
- const poolInfo: PoolPairsInfo = JSON.parse(_pairs);
392
-
393
- const pool = await this.getPool(
394
- poolInfo.token0,
395
- poolInfo.token1,
396
- blockNumber,
397
- );
398
-
399
- if (!pool) {
400
- return false;
401
- }
402
-
403
- return true;
404
- }
405
-
406
- async getPoolIdentifiers(
407
- srcToken: Token,
408
- destToken: Token,
409
- side: SwapSide,
410
- blockNumber: number,
411
- ): Promise<string[]> {
412
- const _srcToken = this.dexHelper.config.wrapETH(srcToken);
413
- const _destToken = this.dexHelper.config.wrapETH(destToken);
414
-
415
- const [_srcAddress, _destAddress] = this._getLoweredAddresses(
416
- _srcToken,
417
- _destToken,
418
- );
419
-
420
- if (_srcAddress === _destAddress) return [];
421
-
422
- const pool = await this.getPool(_srcAddress, _destAddress, blockNumber);
423
- if (!pool) return [];
424
- return [this.getPoolIdentifier(_srcAddress, _destAddress)];
425
- }
426
-
427
- async getPricingFromRpc(
428
- from: Token,
429
- to: Token,
430
- amounts: bigint[],
431
- side: SwapSide,
432
- pool: IAlgebraEventPool,
433
- transferFees: TransferFeeParams = {
434
- srcFee: 0,
435
- destFee: 0,
436
- srcDexFee: 0,
437
- destDexFee: 0,
438
- },
439
- ): Promise<ExchangePrices<AlgebraData> | null> {
440
- if (!this.config.forceRPC) {
441
- this.logger.warn(
442
- `fallback to rpc for ${from.address}_${to.address}_${pool.name}_${pool.poolAddress} pool(s)`,
443
- );
444
- }
445
-
446
- const _isSrcTokenTransferFeeToBeExchanged =
447
- isSrcTokenTransferFeeToBeExchanged(transferFees);
448
- const _isDestTokenTransferFeeToBeExchanged =
449
- isDestTokenTransferFeeToBeExchanged(transferFees);
450
-
451
- const unitVolume = getBigIntPow(
452
- (side === SwapSide.SELL ? from : to).decimals,
453
- );
454
-
455
- const chunks = amounts.length - 1;
456
-
457
- const _width = Math.floor(chunks / this.config.chunksCount);
458
-
459
- const _amounts = [unitVolume].concat(
460
- Array.from(Array(this.config.chunksCount).keys()).map(
461
- i => amounts[(i + 1) * _width],
462
- ),
463
- );
464
-
465
- const _amountsWithFee = _isSrcTokenTransferFeeToBeExchanged
466
- ? applyTransferFee(
467
- _amounts,
468
- side,
469
- transferFees.srcDexFee,
470
- this.SRC_TOKEN_DEX_TRANSFERS,
471
- )
472
- : _amounts;
473
-
474
- const calldata = _amountsWithFee.map(_amount => ({
475
- target: this.config.quoter,
476
- gasLimit: ALGEBRA_QUOTE_GASLIMIT,
477
- callData:
478
- side === SwapSide.SELL
479
- ? this.quoterIface.encodeFunctionData('quoteExactInputSingle', [
480
- from.address,
481
- to.address,
482
- _amount.toString(),
483
- 0, //sqrtPriceLimitX96
484
- ])
485
- : this.quoterIface.encodeFunctionData('quoteExactOutputSingle', [
486
- from.address,
487
- to.address,
488
- _amount.toString(),
489
- 0, //sqrtPriceLimitX96
490
- ]),
491
- }));
492
-
493
- const data = await this.uniswapMulti.methods.multicall(calldata).call();
494
-
495
- let totalGasCost = 0;
496
- let totalSuccessFullSwaps = 0;
497
- const decode = (j: number): bigint => {
498
- const { success, gasUsed, returnData } = data.returnData[j];
499
-
500
- if (!success) {
501
- return 0n;
502
- }
503
- const decoded = defaultAbiCoder.decode(['uint256'], returnData);
504
- totalGasCost += +gasUsed;
505
- totalSuccessFullSwaps++;
506
-
507
- return BigInt(decoded[0].toString());
508
- };
509
-
510
- const averageGasCost = !totalSuccessFullSwaps
511
- ? ALGEBRA_QUOTE_GASLIMIT
512
- : Math.round(totalGasCost / totalSuccessFullSwaps);
513
-
514
- let i = 0;
515
- const _rates = _amountsWithFee.map(() => decode(i++));
516
-
517
- const _ratesWithFee = _isDestTokenTransferFeeToBeExchanged
518
- ? applyTransferFee(
519
- _rates,
520
- side,
521
- transferFees.destDexFee,
522
- this.DEST_TOKEN_DEX_TRANSFERS,
523
- )
524
- : _rates;
525
-
526
- const unit: bigint = _ratesWithFee[0];
527
-
528
- const prices = interpolate(
529
- _amountsWithFee.slice(1),
530
- _ratesWithFee.slice(1),
531
- amounts,
532
- side,
533
- );
534
-
535
- return [
536
- {
537
- prices,
538
- unit,
539
- data: {
540
- feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged,
541
- path: [
542
- {
543
- tokenIn: from.address,
544
- tokenOut: to.address,
545
- },
546
- ],
547
- },
548
- poolIdentifier: this.getPoolIdentifier(pool.token0, pool.token1),
549
- exchange: this.dexKey,
550
- gasCost: prices.map(p => (p === 0n ? 0 : averageGasCost)),
551
- poolAddresses: [pool.poolAddress],
552
- },
553
- ];
554
- }
555
-
556
- async getPricesVolume(
557
- srcToken: Token,
558
- destToken: Token,
559
- amounts: bigint[],
560
- side: SwapSide,
561
- blockNumber: number,
562
- limitPools?: string[],
563
- transferFees: TransferFeeParams = {
564
- srcFee: 0,
565
- destFee: 0,
566
- srcDexFee: 0,
567
- destDexFee: 0,
568
- },
569
- ): Promise<null | ExchangePrices<AlgebraData>> {
570
- try {
571
- const _srcToken = this.dexHelper.config.wrapETH(srcToken);
572
- const _destToken = this.dexHelper.config.wrapETH(destToken);
573
-
574
- const [_srcAddress, _destAddress] = this._getLoweredAddresses(
575
- _srcToken,
576
- _destToken,
577
- );
578
-
579
- const _isSrcTokenTransferFeeToBeExchanged =
580
- isSrcTokenTransferFeeToBeExchanged(transferFees);
581
- const _isDestTokenTransferFeeToBeExchanged =
582
- isDestTokenTransferFeeToBeExchanged(transferFees);
583
-
584
- if (_srcAddress === _destAddress) return null;
585
-
586
- if (
587
- !limitPools?.includes(this.getPoolIdentifier(_srcAddress, _destAddress))
588
- )
589
- return null;
590
-
591
- const pool = await this.getPool(_srcAddress, _destAddress, blockNumber);
592
- if (!pool) return null;
593
-
594
- if (_isSrcTokenTransferFeeToBeExchanged && side == SwapSide.BUY) {
595
- this.logger.error(
596
- `pool: ${pool.poolAddress} doesn't support buy for tax srcToken ${srcToken.address}`,
597
- );
598
- return null;
599
- }
600
-
601
- if (this.config.forceRPC) {
602
- const rpcPrice = await this.getPricingFromRpc(
603
- _srcToken,
604
- _destToken,
605
- amounts,
606
- side,
607
- pool,
608
- transferFees,
609
- );
610
-
611
- return rpcPrice;
612
- }
613
-
614
- let state = pool.getState(blockNumber);
615
-
616
- if (state === null) {
617
- if (this.network === Network.ZKEVM) {
618
- if (pool.initFailed) return null;
619
-
620
- if (
621
- blockNumber - pool.getStateBlockNumber() <=
622
- MAX_STALE_STATE_BLOCK_AGE[this.network]
623
- ) {
624
- this.logger.warn(
625
- `${_srcAddress}_${_destAddress}_${pool.name}_${
626
- pool.poolAddress
627
- } state fallback to latest early enough state. Current blockNumber=${blockNumber}, stateBlockNumber=${pool.getStateBlockNumber()}`,
628
- );
629
- state = pool.getStaleState();
630
- } else {
631
- this.logger.warn(
632
- `${_srcAddress}_${_destAddress}_${pool.name}_${pool.poolAddress} state is unhealthy, cannot compute price (no fallback on this chain)`,
633
- );
634
- return null; // never fallback as takes more time
635
- }
636
- } else {
637
- const rpcPrice = await this.getPricingFromRpc(
638
- _srcToken,
639
- _destToken,
640
- amounts,
641
- side,
642
- pool,
643
- transferFees,
644
- );
645
-
646
- return rpcPrice;
647
- }
648
- }
649
-
650
- if (!state) return null;
651
-
652
- const unitAmount = getBigIntPow(
653
- side == SwapSide.SELL ? _srcToken.decimals : _destToken.decimals,
654
- );
655
-
656
- const _amounts = [...amounts.slice(1)];
657
-
658
- const [token0] = this._sortTokens(_srcAddress, _destAddress);
659
-
660
- const zeroForOne = token0 === _srcAddress ? true : false;
661
-
662
- if (state.liquidity <= 0n) {
663
- if (state.liquidity < 0) {
664
- this.logger.error(
665
- `${this.dexKey}-${this.network}: ${pool.poolAddress} pool has negative liquidity: ${state.liquidity}. Find with key: ${pool.mapKey}`,
666
- );
667
- }
668
- this.logger.trace(`pool have 0 liquidity`);
669
- return null;
670
- }
671
-
672
- const balanceDestToken =
673
- _destAddress === pool.token0 ? state.balance0 : state.balance1;
674
-
675
- const [unitAmountWithFee, ...amountsWithFee] =
676
- _isSrcTokenTransferFeeToBeExchanged
677
- ? applyTransferFee(
678
- [unitAmount, ..._amounts],
679
- side,
680
- transferFees.srcDexFee,
681
- this.SRC_TOKEN_DEX_TRANSFERS,
682
- )
683
- : [unitAmount, ..._amounts];
684
-
685
- const unitResult = this._getOutputs(
686
- state,
687
- [unitAmountWithFee],
688
- zeroForOne,
689
- side,
690
- balanceDestToken,
691
- );
692
- const pricesResult = this._getOutputs(
693
- state,
694
- amountsWithFee,
695
- zeroForOne,
696
- side,
697
- balanceDestToken,
698
- );
699
-
700
- if (!unitResult || !pricesResult) {
701
- this.logger.debug('Prices or unit is not calculated');
702
- return null;
703
- }
704
-
705
- const [unitResultWithFee, ...pricesResultWithFee] =
706
- _isDestTokenTransferFeeToBeExchanged
707
- ? applyTransferFee(
708
- [unitResult.outputs[0], ...pricesResult.outputs],
709
- side,
710
- transferFees.destDexFee,
711
- this.DEST_TOKEN_DEX_TRANSFERS,
712
- )
713
- : [unitResult.outputs[0], ...pricesResult.outputs];
714
-
715
- const prices = [0n, ...pricesResultWithFee];
716
- const gasCost = [
717
- 0,
718
- ...pricesResultWithFee.map((p, index) => {
719
- if (p == 0n) {
720
- return 0;
721
- } else {
722
- return (
723
- ALGEBRA_POOL_SEARCH_OVERHEAD +
724
- ALGEBRA_TICK_BASE_OVERHEAD +
725
- pricesResult.tickCounts[index] * ALGEBRA_TICK_GAS_COST
726
- );
727
- }
728
- }),
729
- ];
730
-
731
- return [
732
- {
733
- unit: unitResultWithFee,
734
- prices,
735
- data: {
736
- feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged,
737
- path: [
738
- {
739
- tokenIn: _srcAddress,
740
- tokenOut: _destAddress,
741
- },
742
- ],
743
- },
744
- poolIdentifier: this.getPoolIdentifier(pool.token0, pool.token1),
745
- exchange: this.dexKey,
746
- gasCost: gasCost,
747
- poolAddresses: [pool.poolAddress],
748
- },
749
- ];
750
- } catch (e) {
751
- this.logger.error(
752
- `Error_getPricesVolume ${srcToken.symbol || srcToken.address}, ${
753
- destToken.symbol || destToken.address
754
- }, ${side}:`,
755
- e,
756
- );
757
- return null;
758
- }
759
- }
760
-
761
- getAdapterParam(
762
- srcToken: string,
763
- destToken: string,
764
- srcAmount: string,
765
- destAmount: string,
766
- data: AlgebraData,
767
- side: SwapSide,
768
- ): AdapterExchangeParam {
769
- let path = this._encodePath(data.path, side);
770
-
771
- const payload = this.abiCoder.encodeParameter(
772
- {
773
- ParentStruct: {
774
- path: 'bytes',
775
- deadline: 'uint256',
776
- feeOnTransfer: 'bool',
777
- },
778
- },
779
- {
780
- path,
781
- deadline: getLocalDeadlineAsFriendlyPlaceholder(), // FIXME: more gas efficient to pass block.timestamp in adapter
782
- feeOnTransfer: data.feeOnTransfer,
783
- },
784
- );
785
-
786
- return {
787
- targetExchange: this.config.router,
788
- payload,
789
- networkFee: '0',
790
- };
791
- }
792
-
793
- getCalldataGasCost(poolPrices: PoolPrices<AlgebraData>): number | number[] {
794
- const gasCost =
795
- CALLDATA_GAS_COST.DEX_OVERHEAD +
796
- CALLDATA_GAS_COST.LENGTH_SMALL +
797
- // ParentStruct header
798
- CALLDATA_GAS_COST.OFFSET_SMALL +
799
- // ParentStruct -> path header
800
- CALLDATA_GAS_COST.OFFSET_SMALL +
801
- // ParentStruct -> deadline
802
- CALLDATA_GAS_COST.TIMESTAMP +
803
- // ParentStruct -> path (20+3+20 = 43 = 32+11 bytes)
804
- CALLDATA_GAS_COST.LENGTH_SMALL +
805
- CALLDATA_GAS_COST.FULL_WORD +
806
- CALLDATA_GAS_COST.wordNonZeroBytes(11);
807
- const arr = new Array(poolPrices.prices.length);
808
- poolPrices.prices.forEach((p, index) => {
809
- if (p == 0n) {
810
- arr[index] = 0;
811
- } else {
812
- arr[index] = gasCost;
813
- }
814
- });
815
- return arr;
816
- }
817
-
818
- async getSimpleParam(
819
- srcToken: string,
820
- destToken: string,
821
- srcAmount: string,
822
- destAmount: string,
823
- data: AlgebraData,
824
- side: SwapSide,
825
- ): Promise<SimpleExchangeParam> {
826
- let swapFunction;
827
- let swapParams;
828
-
829
- if (data.feeOnTransfer) {
830
- _require(
831
- data.path.length === 1,
832
- `LOGIC ERROR: multihop is not supported for feeOnTransfer token, passed: ${data.path
833
- .map(p => `${p?.tokenIn}->${p?.tokenOut}`)
834
- .join(' ')}`,
835
- );
836
- swapFunction = AlgebraFunctions.exactInputWithFeeToken;
837
- swapParams = {
838
- limitSqrtPrice: '0',
839
- recipient: this.augustusAddress,
840
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
841
- amountIn: srcAmount,
842
- amountOutMinimum: destAmount,
843
- tokenIn: data.path[0].tokenIn,
844
- tokenOut: data.path[0].tokenOut,
845
- };
846
- } else {
847
- swapFunction =
848
- side === SwapSide.SELL
849
- ? AlgebraFunctions.exactInput
850
- : AlgebraFunctions.exactOutput;
851
- const path = this._encodePath(data.path, side);
852
- swapParams =
853
- side === SwapSide.SELL
854
- ? {
855
- recipient: this.augustusAddress,
856
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
857
- amountIn: srcAmount,
858
- amountOutMinimum: destAmount,
859
- path,
860
- }
861
- : {
862
- recipient: this.augustusAddress,
863
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
864
- amountOut: destAmount,
865
- amountInMaximum: srcAmount,
866
- path,
867
- };
868
- }
869
-
870
- const swapData = this.routerIface.encodeFunctionData(swapFunction, [
871
- swapParams,
872
- ]);
873
-
874
- return this.buildSimpleParamWithoutWETHConversion(
875
- srcToken,
876
- srcAmount,
877
- destToken,
878
- destAmount,
879
- swapData,
880
- this.config.router,
881
- );
882
- }
883
-
884
- getDexParam(
885
- srcToken: Address,
886
- destToken: Address,
887
- srcAmount: NumberAsString,
888
- destAmount: NumberAsString,
889
- recipient: Address,
890
- data: AlgebraData,
891
- side: SwapSide,
892
- ): DexExchangeParam {
893
- let swapFunction;
894
- let swapFunctionParams;
895
-
896
- if (data.feeOnTransfer) {
897
- _require(
898
- data.path.length === 1,
899
- `LOGIC ERROR: multihop is not supported for feeOnTransfer token, passed: ${data.path
900
- .map(p => `${p?.tokenIn}->${p?.tokenOut}`)
901
- .join(' ')}`,
902
- );
903
- swapFunction = AlgebraFunctions.exactInputWithFeeToken;
904
- swapFunctionParams = {
905
- limitSqrtPrice: '0',
906
- recipient: recipient,
907
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
908
- amountIn: srcAmount,
909
- amountOutMinimum: destAmount,
910
- tokenIn: data.path[0].tokenIn,
911
- tokenOut: data.path[0].tokenOut,
912
- };
913
- } else {
914
- swapFunction =
915
- side === SwapSide.SELL
916
- ? AlgebraFunctions.exactInput
917
- : AlgebraFunctions.exactOutput;
918
- const path = this._encodePath(data.path, side);
919
- swapFunctionParams =
920
- side === SwapSide.SELL
921
- ? {
922
- recipient: recipient,
923
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
924
- amountIn: srcAmount,
925
- amountOutMinimum: destAmount,
926
- path,
927
- }
928
- : {
929
- recipient: recipient,
930
- deadline: getLocalDeadlineAsFriendlyPlaceholder(),
931
- amountOut: destAmount,
932
- amountInMaximum: srcAmount,
933
- path,
934
- };
935
- }
936
-
937
- const exchangeData = this.routerIface.encodeFunctionData(swapFunction, [
938
- swapFunctionParams,
939
- ]);
940
-
941
- return {
942
- needWrapNative: this.needWrapNative,
943
- dexFuncHasRecipient: true,
944
- exchangeData,
945
- targetExchange: this.config.router,
946
- returnAmountPos:
947
- side === SwapSide.SELL
948
- ? extractReturnAmountPosition(
949
- this.routerIface,
950
- swapFunction,
951
- 'amountOut',
952
- )
953
- : undefined,
954
- };
955
- }
956
-
957
- async getTopPoolsForToken(
958
- tokenAddress: Address,
959
- limit: number,
960
- ): Promise<PoolLiquidity[]> {
961
- const _tokenAddress = tokenAddress.toLowerCase();
962
-
963
- const res = await this._querySubgraph(
964
- `query ($token: Bytes!, $count: Int) {
965
- pools0: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token0: $token}) {
966
- id
967
- token0 {
968
- id
969
- decimals
970
- }
971
- token1 {
972
- id
973
- decimals
974
- }
975
- totalValueLockedUSD
976
- }
977
- pools1: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token1: $token}) {
978
- id
979
- token0 {
980
- id
981
- decimals
982
- }
983
- token1 {
984
- id
985
- decimals
986
- }
987
- totalValueLockedUSD
988
- }
989
- }`,
990
- {
991
- token: _tokenAddress,
992
- count: limit,
993
- },
994
- );
995
-
996
- if (!(res && res.pools0 && res.pools1)) {
997
- this.logger.error(
998
- `Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`,
999
- );
1000
- return [];
1001
- }
1002
-
1003
- const pools0 = _.map(res.pools0, pool => ({
1004
- exchange: this.dexKey,
1005
- address: pool.id.toLowerCase(),
1006
- connectorTokens: [
1007
- {
1008
- address: pool.token1.id.toLowerCase(),
1009
- decimals: parseInt(pool.token1.decimals),
1010
- },
1011
- ],
1012
- liquidityUSD:
1013
- parseFloat(pool.totalValueLockedUSD) * ALGEBRA_EFFICIENCY_FACTOR,
1014
- }));
1015
-
1016
- const pools1 = _.map(res.pools1, pool => ({
1017
- exchange: this.dexKey,
1018
- address: pool.id.toLowerCase(),
1019
- connectorTokens: [
1020
- {
1021
- address: pool.token0.id.toLowerCase(),
1022
- decimals: parseInt(pool.token0.decimals),
1023
- },
1024
- ],
1025
- liquidityUSD:
1026
- parseFloat(pool.totalValueLockedUSD) * ALGEBRA_EFFICIENCY_FACTOR,
1027
- }));
1028
-
1029
- const pools = _.slice(
1030
- _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]),
1031
- 0,
1032
- limit,
1033
- );
1034
- return pools;
1035
- }
1036
-
1037
- private _getLoweredAddresses(srcToken: Token, destToken: Token) {
1038
- return [srcToken.address.toLowerCase(), destToken.address.toLowerCase()];
1039
- }
1040
-
1041
- private _sortTokens(srcAddress: Address, destAddress: Address) {
1042
- return [srcAddress, destAddress].sort((a, b) => (a < b ? -1 : 1));
1043
- }
1044
-
1045
- private _toLowerForAllConfigAddresses() {
1046
- // If new config property will be added, the TS will throw compile error
1047
- const newConfig: DexParams = {
1048
- router: this.config.router.toLowerCase(),
1049
- quoter: this.config.quoter.toLowerCase(),
1050
- factory: this.config.factory.toLowerCase(),
1051
- algebraStateMulticall: this.config.algebraStateMulticall.toLowerCase(),
1052
- chunksCount: this.config.chunksCount,
1053
- initRetryFrequency: this.config.initRetryFrequency,
1054
- uniswapMulticall: this.config.uniswapMulticall,
1055
- deployer: this.config.deployer?.toLowerCase(),
1056
- initHash: this.config.initHash,
1057
- subgraphURL: this.config.subgraphURL,
1058
- version: this.config.version,
1059
- forceRPC: this.config.forceRPC,
1060
- forceManualStateGenerate: this.config.forceManualStateGenerate,
1061
- };
1062
- return newConfig;
1063
- }
1064
-
1065
- private _getOutputs(
1066
- state: DeepReadonly<IAlgebraPoolState>,
1067
- amounts: bigint[],
1068
- zeroForOne: boolean,
1069
- side: SwapSide,
1070
- destTokenBalance: bigint,
1071
- ): OutputResult | null {
1072
- try {
1073
- const outputsResult = AlgebraMath.queryOutputs(
1074
- this.network,
1075
- state,
1076
- amounts,
1077
- zeroForOne,
1078
- side,
1079
- );
1080
-
1081
- if (side === SwapSide.SELL) {
1082
- if (outputsResult.outputs[0] > destTokenBalance) {
1083
- return null;
1084
- }
1085
-
1086
- for (let i = 0; i < outputsResult.outputs.length; i++) {
1087
- if (outputsResult.outputs[i] > destTokenBalance) {
1088
- outputsResult.outputs[i] = 0n;
1089
- outputsResult.tickCounts[i] = 0;
1090
- }
1091
- }
1092
- } else {
1093
- if (amounts[0] > destTokenBalance) {
1094
- return null;
1095
- }
1096
-
1097
- // This may be improved by first checking outputs and requesting outputs
1098
- // only for amounts that makes more sense, but I don't think this is really
1099
- // important now
1100
- for (let i = 0; i < amounts.length; i++) {
1101
- if (amounts[i] > destTokenBalance) {
1102
- outputsResult.outputs[i] = 0n;
1103
- outputsResult.tickCounts[i] = 0;
1104
- }
1105
- }
1106
- }
1107
-
1108
- return outputsResult;
1109
- } catch (e) {
1110
- this.logger.debug(
1111
- `${this.dexKey}: received error in _getOutputs while calculating outputs`,
1112
- e,
1113
- );
1114
- return null;
1115
- }
1116
- }
1117
-
1118
- private async _querySubgraph(
1119
- query: string,
1120
- variables: Object,
1121
- timeout = 30000,
1122
- ) {
1123
- try {
1124
- const res = await this.dexHelper.httpRequest.post(
1125
- this.config.subgraphURL,
1126
- { query, variables },
1127
- undefined,
1128
- { timeout: timeout },
1129
- );
1130
- return res.data;
1131
- } catch (e) {
1132
- this.logger.error(`${this.dexKey}: can not query subgraph: `, e);
1133
- return {};
1134
- }
1135
- }
1136
-
1137
- private _encodePath(
1138
- path: {
1139
- tokenIn: Address;
1140
- tokenOut: Address;
1141
- }[],
1142
- side: SwapSide,
1143
- ): string {
1144
- if (path.length === 0) {
1145
- return '0x';
1146
- }
1147
-
1148
- const { _path, types } = path.reduce(
1149
- (
1150
- { _path, types }: { _path: string[]; types: string[] },
1151
- curr,
1152
- index,
1153
- ): { _path: string[]; types: string[] } => {
1154
- if (index === 0) {
1155
- return {
1156
- types: ['address', 'address'],
1157
- _path: [curr.tokenIn, curr.tokenOut],
1158
- };
1159
- } else {
1160
- return {
1161
- types: [...types, 'address'],
1162
- _path: [..._path, curr.tokenOut],
1163
- };
1164
- }
1165
- },
1166
- { _path: [], types: [] },
1167
- );
1168
-
1169
- return side === SwapSide.BUY
1170
- ? pack(types.reverse(), _path.reverse())
1171
- : pack(types, _path);
1172
- }
1173
-
1174
- releaseResources() {
1175
- if (this.intervalTask !== undefined) {
1176
- clearInterval(this.intervalTask);
1177
- this.intervalTask = undefined;
1178
- }
1179
- }
1180
- }
1
+ import { defaultAbiCoder } from '@ethersproject/abi';
2
+ import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
3
+ import { AbiItem } from 'web3-utils';
4
+ import { pack } from '@ethersproject/solidity';
5
+ import _ from 'lodash';
6
+ import {
7
+ ExchangePrices,
8
+ PoolPrices,
9
+ AdapterExchangeParam,
10
+ SimpleExchangeParam,
11
+ Logger,
12
+ TransferFeeParams,
13
+ Address,
14
+ PoolLiquidity,
15
+ Token,
16
+ DexExchangeParam,
17
+ NumberAsString,
18
+ } from '../../types';
19
+ import { SwapSide, Network, CACHE_PREFIX } from '../../constants';
20
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
21
+ import {
22
+ _require,
23
+ getBigIntPow,
24
+ getDexKeysWithNetwork,
25
+ interpolate,
26
+ isDestTokenTransferFeeToBeExchanged,
27
+ isSrcTokenTransferFeeToBeExchanged,
28
+ } from '../../utils';
29
+ import { IDex } from '../../dex/idex';
30
+ import { IDexHelper } from '../../dex-helper/idex-helper';
31
+ import {
32
+ AlgebraData,
33
+ AlgebraFunctions,
34
+ DexParams,
35
+ IAlgebraPoolState,
36
+ } from './types';
37
+ import {
38
+ SimpleExchange,
39
+ getLocalDeadlineAsFriendlyPlaceholder,
40
+ } from '../simple-exchange';
41
+ import { AlgebraConfig, Adapters } from './config';
42
+ import { Contract } from 'web3-eth-contract';
43
+ import { Interface } from 'ethers/lib/utils';
44
+ import SwapRouter from '../../abi/algebra/SwapRouter.json';
45
+ import AlgebraQuoterABI from '../../abi/algebra/AlgebraQuoter.abi.json';
46
+ import UniswapV3MultiABI from '../../abi/uniswap-v3/UniswapMulti.abi.json';
47
+ import AlgebraStateMulticallABI from '../../abi/algebra/AlgebraStateMulticall.abi.json';
48
+ import { OutputResult } from '../uniswap-v3/types';
49
+ import { AlgebraMath } from './lib/AlgebraMath';
50
+ import { AlgebraEventPoolV1_1 } from './algebra-pool-v1_1';
51
+ import { AlgebraEventPoolV1_9 } from './algebra-pool-v1_9';
52
+ import { AlgebraFactory, OnPoolCreatedCallback } from './algebra-factory';
53
+ import { applyTransferFee } from '../../lib/token-transfer-fee';
54
+ import { AlgebraEventPoolV1_9_bidirectional_fee } from './algebra-pool-v1_9_bidirectional_fee';
55
+ import { extractReturnAmountPosition } from '../../executor/utils';
56
+
57
+ type PoolPairsInfo = {
58
+ token0: Address;
59
+ token1: Address;
60
+ };
61
+
62
+ const PoolsRegistryHashKey = `${CACHE_PREFIX}_poolsRegistry`;
63
+
64
+ // const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days
65
+ // const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day
66
+ const ALGEBRA_EFFICIENCY_FACTOR = 3;
67
+ const ALGEBRA_TICK_GAS_COST = 24_000; // Ceiled
68
+ const ALGEBRA_TICK_BASE_OVERHEAD = 75_000;
69
+ const ALGEBRA_POOL_SEARCH_OVERHEAD = 10_000;
70
+ const ALGEBRA_QUOTE_GASLIMIT = 2_000_000;
71
+
72
+ const MAX_STALE_STATE_BLOCK_AGE = {
73
+ [Network.ZKEVM]: 150, // approximately 3min
74
+ };
75
+
76
+ type IAlgebraEventPool =
77
+ | AlgebraEventPoolV1_1
78
+ | AlgebraEventPoolV1_9
79
+ | AlgebraEventPoolV1_9_bidirectional_fee;
80
+
81
+ export class Algebra extends SimpleExchange implements IDex<AlgebraData> {
82
+ private readonly factory: AlgebraFactory;
83
+ readonly isFeeOnTransferSupported: boolean = true;
84
+ protected eventPools: Record<string, IAlgebraEventPool | null> = {};
85
+
86
+ private newlyCreatedPoolKeys: Set<string> = new Set();
87
+
88
+ readonly hasConstantPriceLargeAmounts = false;
89
+ readonly needWrapNative = true;
90
+
91
+ intervalTask?: NodeJS.Timeout;
92
+
93
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
94
+ getDexKeysWithNetwork(AlgebraConfig);
95
+
96
+ logger: Logger;
97
+
98
+ private uniswapMulti: Contract;
99
+ private stateMultiContract: Contract;
100
+
101
+ private notExistingPoolSetKey: string;
102
+
103
+ private AlgebraPoolImplem:
104
+ | typeof AlgebraEventPoolV1_1
105
+ | typeof AlgebraEventPoolV1_9
106
+ | typeof AlgebraEventPoolV1_9_bidirectional_fee;
107
+
108
+ readonly SRC_TOKEN_DEX_TRANSFERS = 1;
109
+ readonly DEST_TOKEN_DEX_TRANSFERS = 1;
110
+
111
+ constructor(
112
+ protected network: Network,
113
+ dexKey: string,
114
+ protected dexHelper: IDexHelper,
115
+ protected adapters = Adapters[network] || {},
116
+ readonly routerIface = new Interface(SwapRouter),
117
+ readonly quoterIface = new Interface(AlgebraQuoterABI),
118
+ readonly config = AlgebraConfig[dexKey][network],
119
+ ) {
120
+ super(dexHelper, dexKey);
121
+ this.logger = dexHelper.getLogger(dexKey + '-' + network);
122
+ this.uniswapMulti = new this.dexHelper.web3Provider.eth.Contract(
123
+ UniswapV3MultiABI as AbiItem[],
124
+ this.config.uniswapMulticall,
125
+ );
126
+ this.stateMultiContract = new this.dexHelper.web3Provider.eth.Contract(
127
+ AlgebraStateMulticallABI as AbiItem[],
128
+ this.config.algebraStateMulticall,
129
+ );
130
+
131
+ this.dexHelper.web3Provider.eth.handleRevert = false;
132
+
133
+ this.config = this._toLowerForAllConfigAddresses();
134
+ // External configuration has priority over internal
135
+ this.config.forceRPC = dexHelper.config.data.forceRpcFallbackDexs.includes(
136
+ dexKey.toLowerCase(),
137
+ );
138
+
139
+ this.notExistingPoolSetKey =
140
+ `${CACHE_PREFIX}_${network}_${dexKey}_not_existings_pool_set`.toLowerCase();
141
+
142
+ this.AlgebraPoolImplem =
143
+ config.version === 'v1.1'
144
+ ? AlgebraEventPoolV1_1
145
+ : config.version === 'v1.9-bidirectional-fee'
146
+ ? AlgebraEventPoolV1_9_bidirectional_fee
147
+ : AlgebraEventPoolV1_9;
148
+
149
+ this.factory = new AlgebraFactory(
150
+ dexHelper,
151
+ dexKey,
152
+ this.config.factory,
153
+ this.logger,
154
+ this.onPoolCreatedDeleteFromNonExistingSet,
155
+ );
156
+ }
157
+
158
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
159
+ return this.adapters[side] ? this.adapters[side] : null;
160
+ }
161
+
162
+ getPoolIdentifier(srcAddress: Address, destAddress: Address) {
163
+ const tokenAddresses = this._sortTokens(srcAddress, destAddress).join('_');
164
+ return `${this.dexKey}_${tokenAddresses}`;
165
+ }
166
+
167
+ async initializePricing(blockNumber: number) {
168
+ // Init listening to new pools creation
169
+ await this.factory.initialize(blockNumber);
170
+
171
+ //// COMMENTING DEPRECATED LOGIC: as we now invalidate pools on creation this is not needed anymore
172
+ // if (!this.dexHelper.config.isSlave) {
173
+ // const cleanExpiredNotExistingPoolsKeys = async () => {
174
+ // const maxTimestamp =
175
+ // Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS;
176
+ // await this.dexHelper.cache.zremrangebyscore(
177
+ // this.notExistingPoolSetKey,
178
+ // 0,
179
+ // maxTimestamp,
180
+ // );
181
+ // };
182
+
183
+ // this.intervalTask = setInterval(
184
+ // cleanExpiredNotExistingPoolsKeys.bind(this),
185
+ // ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS,
186
+ // );
187
+ // }
188
+ }
189
+
190
+ /*
191
+ * When a non existing pool is queried, it's blacklisted for an arbitrary long period in order to prevent issuing too many rpc calls
192
+ * Once the pool is created, it gets immediately flagged
193
+ */
194
+ onPoolCreatedDeleteFromNonExistingSet: OnPoolCreatedCallback = async ({
195
+ token0,
196
+ token1,
197
+ }) => {
198
+ const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]';
199
+ const [_token0, _token1] = this._sortTokens(token0, token1);
200
+ const poolKey = `${_token0}_${_token1}`.toLowerCase();
201
+
202
+ this.newlyCreatedPoolKeys.add(poolKey);
203
+
204
+ // consider doing it only from master pool for less calls to distant cache
205
+
206
+ // delete entry locally to let local instance discover the pool
207
+ delete this.eventPools[this.getPoolIdentifier(_token0, _token1)];
208
+
209
+ try {
210
+ this.logger.info(
211
+ `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}`,
212
+ );
213
+ // delete pool record from set
214
+ const result = await this.dexHelper.cache.zrem(
215
+ this.notExistingPoolSetKey,
216
+ [poolKey],
217
+ );
218
+ this.logger.info(
219
+ `${logPrefix} delete pool from not existing set=${this.notExistingPoolSetKey}; key=${poolKey}; result: ${result}`,
220
+ );
221
+ } catch (error) {
222
+ this.logger.error(
223
+ `${logPrefix} ERROR: failed to delete pool from set: set=${this.notExistingPoolSetKey}; key=${poolKey}`,
224
+ error,
225
+ );
226
+ }
227
+ };
228
+
229
+ async getPool(
230
+ srcAddress: Address,
231
+ destAddress: Address,
232
+ blockNumber: number,
233
+ ): Promise<IAlgebraEventPool | null> {
234
+ let pool = this.eventPools[
235
+ this.getPoolIdentifier(srcAddress, destAddress)
236
+ ] as IAlgebraEventPool | null | undefined;
237
+
238
+ if (pool === null) return null;
239
+
240
+ if (pool) {
241
+ if (!pool.initFailed) {
242
+ if (this.network !== Network.ZKEVM) return pool;
243
+
244
+ if (
245
+ pool.getStaleState() === null ||
246
+ (pool.getState(blockNumber) === null &&
247
+ blockNumber - pool.getStateBlockNumber() >
248
+ MAX_STALE_STATE_BLOCK_AGE[this.network])
249
+ ) {
250
+ /* reload state, on zkEVM this would most likely timeout during request life
251
+ * but would allow to rely on staleState for couple of min for next requests
252
+ */
253
+ await pool.initialize(blockNumber, { forceRegenerate: true });
254
+ }
255
+
256
+ return pool;
257
+ } else {
258
+ // if init failed then prefer to early return pool with empty state to fallback to rpc call
259
+ if (
260
+ ++pool.initRetryAttemptCount % this.config.initRetryFrequency !==
261
+ 0
262
+ ) {
263
+ return pool;
264
+ }
265
+ // else pursue with re-try initialization
266
+ }
267
+ }
268
+
269
+ const [token0, token1] = this._sortTokens(srcAddress, destAddress);
270
+
271
+ const key = `${token0}_${token1}`.toLowerCase();
272
+
273
+ // no need to run this logic on retry initialisation scenario
274
+ if (!pool) {
275
+ const notExistingPoolScore = await this.dexHelper.cache.zscore(
276
+ this.notExistingPoolSetKey,
277
+ key,
278
+ );
279
+
280
+ const poolDoesNotExist = notExistingPoolScore !== null;
281
+
282
+ if (poolDoesNotExist) {
283
+ this.eventPools[this.getPoolIdentifier(srcAddress, destAddress)] = null;
284
+ return null;
285
+ }
286
+ }
287
+
288
+ this.logger.trace(`starting to listen to new pool: ${key}`);
289
+ pool =
290
+ pool ||
291
+ new this.AlgebraPoolImplem(
292
+ this.dexHelper,
293
+ this.dexKey,
294
+ this.stateMultiContract,
295
+ this.erc20Interface,
296
+ this.config.factory,
297
+ token0,
298
+ token1,
299
+ this.logger,
300
+ this.cacheStateKey,
301
+ this.config.initHash,
302
+ this.config.deployer,
303
+ this.config.forceManualStateGenerate,
304
+ );
305
+
306
+ try {
307
+ await pool.initialize(blockNumber, {
308
+ initCallback: state => {
309
+ //really hacky, we need to push poolAddress so that we subscribeToLogs in StatefulEventSubscriber
310
+ pool!.addressesSubscribed[0] = state.pool;
311
+ pool!.poolAddress = state.pool;
312
+ pool!.initFailed = false;
313
+ pool!.initRetryAttemptCount = 0;
314
+ },
315
+ });
316
+
317
+ if (this.newlyCreatedPoolKeys.has(key)) {
318
+ this.newlyCreatedPoolKeys.delete(key);
319
+ }
320
+ } catch (e) {
321
+ if (e instanceof Error && e.message.endsWith('Pool does not exist')) {
322
+ /*
323
+ protection against 2 race conditions
324
+ 1/ if pool.initialize() promise rejects after the Pool creation event got treated
325
+ 2/ if the rpc node we hit on the http request is lagging behind the one we got event from (websocket)
326
+ */
327
+ if (this.newlyCreatedPoolKeys.has(key)) {
328
+ this.logger.warn(
329
+ `[block=${blockNumber}][Pool=${key}] newly created pool failed to initialise, trying on next request`,
330
+ );
331
+ } else {
332
+ this.logger.info(
333
+ `[block=${blockNumber}][Pool=${key}] pool failed to initialize so it's marked as non existing`,
334
+ e,
335
+ );
336
+
337
+ // no need to await we want the set to have the pool key but it's not blocking
338
+ this.dexHelper.cache.zadd(
339
+ this.notExistingPoolSetKey,
340
+ [Date.now(), key],
341
+ 'NX',
342
+ );
343
+
344
+ // Pool does not exist for this pair, so we can set it to null
345
+ // to prevent more requests for this pool
346
+ pool = null;
347
+ }
348
+ } else {
349
+ // on unknown error mark as failed and increase retryCount for retry init strategy
350
+ // note: state would be null by default which allows to fallback
351
+ this.logger.warn(
352
+ `[block=${blockNumber}][Pool=${key}] Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`,
353
+ e,
354
+ );
355
+ pool.initFailed = true;
356
+ }
357
+ }
358
+
359
+ if (pool !== null) {
360
+ const allEventPools = Object.values(this.eventPools);
361
+ // if pool was created, delete pool record from non existing set
362
+ this.dexHelper.cache
363
+ .zrem(this.notExistingPoolSetKey, [key])
364
+ .catch(() => {});
365
+ this.logger.info(
366
+ `starting to listen to new non-null pool: ${key}. Already following ${allEventPools
367
+ // Not that I like this reduce, but since it is done only on initialization, expect this to be ok
368
+ .reduce(
369
+ (acc, curr) => (curr !== null ? ++acc : acc),
370
+ 0,
371
+ )} non-null pools or ${allEventPools.length} total pools`,
372
+ );
373
+ }
374
+
375
+ this.eventPools[this.getPoolIdentifier(srcAddress, destAddress)] = pool;
376
+ return pool;
377
+ }
378
+
379
+ async addMasterPool(poolKey: string, blockNumber: number): Promise<boolean> {
380
+ const _pairs = await this.dexHelper.cache.hget(
381
+ PoolsRegistryHashKey,
382
+ `${this.cacheStateKey}_${poolKey}`,
383
+ );
384
+ if (!_pairs) {
385
+ this.logger.warn(
386
+ `did not find poolConfig in for key ${PoolsRegistryHashKey} ${this.cacheStateKey}_${poolKey}`,
387
+ );
388
+ return false;
389
+ }
390
+
391
+ const poolInfo: PoolPairsInfo = JSON.parse(_pairs);
392
+
393
+ const pool = await this.getPool(
394
+ poolInfo.token0,
395
+ poolInfo.token1,
396
+ blockNumber,
397
+ );
398
+
399
+ if (!pool) {
400
+ return false;
401
+ }
402
+
403
+ return true;
404
+ }
405
+
406
+ async getPoolIdentifiers(
407
+ srcToken: Token,
408
+ destToken: Token,
409
+ side: SwapSide,
410
+ blockNumber: number,
411
+ ): Promise<string[]> {
412
+ const _srcToken = this.dexHelper.config.wrapETH(srcToken);
413
+ const _destToken = this.dexHelper.config.wrapETH(destToken);
414
+
415
+ const [_srcAddress, _destAddress] = this._getLoweredAddresses(
416
+ _srcToken,
417
+ _destToken,
418
+ );
419
+
420
+ if (_srcAddress === _destAddress) return [];
421
+
422
+ const pool = await this.getPool(_srcAddress, _destAddress, blockNumber);
423
+ if (!pool) return [];
424
+ return [this.getPoolIdentifier(_srcAddress, _destAddress)];
425
+ }
426
+
427
+ async getPricingFromRpc(
428
+ from: Token,
429
+ to: Token,
430
+ amounts: bigint[],
431
+ side: SwapSide,
432
+ pool: IAlgebraEventPool,
433
+ transferFees: TransferFeeParams = {
434
+ srcFee: 0,
435
+ destFee: 0,
436
+ srcDexFee: 0,
437
+ destDexFee: 0,
438
+ },
439
+ ): Promise<ExchangePrices<AlgebraData> | null> {
440
+ if (!this.config.forceRPC) {
441
+ this.logger.warn(
442
+ `fallback to rpc for ${from.address}_${to.address}_${pool.name}_${pool.poolAddress} pool(s)`,
443
+ );
444
+ }
445
+
446
+ const _isSrcTokenTransferFeeToBeExchanged =
447
+ isSrcTokenTransferFeeToBeExchanged(transferFees);
448
+ const _isDestTokenTransferFeeToBeExchanged =
449
+ isDestTokenTransferFeeToBeExchanged(transferFees);
450
+
451
+ const unitVolume = getBigIntPow(
452
+ (side === SwapSide.SELL ? from : to).decimals,
453
+ );
454
+
455
+ const chunks = amounts.length - 1;
456
+
457
+ const _width = Math.floor(chunks / this.config.chunksCount);
458
+
459
+ const _amounts = [unitVolume].concat(
460
+ Array.from(Array(this.config.chunksCount).keys()).map(
461
+ i => amounts[(i + 1) * _width],
462
+ ),
463
+ );
464
+
465
+ const _amountsWithFee = _isSrcTokenTransferFeeToBeExchanged
466
+ ? applyTransferFee(
467
+ _amounts,
468
+ side,
469
+ transferFees.srcDexFee,
470
+ this.SRC_TOKEN_DEX_TRANSFERS,
471
+ )
472
+ : _amounts;
473
+
474
+ const calldata = _amountsWithFee.map(_amount => ({
475
+ target: this.config.quoter,
476
+ gasLimit: ALGEBRA_QUOTE_GASLIMIT,
477
+ callData:
478
+ side === SwapSide.SELL
479
+ ? this.quoterIface.encodeFunctionData('quoteExactInputSingle', [
480
+ from.address,
481
+ to.address,
482
+ _amount.toString(),
483
+ 0, //sqrtPriceLimitX96
484
+ ])
485
+ : this.quoterIface.encodeFunctionData('quoteExactOutputSingle', [
486
+ from.address,
487
+ to.address,
488
+ _amount.toString(),
489
+ 0, //sqrtPriceLimitX96
490
+ ]),
491
+ }));
492
+
493
+ const data = await this.uniswapMulti.methods.multicall(calldata).call();
494
+
495
+ let totalGasCost = 0;
496
+ let totalSuccessFullSwaps = 0;
497
+ const decode = (j: number): bigint => {
498
+ const { success, gasUsed, returnData } = data.returnData[j];
499
+
500
+ if (!success) {
501
+ return 0n;
502
+ }
503
+ const decoded = defaultAbiCoder.decode(['uint256'], returnData);
504
+ totalGasCost += +gasUsed;
505
+ totalSuccessFullSwaps++;
506
+
507
+ return BigInt(decoded[0].toString());
508
+ };
509
+
510
+ const averageGasCost = !totalSuccessFullSwaps
511
+ ? ALGEBRA_QUOTE_GASLIMIT
512
+ : Math.round(totalGasCost / totalSuccessFullSwaps);
513
+
514
+ let i = 0;
515
+ const _rates = _amountsWithFee.map(() => decode(i++));
516
+
517
+ const _ratesWithFee = _isDestTokenTransferFeeToBeExchanged
518
+ ? applyTransferFee(
519
+ _rates,
520
+ side,
521
+ transferFees.destDexFee,
522
+ this.DEST_TOKEN_DEX_TRANSFERS,
523
+ )
524
+ : _rates;
525
+
526
+ const unit: bigint = _ratesWithFee[0];
527
+
528
+ const prices = interpolate(
529
+ _amountsWithFee.slice(1),
530
+ _ratesWithFee.slice(1),
531
+ amounts,
532
+ side,
533
+ );
534
+
535
+ return [
536
+ {
537
+ prices,
538
+ unit,
539
+ data: {
540
+ feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged,
541
+ path: [
542
+ {
543
+ tokenIn: from.address,
544
+ tokenOut: to.address,
545
+ },
546
+ ],
547
+ },
548
+ poolIdentifier: this.getPoolIdentifier(pool.token0, pool.token1),
549
+ exchange: this.dexKey,
550
+ gasCost: prices.map(p => (p === 0n ? 0 : averageGasCost)),
551
+ poolAddresses: [pool.poolAddress],
552
+ },
553
+ ];
554
+ }
555
+
556
+ async getPricesVolume(
557
+ srcToken: Token,
558
+ destToken: Token,
559
+ amounts: bigint[],
560
+ side: SwapSide,
561
+ blockNumber: number,
562
+ limitPools?: string[],
563
+ transferFees: TransferFeeParams = {
564
+ srcFee: 0,
565
+ destFee: 0,
566
+ srcDexFee: 0,
567
+ destDexFee: 0,
568
+ },
569
+ ): Promise<null | ExchangePrices<AlgebraData>> {
570
+ try {
571
+ const _srcToken = this.dexHelper.config.wrapETH(srcToken);
572
+ const _destToken = this.dexHelper.config.wrapETH(destToken);
573
+
574
+ const [_srcAddress, _destAddress] = this._getLoweredAddresses(
575
+ _srcToken,
576
+ _destToken,
577
+ );
578
+
579
+ const _isSrcTokenTransferFeeToBeExchanged =
580
+ isSrcTokenTransferFeeToBeExchanged(transferFees);
581
+ const _isDestTokenTransferFeeToBeExchanged =
582
+ isDestTokenTransferFeeToBeExchanged(transferFees);
583
+
584
+ if (_srcAddress === _destAddress) return null;
585
+
586
+ if (
587
+ !limitPools?.includes(this.getPoolIdentifier(_srcAddress, _destAddress))
588
+ )
589
+ return null;
590
+
591
+ const pool = await this.getPool(_srcAddress, _destAddress, blockNumber);
592
+ if (!pool) return null;
593
+
594
+ if (_isSrcTokenTransferFeeToBeExchanged && side == SwapSide.BUY) {
595
+ this.logger.error(
596
+ `pool: ${pool.poolAddress} doesn't support buy for tax srcToken ${srcToken.address}`,
597
+ );
598
+ return null;
599
+ }
600
+
601
+ if (this.config.forceRPC) {
602
+ const rpcPrice = await this.getPricingFromRpc(
603
+ _srcToken,
604
+ _destToken,
605
+ amounts,
606
+ side,
607
+ pool,
608
+ transferFees,
609
+ );
610
+
611
+ return rpcPrice;
612
+ }
613
+
614
+ let state = pool.getState(blockNumber);
615
+
616
+ if (state === null) {
617
+ if (this.network === Network.ZKEVM) {
618
+ if (pool.initFailed) return null;
619
+
620
+ if (
621
+ blockNumber - pool.getStateBlockNumber() <=
622
+ MAX_STALE_STATE_BLOCK_AGE[this.network]
623
+ ) {
624
+ this.logger.warn(
625
+ `${_srcAddress}_${_destAddress}_${pool.name}_${
626
+ pool.poolAddress
627
+ } state fallback to latest early enough state. Current blockNumber=${blockNumber}, stateBlockNumber=${pool.getStateBlockNumber()}`,
628
+ );
629
+ state = pool.getStaleState();
630
+ } else {
631
+ this.logger.warn(
632
+ `${_srcAddress}_${_destAddress}_${pool.name}_${pool.poolAddress} state is unhealthy, cannot compute price (no fallback on this chain)`,
633
+ );
634
+ return null; // never fallback as takes more time
635
+ }
636
+ } else {
637
+ const rpcPrice = await this.getPricingFromRpc(
638
+ _srcToken,
639
+ _destToken,
640
+ amounts,
641
+ side,
642
+ pool,
643
+ transferFees,
644
+ );
645
+
646
+ return rpcPrice;
647
+ }
648
+ }
649
+
650
+ if (!state) return null;
651
+
652
+ const unitAmount = getBigIntPow(
653
+ side == SwapSide.SELL ? _srcToken.decimals : _destToken.decimals,
654
+ );
655
+
656
+ const _amounts = [...amounts.slice(1)];
657
+
658
+ const [token0] = this._sortTokens(_srcAddress, _destAddress);
659
+
660
+ const zeroForOne = token0 === _srcAddress ? true : false;
661
+
662
+ if (state.liquidity <= 0n) {
663
+ if (state.liquidity < 0) {
664
+ this.logger.error(
665
+ `${this.dexKey}-${this.network}: ${pool.poolAddress} pool has negative liquidity: ${state.liquidity}. Find with key: ${pool.mapKey}`,
666
+ );
667
+ }
668
+ this.logger.trace(`pool have 0 liquidity`);
669
+ return null;
670
+ }
671
+
672
+ const balanceDestToken =
673
+ _destAddress === pool.token0 ? state.balance0 : state.balance1;
674
+
675
+ const [unitAmountWithFee, ...amountsWithFee] =
676
+ _isSrcTokenTransferFeeToBeExchanged
677
+ ? applyTransferFee(
678
+ [unitAmount, ..._amounts],
679
+ side,
680
+ transferFees.srcDexFee,
681
+ this.SRC_TOKEN_DEX_TRANSFERS,
682
+ )
683
+ : [unitAmount, ..._amounts];
684
+
685
+ const unitResult = this._getOutputs(
686
+ state,
687
+ [unitAmountWithFee],
688
+ zeroForOne,
689
+ side,
690
+ balanceDestToken,
691
+ );
692
+ const pricesResult = this._getOutputs(
693
+ state,
694
+ amountsWithFee,
695
+ zeroForOne,
696
+ side,
697
+ balanceDestToken,
698
+ );
699
+
700
+ if (!unitResult || !pricesResult) {
701
+ this.logger.debug('Prices or unit is not calculated');
702
+ return null;
703
+ }
704
+
705
+ const [unitResultWithFee, ...pricesResultWithFee] =
706
+ _isDestTokenTransferFeeToBeExchanged
707
+ ? applyTransferFee(
708
+ [unitResult.outputs[0], ...pricesResult.outputs],
709
+ side,
710
+ transferFees.destDexFee,
711
+ this.DEST_TOKEN_DEX_TRANSFERS,
712
+ )
713
+ : [unitResult.outputs[0], ...pricesResult.outputs];
714
+
715
+ const prices = [0n, ...pricesResultWithFee];
716
+ const gasCost = [
717
+ 0,
718
+ ...pricesResultWithFee.map((p, index) => {
719
+ if (p == 0n) {
720
+ return 0;
721
+ } else {
722
+ return (
723
+ ALGEBRA_POOL_SEARCH_OVERHEAD +
724
+ ALGEBRA_TICK_BASE_OVERHEAD +
725
+ pricesResult.tickCounts[index] * ALGEBRA_TICK_GAS_COST
726
+ );
727
+ }
728
+ }),
729
+ ];
730
+
731
+ return [
732
+ {
733
+ unit: unitResultWithFee,
734
+ prices,
735
+ data: {
736
+ feeOnTransfer: _isSrcTokenTransferFeeToBeExchanged,
737
+ path: [
738
+ {
739
+ tokenIn: _srcAddress,
740
+ tokenOut: _destAddress,
741
+ },
742
+ ],
743
+ },
744
+ poolIdentifier: this.getPoolIdentifier(pool.token0, pool.token1),
745
+ exchange: this.dexKey,
746
+ gasCost: gasCost,
747
+ poolAddresses: [pool.poolAddress],
748
+ },
749
+ ];
750
+ } catch (e) {
751
+ this.logger.error(
752
+ `Error_getPricesVolume ${srcToken.symbol || srcToken.address}, ${
753
+ destToken.symbol || destToken.address
754
+ }, ${side}:`,
755
+ e,
756
+ );
757
+ return null;
758
+ }
759
+ }
760
+
761
+ getAdapterParam(
762
+ srcToken: string,
763
+ destToken: string,
764
+ srcAmount: string,
765
+ destAmount: string,
766
+ data: AlgebraData,
767
+ side: SwapSide,
768
+ ): AdapterExchangeParam {
769
+ let path = this._encodePath(data.path, side);
770
+
771
+ const payload = this.abiCoder.encodeParameter(
772
+ {
773
+ ParentStruct: {
774
+ path: 'bytes',
775
+ deadline: 'uint256',
776
+ feeOnTransfer: 'bool',
777
+ },
778
+ },
779
+ {
780
+ path,
781
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(), // FIXME: more gas efficient to pass block.timestamp in adapter
782
+ feeOnTransfer: data.feeOnTransfer,
783
+ },
784
+ );
785
+
786
+ return {
787
+ targetExchange: this.config.router,
788
+ payload,
789
+ networkFee: '0',
790
+ };
791
+ }
792
+
793
+ getCalldataGasCost(poolPrices: PoolPrices<AlgebraData>): number | number[] {
794
+ const gasCost =
795
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
796
+ CALLDATA_GAS_COST.LENGTH_SMALL +
797
+ // ParentStruct header
798
+ CALLDATA_GAS_COST.OFFSET_SMALL +
799
+ // ParentStruct -> path header
800
+ CALLDATA_GAS_COST.OFFSET_SMALL +
801
+ // ParentStruct -> deadline
802
+ CALLDATA_GAS_COST.TIMESTAMP +
803
+ // ParentStruct -> path (20+3+20 = 43 = 32+11 bytes)
804
+ CALLDATA_GAS_COST.LENGTH_SMALL +
805
+ CALLDATA_GAS_COST.FULL_WORD +
806
+ CALLDATA_GAS_COST.wordNonZeroBytes(11);
807
+ const arr = new Array(poolPrices.prices.length);
808
+ poolPrices.prices.forEach((p, index) => {
809
+ if (p == 0n) {
810
+ arr[index] = 0;
811
+ } else {
812
+ arr[index] = gasCost;
813
+ }
814
+ });
815
+ return arr;
816
+ }
817
+
818
+ async getSimpleParam(
819
+ srcToken: string,
820
+ destToken: string,
821
+ srcAmount: string,
822
+ destAmount: string,
823
+ data: AlgebraData,
824
+ side: SwapSide,
825
+ ): Promise<SimpleExchangeParam> {
826
+ let swapFunction;
827
+ let swapParams;
828
+
829
+ if (data.feeOnTransfer) {
830
+ _require(
831
+ data.path.length === 1,
832
+ `LOGIC ERROR: multihop is not supported for feeOnTransfer token, passed: ${data.path
833
+ .map(p => `${p?.tokenIn}->${p?.tokenOut}`)
834
+ .join(' ')}`,
835
+ );
836
+ swapFunction = AlgebraFunctions.exactInputWithFeeToken;
837
+ swapParams = {
838
+ limitSqrtPrice: '0',
839
+ recipient: this.augustusAddress,
840
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
841
+ amountIn: srcAmount,
842
+ amountOutMinimum: destAmount,
843
+ tokenIn: data.path[0].tokenIn,
844
+ tokenOut: data.path[0].tokenOut,
845
+ };
846
+ } else {
847
+ swapFunction =
848
+ side === SwapSide.SELL
849
+ ? AlgebraFunctions.exactInput
850
+ : AlgebraFunctions.exactOutput;
851
+ const path = this._encodePath(data.path, side);
852
+ swapParams =
853
+ side === SwapSide.SELL
854
+ ? {
855
+ recipient: this.augustusAddress,
856
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
857
+ amountIn: srcAmount,
858
+ amountOutMinimum: destAmount,
859
+ path,
860
+ }
861
+ : {
862
+ recipient: this.augustusAddress,
863
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
864
+ amountOut: destAmount,
865
+ amountInMaximum: srcAmount,
866
+ path,
867
+ };
868
+ }
869
+
870
+ const swapData = this.routerIface.encodeFunctionData(swapFunction, [
871
+ swapParams,
872
+ ]);
873
+
874
+ return this.buildSimpleParamWithoutWETHConversion(
875
+ srcToken,
876
+ srcAmount,
877
+ destToken,
878
+ destAmount,
879
+ swapData,
880
+ this.config.router,
881
+ );
882
+ }
883
+
884
+ getDexParam(
885
+ srcToken: Address,
886
+ destToken: Address,
887
+ srcAmount: NumberAsString,
888
+ destAmount: NumberAsString,
889
+ recipient: Address,
890
+ data: AlgebraData,
891
+ side: SwapSide,
892
+ ): DexExchangeParam {
893
+ let swapFunction;
894
+ let swapFunctionParams;
895
+
896
+ if (data.feeOnTransfer) {
897
+ _require(
898
+ data.path.length === 1,
899
+ `LOGIC ERROR: multihop is not supported for feeOnTransfer token, passed: ${data.path
900
+ .map(p => `${p?.tokenIn}->${p?.tokenOut}`)
901
+ .join(' ')}`,
902
+ );
903
+ swapFunction = AlgebraFunctions.exactInputWithFeeToken;
904
+ swapFunctionParams = {
905
+ limitSqrtPrice: '0',
906
+ recipient: recipient,
907
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
908
+ amountIn: srcAmount,
909
+ amountOutMinimum: destAmount,
910
+ tokenIn: data.path[0].tokenIn,
911
+ tokenOut: data.path[0].tokenOut,
912
+ };
913
+ } else {
914
+ swapFunction =
915
+ side === SwapSide.SELL
916
+ ? AlgebraFunctions.exactInput
917
+ : AlgebraFunctions.exactOutput;
918
+ const path = this._encodePath(data.path, side);
919
+ swapFunctionParams =
920
+ side === SwapSide.SELL
921
+ ? {
922
+ recipient: recipient,
923
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
924
+ amountIn: srcAmount,
925
+ amountOutMinimum: destAmount,
926
+ path,
927
+ }
928
+ : {
929
+ recipient: recipient,
930
+ deadline: getLocalDeadlineAsFriendlyPlaceholder(),
931
+ amountOut: destAmount,
932
+ amountInMaximum: srcAmount,
933
+ path,
934
+ };
935
+ }
936
+
937
+ const exchangeData = this.routerIface.encodeFunctionData(swapFunction, [
938
+ swapFunctionParams,
939
+ ]);
940
+
941
+ return {
942
+ needWrapNative: this.needWrapNative,
943
+ dexFuncHasRecipient: true,
944
+ exchangeData,
945
+ targetExchange: this.config.router,
946
+ returnAmountPos:
947
+ side === SwapSide.SELL
948
+ ? extractReturnAmountPosition(
949
+ this.routerIface,
950
+ swapFunction,
951
+ 'amountOut',
952
+ )
953
+ : undefined,
954
+ };
955
+ }
956
+
957
+ async getTopPoolsForToken(
958
+ tokenAddress: Address,
959
+ limit: number,
960
+ ): Promise<PoolLiquidity[]> {
961
+ const _tokenAddress = tokenAddress.toLowerCase();
962
+
963
+ const res = await this._querySubgraph(
964
+ `query ($token: Bytes!, $count: Int) {
965
+ pools0: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token0: $token}) {
966
+ id
967
+ token0 {
968
+ id
969
+ decimals
970
+ }
971
+ token1 {
972
+ id
973
+ decimals
974
+ }
975
+ totalValueLockedUSD
976
+ }
977
+ pools1: pools(first: $count, orderBy: totalValueLockedUSD, orderDirection: desc, where: {token1: $token}) {
978
+ id
979
+ token0 {
980
+ id
981
+ decimals
982
+ }
983
+ token1 {
984
+ id
985
+ decimals
986
+ }
987
+ totalValueLockedUSD
988
+ }
989
+ }`,
990
+ {
991
+ token: _tokenAddress,
992
+ count: limit,
993
+ },
994
+ );
995
+
996
+ if (!(res && res.pools0 && res.pools1)) {
997
+ this.logger.error(
998
+ `Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`,
999
+ );
1000
+ return [];
1001
+ }
1002
+
1003
+ const pools0 = _.map(res.pools0, pool => ({
1004
+ exchange: this.dexKey,
1005
+ address: pool.id.toLowerCase(),
1006
+ connectorTokens: [
1007
+ {
1008
+ address: pool.token1.id.toLowerCase(),
1009
+ decimals: parseInt(pool.token1.decimals),
1010
+ },
1011
+ ],
1012
+ liquidityUSD:
1013
+ parseFloat(pool.totalValueLockedUSD) * ALGEBRA_EFFICIENCY_FACTOR,
1014
+ }));
1015
+
1016
+ const pools1 = _.map(res.pools1, pool => ({
1017
+ exchange: this.dexKey,
1018
+ address: pool.id.toLowerCase(),
1019
+ connectorTokens: [
1020
+ {
1021
+ address: pool.token0.id.toLowerCase(),
1022
+ decimals: parseInt(pool.token0.decimals),
1023
+ },
1024
+ ],
1025
+ liquidityUSD:
1026
+ parseFloat(pool.totalValueLockedUSD) * ALGEBRA_EFFICIENCY_FACTOR,
1027
+ }));
1028
+
1029
+ const pools = _.slice(
1030
+ _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]),
1031
+ 0,
1032
+ limit,
1033
+ );
1034
+ return pools;
1035
+ }
1036
+
1037
+ private _getLoweredAddresses(srcToken: Token, destToken: Token) {
1038
+ return [srcToken.address.toLowerCase(), destToken.address.toLowerCase()];
1039
+ }
1040
+
1041
+ private _sortTokens(srcAddress: Address, destAddress: Address) {
1042
+ return [srcAddress, destAddress].sort((a, b) => (a < b ? -1 : 1));
1043
+ }
1044
+
1045
+ private _toLowerForAllConfigAddresses() {
1046
+ // If new config property will be added, the TS will throw compile error
1047
+ const newConfig: DexParams = {
1048
+ router: this.config.router.toLowerCase(),
1049
+ quoter: this.config.quoter.toLowerCase(),
1050
+ factory: this.config.factory.toLowerCase(),
1051
+ algebraStateMulticall: this.config.algebraStateMulticall.toLowerCase(),
1052
+ chunksCount: this.config.chunksCount,
1053
+ initRetryFrequency: this.config.initRetryFrequency,
1054
+ uniswapMulticall: this.config.uniswapMulticall,
1055
+ deployer: this.config.deployer?.toLowerCase(),
1056
+ initHash: this.config.initHash,
1057
+ subgraphURL: this.config.subgraphURL,
1058
+ version: this.config.version,
1059
+ forceRPC: this.config.forceRPC,
1060
+ forceManualStateGenerate: this.config.forceManualStateGenerate,
1061
+ };
1062
+ return newConfig;
1063
+ }
1064
+
1065
+ private _getOutputs(
1066
+ state: DeepReadonly<IAlgebraPoolState>,
1067
+ amounts: bigint[],
1068
+ zeroForOne: boolean,
1069
+ side: SwapSide,
1070
+ destTokenBalance: bigint,
1071
+ ): OutputResult | null {
1072
+ try {
1073
+ const outputsResult = AlgebraMath.queryOutputs(
1074
+ this.network,
1075
+ state,
1076
+ amounts,
1077
+ zeroForOne,
1078
+ side,
1079
+ );
1080
+
1081
+ if (side === SwapSide.SELL) {
1082
+ if (outputsResult.outputs[0] > destTokenBalance) {
1083
+ return null;
1084
+ }
1085
+
1086
+ for (let i = 0; i < outputsResult.outputs.length; i++) {
1087
+ if (outputsResult.outputs[i] > destTokenBalance) {
1088
+ outputsResult.outputs[i] = 0n;
1089
+ outputsResult.tickCounts[i] = 0;
1090
+ }
1091
+ }
1092
+ } else {
1093
+ if (amounts[0] > destTokenBalance) {
1094
+ return null;
1095
+ }
1096
+
1097
+ // This may be improved by first checking outputs and requesting outputs
1098
+ // only for amounts that makes more sense, but I don't think this is really
1099
+ // important now
1100
+ for (let i = 0; i < amounts.length; i++) {
1101
+ if (amounts[i] > destTokenBalance) {
1102
+ outputsResult.outputs[i] = 0n;
1103
+ outputsResult.tickCounts[i] = 0;
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ return outputsResult;
1109
+ } catch (e) {
1110
+ this.logger.debug(
1111
+ `${this.dexKey}: received error in _getOutputs while calculating outputs`,
1112
+ e,
1113
+ );
1114
+ return null;
1115
+ }
1116
+ }
1117
+
1118
+ private async _querySubgraph(
1119
+ query: string,
1120
+ variables: Object,
1121
+ timeout = 30000,
1122
+ ) {
1123
+ try {
1124
+ const res = await this.dexHelper.httpRequest.post(
1125
+ this.config.subgraphURL,
1126
+ { query, variables },
1127
+ undefined,
1128
+ { timeout: timeout },
1129
+ );
1130
+ return res.data;
1131
+ } catch (e) {
1132
+ this.logger.error(`${this.dexKey}: can not query subgraph: `, e);
1133
+ return {};
1134
+ }
1135
+ }
1136
+
1137
+ private _encodePath(
1138
+ path: {
1139
+ tokenIn: Address;
1140
+ tokenOut: Address;
1141
+ }[],
1142
+ side: SwapSide,
1143
+ ): string {
1144
+ if (path.length === 0) {
1145
+ return '0x';
1146
+ }
1147
+
1148
+ const { _path, types } = path.reduce(
1149
+ (
1150
+ { _path, types }: { _path: string[]; types: string[] },
1151
+ curr,
1152
+ index,
1153
+ ): { _path: string[]; types: string[] } => {
1154
+ if (index === 0) {
1155
+ return {
1156
+ types: ['address', 'address'],
1157
+ _path: [curr.tokenIn, curr.tokenOut],
1158
+ };
1159
+ } else {
1160
+ return {
1161
+ types: [...types, 'address'],
1162
+ _path: [..._path, curr.tokenOut],
1163
+ };
1164
+ }
1165
+ },
1166
+ { _path: [], types: [] },
1167
+ );
1168
+
1169
+ return side === SwapSide.BUY
1170
+ ? pack(types.reverse(), _path.reverse())
1171
+ : pack(types, _path);
1172
+ }
1173
+
1174
+ releaseResources() {
1175
+ if (this.intervalTask !== undefined) {
1176
+ clearInterval(this.intervalTask);
1177
+ this.intervalTask = undefined;
1178
+ }
1179
+ }
1180
+ }