From eddb1e1e8d27b6120c8d6ab5a8f08274f461fab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Wed, 16 Oct 2024 18:06:02 +0200 Subject: [PATCH] fix: do not preload & poll for NFT balances by default (#69) --- package.json | 8 +-- .../balances/BalanceAggregatorService.test.ts | 21 +++++--- .../balances/BalanceAggregatorService.ts | 19 ++++--- .../balances/BalancePollingService.test.ts | 11 +++- .../balances/BalancePollingService.ts | 38 ++++++++++--- .../services/balances/BalancesService.test.ts | 48 +++++++++++++---- .../services/balances/BalancesService.ts | 6 +++ .../balances/handlers/getAvaxBalance.ts | 3 +- .../handlers/startBalancesPolling.test.ts | 6 ++- .../balances/handlers/startBalancesPolling.ts | 9 ++-- .../handlers/updateBalancesForNetwork.ts | 5 +- .../services/bridge/BridgeService.ts | 5 +- .../bridge/handlers/avalanche_bridgeAsset.ts | 13 +++-- .../utils/addXPChainsToFavoriteIfNeeded.ts | 4 +- src/background/utils/findToken.ts | 3 +- src/components/common/CustomFees.tsx | 5 +- src/contexts/BalancesProvider.tsx | 53 ++++++++++++++----- src/hooks/useLiveBalance.ts | 9 ++-- src/pages/Bridge/Bridge.tsx | 5 +- src/pages/Collectibles/Collectibles.tsx | 6 ++- .../components/Portfolio/WalletBalances.tsx | 5 +- yarn.lock | 38 ++++++------- 22 files changed, 231 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index 050b26b98..a6bad1862 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "sentry": "node sentryscript.js" }, "dependencies": { - "@avalabs/avalanche-module": "0.8.0", + "@avalabs/avalanche-module": "0.10.0", "@avalabs/avalanchejs": "4.0.5", - "@avalabs/bitcoin-module": "0.8.0", + "@avalabs/bitcoin-module": "0.10.0", "@avalabs/bridge-unified": "2.1.0", "@avalabs/core-bridge-sdk": "3.1.0-alpha.7", "@avalabs/core-chains-sdk": "3.1.0-alpha.7", @@ -37,11 +37,11 @@ "@avalabs/core-token-prices-sdk": "3.1.0-alpha.7", "@avalabs/core-utils-sdk": "3.1.0-alpha.7", "@avalabs/core-wallets-sdk": "3.1.0-alpha.7", - "@avalabs/evm-module": "0.8.0", + "@avalabs/evm-module": "0.10.0", "@avalabs/glacier-sdk": "3.1.0-alpha.7", "@avalabs/hw-app-avalanche": "0.14.1", "@avalabs/types": "3.1.0-alpha.3", - "@avalabs/vm-module-types": "0.8.0", + "@avalabs/vm-module-types": "0.10.0", "@blockaid/client": "0.10.0", "@coinbase/cbpay-js": "1.6.0", "@cubist-labs/cubesigner-sdk": "0.3.28", diff --git a/src/background/services/balances/BalanceAggregatorService.test.ts b/src/background/services/balances/BalanceAggregatorService.test.ts index 62faa9ae5..957c4ddae 100644 --- a/src/background/services/balances/BalanceAggregatorService.test.ts +++ b/src/background/services/balances/BalanceAggregatorService.test.ts @@ -204,11 +204,16 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { }); it('calls BalancesService.getBalancesForNetwork() with proper params', async () => { - await service.getBalancesForNetworks([network1.chainId], [account1]); + await service.getBalancesForNetworks( + [network1.chainId], + [account1], + [TokenType.NATIVE] + ); expect(balancesServiceMock.getBalancesForNetwork).toBeCalledTimes(1); expect(balancesServiceMock.getBalancesForNetwork).toBeCalledWith( network1, [account1], + [TokenType.NATIVE], undefined ); }); @@ -216,7 +221,8 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { it('can fetch the balance for multiple networks and one account', async () => { const balances = await service.getBalancesForNetworks( [network1.chainId, network2.chainId], - [account1] + [account1], + [TokenType.NATIVE, TokenType.ERC20, TokenType.ERC721] ); expect(balancesServiceMock.getBalancesForNetwork).toBeCalledTimes(2); const expected = { @@ -245,7 +251,8 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { it('fetches the balances for multiple networks and accounts', async () => { const result = await service.getBalancesForNetworks( [network1.chainId, network2.chainId], - [account1, account2] + [account1, account2], + [TokenType.NATIVE, TokenType.ERC20, TokenType.ERC721] ); const expected = { @@ -286,7 +293,8 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { const updatePromise = service.getBalancesForNetworks( [network1.chainId], - [account1] + [account1], + [TokenType.NATIVE, TokenType.ERC20, TokenType.ERC721] ); // eslint-disable-next-line @@ -307,7 +315,8 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { await service.getBalancesForNetworks( [network1.chainId, network2.chainId], - [account1] + [account1], + [TokenType.NATIVE, TokenType.ERC20, TokenType.ERC721] ); expect(balancesServiceMock.getBalancesForNetwork).toBeCalledTimes(2); @@ -395,7 +404,7 @@ describe('src/background/services/balances/BalanceAggregatorService.ts', () => { balances: cachedBalances, }); - await service.getBalancesForNetworks([network2.chainId], [account1]); + await service.getBalancesForNetworks([network2.chainId], [account1], []); await service.onUnlock(); diff --git a/src/background/services/balances/BalanceAggregatorService.ts b/src/background/services/balances/BalanceAggregatorService.ts index 3a09a54db..a75e3bd54 100644 --- a/src/background/services/balances/BalanceAggregatorService.ts +++ b/src/background/services/balances/BalanceAggregatorService.ts @@ -21,7 +21,7 @@ import { import { resolve } from '@avalabs/core-utils-sdk'; import { SettingsService } from '../settings/SettingsService'; import { isFulfilled } from '@src/utils/typeUtils'; -import { NftTokenWithBalance } from '@avalabs/vm-module-types'; +import { NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types'; import { groupTokensByType } from './utils/groupTokensByType'; import { BalancesInfo } from './events/balancesUpdatedEvent'; @@ -54,7 +54,8 @@ export class BalanceAggregatorService implements OnLock, OnUnlock { async getBalancesForNetworks( chainIds: number[], - accounts: Account[] + accounts: Account[], + tokenTypes: TokenType[] ): Promise<{ tokens: Balances; nfts: Balances }> { const sentryTracker = Sentry.startTransaction({ name: 'BalanceAggregatorService: getBatchedUpdatedBalancesForNetworks', @@ -72,6 +73,7 @@ export class BalanceAggregatorService implements OnLock, OnUnlock { await this.balancesService.getBalancesForNetwork( network, accounts, + tokenTypes, priceChangesData ); @@ -112,10 +114,15 @@ export class BalanceAggregatorService implements OnLock, OnUnlock { const aggregatedBalances = merge({}, this.balances, freshBalances.tokens); // NFTs don't have balance = 0, if they are sent they should be removed // from the list, hence deep merge doesn't work - const aggregatedNfts = { - ...this.nfts, - ...freshBalances.nfts, - }; + const hasFetchedNfts = + tokenTypes.includes(TokenType.ERC721) || + tokenTypes.includes(TokenType.ERC1155); + const aggregatedNfts = hasFetchedNfts + ? { + ...this.nfts, + ...freshBalances.nfts, + } + : this.nfts; const hasChanges = networksWithChanges.length > 0; if (hasChanges && !this.lockService.locked) { diff --git a/src/background/services/balances/BalancePollingService.test.ts b/src/background/services/balances/BalancePollingService.test.ts index 7758f378c..eb376a95b 100644 --- a/src/background/services/balances/BalancePollingService.test.ts +++ b/src/background/services/balances/BalancePollingService.test.ts @@ -1,3 +1,4 @@ +import { TokenType } from '@avalabs/vm-module-types'; import { BalanceAggregatorService } from './BalanceAggregatorService'; import { BalancePollingService } from './BalancePollingService'; @@ -18,6 +19,7 @@ describe('src/background/services/balances/BalancePollingService.ts', () => { const account = {} as any; const activeNetworkId = 1; const roundRobinChainIds = [2, 3, 4]; + const tokenTypes = [TokenType.NATIVE, TokenType.ERC20]; const getFetchedNetworksForCall = (mock, callIndex) => { return mock.calls[callIndex][0]; @@ -37,13 +39,18 @@ describe('src/background/services/balances/BalancePollingService.ts', () => { describe('when polling is active', () => { beforeEach(async () => { const service = new BalancePollingService(aggregatorServiceMock); - await service.startPolling(account, activeNetworkId, roundRobinChainIds); + await service.startPolling( + account, + activeNetworkId, + roundRobinChainIds, + tokenTypes + ); }); it('polls for active and favorite networks on the first run', () => { expect( aggregatorServiceMock.getBalancesForNetworks - ).toHaveBeenLastCalledWith([1, 2, 3, 4], expect.anything()); + ).toHaveBeenLastCalledWith([1, 2, 3, 4], expect.anything(), tokenTypes); }); it('polls for active network on every run', () => { diff --git a/src/background/services/balances/BalancePollingService.ts b/src/background/services/balances/BalancePollingService.ts index 358c4667b..97458239d 100644 --- a/src/background/services/balances/BalancePollingService.ts +++ b/src/background/services/balances/BalancePollingService.ts @@ -6,6 +6,7 @@ import { import { singleton } from 'tsyringe'; import { Account } from '../accounts/models'; import { BalanceAggregatorService } from './BalanceAggregatorService'; +import { TokenType } from '@avalabs/vm-module-types'; @singleton() export class BalancePollingService implements OnLock, OnAllExtensionClosed { @@ -28,13 +29,19 @@ export class BalancePollingService implements OnLock, OnAllExtensionClosed { async startPolling( account: Account, activeChainId: number, - roundRobinChainIds: number[] + roundRobinChainIds: number[], + tokenTypes: TokenType[] ) { // Stop any polling that may be occurring already this.stopPolling(); // Start a new interval // this._preventSchedulingNextUpdate = false; - return this.pollBalances(account, activeChainId, roundRobinChainIds); + return this.pollBalances( + account, + activeChainId, + roundRobinChainIds, + tokenTypes + ); } onAllExtensionsClosed() { @@ -52,7 +59,8 @@ export class BalancePollingService implements OnLock, OnAllExtensionClosed { private async pollBalances( account: Account, activeChainId: number, - roundRobinChainIds: number[] + roundRobinChainIds: number[], + tokenTypes: TokenType[] ) { const thisPollingStartedAt = performance.now(); this.#lastPollingStartedAt = thisPollingStartedAt; @@ -67,7 +75,11 @@ export class BalancePollingService implements OnLock, OnAllExtensionClosed { ]); try { - await this.balanceAggregator.getBalancesForNetworks(chainIds, [account]); + await this.balanceAggregator.getBalancesForNetworks( + chainIds, + [account], + tokenTypes + ); } catch { // Do nothing, just schedule another attempt } @@ -75,18 +87,30 @@ export class BalancePollingService implements OnLock, OnAllExtensionClosed { // Only schedule the next update if another polling was not started // while we were waiting for balance results. if (thisPollingStartedAt === this.#lastPollingStartedAt) { - this.scheduleNextUpdate(account, activeChainId, roundRobinChainIds); + this.scheduleNextUpdate( + account, + activeChainId, + roundRobinChainIds, + tokenTypes + ); } } private scheduleNextUpdate( account: Account, activeChainId: number, - roundRobinChainIds: number[] + roundRobinChainIds: number[], + tokenTypes: TokenType[] ) { this.#pollingIteration += 1; this.#timer = setTimeout( - () => this.pollBalances(account, activeChainId, roundRobinChainIds), + () => + this.pollBalances( + account, + activeChainId, + roundRobinChainIds, + tokenTypes + ), BalancePollingService.INTERVAL ); } diff --git a/src/background/services/balances/BalancesService.test.ts b/src/background/services/balances/BalancesService.test.ts index 3545b4927..a0c1dc2b6 100644 --- a/src/background/services/balances/BalancesService.test.ts +++ b/src/background/services/balances/BalancesService.test.ts @@ -6,6 +6,7 @@ import { ModuleManager } from '@src/background/vmModules/ModuleManager'; import { NetworkVMType } from '@avalabs/core-chains-sdk'; import * as Sentry from '@sentry/browser'; import { SettingsService } from '../settings/SettingsService'; +import LRUCache from 'lru-cache'; jest.mock('@src/background/vmModules/ModuleManager'); jest.mock('@sentry/browser', () => { @@ -205,9 +206,11 @@ describe('src/background/services/balances/BalancesService.ts', () => { it('should get balances for P-Chain', async () => { moduleMock.getBalances.mockResolvedValue(pvmResult); const network = { vmName: NetworkVMType.PVM, caipId: 'pvm' }; - const result = await service.getBalancesForNetwork(network as any, [ - account, - ]); + const result = await service.getBalancesForNetwork( + network as any, + [account], + [] + ); expect(result).toEqual({ ['networkId2']: { @@ -226,13 +229,19 @@ describe('src/background/services/balances/BalancesService.ts', () => { currency: 'eur', network, customTokens: [], + tokenTypes: [], + storage: expect.any(LRUCache), }); }); it('should get balances for X-Chain', async () => { moduleMock.getBalances.mockResolvedValue(avmResult); const network = { vmName: NetworkVMType.AVM, caipId: 'avm' } as any; - const result = await service.getBalancesForNetwork(network, [account]); + const result = await service.getBalancesForNetwork( + network, + [account], + [] + ); expect(result).toEqual({ ['networkId2']: { @@ -251,6 +260,8 @@ describe('src/background/services/balances/BalancesService.ts', () => { currency: 'eur', network, customTokens: [], + tokenTypes: [], + storage: expect.any(LRUCache), }); }); @@ -279,7 +290,11 @@ describe('src/background/services/balances/BalancesService.ts', () => { caipId: 'eip155:1337', chainId: 1337, } as any; - const result = await service.getBalancesForNetwork(network, [account]); + const result = await service.getBalancesForNetwork( + network, + [account], + [TokenType.NATIVE, TokenType.ERC20] + ); expect(result).toEqual({ ['networkId2']: { @@ -299,13 +314,19 @@ describe('src/background/services/balances/BalancesService.ts', () => { currency: 'usd', network, customTokens: [{ ...customToken, type: TokenType.ERC20 }], + tokenTypes: [TokenType.NATIVE, TokenType.ERC20], + storage: expect.any(LRUCache), }); }); it('should get balances for Bitcoin', async () => { moduleMock.getBalances.mockResolvedValue(btcResult); const network = { vmName: NetworkVMType.BITCOIN, caipId: 'btc' } as any; - const result = await service.getBalancesForNetwork(network, [account]); + const result = await service.getBalancesForNetwork( + network, + [account], + [] + ); expect(result).toEqual({ ['networkId3']: { @@ -325,15 +346,22 @@ describe('src/background/services/balances/BalancesService.ts', () => { currency: 'eur', network, customTokens: [], + tokenTypes: [], + storage: expect.any(LRUCache), }); }); it('should calculate price changes if provided', async () => { moduleMock.getBalances.mockResolvedValue(evmResult); const network = { vmName: NetworkVMType.EVM, caipId: 'evm' } as any; - const result = await service.getBalancesForNetwork(network, [account], { - test2: { priceChangePercentage: 25 }, - }); + const result = await service.getBalancesForNetwork( + network, + [account], + [], + { + test2: { priceChangePercentage: 25 }, + } + ); expect(result).toEqual({ ['networkId2']: { @@ -353,6 +381,8 @@ describe('src/background/services/balances/BalancesService.ts', () => { currency: 'eur', network, customTokens: [], + tokenTypes: [], + storage: expect.any(LRUCache), }); }); }); diff --git a/src/background/services/balances/BalancesService.ts b/src/background/services/balances/BalancesService.ts index f616a71dc..361676455 100644 --- a/src/background/services/balances/BalancesService.ts +++ b/src/background/services/balances/BalancesService.ts @@ -11,6 +11,9 @@ import { TokenType, TokenWithBalance, } from '@avalabs/vm-module-types'; +import LRUCache from 'lru-cache'; + +const cacheStorage = new LRUCache({ max: 100, ttl: 60 * 1000 }); @singleton() export class BalancesService { @@ -22,6 +25,7 @@ export class BalancesService { async getBalancesForNetwork( network: NetworkWithCaipId, accounts: Account[], + tokenTypes: TokenType[], priceChanges?: TokensPriceShortData ): Promise>> { const sentryTracker = Sentry.startTransaction( @@ -63,6 +67,8 @@ export class BalancesService { network, currency, customTokens, + tokenTypes, + storage: cacheStorage, }); // Apply price changes data, VM Modules don't do this yet diff --git a/src/background/services/balances/handlers/getAvaxBalance.ts b/src/background/services/balances/handlers/getAvaxBalance.ts index e6608255f..e4b73b7d7 100644 --- a/src/background/services/balances/handlers/getAvaxBalance.ts +++ b/src/background/services/balances/handlers/getAvaxBalance.ts @@ -50,7 +50,8 @@ export class GetAvaxBalanceHandler implements HandlerType { name: '', addressBTC: '', }, - ] + ], + [TokenType.NATIVE] ); const nativeTokenWithBalance = diff --git a/src/background/services/balances/handlers/startBalancesPolling.test.ts b/src/background/services/balances/handlers/startBalancesPolling.test.ts index 18cfc80e1..49455e38d 100644 --- a/src/background/services/balances/handlers/startBalancesPolling.test.ts +++ b/src/background/services/balances/handlers/startBalancesPolling.test.ts @@ -4,6 +4,7 @@ import { BalancePollingService } from '../BalancePollingService'; import { StartBalancesPollingHandler } from './startBalancesPolling'; import { buildRpcCall } from '@src/tests/test-utils'; import { caipToChainId } from '@src/utils/caipConversion'; +import { TokenType } from '@avalabs/vm-module-types'; jest.mock('@src/utils/caipConversion'); @@ -64,7 +65,7 @@ describe('background/services/balances/handlers/startBalancesPolling.ts', () => { id: '123', method: ExtensionRequest.BALANCES_START_POLLING, - params: [account, chainIds], + params: [account, chainIds, [TokenType.ERC20]], }, scope ) @@ -73,7 +74,8 @@ describe('background/services/balances/handlers/startBalancesPolling.ts', () => expect(balancePollingService.startPolling).toHaveBeenCalledWith( account, caipToChainId(scope), - chainIds + chainIds, + [TokenType.ERC20] ); }); }); diff --git a/src/background/services/balances/handlers/startBalancesPolling.ts b/src/background/services/balances/handlers/startBalancesPolling.ts index 98a70e98b..15ef3fcb1 100644 --- a/src/background/services/balances/handlers/startBalancesPolling.ts +++ b/src/background/services/balances/handlers/startBalancesPolling.ts @@ -6,7 +6,7 @@ import { BalancePollingService } from '../BalancePollingService'; import { Balances } from '../models'; import { Account } from '../../accounts/models'; import { caipToChainId } from '@src/utils/caipConversion'; -import { NftTokenWithBalance } from '@avalabs/vm-module-types'; +import { NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.BALANCES_START_POLLING, @@ -17,7 +17,7 @@ type HandlerType = ExtensionRequestHandler< }; isBalancesCached: boolean; }, - [account: Account, roundRobinChainIds: number[]] + [account: Account, roundRobinChainIds: number[], tokenTypes: TokenType[]] >; @injectable() @@ -32,12 +32,13 @@ export class StartBalancesPollingHandler implements HandlerType { handle: HandlerType['handle'] = async ({ request, scope }) => { if (scope && !this.pollingService.isPollingActive) { const activeChainId = caipToChainId(scope); - const [account, roundRobinChainIds] = request.params; + const [account, roundRobinChainIds, tokenTypes] = request.params; this.pollingService.startPolling( account, activeChainId, - roundRobinChainIds + roundRobinChainIds, + tokenTypes ); } diff --git a/src/background/services/balances/handlers/updateBalancesForNetwork.ts b/src/background/services/balances/handlers/updateBalancesForNetwork.ts index 48093631d..2e380de9e 100644 --- a/src/background/services/balances/handlers/updateBalancesForNetwork.ts +++ b/src/background/services/balances/handlers/updateBalancesForNetwork.ts @@ -7,7 +7,7 @@ import { NetworkService } from '../../network/NetworkService'; import { BalanceAggregatorService } from '../BalanceAggregatorService'; import { Balances } from '../models'; import { caipToChainId } from '@src/utils/caipConversion'; -import { NftTokenWithBalance } from '@avalabs/vm-module-types'; +import { NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.NETWORK_BALANCES_UPDATE, @@ -62,7 +62,8 @@ export class UpdateBalancesForNetworkHandler implements HandlerType { try { const balances = await this.networkBalancesService.getBalancesForNetworks( networksToFetch, - accountsToFetch + accountsToFetch, + [TokenType.NATIVE, TokenType.ERC20] ); return { ...request, diff --git a/src/background/services/bridge/BridgeService.ts b/src/background/services/bridge/BridgeService.ts index f31578ebf..09a1a9f49 100644 --- a/src/background/services/bridge/BridgeService.ts +++ b/src/background/services/bridge/BridgeService.ts @@ -33,7 +33,7 @@ import { import Big from 'big.js'; import { BalanceAggregatorService } from '../balances/BalanceAggregatorService'; import { FeatureGates } from '../featureFlags/models'; -import { TokenWithBalanceBTC } from '@avalabs/vm-module-types'; +import { TokenType, TokenWithBalanceBTC } from '@avalabs/vm-module-types'; @singleton() export class BridgeService implements OnLock, OnStorageReady { @@ -194,7 +194,8 @@ export class BridgeService implements OnLock, OnStorageReady { const balances = await this.networkBalancesService.getBalancesForNetworks( [btcNetwork.chainId], - [this.accountsService.activeAccount] + [this.accountsService.activeAccount], + [TokenType.NATIVE] // We only care about BTC here, which is a native token ); const token = balances.tokens[btcNetwork.chainId]?.[addressBtc]?.[ diff --git a/src/background/services/bridge/handlers/avalanche_bridgeAsset.ts b/src/background/services/bridge/handlers/avalanche_bridgeAsset.ts index 1db0c7d11..74a89819e 100644 --- a/src/background/services/bridge/handlers/avalanche_bridgeAsset.ts +++ b/src/background/services/bridge/handlers/avalanche_bridgeAsset.ts @@ -3,6 +3,7 @@ import { AccountsService } from '@src/background/services/accounts/AccountsServi import { AppConfig, Asset, + AssetType, Assets, BitcoinConfigAsset, Blockchain, @@ -41,7 +42,7 @@ import { validateBtcSend, } from '@src/utils/send/btcSendUtils'; import { resolve } from '@src/utils/promiseResolver'; -import { TokenWithBalanceBTC } from '@avalabs/vm-module-types'; +import { TokenType, TokenWithBalanceBTC } from '@avalabs/vm-module-types'; import { getProviderForNetwork } from '@src/utils/network/getProviderForNetwork'; import { JsonRpcBatchInternal } from '@avalabs/core-wallets-sdk'; @@ -166,7 +167,12 @@ export class AvalancheBridgeAsset extends DAppRequestHandler const { tokens } = await this.balanceAggregatorService.getBalancesForNetworks( [sourceNetwork.chainId], - [activeAccount] + [activeAccount], + [ + asset.assetType === AssetType.ERC20 + ? TokenType.ERC20 + : TokenType.NATIVE, + ] ); const balanceAddress = @@ -302,7 +308,8 @@ export class AvalancheBridgeAsset extends DAppRequestHandler const balances = await this.balanceAggregatorService.getBalancesForNetworks( [network.chainId], - [account] + [account], + [TokenType.NATIVE] // We only care about BTC here, which is a native token ); const highFeeRate = Number( diff --git a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts index c814bf2ab..e550c8d0d 100644 --- a/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts +++ b/src/background/services/onboarding/utils/addXPChainsToFavoriteIfNeeded.ts @@ -8,6 +8,7 @@ import { isString } from 'lodash'; import { container } from 'tsyringe'; import { HistoryService } from '../../history/HistoryService'; import { + TokenType, TokenWithBalanceAVM, TokenWithBalancePVM, } from '@avalabs/vm-module-types'; @@ -20,7 +21,8 @@ export const addXPChainToFavoriteIfNeeded = async ( const historyService = container.resolve(HistoryService); const balances = await balanceService.getBalancesForNetworks( [ChainId.AVALANCHE_P, ChainId.AVALANCHE_X], - accounts + accounts, + [TokenType.NATIVE] ); if (hasBalance(balances.tokens, accounts, ChainId.AVALANCHE_P)) { diff --git a/src/background/utils/findToken.ts b/src/background/utils/findToken.ts index 6cfc64d0f..fea5a07d4 100644 --- a/src/background/utils/findToken.ts +++ b/src/background/utils/findToken.ts @@ -44,7 +44,8 @@ export async function findToken( balances = ( await balancesService.getBalancesForNetworks( [network.chainId], - [accountsService.activeAccount] + [accountsService.activeAccount], + [TokenType.NATIVE, TokenType.ERC20] ) ).tokens; } diff --git a/src/components/common/CustomFees.tsx b/src/components/common/CustomFees.tsx index ac511bf72..0e723b8fe 100644 --- a/src/components/common/CustomFees.tsx +++ b/src/components/common/CustomFees.tsx @@ -5,6 +5,7 @@ import { useNativeTokenPrice } from '@src/hooks/useTokenPrice'; import { Network, NetworkVMType } from '@avalabs/core-chains-sdk'; import { formatUnits, parseUnits } from 'ethers'; import { Trans, useTranslation } from 'react-i18next'; +import { TokenType } from '@avalabs/vm-module-types'; import { FeeRate, NetworkFee, @@ -146,6 +147,8 @@ export const getGasFeeToDisplay = (fee: string, networkFee: NetworkFee) => { } }; +const POLLED_BALANCES = [TokenType.NATIVE]; + export function CustomFees({ maxFeePerGas, limit, @@ -185,7 +188,7 @@ export function CustomFees({ : selectedGasFeeModifier || GasFeeModifier.NORMAL ); - useLiveBalance(); // Make sure we always use the latest balances. + useLiveBalance(POLLED_BALANCES); // Make sure we always use the latest native balance. useEffect(() => { if (!customFee && networkFee) { diff --git a/src/contexts/BalancesProvider.tsx b/src/contexts/BalancesProvider.tsx index 67583ee95..4afdc11db 100644 --- a/src/contexts/BalancesProvider.tsx +++ b/src/contexts/BalancesProvider.tsx @@ -30,7 +30,7 @@ import { ipfsResolverWithFallback } from '@src/utils/ipsfResolverWithFallback'; import { getSmallImageForNFT } from '@src/background/services/balances/nft/utils/getSmallImageForNFT'; import { TokensPriceShortData } from '@src/background/services/tokens/models'; import { calculateTotalBalance } from '@src/utils/calculateTotalBalance'; -import { NftTokenWithBalance } from '@avalabs/vm-module-types'; +import { NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types'; import { Network } from '@src/background/services/network/models'; import { getAddressForChain } from '@src/utils/getAddressForChain'; @@ -50,6 +50,8 @@ export interface TokensPriceData { tokensData: TokensPriceShortData; } +type BalanceSubscribers = Partial>; + enum BalanceActionType { UPDATE_BALANCES = 'UPDATE_BALANCES', SET_LOADING = 'SET_LOADING', @@ -86,8 +88,8 @@ const BalancesContext = createContext<{ ): Promise; getTokenPrice(addressOrSymbol: string): number | undefined; updateBalanceOnAllNetworks: (accounts: Account[]) => Promise; - registerSubscriber: () => void; - unregisterSubscriber: () => void; + registerSubscriber: (tokenTypes: TokenType[]) => void; + unregisterSubscriber: (tokenTypes: TokenType[]) => void; isTokensCached: boolean; totalBalance?: { sum: number | null; priceChange: TotalPriceChange }; getTotalBalance: (addressC: string) => @@ -161,7 +163,7 @@ export function BalancesProvider({ children }: { children: any }) { cached: true, }); - const [subscriberCount, setSubscriberCount] = useState(0); + const [subscribers, setSubscribers] = useState({}); const [isPolling, setIsPolling] = useState(false); const polledChainIds = useMemo( @@ -169,12 +171,28 @@ export function BalancesProvider({ children }: { children: any }) { [favoriteNetworks] ); - const registerSubscriber = useCallback(() => { - setSubscriberCount((count) => count + 1); + const registerSubscriber = useCallback((tokenTypes: TokenType[]) => { + setSubscribers((oldSubscribers) => + tokenTypes.reduce( + (newSubscribers, tokenType) => ({ + ...newSubscribers, + [tokenType]: (newSubscribers[tokenType] ?? 0) + 1, + }), + oldSubscribers + ) + ); }, []); - const unregisterSubscriber = useCallback(() => { - setSubscriberCount((count) => count - 1); + const unregisterSubscriber = useCallback((tokenTypes: TokenType[]) => { + setSubscribers((oldSubscribers) => + tokenTypes.reduce( + (newSubscribers, tokenType) => ({ + ...newSubscribers, + [tokenType]: Math.max((newSubscribers[tokenType] ?? 0) - 1, 0), + }), + oldSubscribers + ) + ); }, []); useEffect(() => { @@ -216,9 +234,13 @@ export function BalancesProvider({ children }: { children: any }) { } if (isPolling) { + const tokenTypes = Object.entries(subscribers) + .filter(([, subscriberCount]) => subscriberCount > 0) + .map(([tokenType]) => tokenType as TokenType); + request({ method: ExtensionRequest.BALANCES_START_POLLING, - params: [activeAccount, polledChainIds], + params: [activeAccount, polledChainIds, tokenTypes], }).then((balancesData) => { dispatch({ type: BalanceActionType.UPDATE_BALANCES, @@ -232,12 +254,19 @@ export function BalancesProvider({ children }: { children: any }) { method: ExtensionRequest.BALANCES_STOP_POLLING, }); }; - }, [request, isPolling, activeAccount, network?.chainId, polledChainIds]); + }, [ + request, + isPolling, + activeAccount, + network?.chainId, + polledChainIds, + subscribers, + ]); useEffect(() => { // Toggle balance polling based on the amount of dependent components. - setIsPolling(subscriberCount > 0); - }, [subscriberCount]); + setIsPolling(Object.values(subscribers).some((count) => count > 0)); + }, [subscribers]); const updateBalanceOnAllNetworks = useCallback( async (accounts: Account[]) => { diff --git a/src/hooks/useLiveBalance.ts b/src/hooks/useLiveBalance.ts index af83db989..47de8b72f 100644 --- a/src/hooks/useLiveBalance.ts +++ b/src/hooks/useLiveBalance.ts @@ -1,14 +1,15 @@ import { useEffect } from 'react'; import { useBalancesContext } from '@src/contexts/BalancesProvider'; +import { TokenType } from '@avalabs/vm-module-types'; -export const useLiveBalance = () => { +export const useLiveBalance = (tokenTypes: TokenType[]) => { const { registerSubscriber, unregisterSubscriber } = useBalancesContext(); useEffect(() => { - registerSubscriber(); + registerSubscriber(tokenTypes); return () => { - unregisterSubscriber(); + unregisterSubscriber(tokenTypes); }; - }, [registerSubscriber, unregisterSubscriber]); + }, [registerSubscriber, unregisterSubscriber, tokenTypes]); }; diff --git a/src/pages/Bridge/Bridge.tsx b/src/pages/Bridge/Bridge.tsx index 06f16e2b2..6371aa2f7 100644 --- a/src/pages/Bridge/Bridge.tsx +++ b/src/pages/Bridge/Bridge.tsx @@ -30,6 +30,7 @@ import { Typography, toast, } from '@avalabs/core-k2-components'; +import { TokenType } from '@avalabs/vm-module-types'; import { FunctionNames, useIsFunctionAvailable, @@ -45,8 +46,10 @@ import { useUnifiedBridgeContext } from '@src/contexts/UnifiedBridgeProvider'; import { BridgeUnknownNetwork } from './components/BridgeUnknownNetwork'; import { useLiveBalance } from '@src/hooks/useLiveBalance'; +const POLLED_BALANCES = [TokenType.NATIVE, TokenType.ERC20]; + export function Bridge() { - useLiveBalance(); // Make sure we always use the latest balances. + useLiveBalance(POLLED_BALANCES); // Make sure we always use fresh balances of bridgable tokens. useSyncBridgeConfig(); // keep bridge config up-to-date useSetBridgeChainFromNetwork(); diff --git a/src/pages/Collectibles/Collectibles.tsx b/src/pages/Collectibles/Collectibles.tsx index 745f1cad0..633ccfd76 100644 --- a/src/pages/Collectibles/Collectibles.tsx +++ b/src/pages/Collectibles/Collectibles.tsx @@ -26,16 +26,20 @@ import { InfiniteScroll } from '@src/components/common/infiniteScroll/InfiniteSc import { useAnalyticsContext } from '@src/contexts/AnalyticsProvider'; import { useNetworkContext } from '@src/contexts/NetworkProvider'; import { ListType } from '../Home/components/Portfolio/Portfolio'; -import { NftTokenWithBalance } from '@avalabs/vm-module-types'; +import { NftTokenWithBalance, TokenType } from '@avalabs/vm-module-types'; import { useNfts } from '@src/hooks/useNfts'; import { useBalancesContext } from '@src/contexts/BalancesProvider'; +import { useLiveBalance } from '@src/hooks/useLiveBalance'; interface CollectiblesProps { listType: ListType; setListType: Dispatch>; } +const POLLED_BALANCES = [TokenType.ERC721, TokenType.ERC1155]; + export function Collectibles({ listType, setListType }: CollectiblesProps) { + useLiveBalance(POLLED_BALANCES); const { t } = useTranslation(); const { balances } = useBalancesContext(); const nfts = useNfts(); diff --git a/src/pages/Home/components/Portfolio/WalletBalances.tsx b/src/pages/Home/components/Portfolio/WalletBalances.tsx index 68715b003..cbeede3a1 100644 --- a/src/pages/Home/components/Portfolio/WalletBalances.tsx +++ b/src/pages/Home/components/Portfolio/WalletBalances.tsx @@ -5,6 +5,7 @@ import { Typography, Skeleton, } from '@avalabs/core-k2-components'; +import { TokenType } from '@avalabs/vm-module-types'; import { PAndL } from '@src/components/common/ProfitAndLoss'; import { useAccountsContext } from '@src/contexts/AccountsProvider'; import { useBalancesContext } from '@src/contexts/BalancesProvider'; @@ -14,6 +15,8 @@ import { useLiveBalance } from '@src/hooks/useLiveBalance'; import { useTokenPriceMissing } from '@src/hooks/useTokenPriceIsMissing'; import { useTranslation } from 'react-i18next'; +const POLLED_BALANCES = [TokenType.NATIVE, TokenType.ERC20]; + export function WalletBalances() { const { currency, currencyFormatter } = useSettingsContext(); const { @@ -47,7 +50,7 @@ export function WalletBalances() { ? balanceTotalUSDSum : totalBalanceSum ?? null; - useLiveBalance(); // Make sure we show the latest balances. + useLiveBalance(POLLED_BALANCES); // Make sure we show the latest balances. return (