@paraswap/dex-lib 2.14.7 → 2.15.1

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 (69) hide show
  1. package/.idea/aws.xml +17 -0
  2. package/.idea/codeStyles/Project.xml +19 -0
  3. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  4. package/.idea/modules.xml +1 -1
  5. package/.idea/paraswap-dex-lib.iml +9 -0
  6. package/.idea/runConfigurations.xml +10 -0
  7. package/build/dex/aave-v3/tokens/arbitrum.json +20 -20
  8. package/build/dex/aave-v3/tokens/avalanche.json +44 -20
  9. package/build/dex/aave-v3/tokens/fantom.json +30 -30
  10. package/build/dex/aave-v3/tokens/optimism.json +18 -18
  11. package/build/dex/aave-v3/tokens/polygon.json +60 -42
  12. package/build/dex/generic-rfq/fetch-mm-api.js +11 -10
  13. package/build/dex/generic-rfq/fetch-mm-api.js.map +1 -1
  14. package/build/dex/generic-rfq/rate-fetcher.js +12 -11
  15. package/build/dex/generic-rfq/rate-fetcher.js.map +1 -1
  16. package/build/dex/generic-rfq/validators.d.ts +0 -6
  17. package/build/dex/generic-rfq/validators.js +1 -24
  18. package/build/dex/generic-rfq/validators.js.map +1 -1
  19. package/build/dex/hashflow/constants.d.ts +6 -1
  20. package/build/dex/hashflow/constants.js +7 -2
  21. package/build/dex/hashflow/constants.js.map +1 -1
  22. package/build/dex/hashflow/hashflow.d.ts +9 -2
  23. package/build/dex/hashflow/hashflow.js +65 -21
  24. package/build/dex/hashflow/hashflow.js.map +1 -1
  25. package/build/dex/hashflow/rate-fetcher.d.ts +21 -0
  26. package/build/dex/hashflow/rate-fetcher.js +67 -0
  27. package/build/dex/hashflow/rate-fetcher.js.map +1 -0
  28. package/build/dex/hashflow/types.d.ts +36 -0
  29. package/build/dex/hashflow/types.js.map +1 -1
  30. package/build/dex/hashflow/validators.d.ts +4 -0
  31. package/build/dex/hashflow/validators.js +31 -0
  32. package/build/dex/hashflow/validators.js.map +1 -0
  33. package/build/lib/fetcher/fetcher.d.ts +7 -2
  34. package/build/lib/fetcher/fetcher.js +37 -13
  35. package/build/lib/fetcher/fetcher.js.map +1 -1
  36. package/build/lib/validators.d.ts +7 -0
  37. package/build/lib/validators.js +27 -0
  38. package/build/lib/validators.js.map +1 -0
  39. package/package.json +1 -1
  40. package/src/dex/generic-rfq/fetch-mm-api.ts +2 -2
  41. package/src/dex/generic-rfq/generic-rfq-e2e.test.ts +10 -26
  42. package/src/dex/generic-rfq/rate-fetcher.ts +4 -5
  43. package/src/dex/generic-rfq/validators.test.ts +1 -1
  44. package/src/dex/generic-rfq/validators.ts +0 -33
  45. package/src/dex/hashflow/constants.ts +12 -2
  46. package/src/dex/hashflow/hashflow.ts +117 -60
  47. package/src/dex/hashflow/rate-fetcher.ts +120 -0
  48. package/src/dex/hashflow/types.ts +40 -0
  49. package/src/dex/hashflow/validators.ts +27 -0
  50. package/src/lib/fetcher/fetcher.ts +41 -12
  51. package/src/lib/validators.ts +34 -0
  52. package/tests/utils-e2e.ts +13 -13
  53. package/.vscode/launch.json +0 -21
  54. package/build/dex/aave-v3/tokens/mainnet.json +0 -44
  55. package/build/dex/balancer-v2/VirtualBoostedPool.d.ts +0 -79
  56. package/build/dex/balancer-v2/VirtualBoostedPool.js +0 -382
  57. package/build/dex/balancer-v2/VirtualBoostedPool.js.map +0 -1
  58. package/build/dex/test-aave-v3/config.d.ts +0 -4
  59. package/build/dex/test-aave-v3/config.js +0 -15
  60. package/build/dex/test-aave-v3/config.js.map +0 -1
  61. package/build/dex/test-aave-v3/test-aave-v3-pool.d.ts +0 -39
  62. package/build/dex/test-aave-v3/test-aave-v3-pool.js +0 -63
  63. package/build/dex/test-aave-v3/test-aave-v3-pool.js.map +0 -1
  64. package/build/dex/test-aave-v3/test-aave-v3.d.ts +0 -37
  65. package/build/dex/test-aave-v3/test-aave-v3.js +0 -126
  66. package/build/dex/test-aave-v3/test-aave-v3.js.map +0 -1
  67. package/build/dex/test-aave-v3/types.d.ts +0 -6
  68. package/build/dex/test-aave-v3/types.js +0 -3
  69. package/build/dex/test-aave-v3/types.js.map +0 -1
@@ -1,4 +1,3 @@
1
- import _ from 'lodash';
2
1
  import {
3
2
  Token,
4
3
  Address,
@@ -19,7 +18,7 @@ import {
19
18
  CACHE_PREFIX,
20
19
  } from '../../constants';
21
20
  import * as CALLDATA_GAS_COST from '../../calldata-gas-cost';
22
- import { getDexKeysWithNetwork, Utils } from '../../utils';
21
+ import { getDexKeysWithNetwork } from '../../utils';
23
22
  import { IDex } from '../../dex/idex';
24
23
  import { IDexHelper } from '../../dex-helper/idex-helper';
25
24
  import {
@@ -32,12 +31,14 @@ import {
32
31
  import { SimpleExchange } from '../simple-exchange';
33
32
  import { Adapters, HashflowConfig } from './config';
34
33
  import { HashflowApi } from '@hashflow/taker-js';
34
+ import { RateFetcher } from './rate-fetcher';
35
35
  import routerAbi from '../../abi/hashflow/HashflowRouter.abi.json';
36
36
  import BigNumber from 'bignumber.js';
37
37
  import { BN_0, BN_1, getBigNumberPow } from '../../bignumber-constants';
38
38
  import { Interface } from 'ethers/lib/utils';
39
39
  import { ChainId, ZERO_ADDRESS } from '@hashflow/sdk';
40
40
  import {
41
+ MarketMakersResponse,
41
42
  PriceLevelsResponse,
42
43
  RfqResponse,
43
44
  } from '@hashflow/taker-js/dist/types/rest';
@@ -45,22 +46,30 @@ import { assert } from 'ts-essentials';
45
46
  import {
46
47
  HASHFLOW_BLACKLIST_TTL_S,
47
48
  HASHFLOW_MM_RESTRICT_TTL_S,
48
- PRICE_LEVELS_TTL_SECONDS,
49
+ HASHFLOW_API_CLIENT_NAME,
50
+ HASHFLOW_API_URL,
51
+ HASHFLOW_API_PRICES_POLLING_INTERVAL_MS,
52
+ HASHFLOW_API_MARKET_MAKERS_POLLING_INTERVAL_MS,
53
+ HASHFLOW_PRICES_CACHES_TTL_S,
54
+ HASHFLOW_MARKET_MAKERS_CACHES_TTL_S,
49
55
  } from './constants';
50
56
  import { BI_MAX_UINT256 } from '../../bigint-constants';
51
57
 
52
- const HASHFLOW_ASYNC_CALL_TIMEOUT = 150;
53
-
54
58
  export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
59
+ readonly isStatePollingDex = true;
55
60
  readonly hasConstantPriceLargeAmounts = false;
56
61
  readonly needWrapNative = false;
57
62
  readonly isFeeOnTransferSupported = false;
58
63
  private api: HashflowApi;
64
+ private rateFetcher: RateFetcher;
59
65
 
60
66
  private hashFlowAuthToken: string;
61
67
  private disabledMMs: Set<string>;
62
68
  private runtimeMMsRestrictHashMapKey: string;
63
69
 
70
+ private pricesCacheKey: string;
71
+ private marketMakersCacheKey: string;
72
+
64
73
  public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
65
74
  getDexKeysWithNetwork(HashflowConfig);
66
75
 
@@ -82,11 +91,65 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
82
91
  token !== undefined,
83
92
  'Hashflow auth token is not specified with env variable',
84
93
  );
94
+
85
95
  this.hashFlowAuthToken = token;
86
- this.api = new HashflowApi('taker', 'paraswap', this.hashFlowAuthToken);
96
+ this.api = new HashflowApi(
97
+ 'taker',
98
+ HASHFLOW_API_CLIENT_NAME,
99
+ this.hashFlowAuthToken,
100
+ );
101
+
102
+ this.pricesCacheKey = `${CACHE_PREFIX}_${this.dexHelper.config.data.network}_${this.dexKey}_prices`;
103
+ this.marketMakersCacheKey = `${CACHE_PREFIX}_${this.dexHelper.config.data.network}_${this.dexKey}_mms`;
104
+
87
105
  this.disabledMMs = new Set(dexHelper.config.data.hashFlowDisabledMMs);
88
106
  this.runtimeMMsRestrictHashMapKey =
89
107
  `${CACHE_PREFIX}_${this.dexKey}_${this.network}_restricted_mms`.toLowerCase();
108
+
109
+ this.rateFetcher = new RateFetcher(
110
+ this.dexHelper,
111
+ this.dexKey,
112
+ this.network,
113
+ this.logger,
114
+ {
115
+ rateConfig: {
116
+ pricesIntervalMs: HASHFLOW_API_PRICES_POLLING_INTERVAL_MS,
117
+ markerMakersIntervalMs:
118
+ HASHFLOW_API_MARKET_MAKERS_POLLING_INTERVAL_MS,
119
+ marketMakersReqParams: {
120
+ url: `${HASHFLOW_API_URL}/taker/v1/marketMakers`,
121
+ params: {
122
+ networkId: this.network,
123
+ source: HASHFLOW_API_CLIENT_NAME,
124
+ },
125
+ headers: { Authorization: this.hashFlowAuthToken },
126
+ },
127
+ pricesReqParams: {
128
+ url: `${HASHFLOW_API_URL}/taker/v2/price-levels`,
129
+ params: {
130
+ networkId: this.network,
131
+ source: HASHFLOW_API_CLIENT_NAME,
132
+ marketMakers: [],
133
+ },
134
+ headers: { Authorization: this.hashFlowAuthToken },
135
+ },
136
+ getCachedMarketMakers: this.getCachedMarketMakers.bind(this),
137
+ filterMarketMakers: this.getFilteredMarketMakers.bind(this),
138
+ pricesCacheKey: this.pricesCacheKey,
139
+ pricesCacheTTLSecs: HASHFLOW_PRICES_CACHES_TTL_S,
140
+ marketMakersCacheKey: this.marketMakersCacheKey,
141
+ marketMakersCacheTTLSecs: HASHFLOW_MARKET_MAKERS_CACHES_TTL_S,
142
+ },
143
+ },
144
+ );
145
+ }
146
+
147
+ async initializePricing(blockNumber: number): Promise<void> {
148
+ if (!this.dexHelper.config.isSlave) {
149
+ this.rateFetcher.start();
150
+ }
151
+
152
+ return;
90
153
  }
91
154
 
92
155
  getAdapters(side: SwapSide): { name: string; index: number }[] | null {
@@ -123,7 +186,7 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
123
186
  return [];
124
187
  }
125
188
 
126
- const levels = await this.getLevelsWithCache();
189
+ const levels = (await this.getCachedLevels()) || {};
127
190
  const makers = Object.keys(levels);
128
191
 
129
192
  return makers
@@ -144,11 +207,10 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
144
207
  );
145
208
  }
146
209
 
147
- private async getFilteredMarketMakers(chainId: ChainId): Promise<string[]> {
148
- const [makers, cachedRestrictionUnparsed] = await Promise.all([
149
- this.api.getMarketMakers(chainId),
150
- this.dexHelper.cache.hgetAll(this.runtimeMMsRestrictHashMapKey),
151
- ]);
210
+ private async getFilteredMarketMakers(makers: string[]): Promise<string[]> {
211
+ const cachedRestrictionUnparsed = await this.dexHelper.cache.hgetAll(
212
+ this.runtimeMMsRestrictHashMapKey,
213
+ );
152
214
 
153
215
  const runtimeRestrictedMMs = this.parseCacheRestrictionAndExpiryIfNeeded(
154
216
  cachedRestrictionUnparsed,
@@ -312,36 +374,30 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
312
374
  return undefined;
313
375
  }
314
376
 
315
- async getLevelsWithCache(): Promise<PriceLevelsResponse['levels']> {
316
- const cachedLevels = await this.dexHelper.cache.get(
317
- this.dexKey,
318
- this.network as ChainId,
319
- `levels`,
377
+ async getCachedMarketMakers(): Promise<
378
+ MarketMakersResponse['marketMakers'] | null
379
+ > {
380
+ const cachedMarketMakers = await this.dexHelper.cache.rawget(
381
+ this.marketMakersCacheKey,
320
382
  );
321
- if (cachedLevels) {
322
- return JSON.parse(cachedLevels) as PriceLevelsResponse['levels'];
383
+
384
+ if (cachedMarketMakers) {
385
+ return JSON.parse(
386
+ cachedMarketMakers,
387
+ ) as MarketMakersResponse['marketMakers'];
323
388
  }
324
389
 
325
- const makers = await Utils.timeoutPromise(
326
- this.getFilteredMarketMakers(this.network as ChainId),
327
- HASHFLOW_ASYNC_CALL_TIMEOUT,
328
- 'Hashflow: getFilteredMarketMakers timeout',
329
- );
330
- const levels = await Utils.timeoutPromise(
331
- this.api.getPriceLevels(this.network as ChainId, makers),
332
- HASHFLOW_ASYNC_CALL_TIMEOUT,
333
- 'Hashflow: getPriceLevels timeout',
334
- );
390
+ return null;
391
+ }
335
392
 
336
- await this.dexHelper.cache.setex(
337
- this.dexKey,
338
- this.network as ChainId,
339
- `levels`,
340
- PRICE_LEVELS_TTL_SECONDS,
341
- JSON.stringify(levels),
342
- );
393
+ async getCachedLevels(): Promise<PriceLevelsResponse['levels'] | null> {
394
+ const cachedLevels = await this.dexHelper.cache.rawget(this.pricesCacheKey);
343
395
 
344
- return levels;
396
+ if (cachedLevels) {
397
+ return JSON.parse(cachedLevels) as PriceLevelsResponse['levels'];
398
+ }
399
+
400
+ return null;
345
401
  }
346
402
 
347
403
  // Hashflow protocol for native token expects 0x00000... instead of 0xeeeee...
@@ -381,7 +437,7 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
381
437
 
382
438
  const marketMakersToUse = pools.map(p => p.split(`${prefix}_`).pop());
383
439
 
384
- const levelsMap = await this.getLevelsWithCache();
440
+ const levelsMap = (await this.getCachedLevels()) || {};
385
441
 
386
442
  Object.keys(levelsMap).forEach(mmKey => {
387
443
  if (!marketMakersToUse.includes(mmKey)) {
@@ -486,23 +542,19 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
486
542
 
487
543
  let rfq: RfqResponse;
488
544
  try {
489
- rfq = await Utils.timeoutPromise(
490
- this.api.requestQuote({
491
- chainId,
492
- baseToken: normalizedSrcToken.address,
493
- quoteToken: normalizedDestToken.address,
494
- ...(side === SwapSide.SELL
495
- ? {
496
- baseTokenAmount: optimalSwapExchange.srcAmount,
497
- }
498
- : { quoteTokenAmount: optimalSwapExchange.destAmount }),
499
- wallet: this.augustusAddress.toLowerCase(),
500
- effectiveTrader: options.txOrigin.toLowerCase(),
501
- marketMakers: [mm],
502
- }),
503
- HASHFLOW_ASYNC_CALL_TIMEOUT,
504
- 'Hashflow: requestQuote timeout',
505
- );
545
+ rfq = await this.api.requestQuote({
546
+ chainId,
547
+ baseToken: normalizedSrcToken.address,
548
+ quoteToken: normalizedDestToken.address,
549
+ ...(side === SwapSide.SELL
550
+ ? {
551
+ baseTokenAmount: optimalSwapExchange.srcAmount,
552
+ }
553
+ : { quoteTokenAmount: optimalSwapExchange.destAmount }),
554
+ wallet: this.augustusAddress.toLowerCase(),
555
+ effectiveTrader: options.txOrigin.toLowerCase(),
556
+ marketMakers: [mm],
557
+ });
506
558
 
507
559
  if (rfq.status !== 'success') {
508
560
  const message = `${this.dexKey}-${
@@ -816,16 +868,15 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
816
868
  ): Promise<PoolLiquidity[]> {
817
869
  const _tokenAddress = tokenAddress.toLowerCase();
818
870
 
819
- const chainId = this.network as ChainId;
820
-
821
- const makers = await this.getFilteredMarketMakers(chainId);
822
- const pLevels = await this.api.getPriceLevels(chainId, makers);
871
+ const makers = (await this.getCachedMarketMakers()) || [];
872
+ const filteredMakers = await this.getFilteredMarketMakers(makers);
873
+ const pLevels = (await this.getCachedLevels()) || {};
823
874
 
824
875
  let baseToken: Token | undefined = undefined;
825
876
  // TODO: Improve efficiency of this part. Quite inefficient way to determine
826
877
  // Token address and decimals. But since it is not called frequently, not worth
827
878
  // optimizing now
828
- for (const maker of makers) {
879
+ for (const maker of filteredMakers) {
829
880
  const baseTokenEntry = pLevels[maker]?.find(
830
881
  entry => entry.pair.baseToken.toLowerCase() === _tokenAddress,
831
882
  );
@@ -873,4 +924,10 @@ export class Hashflow extends SimpleExchange implements IDex<HashflowData> {
873
924
  .sort((a, b) => b.liquidityUSD - a.liquidityUSD)
874
925
  .slice(0, limit);
875
926
  }
927
+
928
+ releaseResources(): void {
929
+ if (this.rateFetcher) {
930
+ this.rateFetcher.stop();
931
+ }
932
+ }
876
933
  }
@@ -0,0 +1,120 @@
1
+ import { IDexHelper } from '../../dex-helper';
2
+ import { Fetcher, SkippingRequest } from '../../lib/fetcher/fetcher';
3
+ import { validateAndCast } from '../../lib/validators';
4
+ import { Logger } from '../../types';
5
+ import {
6
+ HashflowRateFetcherConfig,
7
+ HashflowRatesResponse,
8
+ HashflowMarketMakersResponse,
9
+ } from './types';
10
+ import { pricesResponseValidator, marketMakersValidator } from './validators';
11
+ import { Network } from '../../constants';
12
+
13
+ export class RateFetcher {
14
+ private rateFetcher: Fetcher<HashflowRatesResponse>;
15
+ private pricesCacheKey: string;
16
+ private pricesCacheTTL: number;
17
+
18
+ private marketMakersFetcher: Fetcher<HashflowMarketMakersResponse>;
19
+ private marketMakersCacheKey: string;
20
+ private marketMakersCacheTTL: number;
21
+
22
+ constructor(
23
+ private dexHelper: IDexHelper,
24
+ private dexKey: string,
25
+ private network: Network,
26
+ private logger: Logger,
27
+ config: HashflowRateFetcherConfig,
28
+ ) {
29
+ this.pricesCacheKey = config.rateConfig.pricesCacheKey;
30
+ this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs;
31
+ this.marketMakersCacheKey = config.rateConfig.marketMakersCacheKey;
32
+ this.marketMakersCacheTTL = config.rateConfig.marketMakersCacheTTLSecs;
33
+
34
+ this.marketMakersFetcher = new Fetcher<HashflowMarketMakersResponse>(
35
+ dexHelper.httpRequest,
36
+ {
37
+ info: {
38
+ requestOptions: config.rateConfig.marketMakersReqParams,
39
+ caster: (data: unknown) => {
40
+ return validateAndCast<HashflowMarketMakersResponse>(
41
+ data,
42
+ marketMakersValidator,
43
+ );
44
+ },
45
+ },
46
+ handler: this.handleMarketMakersResponse.bind(this),
47
+ },
48
+ config.rateConfig.markerMakersIntervalMs,
49
+ logger,
50
+ );
51
+
52
+ this.rateFetcher = new Fetcher<HashflowRatesResponse>(
53
+ dexHelper.httpRequest,
54
+ {
55
+ info: {
56
+ requestOptions: config.rateConfig.pricesReqParams,
57
+ requestFunc: async options => {
58
+ const { filterMarketMakers, getCachedMarketMakers } =
59
+ config.rateConfig;
60
+
61
+ const cachedMarketMakers = (await getCachedMarketMakers()) || [];
62
+ const filteredMarketMakers = await filterMarketMakers(
63
+ cachedMarketMakers,
64
+ );
65
+
66
+ if (filteredMarketMakers.length === 0) {
67
+ return new SkippingRequest(
68
+ `${dexKey}-${network}: got ${filteredMarketMakers.length} market makers.`,
69
+ );
70
+ }
71
+
72
+ const prices = await dexHelper.httpRequest.request({
73
+ ...options,
74
+ params: { ...options.params, marketMakers: filteredMarketMakers },
75
+ });
76
+
77
+ return prices;
78
+ },
79
+ caster: (data: unknown) => {
80
+ return validateAndCast<HashflowRatesResponse>(
81
+ data,
82
+ pricesResponseValidator,
83
+ );
84
+ },
85
+ },
86
+ handler: this.handleRatesResponse.bind(this),
87
+ },
88
+ config.rateConfig.pricesIntervalMs,
89
+ logger,
90
+ );
91
+ }
92
+
93
+ start() {
94
+ this.marketMakersFetcher.startPolling();
95
+ this.rateFetcher.startPolling();
96
+ }
97
+
98
+ stop() {
99
+ this.marketMakersFetcher.stopPolling();
100
+ this.rateFetcher.stopPolling();
101
+ }
102
+
103
+ private handleMarketMakersResponse(resp: HashflowMarketMakersResponse): void {
104
+ const { marketMakers } = resp;
105
+ this.dexHelper.cache.rawset(
106
+ this.marketMakersCacheKey,
107
+ JSON.stringify(marketMakers),
108
+ this.marketMakersCacheTTL,
109
+ );
110
+ }
111
+
112
+ private handleRatesResponse(resp: HashflowRatesResponse): void {
113
+ const { levels } = resp;
114
+ this.dexHelper.cache.rawset(
115
+ this.pricesCacheKey,
116
+ JSON.stringify(levels),
117
+ this.pricesCacheTTL,
118
+ );
119
+ }
120
+ }
@@ -1,4 +1,5 @@
1
1
  import { QuoteData } from '@hashflow/taker-js/dist/types/common';
2
+ import { RequestHeaders } from '../../dex-helper';
2
3
 
3
4
  export type HashflowData = {
4
5
  mm: string;
@@ -24,3 +25,42 @@ export enum RFQType {
24
25
  }
25
26
 
26
27
  export class SlippageCheckError extends Error {}
28
+
29
+ export type HashflowRatesLevel = {
30
+ pair: Record<string, string>;
31
+ levels: Array<Record<string, string>>;
32
+ includesFees: boolean;
33
+ };
34
+
35
+ export type HashflowMarketMakersResponse = {
36
+ marketMakers: string[];
37
+ };
38
+
39
+ export type HashflowRatesResponse = {
40
+ status: string;
41
+ networkId: string;
42
+ levels: Record<string, Array<HashflowRatesLevel>>;
43
+ };
44
+
45
+ export type HashflowRateFetcherConfig = {
46
+ rateConfig: {
47
+ marketMakersReqParams: {
48
+ url: string;
49
+ headers?: RequestHeaders;
50
+ params?: any;
51
+ };
52
+ pricesReqParams: {
53
+ url: string;
54
+ headers?: RequestHeaders;
55
+ params?: any;
56
+ };
57
+ pricesIntervalMs: number;
58
+ markerMakersIntervalMs: number;
59
+ getCachedMarketMakers: () => Promise<string[] | null>;
60
+ filterMarketMakers: (makers: string[]) => Promise<string[]>;
61
+ pricesCacheKey: string;
62
+ marketMakersCacheKey: string;
63
+ pricesCacheTTLSecs: number;
64
+ marketMakersCacheTTLSecs: number;
65
+ };
66
+ };
@@ -0,0 +1,27 @@
1
+ import joi from 'joi';
2
+
3
+ export const levelValidator = joi.array().items(joi.object({
4
+ pair: joi.object({
5
+ baseTokenName: joi.string().min(1).required(),
6
+ quoteTokenName: joi.string().min(1).required(),
7
+ baseToken: joi.string().min(1).required(),
8
+ quoteToken: joi.string().min(1).required(),
9
+ baseTokenDecimals: joi.number().min(1).required(),
10
+ quoteTokenDecimals: joi.number().min(1).required(),
11
+ }),
12
+ levels: joi.array().items(joi.object({
13
+ level: joi.string().min(1).required(),
14
+ price: joi.string().min(1).required(),
15
+ })),
16
+ includesFees: joi.boolean().required(),
17
+ }));
18
+
19
+ export const pricesResponseValidator = joi.object({
20
+ status: joi.string().required(),
21
+ networkId: joi.number().required(),
22
+ levels: joi.object().pattern(joi.string().min(1), levelValidator),
23
+ });
24
+
25
+ export const marketMakersValidator = joi.object({
26
+ marketMakers: joi.array().items(joi.string().min(1).required()),
27
+ });
@@ -1,12 +1,20 @@
1
1
  import { IRequestWrapper } from '../../dex-helper';
2
2
  import { Logger } from 'log4js';
3
3
  import { RequestConfig, Response } from '../../dex-helper/irequest-wrapper';
4
+ import { isFunction } from 'lodash';
4
5
 
5
6
  const FETCH_TIMEOUT_MS = 10 * 1000;
6
7
  const FETCH_FAIL_MAX_ATTEMPT = 5;
7
8
  const FETCH_FAIL_RETRY_TIMEOUT_MS = 60 * 1000;
8
9
 
10
+ export class SkippingRequest {
11
+ constructor(
12
+ public message = '',
13
+ ) {}
14
+ };
15
+
9
16
  export type RequestInfo<T> = {
17
+ requestFunc?: (options: RequestConfig) => Promise<Response<T> | SkippingRequest>;
10
18
  requestOptions: RequestConfig;
11
19
  caster: (data: unknown) => T;
12
20
  authenticate?: (options: RequestConfig) => RequestConfig;
@@ -18,7 +26,7 @@ export type RequestInfoWithHandler<T> = {
18
26
  handler: (data: T) => void;
19
27
  };
20
28
 
21
- export default class Fetcher<T> {
29
+ export class Fetcher<T> {
22
30
  private requests: RequestInfoWithHandler<T>[];
23
31
  public lastFetchSucceeded: boolean = false;
24
32
  private failedCount = 0;
@@ -52,19 +60,29 @@ export default class Fetcher<T> {
52
60
  return;
53
61
  }
54
62
 
55
- const promises = this.requests.map<Promise<Error | Response<T>>>(
63
+ const promises = this.requests.map<Promise<Error | Response<T> | SkippingRequest>>(
56
64
  async (reqInfo: RequestInfoWithHandler<T>) => {
57
65
  const info = reqInfo.info;
58
66
  let options = info.requestOptions;
67
+
59
68
  if (info.authenticate) {
60
69
  info.authenticate(options);
61
70
  }
62
71
 
63
72
  try {
64
- const result = await this.requestWrapper.request({
65
- timeout: FETCH_TIMEOUT_MS,
66
- ...options,
67
- });
73
+ let result;
74
+ if (isFunction(info.requestFunc)) {
75
+ result = await info.requestFunc({
76
+ timeout: FETCH_TIMEOUT_MS,
77
+ ...options,
78
+ });
79
+ } else {
80
+ result = await this.requestWrapper.request({
81
+ timeout: FETCH_TIMEOUT_MS,
82
+ ...options,
83
+ });
84
+ }
85
+
68
86
  return result;
69
87
  } catch (e) {
70
88
  return e as Error;
@@ -76,17 +94,28 @@ export default class Fetcher<T> {
76
94
  .map((_, i) => i)
77
95
  .filter(i => results[i] instanceof Error);
78
96
 
79
- results
80
- .filter(result => !(result instanceof Error))
81
- .map(result => {
82
- debugger;
97
+ const skipped = results
98
+ .map((_, i) => i)
99
+ .filter(i => results[i] instanceof SkippingRequest);
100
+
101
+ results.map(result => {
102
+ if(!(result instanceof Error || result instanceof SkippingRequest)) {
83
103
  this.logger.info(
84
104
  'Results Data:',
85
105
  JSON.stringify((result as any).data)
86
106
  .replace(/(?:\r\n|\r|\n)/g, ' ')
87
107
  .substring(0, 1000),
88
108
  );
89
- });
109
+ }
110
+ });
111
+
112
+ skipped.forEach(i => {
113
+ const skippedRes = results[i] as SkippingRequest;
114
+ this.logger.warn(
115
+ `skipped request ${this.requests[i].info.requestOptions.url} ${skippedRes.message}`,
116
+ );
117
+ });
118
+
90
119
  failures.forEach(i => {
91
120
  this.logger.warn(
92
121
  `failed polling ${this.requests[i].info.requestOptions.url} ${results[i]}`,
@@ -102,7 +131,7 @@ export default class Fetcher<T> {
102
131
 
103
132
  results
104
133
  .map((_, i) => i)
105
- .filter(i => !(results[i] instanceof Error))
134
+ .filter(i => !(results[i] instanceof Error || results[i] instanceof SkippingRequest))
106
135
  .forEach(i => {
107
136
  const response = results[i] as Response<T>;
108
137
  const reqInfo = this.requests[i];
@@ -0,0 +1,34 @@
1
+ import joi from 'joi';
2
+
3
+ export class ValidationError extends Error {
4
+ readonly rawMessage: string;
5
+
6
+ readonly key?: string;
7
+
8
+ constructor(message: string, key?: string) {
9
+ super(key !== undefined ? `'${key}': ${message}` : message);
10
+ this.rawMessage = message;
11
+ this.key = key;
12
+ }
13
+ }
14
+
15
+ export const validateAndCast = <T>(
16
+ value: unknown,
17
+ schema: joi.Schema,
18
+ name?: string,
19
+ ): T => {
20
+ const { error, value: parsed } = schema.validate(value);
21
+
22
+ if (error !== undefined) {
23
+ let message;
24
+ if (name) {
25
+ message = `"${name}" ${error.message.slice(
26
+ error.message.indexOf('must'),
27
+ )}`;
28
+ } else {
29
+ message = error.message;
30
+ }
31
+ throw new ValidationError(message);
32
+ }
33
+ return parsed as T;
34
+ };