@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,572 +1,572 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import { Contract } from 'web3-eth-contract';
|
|
3
|
-
import { Interface } from '@ethersproject/abi';
|
|
4
|
-
import {
|
|
5
|
-
AdapterExchangeParam,
|
|
6
|
-
Address,
|
|
7
|
-
DexExchangeParam,
|
|
8
|
-
ExchangePrices,
|
|
9
|
-
Logger,
|
|
10
|
-
PoolLiquidity,
|
|
11
|
-
PoolPrices,
|
|
12
|
-
SimpleExchangeParam,
|
|
13
|
-
Token,
|
|
14
|
-
} from '../../types';
|
|
15
|
-
import { Network, SUBGRAPH_TIMEOUT } from '../../constants';
|
|
16
|
-
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
17
|
-
import { getBigIntPow, getDexKeysWithNetwork, isETHAddress } from '../../utils';
|
|
18
|
-
import { IDex } from '../../dex/idex';
|
|
19
|
-
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
20
|
-
import {
|
|
21
|
-
BalancerFunctions,
|
|
22
|
-
BalancerParam,
|
|
23
|
-
BalancerV1Data,
|
|
24
|
-
DexParams,
|
|
25
|
-
FractionAsString,
|
|
26
|
-
OptimizedBalancerV1Data,
|
|
27
|
-
PoolInfo,
|
|
28
|
-
PoolsInfo,
|
|
29
|
-
} from './types';
|
|
30
|
-
import { SimpleExchange } from '../simple-exchange';
|
|
31
|
-
import {
|
|
32
|
-
Adapters,
|
|
33
|
-
BALANCER_SWAP_GAS_COST,
|
|
34
|
-
BalancerV1Config,
|
|
35
|
-
MAX_POOL_CNT,
|
|
36
|
-
MAX_POOLS_FOR_PRICING,
|
|
37
|
-
MIN_USD_LIQUIDITY_TO_FETCH,
|
|
38
|
-
} from './config';
|
|
39
|
-
import { BalancerV1EventPool } from './balancer-v1-pool';
|
|
40
|
-
import { generatePoolStates } from './utils';
|
|
41
|
-
import BalancerV1ExchangeProxyABI from '../../abi/BalancerV1ExchangeProxy.json';
|
|
42
|
-
import BalancerCustomMulticallABI from '../../abi/BalancerCustomMulticall.json';
|
|
43
|
-
import { NumberAsString, SwapSide } from '@paraswap/core';
|
|
44
|
-
import { BigNumber, ethers } from 'ethers';
|
|
45
|
-
import { SpecialDex } from '../../executor/types';
|
|
46
|
-
import { extractReturnAmountPosition } from '../../executor/utils';
|
|
47
|
-
|
|
48
|
-
const {
|
|
49
|
-
utils: { hexlify, hexZeroPad, solidityPack },
|
|
50
|
-
} = ethers;
|
|
51
|
-
|
|
52
|
-
const fetchAllPoolsQuery = `query {
|
|
53
|
-
pools(first: ${MAX_POOL_CNT.toString()}
|
|
54
|
-
orderBy: liquidity
|
|
55
|
-
orderDirection: desc
|
|
56
|
-
where: {liquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}}) {
|
|
57
|
-
id
|
|
58
|
-
swapFee
|
|
59
|
-
totalWeight
|
|
60
|
-
tokensList
|
|
61
|
-
tokens {
|
|
62
|
-
address
|
|
63
|
-
balance
|
|
64
|
-
decimals
|
|
65
|
-
denormWeight
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}`;
|
|
69
|
-
|
|
70
|
-
export class BalancerV1
|
|
71
|
-
extends SimpleExchange
|
|
72
|
-
implements IDex<BalancerV1Data | OptimizedBalancerV1Data>
|
|
73
|
-
{
|
|
74
|
-
static readonly proxyIface = new Interface(BalancerV1ExchangeProxyABI);
|
|
75
|
-
|
|
76
|
-
protected config: DexParams;
|
|
77
|
-
protected poolsInfo?: PoolsInfo;
|
|
78
|
-
protected poolInfosByToken: { [tokenAddress: string]: PoolInfo[] } = {};
|
|
79
|
-
|
|
80
|
-
protected eventPools: { [poolAddress: string]: BalancerV1EventPool } = {};
|
|
81
|
-
|
|
82
|
-
readonly hasConstantPriceLargeAmounts = false;
|
|
83
|
-
readonly needWrapNative = false;
|
|
84
|
-
readonly isFeeOnTransferSupported = false;
|
|
85
|
-
|
|
86
|
-
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
87
|
-
getDexKeysWithNetwork(BalancerV1Config);
|
|
88
|
-
|
|
89
|
-
logger: Logger;
|
|
90
|
-
|
|
91
|
-
private balancerMulticall: Contract;
|
|
92
|
-
|
|
93
|
-
constructor(
|
|
94
|
-
readonly network: Network,
|
|
95
|
-
readonly dexKey: string,
|
|
96
|
-
readonly dexHelper: IDexHelper,
|
|
97
|
-
protected adapters = Adapters[network] || {},
|
|
98
|
-
) {
|
|
99
|
-
super(dexHelper, dexKey);
|
|
100
|
-
this.config = BalancerV1Config[dexKey][network];
|
|
101
|
-
this.logger = dexHelper.getLogger(dexKey);
|
|
102
|
-
this.balancerMulticall = new dexHelper.web3Provider.eth.Contract(
|
|
103
|
-
BalancerCustomMulticallABI as any,
|
|
104
|
-
this.config.multicallAddress,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Initialize pricing is called once in the start of
|
|
109
|
-
// pricing service. It is intended to setup the integration
|
|
110
|
-
// for pricing requests. It is optional for a DEX to
|
|
111
|
-
// implement this function
|
|
112
|
-
async initializePricing(_blockNumber: number) {
|
|
113
|
-
const { data } = await this.dexHelper.httpRequest.post<{
|
|
114
|
-
data: { pools: PoolInfo[] };
|
|
115
|
-
}>(
|
|
116
|
-
this.config.subgraphURL,
|
|
117
|
-
{ query: fetchAllPoolsQuery },
|
|
118
|
-
SUBGRAPH_TIMEOUT,
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
if (!(data && data.pools))
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Error ${this.dexKey} Subgraph: couldn't fetch the pools from the subgraph`,
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
this.poolsInfo = data;
|
|
127
|
-
|
|
128
|
-
this.poolInfosByToken = {};
|
|
129
|
-
for (const poolInfo of this.poolsInfo.pools) {
|
|
130
|
-
for (const tokenAddress of poolInfo.tokensList) {
|
|
131
|
-
if (!this.poolInfosByToken[tokenAddress]) {
|
|
132
|
-
this.poolInfosByToken[tokenAddress] = [];
|
|
133
|
-
}
|
|
134
|
-
this.poolInfosByToken[tokenAddress].push(poolInfo);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
getPoolInfosWithTokens(from: Token, to: Token): PoolInfo[] {
|
|
140
|
-
const fromPools = this.poolInfosByToken[from.address];
|
|
141
|
-
const toPools = this.poolInfosByToken[to.address];
|
|
142
|
-
if (!fromPools || !toPools) return [];
|
|
143
|
-
return _.intersectionBy(fromPools, toPools, 'id');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Returns the list of contract adapters (name and index)
|
|
147
|
-
// for a buy/sell. Return null if there are no adapters.
|
|
148
|
-
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
149
|
-
return this.adapters[side] ? this.adapters[side] : null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Returns list of pool identifiers that can be used
|
|
153
|
-
// for a given swap. poolIdentifiers must be unique
|
|
154
|
-
// across DEXes. It is recommended to use
|
|
155
|
-
// ${dexKey}_${poolAddress} as a poolIdentifier
|
|
156
|
-
async getPoolIdentifiers(
|
|
157
|
-
srcToken: Token,
|
|
158
|
-
destToken: Token,
|
|
159
|
-
_side: SwapSide,
|
|
160
|
-
_blockNumber: number,
|
|
161
|
-
): Promise<string[]> {
|
|
162
|
-
if (!this.poolsInfo) return [];
|
|
163
|
-
|
|
164
|
-
const from = this.dexHelper.config.wrapETH(srcToken);
|
|
165
|
-
const to = this.dexHelper.config.wrapETH(destToken);
|
|
166
|
-
from.address = from.address.toLowerCase();
|
|
167
|
-
to.address = to.address.toLowerCase();
|
|
168
|
-
if (from.address === to.address) return [];
|
|
169
|
-
|
|
170
|
-
const poolInfos = this.getPoolInfosWithTokens(from, to);
|
|
171
|
-
return poolInfos.map(p => `${this.dexKey}_${p.id}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Returns pool prices for amounts.
|
|
175
|
-
// If limitPools is defined only pools in limitPools
|
|
176
|
-
// should be used. If limitPools is undefined then
|
|
177
|
-
// any pools can be used.
|
|
178
|
-
async getPricesVolume(
|
|
179
|
-
srcToken: Token,
|
|
180
|
-
destToken: Token,
|
|
181
|
-
amounts: bigint[],
|
|
182
|
-
side: SwapSide,
|
|
183
|
-
blockNumber: number,
|
|
184
|
-
limitPools?: string[],
|
|
185
|
-
): Promise<null | ExchangePrices<BalancerV1Data>> {
|
|
186
|
-
if (!this.poolsInfo) return null;
|
|
187
|
-
|
|
188
|
-
const from = this.dexHelper.config.wrapETH(srcToken);
|
|
189
|
-
const to = this.dexHelper.config.wrapETH(destToken);
|
|
190
|
-
from.address = from.address.toLowerCase();
|
|
191
|
-
to.address = to.address.toLowerCase();
|
|
192
|
-
if (from.address === to.address) return null;
|
|
193
|
-
|
|
194
|
-
let poolInfos = this.getPoolInfosWithTokens(from, to);
|
|
195
|
-
if (!poolInfos.length) return null;
|
|
196
|
-
if (limitPools) {
|
|
197
|
-
poolInfos = poolInfos.filter(p =>
|
|
198
|
-
limitPools.includes(`${this.dexKey}_${p.id}`),
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
if (!poolInfos.length) return null;
|
|
202
|
-
|
|
203
|
-
const poolsMissingState: PoolInfo[] = [];
|
|
204
|
-
for (const poolInfo of poolInfos) {
|
|
205
|
-
const eventPool = this.eventPools[poolInfo.id];
|
|
206
|
-
if (!eventPool || !eventPool.getState(blockNumber)) {
|
|
207
|
-
poolsMissingState.push(poolInfo);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (poolsMissingState.length) {
|
|
212
|
-
const poolStates = await generatePoolStates(
|
|
213
|
-
poolsMissingState,
|
|
214
|
-
this.balancerMulticall,
|
|
215
|
-
blockNumber,
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
await Promise.all(
|
|
219
|
-
poolsMissingState.map(async (poolInfo, i) => {
|
|
220
|
-
const poolState = poolStates[i];
|
|
221
|
-
if (this.eventPools[poolInfo.id]) {
|
|
222
|
-
this.eventPools[poolInfo.id].setState(poolState, blockNumber);
|
|
223
|
-
} else {
|
|
224
|
-
const newPool = new BalancerV1EventPool(
|
|
225
|
-
this.dexKey,
|
|
226
|
-
this.network,
|
|
227
|
-
this.dexHelper,
|
|
228
|
-
this.logger,
|
|
229
|
-
this.balancerMulticall,
|
|
230
|
-
poolInfo,
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
await newPool.initialize(blockNumber, {
|
|
234
|
-
state: poolState,
|
|
235
|
-
});
|
|
236
|
-
this.eventPools[poolInfo.id] = newPool;
|
|
237
|
-
}
|
|
238
|
-
}),
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const pools = poolInfos
|
|
243
|
-
.map(p => this.eventPools[p.id])
|
|
244
|
-
.filter(p => p.checkBalance(from, to, amounts[1], side, blockNumber))
|
|
245
|
-
.map(p => ({
|
|
246
|
-
pool: p,
|
|
247
|
-
score: p.estimatePoolTotalBalance(to, blockNumber),
|
|
248
|
-
}))
|
|
249
|
-
.sort((a, b) => (a.score < b.score ? 1 : a.score > b.score ? -1 : 0))
|
|
250
|
-
.slice(0, MAX_POOLS_FOR_PRICING)
|
|
251
|
-
.map(ps => ps.pool);
|
|
252
|
-
if (!pools.length) return null;
|
|
253
|
-
|
|
254
|
-
const amountsWithUnit = [
|
|
255
|
-
getBigIntPow((side === SwapSide.SELL ? from : to).decimals),
|
|
256
|
-
...amounts.slice(1),
|
|
257
|
-
];
|
|
258
|
-
return pools.map(p => {
|
|
259
|
-
const rates = p.calcPrices(from, to, amountsWithUnit, side, blockNumber);
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
unit: rates[0],
|
|
263
|
-
prices: [0n, ...rates.slice(1)],
|
|
264
|
-
data: {
|
|
265
|
-
poolId: p.poolInfo.id,
|
|
266
|
-
},
|
|
267
|
-
poolAddresses: [p.poolInfo.id],
|
|
268
|
-
exchange: this.dexKey,
|
|
269
|
-
gasCost: BALANCER_SWAP_GAS_COST,
|
|
270
|
-
poolIdentifier: `${this.dexKey}_${p.poolInfo.id}`,
|
|
271
|
-
};
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
276
|
-
getCalldataGasCost(
|
|
277
|
-
_poolPrices: PoolPrices<BalancerV1Data>,
|
|
278
|
-
): number | number[] {
|
|
279
|
-
return (
|
|
280
|
-
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
281
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
282
|
-
// ParentStruct header
|
|
283
|
-
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
284
|
-
// ParentStruct -> swaps[] header
|
|
285
|
-
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
286
|
-
// ParentStruct -> swaps[]
|
|
287
|
-
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
288
|
-
// ParentStruct -> swaps[0]
|
|
289
|
-
CALLDATA_GAS_COST.ADDRESS +
|
|
290
|
-
CALLDATA_GAS_COST.AMOUNT +
|
|
291
|
-
CALLDATA_GAS_COST.AMOUNT +
|
|
292
|
-
CALLDATA_GAS_COST.FULL_WORD
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Encode params required by the exchange adapter
|
|
297
|
-
// Used for multiSwap, buy & megaSwap
|
|
298
|
-
// Hint: abiCoder.encodeParameter() could be useful
|
|
299
|
-
getAdapterParam(
|
|
300
|
-
_srcToken: string,
|
|
301
|
-
_destToken: string,
|
|
302
|
-
_srcAmount: string,
|
|
303
|
-
_destAmount: string,
|
|
304
|
-
data: OptimizedBalancerV1Data,
|
|
305
|
-
_side: SwapSide,
|
|
306
|
-
): AdapterExchangeParam {
|
|
307
|
-
const { swaps } = data;
|
|
308
|
-
const payload = this.abiCoder.encodeParameter(
|
|
309
|
-
{
|
|
310
|
-
ParentStruct: {
|
|
311
|
-
'swaps[]': {
|
|
312
|
-
pool: 'address',
|
|
313
|
-
tokenInParam: 'uint',
|
|
314
|
-
tokenOutParam: 'uint',
|
|
315
|
-
maxPrice: 'uint',
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
{ swaps },
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
targetExchange: this.config.exchangeProxy,
|
|
324
|
-
payload,
|
|
325
|
-
networkFee: '0',
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
getDexParam(
|
|
330
|
-
srcToken: Address,
|
|
331
|
-
destToken: Address,
|
|
332
|
-
srcAmount: NumberAsString,
|
|
333
|
-
destAmount: NumberAsString,
|
|
334
|
-
recipient: Address,
|
|
335
|
-
data: OptimizedBalancerV1Data,
|
|
336
|
-
side: SwapSide,
|
|
337
|
-
): DexExchangeParam {
|
|
338
|
-
const { swaps } = data;
|
|
339
|
-
|
|
340
|
-
if (side === SwapSide.BUY) {
|
|
341
|
-
// Need to adjust the swap input params to match the adjusted srcAmount
|
|
342
|
-
const _srcAmount = BigInt(srcAmount);
|
|
343
|
-
const totalInParam = swaps.reduce(
|
|
344
|
-
(acc, swap) => acc + BigInt(swap.tokenInParam),
|
|
345
|
-
0n,
|
|
346
|
-
);
|
|
347
|
-
swaps.forEach(swap => {
|
|
348
|
-
swap.tokenInParam = (
|
|
349
|
-
(BigInt(swap.tokenInParam) * _srcAmount) /
|
|
350
|
-
totalInParam
|
|
351
|
-
).toString();
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const [swapFunction, swapFunctionParam] = ((): [
|
|
356
|
-
swapFunction: BalancerFunctions,
|
|
357
|
-
swapFunctionParam: BalancerParam,
|
|
358
|
-
] => {
|
|
359
|
-
if (side === SwapSide.SELL) {
|
|
360
|
-
if (isETHAddress(srcToken))
|
|
361
|
-
return [
|
|
362
|
-
BalancerFunctions.batchEthInSwapExactIn,
|
|
363
|
-
[swaps, destToken, destAmount],
|
|
364
|
-
];
|
|
365
|
-
if (isETHAddress(destToken))
|
|
366
|
-
return [
|
|
367
|
-
BalancerFunctions.batchEthOutSwapExactIn,
|
|
368
|
-
[swaps, srcToken, srcAmount, destAmount],
|
|
369
|
-
];
|
|
370
|
-
return [
|
|
371
|
-
BalancerFunctions.batchSwapExactIn,
|
|
372
|
-
[swaps, srcToken, destToken, srcAmount, destAmount],
|
|
373
|
-
];
|
|
374
|
-
} else {
|
|
375
|
-
if (isETHAddress(srcToken))
|
|
376
|
-
return [BalancerFunctions.batchEthInSwapExactOut, [swaps, destToken]];
|
|
377
|
-
if (isETHAddress(destToken))
|
|
378
|
-
return [
|
|
379
|
-
BalancerFunctions.batchEthOutSwapExactOut,
|
|
380
|
-
[swaps, srcToken, srcAmount],
|
|
381
|
-
];
|
|
382
|
-
return [
|
|
383
|
-
BalancerFunctions.batchSwapExactOut,
|
|
384
|
-
[swaps, srcToken, destToken, srcAmount],
|
|
385
|
-
];
|
|
386
|
-
}
|
|
387
|
-
})();
|
|
388
|
-
|
|
389
|
-
let exchangeData = BalancerV1.proxyIface.encodeFunctionData(
|
|
390
|
-
swapFunction,
|
|
391
|
-
swapFunctionParam,
|
|
392
|
-
);
|
|
393
|
-
let specialDexFlag = SpecialDex.DEFAULT;
|
|
394
|
-
|
|
395
|
-
if (side === SwapSide.SELL) {
|
|
396
|
-
const totalAmount = swaps.reduce<BigNumber>((acc, swap) => {
|
|
397
|
-
return acc.add(swap.tokenInParam);
|
|
398
|
-
}, BigNumber.from(0));
|
|
399
|
-
|
|
400
|
-
exchangeData = solidityPack(
|
|
401
|
-
['bytes32', 'bytes32', 'bytes'],
|
|
402
|
-
[
|
|
403
|
-
hexZeroPad(hexlify(swaps.length), 32),
|
|
404
|
-
hexZeroPad(hexlify(totalAmount), 32),
|
|
405
|
-
exchangeData,
|
|
406
|
-
],
|
|
407
|
-
);
|
|
408
|
-
specialDexFlag = SpecialDex.SWAP_ON_BALANCER_V1;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return {
|
|
412
|
-
needWrapNative:
|
|
413
|
-
swapFunction === BalancerFunctions.batchSwapExactIn
|
|
414
|
-
? true
|
|
415
|
-
: this.needWrapNative,
|
|
416
|
-
specialDexFlag,
|
|
417
|
-
dexFuncHasRecipient: false,
|
|
418
|
-
exchangeData: exchangeData,
|
|
419
|
-
targetExchange: this.config.exchangeProxy,
|
|
420
|
-
returnAmountPos:
|
|
421
|
-
side === SwapSide.SELL
|
|
422
|
-
? extractReturnAmountPosition(
|
|
423
|
-
BalancerV1.proxyIface,
|
|
424
|
-
swapFunction,
|
|
425
|
-
'totalAmountOut',
|
|
426
|
-
)
|
|
427
|
-
: undefined,
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Encode call data used by simpleSwap like routers
|
|
432
|
-
// Used for simpleSwap & simpleBuy
|
|
433
|
-
// Hint: this.buildSimpleParamWithoutWETHConversion
|
|
434
|
-
// could be useful
|
|
435
|
-
async getSimpleParam(
|
|
436
|
-
srcToken: string,
|
|
437
|
-
destToken: string,
|
|
438
|
-
srcAmount: string,
|
|
439
|
-
destAmount: string,
|
|
440
|
-
data: OptimizedBalancerV1Data,
|
|
441
|
-
side: SwapSide,
|
|
442
|
-
): Promise<SimpleExchangeParam> {
|
|
443
|
-
const { swaps } = data;
|
|
444
|
-
|
|
445
|
-
if (side === SwapSide.BUY) {
|
|
446
|
-
// Need to adjust the swap input params to match the adjusted srcAmount
|
|
447
|
-
const _srcAmount = BigInt(srcAmount);
|
|
448
|
-
const totalInParam = swaps.reduce(
|
|
449
|
-
(acc, swap) => acc + BigInt(swap.tokenInParam),
|
|
450
|
-
0n,
|
|
451
|
-
);
|
|
452
|
-
swaps.forEach(swap => {
|
|
453
|
-
swap.tokenInParam = (
|
|
454
|
-
(BigInt(swap.tokenInParam) * _srcAmount) /
|
|
455
|
-
totalInParam
|
|
456
|
-
).toString();
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const [swapFunction, swapFunctionParam] = ((): [
|
|
461
|
-
swapFunction: BalancerFunctions,
|
|
462
|
-
swapFunctionParam: BalancerParam,
|
|
463
|
-
] => {
|
|
464
|
-
if (side === SwapSide.SELL) {
|
|
465
|
-
if (isETHAddress(srcToken))
|
|
466
|
-
return [
|
|
467
|
-
BalancerFunctions.batchEthInSwapExactIn,
|
|
468
|
-
[swaps, destToken, destAmount],
|
|
469
|
-
];
|
|
470
|
-
if (isETHAddress(destToken))
|
|
471
|
-
return [
|
|
472
|
-
BalancerFunctions.batchEthOutSwapExactIn,
|
|
473
|
-
[swaps, srcToken, srcAmount, destAmount],
|
|
474
|
-
];
|
|
475
|
-
return [
|
|
476
|
-
BalancerFunctions.batchSwapExactIn,
|
|
477
|
-
[swaps, srcToken, destToken, srcAmount, destAmount],
|
|
478
|
-
];
|
|
479
|
-
} else {
|
|
480
|
-
if (isETHAddress(srcToken))
|
|
481
|
-
return [BalancerFunctions.batchEthInSwapExactOut, [swaps, destToken]];
|
|
482
|
-
if (isETHAddress(destToken))
|
|
483
|
-
return [
|
|
484
|
-
BalancerFunctions.batchEthOutSwapExactOut,
|
|
485
|
-
[swaps, srcToken, srcAmount],
|
|
486
|
-
];
|
|
487
|
-
return [
|
|
488
|
-
BalancerFunctions.batchSwapExactOut,
|
|
489
|
-
[swaps, srcToken, destToken, srcAmount],
|
|
490
|
-
];
|
|
491
|
-
}
|
|
492
|
-
})();
|
|
493
|
-
|
|
494
|
-
const swapData = BalancerV1.proxyIface.encodeFunctionData(
|
|
495
|
-
swapFunction,
|
|
496
|
-
swapFunctionParam,
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
return this.buildSimpleParamWithoutWETHConversion(
|
|
500
|
-
srcToken,
|
|
501
|
-
srcAmount,
|
|
502
|
-
destToken,
|
|
503
|
-
destAmount,
|
|
504
|
-
swapData,
|
|
505
|
-
this.config.exchangeProxy,
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Returns list of top pools based on liquidity. Max
|
|
510
|
-
// limit number pools should be returned.
|
|
511
|
-
async getTopPoolsForToken(
|
|
512
|
-
tokenAddress: Address,
|
|
513
|
-
limit: number,
|
|
514
|
-
): Promise<PoolLiquidity[]> {
|
|
515
|
-
tokenAddress = tokenAddress.toLowerCase();
|
|
516
|
-
|
|
517
|
-
const variables = {
|
|
518
|
-
tokens: [tokenAddress],
|
|
519
|
-
limit,
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
const query = `query ($tokens: [Bytes!], $limit: Int) {
|
|
523
|
-
pools (
|
|
524
|
-
first: $limit, orderBy: liquidity, orderDirection: desc,
|
|
525
|
-
where: {
|
|
526
|
-
tokensList_contains: $tokens,
|
|
527
|
-
active: true,
|
|
528
|
-
publicSwap: true,
|
|
529
|
-
liquidity_gt: 0,
|
|
530
|
-
liquidity_lt: 1000000000
|
|
531
|
-
}
|
|
532
|
-
) {
|
|
533
|
-
id
|
|
534
|
-
liquidity
|
|
535
|
-
tokens {
|
|
536
|
-
address
|
|
537
|
-
decimals
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}`;
|
|
541
|
-
const { data } = await this.dexHelper.httpRequest.post<{
|
|
542
|
-
data: {
|
|
543
|
-
pools: {
|
|
544
|
-
id: Address;
|
|
545
|
-
liquidity: FractionAsString;
|
|
546
|
-
tokens: {
|
|
547
|
-
address: Address;
|
|
548
|
-
decimals: number;
|
|
549
|
-
}[];
|
|
550
|
-
}[];
|
|
551
|
-
};
|
|
552
|
-
}>(this.config.subgraphURL, { query, variables }, SUBGRAPH_TIMEOUT);
|
|
553
|
-
|
|
554
|
-
if (!(data && data.pools))
|
|
555
|
-
throw new Error(
|
|
556
|
-
`Error ${this.dexKey} Subgraph: couldn't fetch the pools from the subgraph`,
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
return data.pools.map(pool => ({
|
|
560
|
-
exchange: this.dexKey,
|
|
561
|
-
address: pool.id,
|
|
562
|
-
connectorTokens: pool.tokens.reduce<Token[]>(
|
|
563
|
-
(acc, { decimals, address }) => {
|
|
564
|
-
if (address !== tokenAddress) acc.push({ decimals, address });
|
|
565
|
-
return acc;
|
|
566
|
-
},
|
|
567
|
-
[],
|
|
568
|
-
),
|
|
569
|
-
liquidityUSD: parseFloat(pool.liquidity),
|
|
570
|
-
}));
|
|
571
|
-
}
|
|
572
|
-
}
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { Contract } from 'web3-eth-contract';
|
|
3
|
+
import { Interface } from '@ethersproject/abi';
|
|
4
|
+
import {
|
|
5
|
+
AdapterExchangeParam,
|
|
6
|
+
Address,
|
|
7
|
+
DexExchangeParam,
|
|
8
|
+
ExchangePrices,
|
|
9
|
+
Logger,
|
|
10
|
+
PoolLiquidity,
|
|
11
|
+
PoolPrices,
|
|
12
|
+
SimpleExchangeParam,
|
|
13
|
+
Token,
|
|
14
|
+
} from '../../types';
|
|
15
|
+
import { Network, SUBGRAPH_TIMEOUT } from '../../constants';
|
|
16
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
17
|
+
import { getBigIntPow, getDexKeysWithNetwork, isETHAddress } from '../../utils';
|
|
18
|
+
import { IDex } from '../../dex/idex';
|
|
19
|
+
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
20
|
+
import {
|
|
21
|
+
BalancerFunctions,
|
|
22
|
+
BalancerParam,
|
|
23
|
+
BalancerV1Data,
|
|
24
|
+
DexParams,
|
|
25
|
+
FractionAsString,
|
|
26
|
+
OptimizedBalancerV1Data,
|
|
27
|
+
PoolInfo,
|
|
28
|
+
PoolsInfo,
|
|
29
|
+
} from './types';
|
|
30
|
+
import { SimpleExchange } from '../simple-exchange';
|
|
31
|
+
import {
|
|
32
|
+
Adapters,
|
|
33
|
+
BALANCER_SWAP_GAS_COST,
|
|
34
|
+
BalancerV1Config,
|
|
35
|
+
MAX_POOL_CNT,
|
|
36
|
+
MAX_POOLS_FOR_PRICING,
|
|
37
|
+
MIN_USD_LIQUIDITY_TO_FETCH,
|
|
38
|
+
} from './config';
|
|
39
|
+
import { BalancerV1EventPool } from './balancer-v1-pool';
|
|
40
|
+
import { generatePoolStates } from './utils';
|
|
41
|
+
import BalancerV1ExchangeProxyABI from '../../abi/BalancerV1ExchangeProxy.json';
|
|
42
|
+
import BalancerCustomMulticallABI from '../../abi/BalancerCustomMulticall.json';
|
|
43
|
+
import { NumberAsString, SwapSide } from '@paraswap/core';
|
|
44
|
+
import { BigNumber, ethers } from 'ethers';
|
|
45
|
+
import { SpecialDex } from '../../executor/types';
|
|
46
|
+
import { extractReturnAmountPosition } from '../../executor/utils';
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
utils: { hexlify, hexZeroPad, solidityPack },
|
|
50
|
+
} = ethers;
|
|
51
|
+
|
|
52
|
+
const fetchAllPoolsQuery = `query {
|
|
53
|
+
pools(first: ${MAX_POOL_CNT.toString()}
|
|
54
|
+
orderBy: liquidity
|
|
55
|
+
orderDirection: desc
|
|
56
|
+
where: {liquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}}) {
|
|
57
|
+
id
|
|
58
|
+
swapFee
|
|
59
|
+
totalWeight
|
|
60
|
+
tokensList
|
|
61
|
+
tokens {
|
|
62
|
+
address
|
|
63
|
+
balance
|
|
64
|
+
decimals
|
|
65
|
+
denormWeight
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}`;
|
|
69
|
+
|
|
70
|
+
export class BalancerV1
|
|
71
|
+
extends SimpleExchange
|
|
72
|
+
implements IDex<BalancerV1Data | OptimizedBalancerV1Data>
|
|
73
|
+
{
|
|
74
|
+
static readonly proxyIface = new Interface(BalancerV1ExchangeProxyABI);
|
|
75
|
+
|
|
76
|
+
protected config: DexParams;
|
|
77
|
+
protected poolsInfo?: PoolsInfo;
|
|
78
|
+
protected poolInfosByToken: { [tokenAddress: string]: PoolInfo[] } = {};
|
|
79
|
+
|
|
80
|
+
protected eventPools: { [poolAddress: string]: BalancerV1EventPool } = {};
|
|
81
|
+
|
|
82
|
+
readonly hasConstantPriceLargeAmounts = false;
|
|
83
|
+
readonly needWrapNative = false;
|
|
84
|
+
readonly isFeeOnTransferSupported = false;
|
|
85
|
+
|
|
86
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
87
|
+
getDexKeysWithNetwork(BalancerV1Config);
|
|
88
|
+
|
|
89
|
+
logger: Logger;
|
|
90
|
+
|
|
91
|
+
private balancerMulticall: Contract;
|
|
92
|
+
|
|
93
|
+
constructor(
|
|
94
|
+
readonly network: Network,
|
|
95
|
+
readonly dexKey: string,
|
|
96
|
+
readonly dexHelper: IDexHelper,
|
|
97
|
+
protected adapters = Adapters[network] || {},
|
|
98
|
+
) {
|
|
99
|
+
super(dexHelper, dexKey);
|
|
100
|
+
this.config = BalancerV1Config[dexKey][network];
|
|
101
|
+
this.logger = dexHelper.getLogger(dexKey);
|
|
102
|
+
this.balancerMulticall = new dexHelper.web3Provider.eth.Contract(
|
|
103
|
+
BalancerCustomMulticallABI as any,
|
|
104
|
+
this.config.multicallAddress,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Initialize pricing is called once in the start of
|
|
109
|
+
// pricing service. It is intended to setup the integration
|
|
110
|
+
// for pricing requests. It is optional for a DEX to
|
|
111
|
+
// implement this function
|
|
112
|
+
async initializePricing(_blockNumber: number) {
|
|
113
|
+
const { data } = await this.dexHelper.httpRequest.post<{
|
|
114
|
+
data: { pools: PoolInfo[] };
|
|
115
|
+
}>(
|
|
116
|
+
this.config.subgraphURL,
|
|
117
|
+
{ query: fetchAllPoolsQuery },
|
|
118
|
+
SUBGRAPH_TIMEOUT,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!(data && data.pools))
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Error ${this.dexKey} Subgraph: couldn't fetch the pools from the subgraph`,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
this.poolsInfo = data;
|
|
127
|
+
|
|
128
|
+
this.poolInfosByToken = {};
|
|
129
|
+
for (const poolInfo of this.poolsInfo.pools) {
|
|
130
|
+
for (const tokenAddress of poolInfo.tokensList) {
|
|
131
|
+
if (!this.poolInfosByToken[tokenAddress]) {
|
|
132
|
+
this.poolInfosByToken[tokenAddress] = [];
|
|
133
|
+
}
|
|
134
|
+
this.poolInfosByToken[tokenAddress].push(poolInfo);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getPoolInfosWithTokens(from: Token, to: Token): PoolInfo[] {
|
|
140
|
+
const fromPools = this.poolInfosByToken[from.address];
|
|
141
|
+
const toPools = this.poolInfosByToken[to.address];
|
|
142
|
+
if (!fromPools || !toPools) return [];
|
|
143
|
+
return _.intersectionBy(fromPools, toPools, 'id');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Returns the list of contract adapters (name and index)
|
|
147
|
+
// for a buy/sell. Return null if there are no adapters.
|
|
148
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
149
|
+
return this.adapters[side] ? this.adapters[side] : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Returns list of pool identifiers that can be used
|
|
153
|
+
// for a given swap. poolIdentifiers must be unique
|
|
154
|
+
// across DEXes. It is recommended to use
|
|
155
|
+
// ${dexKey}_${poolAddress} as a poolIdentifier
|
|
156
|
+
async getPoolIdentifiers(
|
|
157
|
+
srcToken: Token,
|
|
158
|
+
destToken: Token,
|
|
159
|
+
_side: SwapSide,
|
|
160
|
+
_blockNumber: number,
|
|
161
|
+
): Promise<string[]> {
|
|
162
|
+
if (!this.poolsInfo) return [];
|
|
163
|
+
|
|
164
|
+
const from = this.dexHelper.config.wrapETH(srcToken);
|
|
165
|
+
const to = this.dexHelper.config.wrapETH(destToken);
|
|
166
|
+
from.address = from.address.toLowerCase();
|
|
167
|
+
to.address = to.address.toLowerCase();
|
|
168
|
+
if (from.address === to.address) return [];
|
|
169
|
+
|
|
170
|
+
const poolInfos = this.getPoolInfosWithTokens(from, to);
|
|
171
|
+
return poolInfos.map(p => `${this.dexKey}_${p.id}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Returns pool prices for amounts.
|
|
175
|
+
// If limitPools is defined only pools in limitPools
|
|
176
|
+
// should be used. If limitPools is undefined then
|
|
177
|
+
// any pools can be used.
|
|
178
|
+
async getPricesVolume(
|
|
179
|
+
srcToken: Token,
|
|
180
|
+
destToken: Token,
|
|
181
|
+
amounts: bigint[],
|
|
182
|
+
side: SwapSide,
|
|
183
|
+
blockNumber: number,
|
|
184
|
+
limitPools?: string[],
|
|
185
|
+
): Promise<null | ExchangePrices<BalancerV1Data>> {
|
|
186
|
+
if (!this.poolsInfo) return null;
|
|
187
|
+
|
|
188
|
+
const from = this.dexHelper.config.wrapETH(srcToken);
|
|
189
|
+
const to = this.dexHelper.config.wrapETH(destToken);
|
|
190
|
+
from.address = from.address.toLowerCase();
|
|
191
|
+
to.address = to.address.toLowerCase();
|
|
192
|
+
if (from.address === to.address) return null;
|
|
193
|
+
|
|
194
|
+
let poolInfos = this.getPoolInfosWithTokens(from, to);
|
|
195
|
+
if (!poolInfos.length) return null;
|
|
196
|
+
if (limitPools) {
|
|
197
|
+
poolInfos = poolInfos.filter(p =>
|
|
198
|
+
limitPools.includes(`${this.dexKey}_${p.id}`),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (!poolInfos.length) return null;
|
|
202
|
+
|
|
203
|
+
const poolsMissingState: PoolInfo[] = [];
|
|
204
|
+
for (const poolInfo of poolInfos) {
|
|
205
|
+
const eventPool = this.eventPools[poolInfo.id];
|
|
206
|
+
if (!eventPool || !eventPool.getState(blockNumber)) {
|
|
207
|
+
poolsMissingState.push(poolInfo);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (poolsMissingState.length) {
|
|
212
|
+
const poolStates = await generatePoolStates(
|
|
213
|
+
poolsMissingState,
|
|
214
|
+
this.balancerMulticall,
|
|
215
|
+
blockNumber,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await Promise.all(
|
|
219
|
+
poolsMissingState.map(async (poolInfo, i) => {
|
|
220
|
+
const poolState = poolStates[i];
|
|
221
|
+
if (this.eventPools[poolInfo.id]) {
|
|
222
|
+
this.eventPools[poolInfo.id].setState(poolState, blockNumber);
|
|
223
|
+
} else {
|
|
224
|
+
const newPool = new BalancerV1EventPool(
|
|
225
|
+
this.dexKey,
|
|
226
|
+
this.network,
|
|
227
|
+
this.dexHelper,
|
|
228
|
+
this.logger,
|
|
229
|
+
this.balancerMulticall,
|
|
230
|
+
poolInfo,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
await newPool.initialize(blockNumber, {
|
|
234
|
+
state: poolState,
|
|
235
|
+
});
|
|
236
|
+
this.eventPools[poolInfo.id] = newPool;
|
|
237
|
+
}
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const pools = poolInfos
|
|
243
|
+
.map(p => this.eventPools[p.id])
|
|
244
|
+
.filter(p => p.checkBalance(from, to, amounts[1], side, blockNumber))
|
|
245
|
+
.map(p => ({
|
|
246
|
+
pool: p,
|
|
247
|
+
score: p.estimatePoolTotalBalance(to, blockNumber),
|
|
248
|
+
}))
|
|
249
|
+
.sort((a, b) => (a.score < b.score ? 1 : a.score > b.score ? -1 : 0))
|
|
250
|
+
.slice(0, MAX_POOLS_FOR_PRICING)
|
|
251
|
+
.map(ps => ps.pool);
|
|
252
|
+
if (!pools.length) return null;
|
|
253
|
+
|
|
254
|
+
const amountsWithUnit = [
|
|
255
|
+
getBigIntPow((side === SwapSide.SELL ? from : to).decimals),
|
|
256
|
+
...amounts.slice(1),
|
|
257
|
+
];
|
|
258
|
+
return pools.map(p => {
|
|
259
|
+
const rates = p.calcPrices(from, to, amountsWithUnit, side, blockNumber);
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
unit: rates[0],
|
|
263
|
+
prices: [0n, ...rates.slice(1)],
|
|
264
|
+
data: {
|
|
265
|
+
poolId: p.poolInfo.id,
|
|
266
|
+
},
|
|
267
|
+
poolAddresses: [p.poolInfo.id],
|
|
268
|
+
exchange: this.dexKey,
|
|
269
|
+
gasCost: BALANCER_SWAP_GAS_COST,
|
|
270
|
+
poolIdentifier: `${this.dexKey}_${p.poolInfo.id}`,
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Returns estimated gas cost of calldata for this DEX in multiSwap
|
|
276
|
+
getCalldataGasCost(
|
|
277
|
+
_poolPrices: PoolPrices<BalancerV1Data>,
|
|
278
|
+
): number | number[] {
|
|
279
|
+
return (
|
|
280
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
281
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
282
|
+
// ParentStruct header
|
|
283
|
+
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
284
|
+
// ParentStruct -> swaps[] header
|
|
285
|
+
CALLDATA_GAS_COST.OFFSET_SMALL +
|
|
286
|
+
// ParentStruct -> swaps[]
|
|
287
|
+
CALLDATA_GAS_COST.LENGTH_SMALL +
|
|
288
|
+
// ParentStruct -> swaps[0]
|
|
289
|
+
CALLDATA_GAS_COST.ADDRESS +
|
|
290
|
+
CALLDATA_GAS_COST.AMOUNT +
|
|
291
|
+
CALLDATA_GAS_COST.AMOUNT +
|
|
292
|
+
CALLDATA_GAS_COST.FULL_WORD
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Encode params required by the exchange adapter
|
|
297
|
+
// Used for multiSwap, buy & megaSwap
|
|
298
|
+
// Hint: abiCoder.encodeParameter() could be useful
|
|
299
|
+
getAdapterParam(
|
|
300
|
+
_srcToken: string,
|
|
301
|
+
_destToken: string,
|
|
302
|
+
_srcAmount: string,
|
|
303
|
+
_destAmount: string,
|
|
304
|
+
data: OptimizedBalancerV1Data,
|
|
305
|
+
_side: SwapSide,
|
|
306
|
+
): AdapterExchangeParam {
|
|
307
|
+
const { swaps } = data;
|
|
308
|
+
const payload = this.abiCoder.encodeParameter(
|
|
309
|
+
{
|
|
310
|
+
ParentStruct: {
|
|
311
|
+
'swaps[]': {
|
|
312
|
+
pool: 'address',
|
|
313
|
+
tokenInParam: 'uint',
|
|
314
|
+
tokenOutParam: 'uint',
|
|
315
|
+
maxPrice: 'uint',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{ swaps },
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
targetExchange: this.config.exchangeProxy,
|
|
324
|
+
payload,
|
|
325
|
+
networkFee: '0',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
getDexParam(
|
|
330
|
+
srcToken: Address,
|
|
331
|
+
destToken: Address,
|
|
332
|
+
srcAmount: NumberAsString,
|
|
333
|
+
destAmount: NumberAsString,
|
|
334
|
+
recipient: Address,
|
|
335
|
+
data: OptimizedBalancerV1Data,
|
|
336
|
+
side: SwapSide,
|
|
337
|
+
): DexExchangeParam {
|
|
338
|
+
const { swaps } = data;
|
|
339
|
+
|
|
340
|
+
if (side === SwapSide.BUY) {
|
|
341
|
+
// Need to adjust the swap input params to match the adjusted srcAmount
|
|
342
|
+
const _srcAmount = BigInt(srcAmount);
|
|
343
|
+
const totalInParam = swaps.reduce(
|
|
344
|
+
(acc, swap) => acc + BigInt(swap.tokenInParam),
|
|
345
|
+
0n,
|
|
346
|
+
);
|
|
347
|
+
swaps.forEach(swap => {
|
|
348
|
+
swap.tokenInParam = (
|
|
349
|
+
(BigInt(swap.tokenInParam) * _srcAmount) /
|
|
350
|
+
totalInParam
|
|
351
|
+
).toString();
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const [swapFunction, swapFunctionParam] = ((): [
|
|
356
|
+
swapFunction: BalancerFunctions,
|
|
357
|
+
swapFunctionParam: BalancerParam,
|
|
358
|
+
] => {
|
|
359
|
+
if (side === SwapSide.SELL) {
|
|
360
|
+
if (isETHAddress(srcToken))
|
|
361
|
+
return [
|
|
362
|
+
BalancerFunctions.batchEthInSwapExactIn,
|
|
363
|
+
[swaps, destToken, destAmount],
|
|
364
|
+
];
|
|
365
|
+
if (isETHAddress(destToken))
|
|
366
|
+
return [
|
|
367
|
+
BalancerFunctions.batchEthOutSwapExactIn,
|
|
368
|
+
[swaps, srcToken, srcAmount, destAmount],
|
|
369
|
+
];
|
|
370
|
+
return [
|
|
371
|
+
BalancerFunctions.batchSwapExactIn,
|
|
372
|
+
[swaps, srcToken, destToken, srcAmount, destAmount],
|
|
373
|
+
];
|
|
374
|
+
} else {
|
|
375
|
+
if (isETHAddress(srcToken))
|
|
376
|
+
return [BalancerFunctions.batchEthInSwapExactOut, [swaps, destToken]];
|
|
377
|
+
if (isETHAddress(destToken))
|
|
378
|
+
return [
|
|
379
|
+
BalancerFunctions.batchEthOutSwapExactOut,
|
|
380
|
+
[swaps, srcToken, srcAmount],
|
|
381
|
+
];
|
|
382
|
+
return [
|
|
383
|
+
BalancerFunctions.batchSwapExactOut,
|
|
384
|
+
[swaps, srcToken, destToken, srcAmount],
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
})();
|
|
388
|
+
|
|
389
|
+
let exchangeData = BalancerV1.proxyIface.encodeFunctionData(
|
|
390
|
+
swapFunction,
|
|
391
|
+
swapFunctionParam,
|
|
392
|
+
);
|
|
393
|
+
let specialDexFlag = SpecialDex.DEFAULT;
|
|
394
|
+
|
|
395
|
+
if (side === SwapSide.SELL) {
|
|
396
|
+
const totalAmount = swaps.reduce<BigNumber>((acc, swap) => {
|
|
397
|
+
return acc.add(swap.tokenInParam);
|
|
398
|
+
}, BigNumber.from(0));
|
|
399
|
+
|
|
400
|
+
exchangeData = solidityPack(
|
|
401
|
+
['bytes32', 'bytes32', 'bytes'],
|
|
402
|
+
[
|
|
403
|
+
hexZeroPad(hexlify(swaps.length), 32),
|
|
404
|
+
hexZeroPad(hexlify(totalAmount), 32),
|
|
405
|
+
exchangeData,
|
|
406
|
+
],
|
|
407
|
+
);
|
|
408
|
+
specialDexFlag = SpecialDex.SWAP_ON_BALANCER_V1;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
needWrapNative:
|
|
413
|
+
swapFunction === BalancerFunctions.batchSwapExactIn
|
|
414
|
+
? true
|
|
415
|
+
: this.needWrapNative,
|
|
416
|
+
specialDexFlag,
|
|
417
|
+
dexFuncHasRecipient: false,
|
|
418
|
+
exchangeData: exchangeData,
|
|
419
|
+
targetExchange: this.config.exchangeProxy,
|
|
420
|
+
returnAmountPos:
|
|
421
|
+
side === SwapSide.SELL
|
|
422
|
+
? extractReturnAmountPosition(
|
|
423
|
+
BalancerV1.proxyIface,
|
|
424
|
+
swapFunction,
|
|
425
|
+
'totalAmountOut',
|
|
426
|
+
)
|
|
427
|
+
: undefined,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Encode call data used by simpleSwap like routers
|
|
432
|
+
// Used for simpleSwap & simpleBuy
|
|
433
|
+
// Hint: this.buildSimpleParamWithoutWETHConversion
|
|
434
|
+
// could be useful
|
|
435
|
+
async getSimpleParam(
|
|
436
|
+
srcToken: string,
|
|
437
|
+
destToken: string,
|
|
438
|
+
srcAmount: string,
|
|
439
|
+
destAmount: string,
|
|
440
|
+
data: OptimizedBalancerV1Data,
|
|
441
|
+
side: SwapSide,
|
|
442
|
+
): Promise<SimpleExchangeParam> {
|
|
443
|
+
const { swaps } = data;
|
|
444
|
+
|
|
445
|
+
if (side === SwapSide.BUY) {
|
|
446
|
+
// Need to adjust the swap input params to match the adjusted srcAmount
|
|
447
|
+
const _srcAmount = BigInt(srcAmount);
|
|
448
|
+
const totalInParam = swaps.reduce(
|
|
449
|
+
(acc, swap) => acc + BigInt(swap.tokenInParam),
|
|
450
|
+
0n,
|
|
451
|
+
);
|
|
452
|
+
swaps.forEach(swap => {
|
|
453
|
+
swap.tokenInParam = (
|
|
454
|
+
(BigInt(swap.tokenInParam) * _srcAmount) /
|
|
455
|
+
totalInParam
|
|
456
|
+
).toString();
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const [swapFunction, swapFunctionParam] = ((): [
|
|
461
|
+
swapFunction: BalancerFunctions,
|
|
462
|
+
swapFunctionParam: BalancerParam,
|
|
463
|
+
] => {
|
|
464
|
+
if (side === SwapSide.SELL) {
|
|
465
|
+
if (isETHAddress(srcToken))
|
|
466
|
+
return [
|
|
467
|
+
BalancerFunctions.batchEthInSwapExactIn,
|
|
468
|
+
[swaps, destToken, destAmount],
|
|
469
|
+
];
|
|
470
|
+
if (isETHAddress(destToken))
|
|
471
|
+
return [
|
|
472
|
+
BalancerFunctions.batchEthOutSwapExactIn,
|
|
473
|
+
[swaps, srcToken, srcAmount, destAmount],
|
|
474
|
+
];
|
|
475
|
+
return [
|
|
476
|
+
BalancerFunctions.batchSwapExactIn,
|
|
477
|
+
[swaps, srcToken, destToken, srcAmount, destAmount],
|
|
478
|
+
];
|
|
479
|
+
} else {
|
|
480
|
+
if (isETHAddress(srcToken))
|
|
481
|
+
return [BalancerFunctions.batchEthInSwapExactOut, [swaps, destToken]];
|
|
482
|
+
if (isETHAddress(destToken))
|
|
483
|
+
return [
|
|
484
|
+
BalancerFunctions.batchEthOutSwapExactOut,
|
|
485
|
+
[swaps, srcToken, srcAmount],
|
|
486
|
+
];
|
|
487
|
+
return [
|
|
488
|
+
BalancerFunctions.batchSwapExactOut,
|
|
489
|
+
[swaps, srcToken, destToken, srcAmount],
|
|
490
|
+
];
|
|
491
|
+
}
|
|
492
|
+
})();
|
|
493
|
+
|
|
494
|
+
const swapData = BalancerV1.proxyIface.encodeFunctionData(
|
|
495
|
+
swapFunction,
|
|
496
|
+
swapFunctionParam,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
return this.buildSimpleParamWithoutWETHConversion(
|
|
500
|
+
srcToken,
|
|
501
|
+
srcAmount,
|
|
502
|
+
destToken,
|
|
503
|
+
destAmount,
|
|
504
|
+
swapData,
|
|
505
|
+
this.config.exchangeProxy,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Returns list of top pools based on liquidity. Max
|
|
510
|
+
// limit number pools should be returned.
|
|
511
|
+
async getTopPoolsForToken(
|
|
512
|
+
tokenAddress: Address,
|
|
513
|
+
limit: number,
|
|
514
|
+
): Promise<PoolLiquidity[]> {
|
|
515
|
+
tokenAddress = tokenAddress.toLowerCase();
|
|
516
|
+
|
|
517
|
+
const variables = {
|
|
518
|
+
tokens: [tokenAddress],
|
|
519
|
+
limit,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const query = `query ($tokens: [Bytes!], $limit: Int) {
|
|
523
|
+
pools (
|
|
524
|
+
first: $limit, orderBy: liquidity, orderDirection: desc,
|
|
525
|
+
where: {
|
|
526
|
+
tokensList_contains: $tokens,
|
|
527
|
+
active: true,
|
|
528
|
+
publicSwap: true,
|
|
529
|
+
liquidity_gt: 0,
|
|
530
|
+
liquidity_lt: 1000000000
|
|
531
|
+
}
|
|
532
|
+
) {
|
|
533
|
+
id
|
|
534
|
+
liquidity
|
|
535
|
+
tokens {
|
|
536
|
+
address
|
|
537
|
+
decimals
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}`;
|
|
541
|
+
const { data } = await this.dexHelper.httpRequest.post<{
|
|
542
|
+
data: {
|
|
543
|
+
pools: {
|
|
544
|
+
id: Address;
|
|
545
|
+
liquidity: FractionAsString;
|
|
546
|
+
tokens: {
|
|
547
|
+
address: Address;
|
|
548
|
+
decimals: number;
|
|
549
|
+
}[];
|
|
550
|
+
}[];
|
|
551
|
+
};
|
|
552
|
+
}>(this.config.subgraphURL, { query, variables }, SUBGRAPH_TIMEOUT);
|
|
553
|
+
|
|
554
|
+
if (!(data && data.pools))
|
|
555
|
+
throw new Error(
|
|
556
|
+
`Error ${this.dexKey} Subgraph: couldn't fetch the pools from the subgraph`,
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
return data.pools.map(pool => ({
|
|
560
|
+
exchange: this.dexKey,
|
|
561
|
+
address: pool.id,
|
|
562
|
+
connectorTokens: pool.tokens.reduce<Token[]>(
|
|
563
|
+
(acc, { decimals, address }) => {
|
|
564
|
+
if (address !== tokenAddress) acc.push({ decimals, address });
|
|
565
|
+
return acc;
|
|
566
|
+
},
|
|
567
|
+
[],
|
|
568
|
+
),
|
|
569
|
+
liquidityUSD: parseFloat(pool.liquidity),
|
|
570
|
+
}));
|
|
571
|
+
}
|
|
572
|
+
}
|