@dolomite-exchange/zap-sdk 0.3.21 → 0.3.23
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/dist/src/lib/Constants.js +220 -170
- package/dist/src/lib/Constants.js.map +1 -1
- package/package.json +4 -3
- package/src/DolomiteZap.ts +940 -0
- package/src/abis/IArbitrumGasInfo.json +198 -0
- package/src/abis/IDolomiteMarginExchangeWrapper.json +80 -0
- package/src/abis/IERC20.json +185 -0
- package/src/abis/IGmxV2DataStore.json +101 -0
- package/src/abis/IGmxV2Reader.json +3487 -0
- package/src/abis/IIsolationModeFactory.json +902 -0
- package/src/abis/types/IArbitrumGasInfo.ts +299 -0
- package/src/abis/types/IERC20.ts +269 -0
- package/src/abis/types/IGmxV2DataStore.ts +170 -0
- package/src/abis/types/IGmxV2Reader.ts +825 -0
- package/src/clients/AggregatorClient.ts +22 -0
- package/src/clients/DolomiteClient.ts +301 -0
- package/src/clients/IsolationModeClient.ts +75 -0
- package/src/clients/OdosAggregator.ts +107 -0
- package/src/clients/OogaBoogaAggregator.ts +76 -0
- package/src/clients/ParaswapAggregator.ts +135 -0
- package/src/index.ts +29 -0
- package/src/lib/ApiTypes.ts +241 -0
- package/src/lib/Constants.ts +1441 -0
- package/src/lib/Environment.ts +1 -0
- package/src/lib/GraphqlPageable.ts +23 -0
- package/src/lib/LocalCache.ts +34 -0
- package/src/lib/Logger.ts +59 -0
- package/src/lib/MathUtils.ts +13 -0
- package/src/lib/Utils.ts +52 -0
- package/src/lib/estimators/GmxV2GmEstimator.ts +349 -0
- package/src/lib/estimators/PendlePtEstimatorV3.ts +321 -0
- package/src/lib/estimators/PendleYtEstimatorV3.ts +77 -0
- package/src/lib/estimators/SimpleEstimator.ts +16 -0
- package/src/lib/estimators/StandardEstimator.ts +137 -0
- package/src/lib/graphql-types.ts +19 -0
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { ethers } from 'ethers';
|
|
3
|
+
import AggregatorClient from './clients/AggregatorClient';
|
|
4
|
+
import DolomiteClient from './clients/DolomiteClient';
|
|
5
|
+
import OdosAggregator from './clients/OdosAggregator';
|
|
6
|
+
import OogaBoogaAggregator from './clients/OogaBoogaAggregator';
|
|
7
|
+
import ParaswapAggregator from './clients/ParaswapAggregator';
|
|
8
|
+
import {
|
|
9
|
+
Address,
|
|
10
|
+
ApiAsyncAction,
|
|
11
|
+
ApiAsyncActionType,
|
|
12
|
+
ApiAsyncTradeType,
|
|
13
|
+
ApiMarket,
|
|
14
|
+
ApiMarketHelper,
|
|
15
|
+
ApiOraclePrice,
|
|
16
|
+
ApiToken,
|
|
17
|
+
ApiWrapperHelper,
|
|
18
|
+
ApiWrapperInfo,
|
|
19
|
+
BlockTag,
|
|
20
|
+
EstimateOutputResult,
|
|
21
|
+
GenericTraderParam,
|
|
22
|
+
GenericTraderType,
|
|
23
|
+
Integer,
|
|
24
|
+
MarketId,
|
|
25
|
+
MinimalApiToken,
|
|
26
|
+
Network,
|
|
27
|
+
ReferralOutput,
|
|
28
|
+
ZapConfig,
|
|
29
|
+
ZapOutputParam,
|
|
30
|
+
} from './lib/ApiTypes';
|
|
31
|
+
import {
|
|
32
|
+
ADDRESS_ZERO,
|
|
33
|
+
ApiMarketConverter,
|
|
34
|
+
BYTES_EMPTY,
|
|
35
|
+
getGmxV2IsolationModeAsset,
|
|
36
|
+
getPendlePtMarketForIsolationModeToken,
|
|
37
|
+
INTEGERS,
|
|
38
|
+
INVALID_NAME,
|
|
39
|
+
ISOLATION_MODE_CONVERSION_MARKET_ID_MAP,
|
|
40
|
+
LIQUIDITY_TOKEN_CONVERSION_MARKET_ID_MAP,
|
|
41
|
+
} from './lib/Constants';
|
|
42
|
+
import { LocalCache } from './lib/LocalCache';
|
|
43
|
+
import Logger from './lib/Logger';
|
|
44
|
+
import { toChecksumOpt, zapOutputParamIsInvalid } from './lib/Utils';
|
|
45
|
+
|
|
46
|
+
const ONE_HOUR = 60 * 60;
|
|
47
|
+
|
|
48
|
+
const THIRTY_BASIS_POINTS = 0.003;
|
|
49
|
+
|
|
50
|
+
const marketsKey = 'MARKETS';
|
|
51
|
+
const marketHelpersKey = 'MARKET_HELPERS';
|
|
52
|
+
|
|
53
|
+
const INVALID_ESTIMATION = {
|
|
54
|
+
amountOut: INTEGERS.NEGATIVE_ONE,
|
|
55
|
+
tradeData: BYTES_EMPTY,
|
|
56
|
+
extraData: undefined,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export interface DolomiteZapConfig {
|
|
60
|
+
/**
|
|
61
|
+
* The network on which this instance of the Dolomite Zap is running.
|
|
62
|
+
*/
|
|
63
|
+
network: Network;
|
|
64
|
+
/**
|
|
65
|
+
* The URL of the subgraph to use for fetching market data.
|
|
66
|
+
*/
|
|
67
|
+
subgraphUrl: string;
|
|
68
|
+
/**
|
|
69
|
+
* The web3 provider to use for fetching on-chain data.
|
|
70
|
+
*/
|
|
71
|
+
web3Provider: ethers.providers.Provider;
|
|
72
|
+
/**
|
|
73
|
+
* The number of seconds to cache market data for. Defaults to 1 hour (3600s).
|
|
74
|
+
*/
|
|
75
|
+
cacheSeconds?: number;
|
|
76
|
+
/**
|
|
77
|
+
* True if these zaps are for processing liquidations or false for ordinary zaps.
|
|
78
|
+
*/
|
|
79
|
+
defaultIsLiquidation?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* The default slippage tolerance to use when estimating output. Defaults to 0.3% (0.003).
|
|
82
|
+
*/
|
|
83
|
+
defaultSlippageTolerance?: number;
|
|
84
|
+
/**
|
|
85
|
+
* The default block tag to use when fetching on-chain data. Defaults to 'latest'.
|
|
86
|
+
*/
|
|
87
|
+
defaultBlockTag?: BlockTag;
|
|
88
|
+
/**
|
|
89
|
+
* The referral information to use for the various aggregators. Defaults to undefined.
|
|
90
|
+
*/
|
|
91
|
+
referralInfo?: ReferralOutput;
|
|
92
|
+
/**
|
|
93
|
+
* True if the Dolomite proxy server should be used for aggregators that support it. The proxy server is used to make
|
|
94
|
+
* the API requests consistent and prevent browser plugins from blocking requests. Defaults to true.
|
|
95
|
+
*/
|
|
96
|
+
useProxyServer?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The multiplier to apply to any gas prices being used for estimating execution fees for intent-driven calls (like
|
|
99
|
+
* GMX V2).
|
|
100
|
+
*/
|
|
101
|
+
gasMultiplier?: BigNumber;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class DolomiteZap {
|
|
105
|
+
public readonly network: Network;
|
|
106
|
+
public readonly validAggregators: AggregatorClient[];
|
|
107
|
+
private readonly _defaultIsLiquidation: boolean;
|
|
108
|
+
private client: DolomiteClient;
|
|
109
|
+
private marketsCache: LocalCache<Record<string, ApiMarket>>;
|
|
110
|
+
private marketHelpersCache: LocalCache<Record<string, ApiMarketHelper>>;
|
|
111
|
+
private readonly _web3Provider: ethers.providers.Provider;
|
|
112
|
+
|
|
113
|
+
public constructor(
|
|
114
|
+
{
|
|
115
|
+
network,
|
|
116
|
+
subgraphUrl,
|
|
117
|
+
web3Provider,
|
|
118
|
+
cacheSeconds = ONE_HOUR,
|
|
119
|
+
defaultIsLiquidation = false,
|
|
120
|
+
defaultSlippageTolerance = THIRTY_BASIS_POINTS,
|
|
121
|
+
defaultBlockTag = 'latest',
|
|
122
|
+
referralInfo = {
|
|
123
|
+
odosReferralCode: undefined,
|
|
124
|
+
oogaBoogaApiKey: undefined,
|
|
125
|
+
referralAddress: undefined,
|
|
126
|
+
},
|
|
127
|
+
useProxyServer = true,
|
|
128
|
+
gasMultiplier = new BigNumber('1'),
|
|
129
|
+
}: DolomiteZapConfig,
|
|
130
|
+
) {
|
|
131
|
+
this.network = network;
|
|
132
|
+
this._subgraphUrl = subgraphUrl;
|
|
133
|
+
this._web3Provider = web3Provider;
|
|
134
|
+
this._defaultIsLiquidation = defaultIsLiquidation;
|
|
135
|
+
this._defaultSlippageTolerance = defaultSlippageTolerance;
|
|
136
|
+
this._defaultBlockTag = defaultBlockTag;
|
|
137
|
+
|
|
138
|
+
this.client = new DolomiteClient(network, subgraphUrl, web3Provider, gasMultiplier);
|
|
139
|
+
this.marketsCache = new LocalCache<Record<string, ApiMarket>>(cacheSeconds);
|
|
140
|
+
this.marketHelpersCache = new LocalCache<Record<string, ApiMarketHelper>>(cacheSeconds);
|
|
141
|
+
|
|
142
|
+
this.validAggregators = this.getAllAggregators(network, referralInfo, useProxyServer)
|
|
143
|
+
.filter(aggregator => aggregator.isValidForNetwork());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _subgraphUrl: string;
|
|
147
|
+
|
|
148
|
+
public get subgraphUrl(): string {
|
|
149
|
+
return this._subgraphUrl;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public set subgraphUrl(newSubgraphUrl: string) {
|
|
153
|
+
this._subgraphUrl = newSubgraphUrl;
|
|
154
|
+
this.client.subgraphUrl = newSubgraphUrl;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public get web3Provider(): ethers.providers.Provider {
|
|
158
|
+
return this._web3Provider;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _defaultSlippageTolerance: number;
|
|
162
|
+
|
|
163
|
+
public get defaultSlippageTolerance(): number {
|
|
164
|
+
return this._defaultSlippageTolerance;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private _defaultBlockTag: BlockTag;
|
|
168
|
+
|
|
169
|
+
public get defaultBlockTag(): BlockTag {
|
|
170
|
+
return this._defaultBlockTag;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public get defaultIsLiquidation(): boolean {
|
|
174
|
+
return this._defaultIsLiquidation;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static copyForWrapper(
|
|
178
|
+
effectiveOutputMarketIds: BigNumber[],
|
|
179
|
+
marketIdsPaths: Integer[][],
|
|
180
|
+
amountsPaths: Integer[][],
|
|
181
|
+
traderParamsArrays: GenericTraderParam[][],
|
|
182
|
+
executionFees: BigNumber[],
|
|
183
|
+
): [MarketId[][], BigNumber[][], GenericTraderParam[][], BigNumber[]] {
|
|
184
|
+
return effectiveOutputMarketIds.reduce(
|
|
185
|
+
(acc, outputMarketId) => {
|
|
186
|
+
marketIdsPaths.forEach((path, i) => {
|
|
187
|
+
if (!outputMarketId.eq(path[path.length - 1])) {
|
|
188
|
+
acc[0].push([...path, outputMarketId]);
|
|
189
|
+
} else {
|
|
190
|
+
acc[0].push([...path]);
|
|
191
|
+
}
|
|
192
|
+
acc[1].push([...amountsPaths[i]]);
|
|
193
|
+
acc[2].push([...traderParamsArrays[i]]);
|
|
194
|
+
acc[3].push(...executionFees);
|
|
195
|
+
});
|
|
196
|
+
return acc;
|
|
197
|
+
},
|
|
198
|
+
[
|
|
199
|
+
[] as MarketId[][],
|
|
200
|
+
[] as BigNumber[][],
|
|
201
|
+
[] as GenericTraderParam[][],
|
|
202
|
+
[] as BigNumber[],
|
|
203
|
+
],
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public async forceRefreshCache(): Promise<void> {
|
|
208
|
+
await this.getMarketIdToMarketMap(true);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public getIsAsyncAssetByMarketId(marketId: MarketId): boolean {
|
|
212
|
+
return !!this.getIsolationModeConverterByMarketId(marketId)?.isAsync;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public getAsyncAssetOutputMarketsByMarketId(marketId: MarketId): MarketId[] | undefined {
|
|
216
|
+
const converter = this.getIsolationModeConverterByMarketId(marketId);
|
|
217
|
+
if (!converter) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const gmMarket = getGmxV2IsolationModeAsset(this.network, converter.tokenAddress);
|
|
222
|
+
if (!gmMarket) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const longMarketId = gmMarket.longTokenId;
|
|
227
|
+
if (longMarketId) {
|
|
228
|
+
return [longMarketId, gmMarket.shortTokenId];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return [gmMarket.shortTokenId];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public getPendleMarketByIsolationModeAddress(isolationModeAddress: Address): Address | undefined {
|
|
235
|
+
return getPendlePtMarketForIsolationModeToken(this.network, isolationModeAddress);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public setDefaultSlippageTolerance(slippageTolerance: number): void {
|
|
239
|
+
this._defaultSlippageTolerance = slippageTolerance;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public setDefaultBlockTag(blockTag: BlockTag): void {
|
|
243
|
+
this._defaultBlockTag = blockTag;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public setMarketsToAdd(marketsToAdd: ApiMarket[]): void {
|
|
247
|
+
this.client.setMarketsToAdd(marketsToAdd);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public getIsolationModeConverterByMarketId(marketId: MarketId): ApiMarketConverter | undefined {
|
|
251
|
+
return ISOLATION_MODE_CONVERSION_MARKET_ID_MAP[this.network][marketId.toFixed()];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public getLiquidityTokenConverterByMarketId(marketId: MarketId): ApiMarketConverter | undefined {
|
|
255
|
+
return LIQUIDITY_TOKEN_CONVERSION_MARKET_ID_MAP[this.network][marketId.toFixed()];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
*
|
|
260
|
+
* @param tokenIn The input token for the zap
|
|
261
|
+
* @param amountIn The input amount for the zap
|
|
262
|
+
* @param tokenOut The output token for the zap
|
|
263
|
+
* @param amountOutMin The minimum amount out required for the swap to be considered valid
|
|
264
|
+
* @param txOrigin The address that will execute the transaction
|
|
265
|
+
* @param config The additional config for zapping
|
|
266
|
+
* @return {Promise<ZapOutputParam[]>} A list of outputs that can be used to execute the trade. The outputs are
|
|
267
|
+
* sorted by execution, with the best ones being first.
|
|
268
|
+
*/
|
|
269
|
+
public async getSwapExactTokensForTokensParams(
|
|
270
|
+
tokenIn: ApiToken | MinimalApiToken,
|
|
271
|
+
amountIn: Integer,
|
|
272
|
+
tokenOut: ApiToken | MinimalApiToken,
|
|
273
|
+
amountOutMin: Integer,
|
|
274
|
+
txOrigin: Address,
|
|
275
|
+
config?: Partial<ZapConfig>,
|
|
276
|
+
): Promise<ZapOutputParam[]> {
|
|
277
|
+
const actualConfig: ZapConfig = {
|
|
278
|
+
isLiquidation: config?.isLiquidation ?? this.defaultIsLiquidation,
|
|
279
|
+
isVaporizable: config?.isVaporizable ?? false,
|
|
280
|
+
slippageTolerance: config?.slippageTolerance ?? this.defaultSlippageTolerance,
|
|
281
|
+
blockTag: config?.blockTag ?? this._defaultBlockTag,
|
|
282
|
+
filterOutZapsWithInsufficientOutput: config?.filterOutZapsWithInsufficientOutput ?? true,
|
|
283
|
+
subAccountNumber: config?.subAccountNumber,
|
|
284
|
+
disallowAggregator: config?.disallowAggregator ?? false,
|
|
285
|
+
gasPriceInWei: config?.gasPriceInWei,
|
|
286
|
+
};
|
|
287
|
+
const marketsMap = await this.getMarketIdToMarketMap(false);
|
|
288
|
+
const marketHelpersMap = await this.getMarketHelpersMap(marketsMap);
|
|
289
|
+
const inputMarket = marketsMap[tokenIn.marketId.toFixed()];
|
|
290
|
+
const outputMarket = marketsMap[tokenOut.marketId.toFixed()];
|
|
291
|
+
|
|
292
|
+
if (!inputMarket) {
|
|
293
|
+
return Promise.reject(new Error(`Invalid tokenIn: ${tokenIn.symbol} / ${tokenIn.marketId}`));
|
|
294
|
+
} else if (!outputMarket) {
|
|
295
|
+
return Promise.reject(new Error(`Invalid tokenOut: ${tokenOut.symbol} / ${tokenOut.marketId}`));
|
|
296
|
+
} else if (amountIn.lte(INTEGERS.ZERO)) {
|
|
297
|
+
return Promise.reject(new Error('Invalid amountIn. Must be greater than 0'));
|
|
298
|
+
} else if (amountOutMin.lte(INTEGERS.ZERO)) {
|
|
299
|
+
return Promise.reject(new Error('Invalid amountOutMin. Must be greater than 0'));
|
|
300
|
+
} else if (!toChecksumOpt(txOrigin)) {
|
|
301
|
+
return Promise.reject(new Error('Invalid address for txOrigin'));
|
|
302
|
+
} else if (actualConfig.slippageTolerance < 0 || actualConfig.slippageTolerance > 0.1) {
|
|
303
|
+
return Promise.reject(new Error('Invalid slippageTolerance. Must be between 0 and 0.1 (10%)'));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let marketIdsPaths: MarketId[][] = [];
|
|
307
|
+
let amountsPaths: BigNumber[][] = [];
|
|
308
|
+
let traderParamsArrays: GenericTraderParam[][] = [];
|
|
309
|
+
let executionFees: BigNumber[] = [];
|
|
310
|
+
|
|
311
|
+
const {
|
|
312
|
+
effectiveInputMarketIds,
|
|
313
|
+
} = await this.calculateUnwrapperAmountsIfNecessary(
|
|
314
|
+
inputMarket,
|
|
315
|
+
amountIn,
|
|
316
|
+
outputMarket,
|
|
317
|
+
marketIdsPaths,
|
|
318
|
+
amountsPaths,
|
|
319
|
+
traderParamsArrays,
|
|
320
|
+
executionFees,
|
|
321
|
+
actualConfig,
|
|
322
|
+
marketsMap,
|
|
323
|
+
marketHelpersMap,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const wrapperUsage = this.calculateWrapperUsages(
|
|
327
|
+
outputMarket,
|
|
328
|
+
[outputMarket.marketId],
|
|
329
|
+
marketIdsPaths,
|
|
330
|
+
amountsPaths,
|
|
331
|
+
traderParamsArrays,
|
|
332
|
+
executionFees,
|
|
333
|
+
marketsMap,
|
|
334
|
+
marketHelpersMap,
|
|
335
|
+
);
|
|
336
|
+
const { effectiveOutputMarketIds, wrapperInfos, wrapperHelpers, isIsolationModeWrappers } = wrapperUsage;
|
|
337
|
+
marketIdsPaths = wrapperUsage.marketIdsPaths;
|
|
338
|
+
amountsPaths = wrapperUsage.amountsPaths;
|
|
339
|
+
traderParamsArrays = wrapperUsage.traderParamsArrays;
|
|
340
|
+
executionFees = wrapperUsage.executionFees;
|
|
341
|
+
const { outputMarkets } = wrapperUsage;
|
|
342
|
+
|
|
343
|
+
if (
|
|
344
|
+
effectiveInputMarketIds.length !== marketIdsPaths.length
|
|
345
|
+
&& effectiveOutputMarketIds.length !== marketIdsPaths.length
|
|
346
|
+
) {
|
|
347
|
+
// eslint-disable-next-line max-len
|
|
348
|
+
return Promise.reject(new Error(`Developer error: marketIds length does not match <${effectiveInputMarketIds.length}, ${marketIdsPaths.length}>`));
|
|
349
|
+
} else if (marketIdsPaths.length !== amountsPaths.length) {
|
|
350
|
+
// eslint-disable-next-line max-len
|
|
351
|
+
return Promise.reject(new Error(`Developer error: amountsPaths length does not match <${amountsPaths.length}, ${marketIdsPaths.length}>`));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < effectiveInputMarketIds.length; i += 1) {
|
|
355
|
+
const effectiveInputMarketId = effectiveInputMarketIds[i];
|
|
356
|
+
for (let j = 0; j < effectiveOutputMarketIds.length; j += 1) {
|
|
357
|
+
const effectiveOutputMarketId = effectiveOutputMarketIds[j];
|
|
358
|
+
if (!effectiveInputMarketId.eq(effectiveOutputMarketId)) {
|
|
359
|
+
const c = i * effectiveOutputMarketIds.length + j;
|
|
360
|
+
const effectiveInputMarket = marketsMap[effectiveInputMarketId.toFixed()];
|
|
361
|
+
const effectiveOutputMarket = marketsMap[effectiveOutputMarketId.toFixed()];
|
|
362
|
+
const aggregatorOutputOrUndefinedList = await Promise.all(
|
|
363
|
+
this.validAggregators.map(async aggregator => {
|
|
364
|
+
if (actualConfig.disallowAggregator) {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
return await aggregator.getSwapExactTokensForTokensData(
|
|
370
|
+
effectiveInputMarket,
|
|
371
|
+
amountsPaths[c][amountsPaths[c].length - 1],
|
|
372
|
+
effectiveOutputMarket,
|
|
373
|
+
INTEGERS.ONE,
|
|
374
|
+
txOrigin,
|
|
375
|
+
actualConfig,
|
|
376
|
+
);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
// eslint-disable-next-line no-loop-func
|
|
384
|
+
aggregatorOutputOrUndefinedList.forEach((aggregatorOutput, aggregatorIndex) => {
|
|
385
|
+
if (aggregatorIndex === 0) {
|
|
386
|
+
amountsPaths[c] = amountsPaths[c].concat(
|
|
387
|
+
aggregatorOutput?.expectedAmountOut ?? INVALID_ESTIMATION.amountOut,
|
|
388
|
+
);
|
|
389
|
+
traderParamsArrays[c] = traderParamsArrays[c].concat({
|
|
390
|
+
traderType: GenericTraderType.ExternalLiquidity,
|
|
391
|
+
makerAccountIndex: 0,
|
|
392
|
+
trader: aggregatorOutput?.traderAddress ?? ADDRESS_ZERO,
|
|
393
|
+
tradeData: aggregatorOutput?.tradeData ?? BYTES_EMPTY,
|
|
394
|
+
readableName: aggregatorOutput?.readableName ?? INVALID_NAME,
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
marketIdsPaths.push([...marketIdsPaths[c]]);
|
|
398
|
+
amountsPaths.push(
|
|
399
|
+
amountsPaths[c].slice(0, -1)
|
|
400
|
+
.concat(aggregatorOutput?.expectedAmountOut ?? INVALID_ESTIMATION.amountOut),
|
|
401
|
+
);
|
|
402
|
+
traderParamsArrays.push(
|
|
403
|
+
traderParamsArrays[c].slice(0, -1).concat({
|
|
404
|
+
traderType: GenericTraderType.ExternalLiquidity,
|
|
405
|
+
makerAccountIndex: 0,
|
|
406
|
+
trader: aggregatorOutput?.traderAddress ?? ADDRESS_ZERO,
|
|
407
|
+
tradeData: aggregatorOutput?.tradeData ?? BYTES_EMPTY,
|
|
408
|
+
readableName: aggregatorOutput?.readableName ?? INVALID_NAME,
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
executionFees.push(executionFees[c]);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await this.calculateWrapperAmounts(
|
|
419
|
+
wrapperInfos,
|
|
420
|
+
wrapperHelpers,
|
|
421
|
+
isIsolationModeWrappers,
|
|
422
|
+
outputMarkets,
|
|
423
|
+
marketIdsPaths,
|
|
424
|
+
amountsPaths,
|
|
425
|
+
traderParamsArrays,
|
|
426
|
+
executionFees,
|
|
427
|
+
actualConfig,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const tokensPaths = marketIdsPaths.map<ApiToken[]>(marketIdsPath => {
|
|
431
|
+
return marketIdsPath.map(marketId => ({
|
|
432
|
+
marketId,
|
|
433
|
+
symbol: marketsMap[marketId.toFixed()].symbol,
|
|
434
|
+
name: marketsMap[marketId.toFixed()].name,
|
|
435
|
+
tokenAddress: marketsMap[marketId.toFixed()].tokenAddress,
|
|
436
|
+
decimals: marketsMap[marketId.toFixed()].decimals,
|
|
437
|
+
}));
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Unify the min amount out to be the same for UX's sake
|
|
441
|
+
const expectedAmountOut = amountsPaths.reduce((max, amountsPath) => {
|
|
442
|
+
const current = amountsPath[amountsPath.length - 1];
|
|
443
|
+
if (current.gt(max)) {
|
|
444
|
+
return current;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return max;
|
|
448
|
+
}, INTEGERS.ZERO);
|
|
449
|
+
|
|
450
|
+
const minAmountOut = expectedAmountOut
|
|
451
|
+
.multipliedBy(1 - actualConfig.slippageTolerance)
|
|
452
|
+
.integerValue(BigNumber.ROUND_DOWN);
|
|
453
|
+
|
|
454
|
+
const result = marketIdsPaths.map<ZapOutputParam>((_, i) => {
|
|
455
|
+
const expectedAmountOutBeforeOverwrite = amountsPaths[i][amountsPaths[i].length - 1];
|
|
456
|
+
if (!amountsPaths[i].some(amount => amount.eq(INVALID_ESTIMATION.amountOut))) {
|
|
457
|
+
amountsPaths[i][amountsPaths[i].length - 1] = minAmountOut;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
marketIdsPath: marketIdsPaths[i],
|
|
461
|
+
tokensPath: tokensPaths[i],
|
|
462
|
+
expectedAmountOut: expectedAmountOutBeforeOverwrite,
|
|
463
|
+
amountWeisPath: amountsPaths[i],
|
|
464
|
+
traderParams: traderParamsArrays[i],
|
|
465
|
+
makerAccounts: [],
|
|
466
|
+
originalAmountOutMin: amountOutMin,
|
|
467
|
+
executionFee: executionFees[i].gt(INTEGERS.ZERO) ? executionFees[i] : undefined,
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const zaps = result
|
|
472
|
+
.filter(p => !zapOutputParamIsInvalid(p))
|
|
473
|
+
.sort((a, b) => (a.expectedAmountOut.gt(b.expectedAmountOut) ? -1 : 1));
|
|
474
|
+
|
|
475
|
+
if (actualConfig.filterOutZapsWithInsufficientOutput) {
|
|
476
|
+
return zaps.filter(zap => zap.expectedAmountOut.gte(amountOutMin));
|
|
477
|
+
} else {
|
|
478
|
+
return zaps;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
*
|
|
484
|
+
* @param tokenIn The input token for the zap. Must be an async market.
|
|
485
|
+
* @param amountIn The input amount for the zap. This should be held amount of collateral seized for liquidation
|
|
486
|
+
* @param tokenOut The output token for the zap. Must not be an async market.
|
|
487
|
+
* @param amountOutMin The minimum amount out required for the swap to be considered valid
|
|
488
|
+
* @param txOrigin The address that will execute the transaction
|
|
489
|
+
* @param marketIdToActionsMap A mapping from output market to a list of async deposits/withdrawals that output a
|
|
490
|
+
* valid output token from `tokenIn`
|
|
491
|
+
* @param marketIdToOracleMap A mapping from market ID to the corresponding market's oracle price
|
|
492
|
+
* @param config The additional config for zapping
|
|
493
|
+
* @return {Promise<ZapOutputParam[]>} A list of outputs that can be used to execute the trade. The outputs are
|
|
494
|
+
* sorted by execution, with the best ones being first.
|
|
495
|
+
*/
|
|
496
|
+
public async getSwapExactAsyncTokensForTokensParamsForLiquidation(
|
|
497
|
+
tokenIn: ApiToken | MinimalApiToken,
|
|
498
|
+
amountIn: Integer,
|
|
499
|
+
tokenOut: ApiToken | MinimalApiToken,
|
|
500
|
+
amountOutMin: Integer,
|
|
501
|
+
txOrigin: Address,
|
|
502
|
+
marketIdToActionsMap: Record<string, ApiAsyncAction[]>,
|
|
503
|
+
marketIdToOracleMap: Record<string, ApiOraclePrice>,
|
|
504
|
+
config?: Partial<ZapConfig>,
|
|
505
|
+
): Promise<ZapOutputParam[]> {
|
|
506
|
+
if (typeof config?.isLiquidation === 'undefined' || !config.isLiquidation) {
|
|
507
|
+
return Promise.reject(new Error('Config must include `isLiquidation=true`'));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const marketsMap = await this.getMarketIdToMarketMap(false);
|
|
511
|
+
const allActions = Object.values(marketIdToActionsMap);
|
|
512
|
+
if (!this.getIsAsyncAssetByMarketId(tokenIn.marketId)) {
|
|
513
|
+
return Promise.reject(new Error('tokenIn must be an async asset!'));
|
|
514
|
+
} else if (this.getIsAsyncAssetByMarketId(tokenOut.marketId)) {
|
|
515
|
+
return Promise.reject(new Error('tokenOut must not be an async asset!'));
|
|
516
|
+
} else if (allActions.length === 0 || allActions.every(a => a.length === 0)) {
|
|
517
|
+
return Promise.reject(new Error('marketIdToActionsMap must not be empty'));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const outputWeiFromActionsWithMarket = Object.keys(marketIdToActionsMap).reduce(
|
|
521
|
+
(acc, outputMarketId) => {
|
|
522
|
+
const actions = marketIdToActionsMap[outputMarketId];
|
|
523
|
+
const oraclePriceUsd = marketIdToOracleMap[outputMarketId]?.oraclePrice;
|
|
524
|
+
if (!oraclePriceUsd) {
|
|
525
|
+
throw new Error(`Oracle price for ${outputMarketId} could not be found!`);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const outputValue = actions.reduce(
|
|
529
|
+
(sum, action) => {
|
|
530
|
+
if (sum.inputValue.gt(INTEGERS.ZERO)) {
|
|
531
|
+
if (action.inputToken.marketId.eq(tokenIn.marketId)) {
|
|
532
|
+
const usedInputAmount = sum.inputValue.lt(action.inputAmount)
|
|
533
|
+
? sum.inputValue
|
|
534
|
+
: action.inputAmount;
|
|
535
|
+
const usedOutputAmount = sum.inputValue.lt(action.inputAmount)
|
|
536
|
+
? action.outputAmount.times(sum.inputValue).dividedToIntegerBy(action.inputAmount)
|
|
537
|
+
: action.outputAmount;
|
|
538
|
+
|
|
539
|
+
sum.inputValue = sum.inputValue.minus(usedInputAmount);
|
|
540
|
+
sum.outputValue = sum.outputValue.plus(usedOutputAmount);
|
|
541
|
+
sum.outputValueUsd = sum.outputValueUsd.plus(usedOutputAmount.times(oraclePriceUsd));
|
|
542
|
+
} else if (action.outputToken.marketId.eq(tokenIn.marketId)) {
|
|
543
|
+
const usedInputAmount = sum.inputValue.lt(action.outputAmount)
|
|
544
|
+
? sum.inputValue
|
|
545
|
+
: action.outputAmount;
|
|
546
|
+
const usedOutputAmount = sum.inputValue.lt(action.outputAmount)
|
|
547
|
+
? action.inputAmount.times(sum.inputValue).dividedToIntegerBy(action.outputAmount)
|
|
548
|
+
: action.inputAmount;
|
|
549
|
+
|
|
550
|
+
sum.inputValue = sum.inputValue.minus(usedInputAmount);
|
|
551
|
+
sum.outputValue = sum.outputValue.plus(usedOutputAmount);
|
|
552
|
+
sum.outputValueUsd = sum.outputValueUsd.plus(usedOutputAmount.times(oraclePriceUsd));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return sum;
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
inputValue: amountIn,
|
|
559
|
+
outputValue: INTEGERS.ZERO,
|
|
560
|
+
outputValueUsd: INTEGERS.ZERO,
|
|
561
|
+
outputMarket: marketsMap[outputMarketId],
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
if (acc.outputValueUsd.gt(outputValue.outputValueUsd)) {
|
|
566
|
+
return acc;
|
|
567
|
+
} else {
|
|
568
|
+
return outputValue;
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
outputValue: INTEGERS.ZERO,
|
|
573
|
+
outputValueUsd: INTEGERS.ZERO,
|
|
574
|
+
outputMarket: marketsMap[Object.keys(marketIdToActionsMap)[0]],
|
|
575
|
+
},
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
const outputToken: MinimalApiToken = {
|
|
579
|
+
marketId: new BigNumber(outputWeiFromActionsWithMarket.outputMarket.marketId.toFixed()),
|
|
580
|
+
symbol: outputWeiFromActionsWithMarket.outputMarket.symbol,
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const actions = marketIdToActionsMap[outputToken.marketId.toFixed()];
|
|
584
|
+
|
|
585
|
+
let outputs: ZapOutputParam[];
|
|
586
|
+
if (outputToken.marketId.eq(tokenOut.marketId)) {
|
|
587
|
+
const expectedAmountOutWithSlippage = outputWeiFromActionsWithMarket.outputValue
|
|
588
|
+
.multipliedBy(1 - (config.slippageTolerance ?? this.defaultSlippageTolerance))
|
|
589
|
+
.integerValue(BigNumber.ROUND_DOWN);
|
|
590
|
+
outputs = [
|
|
591
|
+
{
|
|
592
|
+
marketIdsPath: [tokenIn.marketId, tokenOut.marketId],
|
|
593
|
+
tokensPath: [marketsMap[tokenIn.marketId.toFixed()], marketsMap[tokenOut.marketId.toFixed()]],
|
|
594
|
+
amountWeisPath: [amountIn, expectedAmountOutWithSlippage],
|
|
595
|
+
traderParams: [this.getAsyncUnwrapperTraderParam(tokenIn, actions, config)],
|
|
596
|
+
makerAccounts: [],
|
|
597
|
+
expectedAmountOut: outputWeiFromActionsWithMarket.outputValue,
|
|
598
|
+
originalAmountOutMin: amountOutMin,
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
} else {
|
|
602
|
+
outputs = await this.getSwapExactTokensForTokensParams(
|
|
603
|
+
outputToken,
|
|
604
|
+
outputWeiFromActionsWithMarket.outputValue,
|
|
605
|
+
tokenOut,
|
|
606
|
+
amountOutMin,
|
|
607
|
+
txOrigin,
|
|
608
|
+
config,
|
|
609
|
+
);
|
|
610
|
+
outputs.forEach(output => {
|
|
611
|
+
output.marketIdsPath = [
|
|
612
|
+
new BigNumber(tokenIn.marketId.toFixed()),
|
|
613
|
+
...output.marketIdsPath,
|
|
614
|
+
];
|
|
615
|
+
output.amountWeisPath = [
|
|
616
|
+
amountIn,
|
|
617
|
+
...output.amountWeisPath,
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
output.traderParams = [
|
|
621
|
+
this.getAsyncUnwrapperTraderParam(tokenIn, actions, config),
|
|
622
|
+
...output.traderParams,
|
|
623
|
+
];
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return outputs;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
protected getAllAggregators(
|
|
631
|
+
network: Network,
|
|
632
|
+
referralInfo: ReferralOutput,
|
|
633
|
+
useProxyServer: boolean,
|
|
634
|
+
): AggregatorClient[] {
|
|
635
|
+
const odosAggregator = new OdosAggregator(network, referralInfo.odosReferralCode, useProxyServer);
|
|
636
|
+
const oogaBoogaAggregator = new OogaBoogaAggregator(network, referralInfo.oogaBoogaApiKey);
|
|
637
|
+
const paraswapAggregator = new ParaswapAggregator(network, referralInfo.referralAddress, useProxyServer);
|
|
638
|
+
return [odosAggregator, oogaBoogaAggregator, paraswapAggregator];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
protected async getMarketIdToMarketMap(forceRefresh: boolean): Promise<Record<string, ApiMarket>> {
|
|
642
|
+
if (!forceRefresh) {
|
|
643
|
+
const cachedMarkets = this.marketsCache.get(marketsKey);
|
|
644
|
+
if (cachedMarkets) {
|
|
645
|
+
return cachedMarkets;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const marketsMap = await this.client.getDolomiteMarketsMap();
|
|
650
|
+
this.marketsCache.set(marketsKey, marketsMap);
|
|
651
|
+
return marketsMap;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private async calculateUnwrapperAmountsIfNecessary(
|
|
655
|
+
inputMarket: ApiMarket,
|
|
656
|
+
amountIn: Integer,
|
|
657
|
+
outputMarket: ApiMarket,
|
|
658
|
+
marketIdsPaths: Integer[][],
|
|
659
|
+
amountsPaths: Integer[][],
|
|
660
|
+
traderParamsArrays: GenericTraderParam[][],
|
|
661
|
+
executionFees: Integer[],
|
|
662
|
+
actualConfig: ZapConfig,
|
|
663
|
+
marketsMap: Record<string, ApiMarket>,
|
|
664
|
+
marketHelpersMap: Record<string, ApiMarketHelper>,
|
|
665
|
+
): Promise<{ effectiveInputMarketIds: Integer[] }> {
|
|
666
|
+
let effectiveInputMarketIds: Integer[];
|
|
667
|
+
const inputHelper = marketHelpersMap[inputMarket.marketId.toFixed()];
|
|
668
|
+
const isIsolationModeUnwrapper = inputMarket.isolationModeUnwrapperInfo;
|
|
669
|
+
const unwrapperInfo = inputMarket.isolationModeUnwrapperInfo ?? inputMarket.liquidityTokenUnwrapperInfo;
|
|
670
|
+
const unwrapperHelper = inputHelper.isolationModeUnwrapperHelper ?? inputHelper.liquidityTokenUnwrapperHelper;
|
|
671
|
+
if (unwrapperInfo && unwrapperHelper) {
|
|
672
|
+
effectiveInputMarketIds = unwrapperInfo.outputMarketIds;
|
|
673
|
+
|
|
674
|
+
const estimateResults = await Promise.all(
|
|
675
|
+
effectiveInputMarketIds.map((inputMarketId, i) => {
|
|
676
|
+
if (!marketIdsPaths[i] || marketIdsPaths[i].length === 0) {
|
|
677
|
+
marketIdsPaths[i] = [inputMarket.marketId];
|
|
678
|
+
amountsPaths[i] = [amountIn];
|
|
679
|
+
traderParamsArrays[i] = [];
|
|
680
|
+
executionFees[i] = INTEGERS.ZERO;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (actualConfig.disallowAggregator && !inputMarketId.eq(outputMarket.marketId)) {
|
|
684
|
+
// If the aggregator is disabled, and we cannot connect the input market to the output, don't bother
|
|
685
|
+
return Promise.resolve(INVALID_ESTIMATION);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return unwrapperHelper.estimateOutputFunction(
|
|
689
|
+
amountIn,
|
|
690
|
+
inputMarketId,
|
|
691
|
+
actualConfig,
|
|
692
|
+
).catch(e => {
|
|
693
|
+
Logger.error({
|
|
694
|
+
message: `Caught error while estimating unwrapping: ${e.message}`,
|
|
695
|
+
error: e,
|
|
696
|
+
});
|
|
697
|
+
return INVALID_ESTIMATION;
|
|
698
|
+
});
|
|
699
|
+
}),
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
estimateResults.forEach(({ amountOut, tradeData, extraData }, i) => {
|
|
703
|
+
marketIdsPaths[i] = marketIdsPaths[i].concat(effectiveInputMarketIds[i]);
|
|
704
|
+
amountsPaths[i] = amountsPaths[i].concat(amountOut);
|
|
705
|
+
traderParamsArrays[i] = traderParamsArrays[i].concat({
|
|
706
|
+
traderType: isIsolationModeUnwrapper
|
|
707
|
+
? GenericTraderType.IsolationModeUnwrapper
|
|
708
|
+
: GenericTraderType.ExternalLiquidity,
|
|
709
|
+
makerAccountIndex: 0,
|
|
710
|
+
trader: actualConfig.isLiquidation
|
|
711
|
+
? (unwrapperInfo.unwrapperForLiquidationAddress ?? unwrapperInfo.unwrapperAddress)
|
|
712
|
+
: unwrapperInfo.unwrapperAddress,
|
|
713
|
+
tradeData,
|
|
714
|
+
readableName: unwrapperInfo.readableName,
|
|
715
|
+
});
|
|
716
|
+
executionFees[i] = executionFees[i].plus(extraData?.executionFee ?? INTEGERS.ZERO);
|
|
717
|
+
});
|
|
718
|
+
} else {
|
|
719
|
+
effectiveInputMarketIds = [inputMarket.marketId];
|
|
720
|
+
marketIdsPaths[0] = [inputMarket.marketId];
|
|
721
|
+
amountsPaths[0] = [amountIn];
|
|
722
|
+
traderParamsArrays[0] = [];
|
|
723
|
+
executionFees[0] = INTEGERS.ZERO;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (effectiveInputMarketIds.length === 1) {
|
|
727
|
+
const liquidityMarketId = effectiveInputMarketIds[0];
|
|
728
|
+
const converter = this.getLiquidityTokenConverterByMarketId(liquidityMarketId);
|
|
729
|
+
if (converter) {
|
|
730
|
+
return this.calculateUnwrapperAmountsIfNecessary(
|
|
731
|
+
marketsMap[liquidityMarketId.toFixed()],
|
|
732
|
+
amountsPaths[0][amountsPaths[0].length - 1],
|
|
733
|
+
outputMarket,
|
|
734
|
+
marketIdsPaths,
|
|
735
|
+
amountsPaths,
|
|
736
|
+
traderParamsArrays,
|
|
737
|
+
executionFees,
|
|
738
|
+
actualConfig,
|
|
739
|
+
marketsMap,
|
|
740
|
+
marketHelpersMap,
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return { effectiveInputMarketIds };
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private calculateWrapperUsages(
|
|
749
|
+
outputMarket: ApiMarket,
|
|
750
|
+
effectiveOutputMarketIds: Integer[], // sGLP first --> USDC.e second
|
|
751
|
+
marketIdsPaths: Integer[][],
|
|
752
|
+
amountsPaths: Integer[][],
|
|
753
|
+
traderParamsArrays: GenericTraderParam[][],
|
|
754
|
+
executionFees: Integer[],
|
|
755
|
+
marketsMap: Record<string, ApiMarket>,
|
|
756
|
+
marketHelpers: Record<string, ApiMarketHelper>,
|
|
757
|
+
): CalculatedWrapperUsage {
|
|
758
|
+
const outputHelper = marketHelpers[outputMarket.marketId.toFixed()];
|
|
759
|
+
const isIsolationModeWrapper = !!outputMarket.isolationModeWrapperInfo;
|
|
760
|
+
const wrapperInfo = outputMarket.isolationModeWrapperInfo ?? outputMarket.liquidityTokenWrapperInfo;
|
|
761
|
+
const wrapperHelper = outputHelper.isolationModeWrapperHelper ?? outputHelper.liquidityTokenWrapperHelper;
|
|
762
|
+
|
|
763
|
+
const isIsolationModeWrappers = wrapperInfo ? [!!outputMarket.isolationModeWrapperInfo] : [];
|
|
764
|
+
const wrapperInfos = wrapperInfo ? [wrapperInfo] : [];
|
|
765
|
+
const wrapperHelpers = wrapperHelper ? [wrapperHelper] : [];
|
|
766
|
+
|
|
767
|
+
if (wrapperInfo) {
|
|
768
|
+
// We can't get the amount yet until we know if we need to use an aggregator in the middle
|
|
769
|
+
effectiveOutputMarketIds = wrapperInfo.inputMarketIds;
|
|
770
|
+
|
|
771
|
+
if (effectiveOutputMarketIds.length === 1) {
|
|
772
|
+
const converter = this.getLiquidityTokenConverterByMarketId(effectiveOutputMarketIds[0]);
|
|
773
|
+
if (converter) {
|
|
774
|
+
const innerUsage = this.calculateWrapperUsages(
|
|
775
|
+
marketsMap[effectiveOutputMarketIds[0].toFixed()],
|
|
776
|
+
effectiveOutputMarketIds, // USDC.e (2) first
|
|
777
|
+
marketIdsPaths,
|
|
778
|
+
amountsPaths,
|
|
779
|
+
traderParamsArrays,
|
|
780
|
+
executionFees,
|
|
781
|
+
marketsMap,
|
|
782
|
+
marketHelpers,
|
|
783
|
+
);
|
|
784
|
+
return {
|
|
785
|
+
effectiveOutputMarketIds: innerUsage.effectiveOutputMarketIds,
|
|
786
|
+
marketIdsPaths: innerUsage.marketIdsPaths,
|
|
787
|
+
amountsPaths: innerUsage.amountsPaths,
|
|
788
|
+
traderParamsArrays: innerUsage.traderParamsArrays,
|
|
789
|
+
executionFees: innerUsage.executionFees,
|
|
790
|
+
isIsolationModeWrappers: [...innerUsage.isIsolationModeWrappers, isIsolationModeWrapper],
|
|
791
|
+
wrapperInfos: [...innerUsage.wrapperInfos, ...(wrapperInfo ? [wrapperInfo] : [])],
|
|
792
|
+
wrapperHelpers: [...innerUsage.wrapperHelpers, ...(wrapperHelper ? [wrapperHelper] : [])],
|
|
793
|
+
outputMarkets: [...innerUsage.outputMarkets, outputMarket],
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
[marketIdsPaths, amountsPaths, traderParamsArrays, executionFees] = DolomiteZap.copyForWrapper(
|
|
799
|
+
effectiveOutputMarketIds,
|
|
800
|
+
marketIdsPaths,
|
|
801
|
+
amountsPaths,
|
|
802
|
+
traderParamsArrays,
|
|
803
|
+
executionFees,
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return {
|
|
808
|
+
effectiveOutputMarketIds,
|
|
809
|
+
marketIdsPaths,
|
|
810
|
+
amountsPaths,
|
|
811
|
+
traderParamsArrays,
|
|
812
|
+
executionFees,
|
|
813
|
+
isIsolationModeWrappers,
|
|
814
|
+
wrapperInfos,
|
|
815
|
+
wrapperHelpers,
|
|
816
|
+
outputMarkets: [outputMarket],
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private async calculateWrapperAmounts(
|
|
821
|
+
wrapperInfos: ApiWrapperInfo[],
|
|
822
|
+
wrapperHelpers: ApiWrapperHelper[],
|
|
823
|
+
isIsolationModeWrappers: boolean[],
|
|
824
|
+
outputMarkets: ApiMarket[],
|
|
825
|
+
marketIdsPaths: Integer[][],
|
|
826
|
+
amountsPaths: Integer[][],
|
|
827
|
+
traderParamsArrays: GenericTraderParam[][],
|
|
828
|
+
executionFees: Integer[],
|
|
829
|
+
actualConfig: ZapConfig,
|
|
830
|
+
): Promise<void> {
|
|
831
|
+
if (
|
|
832
|
+
wrapperInfos.length > 0
|
|
833
|
+
&& wrapperInfos.length === wrapperHelpers.length
|
|
834
|
+
&& outputMarkets.length === wrapperHelpers.length
|
|
835
|
+
) {
|
|
836
|
+
for (let i = 0; i < wrapperInfos.length; i += 1) {
|
|
837
|
+
// Append the amounts and trader params for the wrapper
|
|
838
|
+
const wrapperInfo = wrapperInfos[i];
|
|
839
|
+
const wrapperHelper = wrapperHelpers[i];
|
|
840
|
+
for (let j = 0; j < marketIdsPaths.length; j += 1) {
|
|
841
|
+
const marketIdsPath = marketIdsPaths[j];
|
|
842
|
+
const amountsPath = amountsPaths[j];
|
|
843
|
+
let outputEstimate: EstimateOutputResult;
|
|
844
|
+
if (amountsPath.some(a => a.eq(INVALID_ESTIMATION.amountOut))) {
|
|
845
|
+
outputEstimate = INVALID_ESTIMATION;
|
|
846
|
+
} else {
|
|
847
|
+
outputEstimate = await wrapperHelper.estimateOutputFunction(
|
|
848
|
+
amountsPath[amountsPath.length - 1],
|
|
849
|
+
marketIdsPath[marketIdsPath.length - 1],
|
|
850
|
+
actualConfig,
|
|
851
|
+
).catch(e => {
|
|
852
|
+
Logger.error({
|
|
853
|
+
message: `Caught error while estimating wrapping: ${e.message}`,
|
|
854
|
+
error: e,
|
|
855
|
+
});
|
|
856
|
+
return INVALID_ESTIMATION;
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
marketIdsPath.push(outputMarkets[i].marketId);
|
|
861
|
+
amountsPath.push(outputEstimate.amountOut);
|
|
862
|
+
traderParamsArrays[j].push({
|
|
863
|
+
traderType: isIsolationModeWrappers[i]
|
|
864
|
+
? GenericTraderType.IsolationModeWrapper
|
|
865
|
+
: GenericTraderType.ExternalLiquidity,
|
|
866
|
+
makerAccountIndex: 0,
|
|
867
|
+
trader: wrapperInfo.wrapperAddress,
|
|
868
|
+
tradeData: outputEstimate.tradeData,
|
|
869
|
+
readableName: outputEstimate.amountOut.eq(INVALID_ESTIMATION.amountOut)
|
|
870
|
+
? INVALID_NAME
|
|
871
|
+
: wrapperInfo.readableName,
|
|
872
|
+
});
|
|
873
|
+
executionFees[j] = executionFees[j].plus(outputEstimate.extraData?.executionFee ?? INTEGERS.ZERO);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
} else {
|
|
877
|
+
if (outputMarkets.length !== 1) {
|
|
878
|
+
throw new Error('Invalid output markets length!');
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
marketIdsPaths.forEach(marketIdsPath => {
|
|
882
|
+
if (!marketIdsPath[marketIdsPath.length - 1].eq(outputMarkets[0].marketId)) {
|
|
883
|
+
marketIdsPath.push(outputMarkets[0].marketId);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
private getAsyncUnwrapperTraderParam(
|
|
890
|
+
asyncToken: MinimalApiToken,
|
|
891
|
+
actions: ApiAsyncAction[],
|
|
892
|
+
config: Partial<ZapConfig>,
|
|
893
|
+
): GenericTraderParam {
|
|
894
|
+
const converter = this.getIsolationModeConverterByMarketId(asyncToken.marketId)!;
|
|
895
|
+
return {
|
|
896
|
+
traderType: GenericTraderType.IsolationModeUnwrapper,
|
|
897
|
+
tradeData: ethers.utils.defaultAbiCoder.encode(
|
|
898
|
+
['uint8[]', 'bytes32[]', 'bool'],
|
|
899
|
+
[
|
|
900
|
+
actions.map(a => (a.actionType === ApiAsyncActionType.WITHDRAWAL
|
|
901
|
+
? ApiAsyncTradeType.FromWithdrawal
|
|
902
|
+
: ApiAsyncTradeType.FromDeposit)),
|
|
903
|
+
actions.map(a => a.key),
|
|
904
|
+
!config.isVaporizable,
|
|
905
|
+
],
|
|
906
|
+
),
|
|
907
|
+
readableName: converter.unwrapperReadableName,
|
|
908
|
+
trader: converter.unwrapper,
|
|
909
|
+
makerAccountIndex: 0,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
private async getMarketHelpersMap(
|
|
914
|
+
marketsMap: Record<string, ApiMarket>,
|
|
915
|
+
): Promise<Record<string, ApiMarketHelper>> {
|
|
916
|
+
const cachedMarkets = this.marketHelpersCache.get(marketHelpersKey);
|
|
917
|
+
if (cachedMarkets) {
|
|
918
|
+
return cachedMarkets;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const marketHelpersMap = await this.client.getDolomiteMarketHelpers(marketsMap);
|
|
922
|
+
this.marketHelpersCache.set(marketHelpersKey, marketHelpersMap);
|
|
923
|
+
return marketHelpersMap;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Used internally to make data passing cleaner
|
|
929
|
+
*/
|
|
930
|
+
interface CalculatedWrapperUsage {
|
|
931
|
+
effectiveOutputMarketIds: Integer[];
|
|
932
|
+
marketIdsPaths: Integer[][];
|
|
933
|
+
amountsPaths: Integer[][];
|
|
934
|
+
traderParamsArrays: GenericTraderParam[][];
|
|
935
|
+
executionFees: Integer[];
|
|
936
|
+
isIsolationModeWrappers: boolean[];
|
|
937
|
+
wrapperInfos: ApiWrapperInfo[];
|
|
938
|
+
wrapperHelpers: ApiWrapperHelper[];
|
|
939
|
+
outputMarkets: ApiMarket[];
|
|
940
|
+
}
|