@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.
- package/.idea/aws.xml +17 -0
- package/.idea/codeStyles/Project.xml +19 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/modules.xml +1 -1
- package/.idea/paraswap-dex-lib.iml +9 -0
- package/.idea/runConfigurations.xml +10 -0
- package/build/dex/aave-v3/tokens/arbitrum.json +20 -20
- package/build/dex/aave-v3/tokens/avalanche.json +44 -20
- package/build/dex/aave-v3/tokens/fantom.json +30 -30
- package/build/dex/aave-v3/tokens/optimism.json +18 -18
- package/build/dex/aave-v3/tokens/polygon.json +60 -42
- package/build/dex/generic-rfq/fetch-mm-api.js +11 -10
- package/build/dex/generic-rfq/fetch-mm-api.js.map +1 -1
- package/build/dex/generic-rfq/rate-fetcher.js +12 -11
- package/build/dex/generic-rfq/rate-fetcher.js.map +1 -1
- package/build/dex/generic-rfq/validators.d.ts +0 -6
- package/build/dex/generic-rfq/validators.js +1 -24
- package/build/dex/generic-rfq/validators.js.map +1 -1
- package/build/dex/hashflow/constants.d.ts +6 -1
- package/build/dex/hashflow/constants.js +7 -2
- package/build/dex/hashflow/constants.js.map +1 -1
- package/build/dex/hashflow/hashflow.d.ts +9 -2
- package/build/dex/hashflow/hashflow.js +65 -21
- package/build/dex/hashflow/hashflow.js.map +1 -1
- package/build/dex/hashflow/rate-fetcher.d.ts +21 -0
- package/build/dex/hashflow/rate-fetcher.js +67 -0
- package/build/dex/hashflow/rate-fetcher.js.map +1 -0
- package/build/dex/hashflow/types.d.ts +36 -0
- package/build/dex/hashflow/types.js.map +1 -1
- package/build/dex/hashflow/validators.d.ts +4 -0
- package/build/dex/hashflow/validators.js +31 -0
- package/build/dex/hashflow/validators.js.map +1 -0
- package/build/lib/fetcher/fetcher.d.ts +7 -2
- package/build/lib/fetcher/fetcher.js +37 -13
- package/build/lib/fetcher/fetcher.js.map +1 -1
- package/build/lib/validators.d.ts +7 -0
- package/build/lib/validators.js +27 -0
- package/build/lib/validators.js.map +1 -0
- package/package.json +1 -1
- package/src/dex/generic-rfq/fetch-mm-api.ts +2 -2
- package/src/dex/generic-rfq/generic-rfq-e2e.test.ts +10 -26
- package/src/dex/generic-rfq/rate-fetcher.ts +4 -5
- package/src/dex/generic-rfq/validators.test.ts +1 -1
- package/src/dex/generic-rfq/validators.ts +0 -33
- package/src/dex/hashflow/constants.ts +12 -2
- package/src/dex/hashflow/hashflow.ts +117 -60
- package/src/dex/hashflow/rate-fetcher.ts +120 -0
- package/src/dex/hashflow/types.ts +40 -0
- package/src/dex/hashflow/validators.ts +27 -0
- package/src/lib/fetcher/fetcher.ts +41 -12
- package/src/lib/validators.ts +34 -0
- package/tests/utils-e2e.ts +13 -13
- package/.vscode/launch.json +0 -21
- package/build/dex/aave-v3/tokens/mainnet.json +0 -44
- package/build/dex/balancer-v2/VirtualBoostedPool.d.ts +0 -79
- package/build/dex/balancer-v2/VirtualBoostedPool.js +0 -382
- package/build/dex/balancer-v2/VirtualBoostedPool.js.map +0 -1
- package/build/dex/test-aave-v3/config.d.ts +0 -4
- package/build/dex/test-aave-v3/config.js +0 -15
- package/build/dex/test-aave-v3/config.js.map +0 -1
- package/build/dex/test-aave-v3/test-aave-v3-pool.d.ts +0 -39
- package/build/dex/test-aave-v3/test-aave-v3-pool.js +0 -63
- package/build/dex/test-aave-v3/test-aave-v3-pool.js.map +0 -1
- package/build/dex/test-aave-v3/test-aave-v3.d.ts +0 -37
- package/build/dex/test-aave-v3/test-aave-v3.js +0 -126
- package/build/dex/test-aave-v3/test-aave-v3.js.map +0 -1
- package/build/dex/test-aave-v3/types.d.ts +0 -6
- package/build/dex/test-aave-v3/types.js +0 -3
- 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
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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(
|
|
148
|
-
const
|
|
149
|
-
this.
|
|
150
|
-
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
377
|
+
async getCachedMarketMakers(): Promise<
|
|
378
|
+
MarketMakersResponse['marketMakers'] | null
|
|
379
|
+
> {
|
|
380
|
+
const cachedMarketMakers = await this.dexHelper.cache.rawget(
|
|
381
|
+
this.marketMakersCacheKey,
|
|
320
382
|
);
|
|
321
|
-
|
|
322
|
-
|
|
383
|
+
|
|
384
|
+
if (cachedMarketMakers) {
|
|
385
|
+
return JSON.parse(
|
|
386
|
+
cachedMarketMakers,
|
|
387
|
+
) as MarketMakersResponse['marketMakers'];
|
|
323
388
|
}
|
|
324
389
|
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
|
820
|
-
|
|
821
|
-
const
|
|
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
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
.
|
|
81
|
-
.
|
|
82
|
-
|
|
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
|
+
};
|