@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.
- package/build/config.js +6 -0
- package/build/config.js.map +1 -1
- package/build/dex/bebop/bebop.d.ts +0 -1
- package/build/dex/bebop/bebop.js +16 -14
- package/build/dex/bebop/bebop.js.map +1 -1
- package/build/dex/index.js +2 -0
- package/build/dex/index.js.map +1 -1
- package/build/executor/Executor01BytecodeBuilder.js +3 -2
- package/build/executor/Executor01BytecodeBuilder.js.map +1 -1
- package/build/executor/Executor02BytecodeBuilder.js +3 -2
- package/build/executor/Executor02BytecodeBuilder.js.map +1 -1
- package/build/executor/Executor03BytecodeBuilder.js +3 -2
- package/build/executor/Executor03BytecodeBuilder.js.map +1 -1
- 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 +7 -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 +840 -0
- package/src/dex/bebop/config.ts +37 -0
- package/src/dex/bebop/constants.ts +15 -0
- package/src/dex/bebop/rate-fetcher.ts +138 -0
- package/src/dex/bebop/types.ts +90 -0
- package/src/dex/bebop/validators.ts +29 -0
- package/src/dex/index.ts +2 -0
- package/src/executor/Executor01BytecodeBuilder.ts +3 -2
- package/src/executor/Executor02BytecodeBuilder.ts +3 -2
- package/src/executor/Executor03BytecodeBuilder.ts +3 -2
- package/src/lib/fetcher/wsFetcher.ts +145 -0
- package/src/types.ts +1 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
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;
|