@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.
- package/build/abi/USDM.json +807 -0
- package/build/abi/bebop/BebopSettlement.abi.json +2137 -0
- package/build/abi/wUSDM.json +757 -0
- package/build/config.js +7 -0
- package/build/config.js.map +1 -1
- package/build/dex/bebop/bebop.d.ts +55 -0
- package/build/dex/bebop/bebop.js +524 -0
- package/build/dex/bebop/bebop.js.map +1 -0
- package/build/dex/bebop/config.d.ts +3 -0
- package/build/dex/bebop/config.js +38 -0
- package/build/dex/bebop/config.js.map +1 -0
- package/build/dex/bebop/constants.d.ts +9 -0
- package/build/dex/bebop/constants.js +13 -0
- package/build/dex/bebop/constants.js.map +1 -0
- package/build/dex/bebop/rate-fetcher.d.ts +22 -0
- package/build/dex/bebop/rate-fetcher.js +80 -0
- package/build/dex/bebop/rate-fetcher.js.map +1 -0
- package/build/dex/bebop/types.d.ts +76 -0
- package/build/dex/bebop/types.js +3 -0
- package/build/dex/bebop/types.js.map +1 -0
- package/build/dex/bebop/validators.d.ts +4 -0
- package/build/dex/bebop/validators.js +42 -0
- package/build/dex/bebop/validators.js.map +1 -0
- package/build/dex/bebop/websocket-fetcher.d.ts +35 -0
- package/build/dex/bebop/websocket-fetcher.js +87 -0
- package/build/dex/bebop/websocket-fetcher.js.map +1 -0
- package/build/dex/index.js +2 -0
- package/build/dex/index.js.map +1 -1
- package/build/dex/woo-fi-v2/woo-fi-v2-pool.d.ts +2 -2
- package/build/dex/woo-fi-v2/woo-fi-v2-pool.js +30 -9
- package/build/dex/woo-fi-v2/woo-fi-v2-pool.js.map +1 -1
- package/build/dex/woo-fi-v2/woo-fi-v2.js +1 -1
- package/build/dex/woo-fi-v2/woo-fi-v2.js.map +1 -1
- package/build/dex/wusdm/config.d.ts +3 -0
- package/build/dex/wusdm/config.js +29 -0
- package/build/dex/wusdm/config.js.map +1 -0
- package/build/dex/wusdm/types.d.ts +10 -0
- package/build/dex/wusdm/types.js +3 -0
- package/build/dex/wusdm/types.js.map +1 -0
- package/build/dex/wusdm/wusdm.d.ts +39 -0
- package/build/dex/wusdm/wusdm.js +212 -0
- package/build/dex/wusdm/wusdm.js.map +1 -0
- package/build/lib/fetcher/wsFetcher.d.ts +35 -0
- package/build/lib/fetcher/wsFetcher.js +87 -0
- package/build/lib/fetcher/wsFetcher.js.map +1 -0
- package/build/types.d.ts +1 -0
- package/package.json +4 -2
- package/src/abi/bebop/BebopSettlement.abi.json +2137 -0
- package/src/config.ts +8 -0
- package/src/dex/bebop/bebop-e2e.test.ts +195 -0
- package/src/dex/bebop/bebop-integration.test.ts +170 -0
- package/src/dex/bebop/bebop.ts +732 -0
- package/src/dex/bebop/config.ts +37 -0
- package/src/dex/bebop/constants.ts +9 -0
- package/src/dex/bebop/rate-fetcher.ts +138 -0
- package/src/dex/bebop/types.ts +81 -0
- package/src/dex/bebop/validators.ts +43 -0
- package/src/dex/index.ts +2 -0
- package/src/dex/woo-fi-v2/woo-fi-v2-pool.ts +58 -27
- package/src/dex/woo-fi-v2/woo-fi-v2.ts +1 -2
- package/src/lib/fetcher/wsFetcher.ts +126 -0
- 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
|
+
});
|