@paraswap/dex-lib 3.9.1 → 3.9.2-bebop.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.
@@ -0,0 +1,37 @@
1
+ import { DexParams } from './types';
2
+ import { DexConfigMap } from '../../types';
3
+ import { Network } from '../../constants';
4
+
5
+ export const BebopConfig: DexConfigMap<DexParams> = {
6
+ Bebop: {
7
+ [Network.MAINNET]: {
8
+ settlementAddress: '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F',
9
+ chainName: 'ethereum',
10
+ middleTokens: [
11
+ '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
12
+ '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
13
+ '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
14
+ ],
15
+ },
16
+ [Network.ARBITRUM]: {
17
+ settlementAddress: '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F',
18
+ chainName: 'arbitrum',
19
+ middleTokens: [
20
+ '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', // USDC.e
21
+ '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC
22
+ '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', // USDT
23
+ '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', // WETH
24
+ ],
25
+ },
26
+ [Network.BASE]: {
27
+ settlementAddress: '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F',
28
+ chainName: 'base',
29
+ middleTokens: ['0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'],
30
+ },
31
+ [Network.OPTIMISM]: {
32
+ settlementAddress: '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F',
33
+ chainName: 'optimism',
34
+ middleTokens: ['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'],
35
+ },
36
+ },
37
+ };
@@ -0,0 +1,15 @@
1
+ export const BEBOP_INIT_TIMEOUT_MS = 5000;
2
+ export const BEBOP_PRICES_CACHE_TTL = 10;
3
+ export const BEBOP_TOKENS_CACHE_TTL = 60;
4
+ export const BEBOP_TOKENS_POLLING_INTERVAL_MS = 30 * 1000;
5
+ export const BEBOP_API_URL = 'https://api.bebop.xyz';
6
+ export const BEBOP_WS_API_URL = 'wss://api.bebop.xyz';
7
+ export const BEBOP_GAS_COST = 120_000;
8
+ export const BEBOP_AUTH_NAME = 'paraswap';
9
+ export const BEBOP_QUOTE_TIMEOUT_MS = 2000;
10
+ export const BEBOP_ERRORS_CACHE_KEY = 'errors';
11
+ export const BEBOP_RESTRICTED_CACHE_KEY = 'restricted';
12
+ // Restrict for BEBOP_RESTRICT_TTL_S if an error occured >= BEBOP_RESTRICT_COUNT_THRESHOLD times within BEBOP_RESTRICT_CHECK_INTERVAL_S interval
13
+ export const BEBOP_RESTRICT_TTL_S = 10 * 60; // 10 min
14
+ export const BEBOP_RESTRICT_CHECK_INTERVAL_MS = 1000 * 60 * 3; // 3 min
15
+ export const BEBOP_RESTRICT_COUNT_THRESHOLD = 3;
@@ -0,0 +1,138 @@
1
+ import { ETHER_ADDRESS, Network } from '../../constants';
2
+ import { IDexHelper } from '../../dex-helper';
3
+ import { Fetcher } from '../../lib/fetcher/fetcher';
4
+ import { validateAndCast } from '../../lib/validators';
5
+ import { Logger, Token } from '../../types';
6
+ import {
7
+ BebopPricingResponse,
8
+ BebopRateFetcherConfig,
9
+ BebopTokensResponse,
10
+ } from './types';
11
+ import { pricesResponseValidator, tokensResponseValidator } from './validators';
12
+ import { WebSocketFetcher } from '../../lib/fetcher/wsFetcher';
13
+
14
+ export class RateFetcher {
15
+ private pricesFetcher: WebSocketFetcher<BebopPricingResponse>;
16
+ private pricesCacheKey: string;
17
+ private pricesCacheTTL: number;
18
+
19
+ private tokensFetcher: Fetcher<BebopTokensResponse>;
20
+ private tokensAddrCacheKey: string;
21
+ private tokensCacheKey: string;
22
+ private tokensCacheTTL: number;
23
+
24
+ constructor(
25
+ private dexHelper: IDexHelper,
26
+ private dexKey: string,
27
+ private network: Network,
28
+ private logger: Logger,
29
+ config: BebopRateFetcherConfig,
30
+ ) {
31
+ this.pricesCacheKey = config.rateConfig.pricesCacheKey;
32
+ this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs;
33
+ this.pricesFetcher = new WebSocketFetcher<BebopPricingResponse>(
34
+ {
35
+ info: {
36
+ requestOptions: config.rateConfig.pricesReqParams,
37
+ caster: (data: unknown) => {
38
+ return validateAndCast<BebopPricingResponse>(
39
+ data,
40
+ pricesResponseValidator,
41
+ );
42
+ },
43
+ },
44
+ handler: this.handlePricesResponse.bind(this),
45
+ },
46
+ logger,
47
+ );
48
+
49
+ this.tokensAddrCacheKey = config.rateConfig.tokensAddrCacheKey;
50
+ this.tokensCacheKey = config.rateConfig.tokensCacheKey;
51
+ this.tokensCacheTTL = config.rateConfig.tokensCacheTTLSecs;
52
+ this.tokensFetcher = new Fetcher<BebopTokensResponse>(
53
+ dexHelper.httpRequest,
54
+ {
55
+ info: {
56
+ requestOptions: config.rateConfig.tokensReqParams,
57
+ caster: (data: unknown) => {
58
+ return validateAndCast<BebopTokensResponse>(
59
+ data,
60
+ tokensResponseValidator,
61
+ );
62
+ },
63
+ },
64
+ handler: this.handleTokensResponse.bind(this),
65
+ },
66
+ config.rateConfig.tokensIntervalMs,
67
+ logger,
68
+ );
69
+ }
70
+
71
+ start() {
72
+ this.pricesFetcher.startPolling();
73
+ this.tokensFetcher.startPolling();
74
+ }
75
+
76
+ stop() {
77
+ this.pricesFetcher.stopPolling();
78
+ this.tokensFetcher.stopPolling();
79
+ }
80
+
81
+ private handleTokensResponse(resp: BebopTokensResponse): void {
82
+ const tokenMap: { [address: string]: Token } = {};
83
+ const tokenAddrMap: { [symbol: string]: Token } = {};
84
+
85
+ Object.keys(resp.tokens).forEach(tokenSymbol => {
86
+ const token = resp.tokens[tokenSymbol];
87
+ const tokenData = {
88
+ address: token.contractAddress.toLowerCase(),
89
+ symbol: token.ticker,
90
+ decimals: token.decimals,
91
+ };
92
+ tokenAddrMap[token.contractAddress.toLowerCase()] = tokenData;
93
+ tokenMap[token.ticker.toLowerCase()] = tokenData;
94
+ });
95
+
96
+ this.dexHelper.cache.setex(
97
+ this.dexKey,
98
+ this.network,
99
+ this.tokensCacheKey,
100
+ this.tokensCacheTTL,
101
+ JSON.stringify(tokenMap),
102
+ );
103
+
104
+ this.dexHelper.cache.setex(
105
+ this.dexKey,
106
+ this.network,
107
+ this.tokensAddrCacheKey,
108
+ this.tokensCacheTTL,
109
+ JSON.stringify(tokenAddrMap),
110
+ );
111
+ }
112
+
113
+ private handlePricesResponse(resp: BebopPricingResponse): void {
114
+ const wethAddress =
115
+ this.dexHelper.config.data.wrappedNativeTokenAddress.toLowerCase();
116
+ const normalizedPrices: BebopPricingResponse = {};
117
+ for (const [pair, levels] of Object.entries(resp)) {
118
+ normalizedPrices[pair.toLowerCase()] = levels;
119
+ const [base, quote] = pair.split('/');
120
+ // Also enter native token prices. Pricing doesn't come with these
121
+ if (
122
+ base.toLowerCase() === wethAddress ||
123
+ quote.toLowerCase() === wethAddress
124
+ ) {
125
+ const nativePair = pair.replace(base, ETHER_ADDRESS);
126
+ normalizedPrices[nativePair.toLowerCase()] = levels;
127
+ }
128
+ }
129
+
130
+ this.dexHelper.cache.setex(
131
+ this.dexKey,
132
+ this.network,
133
+ this.pricesCacheKey,
134
+ this.pricesCacheTTL,
135
+ JSON.stringify(normalizedPrices),
136
+ );
137
+ }
138
+ }
@@ -0,0 +1,90 @@
1
+ import { SwapSide } from '../../constants';
2
+ import { RequestHeaders } from '../../dex-helper';
3
+
4
+ export type BebopRateFetcherConfig = {
5
+ rateConfig: {
6
+ pricesReqParams: {
7
+ url: string;
8
+ headers?: RequestHeaders;
9
+ params?: any;
10
+ };
11
+ tokensReqParams: {
12
+ url: string;
13
+ headers?: RequestHeaders;
14
+ params?: any;
15
+ };
16
+ tokensIntervalMs: number;
17
+ pricesCacheKey: string;
18
+ tokensAddrCacheKey: string;
19
+ tokensCacheKey: string;
20
+ pricesCacheTTLSecs: number;
21
+ tokensCacheTTLSecs: number;
22
+ };
23
+ };
24
+
25
+ export type TokenDataMap = { [index: string]: BebopToken };
26
+
27
+ export type BebopToken = {
28
+ decimals: number;
29
+ contractAddress: string;
30
+ ticker: string;
31
+ };
32
+
33
+ export type BebopTokensResponse = {
34
+ tokens: { [symbol: string]: BebopToken };
35
+ };
36
+
37
+ export type BebopLevel = [price: number, size: number];
38
+
39
+ export type BebopPair = {
40
+ bids: BebopLevel[];
41
+ asks: BebopLevel[];
42
+ last_update_ts: number;
43
+ };
44
+
45
+ export type BebopPricingResponse = {
46
+ [pair: string]: BebopPair;
47
+ };
48
+
49
+ export interface BebopTx {
50
+ to: string;
51
+ value: string;
52
+ data: string;
53
+ from: string;
54
+ gas: number;
55
+ }
56
+
57
+ export type BebopTokenAmount = {
58
+ amount: string;
59
+ priceUsd: number;
60
+ };
61
+
62
+ // For now nothing but we may have to add something
63
+ export type BebopData = {
64
+ expiry?: number;
65
+ buyTokens?: { [address: string]: BebopTokenAmount };
66
+ sellTokens?: { [address: string]: BebopTokenAmount };
67
+ tx?: BebopTx;
68
+ };
69
+
70
+ export type DexParams = {
71
+ settlementAddress: string;
72
+ chainName: string;
73
+ middleTokens: string[];
74
+ };
75
+
76
+ export type RoutingInstruction = {
77
+ side: SwapSide; // Buy for bids, Sell for asks
78
+ book: BebopPair;
79
+ pair: string;
80
+ targetQuote: boolean;
81
+ };
82
+
83
+ export class SlippageError extends Error {
84
+ isSlippageError = true;
85
+ }
86
+
87
+ export type RestrictData = {
88
+ count: number;
89
+ addedDatetimeMs: number;
90
+ } | null;
@@ -0,0 +1,29 @@
1
+ import joi from 'joi';
2
+
3
+ const levelValidator = joi.array().items(joi.number()).length(2);
4
+
5
+ const pairValidator = joi.object({
6
+ bids: joi.array().items(levelValidator).required(),
7
+ asks: joi.array().items(levelValidator).required(),
8
+ last_update_ts: joi.number().min(0).required(),
9
+ });
10
+
11
+ export const pricesResponseValidator = joi
12
+ .object()
13
+ .pattern(joi.string(), pairValidator);
14
+
15
+ const tokenValidator = joi
16
+ .object({
17
+ ticker: joi.string().min(1).required(),
18
+ contractAddress: joi.string().min(1).required(),
19
+ decimals: joi.number().min(0).required(),
20
+ })
21
+ .unknown(true);
22
+
23
+ export const tokensResponseValidator = joi.object({
24
+ tokens: joi.object().pattern(joi.string(), tokenValidator),
25
+ });
26
+
27
+ export const blacklistResponseValidator = joi.object({
28
+ blacklist: joi.array().items(joi.string().min(1)).required(),
29
+ });
package/src/dex/index.ts CHANGED
@@ -77,6 +77,7 @@ import { AngleStakedStable } from './angle-staked-stable/angle-staked-stable';
77
77
  import { QuickPerps } from './quick-perps/quick-perps';
78
78
  import { NomiswapV2 } from './uniswap-v2/nomiswap-v2';
79
79
  import { Dexalot } from './dexalot/dexalot';
80
+ import { Bebop } from './bebop/bebop';
80
81
  import { Wombat } from './wombat/wombat';
81
82
  import { Swell } from './swell/swell';
82
83
  import { PharaohV1 } from './solidly/forks-override/pharaohV1';
@@ -111,6 +112,7 @@ const LegacyDexes = [
111
112
  ];
112
113
 
113
114
  const Dexes = [
115
+ Bebop,
114
116
  Dexalot,
115
117
  CurveV1,
116
118
  CurveFork,
@@ -80,8 +80,9 @@ export class Executor01BytecodeBuilder extends ExecutorBytecodeBuilder<
80
80
  Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP; // 0
81
81
 
82
82
  if (isEthSrc && !needWrap) {
83
- dexFlag =
84
- Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
83
+ dexFlag = dexFuncHasRecipient
84
+ ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 9
85
+ : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
85
86
  } else if (isEthDest && !needUnwrap) {
86
87
  dexFlag = forcePreventInsertFromAmount
87
88
  ? Flag.DONT_INSERT_FROM_AMOUNT_CHECK_ETH_BALANCE_AFTER_SWAP
@@ -107,8 +107,9 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder<
107
107
  Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP; // 0
108
108
 
109
109
  if (isEthSrc && !needWrap) {
110
- dexFlag =
111
- Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
110
+ dexFlag = dexFuncHasRecipient
111
+ ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 9
112
+ : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
112
113
  } else if (isEthDest && !needUnwrap) {
113
114
  dexFlag = forcePreventInsertFromAmount
114
115
  ? Flag.DONT_INSERT_FROM_AMOUNT_CHECK_ETH_BALANCE_AFTER_SWAP
@@ -93,8 +93,9 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder<
93
93
  Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP; // 0
94
94
 
95
95
  if (isEthSrc && !needWrap) {
96
- dexFlag =
97
- Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
96
+ dexFlag = dexFuncHasRecipient
97
+ ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 9
98
+ : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5
98
99
  } else if (isEthDest && !needUnwrap) {
99
100
  dexFlag = forcePreventInsertFromAmount
100
101
  ? Flag.DONT_INSERT_FROM_AMOUNT_CHECK_ETH_BALANCE_AFTER_SWAP // 4
@@ -0,0 +1,145 @@
1
+ import { Logger } from 'log4js';
2
+ import { RequestConfig, Response } from '../../dex-helper/irequest-wrapper';
3
+ import {
4
+ connection as WebSocketConnection,
5
+ client as WebSocketClient,
6
+ } from 'websocket';
7
+
8
+ export class SkippingRequest {
9
+ constructor(public message = '') {}
10
+ }
11
+
12
+ export type RequestInfo<T> = {
13
+ requestFunc?: (
14
+ options: RequestConfig,
15
+ ) => Promise<Response<T> | SkippingRequest>;
16
+ requestOptions: RequestConfig;
17
+ caster: (data: unknown) => T;
18
+ authenticate?: (options: RequestConfig) => RequestConfig;
19
+ excludedFieldsCaching?: string[];
20
+ };
21
+
22
+ export type RequestInfoWithHandler<T> = {
23
+ info: RequestInfo<T>;
24
+ handler: (data: T) => void;
25
+ };
26
+
27
+ export class WebSocketFetcher<T> {
28
+ private requests: RequestInfoWithHandler<T>;
29
+ public lastFetchSucceeded: boolean = false;
30
+ private stop: boolean = true;
31
+ private ws: WebSocketClient = new WebSocketClient();
32
+ private connection: WebSocketConnection | null = null;
33
+
34
+ constructor(requestsInfo: RequestInfoWithHandler<T>, private logger: Logger) {
35
+ this.requests = requestsInfo;
36
+ this.ws.on('connect', this.connected.bind(this));
37
+ this.ws.on('connectFailed', this.connectFailed.bind(this));
38
+ }
39
+
40
+ private connected(connection: WebSocketConnection) {
41
+ this.connection = connection;
42
+ this.logger.info(`Connected to ${this.requests.info.requestOptions.url}`);
43
+ this.connection.on('error', this.onError.bind(this));
44
+ this.connection.on('close', this.onClose.bind(this));
45
+ this.connection.on('message', this.onMessage.bind(this));
46
+ }
47
+
48
+ private connectFailed(error: any) {
49
+ this.logger.error(`Connect Error: ${error.toString()}. Reconnecting...`);
50
+ // reconnect on errors / failures
51
+ setTimeout(() => {
52
+ this.startPolling();
53
+ }, 3000);
54
+ }
55
+
56
+ private onClose() {
57
+ this.logger.info(`Connection closed. Reconnecting...`);
58
+ // reconnect on errors / failures
59
+ setTimeout(() => {
60
+ this.startPolling();
61
+ }, 3000);
62
+ }
63
+
64
+ private onError(error: any) {
65
+ this.logger.error(
66
+ `Connection Error: ${error.toString()}. Stopping & Reconnecting...`,
67
+ );
68
+ this.stopPolling();
69
+
70
+ // reconnect on errors / failures
71
+ setTimeout(() => {
72
+ this.startPolling();
73
+ }, 3000);
74
+ }
75
+
76
+ private onMessage(message: any) {
77
+ if (message.type === 'utf8') {
78
+ const response = JSON.parse(message.utf8Data) as Response<T>;
79
+ const reqInfo = this.requests;
80
+ const info = reqInfo.info;
81
+ const options = reqInfo.info.requestOptions;
82
+ this.logger.debug(`(${options.url}) received new data`);
83
+
84
+ try {
85
+ const parsedData = info.caster(response);
86
+ reqInfo.handler(parsedData);
87
+ } catch (e) {
88
+ this.logger.info(e);
89
+ this.logger.info(
90
+ `(${options.url}) received incorrect data ${JSON.stringify(
91
+ response,
92
+ ).replace(/(?:\r\n|\r|\n)/g, ' ')}`,
93
+ e,
94
+ );
95
+ return;
96
+ }
97
+ }
98
+ }
99
+
100
+ private connect() {
101
+ const authorization =
102
+ this.requests.info.requestOptions.headers!.authorization;
103
+ const name = this.requests.info.requestOptions.headers!.name;
104
+ if (typeof authorization !== 'string') {
105
+ throw new Error('Authorization header is not a string');
106
+ }
107
+ if (typeof name !== 'string') {
108
+ throw new Error('Name header is not a string');
109
+ }
110
+ this.logger.info(
111
+ `Connecting to ${this.requests.info.requestOptions.url}...`,
112
+ );
113
+ this.ws.connect(
114
+ this.requests.info.requestOptions.url!,
115
+ undefined,
116
+ undefined,
117
+ {
118
+ Authorization: authorization,
119
+ name: name,
120
+ },
121
+ );
122
+ }
123
+
124
+ startPolling(): void {
125
+ this.stop = false;
126
+ this.connect();
127
+ this.logger.info(
128
+ `Connection started for ${this.requests.info.requestOptions.url}`,
129
+ );
130
+ }
131
+
132
+ stopPolling() {
133
+ if (this.connection) {
134
+ this.connection.close();
135
+ }
136
+ this.stop = true;
137
+ this.logger.info(
138
+ `Connection stopped for ${this.requests.info.requestOptions.url}`,
139
+ );
140
+ }
141
+
142
+ isPolling(): boolean {
143
+ return !this.stop;
144
+ }
145
+ }
package/src/types.ts CHANGED
@@ -310,6 +310,7 @@ export type Config = {
310
310
  uniswapV3EventLoggingSampleRate?: number;
311
311
  swaapV2AuthToken?: string;
312
312
  dexalotAuthToken?: string;
313
+ bebopAuthToken?: string;
313
314
  idleDaoAuthToken?: string;
314
315
  forceRpcFallbackDexs: string[];
315
316
  apiKeyTheGraph: string;