Skip to content

Commit

Permalink
Merge pull request #650 from Keeper-Wallet/KEEP-1125/usd-prices-from-…
Browse files Browse the repository at this point in the history
…data-service

load usd prices from data service
  • Loading branch information
domnikov-timofei authored Feb 3, 2023
2 parents 544ea18 + db6e948 commit 46871e7
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"dist/build/popup.js",
"dist/build/vendors*.js"
],
"limit": "438 kB"
"limit": "439 kB"
},
{
"name": "contentscript",
Expand Down
100 changes: 100 additions & 0 deletions src/_core/usdPrices.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import invariant from 'tiny-invariant';

import { usePopupSelector } from '../popup/store/react';
import Background from '../ui/services/Background';
import { startPolling } from './polling';
import { useDebouncedValue } from './useDebouncedValue';

const USD_PRICES_UPDATE_INTERVAL = 5000;

const UsdPricesContext = createContext<
((assetIds: string[]) => (() => void) | undefined) | null
>(null);

export function UsdPricesProvider({ children }: { children: ReactNode }) {
const lastUpdatedAssetIdsTimestampsRef = useRef<Record<string, number>>({});
const [observedAssetIds, setObservedAssetIds] = useState<string[][]>([]);
const observedAssetIdsDebounced = useDebouncedValue(observedAssetIds, 100);

useEffect(() => {
const idsToUpdate = Array.from(new Set(observedAssetIdsDebounced.flat()));

if (idsToUpdate.length === 0) {
return;
}

return startPolling(USD_PRICES_UPDATE_INTERVAL, async () => {
const currentTime = new Date().getTime();

const areAllAssetsUpToDate = idsToUpdate.every(id => {
const timestamp = lastUpdatedAssetIdsTimestampsRef.current[id];

if (timestamp == null) {
return false;
}

return currentTime - timestamp < USD_PRICES_UPDATE_INTERVAL;
});

if (!areAllAssetsUpToDate) {
await Background.updateUsdPricesByAssetIds(idsToUpdate);

const updatedTime = new Date().getTime();

for (const id of idsToUpdate) {
lastUpdatedAssetIdsTimestampsRef.current[id] = updatedTime;
}
}
});
}, [observedAssetIdsDebounced]);

const observe = useCallback((assetIds: string[]) => {
setObservedAssetIds(ids => [...ids, assetIds]);

return () => {
setObservedAssetIds(prev => prev.filter(ids => ids !== assetIds));
};
}, []);

return (
<UsdPricesContext.Provider value={observe}>
{children}
</UsdPricesContext.Provider>
);
}

export function useUsdPrices(assetIds: string[]) {
const currentNetwork = usePopupSelector(state => state.currentNetwork);
const isMainnet = currentNetwork === 'mainnet';

const observe = useContext(UsdPricesContext);
invariant(observe);

useEffect(() => {
if (!isMainnet) {
return;
}

return observe(assetIds);
}, [observe, assetIds, isMainnet]);

const usdPrices = usePopupSelector(state => state.usdPrices);

return useMemo(() => {
const assetIdsSet = new Set(assetIds);

return Object.fromEntries(
Object.entries(usdPrices).filter(([id]) => assetIdsSet.has(id))
);
}, [assetIds, usdPrices]);
}
9 changes: 0 additions & 9 deletions src/assets/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,6 @@ export const assetIds: Record<NetworkName, Record<string, string>> = {
},
};

export const stablecoinAssetIds = new Set([
'2thtesXvnVMcCnih9iZbJL3d2NQZMfzENJo8YFj6r5jU',
'34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ',
'6XtHjpXbs9RRJP2Sr9GUyVqzACcby9TkThHXnjVC5CDJ',
'8DLiYZjo3UUaRBTHU7Ayoqg4ihwb6YH1AfXrrhdjQ7K1',
'8zUYbdB8Q6mDhpcXYv52ji8ycfj4SDX4gJXS7YY3dA4R',
'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p',
]);

export const defaultAssetTickers = {
B1dG9exXzJdFASDF2MwCE7TYJE5My4UgVRx43nqDbF6s: 'ABTCLPC',
'4NyYnDGopZvEAQ3TcBDJrJFWSiA2xzuAw83Ms8jT7WuK': 'ABTCLPM',
Expand Down
4 changes: 4 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,10 @@ class BackgroundService extends EventEmitter {
updateAssets: this.assetInfoController.updateAssets.bind(
this.assetInfoController
),
updateUsdPricesByAssetIds:
this.assetInfoController.updateUsdPricesByAssetIds.bind(
this.assetInfoController
),
toggleAssetFavorite: this.assetInfoController.toggleAssetFavorite.bind(
this.assetInfoController
),
Expand Down
72 changes: 24 additions & 48 deletions src/controllers/assetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NetworkName } from 'networks/types';
import ObservableStore from 'obs-store';
import Browser from 'webextension-polyfill';

import { defaultAssetTickers, stablecoinAssetIds } from '../assets/constants';
import { defaultAssetTickers } from '../assets/constants';
import { ExtensionStorage, StorageLocalState } from '../storage/storage';
import { NetworkController } from './network';
import { RemoteConfigController } from './remoteConfig';
Expand All @@ -30,12 +30,8 @@ const SUSPICIOUS_LIST_URL =
const SUSPICIOUS_PERIOD_IN_MINUTES = 60;
const MAX_AGE = 60 * 60 * 1000;

const MARKETDATA_URL = 'https://marketdata.wavesplatform.com/';
const MARKETDATA_USD_ASSET_ID = 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p';
const MARKETDATA_PERIOD_IN_MINUTES = 10;

const STATIC_SERVICE_URL = 'https://api.keeper-wallet.app';
const SWAPSERVICE_URL = 'https://swap-api.keeper-wallet.app';
const DATA_SERVICE_URL = 'https://api.keeper-wallet.app';
const SWAP_SERVICE_URL = 'https://swap-api.keeper-wallet.app';

const INFO_PERIOD_IN_MINUTES = 60;
const SWAPPABLE_ASSETS_UPDATE_PERIOD_IN_MINUTES = 240;
Expand Down Expand Up @@ -123,19 +119,12 @@ export class AssetInfoController {
this.updateSuspiciousAssets();
}

if (Object.keys(initState.usdPrices).length === 0) {
this.updateUsdPrices();
}

this.updateInfo();
this.updateSwappableAssetIdsByVendor();

Browser.alarms.create('updateSuspiciousAssets', {
periodInMinutes: SUSPICIOUS_PERIOD_IN_MINUTES,
});
Browser.alarms.create('updateUsdPrices', {
periodInMinutes: MARKETDATA_PERIOD_IN_MINUTES,
});
Browser.alarms.create('updateInfo', {
periodInMinutes: INFO_PERIOD_IN_MINUTES,
});
Expand All @@ -148,9 +137,6 @@ export class AssetInfoController {
case 'updateSuspiciousAssets':
this.updateSuspiciousAssets();
break;
case 'updateUsdPrices':
this.updateUsdPrices();
break;
case 'updateInfo':
this.updateInfo();
break;
Expand Down Expand Up @@ -392,49 +378,39 @@ export class AssetInfoController {
}
}

async updateUsdPrices() {
const { usdPrices } = this.store.getState();
async updateUsdPricesByAssetIds(assetIds: string[]) {
const network = this.getNetwork();

if (!usdPrices || network === NetworkName.Mainnet) {
const resp = await fetch(new URL('/api/tickers', MARKETDATA_URL));
if (assetIds.length === 0 || network !== NetworkName.Mainnet) {
return;
}

if (resp.ok) {
const tickers = (await resp.json()) as Array<{
'24h_close': string;
amountAssetID: string;
priceAssetID: string;
}>;
const { usdPrices } = this.store.getState();

// eslint-disable-next-line @typescript-eslint/no-shadow
const usdPrices = tickers.reduce<Record<string, string>>(
(acc, ticker) => {
if (
!stablecoinAssetIds.has(ticker.amountAssetID) &&
ticker.priceAssetID === MARKETDATA_USD_ASSET_ID
) {
acc[ticker.amountAssetID] = ticker['24h_close'];
}
const response = await fetch(new URL('/api/v1/rates', DATA_SERVICE_URL), {
method: 'POST',
body: JSON.stringify({ ids: assetIds }),
});

return acc;
},
{}
);
if (!response.ok) {
throw response;
}

stablecoinAssetIds.forEach(ticker => {
usdPrices[ticker] = '1';
});
const updatedUsdPrices: Record<string, string> = await response.json();

this.store.updateState({ usdPrices });
}
}
this.store.updateState({
usdPrices: {
...usdPrices,
...updatedUsdPrices,
},
});
}

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

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

if (resp.ok) {
const assets = (await resp.json()) as Array<{
Expand Down Expand Up @@ -463,7 +439,7 @@ export class AssetInfoController {
}

async updateSwappableAssetIdsByVendor() {
const resp = await fetch(new URL('/assets', SWAPSERVICE_URL));
const resp = await fetch(new URL('/assets', SWAP_SERVICE_URL));
if (resp.ok) {
const swappableAssetIdsByVendor = (await resp.json()) as Record<
string,
Expand Down
9 changes: 6 additions & 3 deletions src/popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import invariant from 'tiny-invariant';
import Browser from 'webextension-polyfill';

import { SignProvider } from './_core/signContext';
import { UsdPricesProvider } from './_core/usdPrices';
import type { UiApi } from './background';
import { i18nextInit } from './i18n/init';
import {
Expand Down Expand Up @@ -58,9 +59,11 @@ Promise.all([
<StrictMode>
<Provider store={store}>
<RootWrapper>
<SignProvider>
<PopupRoot />
</SignProvider>
<UsdPricesProvider>
<SignProvider>
<PopupRoot />
</SignProvider>
</UsdPricesProvider>
</RootWrapper>
</Provider>
</StrictMode>
Expand Down
22 changes: 15 additions & 7 deletions src/ui/components/ui/UsdAmount/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import BigNumber from '@waves/bignumber';
import { usePopupSelector } from 'popup/store/react';
import { useMemo } from 'react';

import { useUsdPrices } from '../../../../_core/usdPrices';
import { Loader } from '../loader';

interface Props {
id: string;
Expand All @@ -8,18 +12,22 @@ interface Props {
}

export function UsdAmount({ id, tokens, className }: Props) {
const usdPrices = usePopupSelector(state => state.usdPrices);

const currentNetwork = usePopupSelector(state => state.currentNetwork);
const isMainnet = currentNetwork === 'mainnet';

if (!usdPrices || !isMainnet) {
const usdPrices = useUsdPrices(useMemo(() => [id], [id]));

if (!isMainnet) {
return null;
}

return !usdPrices[id] || usdPrices[id] === '1' ? null : (
<p className={className}>{`≈ $${new BigNumber(usdPrices[id])
.mul(tokens)
.toFixed(2)}`}</p>
if (usdPrices[id] == null) {
return <Loader />;
}

return (
<p className={className}>
≈ ${new BigNumber(usdPrices[id]).mul(tokens).toFixed(2)}
</p>
);
}
7 changes: 7 additions & 0 deletions src/ui/services/Background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,13 @@ class Background {
return await this.background!.updateAssets(assetIds, options);
}

async updateUsdPricesByAssetIds(assetIds: string[]) {
await this.initPromise;
this._connect();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return await this.background!.updateUsdPricesByAssetIds(assetIds);
}

async setAddress(address: string, name: string): Promise<void> {
await this.initPromise;
this._connect();
Expand Down

0 comments on commit 46871e7

Please sign in to comment.