@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.
- package/README.md +377 -377
- package/build/config.js +9 -5
- package/build/config.js.map +1 -1
- package/build/dex/algebra/algebra.js +25 -25
- package/build/dex/balancer-v1/balancer-v1.js +34 -34
- package/build/dex/balancer-v2/balancer-v2.d.ts +1 -1
- package/build/dex/balancer-v2/balancer-v2.js +65 -78
- package/build/dex/balancer-v2/balancer-v2.js.map +1 -1
- package/build/dex/camelot/camelot.js +27 -27
- package/build/dex/kyberdmm/kyberdmm.js +27 -27
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js +25 -25
- package/build/dex/solidly/solidly.js +27 -27
- package/build/dex/solidly-v3/solidly-v3.js +25 -25
- package/build/dex/swaap-v2/config.js +15 -0
- package/build/dex/swaap-v2/config.js.map +1 -1
- package/build/dex/uniswap-v2/uniswap-v2.js +26 -26
- package/build/dex/uniswap-v3/uniswap-v3.js +25 -25
- package/funding.json +5 -5
- package/package.json +1 -1
- package/src/config.ts +500 -496
- package/src/constants.ts +67 -67
- package/src/dex/aave-v2/aave-v2.ts +409 -409
- package/src/dex/aave-v3/aave-v3.ts +320 -320
- package/src/dex/algebra/algebra.ts +1180 -1180
- package/src/dex/angle-staked-stable/angle-staked-stable.ts +347 -347
- package/src/dex/augustus-rfq.ts +180 -180
- package/src/dex/bProtocol/bProtocol.ts +108 -108
- package/src/dex/balancer-v1/balancer-v1.ts +572 -572
- package/src/dex/balancer-v2/balancer-v2-e2e.test.ts +1699 -1699
- package/src/dex/balancer-v2/balancer-v2.ts +1669 -1701
- package/src/dex/bancor/bancor.ts +128 -128
- package/src/dex/camelot/camelot.ts +873 -873
- package/src/dex/compound/compound.ts +107 -107
- package/src/dex/curve-v1/config.ts +1179 -1179
- package/src/dex/curve-v1/curve-v1-e2e.test.ts +507 -507
- package/src/dex/curve-v1/curve-v1.ts +1315 -1315
- package/src/dex/curve-v1-factory/config.ts +1018 -1018
- package/src/dex/curve-v1-factory/curve-v1-factory.ts +1288 -1288
- package/src/dex/curve-v2/curve-v2.ts +440 -440
- package/src/dex/dexalot/dexalot.ts +1100 -1100
- package/src/dex/dodo-v1/dodo-v1.ts +132 -132
- package/src/dex/dodo-v2.ts +266 -266
- package/src/dex/etherfi.ts +239 -239
- package/src/dex/generic-rfq/generic-rfq.ts +431 -431
- package/src/dex/gmx/gmx.ts +321 -321
- package/src/dex/hashflow/hashflow.ts +1135 -1135
- package/src/dex/idex.ts +254 -254
- package/src/dex/index.ts +357 -357
- package/src/dex/jarvis-v6/jarvis-v6.ts +509 -509
- package/src/dex/jarvis.ts +262 -262
- package/src/dex/kyberdmm/kyberdmm.ts +649 -649
- package/src/dex/lido/lido.ts +110 -110
- package/src/dex/maker-psm/maker-psm.ts +592 -592
- package/src/dex/maverick-v1/maverick-v1.ts +537 -537
- package/src/dex/nerve/config.ts +635 -635
- package/src/dex/nerve/nerve-e2e.test.ts +731 -731
- package/src/dex/nerve/nerve-events.test.ts +911 -911
- package/src/dex/nerve/nerve.ts +467 -467
- package/src/dex/onebit/onebit.ts +113 -113
- package/src/dex/pancakeswap-v3/pancakeswap-v3.ts +1150 -1150
- package/src/dex/paraswap-limit-orders/config.ts +60 -60
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-e2e.test.ts +137 -137
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders-integration.test.ts +328 -328
- package/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +631 -631
- package/src/dex/polygon-migrator/polygon-migrator.ts +194 -194
- package/src/dex/quick-perps/quick-perps.ts +337 -337
- package/src/dex/quickswap/config.ts +16 -16
- package/src/dex/quickswap/quickswap-e2e.test.ts +384 -384
- package/src/dex/smoothy/smoothy.ts +110 -110
- package/src/dex/solidly/config.ts +225 -225
- package/src/dex/solidly/solidly-e2e.test.ts +1421 -1421
- package/src/dex/solidly/solidly-integration.test.ts +1753 -1753
- package/src/dex/solidly/solidly.ts +687 -687
- package/src/dex/solidly-v3/solidly-v3.ts +968 -968
- package/src/dex/spark/spark.ts +284 -284
- package/src/dex/stable-pool/stable-pool.ts +129 -129
- package/src/dex/swaap-v2/config.ts +15 -0
- package/src/dex/swaap-v2/swaap-v2-e2e.test.ts +233 -17
- package/src/dex/swaap-v2/swaap-v2-integration.test.ts +1 -1
- package/src/dex/swaap-v2/swaap-v2.ts +1056 -1056
- package/src/dex/swell/swell.ts +312 -312
- package/src/dex/synthetix/synthetix.ts +420 -420
- package/src/dex/trader-joe-v2.1/trader-joe-v2.1.ts +180 -180
- package/src/dex/uniswap-v2/config.ts +637 -637
- package/src/dex/uniswap-v2/constants.ts +58 -58
- package/src/dex/uniswap-v2/uniswap-v2-e2e-avalanche.test.ts +1257 -1257
- package/src/dex/uniswap-v2/uniswap-v2-e2e-base.test.ts +42 -42
- package/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +1295 -1295
- package/src/dex/uniswap-v2/uniswap-v2-e2e-mainnet.test.ts +1834 -1834
- package/src/dex/uniswap-v2/uniswap-v2-e2e-polygon.test.ts +871 -871
- package/src/dex/uniswap-v2/uniswap-v2.ts +1117 -1117
- package/src/dex/uniswap-v3/uniswap-v3.ts +1501 -1501
- package/src/dex/uniswap-v3.ts +242 -242
- package/src/dex/weth/config.ts +73 -73
- package/src/dex/weth/weth.ts +230 -230
- package/src/dex/wombat/wombat.ts +467 -467
- package/src/dex/woo-fi-v2/woo-fi-v2.ts +529 -529
- package/src/dex/wsteth/wsteth.ts +304 -304
- package/src/dex/zerox/index.ts +328 -328
- package/src/executor/Executor01BytecodeBuilder.ts +474 -474
- package/src/executor/Executor02BytecodeBuilder.ts +1392 -1392
- package/src/executor/ExecutorBytecodeBuilder.ts +390 -390
- package/src/executor/ExecutorDetector.ts +131 -131
- package/src/executor/constants.ts +66 -66
- package/src/executor/executor01-bytecode-builder-e2e.test.ts +1478 -1478
- package/src/executor/utils.ts +62 -62
- package/src/router/simpleswap.ts +366 -366
- package/src/types.ts +342 -342
- package/tests/constants-e2e.ts +1632 -1622
- package/tests/generate-new-limit-order.ts +107 -107
|
@@ -1,1315 +1,1315 @@
|
|
|
1
|
-
import { Interface, AbiCoder, JsonFragment } from '@ethersproject/abi';
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
|
|
4
|
-
import { assert } from 'ts-essentials';
|
|
5
|
-
import BigNumber from 'bignumber.js';
|
|
6
|
-
import CurveABI from '../../abi/Curve.json';
|
|
7
|
-
import DirectSwapABI from '../../abi/DirectSwap.json';
|
|
8
|
-
import { Adapters, CurveV1Config } from './config';
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
AdapterExchangeParam,
|
|
12
|
-
Address,
|
|
13
|
-
DexExchangeParam,
|
|
14
|
-
ExchangePrices,
|
|
15
|
-
ExchangeTxInfo,
|
|
16
|
-
PoolLiquidity,
|
|
17
|
-
PoolPrices,
|
|
18
|
-
PreprocessTransactionOptions,
|
|
19
|
-
SimpleExchangeParam,
|
|
20
|
-
Token,
|
|
21
|
-
TransferFeeParams,
|
|
22
|
-
TxInfo,
|
|
23
|
-
} from '../../types';
|
|
24
|
-
import {
|
|
25
|
-
Network,
|
|
26
|
-
NULL_ADDRESS,
|
|
27
|
-
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
28
|
-
ETHER_ADDRESS,
|
|
29
|
-
SwapSide,
|
|
30
|
-
} from '../../constants';
|
|
31
|
-
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
32
|
-
import StableSwapBBTC from '../../abi/curve-v1/StableSwapBBTC.json';
|
|
33
|
-
import { CurvePool } from './pools/curve-pool';
|
|
34
|
-
import { CurveMetapool } from './pools/curve-metapool';
|
|
35
|
-
import { ThreePool, address as ThreePoolAddress } from './pools/3pool';
|
|
36
|
-
import { SUSDPool, address as SUSDPoolAddress } from './pools/sUSDpool';
|
|
37
|
-
import { HBTCPool, address as HBTCPoolAddress } from './pools/hBTCpool';
|
|
38
|
-
import { RenPool, address as RenPoolAddress } from './pools/renpool';
|
|
39
|
-
import { SBTCPool, address as SBTCPoolAddress } from './pools/sBTCpool';
|
|
40
|
-
import { SETHPool, address as SETHPoolAddress } from './pools/sETHpool';
|
|
41
|
-
import { STETHPool, address as STETHPoolAddress } from './pools/stETHpool';
|
|
42
|
-
import { EURSPool, address as EURSPoolAddress } from './pools/EURSpool';
|
|
43
|
-
import { DUSDPool, address as DUSDPoolAddress } from './pools/DUSDpool';
|
|
44
|
-
import { BBTCPool, address as BBTCPoolAddress } from './pools/bBTCpool';
|
|
45
|
-
import { GUSDPool, address as GUSDPoolAddress } from './pools/GUSDpool';
|
|
46
|
-
import { HUSDPool, address as HUSDPoolAddress } from './pools/HUSDpool';
|
|
47
|
-
import {
|
|
48
|
-
LinkUSDPool,
|
|
49
|
-
address as LinkUSDPoolAddress,
|
|
50
|
-
} from './pools/LinkUSDpool';
|
|
51
|
-
import { MUSDPool, address as MUSDPoolAddress } from './pools/MUSDpool';
|
|
52
|
-
import { OBTCPool, address as OBTCPoolAddress } from './pools/oBTCpool';
|
|
53
|
-
import { PBTCPool, address as PBTCPoolAddress } from './pools/pBTCpool';
|
|
54
|
-
import { RSVPool, address as RSVPoolAddress } from './pools/rsvpool';
|
|
55
|
-
import { TBTCPool, address as TBTCPoolAddress } from './pools/tBTCpool';
|
|
56
|
-
import { USDKPool, address as USDKPoolAddress } from './pools/usdkpool';
|
|
57
|
-
import { USTPool, address as USTPoolAddress } from './pools/ustpool';
|
|
58
|
-
import { SLINKPool, address as SLINKPoolAddress } from './pools/sLinkpool';
|
|
59
|
-
import {
|
|
60
|
-
getBigIntPow,
|
|
61
|
-
getDexKeysWithNetwork,
|
|
62
|
-
interpolate,
|
|
63
|
-
isDestTokenTransferFeeToBeExchanged,
|
|
64
|
-
isETHAddress,
|
|
65
|
-
isSrcTokenTransferFeeToBeExchanged,
|
|
66
|
-
Utils,
|
|
67
|
-
uuidToBytes16,
|
|
68
|
-
} from '../../utils';
|
|
69
|
-
import { BN_0 } from '../../bignumber-constants';
|
|
70
|
-
import {
|
|
71
|
-
getLocalDeadlineAsFriendlyPlaceholder,
|
|
72
|
-
SimpleExchange,
|
|
73
|
-
} from '../simple-exchange';
|
|
74
|
-
import { IDex } from '../idex';
|
|
75
|
-
import { IDexHelper } from '../../dex-helper';
|
|
76
|
-
import { Logger } from 'log4js';
|
|
77
|
-
import { uin128DecodeToInt, uin256DecodeToFloat } from '../../lib/decoders';
|
|
78
|
-
import { getManyPoolStates } from './pools/getstate-multicall';
|
|
79
|
-
import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
|
|
80
|
-
import {
|
|
81
|
-
PoolConfig,
|
|
82
|
-
CurveV1Data,
|
|
83
|
-
TokenWithReasonableVolume,
|
|
84
|
-
CurveSwapFunctions,
|
|
85
|
-
CurveV1SwapType,
|
|
86
|
-
DirectCurveV1Param,
|
|
87
|
-
DirectCurveV1ParamV6,
|
|
88
|
-
CurveV1DirectSwap,
|
|
89
|
-
} from './types';
|
|
90
|
-
import { erc20Iface } from '../../lib/utils-interfaces';
|
|
91
|
-
import { applyTransferFee } from '../../lib/token-transfer-fee';
|
|
92
|
-
import { DIRECT_METHOD_NAME, DIRECT_METHOD_NAME_V6 } from './constants';
|
|
93
|
-
import { packCurveData } from '../../lib/curve/encoder';
|
|
94
|
-
import { encodeCurveAssets } from './packer';
|
|
95
|
-
import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
|
|
96
|
-
|
|
97
|
-
const CURVE_DEFAULT_CHUNKS = 10;
|
|
98
|
-
|
|
99
|
-
const CURVE_LENDING_POOl_GAS = 340 * 1000;
|
|
100
|
-
const CURVE_POOL_GAS = 200 * 1000;
|
|
101
|
-
|
|
102
|
-
const coder = new AbiCoder();
|
|
103
|
-
|
|
104
|
-
export class CurveV1
|
|
105
|
-
extends SimpleExchange
|
|
106
|
-
implements IDex<CurveV1Data, DirectCurveV1Param | CurveV1DirectSwap>
|
|
107
|
-
{
|
|
108
|
-
exchangeRouterInterface: Interface;
|
|
109
|
-
minConversionRate = '1';
|
|
110
|
-
|
|
111
|
-
eventPools = new Array<CurvePool | CurveMetapool>();
|
|
112
|
-
public poolInterface: Interface;
|
|
113
|
-
|
|
114
|
-
private logger: Logger;
|
|
115
|
-
|
|
116
|
-
readonly hasConstantPriceLargeAmounts = false;
|
|
117
|
-
readonly isFeeOnTransferSupported = true;
|
|
118
|
-
|
|
119
|
-
private decimalsCoinsAndUnderlying: Record<string, number> = {};
|
|
120
|
-
|
|
121
|
-
protected pools: Record<string, PoolConfig>;
|
|
122
|
-
protected eventSupportedPools: string[];
|
|
123
|
-
protected baseTokens: Record<string, TokenWithReasonableVolume>;
|
|
124
|
-
|
|
125
|
-
protected disableFeeOnTransferTokenAddresses: Set<string>;
|
|
126
|
-
|
|
127
|
-
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
128
|
-
|
|
129
|
-
readonly SRC_TOKEN_DEX_TRANSFERS = 1;
|
|
130
|
-
readonly DEST_TOKEN_DEX_TRANSFERS = 1;
|
|
131
|
-
|
|
132
|
-
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
133
|
-
getDexKeysWithNetwork(CurveV1Config);
|
|
134
|
-
|
|
135
|
-
constructor(
|
|
136
|
-
protected network: Network,
|
|
137
|
-
dexKey: string,
|
|
138
|
-
protected dexHelper: IDexHelper,
|
|
139
|
-
dexConfig = CurveV1Config[dexKey][network],
|
|
140
|
-
|
|
141
|
-
protected adapters = Adapters[network],
|
|
142
|
-
) {
|
|
143
|
-
super(dexHelper, dexKey);
|
|
144
|
-
this.pools = Object.keys(dexConfig.pools).reduce<
|
|
145
|
-
Record<string, PoolConfig>
|
|
146
|
-
>((acc, key) => {
|
|
147
|
-
const poolConf = dexConfig.pools[key];
|
|
148
|
-
|
|
149
|
-
acc[key] = {
|
|
150
|
-
underlying: poolConf.underlying.map(t => t.toLowerCase()),
|
|
151
|
-
coins: poolConf.coins.map(t => t.toLowerCase()),
|
|
152
|
-
address: poolConf.address.toLowerCase(),
|
|
153
|
-
name: poolConf.name,
|
|
154
|
-
type: poolConf.type,
|
|
155
|
-
version: poolConf.version,
|
|
156
|
-
isLending: poolConf.isLending,
|
|
157
|
-
isMetapool: poolConf.isMetapool,
|
|
158
|
-
baseToken: poolConf.baseToken
|
|
159
|
-
? poolConf.baseToken.toLowerCase()
|
|
160
|
-
: poolConf.baseToken,
|
|
161
|
-
liquidityUSD: poolConf.liquidityUSD,
|
|
162
|
-
trackCoins: poolConf.trackCoins,
|
|
163
|
-
useLending: poolConf.useLending,
|
|
164
|
-
precisionMul: poolConf.precisionMul,
|
|
165
|
-
tokenAddress: poolConf.tokenAddress
|
|
166
|
-
? poolConf.tokenAddress.toLowerCase()
|
|
167
|
-
: undefined,
|
|
168
|
-
isFeeOnTransferSupported: poolConf.isFeeOnTransferSupported,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return acc;
|
|
172
|
-
}, {});
|
|
173
|
-
|
|
174
|
-
this.eventSupportedPools = dexConfig.eventSupportedPools.map(addr =>
|
|
175
|
-
addr.toLowerCase(),
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
this.baseTokens = Object.keys(dexConfig.baseTokens).reduce<
|
|
179
|
-
Record<string, TokenWithReasonableVolume>
|
|
180
|
-
>((acc, key) => {
|
|
181
|
-
const baseToken = dexConfig.baseTokens[key];
|
|
182
|
-
acc[key.toLowerCase()] = {
|
|
183
|
-
address: baseToken.address.toLowerCase(),
|
|
184
|
-
decimals: baseToken.decimals,
|
|
185
|
-
reasonableVolume: baseToken.reasonableVolume,
|
|
186
|
-
};
|
|
187
|
-
return acc;
|
|
188
|
-
}, {});
|
|
189
|
-
|
|
190
|
-
this.disableFeeOnTransferTokenAddresses =
|
|
191
|
-
dexConfig.disableFeeOnTransferTokenAddresses
|
|
192
|
-
? new Set<string>(
|
|
193
|
-
Array.from(
|
|
194
|
-
dexConfig.disableFeeOnTransferTokenAddresses.values(),
|
|
195
|
-
).map(addr => addr.toLowerCase()),
|
|
196
|
-
)
|
|
197
|
-
: new Set<string>();
|
|
198
|
-
|
|
199
|
-
this.exchangeRouterInterface = new Interface(CurveABI as JsonFragment[]);
|
|
200
|
-
|
|
201
|
-
this.logger = dexHelper.getLogger(dexKey);
|
|
202
|
-
|
|
203
|
-
this.poolInterface = new Interface(StableSwapBBTC as any);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
protected getPoolByAddress(address: Address) {
|
|
207
|
-
return (
|
|
208
|
-
this.eventPools.find(
|
|
209
|
-
p => p.address.toLowerCase() == address.toLowerCase(),
|
|
210
|
-
) || null
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
protected getPoolConfigByAddress(address: Address) {
|
|
215
|
-
return Object.values(this.pools).find(p => p.address == address);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
getPoolConfig(from: Token, to: Token): PoolConfig | undefined {
|
|
219
|
-
return this.getPoolConfigs(from, to)[0];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
getPoolConfigs(from: Token, to: Token) {
|
|
223
|
-
const fromAddress = from.address.toLowerCase();
|
|
224
|
-
const toAddress = to.address.toLowerCase();
|
|
225
|
-
return Object.values(this.pools).filter(config => {
|
|
226
|
-
const underlying = config.underlying.map((add: string) =>
|
|
227
|
-
add.toLowerCase(),
|
|
228
|
-
);
|
|
229
|
-
const coins = config.coins.map((add: string) => add.toLowerCase());
|
|
230
|
-
|
|
231
|
-
// Case 0: If the swap is between the underlying first token and any underlying token
|
|
232
|
-
if (
|
|
233
|
-
underlying.length &&
|
|
234
|
-
((underlying[0] === fromAddress && underlying.includes(toAddress)) ||
|
|
235
|
-
(underlying[0] === toAddress && underlying.includes(fromAddress)))
|
|
236
|
-
)
|
|
237
|
-
return true;
|
|
238
|
-
// Case 1: If it not a metapool and the swap is between any underlying tokens
|
|
239
|
-
if (
|
|
240
|
-
config.isLending &&
|
|
241
|
-
underlying.includes(toAddress) &&
|
|
242
|
-
underlying.includes(fromAddress)
|
|
243
|
-
)
|
|
244
|
-
return true;
|
|
245
|
-
|
|
246
|
-
// Case 2: If it a swap between any direct tokens
|
|
247
|
-
if (coins.includes(toAddress) && coins.includes(fromAddress)) return true;
|
|
248
|
-
|
|
249
|
-
return false;
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async getPoolIdentifiers(
|
|
254
|
-
_from: Token,
|
|
255
|
-
_to: Token,
|
|
256
|
-
side: SwapSide,
|
|
257
|
-
blockNumber: number,
|
|
258
|
-
): Promise<string[]> {
|
|
259
|
-
const pools = this.getPoolConfigs(_from, _to);
|
|
260
|
-
return pools.map(pool => this.getPoolIdentifier(pool.name));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
getContractAddress(srcToken: Token, to: Token) {
|
|
264
|
-
const pool = this.getPoolConfig(srcToken, to);
|
|
265
|
-
return pool && pool.address;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
poolConfigsByAddress() {
|
|
269
|
-
return Object.keys(this.pools).reduce((acc: any, poolName: string) => {
|
|
270
|
-
acc[this.pools[poolName].address.toLowerCase()] = this.pools[poolName];
|
|
271
|
-
return acc;
|
|
272
|
-
}, {});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// TODO: revisit to verify the chunks for BTC pools vs USD pools
|
|
276
|
-
getOnChainChunks(
|
|
277
|
-
from: Token,
|
|
278
|
-
to: Token,
|
|
279
|
-
srcAmount: bigint,
|
|
280
|
-
defaultChunks: number,
|
|
281
|
-
) {
|
|
282
|
-
return CURVE_DEFAULT_CHUNKS;
|
|
283
|
-
//return srcAmount >= 10000n * getBigIntPow(from.decimals)
|
|
284
|
-
// ? defaultChunks
|
|
285
|
-
// : CURVE_DEFAULT_CHUNKS;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
getEventPoolInstance(poolAddress: Address): CurvePool | CurveMetapool | null {
|
|
289
|
-
switch (poolAddress.toLowerCase()) {
|
|
290
|
-
case ThreePoolAddress:
|
|
291
|
-
return new ThreePool(this.dexKey, this.dexHelper);
|
|
292
|
-
case SUSDPoolAddress:
|
|
293
|
-
return new SUSDPool(this.dexKey, this.dexHelper);
|
|
294
|
-
case HBTCPoolAddress:
|
|
295
|
-
return new HBTCPool(this.dexKey, this.dexHelper);
|
|
296
|
-
case RenPoolAddress:
|
|
297
|
-
return new RenPool(this.dexKey, this.dexHelper);
|
|
298
|
-
case SBTCPoolAddress:
|
|
299
|
-
return new SBTCPool(this.dexKey, this.dexHelper);
|
|
300
|
-
case SETHPoolAddress:
|
|
301
|
-
return new SETHPool(this.dexKey, this.dexHelper);
|
|
302
|
-
case STETHPoolAddress:
|
|
303
|
-
return new STETHPool(this.dexKey, this.dexHelper);
|
|
304
|
-
case EURSPoolAddress:
|
|
305
|
-
return new EURSPool(this.dexKey, this.dexHelper);
|
|
306
|
-
case DUSDPoolAddress:
|
|
307
|
-
return new DUSDPool(this.dexKey, this.dexHelper);
|
|
308
|
-
case BBTCPoolAddress:
|
|
309
|
-
return new BBTCPool(this.dexKey, this.dexHelper);
|
|
310
|
-
case GUSDPoolAddress:
|
|
311
|
-
return new GUSDPool(this.dexKey, this.dexHelper);
|
|
312
|
-
case HUSDPoolAddress:
|
|
313
|
-
return new HUSDPool(this.dexKey, this.dexHelper);
|
|
314
|
-
case LinkUSDPoolAddress:
|
|
315
|
-
return new LinkUSDPool(this.dexKey, this.dexHelper);
|
|
316
|
-
case MUSDPoolAddress:
|
|
317
|
-
return new MUSDPool(this.dexKey, this.dexHelper);
|
|
318
|
-
case OBTCPoolAddress:
|
|
319
|
-
return new OBTCPool(this.dexKey, this.dexHelper);
|
|
320
|
-
case PBTCPoolAddress:
|
|
321
|
-
return new PBTCPool(this.dexKey, this.dexHelper);
|
|
322
|
-
case RSVPoolAddress:
|
|
323
|
-
return new RSVPool(this.dexKey, this.dexHelper);
|
|
324
|
-
case TBTCPoolAddress:
|
|
325
|
-
return new TBTCPool(this.dexKey, this.dexHelper);
|
|
326
|
-
case USDKPoolAddress:
|
|
327
|
-
return new USDKPool(this.dexKey, this.dexHelper);
|
|
328
|
-
case USTPoolAddress:
|
|
329
|
-
return new USTPool(this.dexKey, this.dexHelper);
|
|
330
|
-
case SLINKPoolAddress:
|
|
331
|
-
return new SLINKPool(this.dexKey, this.dexHelper);
|
|
332
|
-
default:
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Makes sure all the pools from poolAddress are being tracked. If a pool in not tracked yet
|
|
338
|
-
// fetch the state and setup the pool, if they are being watched but don't have a valid state
|
|
339
|
-
// add it to the list of unavailable pools. Return the list of unavailable pools.
|
|
340
|
-
async batchCatchUpPools(
|
|
341
|
-
poolAddresses: string[],
|
|
342
|
-
blockNumber: number,
|
|
343
|
-
): Promise<string[]> {
|
|
344
|
-
const unavailablePools = new Array<string>();
|
|
345
|
-
if (!blockNumber) return unavailablePools;
|
|
346
|
-
let poolsToFetch: (CurvePool | CurveMetapool)[] = [];
|
|
347
|
-
for (const poolAddress of poolAddresses) {
|
|
348
|
-
// Check if the pool is already tracked
|
|
349
|
-
let pool = this.getPoolByAddress(poolAddress);
|
|
350
|
-
// If the pool is not stracked add it to the list of pools to fetch
|
|
351
|
-
if (!pool) {
|
|
352
|
-
let newPool = this.getEventPoolInstance(poolAddress);
|
|
353
|
-
if (!newPool)
|
|
354
|
-
throw new Error(
|
|
355
|
-
`Error_${this.dexKey} requested unsupported event pool with address ${poolAddress}`,
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const addressesSubscribed = newPool.addressesSubscribed.map(address =>
|
|
359
|
-
address.toLowerCase(),
|
|
360
|
-
);
|
|
361
|
-
if (!addressesSubscribed.includes(poolAddress)) {
|
|
362
|
-
newPool.addressesSubscribed.push(poolAddress);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
await newPool.initialize(blockNumber);
|
|
366
|
-
poolsToFetch.push(newPool);
|
|
367
|
-
} else if (!pool.getState(blockNumber)) {
|
|
368
|
-
unavailablePools.push(poolAddress);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (!poolsToFetch.length) return unavailablePools;
|
|
373
|
-
|
|
374
|
-
try {
|
|
375
|
-
const poolStates = await getManyPoolStates(
|
|
376
|
-
poolsToFetch,
|
|
377
|
-
this.dexHelper.multiContract,
|
|
378
|
-
blockNumber,
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
for (let i = 0; i < poolsToFetch.length; i++) {
|
|
382
|
-
await poolsToFetch[i].setup(blockNumber, poolStates[i] as any);
|
|
383
|
-
this.eventPools.push(poolsToFetch[i]);
|
|
384
|
-
}
|
|
385
|
-
return unavailablePools;
|
|
386
|
-
} catch (e) {
|
|
387
|
-
this.logger.error(`Error_batchCatchUpPools`, e);
|
|
388
|
-
throw e;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async getRatesEventPools(
|
|
393
|
-
fromToken: Token,
|
|
394
|
-
toToken: Token,
|
|
395
|
-
amounts: BigNumber[],
|
|
396
|
-
fromBigNumbers: number,
|
|
397
|
-
pools: string[],
|
|
398
|
-
indexes: [number, number, number][],
|
|
399
|
-
blockNumber: number,
|
|
400
|
-
) {
|
|
401
|
-
const unavailablePools = await this.batchCatchUpPools(pools, blockNumber);
|
|
402
|
-
|
|
403
|
-
if (unavailablePools.length) {
|
|
404
|
-
this.logger.error(
|
|
405
|
-
`Couldn't use events for ${unavailablePools.length} ${this.dexKey} pools!`,
|
|
406
|
-
);
|
|
407
|
-
pools = pools.filter(p => !unavailablePools.includes(p));
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
let rates = new Array(pools.length);
|
|
411
|
-
for (let i = 0; i < pools.length; i++) {
|
|
412
|
-
rates[i] = {
|
|
413
|
-
exchange: pools[i],
|
|
414
|
-
rates: this.getRatesEventPool(
|
|
415
|
-
pools[i],
|
|
416
|
-
indexes[i],
|
|
417
|
-
amounts,
|
|
418
|
-
fromBigNumbers,
|
|
419
|
-
blockNumber,
|
|
420
|
-
),
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
return rates;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
async getRatesOnChain(
|
|
427
|
-
fromToken: Token,
|
|
428
|
-
toToken: Token,
|
|
429
|
-
amounts: bigint[],
|
|
430
|
-
pools: string[],
|
|
431
|
-
indexes: [number, number, number][],
|
|
432
|
-
blockNumber: number,
|
|
433
|
-
) {
|
|
434
|
-
const chunks = amounts.length - 1;
|
|
435
|
-
const srcAmount = amounts[chunks]!;
|
|
436
|
-
|
|
437
|
-
// The number of chunks used onchain to minimize the full node calls
|
|
438
|
-
const onChainChunks = this.getOnChainChunks(
|
|
439
|
-
fromToken,
|
|
440
|
-
toToken,
|
|
441
|
-
srcAmount,
|
|
442
|
-
chunks,
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
const _width = Math.floor(chunks / onChainChunks);
|
|
446
|
-
|
|
447
|
-
const unitVolume = amounts[0];
|
|
448
|
-
amounts[0] = 0n;
|
|
449
|
-
|
|
450
|
-
const _amounts = [unitVolume].concat(
|
|
451
|
-
Array.from(Array(onChainChunks).keys()).map(
|
|
452
|
-
i => amounts[(i + 1) * _width],
|
|
453
|
-
),
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
const calldata = pools.map((p, i) =>
|
|
457
|
-
_amounts.map(_amount => ({
|
|
458
|
-
target: p,
|
|
459
|
-
callData: this.poolInterface.encodeFunctionData(
|
|
460
|
-
indexes[i][2] === 1 ? 'get_dy_underlying' : 'get_dy',
|
|
461
|
-
[indexes[i][0], indexes[i][1], _amount],
|
|
462
|
-
),
|
|
463
|
-
})),
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
//const data = await this.multi.methods
|
|
467
|
-
// .tryAggregate(false, calldata.flat())
|
|
468
|
-
// .call({}, 'latest');
|
|
469
|
-
const data = (
|
|
470
|
-
await Promise.all(
|
|
471
|
-
calldata.map(async poolCalldata => {
|
|
472
|
-
try {
|
|
473
|
-
const result = await Utils.timeoutPromise<MultiResult<any>[]>(
|
|
474
|
-
this.dexHelper.multiContract.methods
|
|
475
|
-
.tryAggregate(false, poolCalldata)
|
|
476
|
-
.call({}, 'latest'),
|
|
477
|
-
2000,
|
|
478
|
-
`Timed out multicall for curve pool ${poolCalldata[0].target}`,
|
|
479
|
-
);
|
|
480
|
-
return result;
|
|
481
|
-
} catch (e) {
|
|
482
|
-
this.logger.error(e);
|
|
483
|
-
return Array<MultiResult<any>>(poolCalldata.length).fill({
|
|
484
|
-
success: false,
|
|
485
|
-
returnData: '0x',
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}),
|
|
489
|
-
)
|
|
490
|
-
).flat();
|
|
491
|
-
|
|
492
|
-
// Convert the on chain rates to the requested chunks, using simple interpolation
|
|
493
|
-
const correctChunks = (_r: bigint[]): bigint[] => [
|
|
494
|
-
_r[0],
|
|
495
|
-
...interpolate(
|
|
496
|
-
_amounts.slice(1),
|
|
497
|
-
_r.slice(1),
|
|
498
|
-
amounts.slice(1),
|
|
499
|
-
SwapSide.SELL,
|
|
500
|
-
),
|
|
501
|
-
];
|
|
502
|
-
|
|
503
|
-
const decode = (j: number) => {
|
|
504
|
-
if (!data[j].success) return 0n;
|
|
505
|
-
const decoded = coder.decode(['uint256'], data[j].returnData);
|
|
506
|
-
return BigInt(decoded[0].toString());
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
let i = 0;
|
|
510
|
-
// chunks !== onChainChunks || !isAmountsLinear
|
|
511
|
-
const ratesAllPools = pools.map(p => {
|
|
512
|
-
const rates = _amounts.map(a => decode(i++));
|
|
513
|
-
return {
|
|
514
|
-
rates: correctChunks(rates),
|
|
515
|
-
exchange: p,
|
|
516
|
-
};
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
return ratesAllPools;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
protected noMorePrice(prices: BigNumber[], index: number): boolean {
|
|
523
|
-
return index > 1 && prices[index - 1].eq(BN_0);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
getRatesEventPool(
|
|
527
|
-
exchange: string,
|
|
528
|
-
curveIndexes: number[],
|
|
529
|
-
amounts: BigNumber[],
|
|
530
|
-
fromBigNumbers: number,
|
|
531
|
-
blockNumber: number,
|
|
532
|
-
) {
|
|
533
|
-
let rates = new Array<BigNumber>(amounts.length).fill(BN_0);
|
|
534
|
-
|
|
535
|
-
let fromIndex = curveIndexes[0];
|
|
536
|
-
let toIndex = curveIndexes[1];
|
|
537
|
-
let isTokenSwap = curveIndexes[2] === 1;
|
|
538
|
-
|
|
539
|
-
let pool = this.getPoolByAddress(exchange);
|
|
540
|
-
if (!pool) {
|
|
541
|
-
throw new Error(
|
|
542
|
-
`Error_${this.dexKey}_getRatesEvent pool is not initialised`,
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const state = pool.getState(blockNumber);
|
|
547
|
-
if (!state)
|
|
548
|
-
throw new Error(
|
|
549
|
-
`Error_${this.dexKey}_${pool.name} expected state at blocknumber ${blockNumber}, got none`,
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
if (isTokenSwap) {
|
|
553
|
-
rates[0] = pool.get_dy_underlying(
|
|
554
|
-
fromIndex,
|
|
555
|
-
toIndex,
|
|
556
|
-
amounts[0],
|
|
557
|
-
state as any,
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
for (let i = 1; i < amounts.length; i++) {
|
|
561
|
-
rates[i] = this.noMorePrice(rates, i)
|
|
562
|
-
? BN_0
|
|
563
|
-
: pool.get_dy_underlying(
|
|
564
|
-
fromIndex,
|
|
565
|
-
toIndex,
|
|
566
|
-
amounts[i],
|
|
567
|
-
state as any,
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
} else {
|
|
571
|
-
rates[0] = pool.get_dy(fromIndex, toIndex, amounts[0], state as any);
|
|
572
|
-
|
|
573
|
-
for (let i = 1; i < amounts.length; i++) {
|
|
574
|
-
rates[i] = this.noMorePrice(rates, i)
|
|
575
|
-
? BN_0
|
|
576
|
-
: pool.get_dy(fromIndex, toIndex, amounts[i], state as any);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
let floatRates = rates.map(rate => BigInt(rate.toFixed(0)));
|
|
581
|
-
return floatRates;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
isEventPoolSupported(poolAddress: Address): Boolean {
|
|
585
|
-
return this.eventSupportedPools.some(p => p === poolAddress.toLowerCase());
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
allocPools(
|
|
589
|
-
from: Token,
|
|
590
|
-
to: Token,
|
|
591
|
-
side: SwapSide,
|
|
592
|
-
routeID: number,
|
|
593
|
-
usedPools: { [poolIdentifier: string]: number },
|
|
594
|
-
isFirstSwap: boolean,
|
|
595
|
-
) {
|
|
596
|
-
if (side === SwapSide.BUY) {
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
this.getPoolConfigs(from, to).forEach(p => {
|
|
601
|
-
const poolIdentifier = this.getPoolIdentifier(p.name);
|
|
602
|
-
if (!(poolIdentifier in usedPools)) {
|
|
603
|
-
usedPools[poolIdentifier] = routeID;
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
public getPoolIdentifier(name: string) {
|
|
609
|
-
return `${this.dexKey}_${name.toLowerCase()}`;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
async getPricesVolume(
|
|
613
|
-
_from: Token,
|
|
614
|
-
_to: Token,
|
|
615
|
-
amounts: bigint[],
|
|
616
|
-
side: SwapSide,
|
|
617
|
-
blockNumber: number,
|
|
618
|
-
// list of pool identifiers to use for pricing, if undefined use all pools
|
|
619
|
-
limitPools?: string[],
|
|
620
|
-
transferFees: TransferFeeParams = {
|
|
621
|
-
srcFee: 0,
|
|
622
|
-
destFee: 0,
|
|
623
|
-
srcDexFee: 0,
|
|
624
|
-
destDexFee: 0,
|
|
625
|
-
},
|
|
626
|
-
): Promise<ExchangePrices<CurveV1Data> | null> {
|
|
627
|
-
try {
|
|
628
|
-
if (side === SwapSide.BUY) {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
_from.address = _from.address.toLowerCase();
|
|
633
|
-
_to.address = _to.address.toLowerCase();
|
|
634
|
-
|
|
635
|
-
const _isSrcTokenTransferFeeToBeExchanged =
|
|
636
|
-
this.disableFeeOnTransferTokenAddresses.has(_from.address)
|
|
637
|
-
? false
|
|
638
|
-
: isSrcTokenTransferFeeToBeExchanged(transferFees);
|
|
639
|
-
|
|
640
|
-
const _isDestTokenTransferFeeToBeExchanged =
|
|
641
|
-
this.disableFeeOnTransferTokenAddresses.has(_to.address)
|
|
642
|
-
? false
|
|
643
|
-
: isDestTokenTransferFeeToBeExchanged(transferFees);
|
|
644
|
-
|
|
645
|
-
// We first filter out pools which were explicitly excluded and pools which are already used
|
|
646
|
-
// then for the good pools we set the boolean to be true for used pools
|
|
647
|
-
// and for each pool we take the address.
|
|
648
|
-
const goodPoolConfigs = this.getPoolConfigs(_from, _to).filter(p => {
|
|
649
|
-
if (limitPools !== undefined) {
|
|
650
|
-
const id = this.getPoolIdentifier(p.name);
|
|
651
|
-
if (!limitPools.includes(id)) {
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (_isSrcTokenTransferFeeToBeExchanged) {
|
|
657
|
-
// Fee on transfers supported only when flag is specified
|
|
658
|
-
return !!p.isFeeOnTransferSupported;
|
|
659
|
-
}
|
|
660
|
-
return true;
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
const goodPoolAddress = goodPoolConfigs.map(p => p.address);
|
|
664
|
-
const indexesByAddress = goodPoolConfigs.reduce(
|
|
665
|
-
(acc: { [address: string]: [number, number, number] }, pc) => {
|
|
666
|
-
acc[pc.address] = this.getSwapIndexes(_from, _to, pc);
|
|
667
|
-
return acc;
|
|
668
|
-
},
|
|
669
|
-
{},
|
|
670
|
-
);
|
|
671
|
-
|
|
672
|
-
if (goodPoolAddress.length === 0) {
|
|
673
|
-
return null;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const amountsWithUnit = [
|
|
677
|
-
getBigIntPow(_from.decimals),
|
|
678
|
-
...amounts.slice(1),
|
|
679
|
-
];
|
|
680
|
-
const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
|
|
681
|
-
? applyTransferFee(
|
|
682
|
-
applyTransferFee(
|
|
683
|
-
amountsWithUnit,
|
|
684
|
-
side,
|
|
685
|
-
transferFees.srcFee,
|
|
686
|
-
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
687
|
-
),
|
|
688
|
-
side,
|
|
689
|
-
transferFees.srcDexFee,
|
|
690
|
-
this.SRC_TOKEN_DEX_TRANSFERS,
|
|
691
|
-
)
|
|
692
|
-
: amountsWithUnit;
|
|
693
|
-
|
|
694
|
-
let _prices = new Array();
|
|
695
|
-
|
|
696
|
-
const eventPools = goodPoolAddress.filter(p =>
|
|
697
|
-
this.isEventPoolSupported(p),
|
|
698
|
-
);
|
|
699
|
-
const eventPoolIndexes = eventPools.map(ep => indexesByAddress[ep]);
|
|
700
|
-
if (eventPools) {
|
|
701
|
-
const pricesEvents = await this.getRatesEventPools(
|
|
702
|
-
_from,
|
|
703
|
-
_to,
|
|
704
|
-
amountsWithUnitAndFee.map(a => new BigNumber(a.toString())),
|
|
705
|
-
_from.decimals,
|
|
706
|
-
eventPools,
|
|
707
|
-
eventPoolIndexes,
|
|
708
|
-
blockNumber,
|
|
709
|
-
);
|
|
710
|
-
_prices = _prices.concat(pricesEvents);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// if the pool rates was not available in the event pool then fallback to the onchain pool query
|
|
714
|
-
const isPoolNotInPrices = (poolAddress: Address) =>
|
|
715
|
-
!_prices.find(
|
|
716
|
-
existingPrice =>
|
|
717
|
-
existingPrice.exchange.toLowerCase() === poolAddress.toLowerCase(),
|
|
718
|
-
);
|
|
719
|
-
const onChainPools = goodPoolAddress.filter(isPoolNotInPrices);
|
|
720
|
-
|
|
721
|
-
const onChainPoolIndexes = onChainPools.map(op => indexesByAddress[op]);
|
|
722
|
-
|
|
723
|
-
// onChainPools are only supported for pools with linear price chunks
|
|
724
|
-
if (onChainPools.length) {
|
|
725
|
-
const pricesOnChain = await this.getRatesOnChain(
|
|
726
|
-
_from,
|
|
727
|
-
_to,
|
|
728
|
-
amountsWithUnitAndFee,
|
|
729
|
-
onChainPools,
|
|
730
|
-
onChainPoolIndexes,
|
|
731
|
-
blockNumber,
|
|
732
|
-
);
|
|
733
|
-
_prices = _prices.concat(pricesOnChain);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
const poolConfigsByAddress = this.poolConfigsByAddress();
|
|
737
|
-
|
|
738
|
-
const poolPrices = _prices.reduce(
|
|
739
|
-
(acc: PoolPrices<CurveV1Data>[], _price: any, idx: number) => {
|
|
740
|
-
if (
|
|
741
|
-
_price.exchange === NULL_ADDRESS ||
|
|
742
|
-
!(_price.exchange.toLowerCase() in poolConfigsByAddress)
|
|
743
|
-
)
|
|
744
|
-
return acc;
|
|
745
|
-
|
|
746
|
-
const poolConfig =
|
|
747
|
-
poolConfigsByAddress[_price.exchange.toLowerCase()];
|
|
748
|
-
const indexes = this.getSwapIndexes(_from, _to, poolConfig);
|
|
749
|
-
const [i, j, swapType] = indexes;
|
|
750
|
-
|
|
751
|
-
if (_isDestTokenTransferFeeToBeExchanged) {
|
|
752
|
-
_price.rates = applyTransferFee(
|
|
753
|
-
_price.rates,
|
|
754
|
-
side,
|
|
755
|
-
transferFees.destDexFee,
|
|
756
|
-
this.DEST_TOKEN_DEX_TRANSFERS,
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
acc.push({
|
|
761
|
-
prices: [0n, ..._price.rates.slice(1)],
|
|
762
|
-
unit: _price.rates[0],
|
|
763
|
-
data: {
|
|
764
|
-
exchange: _price.exchange,
|
|
765
|
-
i,
|
|
766
|
-
j,
|
|
767
|
-
underlyingSwap: swapType === 1,
|
|
768
|
-
deadline: 0,
|
|
769
|
-
},
|
|
770
|
-
exchange: this.dexKey,
|
|
771
|
-
poolIdentifier: `${this.dexKey}_${poolConfig.name.toLowerCase()}`,
|
|
772
|
-
gasCost: poolConfig.isLending
|
|
773
|
-
? CURVE_LENDING_POOl_GAS
|
|
774
|
-
: CURVE_POOL_GAS,
|
|
775
|
-
poolAddresses: [_price.exchange],
|
|
776
|
-
});
|
|
777
|
-
return acc;
|
|
778
|
-
},
|
|
779
|
-
[],
|
|
780
|
-
);
|
|
781
|
-
return poolPrices;
|
|
782
|
-
} catch (e) {
|
|
783
|
-
if (blockNumber === 0)
|
|
784
|
-
this.logger.error(
|
|
785
|
-
`Error_${this.dexKey}_getPrices: Aurelius block manager not yet instantiated`,
|
|
786
|
-
);
|
|
787
|
-
this.logger.error(`Error_${this.dexKey}_getPrices`, e);
|
|
788
|
-
return null;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
getSwapIndexes(
|
|
793
|
-
from: Token,
|
|
794
|
-
to: Token,
|
|
795
|
-
pool?: PoolConfig,
|
|
796
|
-
): [number, number, number] {
|
|
797
|
-
pool = pool || this.getPoolConfig(from, to);
|
|
798
|
-
if (!pool) {
|
|
799
|
-
return [-1, -1, -1];
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const fromAddress = from.address.toLowerCase();
|
|
803
|
-
const toAddress = to.address.toLowerCase();
|
|
804
|
-
const isUnderlyingSwap =
|
|
805
|
-
pool.underlying.length &&
|
|
806
|
-
pool.underlying.some(t => t.toLowerCase() === fromAddress) &&
|
|
807
|
-
pool.underlying.some(t => t.toLowerCase() === toAddress);
|
|
808
|
-
|
|
809
|
-
const fromIndex = isUnderlyingSwap
|
|
810
|
-
? pool.underlying.findIndex(t => t.toLowerCase() === fromAddress)
|
|
811
|
-
: pool.coins.findIndex(t => t.toLowerCase() === fromAddress);
|
|
812
|
-
|
|
813
|
-
const toIndex = isUnderlyingSwap
|
|
814
|
-
? pool.underlying.findIndex(t => t.toLowerCase() === toAddress)
|
|
815
|
-
: pool.coins.findIndex(t => t.toLowerCase() === toAddress);
|
|
816
|
-
|
|
817
|
-
const swapType = isUnderlyingSwap ? 1 : 2;
|
|
818
|
-
|
|
819
|
-
return [fromIndex, toIndex, swapType];
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
getAdapterParam(
|
|
823
|
-
srcToken: string,
|
|
824
|
-
destToken: string,
|
|
825
|
-
srcAmount: string,
|
|
826
|
-
destAmount: string,
|
|
827
|
-
data: CurveV1Data,
|
|
828
|
-
side: SwapSide,
|
|
829
|
-
): AdapterExchangeParam {
|
|
830
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
831
|
-
|
|
832
|
-
const { i, j, deadline, underlyingSwap } = data;
|
|
833
|
-
const payload = this.abiCoder.encodeParameter(
|
|
834
|
-
{
|
|
835
|
-
ParentStruct: {
|
|
836
|
-
i: 'int128',
|
|
837
|
-
j: 'int128',
|
|
838
|
-
deadline: 'uint256',
|
|
839
|
-
underlyingSwap: 'bool',
|
|
840
|
-
},
|
|
841
|
-
},
|
|
842
|
-
{ i, j, deadline, underlyingSwap },
|
|
843
|
-
);
|
|
844
|
-
|
|
845
|
-
return {
|
|
846
|
-
targetExchange: data.exchange,
|
|
847
|
-
payload,
|
|
848
|
-
networkFee: '0',
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
getCalldataGasCost(poolPrices: PoolPrices<CurveV1Data>): number | number[] {
|
|
853
|
-
return (
|
|
854
|
-
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
855
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
856
|
-
CALLDATA_GAS_COST.INDEX +
|
|
857
|
-
CALLDATA_GAS_COST.INDEX +
|
|
858
|
-
CALLDATA_GAS_COST.TIMESTAMP +
|
|
859
|
-
CALLDATA_GAS_COST.BOOL
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
864
|
-
return this.adapters[side];
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
static getDirectFunctionName(): string[] {
|
|
868
|
-
return [DIRECT_METHOD_NAME];
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
getTokenFromAddress(address: Address): Token {
|
|
872
|
-
// In this Dex decimals are not used
|
|
873
|
-
return { address, decimals: 0 };
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
async preProcessTransaction(
|
|
877
|
-
optimalSwapExchange: OptimalSwapExchange<CurveV1Data>,
|
|
878
|
-
srcToken: Token,
|
|
879
|
-
_0: Token,
|
|
880
|
-
_1: SwapSide,
|
|
881
|
-
options: PreprocessTransactionOptions,
|
|
882
|
-
): Promise<[OptimalSwapExchange<CurveV1Data>, ExchangeTxInfo]> {
|
|
883
|
-
if (!options.isDirectMethod) {
|
|
884
|
-
return [
|
|
885
|
-
optimalSwapExchange,
|
|
886
|
-
{
|
|
887
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
888
|
-
},
|
|
889
|
-
];
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
assert(
|
|
893
|
-
optimalSwapExchange.data !== undefined,
|
|
894
|
-
`preProcessTransaction: data field is missing`,
|
|
895
|
-
);
|
|
896
|
-
|
|
897
|
-
let isApproved: boolean | undefined;
|
|
898
|
-
|
|
899
|
-
try {
|
|
900
|
-
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
901
|
-
options.executionContractAddress,
|
|
902
|
-
this.dexHelper.config.wrapETH(srcToken).address,
|
|
903
|
-
optimalSwapExchange.data.exchange,
|
|
904
|
-
);
|
|
905
|
-
} catch (e) {
|
|
906
|
-
this.logger.error(
|
|
907
|
-
`preProcessTransaction failed to retrieve allowance info: `,
|
|
908
|
-
e,
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
return [
|
|
913
|
-
{
|
|
914
|
-
...optimalSwapExchange,
|
|
915
|
-
data: {
|
|
916
|
-
...optimalSwapExchange.data,
|
|
917
|
-
isApproved,
|
|
918
|
-
},
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
922
|
-
},
|
|
923
|
-
];
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
getDirectParam(
|
|
927
|
-
srcToken: Address,
|
|
928
|
-
destToken: Address,
|
|
929
|
-
srcAmount: NumberAsString,
|
|
930
|
-
destAmount: NumberAsString,
|
|
931
|
-
expectedAmount: NumberAsString,
|
|
932
|
-
data: CurveV1Data,
|
|
933
|
-
side: SwapSide,
|
|
934
|
-
permit: string,
|
|
935
|
-
uuid: string,
|
|
936
|
-
feePercent: NumberAsString,
|
|
937
|
-
deadline: NumberAsString,
|
|
938
|
-
partner: string,
|
|
939
|
-
beneficiary: string,
|
|
940
|
-
contractMethod?: string,
|
|
941
|
-
): TxInfo<DirectCurveV1Param> {
|
|
942
|
-
if (contractMethod !== DIRECT_METHOD_NAME) {
|
|
943
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
944
|
-
}
|
|
945
|
-
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
946
|
-
|
|
947
|
-
let isApproved: boolean = !!data.isApproved;
|
|
948
|
-
if (data.isApproved === undefined) {
|
|
949
|
-
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
const swapParams: DirectCurveV1Param = [
|
|
953
|
-
srcToken,
|
|
954
|
-
destToken,
|
|
955
|
-
data.exchange,
|
|
956
|
-
srcAmount,
|
|
957
|
-
destAmount,
|
|
958
|
-
expectedAmount,
|
|
959
|
-
feePercent,
|
|
960
|
-
data.i.toString(),
|
|
961
|
-
data.j.toString(),
|
|
962
|
-
partner,
|
|
963
|
-
isApproved,
|
|
964
|
-
data.underlyingSwap
|
|
965
|
-
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
966
|
-
: CurveV1SwapType.EXCHANGE,
|
|
967
|
-
beneficiary,
|
|
968
|
-
// For CurveV1 we work as it is, without wrapping and unwrapping
|
|
969
|
-
false,
|
|
970
|
-
permit,
|
|
971
|
-
uuidToBytes16(uuid),
|
|
972
|
-
];
|
|
973
|
-
|
|
974
|
-
const encoder = (...params: DirectCurveV1Param) => {
|
|
975
|
-
return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
|
|
976
|
-
params,
|
|
977
|
-
]);
|
|
978
|
-
};
|
|
979
|
-
|
|
980
|
-
return {
|
|
981
|
-
params: swapParams,
|
|
982
|
-
encoder,
|
|
983
|
-
networkFee: '0',
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
getDirectParamV6(
|
|
988
|
-
srcToken: Address,
|
|
989
|
-
destToken: Address,
|
|
990
|
-
fromAmount: NumberAsString,
|
|
991
|
-
toAmount: NumberAsString,
|
|
992
|
-
quotedAmount: NumberAsString,
|
|
993
|
-
data: CurveV1Data,
|
|
994
|
-
side: SwapSide,
|
|
995
|
-
permit: string,
|
|
996
|
-
uuid: string,
|
|
997
|
-
partnerAndFee: string,
|
|
998
|
-
beneficiary: string,
|
|
999
|
-
blockNumber: number,
|
|
1000
|
-
contractMethod?: string,
|
|
1001
|
-
) {
|
|
1002
|
-
if (contractMethod !== DIRECT_METHOD_NAME_V6) {
|
|
1003
|
-
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1004
|
-
}
|
|
1005
|
-
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1006
|
-
|
|
1007
|
-
const metadata = hexConcat([
|
|
1008
|
-
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1009
|
-
hexZeroPad(hexlify(blockNumber), 16),
|
|
1010
|
-
]);
|
|
1011
|
-
|
|
1012
|
-
const swapParams: DirectCurveV1ParamV6 = [
|
|
1013
|
-
packCurveData(
|
|
1014
|
-
data.exchange,
|
|
1015
|
-
!data.isApproved, // approve flag, if not approved then set to true
|
|
1016
|
-
isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
|
|
1017
|
-
data.underlyingSwap
|
|
1018
|
-
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1019
|
-
: CurveV1SwapType.EXCHANGE,
|
|
1020
|
-
).toString(),
|
|
1021
|
-
encodeCurveAssets(data.i, data.j).toString(),
|
|
1022
|
-
srcToken,
|
|
1023
|
-
destToken,
|
|
1024
|
-
fromAmount,
|
|
1025
|
-
toAmount,
|
|
1026
|
-
quotedAmount,
|
|
1027
|
-
metadata,
|
|
1028
|
-
beneficiary,
|
|
1029
|
-
];
|
|
1030
|
-
|
|
1031
|
-
const encodeParams: CurveV1DirectSwap = [swapParams, partnerAndFee, permit];
|
|
1032
|
-
|
|
1033
|
-
const encoder = (...params: CurveV1DirectSwap) => {
|
|
1034
|
-
return this.augustusV6Interface.encodeFunctionData(
|
|
1035
|
-
DIRECT_METHOD_NAME_V6,
|
|
1036
|
-
[...params],
|
|
1037
|
-
);
|
|
1038
|
-
};
|
|
1039
|
-
|
|
1040
|
-
return {
|
|
1041
|
-
encoder,
|
|
1042
|
-
params: encodeParams,
|
|
1043
|
-
networkFee: '0',
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
static getDirectFunctionNameV6(): string[] {
|
|
1048
|
-
return [DIRECT_METHOD_NAME_V6];
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
getDexParam(
|
|
1052
|
-
srcToken: Address,
|
|
1053
|
-
destToken: Address,
|
|
1054
|
-
srcAmount: NumberAsString,
|
|
1055
|
-
destAmount: NumberAsString,
|
|
1056
|
-
recipient: Address,
|
|
1057
|
-
data: CurveV1Data,
|
|
1058
|
-
side: SwapSide,
|
|
1059
|
-
): DexExchangeParam {
|
|
1060
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1061
|
-
|
|
1062
|
-
const { exchange, i, j, underlyingSwap } = data;
|
|
1063
|
-
const defaultArgs = [i, j, srcAmount, this.minConversionRate];
|
|
1064
|
-
const swapMethod = underlyingSwap
|
|
1065
|
-
? CurveSwapFunctions.exchange_underlying
|
|
1066
|
-
: CurveSwapFunctions.exchange;
|
|
1067
|
-
const exchangeData = this.exchangeRouterInterface.encodeFunctionData(
|
|
1068
|
-
swapMethod,
|
|
1069
|
-
defaultArgs,
|
|
1070
|
-
);
|
|
1071
|
-
|
|
1072
|
-
return {
|
|
1073
|
-
exchangeData,
|
|
1074
|
-
needWrapNative: this.needWrapNative,
|
|
1075
|
-
sendEthButSupportsInsertFromAmount: true,
|
|
1076
|
-
dexFuncHasRecipient: false,
|
|
1077
|
-
targetExchange: exchange,
|
|
1078
|
-
returnAmountPos: undefined,
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
async getSimpleParam(
|
|
1083
|
-
srcToken: string,
|
|
1084
|
-
destToken: string,
|
|
1085
|
-
srcAmount: string,
|
|
1086
|
-
destAmount: string,
|
|
1087
|
-
data: CurveV1Data,
|
|
1088
|
-
side: SwapSide,
|
|
1089
|
-
): Promise<SimpleExchangeParam> {
|
|
1090
|
-
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1091
|
-
|
|
1092
|
-
const { exchange, i, j, underlyingSwap } = data;
|
|
1093
|
-
const defaultArgs = [i, j, srcAmount, this.minConversionRate];
|
|
1094
|
-
const swapMethod = underlyingSwap
|
|
1095
|
-
? CurveSwapFunctions.exchange_underlying
|
|
1096
|
-
: CurveSwapFunctions.exchange;
|
|
1097
|
-
const swapData = this.exchangeRouterInterface.encodeFunctionData(
|
|
1098
|
-
swapMethod,
|
|
1099
|
-
defaultArgs,
|
|
1100
|
-
);
|
|
1101
|
-
|
|
1102
|
-
return this.buildSimpleParamWithoutWETHConversion(
|
|
1103
|
-
srcToken,
|
|
1104
|
-
srcAmount,
|
|
1105
|
-
destToken,
|
|
1106
|
-
destAmount,
|
|
1107
|
-
swapData,
|
|
1108
|
-
exchange,
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
async updateBaseTokenPrice() {
|
|
1113
|
-
for (let baseToken of Object.values(this.baseTokens)) {
|
|
1114
|
-
const usdPrice = await this.dexHelper.getTokenUSDPrice(
|
|
1115
|
-
baseToken,
|
|
1116
|
-
baseToken.reasonableVolume,
|
|
1117
|
-
);
|
|
1118
|
-
|
|
1119
|
-
const denominator =
|
|
1120
|
-
Number(baseToken.reasonableVolume) / 10 ** baseToken.decimals;
|
|
1121
|
-
|
|
1122
|
-
baseToken.tokenPrice = usdPrice / denominator;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
async fetchDecimals() {
|
|
1127
|
-
const calls = Object.values(this.pools).reduce<MultiCallParams<any>[]>(
|
|
1128
|
-
(acc, pool) => {
|
|
1129
|
-
const underlyingDecimalsCalls = pool.underlying.reduce<
|
|
1130
|
-
MultiCallParams<any>[]
|
|
1131
|
-
>((_acc, _token) => {
|
|
1132
|
-
const token = _token.toLowerCase();
|
|
1133
|
-
if (token === ETHER_ADDRESS) {
|
|
1134
|
-
this.decimalsCoinsAndUnderlying[token] = 18;
|
|
1135
|
-
} else {
|
|
1136
|
-
_acc.push({
|
|
1137
|
-
target: token,
|
|
1138
|
-
callData: erc20Iface.encodeFunctionData('decimals'),
|
|
1139
|
-
decodeFunction: uin128DecodeToInt,
|
|
1140
|
-
cb: (decimals: number) => {
|
|
1141
|
-
this.decimalsCoinsAndUnderlying[token.toLowerCase()] = decimals;
|
|
1142
|
-
},
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
return _acc;
|
|
1147
|
-
}, []);
|
|
1148
|
-
|
|
1149
|
-
const coinsDecimalsCalls = pool.coins.reduce<MultiCallParams<any>[]>(
|
|
1150
|
-
(_acc, _coin) => {
|
|
1151
|
-
const coin = _coin.toLowerCase();
|
|
1152
|
-
if (coin === ETHER_ADDRESS) {
|
|
1153
|
-
this.decimalsCoinsAndUnderlying[coin] = 18;
|
|
1154
|
-
} else {
|
|
1155
|
-
_acc.push({
|
|
1156
|
-
target: coin,
|
|
1157
|
-
callData: erc20Iface.encodeFunctionData('decimals'),
|
|
1158
|
-
decodeFunction: uin128DecodeToInt,
|
|
1159
|
-
cb: (decimals: number) => {
|
|
1160
|
-
this.decimalsCoinsAndUnderlying[coin.toLowerCase()] =
|
|
1161
|
-
decimals;
|
|
1162
|
-
},
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
return _acc;
|
|
1166
|
-
},
|
|
1167
|
-
[],
|
|
1168
|
-
);
|
|
1169
|
-
|
|
1170
|
-
acc.push(...underlyingDecimalsCalls, ...coinsDecimalsCalls);
|
|
1171
|
-
return acc;
|
|
1172
|
-
},
|
|
1173
|
-
[],
|
|
1174
|
-
);
|
|
1175
|
-
|
|
1176
|
-
return this.dexHelper.multiWrapper!.tryAggregate(true, calls);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
async fetchAllPools() {
|
|
1180
|
-
try {
|
|
1181
|
-
// Hack: We choose to check the balance of the token for the pool instead of calling
|
|
1182
|
-
// the balances array in the pool as it was a mess to have each curve abi
|
|
1183
|
-
// Some have balances[uint128] some have balances[uint256]
|
|
1184
|
-
// One of the consequence for such a hack is below for ETH Pools
|
|
1185
|
-
// TODO: below can be highly optimized
|
|
1186
|
-
// * we don't need to query token decimals every iteration
|
|
1187
|
-
// * we can use the decimals from the tokens list using the api
|
|
1188
|
-
|
|
1189
|
-
const calls = Object.values(this.pools).reduce<MultiCallParams<number>[]>(
|
|
1190
|
-
(acc, pool) => {
|
|
1191
|
-
const coinCalls = pool.coins.map<MultiCallParams<number>>(coin => {
|
|
1192
|
-
const coinLowerCase = coin.toLowerCase();
|
|
1193
|
-
if (coinLowerCase === ETHER_ADDRESS) {
|
|
1194
|
-
return {
|
|
1195
|
-
target:
|
|
1196
|
-
this.dexHelper.config.data.multicallV2Address.toLowerCase(),
|
|
1197
|
-
callData: this.dexHelper.multiContract.methods
|
|
1198
|
-
.getEthBalance(pool.address.toLowerCase())
|
|
1199
|
-
.encodeABI(),
|
|
1200
|
-
decodeFunction: uin256DecodeToFloat,
|
|
1201
|
-
};
|
|
1202
|
-
} else {
|
|
1203
|
-
return {
|
|
1204
|
-
target: coinLowerCase,
|
|
1205
|
-
callData: erc20Iface.encodeFunctionData('balanceOf', [
|
|
1206
|
-
pool.address,
|
|
1207
|
-
]),
|
|
1208
|
-
decodeFunction: uin256DecodeToFloat,
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
});
|
|
1212
|
-
acc.push(...coinCalls);
|
|
1213
|
-
return acc;
|
|
1214
|
-
},
|
|
1215
|
-
[],
|
|
1216
|
-
);
|
|
1217
|
-
|
|
1218
|
-
const results = await this.dexHelper.multiWrapper!.tryAggregate(
|
|
1219
|
-
true,
|
|
1220
|
-
calls,
|
|
1221
|
-
);
|
|
1222
|
-
|
|
1223
|
-
let index = 0;
|
|
1224
|
-
Object.values(this.pools).forEach(pool => {
|
|
1225
|
-
const balances = results.slice(index, index + pool.coins.length);
|
|
1226
|
-
index += pool.coins.length;
|
|
1227
|
-
|
|
1228
|
-
const sumLiquidity = balances.reduce((acc, balance, i) => {
|
|
1229
|
-
const coin = pool.coins[i].toLowerCase();
|
|
1230
|
-
const decimals = this.decimalsCoinsAndUnderlying[coin];
|
|
1231
|
-
|
|
1232
|
-
return acc + balance.returnData / 10 ** decimals;
|
|
1233
|
-
}, 0);
|
|
1234
|
-
|
|
1235
|
-
let liquidityUSD = 0;
|
|
1236
|
-
if (pool.baseToken) {
|
|
1237
|
-
if (!this.baseTokens[pool.baseToken]) {
|
|
1238
|
-
throw new Error(
|
|
1239
|
-
`missing base token ${pool.baseToken} for ${this.dexKey}`,
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
liquidityUSD =
|
|
1243
|
-
sumLiquidity * Number(this.baseTokens[pool.baseToken].tokenPrice!);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
pool.liquidityUSD = liquidityUSD;
|
|
1247
|
-
});
|
|
1248
|
-
} catch (e) {
|
|
1249
|
-
this.logger.error(`fetchAllPools ${this.dexKey}`, e);
|
|
1250
|
-
throw e;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
async updatePoolState(): Promise<void> {
|
|
1255
|
-
if (!Object.keys(this.decimalsCoinsAndUnderlying).length) {
|
|
1256
|
-
await this.fetchDecimals();
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
await this.updateBaseTokenPrice();
|
|
1260
|
-
await this.fetchAllPools();
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
async getTopPoolsForToken(
|
|
1264
|
-
tokenAddress: Address,
|
|
1265
|
-
limit: number,
|
|
1266
|
-
): Promise<PoolLiquidity[]> {
|
|
1267
|
-
const _tokenAddress = this.dexHelper.config.unwrapETH({
|
|
1268
|
-
address: tokenAddress,
|
|
1269
|
-
decimals: 18,
|
|
1270
|
-
}).address;
|
|
1271
|
-
// In general case a token can be in the coins or underlying
|
|
1272
|
-
// In case of the metapool the token might be in both at the same time
|
|
1273
|
-
// It is important to note that the connector tokens might not be
|
|
1274
|
-
// compatible for exchange among themselves.
|
|
1275
|
-
const selectedPool = Object.values(this.pools).reduce<PoolLiquidity[]>(
|
|
1276
|
-
(acc, pool) => {
|
|
1277
|
-
const inCoins = pool.coins.some(
|
|
1278
|
-
_token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
|
|
1279
|
-
);
|
|
1280
|
-
const inUnderlying = pool.underlying.some(
|
|
1281
|
-
_token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
|
|
1282
|
-
);
|
|
1283
|
-
let connectorTokens = inCoins ? pool.coins : [];
|
|
1284
|
-
connectorTokens = inUnderlying
|
|
1285
|
-
? _.concat(connectorTokens, pool.underlying)
|
|
1286
|
-
: connectorTokens;
|
|
1287
|
-
|
|
1288
|
-
if (connectorTokens.length) {
|
|
1289
|
-
acc.push({
|
|
1290
|
-
exchange: this.dexKey,
|
|
1291
|
-
address: pool.address,
|
|
1292
|
-
liquidityUSD: pool.liquidityUSD!,
|
|
1293
|
-
connectorTokens: _.uniq(connectorTokens)
|
|
1294
|
-
.filter(
|
|
1295
|
-
(_token: string) =>
|
|
1296
|
-
_token.toLowerCase() !== _tokenAddress.toLowerCase(),
|
|
1297
|
-
)
|
|
1298
|
-
.map(tokenAddress => {
|
|
1299
|
-
const address = tokenAddress.toLowerCase();
|
|
1300
|
-
return {
|
|
1301
|
-
address,
|
|
1302
|
-
decimals: this.decimalsCoinsAndUnderlying[address],
|
|
1303
|
-
};
|
|
1304
|
-
}),
|
|
1305
|
-
});
|
|
1306
|
-
}
|
|
1307
|
-
return acc;
|
|
1308
|
-
},
|
|
1309
|
-
[],
|
|
1310
|
-
);
|
|
1311
|
-
return selectedPool
|
|
1312
|
-
.sort((a, b) => b.liquidityUSD - a.liquidityUSD)
|
|
1313
|
-
.slice(0, limit);
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1
|
+
import { Interface, AbiCoder, JsonFragment } from '@ethersproject/abi';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { NumberAsString, OptimalSwapExchange } from '@paraswap/core';
|
|
4
|
+
import { assert } from 'ts-essentials';
|
|
5
|
+
import BigNumber from 'bignumber.js';
|
|
6
|
+
import CurveABI from '../../abi/Curve.json';
|
|
7
|
+
import DirectSwapABI from '../../abi/DirectSwap.json';
|
|
8
|
+
import { Adapters, CurveV1Config } from './config';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
AdapterExchangeParam,
|
|
12
|
+
Address,
|
|
13
|
+
DexExchangeParam,
|
|
14
|
+
ExchangePrices,
|
|
15
|
+
ExchangeTxInfo,
|
|
16
|
+
PoolLiquidity,
|
|
17
|
+
PoolPrices,
|
|
18
|
+
PreprocessTransactionOptions,
|
|
19
|
+
SimpleExchangeParam,
|
|
20
|
+
Token,
|
|
21
|
+
TransferFeeParams,
|
|
22
|
+
TxInfo,
|
|
23
|
+
} from '../../types';
|
|
24
|
+
import {
|
|
25
|
+
Network,
|
|
26
|
+
NULL_ADDRESS,
|
|
27
|
+
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
28
|
+
ETHER_ADDRESS,
|
|
29
|
+
SwapSide,
|
|
30
|
+
} from '../../constants';
|
|
31
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
32
|
+
import StableSwapBBTC from '../../abi/curve-v1/StableSwapBBTC.json';
|
|
33
|
+
import { CurvePool } from './pools/curve-pool';
|
|
34
|
+
import { CurveMetapool } from './pools/curve-metapool';
|
|
35
|
+
import { ThreePool, address as ThreePoolAddress } from './pools/3pool';
|
|
36
|
+
import { SUSDPool, address as SUSDPoolAddress } from './pools/sUSDpool';
|
|
37
|
+
import { HBTCPool, address as HBTCPoolAddress } from './pools/hBTCpool';
|
|
38
|
+
import { RenPool, address as RenPoolAddress } from './pools/renpool';
|
|
39
|
+
import { SBTCPool, address as SBTCPoolAddress } from './pools/sBTCpool';
|
|
40
|
+
import { SETHPool, address as SETHPoolAddress } from './pools/sETHpool';
|
|
41
|
+
import { STETHPool, address as STETHPoolAddress } from './pools/stETHpool';
|
|
42
|
+
import { EURSPool, address as EURSPoolAddress } from './pools/EURSpool';
|
|
43
|
+
import { DUSDPool, address as DUSDPoolAddress } from './pools/DUSDpool';
|
|
44
|
+
import { BBTCPool, address as BBTCPoolAddress } from './pools/bBTCpool';
|
|
45
|
+
import { GUSDPool, address as GUSDPoolAddress } from './pools/GUSDpool';
|
|
46
|
+
import { HUSDPool, address as HUSDPoolAddress } from './pools/HUSDpool';
|
|
47
|
+
import {
|
|
48
|
+
LinkUSDPool,
|
|
49
|
+
address as LinkUSDPoolAddress,
|
|
50
|
+
} from './pools/LinkUSDpool';
|
|
51
|
+
import { MUSDPool, address as MUSDPoolAddress } from './pools/MUSDpool';
|
|
52
|
+
import { OBTCPool, address as OBTCPoolAddress } from './pools/oBTCpool';
|
|
53
|
+
import { PBTCPool, address as PBTCPoolAddress } from './pools/pBTCpool';
|
|
54
|
+
import { RSVPool, address as RSVPoolAddress } from './pools/rsvpool';
|
|
55
|
+
import { TBTCPool, address as TBTCPoolAddress } from './pools/tBTCpool';
|
|
56
|
+
import { USDKPool, address as USDKPoolAddress } from './pools/usdkpool';
|
|
57
|
+
import { USTPool, address as USTPoolAddress } from './pools/ustpool';
|
|
58
|
+
import { SLINKPool, address as SLINKPoolAddress } from './pools/sLinkpool';
|
|
59
|
+
import {
|
|
60
|
+
getBigIntPow,
|
|
61
|
+
getDexKeysWithNetwork,
|
|
62
|
+
interpolate,
|
|
63
|
+
isDestTokenTransferFeeToBeExchanged,
|
|
64
|
+
isETHAddress,
|
|
65
|
+
isSrcTokenTransferFeeToBeExchanged,
|
|
66
|
+
Utils,
|
|
67
|
+
uuidToBytes16,
|
|
68
|
+
} from '../../utils';
|
|
69
|
+
import { BN_0 } from '../../bignumber-constants';
|
|
70
|
+
import {
|
|
71
|
+
getLocalDeadlineAsFriendlyPlaceholder,
|
|
72
|
+
SimpleExchange,
|
|
73
|
+
} from '../simple-exchange';
|
|
74
|
+
import { IDex } from '../idex';
|
|
75
|
+
import { IDexHelper } from '../../dex-helper';
|
|
76
|
+
import { Logger } from 'log4js';
|
|
77
|
+
import { uin128DecodeToInt, uin256DecodeToFloat } from '../../lib/decoders';
|
|
78
|
+
import { getManyPoolStates } from './pools/getstate-multicall';
|
|
79
|
+
import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
|
|
80
|
+
import {
|
|
81
|
+
PoolConfig,
|
|
82
|
+
CurveV1Data,
|
|
83
|
+
TokenWithReasonableVolume,
|
|
84
|
+
CurveSwapFunctions,
|
|
85
|
+
CurveV1SwapType,
|
|
86
|
+
DirectCurveV1Param,
|
|
87
|
+
DirectCurveV1ParamV6,
|
|
88
|
+
CurveV1DirectSwap,
|
|
89
|
+
} from './types';
|
|
90
|
+
import { erc20Iface } from '../../lib/utils-interfaces';
|
|
91
|
+
import { applyTransferFee } from '../../lib/token-transfer-fee';
|
|
92
|
+
import { DIRECT_METHOD_NAME, DIRECT_METHOD_NAME_V6 } from './constants';
|
|
93
|
+
import { packCurveData } from '../../lib/curve/encoder';
|
|
94
|
+
import { encodeCurveAssets } from './packer';
|
|
95
|
+
import { hexConcat, hexZeroPad, hexlify } from 'ethers/lib/utils';
|
|
96
|
+
|
|
97
|
+
const CURVE_DEFAULT_CHUNKS = 10;
|
|
98
|
+
|
|
99
|
+
const CURVE_LENDING_POOl_GAS = 340 * 1000;
|
|
100
|
+
const CURVE_POOL_GAS = 200 * 1000;
|
|
101
|
+
|
|
102
|
+
const coder = new AbiCoder();
|
|
103
|
+
|
|
104
|
+
export class CurveV1
|
|
105
|
+
extends SimpleExchange
|
|
106
|
+
implements IDex<CurveV1Data, DirectCurveV1Param | CurveV1DirectSwap>
|
|
107
|
+
{
|
|
108
|
+
exchangeRouterInterface: Interface;
|
|
109
|
+
minConversionRate = '1';
|
|
110
|
+
|
|
111
|
+
eventPools = new Array<CurvePool | CurveMetapool>();
|
|
112
|
+
public poolInterface: Interface;
|
|
113
|
+
|
|
114
|
+
private logger: Logger;
|
|
115
|
+
|
|
116
|
+
readonly hasConstantPriceLargeAmounts = false;
|
|
117
|
+
readonly isFeeOnTransferSupported = true;
|
|
118
|
+
|
|
119
|
+
private decimalsCoinsAndUnderlying: Record<string, number> = {};
|
|
120
|
+
|
|
121
|
+
protected pools: Record<string, PoolConfig>;
|
|
122
|
+
protected eventSupportedPools: string[];
|
|
123
|
+
protected baseTokens: Record<string, TokenWithReasonableVolume>;
|
|
124
|
+
|
|
125
|
+
protected disableFeeOnTransferTokenAddresses: Set<string>;
|
|
126
|
+
|
|
127
|
+
readonly directSwapIface = new Interface(DirectSwapABI);
|
|
128
|
+
|
|
129
|
+
readonly SRC_TOKEN_DEX_TRANSFERS = 1;
|
|
130
|
+
readonly DEST_TOKEN_DEX_TRANSFERS = 1;
|
|
131
|
+
|
|
132
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
133
|
+
getDexKeysWithNetwork(CurveV1Config);
|
|
134
|
+
|
|
135
|
+
constructor(
|
|
136
|
+
protected network: Network,
|
|
137
|
+
dexKey: string,
|
|
138
|
+
protected dexHelper: IDexHelper,
|
|
139
|
+
dexConfig = CurveV1Config[dexKey][network],
|
|
140
|
+
|
|
141
|
+
protected adapters = Adapters[network],
|
|
142
|
+
) {
|
|
143
|
+
super(dexHelper, dexKey);
|
|
144
|
+
this.pools = Object.keys(dexConfig.pools).reduce<
|
|
145
|
+
Record<string, PoolConfig>
|
|
146
|
+
>((acc, key) => {
|
|
147
|
+
const poolConf = dexConfig.pools[key];
|
|
148
|
+
|
|
149
|
+
acc[key] = {
|
|
150
|
+
underlying: poolConf.underlying.map(t => t.toLowerCase()),
|
|
151
|
+
coins: poolConf.coins.map(t => t.toLowerCase()),
|
|
152
|
+
address: poolConf.address.toLowerCase(),
|
|
153
|
+
name: poolConf.name,
|
|
154
|
+
type: poolConf.type,
|
|
155
|
+
version: poolConf.version,
|
|
156
|
+
isLending: poolConf.isLending,
|
|
157
|
+
isMetapool: poolConf.isMetapool,
|
|
158
|
+
baseToken: poolConf.baseToken
|
|
159
|
+
? poolConf.baseToken.toLowerCase()
|
|
160
|
+
: poolConf.baseToken,
|
|
161
|
+
liquidityUSD: poolConf.liquidityUSD,
|
|
162
|
+
trackCoins: poolConf.trackCoins,
|
|
163
|
+
useLending: poolConf.useLending,
|
|
164
|
+
precisionMul: poolConf.precisionMul,
|
|
165
|
+
tokenAddress: poolConf.tokenAddress
|
|
166
|
+
? poolConf.tokenAddress.toLowerCase()
|
|
167
|
+
: undefined,
|
|
168
|
+
isFeeOnTransferSupported: poolConf.isFeeOnTransferSupported,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return acc;
|
|
172
|
+
}, {});
|
|
173
|
+
|
|
174
|
+
this.eventSupportedPools = dexConfig.eventSupportedPools.map(addr =>
|
|
175
|
+
addr.toLowerCase(),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
this.baseTokens = Object.keys(dexConfig.baseTokens).reduce<
|
|
179
|
+
Record<string, TokenWithReasonableVolume>
|
|
180
|
+
>((acc, key) => {
|
|
181
|
+
const baseToken = dexConfig.baseTokens[key];
|
|
182
|
+
acc[key.toLowerCase()] = {
|
|
183
|
+
address: baseToken.address.toLowerCase(),
|
|
184
|
+
decimals: baseToken.decimals,
|
|
185
|
+
reasonableVolume: baseToken.reasonableVolume,
|
|
186
|
+
};
|
|
187
|
+
return acc;
|
|
188
|
+
}, {});
|
|
189
|
+
|
|
190
|
+
this.disableFeeOnTransferTokenAddresses =
|
|
191
|
+
dexConfig.disableFeeOnTransferTokenAddresses
|
|
192
|
+
? new Set<string>(
|
|
193
|
+
Array.from(
|
|
194
|
+
dexConfig.disableFeeOnTransferTokenAddresses.values(),
|
|
195
|
+
).map(addr => addr.toLowerCase()),
|
|
196
|
+
)
|
|
197
|
+
: new Set<string>();
|
|
198
|
+
|
|
199
|
+
this.exchangeRouterInterface = new Interface(CurveABI as JsonFragment[]);
|
|
200
|
+
|
|
201
|
+
this.logger = dexHelper.getLogger(dexKey);
|
|
202
|
+
|
|
203
|
+
this.poolInterface = new Interface(StableSwapBBTC as any);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected getPoolByAddress(address: Address) {
|
|
207
|
+
return (
|
|
208
|
+
this.eventPools.find(
|
|
209
|
+
p => p.address.toLowerCase() == address.toLowerCase(),
|
|
210
|
+
) || null
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
protected getPoolConfigByAddress(address: Address) {
|
|
215
|
+
return Object.values(this.pools).find(p => p.address == address);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getPoolConfig(from: Token, to: Token): PoolConfig | undefined {
|
|
219
|
+
return this.getPoolConfigs(from, to)[0];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getPoolConfigs(from: Token, to: Token) {
|
|
223
|
+
const fromAddress = from.address.toLowerCase();
|
|
224
|
+
const toAddress = to.address.toLowerCase();
|
|
225
|
+
return Object.values(this.pools).filter(config => {
|
|
226
|
+
const underlying = config.underlying.map((add: string) =>
|
|
227
|
+
add.toLowerCase(),
|
|
228
|
+
);
|
|
229
|
+
const coins = config.coins.map((add: string) => add.toLowerCase());
|
|
230
|
+
|
|
231
|
+
// Case 0: If the swap is between the underlying first token and any underlying token
|
|
232
|
+
if (
|
|
233
|
+
underlying.length &&
|
|
234
|
+
((underlying[0] === fromAddress && underlying.includes(toAddress)) ||
|
|
235
|
+
(underlying[0] === toAddress && underlying.includes(fromAddress)))
|
|
236
|
+
)
|
|
237
|
+
return true;
|
|
238
|
+
// Case 1: If it not a metapool and the swap is between any underlying tokens
|
|
239
|
+
if (
|
|
240
|
+
config.isLending &&
|
|
241
|
+
underlying.includes(toAddress) &&
|
|
242
|
+
underlying.includes(fromAddress)
|
|
243
|
+
)
|
|
244
|
+
return true;
|
|
245
|
+
|
|
246
|
+
// Case 2: If it a swap between any direct tokens
|
|
247
|
+
if (coins.includes(toAddress) && coins.includes(fromAddress)) return true;
|
|
248
|
+
|
|
249
|
+
return false;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async getPoolIdentifiers(
|
|
254
|
+
_from: Token,
|
|
255
|
+
_to: Token,
|
|
256
|
+
side: SwapSide,
|
|
257
|
+
blockNumber: number,
|
|
258
|
+
): Promise<string[]> {
|
|
259
|
+
const pools = this.getPoolConfigs(_from, _to);
|
|
260
|
+
return pools.map(pool => this.getPoolIdentifier(pool.name));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getContractAddress(srcToken: Token, to: Token) {
|
|
264
|
+
const pool = this.getPoolConfig(srcToken, to);
|
|
265
|
+
return pool && pool.address;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
poolConfigsByAddress() {
|
|
269
|
+
return Object.keys(this.pools).reduce((acc: any, poolName: string) => {
|
|
270
|
+
acc[this.pools[poolName].address.toLowerCase()] = this.pools[poolName];
|
|
271
|
+
return acc;
|
|
272
|
+
}, {});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// TODO: revisit to verify the chunks for BTC pools vs USD pools
|
|
276
|
+
getOnChainChunks(
|
|
277
|
+
from: Token,
|
|
278
|
+
to: Token,
|
|
279
|
+
srcAmount: bigint,
|
|
280
|
+
defaultChunks: number,
|
|
281
|
+
) {
|
|
282
|
+
return CURVE_DEFAULT_CHUNKS;
|
|
283
|
+
//return srcAmount >= 10000n * getBigIntPow(from.decimals)
|
|
284
|
+
// ? defaultChunks
|
|
285
|
+
// : CURVE_DEFAULT_CHUNKS;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getEventPoolInstance(poolAddress: Address): CurvePool | CurveMetapool | null {
|
|
289
|
+
switch (poolAddress.toLowerCase()) {
|
|
290
|
+
case ThreePoolAddress:
|
|
291
|
+
return new ThreePool(this.dexKey, this.dexHelper);
|
|
292
|
+
case SUSDPoolAddress:
|
|
293
|
+
return new SUSDPool(this.dexKey, this.dexHelper);
|
|
294
|
+
case HBTCPoolAddress:
|
|
295
|
+
return new HBTCPool(this.dexKey, this.dexHelper);
|
|
296
|
+
case RenPoolAddress:
|
|
297
|
+
return new RenPool(this.dexKey, this.dexHelper);
|
|
298
|
+
case SBTCPoolAddress:
|
|
299
|
+
return new SBTCPool(this.dexKey, this.dexHelper);
|
|
300
|
+
case SETHPoolAddress:
|
|
301
|
+
return new SETHPool(this.dexKey, this.dexHelper);
|
|
302
|
+
case STETHPoolAddress:
|
|
303
|
+
return new STETHPool(this.dexKey, this.dexHelper);
|
|
304
|
+
case EURSPoolAddress:
|
|
305
|
+
return new EURSPool(this.dexKey, this.dexHelper);
|
|
306
|
+
case DUSDPoolAddress:
|
|
307
|
+
return new DUSDPool(this.dexKey, this.dexHelper);
|
|
308
|
+
case BBTCPoolAddress:
|
|
309
|
+
return new BBTCPool(this.dexKey, this.dexHelper);
|
|
310
|
+
case GUSDPoolAddress:
|
|
311
|
+
return new GUSDPool(this.dexKey, this.dexHelper);
|
|
312
|
+
case HUSDPoolAddress:
|
|
313
|
+
return new HUSDPool(this.dexKey, this.dexHelper);
|
|
314
|
+
case LinkUSDPoolAddress:
|
|
315
|
+
return new LinkUSDPool(this.dexKey, this.dexHelper);
|
|
316
|
+
case MUSDPoolAddress:
|
|
317
|
+
return new MUSDPool(this.dexKey, this.dexHelper);
|
|
318
|
+
case OBTCPoolAddress:
|
|
319
|
+
return new OBTCPool(this.dexKey, this.dexHelper);
|
|
320
|
+
case PBTCPoolAddress:
|
|
321
|
+
return new PBTCPool(this.dexKey, this.dexHelper);
|
|
322
|
+
case RSVPoolAddress:
|
|
323
|
+
return new RSVPool(this.dexKey, this.dexHelper);
|
|
324
|
+
case TBTCPoolAddress:
|
|
325
|
+
return new TBTCPool(this.dexKey, this.dexHelper);
|
|
326
|
+
case USDKPoolAddress:
|
|
327
|
+
return new USDKPool(this.dexKey, this.dexHelper);
|
|
328
|
+
case USTPoolAddress:
|
|
329
|
+
return new USTPool(this.dexKey, this.dexHelper);
|
|
330
|
+
case SLINKPoolAddress:
|
|
331
|
+
return new SLINKPool(this.dexKey, this.dexHelper);
|
|
332
|
+
default:
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Makes sure all the pools from poolAddress are being tracked. If a pool in not tracked yet
|
|
338
|
+
// fetch the state and setup the pool, if they are being watched but don't have a valid state
|
|
339
|
+
// add it to the list of unavailable pools. Return the list of unavailable pools.
|
|
340
|
+
async batchCatchUpPools(
|
|
341
|
+
poolAddresses: string[],
|
|
342
|
+
blockNumber: number,
|
|
343
|
+
): Promise<string[]> {
|
|
344
|
+
const unavailablePools = new Array<string>();
|
|
345
|
+
if (!blockNumber) return unavailablePools;
|
|
346
|
+
let poolsToFetch: (CurvePool | CurveMetapool)[] = [];
|
|
347
|
+
for (const poolAddress of poolAddresses) {
|
|
348
|
+
// Check if the pool is already tracked
|
|
349
|
+
let pool = this.getPoolByAddress(poolAddress);
|
|
350
|
+
// If the pool is not stracked add it to the list of pools to fetch
|
|
351
|
+
if (!pool) {
|
|
352
|
+
let newPool = this.getEventPoolInstance(poolAddress);
|
|
353
|
+
if (!newPool)
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Error_${this.dexKey} requested unsupported event pool with address ${poolAddress}`,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const addressesSubscribed = newPool.addressesSubscribed.map(address =>
|
|
359
|
+
address.toLowerCase(),
|
|
360
|
+
);
|
|
361
|
+
if (!addressesSubscribed.includes(poolAddress)) {
|
|
362
|
+
newPool.addressesSubscribed.push(poolAddress);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await newPool.initialize(blockNumber);
|
|
366
|
+
poolsToFetch.push(newPool);
|
|
367
|
+
} else if (!pool.getState(blockNumber)) {
|
|
368
|
+
unavailablePools.push(poolAddress);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!poolsToFetch.length) return unavailablePools;
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const poolStates = await getManyPoolStates(
|
|
376
|
+
poolsToFetch,
|
|
377
|
+
this.dexHelper.multiContract,
|
|
378
|
+
blockNumber,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < poolsToFetch.length; i++) {
|
|
382
|
+
await poolsToFetch[i].setup(blockNumber, poolStates[i] as any);
|
|
383
|
+
this.eventPools.push(poolsToFetch[i]);
|
|
384
|
+
}
|
|
385
|
+
return unavailablePools;
|
|
386
|
+
} catch (e) {
|
|
387
|
+
this.logger.error(`Error_batchCatchUpPools`, e);
|
|
388
|
+
throw e;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async getRatesEventPools(
|
|
393
|
+
fromToken: Token,
|
|
394
|
+
toToken: Token,
|
|
395
|
+
amounts: BigNumber[],
|
|
396
|
+
fromBigNumbers: number,
|
|
397
|
+
pools: string[],
|
|
398
|
+
indexes: [number, number, number][],
|
|
399
|
+
blockNumber: number,
|
|
400
|
+
) {
|
|
401
|
+
const unavailablePools = await this.batchCatchUpPools(pools, blockNumber);
|
|
402
|
+
|
|
403
|
+
if (unavailablePools.length) {
|
|
404
|
+
this.logger.error(
|
|
405
|
+
`Couldn't use events for ${unavailablePools.length} ${this.dexKey} pools!`,
|
|
406
|
+
);
|
|
407
|
+
pools = pools.filter(p => !unavailablePools.includes(p));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let rates = new Array(pools.length);
|
|
411
|
+
for (let i = 0; i < pools.length; i++) {
|
|
412
|
+
rates[i] = {
|
|
413
|
+
exchange: pools[i],
|
|
414
|
+
rates: this.getRatesEventPool(
|
|
415
|
+
pools[i],
|
|
416
|
+
indexes[i],
|
|
417
|
+
amounts,
|
|
418
|
+
fromBigNumbers,
|
|
419
|
+
blockNumber,
|
|
420
|
+
),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return rates;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async getRatesOnChain(
|
|
427
|
+
fromToken: Token,
|
|
428
|
+
toToken: Token,
|
|
429
|
+
amounts: bigint[],
|
|
430
|
+
pools: string[],
|
|
431
|
+
indexes: [number, number, number][],
|
|
432
|
+
blockNumber: number,
|
|
433
|
+
) {
|
|
434
|
+
const chunks = amounts.length - 1;
|
|
435
|
+
const srcAmount = amounts[chunks]!;
|
|
436
|
+
|
|
437
|
+
// The number of chunks used onchain to minimize the full node calls
|
|
438
|
+
const onChainChunks = this.getOnChainChunks(
|
|
439
|
+
fromToken,
|
|
440
|
+
toToken,
|
|
441
|
+
srcAmount,
|
|
442
|
+
chunks,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const _width = Math.floor(chunks / onChainChunks);
|
|
446
|
+
|
|
447
|
+
const unitVolume = amounts[0];
|
|
448
|
+
amounts[0] = 0n;
|
|
449
|
+
|
|
450
|
+
const _amounts = [unitVolume].concat(
|
|
451
|
+
Array.from(Array(onChainChunks).keys()).map(
|
|
452
|
+
i => amounts[(i + 1) * _width],
|
|
453
|
+
),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const calldata = pools.map((p, i) =>
|
|
457
|
+
_amounts.map(_amount => ({
|
|
458
|
+
target: p,
|
|
459
|
+
callData: this.poolInterface.encodeFunctionData(
|
|
460
|
+
indexes[i][2] === 1 ? 'get_dy_underlying' : 'get_dy',
|
|
461
|
+
[indexes[i][0], indexes[i][1], _amount],
|
|
462
|
+
),
|
|
463
|
+
})),
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
//const data = await this.multi.methods
|
|
467
|
+
// .tryAggregate(false, calldata.flat())
|
|
468
|
+
// .call({}, 'latest');
|
|
469
|
+
const data = (
|
|
470
|
+
await Promise.all(
|
|
471
|
+
calldata.map(async poolCalldata => {
|
|
472
|
+
try {
|
|
473
|
+
const result = await Utils.timeoutPromise<MultiResult<any>[]>(
|
|
474
|
+
this.dexHelper.multiContract.methods
|
|
475
|
+
.tryAggregate(false, poolCalldata)
|
|
476
|
+
.call({}, 'latest'),
|
|
477
|
+
2000,
|
|
478
|
+
`Timed out multicall for curve pool ${poolCalldata[0].target}`,
|
|
479
|
+
);
|
|
480
|
+
return result;
|
|
481
|
+
} catch (e) {
|
|
482
|
+
this.logger.error(e);
|
|
483
|
+
return Array<MultiResult<any>>(poolCalldata.length).fill({
|
|
484
|
+
success: false,
|
|
485
|
+
returnData: '0x',
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}),
|
|
489
|
+
)
|
|
490
|
+
).flat();
|
|
491
|
+
|
|
492
|
+
// Convert the on chain rates to the requested chunks, using simple interpolation
|
|
493
|
+
const correctChunks = (_r: bigint[]): bigint[] => [
|
|
494
|
+
_r[0],
|
|
495
|
+
...interpolate(
|
|
496
|
+
_amounts.slice(1),
|
|
497
|
+
_r.slice(1),
|
|
498
|
+
amounts.slice(1),
|
|
499
|
+
SwapSide.SELL,
|
|
500
|
+
),
|
|
501
|
+
];
|
|
502
|
+
|
|
503
|
+
const decode = (j: number) => {
|
|
504
|
+
if (!data[j].success) return 0n;
|
|
505
|
+
const decoded = coder.decode(['uint256'], data[j].returnData);
|
|
506
|
+
return BigInt(decoded[0].toString());
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
let i = 0;
|
|
510
|
+
// chunks !== onChainChunks || !isAmountsLinear
|
|
511
|
+
const ratesAllPools = pools.map(p => {
|
|
512
|
+
const rates = _amounts.map(a => decode(i++));
|
|
513
|
+
return {
|
|
514
|
+
rates: correctChunks(rates),
|
|
515
|
+
exchange: p,
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
return ratesAllPools;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
protected noMorePrice(prices: BigNumber[], index: number): boolean {
|
|
523
|
+
return index > 1 && prices[index - 1].eq(BN_0);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
getRatesEventPool(
|
|
527
|
+
exchange: string,
|
|
528
|
+
curveIndexes: number[],
|
|
529
|
+
amounts: BigNumber[],
|
|
530
|
+
fromBigNumbers: number,
|
|
531
|
+
blockNumber: number,
|
|
532
|
+
) {
|
|
533
|
+
let rates = new Array<BigNumber>(amounts.length).fill(BN_0);
|
|
534
|
+
|
|
535
|
+
let fromIndex = curveIndexes[0];
|
|
536
|
+
let toIndex = curveIndexes[1];
|
|
537
|
+
let isTokenSwap = curveIndexes[2] === 1;
|
|
538
|
+
|
|
539
|
+
let pool = this.getPoolByAddress(exchange);
|
|
540
|
+
if (!pool) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Error_${this.dexKey}_getRatesEvent pool is not initialised`,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const state = pool.getState(blockNumber);
|
|
547
|
+
if (!state)
|
|
548
|
+
throw new Error(
|
|
549
|
+
`Error_${this.dexKey}_${pool.name} expected state at blocknumber ${blockNumber}, got none`,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
if (isTokenSwap) {
|
|
553
|
+
rates[0] = pool.get_dy_underlying(
|
|
554
|
+
fromIndex,
|
|
555
|
+
toIndex,
|
|
556
|
+
amounts[0],
|
|
557
|
+
state as any,
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
for (let i = 1; i < amounts.length; i++) {
|
|
561
|
+
rates[i] = this.noMorePrice(rates, i)
|
|
562
|
+
? BN_0
|
|
563
|
+
: pool.get_dy_underlying(
|
|
564
|
+
fromIndex,
|
|
565
|
+
toIndex,
|
|
566
|
+
amounts[i],
|
|
567
|
+
state as any,
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
} else {
|
|
571
|
+
rates[0] = pool.get_dy(fromIndex, toIndex, amounts[0], state as any);
|
|
572
|
+
|
|
573
|
+
for (let i = 1; i < amounts.length; i++) {
|
|
574
|
+
rates[i] = this.noMorePrice(rates, i)
|
|
575
|
+
? BN_0
|
|
576
|
+
: pool.get_dy(fromIndex, toIndex, amounts[i], state as any);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let floatRates = rates.map(rate => BigInt(rate.toFixed(0)));
|
|
581
|
+
return floatRates;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
isEventPoolSupported(poolAddress: Address): Boolean {
|
|
585
|
+
return this.eventSupportedPools.some(p => p === poolAddress.toLowerCase());
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
allocPools(
|
|
589
|
+
from: Token,
|
|
590
|
+
to: Token,
|
|
591
|
+
side: SwapSide,
|
|
592
|
+
routeID: number,
|
|
593
|
+
usedPools: { [poolIdentifier: string]: number },
|
|
594
|
+
isFirstSwap: boolean,
|
|
595
|
+
) {
|
|
596
|
+
if (side === SwapSide.BUY) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
this.getPoolConfigs(from, to).forEach(p => {
|
|
601
|
+
const poolIdentifier = this.getPoolIdentifier(p.name);
|
|
602
|
+
if (!(poolIdentifier in usedPools)) {
|
|
603
|
+
usedPools[poolIdentifier] = routeID;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
public getPoolIdentifier(name: string) {
|
|
609
|
+
return `${this.dexKey}_${name.toLowerCase()}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async getPricesVolume(
|
|
613
|
+
_from: Token,
|
|
614
|
+
_to: Token,
|
|
615
|
+
amounts: bigint[],
|
|
616
|
+
side: SwapSide,
|
|
617
|
+
blockNumber: number,
|
|
618
|
+
// list of pool identifiers to use for pricing, if undefined use all pools
|
|
619
|
+
limitPools?: string[],
|
|
620
|
+
transferFees: TransferFeeParams = {
|
|
621
|
+
srcFee: 0,
|
|
622
|
+
destFee: 0,
|
|
623
|
+
srcDexFee: 0,
|
|
624
|
+
destDexFee: 0,
|
|
625
|
+
},
|
|
626
|
+
): Promise<ExchangePrices<CurveV1Data> | null> {
|
|
627
|
+
try {
|
|
628
|
+
if (side === SwapSide.BUY) {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
_from.address = _from.address.toLowerCase();
|
|
633
|
+
_to.address = _to.address.toLowerCase();
|
|
634
|
+
|
|
635
|
+
const _isSrcTokenTransferFeeToBeExchanged =
|
|
636
|
+
this.disableFeeOnTransferTokenAddresses.has(_from.address)
|
|
637
|
+
? false
|
|
638
|
+
: isSrcTokenTransferFeeToBeExchanged(transferFees);
|
|
639
|
+
|
|
640
|
+
const _isDestTokenTransferFeeToBeExchanged =
|
|
641
|
+
this.disableFeeOnTransferTokenAddresses.has(_to.address)
|
|
642
|
+
? false
|
|
643
|
+
: isDestTokenTransferFeeToBeExchanged(transferFees);
|
|
644
|
+
|
|
645
|
+
// We first filter out pools which were explicitly excluded and pools which are already used
|
|
646
|
+
// then for the good pools we set the boolean to be true for used pools
|
|
647
|
+
// and for each pool we take the address.
|
|
648
|
+
const goodPoolConfigs = this.getPoolConfigs(_from, _to).filter(p => {
|
|
649
|
+
if (limitPools !== undefined) {
|
|
650
|
+
const id = this.getPoolIdentifier(p.name);
|
|
651
|
+
if (!limitPools.includes(id)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (_isSrcTokenTransferFeeToBeExchanged) {
|
|
657
|
+
// Fee on transfers supported only when flag is specified
|
|
658
|
+
return !!p.isFeeOnTransferSupported;
|
|
659
|
+
}
|
|
660
|
+
return true;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
const goodPoolAddress = goodPoolConfigs.map(p => p.address);
|
|
664
|
+
const indexesByAddress = goodPoolConfigs.reduce(
|
|
665
|
+
(acc: { [address: string]: [number, number, number] }, pc) => {
|
|
666
|
+
acc[pc.address] = this.getSwapIndexes(_from, _to, pc);
|
|
667
|
+
return acc;
|
|
668
|
+
},
|
|
669
|
+
{},
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
if (goodPoolAddress.length === 0) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const amountsWithUnit = [
|
|
677
|
+
getBigIntPow(_from.decimals),
|
|
678
|
+
...amounts.slice(1),
|
|
679
|
+
];
|
|
680
|
+
const amountsWithUnitAndFee = _isSrcTokenTransferFeeToBeExchanged
|
|
681
|
+
? applyTransferFee(
|
|
682
|
+
applyTransferFee(
|
|
683
|
+
amountsWithUnit,
|
|
684
|
+
side,
|
|
685
|
+
transferFees.srcFee,
|
|
686
|
+
SRC_TOKEN_PARASWAP_TRANSFERS,
|
|
687
|
+
),
|
|
688
|
+
side,
|
|
689
|
+
transferFees.srcDexFee,
|
|
690
|
+
this.SRC_TOKEN_DEX_TRANSFERS,
|
|
691
|
+
)
|
|
692
|
+
: amountsWithUnit;
|
|
693
|
+
|
|
694
|
+
let _prices = new Array();
|
|
695
|
+
|
|
696
|
+
const eventPools = goodPoolAddress.filter(p =>
|
|
697
|
+
this.isEventPoolSupported(p),
|
|
698
|
+
);
|
|
699
|
+
const eventPoolIndexes = eventPools.map(ep => indexesByAddress[ep]);
|
|
700
|
+
if (eventPools) {
|
|
701
|
+
const pricesEvents = await this.getRatesEventPools(
|
|
702
|
+
_from,
|
|
703
|
+
_to,
|
|
704
|
+
amountsWithUnitAndFee.map(a => new BigNumber(a.toString())),
|
|
705
|
+
_from.decimals,
|
|
706
|
+
eventPools,
|
|
707
|
+
eventPoolIndexes,
|
|
708
|
+
blockNumber,
|
|
709
|
+
);
|
|
710
|
+
_prices = _prices.concat(pricesEvents);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// if the pool rates was not available in the event pool then fallback to the onchain pool query
|
|
714
|
+
const isPoolNotInPrices = (poolAddress: Address) =>
|
|
715
|
+
!_prices.find(
|
|
716
|
+
existingPrice =>
|
|
717
|
+
existingPrice.exchange.toLowerCase() === poolAddress.toLowerCase(),
|
|
718
|
+
);
|
|
719
|
+
const onChainPools = goodPoolAddress.filter(isPoolNotInPrices);
|
|
720
|
+
|
|
721
|
+
const onChainPoolIndexes = onChainPools.map(op => indexesByAddress[op]);
|
|
722
|
+
|
|
723
|
+
// onChainPools are only supported for pools with linear price chunks
|
|
724
|
+
if (onChainPools.length) {
|
|
725
|
+
const pricesOnChain = await this.getRatesOnChain(
|
|
726
|
+
_from,
|
|
727
|
+
_to,
|
|
728
|
+
amountsWithUnitAndFee,
|
|
729
|
+
onChainPools,
|
|
730
|
+
onChainPoolIndexes,
|
|
731
|
+
blockNumber,
|
|
732
|
+
);
|
|
733
|
+
_prices = _prices.concat(pricesOnChain);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const poolConfigsByAddress = this.poolConfigsByAddress();
|
|
737
|
+
|
|
738
|
+
const poolPrices = _prices.reduce(
|
|
739
|
+
(acc: PoolPrices<CurveV1Data>[], _price: any, idx: number) => {
|
|
740
|
+
if (
|
|
741
|
+
_price.exchange === NULL_ADDRESS ||
|
|
742
|
+
!(_price.exchange.toLowerCase() in poolConfigsByAddress)
|
|
743
|
+
)
|
|
744
|
+
return acc;
|
|
745
|
+
|
|
746
|
+
const poolConfig =
|
|
747
|
+
poolConfigsByAddress[_price.exchange.toLowerCase()];
|
|
748
|
+
const indexes = this.getSwapIndexes(_from, _to, poolConfig);
|
|
749
|
+
const [i, j, swapType] = indexes;
|
|
750
|
+
|
|
751
|
+
if (_isDestTokenTransferFeeToBeExchanged) {
|
|
752
|
+
_price.rates = applyTransferFee(
|
|
753
|
+
_price.rates,
|
|
754
|
+
side,
|
|
755
|
+
transferFees.destDexFee,
|
|
756
|
+
this.DEST_TOKEN_DEX_TRANSFERS,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
acc.push({
|
|
761
|
+
prices: [0n, ..._price.rates.slice(1)],
|
|
762
|
+
unit: _price.rates[0],
|
|
763
|
+
data: {
|
|
764
|
+
exchange: _price.exchange,
|
|
765
|
+
i,
|
|
766
|
+
j,
|
|
767
|
+
underlyingSwap: swapType === 1,
|
|
768
|
+
deadline: 0,
|
|
769
|
+
},
|
|
770
|
+
exchange: this.dexKey,
|
|
771
|
+
poolIdentifier: `${this.dexKey}_${poolConfig.name.toLowerCase()}`,
|
|
772
|
+
gasCost: poolConfig.isLending
|
|
773
|
+
? CURVE_LENDING_POOl_GAS
|
|
774
|
+
: CURVE_POOL_GAS,
|
|
775
|
+
poolAddresses: [_price.exchange],
|
|
776
|
+
});
|
|
777
|
+
return acc;
|
|
778
|
+
},
|
|
779
|
+
[],
|
|
780
|
+
);
|
|
781
|
+
return poolPrices;
|
|
782
|
+
} catch (e) {
|
|
783
|
+
if (blockNumber === 0)
|
|
784
|
+
this.logger.error(
|
|
785
|
+
`Error_${this.dexKey}_getPrices: Aurelius block manager not yet instantiated`,
|
|
786
|
+
);
|
|
787
|
+
this.logger.error(`Error_${this.dexKey}_getPrices`, e);
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
getSwapIndexes(
|
|
793
|
+
from: Token,
|
|
794
|
+
to: Token,
|
|
795
|
+
pool?: PoolConfig,
|
|
796
|
+
): [number, number, number] {
|
|
797
|
+
pool = pool || this.getPoolConfig(from, to);
|
|
798
|
+
if (!pool) {
|
|
799
|
+
return [-1, -1, -1];
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const fromAddress = from.address.toLowerCase();
|
|
803
|
+
const toAddress = to.address.toLowerCase();
|
|
804
|
+
const isUnderlyingSwap =
|
|
805
|
+
pool.underlying.length &&
|
|
806
|
+
pool.underlying.some(t => t.toLowerCase() === fromAddress) &&
|
|
807
|
+
pool.underlying.some(t => t.toLowerCase() === toAddress);
|
|
808
|
+
|
|
809
|
+
const fromIndex = isUnderlyingSwap
|
|
810
|
+
? pool.underlying.findIndex(t => t.toLowerCase() === fromAddress)
|
|
811
|
+
: pool.coins.findIndex(t => t.toLowerCase() === fromAddress);
|
|
812
|
+
|
|
813
|
+
const toIndex = isUnderlyingSwap
|
|
814
|
+
? pool.underlying.findIndex(t => t.toLowerCase() === toAddress)
|
|
815
|
+
: pool.coins.findIndex(t => t.toLowerCase() === toAddress);
|
|
816
|
+
|
|
817
|
+
const swapType = isUnderlyingSwap ? 1 : 2;
|
|
818
|
+
|
|
819
|
+
return [fromIndex, toIndex, swapType];
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
getAdapterParam(
|
|
823
|
+
srcToken: string,
|
|
824
|
+
destToken: string,
|
|
825
|
+
srcAmount: string,
|
|
826
|
+
destAmount: string,
|
|
827
|
+
data: CurveV1Data,
|
|
828
|
+
side: SwapSide,
|
|
829
|
+
): AdapterExchangeParam {
|
|
830
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
831
|
+
|
|
832
|
+
const { i, j, deadline, underlyingSwap } = data;
|
|
833
|
+
const payload = this.abiCoder.encodeParameter(
|
|
834
|
+
{
|
|
835
|
+
ParentStruct: {
|
|
836
|
+
i: 'int128',
|
|
837
|
+
j: 'int128',
|
|
838
|
+
deadline: 'uint256',
|
|
839
|
+
underlyingSwap: 'bool',
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
{ i, j, deadline, underlyingSwap },
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
targetExchange: data.exchange,
|
|
847
|
+
payload,
|
|
848
|
+
networkFee: '0',
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
getCalldataGasCost(poolPrices: PoolPrices<CurveV1Data>): number | number[] {
|
|
853
|
+
return (
|
|
854
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
855
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
856
|
+
CALLDATA_GAS_COST.INDEX +
|
|
857
|
+
CALLDATA_GAS_COST.INDEX +
|
|
858
|
+
CALLDATA_GAS_COST.TIMESTAMP +
|
|
859
|
+
CALLDATA_GAS_COST.BOOL
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
864
|
+
return this.adapters[side];
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
static getDirectFunctionName(): string[] {
|
|
868
|
+
return [DIRECT_METHOD_NAME];
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
getTokenFromAddress(address: Address): Token {
|
|
872
|
+
// In this Dex decimals are not used
|
|
873
|
+
return { address, decimals: 0 };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
async preProcessTransaction(
|
|
877
|
+
optimalSwapExchange: OptimalSwapExchange<CurveV1Data>,
|
|
878
|
+
srcToken: Token,
|
|
879
|
+
_0: Token,
|
|
880
|
+
_1: SwapSide,
|
|
881
|
+
options: PreprocessTransactionOptions,
|
|
882
|
+
): Promise<[OptimalSwapExchange<CurveV1Data>, ExchangeTxInfo]> {
|
|
883
|
+
if (!options.isDirectMethod) {
|
|
884
|
+
return [
|
|
885
|
+
optimalSwapExchange,
|
|
886
|
+
{
|
|
887
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
888
|
+
},
|
|
889
|
+
];
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
assert(
|
|
893
|
+
optimalSwapExchange.data !== undefined,
|
|
894
|
+
`preProcessTransaction: data field is missing`,
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
let isApproved: boolean | undefined;
|
|
898
|
+
|
|
899
|
+
try {
|
|
900
|
+
isApproved = await this.dexHelper.augustusApprovals.hasApproval(
|
|
901
|
+
options.executionContractAddress,
|
|
902
|
+
this.dexHelper.config.wrapETH(srcToken).address,
|
|
903
|
+
optimalSwapExchange.data.exchange,
|
|
904
|
+
);
|
|
905
|
+
} catch (e) {
|
|
906
|
+
this.logger.error(
|
|
907
|
+
`preProcessTransaction failed to retrieve allowance info: `,
|
|
908
|
+
e,
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return [
|
|
913
|
+
{
|
|
914
|
+
...optimalSwapExchange,
|
|
915
|
+
data: {
|
|
916
|
+
...optimalSwapExchange.data,
|
|
917
|
+
isApproved,
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()),
|
|
922
|
+
},
|
|
923
|
+
];
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
getDirectParam(
|
|
927
|
+
srcToken: Address,
|
|
928
|
+
destToken: Address,
|
|
929
|
+
srcAmount: NumberAsString,
|
|
930
|
+
destAmount: NumberAsString,
|
|
931
|
+
expectedAmount: NumberAsString,
|
|
932
|
+
data: CurveV1Data,
|
|
933
|
+
side: SwapSide,
|
|
934
|
+
permit: string,
|
|
935
|
+
uuid: string,
|
|
936
|
+
feePercent: NumberAsString,
|
|
937
|
+
deadline: NumberAsString,
|
|
938
|
+
partner: string,
|
|
939
|
+
beneficiary: string,
|
|
940
|
+
contractMethod?: string,
|
|
941
|
+
): TxInfo<DirectCurveV1Param> {
|
|
942
|
+
if (contractMethod !== DIRECT_METHOD_NAME) {
|
|
943
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
944
|
+
}
|
|
945
|
+
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
946
|
+
|
|
947
|
+
let isApproved: boolean = !!data.isApproved;
|
|
948
|
+
if (data.isApproved === undefined) {
|
|
949
|
+
this.logger.warn(`isApproved is undefined, defaulting to false`);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const swapParams: DirectCurveV1Param = [
|
|
953
|
+
srcToken,
|
|
954
|
+
destToken,
|
|
955
|
+
data.exchange,
|
|
956
|
+
srcAmount,
|
|
957
|
+
destAmount,
|
|
958
|
+
expectedAmount,
|
|
959
|
+
feePercent,
|
|
960
|
+
data.i.toString(),
|
|
961
|
+
data.j.toString(),
|
|
962
|
+
partner,
|
|
963
|
+
isApproved,
|
|
964
|
+
data.underlyingSwap
|
|
965
|
+
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
966
|
+
: CurveV1SwapType.EXCHANGE,
|
|
967
|
+
beneficiary,
|
|
968
|
+
// For CurveV1 we work as it is, without wrapping and unwrapping
|
|
969
|
+
false,
|
|
970
|
+
permit,
|
|
971
|
+
uuidToBytes16(uuid),
|
|
972
|
+
];
|
|
973
|
+
|
|
974
|
+
const encoder = (...params: DirectCurveV1Param) => {
|
|
975
|
+
return this.directSwapIface.encodeFunctionData(DIRECT_METHOD_NAME, [
|
|
976
|
+
params,
|
|
977
|
+
]);
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
return {
|
|
981
|
+
params: swapParams,
|
|
982
|
+
encoder,
|
|
983
|
+
networkFee: '0',
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
getDirectParamV6(
|
|
988
|
+
srcToken: Address,
|
|
989
|
+
destToken: Address,
|
|
990
|
+
fromAmount: NumberAsString,
|
|
991
|
+
toAmount: NumberAsString,
|
|
992
|
+
quotedAmount: NumberAsString,
|
|
993
|
+
data: CurveV1Data,
|
|
994
|
+
side: SwapSide,
|
|
995
|
+
permit: string,
|
|
996
|
+
uuid: string,
|
|
997
|
+
partnerAndFee: string,
|
|
998
|
+
beneficiary: string,
|
|
999
|
+
blockNumber: number,
|
|
1000
|
+
contractMethod?: string,
|
|
1001
|
+
) {
|
|
1002
|
+
if (contractMethod !== DIRECT_METHOD_NAME_V6) {
|
|
1003
|
+
throw new Error(`Invalid contract method ${contractMethod}`);
|
|
1004
|
+
}
|
|
1005
|
+
assert(side === SwapSide.SELL, 'Buy not supported');
|
|
1006
|
+
|
|
1007
|
+
const metadata = hexConcat([
|
|
1008
|
+
hexZeroPad(uuidToBytes16(uuid), 16),
|
|
1009
|
+
hexZeroPad(hexlify(blockNumber), 16),
|
|
1010
|
+
]);
|
|
1011
|
+
|
|
1012
|
+
const swapParams: DirectCurveV1ParamV6 = [
|
|
1013
|
+
packCurveData(
|
|
1014
|
+
data.exchange,
|
|
1015
|
+
!data.isApproved, // approve flag, if not approved then set to true
|
|
1016
|
+
isETHAddress(destToken) ? 0 : isETHAddress(srcToken) ? 3 : 0,
|
|
1017
|
+
data.underlyingSwap
|
|
1018
|
+
? CurveV1SwapType.EXCHANGE_UNDERLYING
|
|
1019
|
+
: CurveV1SwapType.EXCHANGE,
|
|
1020
|
+
).toString(),
|
|
1021
|
+
encodeCurveAssets(data.i, data.j).toString(),
|
|
1022
|
+
srcToken,
|
|
1023
|
+
destToken,
|
|
1024
|
+
fromAmount,
|
|
1025
|
+
toAmount,
|
|
1026
|
+
quotedAmount,
|
|
1027
|
+
metadata,
|
|
1028
|
+
beneficiary,
|
|
1029
|
+
];
|
|
1030
|
+
|
|
1031
|
+
const encodeParams: CurveV1DirectSwap = [swapParams, partnerAndFee, permit];
|
|
1032
|
+
|
|
1033
|
+
const encoder = (...params: CurveV1DirectSwap) => {
|
|
1034
|
+
return this.augustusV6Interface.encodeFunctionData(
|
|
1035
|
+
DIRECT_METHOD_NAME_V6,
|
|
1036
|
+
[...params],
|
|
1037
|
+
);
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
encoder,
|
|
1042
|
+
params: encodeParams,
|
|
1043
|
+
networkFee: '0',
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
static getDirectFunctionNameV6(): string[] {
|
|
1048
|
+
return [DIRECT_METHOD_NAME_V6];
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
getDexParam(
|
|
1052
|
+
srcToken: Address,
|
|
1053
|
+
destToken: Address,
|
|
1054
|
+
srcAmount: NumberAsString,
|
|
1055
|
+
destAmount: NumberAsString,
|
|
1056
|
+
recipient: Address,
|
|
1057
|
+
data: CurveV1Data,
|
|
1058
|
+
side: SwapSide,
|
|
1059
|
+
): DexExchangeParam {
|
|
1060
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1061
|
+
|
|
1062
|
+
const { exchange, i, j, underlyingSwap } = data;
|
|
1063
|
+
const defaultArgs = [i, j, srcAmount, this.minConversionRate];
|
|
1064
|
+
const swapMethod = underlyingSwap
|
|
1065
|
+
? CurveSwapFunctions.exchange_underlying
|
|
1066
|
+
: CurveSwapFunctions.exchange;
|
|
1067
|
+
const exchangeData = this.exchangeRouterInterface.encodeFunctionData(
|
|
1068
|
+
swapMethod,
|
|
1069
|
+
defaultArgs,
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
return {
|
|
1073
|
+
exchangeData,
|
|
1074
|
+
needWrapNative: this.needWrapNative,
|
|
1075
|
+
sendEthButSupportsInsertFromAmount: true,
|
|
1076
|
+
dexFuncHasRecipient: false,
|
|
1077
|
+
targetExchange: exchange,
|
|
1078
|
+
returnAmountPos: undefined,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
async getSimpleParam(
|
|
1083
|
+
srcToken: string,
|
|
1084
|
+
destToken: string,
|
|
1085
|
+
srcAmount: string,
|
|
1086
|
+
destAmount: string,
|
|
1087
|
+
data: CurveV1Data,
|
|
1088
|
+
side: SwapSide,
|
|
1089
|
+
): Promise<SimpleExchangeParam> {
|
|
1090
|
+
if (side === SwapSide.BUY) throw new Error(`Buy not supported`);
|
|
1091
|
+
|
|
1092
|
+
const { exchange, i, j, underlyingSwap } = data;
|
|
1093
|
+
const defaultArgs = [i, j, srcAmount, this.minConversionRate];
|
|
1094
|
+
const swapMethod = underlyingSwap
|
|
1095
|
+
? CurveSwapFunctions.exchange_underlying
|
|
1096
|
+
: CurveSwapFunctions.exchange;
|
|
1097
|
+
const swapData = this.exchangeRouterInterface.encodeFunctionData(
|
|
1098
|
+
swapMethod,
|
|
1099
|
+
defaultArgs,
|
|
1100
|
+
);
|
|
1101
|
+
|
|
1102
|
+
return this.buildSimpleParamWithoutWETHConversion(
|
|
1103
|
+
srcToken,
|
|
1104
|
+
srcAmount,
|
|
1105
|
+
destToken,
|
|
1106
|
+
destAmount,
|
|
1107
|
+
swapData,
|
|
1108
|
+
exchange,
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
async updateBaseTokenPrice() {
|
|
1113
|
+
for (let baseToken of Object.values(this.baseTokens)) {
|
|
1114
|
+
const usdPrice = await this.dexHelper.getTokenUSDPrice(
|
|
1115
|
+
baseToken,
|
|
1116
|
+
baseToken.reasonableVolume,
|
|
1117
|
+
);
|
|
1118
|
+
|
|
1119
|
+
const denominator =
|
|
1120
|
+
Number(baseToken.reasonableVolume) / 10 ** baseToken.decimals;
|
|
1121
|
+
|
|
1122
|
+
baseToken.tokenPrice = usdPrice / denominator;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
async fetchDecimals() {
|
|
1127
|
+
const calls = Object.values(this.pools).reduce<MultiCallParams<any>[]>(
|
|
1128
|
+
(acc, pool) => {
|
|
1129
|
+
const underlyingDecimalsCalls = pool.underlying.reduce<
|
|
1130
|
+
MultiCallParams<any>[]
|
|
1131
|
+
>((_acc, _token) => {
|
|
1132
|
+
const token = _token.toLowerCase();
|
|
1133
|
+
if (token === ETHER_ADDRESS) {
|
|
1134
|
+
this.decimalsCoinsAndUnderlying[token] = 18;
|
|
1135
|
+
} else {
|
|
1136
|
+
_acc.push({
|
|
1137
|
+
target: token,
|
|
1138
|
+
callData: erc20Iface.encodeFunctionData('decimals'),
|
|
1139
|
+
decodeFunction: uin128DecodeToInt,
|
|
1140
|
+
cb: (decimals: number) => {
|
|
1141
|
+
this.decimalsCoinsAndUnderlying[token.toLowerCase()] = decimals;
|
|
1142
|
+
},
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
return _acc;
|
|
1147
|
+
}, []);
|
|
1148
|
+
|
|
1149
|
+
const coinsDecimalsCalls = pool.coins.reduce<MultiCallParams<any>[]>(
|
|
1150
|
+
(_acc, _coin) => {
|
|
1151
|
+
const coin = _coin.toLowerCase();
|
|
1152
|
+
if (coin === ETHER_ADDRESS) {
|
|
1153
|
+
this.decimalsCoinsAndUnderlying[coin] = 18;
|
|
1154
|
+
} else {
|
|
1155
|
+
_acc.push({
|
|
1156
|
+
target: coin,
|
|
1157
|
+
callData: erc20Iface.encodeFunctionData('decimals'),
|
|
1158
|
+
decodeFunction: uin128DecodeToInt,
|
|
1159
|
+
cb: (decimals: number) => {
|
|
1160
|
+
this.decimalsCoinsAndUnderlying[coin.toLowerCase()] =
|
|
1161
|
+
decimals;
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
return _acc;
|
|
1166
|
+
},
|
|
1167
|
+
[],
|
|
1168
|
+
);
|
|
1169
|
+
|
|
1170
|
+
acc.push(...underlyingDecimalsCalls, ...coinsDecimalsCalls);
|
|
1171
|
+
return acc;
|
|
1172
|
+
},
|
|
1173
|
+
[],
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
return this.dexHelper.multiWrapper!.tryAggregate(true, calls);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async fetchAllPools() {
|
|
1180
|
+
try {
|
|
1181
|
+
// Hack: We choose to check the balance of the token for the pool instead of calling
|
|
1182
|
+
// the balances array in the pool as it was a mess to have each curve abi
|
|
1183
|
+
// Some have balances[uint128] some have balances[uint256]
|
|
1184
|
+
// One of the consequence for such a hack is below for ETH Pools
|
|
1185
|
+
// TODO: below can be highly optimized
|
|
1186
|
+
// * we don't need to query token decimals every iteration
|
|
1187
|
+
// * we can use the decimals from the tokens list using the api
|
|
1188
|
+
|
|
1189
|
+
const calls = Object.values(this.pools).reduce<MultiCallParams<number>[]>(
|
|
1190
|
+
(acc, pool) => {
|
|
1191
|
+
const coinCalls = pool.coins.map<MultiCallParams<number>>(coin => {
|
|
1192
|
+
const coinLowerCase = coin.toLowerCase();
|
|
1193
|
+
if (coinLowerCase === ETHER_ADDRESS) {
|
|
1194
|
+
return {
|
|
1195
|
+
target:
|
|
1196
|
+
this.dexHelper.config.data.multicallV2Address.toLowerCase(),
|
|
1197
|
+
callData: this.dexHelper.multiContract.methods
|
|
1198
|
+
.getEthBalance(pool.address.toLowerCase())
|
|
1199
|
+
.encodeABI(),
|
|
1200
|
+
decodeFunction: uin256DecodeToFloat,
|
|
1201
|
+
};
|
|
1202
|
+
} else {
|
|
1203
|
+
return {
|
|
1204
|
+
target: coinLowerCase,
|
|
1205
|
+
callData: erc20Iface.encodeFunctionData('balanceOf', [
|
|
1206
|
+
pool.address,
|
|
1207
|
+
]),
|
|
1208
|
+
decodeFunction: uin256DecodeToFloat,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
acc.push(...coinCalls);
|
|
1213
|
+
return acc;
|
|
1214
|
+
},
|
|
1215
|
+
[],
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
const results = await this.dexHelper.multiWrapper!.tryAggregate(
|
|
1219
|
+
true,
|
|
1220
|
+
calls,
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
let index = 0;
|
|
1224
|
+
Object.values(this.pools).forEach(pool => {
|
|
1225
|
+
const balances = results.slice(index, index + pool.coins.length);
|
|
1226
|
+
index += pool.coins.length;
|
|
1227
|
+
|
|
1228
|
+
const sumLiquidity = balances.reduce((acc, balance, i) => {
|
|
1229
|
+
const coin = pool.coins[i].toLowerCase();
|
|
1230
|
+
const decimals = this.decimalsCoinsAndUnderlying[coin];
|
|
1231
|
+
|
|
1232
|
+
return acc + balance.returnData / 10 ** decimals;
|
|
1233
|
+
}, 0);
|
|
1234
|
+
|
|
1235
|
+
let liquidityUSD = 0;
|
|
1236
|
+
if (pool.baseToken) {
|
|
1237
|
+
if (!this.baseTokens[pool.baseToken]) {
|
|
1238
|
+
throw new Error(
|
|
1239
|
+
`missing base token ${pool.baseToken} for ${this.dexKey}`,
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
liquidityUSD =
|
|
1243
|
+
sumLiquidity * Number(this.baseTokens[pool.baseToken].tokenPrice!);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
pool.liquidityUSD = liquidityUSD;
|
|
1247
|
+
});
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
this.logger.error(`fetchAllPools ${this.dexKey}`, e);
|
|
1250
|
+
throw e;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
async updatePoolState(): Promise<void> {
|
|
1255
|
+
if (!Object.keys(this.decimalsCoinsAndUnderlying).length) {
|
|
1256
|
+
await this.fetchDecimals();
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
await this.updateBaseTokenPrice();
|
|
1260
|
+
await this.fetchAllPools();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async getTopPoolsForToken(
|
|
1264
|
+
tokenAddress: Address,
|
|
1265
|
+
limit: number,
|
|
1266
|
+
): Promise<PoolLiquidity[]> {
|
|
1267
|
+
const _tokenAddress = this.dexHelper.config.unwrapETH({
|
|
1268
|
+
address: tokenAddress,
|
|
1269
|
+
decimals: 18,
|
|
1270
|
+
}).address;
|
|
1271
|
+
// In general case a token can be in the coins or underlying
|
|
1272
|
+
// In case of the metapool the token might be in both at the same time
|
|
1273
|
+
// It is important to note that the connector tokens might not be
|
|
1274
|
+
// compatible for exchange among themselves.
|
|
1275
|
+
const selectedPool = Object.values(this.pools).reduce<PoolLiquidity[]>(
|
|
1276
|
+
(acc, pool) => {
|
|
1277
|
+
const inCoins = pool.coins.some(
|
|
1278
|
+
_token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
|
|
1279
|
+
);
|
|
1280
|
+
const inUnderlying = pool.underlying.some(
|
|
1281
|
+
_token => _token.toLowerCase() === _tokenAddress.toLowerCase(),
|
|
1282
|
+
);
|
|
1283
|
+
let connectorTokens = inCoins ? pool.coins : [];
|
|
1284
|
+
connectorTokens = inUnderlying
|
|
1285
|
+
? _.concat(connectorTokens, pool.underlying)
|
|
1286
|
+
: connectorTokens;
|
|
1287
|
+
|
|
1288
|
+
if (connectorTokens.length) {
|
|
1289
|
+
acc.push({
|
|
1290
|
+
exchange: this.dexKey,
|
|
1291
|
+
address: pool.address,
|
|
1292
|
+
liquidityUSD: pool.liquidityUSD!,
|
|
1293
|
+
connectorTokens: _.uniq(connectorTokens)
|
|
1294
|
+
.filter(
|
|
1295
|
+
(_token: string) =>
|
|
1296
|
+
_token.toLowerCase() !== _tokenAddress.toLowerCase(),
|
|
1297
|
+
)
|
|
1298
|
+
.map(tokenAddress => {
|
|
1299
|
+
const address = tokenAddress.toLowerCase();
|
|
1300
|
+
return {
|
|
1301
|
+
address,
|
|
1302
|
+
decimals: this.decimalsCoinsAndUnderlying[address],
|
|
1303
|
+
};
|
|
1304
|
+
}),
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
return acc;
|
|
1308
|
+
},
|
|
1309
|
+
[],
|
|
1310
|
+
);
|
|
1311
|
+
return selectedPool
|
|
1312
|
+
.sort((a, b) => b.liquidityUSD - a.liquidityUSD)
|
|
1313
|
+
.slice(0, limit);
|
|
1314
|
+
}
|
|
1315
|
+
}
|