@paraswap/dex-lib 3.11.3 → 3.11.4-cables.0
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/build/dex/cables/cables.d.ts +2 -0
- package/build/dex/cables/cables.js +80 -26
- package/build/dex/cables/cables.js.map +1 -1
- package/build/dex/index.js +2 -0
- package/build/dex/index.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/cables/CablesMainnetRFQ.json +1083 -0
- package/src/dex/cables/cables-e2e.test.ts +171 -0
- package/src/dex/cables/cables-integration.test.ts +319 -0
- package/src/dex/cables/cables.ts +766 -0
- package/src/dex/cables/config.ts +13 -0
- package/src/dex/cables/constants.ts +30 -0
- package/src/dex/cables/rate-fetcher.ts +212 -0
- package/src/dex/cables/types.ts +119 -0
- package/src/dex/cables/validators.test.ts +151 -0
- package/src/dex/cables/validators.ts +61 -0
- package/src/dex/index.ts +2 -0
- package/tests/constants-e2e.ts +2 -2
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Address,
|
|
3
|
+
NumberAsString,
|
|
4
|
+
OptimalSwapExchange,
|
|
5
|
+
SwapSide,
|
|
6
|
+
} from '@paraswap/core';
|
|
7
|
+
import { AsyncOrSync } from 'ts-essentials';
|
|
8
|
+
import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
|
|
9
|
+
import { assert } from 'ts-essentials';
|
|
10
|
+
import { Network, ETHER_ADDRESS, NULL_ADDRESS } from '../../constants';
|
|
11
|
+
import { IDexHelper } from '../../dex-helper';
|
|
12
|
+
import {
|
|
13
|
+
AdapterExchangeParam,
|
|
14
|
+
DexExchangeParam,
|
|
15
|
+
ExchangePrices,
|
|
16
|
+
ExchangeTxInfo,
|
|
17
|
+
Logger,
|
|
18
|
+
PoolLiquidity,
|
|
19
|
+
PoolPrices,
|
|
20
|
+
PreprocessTransactionOptions,
|
|
21
|
+
Token,
|
|
22
|
+
TransferFeeParams,
|
|
23
|
+
} from '../../types';
|
|
24
|
+
import { getDexKeysWithNetwork } from '../../utils';
|
|
25
|
+
import { IDex } from '../idex';
|
|
26
|
+
import { SimpleExchange } from '../simple-exchange';
|
|
27
|
+
import { CablesConfig } from './config';
|
|
28
|
+
import {
|
|
29
|
+
CABLES_API_URL,
|
|
30
|
+
CABLES_FIRM_QUOTE_TIMEOUT_MS,
|
|
31
|
+
CABLES_GAS_COST,
|
|
32
|
+
} from './constants';
|
|
33
|
+
import { CablesRateFetcher } from './rate-fetcher';
|
|
34
|
+
import { CablesData, CablesRFQResponse } from './types';
|
|
35
|
+
import mainnetRFQAbi from '../../abi/cables/CablesMainnetRFQ.json';
|
|
36
|
+
import { Interface } from 'ethers/lib/utils';
|
|
37
|
+
import BigNumber from 'bignumber.js';
|
|
38
|
+
import { ethers } from 'ethers';
|
|
39
|
+
import { BI_MAX_UINT256 } from '../../bigint-constants';
|
|
40
|
+
import { B } from '@bgd-labs/aave-address-book/dist/AaveGovernanceV2-WaqoK4ZA';
|
|
41
|
+
|
|
42
|
+
export class Cables extends SimpleExchange implements IDex<any> {
|
|
43
|
+
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
44
|
+
getDexKeysWithNetwork(CablesConfig);
|
|
45
|
+
private rateFetcher: CablesRateFetcher;
|
|
46
|
+
logger: Logger;
|
|
47
|
+
private tokensMap: { [address: string]: Token } = {};
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
readonly network: Network,
|
|
51
|
+
readonly dexKey: string,
|
|
52
|
+
readonly dexHelper: IDexHelper,
|
|
53
|
+
readonly mainnetRFQAddress: string = CablesConfig['Cables'][network]
|
|
54
|
+
.mainnetRFQAddress,
|
|
55
|
+
protected rfqInterface = new Interface(mainnetRFQAbi),
|
|
56
|
+
) {
|
|
57
|
+
super(dexHelper, dexKey);
|
|
58
|
+
this.logger = dexHelper.getLogger(dexKey);
|
|
59
|
+
|
|
60
|
+
this.rateFetcher = new CablesRateFetcher(
|
|
61
|
+
this.dexHelper,
|
|
62
|
+
this.dexKey,
|
|
63
|
+
this.network,
|
|
64
|
+
this.logger,
|
|
65
|
+
{
|
|
66
|
+
rateConfig: {
|
|
67
|
+
pairsReqParams: {
|
|
68
|
+
url: CABLES_API_URL + '/pairs',
|
|
69
|
+
},
|
|
70
|
+
pricesReqParams: {
|
|
71
|
+
url: CABLES_API_URL + '/prices',
|
|
72
|
+
},
|
|
73
|
+
blacklistReqParams: {
|
|
74
|
+
url: CABLES_API_URL + '/blacklist',
|
|
75
|
+
},
|
|
76
|
+
tokensReqParams: {
|
|
77
|
+
url: CABLES_API_URL + '/tokens',
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
pricesIntervalMs: 2000,
|
|
81
|
+
pairsIntervalMs: 10000,
|
|
82
|
+
blacklistIntervalMs: 30000,
|
|
83
|
+
tokensIntervalMs: 30000,
|
|
84
|
+
|
|
85
|
+
pairsCacheKey: 'cablesPairsCacheKey',
|
|
86
|
+
pricesCacheKey: 'cablesPricesCacheKey',
|
|
87
|
+
tokensCacheKey: 'cablesTokensCacheKey',
|
|
88
|
+
blacklistCacheKey: 'cablesBlacklistCacheKey',
|
|
89
|
+
|
|
90
|
+
pairsCacheTTLSecs: 2,
|
|
91
|
+
pricesCacheTTLSecs: 10,
|
|
92
|
+
blacklistCacheTTLSecs: 30,
|
|
93
|
+
tokensCacheTTLSecs: 30,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
hasConstantPriceLargeAmounts: boolean = false;
|
|
100
|
+
|
|
101
|
+
needsSequentialPreprocessing?: boolean | undefined;
|
|
102
|
+
|
|
103
|
+
async preProcessTransaction?(
|
|
104
|
+
optimalSwapExchange: OptimalSwapExchange<CablesData>,
|
|
105
|
+
srcToken: Token,
|
|
106
|
+
destToken: Token,
|
|
107
|
+
side: SwapSide,
|
|
108
|
+
options: PreprocessTransactionOptions,
|
|
109
|
+
): Promise<[OptimalSwapExchange<CablesData>, ExchangeTxInfo]> {
|
|
110
|
+
if (BigInt(optimalSwapExchange.srcAmount) === 0n) {
|
|
111
|
+
throw new Error('getFirmRate failed with srcAmount === 0');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const normalizedSrcToken = this.normalizeToken(srcToken);
|
|
115
|
+
const normalizedDestToken = this.normalizeToken(destToken);
|
|
116
|
+
const swapIdentifier = `${this.dexKey}_${normalizedSrcToken.address}_${normalizedDestToken.address}_${side}`;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const makerToken = normalizedDestToken;
|
|
120
|
+
const takerToken = normalizedSrcToken;
|
|
121
|
+
|
|
122
|
+
const isSell = side === SwapSide.SELL;
|
|
123
|
+
const isBuy = side === SwapSide.BUY;
|
|
124
|
+
|
|
125
|
+
const rfqParams = {
|
|
126
|
+
makerAsset: ethers.utils.getAddress(makerToken.address),
|
|
127
|
+
takerAsset: ethers.utils.getAddress(takerToken.address),
|
|
128
|
+
...(isBuy && { makerAmount: optimalSwapExchange.destAmount }),
|
|
129
|
+
...(isSell && { takerAmount: optimalSwapExchange.srcAmount }),
|
|
130
|
+
userAddress: options.executionContractAddress,
|
|
131
|
+
chainId: String(this.network),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const rfq: CablesRFQResponse = await this.dexHelper.httpRequest.post(
|
|
135
|
+
`${CABLES_API_URL}/quote`,
|
|
136
|
+
rfqParams,
|
|
137
|
+
CABLES_FIRM_QUOTE_TIMEOUT_MS,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (!rfq) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'Failed to fetch RFQ' +
|
|
143
|
+
swapIdentifier +
|
|
144
|
+
JSON.stringify(rfq + 'params' + rfqParams),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { order } = rfq;
|
|
149
|
+
|
|
150
|
+
assert(
|
|
151
|
+
order.makerAsset.toLowerCase() === makerToken.address,
|
|
152
|
+
`QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`,
|
|
153
|
+
);
|
|
154
|
+
assert(
|
|
155
|
+
order.takerAsset.toLowerCase() === takerToken.address,
|
|
156
|
+
`QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`,
|
|
157
|
+
);
|
|
158
|
+
if (isSell) {
|
|
159
|
+
assert(
|
|
160
|
+
order.takerAmount === optimalSwapExchange.srcAmount,
|
|
161
|
+
`QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`,
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
assert(
|
|
165
|
+
order.makerAmount === optimalSwapExchange.destAmount,
|
|
166
|
+
`QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const expiryAsBigInt = BigInt(order.expiry);
|
|
171
|
+
const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256;
|
|
172
|
+
|
|
173
|
+
// Correction of the srcAmount
|
|
174
|
+
// because flag specialDexSupportsInsertFromAmount: false
|
|
175
|
+
// is not working for Buy in test
|
|
176
|
+
if (isBuy) {
|
|
177
|
+
optimalSwapExchange.srcAmount = BigNumber(
|
|
178
|
+
(Number(optimalSwapExchange.srcAmount) * 10000) /
|
|
179
|
+
(10000 + Number(options.slippageFactor)),
|
|
180
|
+
).toFixed(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
...optimalSwapExchange,
|
|
186
|
+
data: {
|
|
187
|
+
quoteData: order,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{ deadline: minDeadline },
|
|
191
|
+
];
|
|
192
|
+
} catch (e) {
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getDexParam(
|
|
198
|
+
srcToken: Address,
|
|
199
|
+
destToken: Address,
|
|
200
|
+
srcAmount: NumberAsString,
|
|
201
|
+
destAmount: NumberAsString,
|
|
202
|
+
recipient: Address,
|
|
203
|
+
data: CablesData,
|
|
204
|
+
side: SwapSide,
|
|
205
|
+
): DexExchangeParam {
|
|
206
|
+
const { quoteData } = data;
|
|
207
|
+
|
|
208
|
+
assert(
|
|
209
|
+
quoteData !== undefined,
|
|
210
|
+
`${this.dexKey}-${this.network}: quoteData undefined`,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const swapFunction = 'simpleSwap';
|
|
214
|
+
const swapFunctionParams = [
|
|
215
|
+
[
|
|
216
|
+
quoteData.nonceAndMeta,
|
|
217
|
+
quoteData.expiry,
|
|
218
|
+
quoteData.makerAsset,
|
|
219
|
+
quoteData.takerAsset,
|
|
220
|
+
quoteData.maker,
|
|
221
|
+
quoteData.taker,
|
|
222
|
+
quoteData.makerAmount,
|
|
223
|
+
quoteData.takerAmount,
|
|
224
|
+
],
|
|
225
|
+
quoteData.signature,
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const exchangeData = this.rfqInterface.encodeFunctionData(
|
|
229
|
+
swapFunction,
|
|
230
|
+
swapFunctionParams,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
exchangeData,
|
|
235
|
+
needWrapNative: this.needWrapNative,
|
|
236
|
+
dexFuncHasRecipient: false,
|
|
237
|
+
targetExchange: this.mainnetRFQAddress,
|
|
238
|
+
returnAmountPos: undefined,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
getAdapterParam(
|
|
243
|
+
srcToken: string,
|
|
244
|
+
destToken: string,
|
|
245
|
+
srcAmount: string,
|
|
246
|
+
destAmount: string,
|
|
247
|
+
data: CablesData,
|
|
248
|
+
side: SwapSide,
|
|
249
|
+
): AdapterExchangeParam {
|
|
250
|
+
const { quoteData } = data;
|
|
251
|
+
|
|
252
|
+
assert(
|
|
253
|
+
quoteData !== undefined,
|
|
254
|
+
`${this.dexKey}-${this.network}: quoteData undefined`,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const params = [
|
|
258
|
+
{
|
|
259
|
+
nonceAndMeta: quoteData.nonceAndMeta,
|
|
260
|
+
expiry: quoteData.expiry,
|
|
261
|
+
makerAsset: quoteData.makerAsset,
|
|
262
|
+
takerAsset: quoteData.takerAsset,
|
|
263
|
+
maker: quoteData.maker,
|
|
264
|
+
taker: quoteData.taker,
|
|
265
|
+
makerAmount: quoteData.makerAmount,
|
|
266
|
+
takerAmount: quoteData.takerAmount,
|
|
267
|
+
},
|
|
268
|
+
quoteData.signature,
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const payload = this.abiCoder.encodeParameter(
|
|
272
|
+
{
|
|
273
|
+
ParentStruct: {
|
|
274
|
+
order: {
|
|
275
|
+
nonceAndMeta: 'uint256',
|
|
276
|
+
expiry: 'uint128',
|
|
277
|
+
makerAsset: 'address',
|
|
278
|
+
takerAsset: 'address',
|
|
279
|
+
maker: 'address',
|
|
280
|
+
taker: 'address',
|
|
281
|
+
makerAmount: 'uint256',
|
|
282
|
+
takerAmount: 'uint256',
|
|
283
|
+
},
|
|
284
|
+
signature: 'bytes',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
order: params[0],
|
|
289
|
+
signature: params[1],
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
targetExchange: this.mainnetRFQAddress,
|
|
295
|
+
payload,
|
|
296
|
+
networkFee: '0',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
normalizeToken(token: Token): Token {
|
|
301
|
+
return {
|
|
302
|
+
...token,
|
|
303
|
+
address: this.normalizeTokenAddress(token.address),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
normalizeTokenAddress(address: Address): Address {
|
|
308
|
+
return address.toLowerCase();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getTokenFromAddress(address: Address): Token {
|
|
312
|
+
return this.tokensMap[this.normalizeAddress(address)];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* POOLS
|
|
317
|
+
*/
|
|
318
|
+
getPoolIdentifier(srcAddress: Address, destAddress: Address, mm?: string) {
|
|
319
|
+
return `${this.dexKey}_${srcAddress}_${destAddress}`.toLowerCase();
|
|
320
|
+
return `${this.dexKey}_${srcAddress}_${destAddress}_${mm}`.toLowerCase();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async getPoolIdentifiers(
|
|
324
|
+
srcToken: Token,
|
|
325
|
+
destToken: Token,
|
|
326
|
+
side: SwapSide,
|
|
327
|
+
blockNumber: number,
|
|
328
|
+
): Promise<string[]> {
|
|
329
|
+
if (!srcToken || !destToken) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (srcToken.address.toLowerCase() === destToken.address.toLowerCase()) {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const pairData = await this.getPairData(srcToken, destToken);
|
|
338
|
+
|
|
339
|
+
if (!pairData) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const tokensAddr = (await this.getCachedTokensAddr()) || {};
|
|
344
|
+
|
|
345
|
+
return [
|
|
346
|
+
this.getPoolIdentifier(
|
|
347
|
+
tokensAddr[pairData.base.toLowerCase()],
|
|
348
|
+
tokensAddr[pairData.quote.toLowerCase()],
|
|
349
|
+
),
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
calculateOrderPrice(
|
|
354
|
+
amounts: bigint[],
|
|
355
|
+
orderbook: string[][],
|
|
356
|
+
baseToken: Token,
|
|
357
|
+
quoteToken: Token,
|
|
358
|
+
isInputQuote: boolean,
|
|
359
|
+
) {
|
|
360
|
+
let result = [];
|
|
361
|
+
|
|
362
|
+
for (let i = 0; i < amounts.length; i++) {
|
|
363
|
+
let amt = amounts[i];
|
|
364
|
+
if (amt === 0n) {
|
|
365
|
+
result.push(amt);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let decimals = baseToken.decimals;
|
|
370
|
+
if (isInputQuote) {
|
|
371
|
+
decimals = quoteToken.decimals;
|
|
372
|
+
}
|
|
373
|
+
let price = this.calculatePriceSwap(
|
|
374
|
+
orderbook,
|
|
375
|
+
Number(amt) / 10 ** decimals,
|
|
376
|
+
isInputQuote,
|
|
377
|
+
);
|
|
378
|
+
result.push(BigInt(Math.round(price * 10 ** decimals)));
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
calculatePriceSwap(
|
|
384
|
+
prices: string[][],
|
|
385
|
+
requiredQty: number,
|
|
386
|
+
qtyMode: Boolean,
|
|
387
|
+
) {
|
|
388
|
+
let sumBaseQty = 0;
|
|
389
|
+
let sumQuoteQty = 0;
|
|
390
|
+
const selectedRows: string[][] = [];
|
|
391
|
+
|
|
392
|
+
const isBase = !qtyMode;
|
|
393
|
+
const isQuote = qtyMode;
|
|
394
|
+
|
|
395
|
+
for (const [price, volume] of prices) {
|
|
396
|
+
if (isBase) {
|
|
397
|
+
if (sumBaseQty >= requiredQty) {
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (isQuote) {
|
|
403
|
+
if (sumQuoteQty >= requiredQty) {
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let currentBaseQty = Number(volume);
|
|
409
|
+
let currentQuoteQty = Number(volume) * Number(price);
|
|
410
|
+
|
|
411
|
+
const overQty = isBase
|
|
412
|
+
? currentBaseQty + sumBaseQty > requiredQty
|
|
413
|
+
: currentQuoteQty + sumQuoteQty > requiredQty;
|
|
414
|
+
|
|
415
|
+
if (overQty) {
|
|
416
|
+
if (isBase) {
|
|
417
|
+
currentBaseQty = requiredQty - sumBaseQty;
|
|
418
|
+
currentQuoteQty = currentBaseQty * Number(price);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (isQuote) {
|
|
422
|
+
currentQuoteQty = requiredQty - sumQuoteQty;
|
|
423
|
+
currentBaseQty =
|
|
424
|
+
currentQuoteQty *
|
|
425
|
+
new BigNumber(1).dividedBy(new BigNumber(price)).toNumber();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
sumBaseQty += currentBaseQty;
|
|
430
|
+
sumQuoteQty += currentQuoteQty;
|
|
431
|
+
selectedRows.push([price, currentBaseQty.toString()]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const vSumBase = selectedRows.reduce((sum: number, [price, volume]) => {
|
|
435
|
+
return sum + Number(price) * Number(volume);
|
|
436
|
+
}, 0);
|
|
437
|
+
|
|
438
|
+
if (isBase) {
|
|
439
|
+
return sumBaseQty;
|
|
440
|
+
} else {
|
|
441
|
+
return sumQuoteQty;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
async getPricesVolume(
|
|
446
|
+
srcToken: Token,
|
|
447
|
+
destToken: Token,
|
|
448
|
+
amounts: bigint[],
|
|
449
|
+
side: SwapSide,
|
|
450
|
+
blockNumber: number,
|
|
451
|
+
limitPools?: string[],
|
|
452
|
+
transferFees?: TransferFeeParams,
|
|
453
|
+
isFirstSwap?: boolean,
|
|
454
|
+
): Promise<ExchangePrices<CablesData> | null> {
|
|
455
|
+
try {
|
|
456
|
+
const normalizedSrcToken = this.normalizeToken(srcToken);
|
|
457
|
+
const normalizedDestToken = this.normalizeToken(destToken);
|
|
458
|
+
// If: same token, return null
|
|
459
|
+
if (
|
|
460
|
+
normalizedSrcToken.address.toLowerCase() ===
|
|
461
|
+
normalizedDestToken.address.toLowerCase()
|
|
462
|
+
) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Ensure that "symbol" is set
|
|
467
|
+
const tokens = await this.getCachedTokens();
|
|
468
|
+
this.tokensMap = Object.keys(tokens).reduce((acc, key) => {
|
|
469
|
+
//@ts-ignore
|
|
470
|
+
acc[tokens[key].address.toLowerCase()] = tokens[key];
|
|
471
|
+
return acc;
|
|
472
|
+
}, {});
|
|
473
|
+
|
|
474
|
+
for (const symbol of Object.keys(tokens)) {
|
|
475
|
+
const normalizedTokenAddress = tokens[symbol].address.toLowerCase();
|
|
476
|
+
|
|
477
|
+
if (normalizedSrcToken.address === normalizedTokenAddress) {
|
|
478
|
+
normalizedSrcToken.symbol = tokens[symbol].symbol;
|
|
479
|
+
}
|
|
480
|
+
if (normalizedDestToken.address === normalizedTokenAddress) {
|
|
481
|
+
normalizedDestToken.symbol = tokens[symbol].symbol;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---------- Pools ----------
|
|
486
|
+
let pools = await this.getPoolIdentifiers(
|
|
487
|
+
srcToken,
|
|
488
|
+
destToken,
|
|
489
|
+
side,
|
|
490
|
+
blockNumber,
|
|
491
|
+
);
|
|
492
|
+
if (pools.length === 0) return null;
|
|
493
|
+
|
|
494
|
+
// ---------- Prices ----------
|
|
495
|
+
const priceMap = await this.getCachedPrices();
|
|
496
|
+
|
|
497
|
+
if (!priceMap) return null;
|
|
498
|
+
|
|
499
|
+
let pairKey = `${normalizedSrcToken.symbol}/${normalizedDestToken.symbol}`;
|
|
500
|
+
const pairsKeys = Object.keys(priceMap);
|
|
501
|
+
|
|
502
|
+
if (!pairsKeys.includes(pairKey)) {
|
|
503
|
+
// Revert
|
|
504
|
+
pairKey = `${normalizedDestToken.symbol}/${normalizedSrcToken.symbol}`;
|
|
505
|
+
if (!pairsKeys.includes(pairKey)) {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Orderbook
|
|
512
|
+
*/
|
|
513
|
+
const priceData = priceMap[pairKey];
|
|
514
|
+
|
|
515
|
+
let orderbook: any[] = [];
|
|
516
|
+
if (side === SwapSide.BUY) {
|
|
517
|
+
orderbook = priceData.asks;
|
|
518
|
+
} else {
|
|
519
|
+
orderbook = priceData.bids;
|
|
520
|
+
}
|
|
521
|
+
if (orderbook?.length === 0) {
|
|
522
|
+
throw new Error(`Empty orderbook for ${pairKey}`);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const isInputQuote = side === SwapSide.BUY;
|
|
526
|
+
|
|
527
|
+
const prices = this.calculateOrderPrice(
|
|
528
|
+
amounts,
|
|
529
|
+
orderbook,
|
|
530
|
+
srcToken,
|
|
531
|
+
destToken,
|
|
532
|
+
isInputQuote,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const outDecimals =
|
|
536
|
+
side === SwapSide.BUY
|
|
537
|
+
? normalizedSrcToken.decimals
|
|
538
|
+
: normalizedDestToken.decimals;
|
|
539
|
+
const result = [
|
|
540
|
+
{
|
|
541
|
+
prices: prices,
|
|
542
|
+
unit: BigInt(outDecimals),
|
|
543
|
+
exchange: this.dexKey,
|
|
544
|
+
gasCost: CABLES_GAS_COST,
|
|
545
|
+
poolAddresses: [this.mainnetRFQAddress],
|
|
546
|
+
data: {},
|
|
547
|
+
},
|
|
548
|
+
];
|
|
549
|
+
return result;
|
|
550
|
+
} catch (e: unknown) {
|
|
551
|
+
this.logger.error(
|
|
552
|
+
`Error in getPricesVolume`,
|
|
553
|
+
{
|
|
554
|
+
srcToken: srcToken.address || srcToken.symbol,
|
|
555
|
+
destToken: destToken.address || destToken.symbol,
|
|
556
|
+
side,
|
|
557
|
+
},
|
|
558
|
+
e,
|
|
559
|
+
);
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
getCalldataGasCost(poolPrices: PoolPrices<CablesData>): number | number[] {
|
|
565
|
+
return (
|
|
566
|
+
CALLDATA_GAS_COST.DEX_OVERHEAD +
|
|
567
|
+
// addresses: makerAsset, takerAsset, maker, taker
|
|
568
|
+
CALLDATA_GAS_COST.ADDRESS * 4 +
|
|
569
|
+
// uint256: expiry
|
|
570
|
+
CALLDATA_GAS_COST.wordNonZeroBytes(16) +
|
|
571
|
+
// uint256: nonceAndMeta, makerAmount, takerAmount
|
|
572
|
+
CALLDATA_GAS_COST.AMOUNT * 3 +
|
|
573
|
+
// bytes: _signature (65 bytes)
|
|
574
|
+
CALLDATA_GAS_COST.FULL_WORD * 2 +
|
|
575
|
+
CALLDATA_GAS_COST.OFFSET_SMALL
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async initializePricing(blockNumber: number): Promise<void> {
|
|
580
|
+
if (!this.dexHelper.config.isSlave) {
|
|
581
|
+
this.rateFetcher.start();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
getAdapters(side: SwapSide): { name: string; index: number }[] | null {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
releaseResources?(): AsyncOrSync<void> {
|
|
592
|
+
if (this.rateFetcher) {
|
|
593
|
+
this.rateFetcher.stop();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
normalizeAddress(address: string): string {
|
|
598
|
+
return address.toLowerCase() === ETHER_ADDRESS
|
|
599
|
+
? NULL_ADDRESS
|
|
600
|
+
: address.toLowerCase();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async getTopPoolsForToken(
|
|
604
|
+
tokenAddress: Address,
|
|
605
|
+
limit: number,
|
|
606
|
+
): Promise<PoolLiquidity[]> {
|
|
607
|
+
const tokens = (await this.getCachedTokens()) as { [key: string]: Token };
|
|
608
|
+
const token = Object.values(tokens).find(
|
|
609
|
+
token => token.address.toLowerCase() === tokenAddress.toLowerCase(),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
if (!token) {
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const tokenSymbol = token.symbol?.toLowerCase() || '';
|
|
617
|
+
|
|
618
|
+
const tokenPriceUsd = await this.dexHelper.getTokenUSDPrice(
|
|
619
|
+
token,
|
|
620
|
+
BigInt(10 ** token.decimals),
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
const erc20BalanceCalldata = this.erc20Interface.encodeFunctionData(
|
|
624
|
+
'balanceOf',
|
|
625
|
+
[this.mainnetRFQAddress],
|
|
626
|
+
);
|
|
627
|
+
const tokenBalanceMultiCall = [
|
|
628
|
+
{
|
|
629
|
+
target: token.address,
|
|
630
|
+
callData: erc20BalanceCalldata,
|
|
631
|
+
},
|
|
632
|
+
];
|
|
633
|
+
const res = (
|
|
634
|
+
await this.dexHelper.multiContract.methods
|
|
635
|
+
.aggregate(tokenBalanceMultiCall)
|
|
636
|
+
.call()
|
|
637
|
+
).returnData[0];
|
|
638
|
+
|
|
639
|
+
let tokenLiquidity = BigInt(res);
|
|
640
|
+
|
|
641
|
+
let tokenLiquidityUsd =
|
|
642
|
+
(tokenLiquidity * BigInt(tokenPriceUsd * 1_000_000)) /
|
|
643
|
+
BigInt(1_000_000 * 10 ** token.decimals);
|
|
644
|
+
|
|
645
|
+
let tokenWithLiquidity = [];
|
|
646
|
+
|
|
647
|
+
tokenWithLiquidity.push({
|
|
648
|
+
exchange: this.dexKey,
|
|
649
|
+
address: this.mainnetRFQAddress,
|
|
650
|
+
connectorTokens: [
|
|
651
|
+
{
|
|
652
|
+
address: token.address,
|
|
653
|
+
decimals: token.decimals,
|
|
654
|
+
},
|
|
655
|
+
],
|
|
656
|
+
liquidityUSD: Number(tokenLiquidityUsd),
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
return tokenWithLiquidity;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* CACHED UTILS
|
|
664
|
+
*/
|
|
665
|
+
async getCachedTokens(): Promise<any> {
|
|
666
|
+
const cachedTokens = await this.dexHelper.cache.get(
|
|
667
|
+
this.dexKey,
|
|
668
|
+
this.network,
|
|
669
|
+
this.rateFetcher.tokensCacheKey,
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
return cachedTokens ? JSON.parse(cachedTokens) : {};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async getCachedPairs(): Promise<any> {
|
|
676
|
+
const cachedPairs = await this.dexHelper.cache.get(
|
|
677
|
+
this.dexKey,
|
|
678
|
+
this.network,
|
|
679
|
+
this.rateFetcher.pairsCacheKey,
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
return cachedPairs ? JSON.parse(cachedPairs) : {};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async getCachedPrices(): Promise<any> {
|
|
686
|
+
const cachedPrices = await this.dexHelper.cache.get(
|
|
687
|
+
this.dexKey,
|
|
688
|
+
this.network,
|
|
689
|
+
this.rateFetcher.pricesCacheKey,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
return cachedPrices ? JSON.parse(cachedPrices) : {};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async getCachedTokensAddr(): Promise<any> {
|
|
696
|
+
const tokens = await this.getCachedTokens();
|
|
697
|
+
const tokensAddr: Record<string, Address> = {};
|
|
698
|
+
for (const key of Object.keys(tokens)) {
|
|
699
|
+
tokensAddr[tokens[key].symbol.toLowerCase()] = tokens[key].address;
|
|
700
|
+
}
|
|
701
|
+
return tokensAddr;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
getPairString(baseToken: Token, quoteToken: Token): string {
|
|
705
|
+
return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase();
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Function to find a key by address
|
|
709
|
+
private findKeyByAddress = (
|
|
710
|
+
jsonData: Record<string, { address: string }>,
|
|
711
|
+
targetAddress: string,
|
|
712
|
+
): string | undefined => {
|
|
713
|
+
const entries = Object.entries(jsonData);
|
|
714
|
+
const foundEntry = entries.find(
|
|
715
|
+
([_, value]) =>
|
|
716
|
+
value.address.toLowerCase() === targetAddress.toLowerCase(),
|
|
717
|
+
);
|
|
718
|
+
return foundEntry ? foundEntry[0] : undefined;
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
async getPairData(srcToken: Token, destToken: Token): Promise<any> {
|
|
722
|
+
const normalizedSrcToken = this.normalizeToken(srcToken);
|
|
723
|
+
const normalizedDestToken = this.normalizeToken(destToken);
|
|
724
|
+
|
|
725
|
+
if (normalizedSrcToken.address === normalizedDestToken.address) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const cachedTokens = await this.getCachedTokens();
|
|
730
|
+
|
|
731
|
+
normalizedSrcToken.symbol = this.findKeyByAddress(
|
|
732
|
+
cachedTokens,
|
|
733
|
+
normalizedSrcToken.address,
|
|
734
|
+
);
|
|
735
|
+
normalizedDestToken.symbol = this.findKeyByAddress(
|
|
736
|
+
cachedTokens,
|
|
737
|
+
normalizedDestToken.address,
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const cachedPairs = await this.getCachedPairs();
|
|
741
|
+
|
|
742
|
+
const potentialPairs = [
|
|
743
|
+
{
|
|
744
|
+
base: normalizedSrcToken.symbol,
|
|
745
|
+
quote: normalizedDestToken.symbol,
|
|
746
|
+
identifier: this.getPairString(normalizedSrcToken, normalizedDestToken),
|
|
747
|
+
isSrcBase: true,
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
base: normalizedDestToken.symbol,
|
|
751
|
+
quote: normalizedSrcToken.symbol,
|
|
752
|
+
identifier: this.getPairString(normalizedDestToken, normalizedSrcToken),
|
|
753
|
+
isSrcBase: false,
|
|
754
|
+
},
|
|
755
|
+
];
|
|
756
|
+
|
|
757
|
+
for (const pair of potentialPairs) {
|
|
758
|
+
if (pair.identifier in cachedPairs) {
|
|
759
|
+
const pairData = cachedPairs[pair.identifier];
|
|
760
|
+
pairData.isSrcBase = pair.isSrcBase;
|
|
761
|
+
return pairData;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|