Skip to content

Commit 46871e7

Browse files
Merge pull request #650 from Keeper-Wallet/KEEP-1125/usd-prices-from-data-service
load usd prices from data service
2 parents 544ea18 + db6e948 commit 46871e7

File tree

8 files changed

+157
-68
lines changed

8 files changed

+157
-68
lines changed

.size-limit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"dist/build/popup.js",
1818
"dist/build/vendors*.js"
1919
],
20-
"limit": "438 kB"
20+
"limit": "439 kB"
2121
},
2222
{
2323
"name": "contentscript",

src/_core/usdPrices.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
createContext,
3+
ReactNode,
4+
useCallback,
5+
useContext,
6+
useEffect,
7+
useMemo,
8+
useRef,
9+
useState,
10+
} from 'react';
11+
import invariant from 'tiny-invariant';
12+
13+
import { usePopupSelector } from '../popup/store/react';
14+
import Background from '../ui/services/Background';
15+
import { startPolling } from './polling';
16+
import { useDebouncedValue } from './useDebouncedValue';
17+
18+
const USD_PRICES_UPDATE_INTERVAL = 5000;
19+
20+
const UsdPricesContext = createContext<
21+
((assetIds: string[]) => (() => void) | undefined) | null
22+
>(null);
23+
24+
export function UsdPricesProvider({ children }: { children: ReactNode }) {
25+
const lastUpdatedAssetIdsTimestampsRef = useRef<Record<string, number>>({});
26+
const [observedAssetIds, setObservedAssetIds] = useState<string[][]>([]);
27+
const observedAssetIdsDebounced = useDebouncedValue(observedAssetIds, 100);
28+
29+
useEffect(() => {
30+
const idsToUpdate = Array.from(new Set(observedAssetIdsDebounced.flat()));
31+
32+
if (idsToUpdate.length === 0) {
33+
return;
34+
}
35+
36+
return startPolling(USD_PRICES_UPDATE_INTERVAL, async () => {
37+
const currentTime = new Date().getTime();
38+
39+
const areAllAssetsUpToDate = idsToUpdate.every(id => {
40+
const timestamp = lastUpdatedAssetIdsTimestampsRef.current[id];
41+
42+
if (timestamp == null) {
43+
return false;
44+
}
45+
46+
return currentTime - timestamp < USD_PRICES_UPDATE_INTERVAL;
47+
});
48+
49+
if (!areAllAssetsUpToDate) {
50+
await Background.updateUsdPricesByAssetIds(idsToUpdate);
51+
52+
const updatedTime = new Date().getTime();
53+
54+
for (const id of idsToUpdate) {
55+
lastUpdatedAssetIdsTimestampsRef.current[id] = updatedTime;
56+
}
57+
}
58+
});
59+
}, [observedAssetIdsDebounced]);
60+
61+
const observe = useCallback((assetIds: string[]) => {
62+
setObservedAssetIds(ids => [...ids, assetIds]);
63+
64+
return () => {
65+
setObservedAssetIds(prev => prev.filter(ids => ids !== assetIds));
66+
};
67+
}, []);
68+
69+
return (
70+
<UsdPricesContext.Provider value={observe}>
71+
{children}
72+
</UsdPricesContext.Provider>
73+
);
74+
}
75+
76+
export function useUsdPrices(assetIds: string[]) {
77+
const currentNetwork = usePopupSelector(state => state.currentNetwork);
78+
const isMainnet = currentNetwork === 'mainnet';
79+
80+
const observe = useContext(UsdPricesContext);
81+
invariant(observe);
82+
83+
useEffect(() => {
84+
if (!isMainnet) {
85+
return;
86+
}
87+
88+
return observe(assetIds);
89+
}, [observe, assetIds, isMainnet]);
90+
91+
const usdPrices = usePopupSelector(state => state.usdPrices);
92+
93+
return useMemo(() => {
94+
const assetIdsSet = new Set(assetIds);
95+
96+
return Object.fromEntries(
97+
Object.entries(usdPrices).filter(([id]) => assetIdsSet.has(id))
98+
);
99+
}, [assetIds, usdPrices]);
100+
}

src/assets/constants.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,6 @@ export const assetIds: Record<NetworkName, Record<string, string>> = {
9898
},
9999
};
100100

101-
export const stablecoinAssetIds = new Set([
102-
'2thtesXvnVMcCnih9iZbJL3d2NQZMfzENJo8YFj6r5jU',
103-
'34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ',
104-
'6XtHjpXbs9RRJP2Sr9GUyVqzACcby9TkThHXnjVC5CDJ',
105-
'8DLiYZjo3UUaRBTHU7Ayoqg4ihwb6YH1AfXrrhdjQ7K1',
106-
'8zUYbdB8Q6mDhpcXYv52ji8ycfj4SDX4gJXS7YY3dA4R',
107-
'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p',
108-
]);
109-
110101
export const defaultAssetTickers = {
111102
B1dG9exXzJdFASDF2MwCE7TYJE5My4UgVRx43nqDbF6s: 'ABTCLPC',
112103
'4NyYnDGopZvEAQ3TcBDJrJFWSiA2xzuAw83Ms8jT7WuK': 'ABTCLPM',

src/background.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,10 @@ class BackgroundService extends EventEmitter {
532532
updateAssets: this.assetInfoController.updateAssets.bind(
533533
this.assetInfoController
534534
),
535+
updateUsdPricesByAssetIds:
536+
this.assetInfoController.updateUsdPricesByAssetIds.bind(
537+
this.assetInfoController
538+
),
535539
toggleAssetFavorite: this.assetInfoController.toggleAssetFavorite.bind(
536540
this.assetInfoController
537541
),

src/controllers/assetInfo.ts

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NetworkName } from 'networks/types';
44
import ObservableStore from 'obs-store';
55
import Browser from 'webextension-polyfill';
66

7-
import { defaultAssetTickers, stablecoinAssetIds } from '../assets/constants';
7+
import { defaultAssetTickers } from '../assets/constants';
88
import { ExtensionStorage, StorageLocalState } from '../storage/storage';
99
import { NetworkController } from './network';
1010
import { RemoteConfigController } from './remoteConfig';
@@ -30,12 +30,8 @@ const SUSPICIOUS_LIST_URL =
3030
const SUSPICIOUS_PERIOD_IN_MINUTES = 60;
3131
const MAX_AGE = 60 * 60 * 1000;
3232

33-
const MARKETDATA_URL = 'https://marketdata.wavesplatform.com/';
34-
const MARKETDATA_USD_ASSET_ID = 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p';
35-
const MARKETDATA_PERIOD_IN_MINUTES = 10;
36-
37-
const STATIC_SERVICE_URL = 'https://api.keeper-wallet.app';
38-
const SWAPSERVICE_URL = 'https://swap-api.keeper-wallet.app';
33+
const DATA_SERVICE_URL = 'https://api.keeper-wallet.app';
34+
const SWAP_SERVICE_URL = 'https://swap-api.keeper-wallet.app';
3935

4036
const INFO_PERIOD_IN_MINUTES = 60;
4137
const SWAPPABLE_ASSETS_UPDATE_PERIOD_IN_MINUTES = 240;
@@ -123,19 +119,12 @@ export class AssetInfoController {
123119
this.updateSuspiciousAssets();
124120
}
125121

126-
if (Object.keys(initState.usdPrices).length === 0) {
127-
this.updateUsdPrices();
128-
}
129-
130122
this.updateInfo();
131123
this.updateSwappableAssetIdsByVendor();
132124

133125
Browser.alarms.create('updateSuspiciousAssets', {
134126
periodInMinutes: SUSPICIOUS_PERIOD_IN_MINUTES,
135127
});
136-
Browser.alarms.create('updateUsdPrices', {
137-
periodInMinutes: MARKETDATA_PERIOD_IN_MINUTES,
138-
});
139128
Browser.alarms.create('updateInfo', {
140129
periodInMinutes: INFO_PERIOD_IN_MINUTES,
141130
});
@@ -148,9 +137,6 @@ export class AssetInfoController {
148137
case 'updateSuspiciousAssets':
149138
this.updateSuspiciousAssets();
150139
break;
151-
case 'updateUsdPrices':
152-
this.updateUsdPrices();
153-
break;
154140
case 'updateInfo':
155141
this.updateInfo();
156142
break;
@@ -392,49 +378,39 @@ export class AssetInfoController {
392378
}
393379
}
394380

395-
async updateUsdPrices() {
396-
const { usdPrices } = this.store.getState();
381+
async updateUsdPricesByAssetIds(assetIds: string[]) {
397382
const network = this.getNetwork();
398383

399-
if (!usdPrices || network === NetworkName.Mainnet) {
400-
const resp = await fetch(new URL('/api/tickers', MARKETDATA_URL));
384+
if (assetIds.length === 0 || network !== NetworkName.Mainnet) {
385+
return;
386+
}
401387

402-
if (resp.ok) {
403-
const tickers = (await resp.json()) as Array<{
404-
'24h_close': string;
405-
amountAssetID: string;
406-
priceAssetID: string;
407-
}>;
388+
const { usdPrices } = this.store.getState();
408389

409-
// eslint-disable-next-line @typescript-eslint/no-shadow
410-
const usdPrices = tickers.reduce<Record<string, string>>(
411-
(acc, ticker) => {
412-
if (
413-
!stablecoinAssetIds.has(ticker.amountAssetID) &&
414-
ticker.priceAssetID === MARKETDATA_USD_ASSET_ID
415-
) {
416-
acc[ticker.amountAssetID] = ticker['24h_close'];
417-
}
390+
const response = await fetch(new URL('/api/v1/rates', DATA_SERVICE_URL), {
391+
method: 'POST',
392+
body: JSON.stringify({ ids: assetIds }),
393+
});
418394

419-
return acc;
420-
},
421-
{}
422-
);
395+
if (!response.ok) {
396+
throw response;
397+
}
423398

424-
stablecoinAssetIds.forEach(ticker => {
425-
usdPrices[ticker] = '1';
426-
});
399+
const updatedUsdPrices: Record<string, string> = await response.json();
427400

428-
this.store.updateState({ usdPrices });
429-
}
430-
}
401+
this.store.updateState({
402+
usdPrices: {
403+
...usdPrices,
404+
...updatedUsdPrices,
405+
},
406+
});
431407
}
432408

433409
async updateInfo() {
434410
const network = this.getNetwork();
435411

436412
if (network === NetworkName.Mainnet) {
437-
const resp = await fetch(new URL('/api/v1/assets', STATIC_SERVICE_URL));
413+
const resp = await fetch(new URL('/api/v1/assets', DATA_SERVICE_URL));
438414

439415
if (resp.ok) {
440416
const assets = (await resp.json()) as Array<{
@@ -463,7 +439,7 @@ export class AssetInfoController {
463439
}
464440

465441
async updateSwappableAssetIdsByVendor() {
466-
const resp = await fetch(new URL('/assets', SWAPSERVICE_URL));
442+
const resp = await fetch(new URL('/assets', SWAP_SERVICE_URL));
467443
if (resp.ok) {
468444
const swappableAssetIdsByVendor = (await resp.json()) as Record<
469445
string,

src/popup.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import invariant from 'tiny-invariant';
1313
import Browser from 'webextension-polyfill';
1414

1515
import { SignProvider } from './_core/signContext';
16+
import { UsdPricesProvider } from './_core/usdPrices';
1617
import type { UiApi } from './background';
1718
import { i18nextInit } from './i18n/init';
1819
import {
@@ -58,9 +59,11 @@ Promise.all([
5859
<StrictMode>
5960
<Provider store={store}>
6061
<RootWrapper>
61-
<SignProvider>
62-
<PopupRoot />
63-
</SignProvider>
62+
<UsdPricesProvider>
63+
<SignProvider>
64+
<PopupRoot />
65+
</SignProvider>
66+
</UsdPricesProvider>
6467
</RootWrapper>
6568
</Provider>
6669
</StrictMode>
Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import BigNumber from '@waves/bignumber';
22
import { usePopupSelector } from 'popup/store/react';
3+
import { useMemo } from 'react';
4+
5+
import { useUsdPrices } from '../../../../_core/usdPrices';
6+
import { Loader } from '../loader';
37

48
interface Props {
59
id: string;
@@ -8,18 +12,22 @@ interface Props {
812
}
913

1014
export function UsdAmount({ id, tokens, className }: Props) {
11-
const usdPrices = usePopupSelector(state => state.usdPrices);
12-
1315
const currentNetwork = usePopupSelector(state => state.currentNetwork);
1416
const isMainnet = currentNetwork === 'mainnet';
1517

16-
if (!usdPrices || !isMainnet) {
18+
const usdPrices = useUsdPrices(useMemo(() => [id], [id]));
19+
20+
if (!isMainnet) {
1721
return null;
1822
}
1923

20-
return !usdPrices[id] || usdPrices[id] === '1' ? null : (
21-
<p className={className}>{`≈ $${new BigNumber(usdPrices[id])
22-
.mul(tokens)
23-
.toFixed(2)}`}</p>
24+
if (usdPrices[id] == null) {
25+
return <Loader />;
26+
}
27+
28+
return (
29+
<p className={className}>
30+
≈ ${new BigNumber(usdPrices[id]).mul(tokens).toFixed(2)}
31+
</p>
2432
);
2533
}

src/ui/services/Background.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,13 @@ class Background {
334334
return await this.background!.updateAssets(assetIds, options);
335335
}
336336

337+
async updateUsdPricesByAssetIds(assetIds: string[]) {
338+
await this.initPromise;
339+
this._connect();
340+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
341+
return await this.background!.updateUsdPricesByAssetIds(assetIds);
342+
}
343+
337344
async setAddress(address: string, name: string): Promise<void> {
338345
await this.initPromise;
339346
this._connect();

0 commit comments

Comments
 (0)