Skip to content

Commit eb846cb

Browse files
authored
fix: client side rate limiter for coingecko (#1898)
perf: refactor cardanoTokenPrices to make less requests - tokens price tracking is now fully in the SW: UI no longer requests token price, only reads what's currently available - throttle fetch requests that come from UI - batch token price map updates sent back to UI - wait until initial token price fetch completes before requesting again - add checks to exclude nfts from token price requests: some of those checks are probably redundant today, but it's good to be defensive to make sure it doesn't break again in the future other changes: - do not bother the user about 429 errors: it's a bug on client-side, we can't fix it without extension update
1 parent 5f8a27a commit eb846cb

File tree

15 files changed

+279
-187
lines changed

15 files changed

+279
-187
lines changed

apps/browser-extension-wallet/src/api/transformers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { formatDate, formatTime } from '../utils/format-date';
1414
import { TokenInfo } from '@src/utils/get-assets-information';
1515
import { getTokenAmountInFiat, parseFiat } from '@src/utils/assets-transformers';
1616
import { PriceResult } from '@hooks';
17+
import { mayBeNFT } from '@src/utils/is-nft';
1718

1819
export const walletBalanceTransformer = (lovelaceBalance: string, fiat?: number): WalletBalance => {
1920
const adaValue = Wallet.util.lovelacesToAdaString(lovelaceBalance);
@@ -61,7 +62,7 @@ export const tokenTransformer = (
6162
const { name } = { ...tokenMetadata, ...nftMetadata };
6263
const [assetId, bigintBalance] = assetBalance;
6364
const amount = Wallet.util.calculateAssetBalance(bigintBalance, assetInfo);
64-
const tokenPriceInAda = prices?.cardano.getTokenPrice(assetId)?.priceInAda;
65+
const tokenPriceInAda = mayBeNFT(assetInfo) ? undefined : prices?.cardano.getTokenPrice(assetId)?.priceInAda;
6566
const fiatBalance =
6667
tokenMetadata !== undefined &&
6768
tokenPriceInAda &&

apps/browser-extension-wallet/src/hooks/useFetchCoinPrice.ts

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { useMemo } from 'react';
2-
import { logger, useObservable } from '@lace/common';
2+
import { useObservable } from '@lace/common';
33
import { TokenPrices, StatusTypes, ADAPricesKeys, TokenPrice } from '@lib/scripts/types';
44
import { useBackgroundServiceAPIContext, useCurrencyStore } from '../providers';
55
import { CARDANO_COIN_SYMBOL } from '@src/utils/constants';
66
import { Wallet } from '@lace/cardano';
7-
import { config } from '@src/config';
8-
import { useWalletStore } from '@stores';
97

108
export interface PriceResult {
119
cardano: {
@@ -27,15 +25,12 @@ export interface UseFetchCoinPrice {
2725
timestamp?: number;
2826
}
2927

30-
const { TOKEN_PRICE_CHECK_INTERVAL } = config();
31-
3228
export const useFetchCoinPrice = (): UseFetchCoinPrice => {
33-
const { coinPrices, trackCardanoTokenPrice } = useBackgroundServiceAPIContext();
29+
const { coinPrices } = useBackgroundServiceAPIContext();
3430
const { fiatCurrency } = useCurrencyStore();
3531
const tokenPrices = useObservable(coinPrices.tokenPrices$);
3632
const adaPrices = useObservable(coinPrices.adaPrices$);
3733
const bitcoinPrices = useObservable(coinPrices.bitcoinPrices$);
38-
const networkId = useWalletStore((state) => state.currentChain?.networkId);
3934

4035
const isAdaCurrency = fiatCurrency.code === CARDANO_COIN_SYMBOL[Wallet.Cardano.NetworkId.Mainnet];
4136

@@ -50,35 +45,13 @@ export const useFetchCoinPrice = (): UseFetchCoinPrice => {
5045

5146
const cardano = useMemo(
5247
() => ({
53-
getTokenPrice: (assetId: Wallet.Cardano.AssetId): TokenPrice | undefined => {
54-
const tokenPrice = tokenPrices?.tokens.get(assetId);
55-
// Actually track the price only for token in Cardano mainnet, otherwise just do nothing
56-
const trackPrice = () =>
57-
networkId === Wallet.Cardano.NetworkId.Mainnet
58-
? trackCardanoTokenPrice(assetId).catch((error) => logger.error(error))
59-
: undefined;
60-
61-
// If the price for this token was never fetched, wee need to track it
62-
if (!tokenPrice) {
63-
trackPrice();
64-
65-
return undefined;
66-
}
67-
68-
const { lastFetchTime, price } = tokenPrice;
69-
70-
// If the price was fetched, but it is still not present, it means the price for this token is not tracked by CoinGecko:
71-
// let's retry a new fetch only after the TOKEN_PRICE_CHECK_INTERVAL to check if now the price is being tracked.
72-
if (!price && lastFetchTime < Date.now() - TOKEN_PRICE_CHECK_INTERVAL) trackPrice();
73-
74-
// eslint-disable-next-line consistent-return
75-
return price;
76-
},
48+
getTokenPrice: (assetId: Wallet.Cardano.AssetId): TokenPrice | undefined =>
49+
tokenPrices?.tokens.get(assetId)?.price,
7750
price: isAdaCurrency ? 1 : adaPrices?.prices?.[fiatCurrency.code.toLowerCase() as ADAPricesKeys],
7851
priceVariationPercentage24h:
7952
adaPrices?.prices?.[`${fiatCurrency.code.toLowerCase()}_24h_change` as ADAPricesKeys] || 0
8053
}),
81-
[adaPrices?.prices, fiatCurrency.code, isAdaCurrency, tokenPrices?.tokens, trackCardanoTokenPrice, networkId]
54+
[adaPrices?.prices, fiatCurrency.code, isAdaCurrency, tokenPrices?.tokens]
8255
);
8356

8457
return useMemo(

apps/browser-extension-wallet/src/lib/scripts/background/config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export const backgroundServiceProperties: RemoteApiProperties<BackgroundService>
1919
adaPrices$: RemoteApiPropertyType.HotObservable,
2020
tokenPrices$: RemoteApiPropertyType.HotObservable
2121
},
22-
trackCardanoTokenPrice: RemoteApiPropertyType.MethodReturningPromise,
2322
handleOpenBrowser: RemoteApiPropertyType.MethodReturningPromise,
2423
handleOpenNamiBrowser: RemoteApiPropertyType.MethodReturningPromise,
2524
closeAllTabsAndOpenPopup: RemoteApiPropertyType.MethodReturningPromise,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { wallet$ } from './wallet';
2+
import { exposeBackgroundService } from './services/utilityServices';
3+
4+
exposeBackgroundService(wallet$);

apps/browser-extension-wallet/src/lib/scripts/background/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import './onError';
33
import './onUpdate';
44
import './services';
55
import './services/expose';
6+
import './expose-background-service';
67
import './keep-alive-sw';
78
import './onUninstall';
89
import './nami-migration';

apps/browser-extension-wallet/src/lib/scripts/background/onError.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const INTERNAL_SERVER_ERROR_STATUS_CODE = 500;
88
const GATEWAY_TIMEOUT_STATUS_CODE = 503;
99
const UNAUTHORIZED_STATUS_CODE = 401;
1010
const NOT_FOUND_STATUS_CODE = 404;
11+
const TOO_MANY_REQUESTS_STATUS_CODE = 429;
1112

1213
// eslint-disable-next-line unicorn/no-null
1314
let sentryUrl: string | null = '';
@@ -21,7 +22,10 @@ const isSentryRequest = (url: string) => !!sentryUrl && url.startsWith(sentryUrl
2122
const handleProviderServerErrors = (data: WebRequest.OnCompletedDetailsType) => {
2223
if (data?.type === 'xmlhttprequest' && runtime.getURL('').startsWith(data.initiator)) {
2324
const statusCodeQualifiedAsFailure =
25+
// normal response for empty/no data from Blockfrost
2426
data.statusCode !== NOT_FOUND_STATUS_CODE &&
27+
// client's fault, not server's fault
28+
data.statusCode !== TOO_MANY_REQUESTS_STATUS_CODE &&
2529
data.statusCode > UNAUTHORIZED_STATUS_CODE &&
2630
data.statusCode < GATEWAY_TIMEOUT_STATUS_CODE;
2731
if (statusCodeQualifiedAsFailure) {

0 commit comments

Comments
 (0)