@paraswap/dex-lib 3.8.38 → 3.8.39-bebop

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/build/abi/USDM.json +807 -0
  2. package/build/abi/bebop/BebopSettlement.abi.json +2137 -0
  3. package/build/abi/wUSDM.json +757 -0
  4. package/build/config.js +7 -0
  5. package/build/config.js.map +1 -1
  6. package/build/dex/bebop/bebop.d.ts +55 -0
  7. package/build/dex/bebop/bebop.js +524 -0
  8. package/build/dex/bebop/bebop.js.map +1 -0
  9. package/build/dex/bebop/config.d.ts +3 -0
  10. package/build/dex/bebop/config.js +38 -0
  11. package/build/dex/bebop/config.js.map +1 -0
  12. package/build/dex/bebop/constants.d.ts +9 -0
  13. package/build/dex/bebop/constants.js +13 -0
  14. package/build/dex/bebop/constants.js.map +1 -0
  15. package/build/dex/bebop/rate-fetcher.d.ts +22 -0
  16. package/build/dex/bebop/rate-fetcher.js +80 -0
  17. package/build/dex/bebop/rate-fetcher.js.map +1 -0
  18. package/build/dex/bebop/types.d.ts +76 -0
  19. package/build/dex/bebop/types.js +3 -0
  20. package/build/dex/bebop/types.js.map +1 -0
  21. package/build/dex/bebop/validators.d.ts +4 -0
  22. package/build/dex/bebop/validators.js +42 -0
  23. package/build/dex/bebop/validators.js.map +1 -0
  24. package/build/dex/bebop/websocket-fetcher.d.ts +35 -0
  25. package/build/dex/bebop/websocket-fetcher.js +87 -0
  26. package/build/dex/bebop/websocket-fetcher.js.map +1 -0
  27. package/build/dex/index.js +2 -0
  28. package/build/dex/index.js.map +1 -1
  29. package/build/dex/woo-fi-v2/woo-fi-v2-pool.d.ts +2 -2
  30. package/build/dex/woo-fi-v2/woo-fi-v2-pool.js +30 -9
  31. package/build/dex/woo-fi-v2/woo-fi-v2-pool.js.map +1 -1
  32. package/build/dex/woo-fi-v2/woo-fi-v2.js +1 -1
  33. package/build/dex/woo-fi-v2/woo-fi-v2.js.map +1 -1
  34. package/build/dex/wusdm/config.d.ts +3 -0
  35. package/build/dex/wusdm/config.js +29 -0
  36. package/build/dex/wusdm/config.js.map +1 -0
  37. package/build/dex/wusdm/types.d.ts +10 -0
  38. package/build/dex/wusdm/types.js +3 -0
  39. package/build/dex/wusdm/types.js.map +1 -0
  40. package/build/dex/wusdm/wusdm.d.ts +39 -0
  41. package/build/dex/wusdm/wusdm.js +212 -0
  42. package/build/dex/wusdm/wusdm.js.map +1 -0
  43. package/build/lib/fetcher/wsFetcher.d.ts +35 -0
  44. package/build/lib/fetcher/wsFetcher.js +87 -0
  45. package/build/lib/fetcher/wsFetcher.js.map +1 -0
  46. package/build/types.d.ts +1 -0
  47. package/package.json +4 -2
  48. package/src/abi/bebop/BebopSettlement.abi.json +2137 -0
  49. package/src/config.ts +8 -0
  50. package/src/dex/bebop/bebop-e2e.test.ts +195 -0
  51. package/src/dex/bebop/bebop-integration.test.ts +170 -0
  52. package/src/dex/bebop/bebop.ts +732 -0
  53. package/src/dex/bebop/config.ts +37 -0
  54. package/src/dex/bebop/constants.ts +9 -0
  55. package/src/dex/bebop/rate-fetcher.ts +138 -0
  56. package/src/dex/bebop/types.ts +81 -0
  57. package/src/dex/bebop/validators.ts +43 -0
  58. package/src/dex/index.ts +2 -0
  59. package/src/dex/woo-fi-v2/woo-fi-v2-pool.ts +58 -27
  60. package/src/dex/woo-fi-v2/woo-fi-v2.ts +1 -2
  61. package/src/lib/fetcher/wsFetcher.ts +126 -0
  62. package/src/types.ts +1 -0
@@ -0,0 +1,732 @@
1
+ import { assert, AsyncOrSync } from 'ts-essentials';
2
+ import {
3
+ Token,
4
+ Address,
5
+ ExchangePrices,
6
+ PoolPrices,
7
+ AdapterExchangeParam,
8
+ PoolLiquidity,
9
+ Logger,
10
+ OptimalSwapExchange,
11
+ PreprocessTransactionOptions,
12
+ ExchangeTxInfo,
13
+ NumberAsString,
14
+ DexExchangeParam,
15
+ } from '../../types';
16
+ import { SwapSide, Network } from '../../constants';
17
+ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
18
+ import { getDexKeysWithNetwork } from '../../utils';
19
+ import { IDex } from '../../dex/idex';
20
+ import { IDexHelper } from '../../dex-helper/idex-helper';
21
+ import {
22
+ BebopData,
23
+ BebopLevel,
24
+ BebopPair,
25
+ BebopPricingResponse,
26
+ RoutingInstruction,
27
+ TokenDataMap,
28
+ } from './types';
29
+ import settlementABI from '../../abi/bebop/BebopSettlement.abi.json';
30
+ import { SimpleExchange } from '../simple-exchange';
31
+ import { BebopConfig } from './config';
32
+ import { Interface } from 'ethers/lib/utils';
33
+ import { RateFetcher } from './rate-fetcher';
34
+ import {
35
+ BEBOP_API_URL,
36
+ BEBOP_AUTH_NAME,
37
+ BEBOP_GAS_COST,
38
+ BEBOP_INIT_TIMEOUT_MS,
39
+ BEBOP_PRICES_CACHE_TTL,
40
+ BEBOP_QUOTE_TIMEOUT_MS,
41
+ BEBOP_TOKENS_CACHE_TTL,
42
+ BEBOP_TOKENS_POLLING_INTERVAL_MS,
43
+ BEBOP_WS_API_URL,
44
+ } from './constants';
45
+ import BigNumber from 'bignumber.js';
46
+ import { getBigNumberPow } from '../../bignumber-constants';
47
+ import { utils } from 'ethers';
48
+ import qs from 'qs';
49
+
50
+ export class Bebop extends SimpleExchange implements IDex<BebopData> {
51
+ readonly hasConstantPriceLargeAmounts = false;
52
+ readonly needWrapNative = false;
53
+
54
+ readonly isFeeOnTransferSupported = false;
55
+
56
+ public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
57
+ getDexKeysWithNetwork(BebopConfig);
58
+
59
+ private rateFetcher: RateFetcher;
60
+ private tokensMap: TokenDataMap = {};
61
+
62
+ private pricesCacheKey: string;
63
+ private tokensCacheKey: string;
64
+ private tokensAddrCacheKey: string;
65
+
66
+ private bebopAuthToken: string;
67
+
68
+ logger: Logger;
69
+
70
+ constructor(
71
+ readonly network: Network,
72
+ readonly dexKey: string,
73
+ readonly dexHelper: IDexHelper,
74
+ readonly settlementAddress: string = BebopConfig['Bebop'][network]
75
+ .settlementAddress,
76
+ protected settlementInterface = new Interface(settlementABI),
77
+ ) {
78
+ super(dexHelper, dexKey);
79
+ this.logger = dexHelper.getLogger(dexKey);
80
+ this.tokensCacheKey = `tokens`;
81
+ this.pricesCacheKey = `prices`;
82
+ this.tokensAddrCacheKey = `tokens_addr`;
83
+ const token = this.dexHelper.config.data.bebopAuthToken;
84
+ if (!token) {
85
+ throw new Error('Bebop auth token is not set');
86
+ }
87
+ this.bebopAuthToken = token;
88
+
89
+ this.rateFetcher = new RateFetcher(
90
+ this.dexHelper,
91
+ this.dexKey,
92
+ this.network,
93
+ this.logger,
94
+ {
95
+ rateConfig: {
96
+ tokensIntervalMs: BEBOP_TOKENS_POLLING_INTERVAL_MS,
97
+ pricesCacheKey: this.pricesCacheKey,
98
+ pricesCacheTTLSecs: BEBOP_PRICES_CACHE_TTL,
99
+ tokensCacheKey: this.tokensCacheKey,
100
+ tokensAddrCacheKey: this.tokensAddrCacheKey,
101
+ tokensCacheTTLSecs: BEBOP_TOKENS_CACHE_TTL,
102
+ tokensReqParams: {
103
+ url:
104
+ BEBOP_API_URL +
105
+ `/pmm/${BebopConfig['Bebop'][network].chainName}/v3/token-info`,
106
+ },
107
+ pricesReqParams: {
108
+ url:
109
+ BEBOP_WS_API_URL +
110
+ `/pmm/${BebopConfig['Bebop'][network].chainName}/v3/pricing`,
111
+ headers: {
112
+ name: BEBOP_AUTH_NAME,
113
+ authorization: this.bebopAuthToken,
114
+ },
115
+ },
116
+ },
117
+ },
118
+ );
119
+ }
120
+
121
+ async initializePricing(blockNumber: number) {
122
+ if (!this.dexHelper.config.isSlave) {
123
+ this.rateFetcher.start();
124
+ await sleep(BEBOP_INIT_TIMEOUT_MS);
125
+ }
126
+ }
127
+
128
+ // Returns the list of contract adapters (name and index)
129
+ // for a buy/sell. Return null if there are no adapters.
130
+ getAdapters(side: SwapSide): { name: string; index: number }[] | null {
131
+ return null;
132
+ }
133
+
134
+ getPoolIdentifier(base: string, quote: string): string {
135
+ const identifier = `${
136
+ this.dexKey
137
+ }_${base.toLowerCase()}_${quote.toLowerCase()}`;
138
+ return identifier;
139
+ }
140
+
141
+ invertLevels(levels: BebopLevel[]): BebopLevel[] {
142
+ return levels.map(([price, size]) => [1 / price, size * price]);
143
+ }
144
+
145
+ invertBook(book: BebopPair): BebopPair {
146
+ return {
147
+ bids: this.invertLevels(book.asks),
148
+ asks: this.invertLevels(book.bids),
149
+ last_update_ts: book.last_update_ts,
150
+ };
151
+ }
152
+
153
+ // considers only swapside.sell
154
+ async calculateInstructions(
155
+ srcToken: Token,
156
+ destToken: Token,
157
+ side: SwapSide,
158
+ ): Promise<RoutingInstruction[]> {
159
+ const prices = await this.getCachedPrices();
160
+ if (!prices) {
161
+ throw new Error('No prices available');
162
+ }
163
+ const directBook =
164
+ prices[
165
+ `${srcToken.address.toLowerCase()}/${destToken.address.toLowerCase()}`
166
+ ];
167
+ if (directBook) {
168
+ return [
169
+ {
170
+ pair: `${srcToken.address.toLowerCase()}/${destToken.address.toLowerCase()}`,
171
+ side,
172
+ book: directBook,
173
+ targetQuote: side == SwapSide.BUY,
174
+ },
175
+ ];
176
+ }
177
+
178
+ const inverseBook =
179
+ prices[
180
+ `${destToken.address.toLowerCase()}/${srcToken.address.toLowerCase()}`
181
+ ];
182
+ if (inverseBook) {
183
+ const invertedBook = this.invertBook(inverseBook);
184
+ return [
185
+ {
186
+ pair: `${srcToken.address.toLowerCase()}/${destToken.address.toLowerCase()}`,
187
+ side,
188
+ book: invertedBook,
189
+ targetQuote: side == SwapSide.BUY,
190
+ },
191
+ ];
192
+ }
193
+
194
+ for (const middleToken of BebopConfig['Bebop'][this.network].middleTokens) {
195
+ const baseMiddle =
196
+ prices[
197
+ `${srcToken.address.toLowerCase()}/${middleToken.toLowerCase()}`
198
+ ];
199
+ const quoteMiddle =
200
+ prices[
201
+ `${destToken.address.toLowerCase()}/${middleToken.toLowerCase()}`
202
+ ];
203
+ if (baseMiddle && quoteMiddle) {
204
+ if (side == SwapSide.SELL) {
205
+ return [
206
+ {
207
+ pair: `${srcToken.address.toLowerCase()}/${middleToken.toLowerCase()}`,
208
+ side: side,
209
+ book: baseMiddle,
210
+ targetQuote: false,
211
+ },
212
+ {
213
+ pair: `${middleToken.toLowerCase()}/${destToken.address.toLowerCase()}`,
214
+ side: side,
215
+ book: this.invertBook(quoteMiddle),
216
+ targetQuote: false,
217
+ },
218
+ ];
219
+ } else {
220
+ return [
221
+ {
222
+ pair: `${middleToken.toLowerCase()}/${destToken.address.toLowerCase()}`,
223
+ side: side,
224
+ book: this.invertBook(quoteMiddle),
225
+ targetQuote: true,
226
+ },
227
+ {
228
+ pair: `${srcToken.address.toLowerCase()}/${middleToken.toLowerCase()}`,
229
+ side: side,
230
+ book: baseMiddle,
231
+ targetQuote: true,
232
+ },
233
+ ];
234
+ }
235
+ }
236
+ }
237
+
238
+ return [];
239
+ }
240
+
241
+ // Returns list of pool identifiers that can be used
242
+ // for a given swap. poolIdentifiers must be unique
243
+ // across DEXes. It is recommended to use
244
+ // ${dexKey}_${poolAddress} as a poolIdentifier
245
+ async getPoolIdentifiers(
246
+ srcToken: Token,
247
+ destToken: Token,
248
+ side: SwapSide,
249
+ blockNumber: number,
250
+ ): Promise<string[]> {
251
+ if (
252
+ (await this.calculateInstructions(srcToken, destToken, side)).length > 0
253
+ ) {
254
+ const identifier = this.getPoolIdentifier(
255
+ srcToken.address,
256
+ destToken.address,
257
+ );
258
+ return [identifier];
259
+ }
260
+
261
+ return [];
262
+ }
263
+
264
+ runInstruction(
265
+ instruction: RoutingInstruction,
266
+ amount: BigNumber,
267
+ ): BigNumber {
268
+ let accumulated = BigNumber(0);
269
+ let output = BigNumber(0);
270
+ let filled = false;
271
+ for (const level of instruction.book.bids) {
272
+ const [price, size] = level;
273
+ const amountToAccumulate = instruction.targetQuote
274
+ ? BigNumber(size).times(price)
275
+ : BigNumber(size);
276
+ const afterAccumulated = accumulated.plus(amountToAccumulate);
277
+ if (afterAccumulated.lt(amount)) {
278
+ accumulated = accumulated.plus(amountToAccumulate);
279
+ const amountToAddToOutput = instruction.targetQuote
280
+ ? BigNumber(size)
281
+ : BigNumber(size).times(price);
282
+ output = output.plus(amountToAddToOutput);
283
+ if (accumulated.eq(amount)) {
284
+ filled = true;
285
+ break;
286
+ }
287
+ } else {
288
+ const remaining = amount.minus(accumulated);
289
+ output = output.plus(
290
+ instruction.targetQuote
291
+ ? remaining.div(price)
292
+ : remaining.times(price),
293
+ );
294
+ filled = true;
295
+ break;
296
+ }
297
+ }
298
+ if (filled) {
299
+ return output;
300
+ } else {
301
+ return BigNumber(0);
302
+ }
303
+ }
304
+
305
+ calculateOutput(
306
+ instructions: RoutingInstruction[],
307
+ srcToken: Token,
308
+ destToken: Token,
309
+ amounts: bigint[],
310
+ side: SwapSide,
311
+ ): bigint[] {
312
+ const outputs = [];
313
+ const inputDecimals =
314
+ side == SwapSide.SELL ? srcToken.decimals : destToken.decimals;
315
+ const outputDecimals =
316
+ side == SwapSide.SELL ? destToken.decimals : srcToken.decimals;
317
+ for (const amount of amounts) {
318
+ if (amount == 0n) {
319
+ outputs.push(0n);
320
+ continue;
321
+ }
322
+ const amountDecimals = BigNumber(amount.toString()).div(
323
+ getBigNumberPow(inputDecimals),
324
+ );
325
+ let output: BigNumber = BigNumber(0);
326
+ for (const instruction of instructions) {
327
+ output = this.runInstruction(
328
+ instruction,
329
+ output.gt(0) ? output : amountDecimals,
330
+ );
331
+ if (output.eq(0)) {
332
+ outputs.push(0n); // No liquidity at this size
333
+ continue;
334
+ }
335
+ }
336
+ if (output.gt(0)) {
337
+ outputs.push(
338
+ BigInt(output.times(getBigNumberPow(outputDecimals)).toFixed(0)),
339
+ );
340
+ }
341
+ }
342
+ return outputs;
343
+ }
344
+
345
+ // Returns pool prices for amounts.
346
+ // If limitPools is defined only pools in limitPools
347
+ // should be used. If limitPools is undefined then
348
+ // any pools can be used.
349
+ async getPricesVolume(
350
+ srcToken: Token,
351
+ destToken: Token,
352
+ amounts: bigint[],
353
+ side: SwapSide,
354
+ blockNumber: number,
355
+ limitPools?: string[],
356
+ ): Promise<null | ExchangePrices<BebopData>> {
357
+ this.tokensMap = (await this.getCachedTokens()) || {};
358
+
359
+ try {
360
+ const pools =
361
+ limitPools ??
362
+ (await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber));
363
+
364
+ if (pools.length === 0) {
365
+ return null;
366
+ }
367
+
368
+ const instructions = await this.calculateInstructions(
369
+ srcToken,
370
+ destToken,
371
+ side,
372
+ );
373
+
374
+ if (!instructions) {
375
+ return null;
376
+ }
377
+
378
+ const outputs = this.calculateOutput(
379
+ instructions,
380
+ srcToken,
381
+ destToken,
382
+ amounts,
383
+ side,
384
+ );
385
+
386
+ // Up to 3bps deviation is expected in the output compared to the on-chain result
387
+ // On SwapSide.Sell, outputs compared to quoting are coming out: -0.1 bips -> USDC, -1 bips Alt -> Alt.
388
+ // On SwapSide.Buy, outputs compared to quoteing are coming out: 0.1 bips -> USDC, 1-3 bips Alt -> Alt.
389
+
390
+ const outDecimals = SwapSide.SELL
391
+ ? destToken.decimals
392
+ : srcToken.decimals;
393
+
394
+ return [
395
+ {
396
+ prices: outputs,
397
+ unit: BigInt(outDecimals),
398
+ data: {},
399
+ poolIdentifier: pools[0],
400
+ exchange: this.dexKey,
401
+ gasCost: BEBOP_GAS_COST,
402
+ poolAddresses: [this.settlementAddress],
403
+ },
404
+ ];
405
+ } catch (e: unknown) {
406
+ this.logger.error(
407
+ `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${
408
+ destToken.address || destToken.symbol
409
+ }, ${side}:`,
410
+ e,
411
+ );
412
+ return null;
413
+ }
414
+ }
415
+
416
+ // Returns estimated gas cost of calldata for this DEX in multiSwap
417
+ getCalldataGasCost(poolPrices: PoolPrices<BebopData>): number | number[] {
418
+ // This relies heavily on exact quote. Can we note use Bebop Data to find this? either via pools or the data itself?
419
+ // This assumes that a single maker was used to fill this trade
420
+ // "order":{
421
+ return (
422
+ CALLDATA_GAS_COST.DEX_OVERHEAD +
423
+ // "expiry":"1725541751"
424
+ CALLDATA_GAS_COST.TIMESTAMP +
425
+ // "taker_address":"0x9008d19f58aabd9ed0d60971565aa8510560ab41"
426
+ CALLDATA_GAS_COST.ADDRESS +
427
+ // "maker_address":"0x807cf9a772d5a3f9cefbc1192e939d62f0d9bd38"
428
+ CALLDATA_GAS_COST.ADDRESS +
429
+ // "maker_nonce":"1725541661402362944"
430
+ CALLDATA_GAS_COST.UUID +
431
+ // "taker_token":"0x3429d03c6f7521aec737a0bbf2e5ddcef2c3ae31"
432
+ CALLDATA_GAS_COST.ADDRESS +
433
+ // "maker_token":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
434
+ CALLDATA_GAS_COST.ADDRESS +
435
+ // "taker_amount":"3789033775358849793876"
436
+ CALLDATA_GAS_COST.AMOUNT +
437
+ // "maker_amount":"199726023273987970"
438
+ CALLDATA_GAS_COST.AMOUNT +
439
+ // "receiver":"0x9008d19f58aabd9ed0d60971565aa8510560ab41"
440
+ CALLDATA_GAS_COST.ADDRESS +
441
+ // "packed_commands":"0"
442
+ CALLDATA_GAS_COST.wordNonZeroBytes(4) +
443
+ // "flags":"103712057072216206793253728668500811363805133225332165859664901734817333248000"
444
+ CALLDATA_GAS_COST.FULL_WORD +
445
+ // }
446
+ // "makerSignature":{
447
+ // "signatureBytes":"0x34e1fbda1d48296949f0a003d4194646ed750a4ccd408b52979f60c2e7332207190a91600a40396b25fd645422dde85bc0ff53f26b66202a985ac8b9da87b47e1c"
448
+ CALLDATA_GAS_COST.FULL_WORD +
449
+ // "flags":"1"
450
+ CALLDATA_GAS_COST.LENGTH_SMALL +
451
+ // }
452
+ // "filledTakerAmount":"0"
453
+ CALLDATA_GAS_COST.AMOUNT
454
+ );
455
+ }
456
+
457
+ // Encode params required by the exchange adapter
458
+ // V5: Used for multiSwap, buy & megaSwap
459
+ // V6: Not used, can be left blank
460
+ // Hint: abiCoder.encodeParameter() could be useful
461
+ getAdapterParam(
462
+ srcToken: string,
463
+ destToken: string,
464
+ srcAmount: string,
465
+ destAmount: string,
466
+ data: BebopData,
467
+ side: SwapSide,
468
+ ): AdapterExchangeParam {
469
+ const { tx } = data;
470
+
471
+ if (!tx) {
472
+ throw new Error('No tx in data');
473
+ }
474
+
475
+ // Encode here the payload for adapter
476
+ const payload = tx.data;
477
+
478
+ return {
479
+ targetExchange: this.settlementAddress,
480
+ payload,
481
+ networkFee: '0',
482
+ };
483
+ }
484
+
485
+ // This is called once before getTopPoolsForToken is
486
+ // called for multiple tokens. This can be helpful to
487
+ // update common state required for calculating
488
+ // getTopPoolsForToken. It is optional for a DEX
489
+ // to implement this
490
+ async updatePoolState(): Promise<void> {
491
+ const tokens = await this.getCachedTokens();
492
+
493
+ if (tokens) {
494
+ this.tokensMap = tokens;
495
+ }
496
+ }
497
+
498
+ getMaxLiquidity(levels: BebopLevel[]) {
499
+ return levels.reduce((acc, [price, size]) => {
500
+ return acc + price * size;
501
+ }, 0);
502
+ }
503
+
504
+ // Returns list of top pools based on liquidity. Max
505
+ // limit number pools should be returned.
506
+ async getTopPoolsForToken(
507
+ tokenAddress: Address,
508
+ limit: number,
509
+ ): Promise<PoolLiquidity[]> {
510
+ const prices = await this.getCachedPrices();
511
+
512
+ if (!prices) {
513
+ return [];
514
+ }
515
+
516
+ const pools: PoolLiquidity[] = [];
517
+
518
+ for (const [pair, pairData] of Object.entries(prices)) {
519
+ let liquidityUSD = 0;
520
+ let token;
521
+ const [base, quote] = pair.split('/');
522
+ if (base.toLowerCase() == tokenAddress.toLowerCase()) {
523
+ const liquidityInQuote = this.getMaxLiquidity(pairData.bids);
524
+ token = {
525
+ address: quote,
526
+ decimals: this.tokensMap[quote.toLowerCase()].decimals,
527
+ };
528
+ const quoteTokenUsd = await this.dexHelper.getTokenUSDPrice(
529
+ token,
530
+ BigInt(Math.round(liquidityInQuote)),
531
+ );
532
+ liquidityUSD = liquidityInQuote * quoteTokenUsd;
533
+ } else if (quote.toLowerCase() == tokenAddress.toLowerCase()) {
534
+ const liquidityInBase = this.getMaxLiquidity(pairData.asks);
535
+ token = {
536
+ address: base,
537
+ decimals: this.tokensMap[base.toLowerCase()].decimals,
538
+ };
539
+ const baseTokenUsd = await this.dexHelper.getTokenUSDPrice(
540
+ token,
541
+ BigInt(Math.round(liquidityInBase)),
542
+ );
543
+ liquidityUSD = liquidityInBase * baseTokenUsd;
544
+ }
545
+ if (liquidityUSD) {
546
+ pools.push({
547
+ exchange: this.dexKey,
548
+ address: this.settlementAddress,
549
+ connectorTokens: [
550
+ {
551
+ address: quote,
552
+ decimals: this.tokensMap[quote.toLowerCase()].decimals,
553
+ symbol: this.tokensMap[quote.toLowerCase()].ticker,
554
+ },
555
+ ],
556
+ liquidityUSD,
557
+ });
558
+ }
559
+ }
560
+
561
+ return pools
562
+ .sort((a, b) => b.liquidityUSD - a.liquidityUSD)
563
+ .slice(0, limit);
564
+ }
565
+
566
+ getDexParam(
567
+ srcToken: Address,
568
+ destToken: Address,
569
+ srcAmount: NumberAsString,
570
+ destAmount: NumberAsString,
571
+ recipient: Address,
572
+ data: BebopData,
573
+ side: SwapSide,
574
+ ): DexExchangeParam {
575
+ const { tx } = data;
576
+
577
+ assert(tx !== undefined, `${this.dexKey}-${this.network}: tx undefined`);
578
+
579
+ return {
580
+ exchangeData: tx.data,
581
+ needWrapNative: this.needWrapNative,
582
+ dexFuncHasRecipient: false,
583
+ targetExchange: this.settlementAddress,
584
+ returnAmountPos: undefined,
585
+ };
586
+ }
587
+
588
+ async preProcessTransaction(
589
+ optimalSwapExchange: OptimalSwapExchange<BebopData>,
590
+ srcToken: Token,
591
+ destToken: Token,
592
+ side: SwapSide,
593
+ options: PreprocessTransactionOptions,
594
+ ): Promise<[OptimalSwapExchange<BebopData>, ExchangeTxInfo]> {
595
+ const isSell = side === SwapSide.SELL;
596
+ const isBuy = side === SwapSide.BUY;
597
+
598
+ const params = {
599
+ sell_tokens: utils.getAddress(srcToken.address),
600
+ buy_tokens: utils.getAddress(destToken.address),
601
+ sell_amounts: isSell ? optimalSwapExchange.srcAmount : undefined,
602
+ buy_amounts: isBuy ? optimalSwapExchange.destAmount : undefined,
603
+ taker_address: utils.getAddress(options.executionContractAddress),
604
+ receiver_address: utils.getAddress(options.executionContractAddress),
605
+ gasless: false,
606
+ skip_validation: true,
607
+ source: BEBOP_AUTH_NAME,
608
+ };
609
+
610
+ try {
611
+ const response: BebopData = await this.dexHelper.httpRequest.get(
612
+ `${BEBOP_API_URL}/pmm/${
613
+ BebopConfig['Bebop'][this.network].chainName
614
+ }/v3/quote?${qs.stringify(params)}`,
615
+ BEBOP_QUOTE_TIMEOUT_MS,
616
+ {
617
+ 'source-auth': this.bebopAuthToken,
618
+ },
619
+ );
620
+
621
+ if (!response) {
622
+ throw new Error('Failed to get quote');
623
+ }
624
+
625
+ if (
626
+ !response.tx ||
627
+ !response.buyTokens ||
628
+ !response.sellTokens ||
629
+ !response.expiry
630
+ ) {
631
+ throw new Error('Failed to get quote. No tx info');
632
+ }
633
+
634
+ if (side == SwapSide.SELL) {
635
+ const requiredAmount = BigInt(optimalSwapExchange.destAmount);
636
+ const quoteAmount = BigInt(
637
+ response.buyTokens[utils.getAddress(destToken.address)].amount,
638
+ );
639
+ const requiredAmountWithSlippage = new BigNumber(
640
+ requiredAmount.toString(),
641
+ )
642
+ .times(options.slippageFactor)
643
+ .toFixed(0);
644
+ if (quoteAmount < BigInt(requiredAmountWithSlippage)) {
645
+ throw new Error(
646
+ `Slipped, factor: ${quoteAmount.toString()} < ${requiredAmountWithSlippage}`,
647
+ );
648
+ }
649
+ } else {
650
+ const requiredAmount = BigInt(optimalSwapExchange.srcAmount);
651
+ const quoteAmount = BigInt(
652
+ response.sellTokens[utils.getAddress(srcToken.address)].amount,
653
+ );
654
+ const requiredAmountWithSlippage = new BigNumber(
655
+ requiredAmount.toString(),
656
+ )
657
+ .times(options.slippageFactor)
658
+ .toFixed(0);
659
+ if (quoteAmount > BigInt(requiredAmountWithSlippage)) {
660
+ throw new Error(
661
+ `Slipped, factor: ${
662
+ options.slippageFactor
663
+ } ${quoteAmount.toString()} > ${requiredAmountWithSlippage}`,
664
+ );
665
+ }
666
+ }
667
+ return [
668
+ {
669
+ ...optimalSwapExchange,
670
+ data: {
671
+ ...response,
672
+ },
673
+ },
674
+ { deadline: BigInt(response.expiry) },
675
+ ];
676
+ } catch (e) {
677
+ const message = `${this.dexKey}-${this.network}: ${e}`;
678
+ this.logger.error(message);
679
+ throw new Error(message);
680
+ }
681
+ }
682
+
683
+ async getCachedPrices(): Promise<BebopPricingResponse | null> {
684
+ const cachedPrices = await this.dexHelper.cache.get(
685
+ this.dexKey,
686
+ this.network,
687
+ this.pricesCacheKey,
688
+ );
689
+
690
+ if (cachedPrices) {
691
+ return JSON.parse(cachedPrices) as BebopPricingResponse;
692
+ }
693
+
694
+ return null;
695
+ }
696
+
697
+ async getCachedTokens(): Promise<TokenDataMap | null> {
698
+ const cachedTokens = await this.dexHelper.cache.get(
699
+ this.dexKey,
700
+ this.network,
701
+ this.tokensAddrCacheKey,
702
+ );
703
+
704
+ if (cachedTokens) {
705
+ return JSON.parse(cachedTokens) as TokenDataMap;
706
+ }
707
+
708
+ return null;
709
+ }
710
+
711
+ getTokenFromAddress(address: Address): Token {
712
+ const bebopToken = this.tokensMap[address.toLowerCase()];
713
+ return {
714
+ address,
715
+ decimals: bebopToken.decimals,
716
+ symbol: bebopToken.ticker,
717
+ };
718
+ }
719
+
720
+ // This is optional function in case if your implementation has acquired any resources
721
+ // you need to release for graceful shutdown. For example, it may be any interval timer
722
+ releaseResources(): AsyncOrSync<void> {
723
+ if (!this.dexHelper.config.isSlave) {
724
+ this.rateFetcher.stop();
725
+ }
726
+ }
727
+ }
728
+
729
+ const sleep = (time: number) =>
730
+ new Promise(resolve => {
731
+ setTimeout(resolve, time);
732
+ });