From 87a2daf85fa5c0dcdb66acafa2ddec61ecde2625 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:39:11 +0200 Subject: [PATCH 01/22] feat: rm request queue --- .../src/cosmossdk/CosmosSdkBaseAdapter.ts | 18 ++++++---------- .../thorchain/ThorchainChainAdapter.ts | 20 +++++++----------- .../chain-adapters/src/evm/EvmBaseAdapter.ts | 19 ++++++----------- packages/chain-adapters/src/types.ts | 2 -- .../src/utxo/UtxoBaseAdapter.ts | 21 +++++++------------ .../slices/portfolioSlice/portfolioSlice.ts | 1 - .../slices/txHistorySlice/txHistorySlice.ts | 5 +---- 7 files changed, 27 insertions(+), 59 deletions(-) diff --git a/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts b/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts index 6c9ae25acf5..3ccc056bc4d 100644 --- a/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts @@ -4,7 +4,6 @@ import type { BIP44Params, CosmosSdkChainId } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import * as unchained from '@shapeshiftoss/unchained-client' import { bech32 } from 'bech32' -import PQueue from 'p-queue' import type { ChainAdapter as IChainAdapter } from '../api' import { ErrorHandler } from '../error/ErrorHandler' @@ -229,19 +228,14 @@ export abstract class CosmosSdkBaseAdapter implement } async getTxHistory(input: TxHistoryInput): Promise { - const requestQueue = input.requestQueue ?? new PQueue() try { - const data = await requestQueue.add(() => - this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }), - ) + const data = await this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }) - const txs = await Promise.all( - data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), - ) + const txs = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) return { cursor: data.cursor, diff --git a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts index 20de32274ad..31d2cd79635 100644 --- a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts @@ -6,7 +6,6 @@ import type { BIP44Params } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import * as unchained from '@shapeshiftoss/unchained-client' import { bech32 } from 'bech32' -import PQueue from 'p-queue' import { ErrorHandler } from '../../error/ErrorHandler' import type { @@ -145,19 +144,14 @@ export class ChainAdapter extends CosmosSdkBaseAdapter { - const requestQueue = input.requestQueue ?? new PQueue() try { - const data = await requestQueue.add(() => - this.httpV1.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }), - ) - - const txs = await Promise.all( - data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), - ) + const data = await this.httpV1.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }) + + const txs = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) return { cursor: data.cursor, diff --git a/packages/chain-adapters/src/evm/EvmBaseAdapter.ts b/packages/chain-adapters/src/evm/EvmBaseAdapter.ts index e4d031ca04f..52e33916585 100644 --- a/packages/chain-adapters/src/evm/EvmBaseAdapter.ts +++ b/packages/chain-adapters/src/evm/EvmBaseAdapter.ts @@ -22,7 +22,6 @@ import type { BIP44Params, EvmChainId } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' import BigNumber from 'bignumber.js' -import PQueue from 'p-queue' import { isAddress, toHex } from 'viem' import type { ChainAdapter as IChainAdapter } from '../api' @@ -385,19 +384,13 @@ export abstract class EvmBaseAdapter implements IChainAdap } async getTxHistory(input: TxHistoryInput): Promise { - const requestQueue = input.requestQueue ?? new PQueue() - - const data = await requestQueue.add(() => - this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }), - ) + const data = await this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }) - const transactions = await Promise.all( - data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), - ) + const transactions = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) return { cursor: data.cursor ?? '', diff --git a/packages/chain-adapters/src/types.ts b/packages/chain-adapters/src/types.ts index 29cc7a7e4fe..ee97cd378f7 100644 --- a/packages/chain-adapters/src/types.ts +++ b/packages/chain-adapters/src/types.ts @@ -13,7 +13,6 @@ import type { UtxoChainId, } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' -import type PQueue from 'p-queue' import type * as cosmossdk from './cosmossdk/types' import type * as evm from './evm/types' @@ -251,7 +250,6 @@ export interface TxHistoryInput { readonly cursor?: string readonly pubkey: string readonly pageSize?: number - readonly requestQueue?: PQueue } export type GetAddressInputBase = { diff --git a/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts b/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts index 44e65a25e67..9ab164c85c2 100644 --- a/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts +++ b/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts @@ -14,7 +14,6 @@ import type { BIP44Params, UtxoChainId } from '@shapeshiftoss/types' import { KnownChainIds, UtxoAccountType } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' import WAValidator from 'multicoin-address-validator' -import PQueue from 'p-queue' import type { ChainAdapter as IChainAdapter } from '../api' import { ErrorHandler } from '../error/ErrorHandler' @@ -472,23 +471,17 @@ export abstract class UtxoBaseAdapter implements IChainAd } async getTxHistory(input: TxHistoryInput): Promise { - const requestQueue = input.requestQueue ?? new PQueue() - if (!this.accountAddresses[input.pubkey]) { - await requestQueue.add(() => this.getAccount(input.pubkey)) + await this.getAccount(input.pubkey) } - const data = await requestQueue.add(() => - this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }), - ) + const data = await this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }) - const transactions = await Promise.all( - data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), - ) + const transactions = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) return { cursor: data.cursor ?? '', diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index 2ddec6fe6a8..e2332f6bde6 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -180,7 +180,6 @@ export const portfolio = createSlice({ type GetAccountArgs = { accountId: AccountId - upsertOnFetch?: boolean } export const portfolioApi = createApi({ diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 92790ba1162..06889532c70 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -5,7 +5,6 @@ import { fromAccountId, isNft, thorchainChainId } from '@shapeshiftoss/caip' import type { ChainAdapter, thorchain, Transaction } from '@shapeshiftoss/chain-adapters' import type { PartialRecord, UtxoAccountType } from '@shapeshiftoss/types' import orderBy from 'lodash/orderBy' -import PQueue from 'p-queue' import { PURGE } from 'redux-persist' import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton' import { deepUpsertArray } from 'lib/utils' @@ -212,8 +211,6 @@ export const txHistoryApi = createApi({ endpoints: build => ({ getAllTxHistory: build.query({ queryFn: async (accountIds, { dispatch, getState }) => { - const requestQueue = new PQueue({ concurrency: 2 }) - await Promise.all( accountIds.map(async accountId => { const { chainId, account: pubkey } = fromAccountId(accountId) @@ -237,8 +234,8 @@ export const txHistoryApi = createApi({ cursor: requestCursor, pubkey, pageSize, - requestQueue, }) + console.log({ transactions }) const state = getState() as State const txsById = state.txHistory.txs.byId From 44630f96a3f02842a87efde353099619113fa2cc Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:39:30 +0200 Subject: [PATCH 02/22] feat: rm upsertOnFetch --- src/components/Layout/Header/DegradedStateBanner.tsx | 7 +------ .../ManageAccountsDrawer/components/ImportAccounts.tsx | 4 +--- src/context/AppProvider/AppContext.tsx | 5 +---- src/context/TransactionsProvider/TransactionsProvider.tsx | 4 +--- src/pages/Accounts/AddAccountModal.tsx | 2 +- src/state/slices/portfolioSlice/portfolioSlice.ts | 4 ++-- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/components/Layout/Header/DegradedStateBanner.tsx b/src/components/Layout/Header/DegradedStateBanner.tsx index 936c5fceecd..74e237f3ccc 100644 --- a/src/components/Layout/Header/DegradedStateBanner.tsx +++ b/src/components/Layout/Header/DegradedStateBanner.tsx @@ -59,12 +59,7 @@ export const DegradedStateBanner = memo(() => { const handleRetry = useCallback(() => { erroredAccountIds.forEach(accountId => - dispatch( - portfolioApi.endpoints.getAccount.initiate( - { accountId, upsertOnFetch: true }, - { forceRefetch: true }, - ), - ), + dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId }, { forceRefetch: true })), ) }, [dispatch, erroredAccountIds]) diff --git a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx index 10e063e03d3..fb217c6767f 100644 --- a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx +++ b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx @@ -326,9 +326,7 @@ export const ImportAccounts = ({ chainId, onClose }: ImportAccountsProps) => { if (isEnabled) { return } - await dispatch( - portfolioApi.endpoints.getAccount.initiate({ accountId, upsertOnFetch: true }), - ) + await dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId })) }), ) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 5ebc09e7679..cabd5f21cdc 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -145,10 +145,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { if (hasManagedAccounts) { requestedAccountIds.forEach(accountId => { dispatch( - portfolioApi.endpoints.getAccount.initiate( - { accountId, upsertOnFetch: true }, - { forceRefetch: true }, - ), + portfolioApi.endpoints.getAccount.initiate({ accountId }, { forceRefetch: true }), ) }) diff --git a/src/context/TransactionsProvider/TransactionsProvider.tsx b/src/context/TransactionsProvider/TransactionsProvider.tsx index 878ea4aa5d0..44a73b70008 100644 --- a/src/context/TransactionsProvider/TransactionsProvider.tsx +++ b/src/context/TransactionsProvider/TransactionsProvider.tsx @@ -236,9 +236,7 @@ export const TransactionsProvider: React.FC = ({ chil const { onMessage } = txHistory.actions // refetch account on new tx - dispatch( - getAccount.initiate({ accountId, upsertOnFetch: true }, { forceRefetch: true }), - ) + dispatch(getAccount.initiate({ accountId }, { forceRefetch: true })) maybeRefetchVotingPower(msg, chainId) maybeRefetchOpportunities(msg, accountId) diff --git a/src/pages/Accounts/AddAccountModal.tsx b/src/pages/Accounts/AddAccountModal.tsx index 92c8dd0b30e..5d9b6d573a0 100644 --- a/src/pages/Accounts/AddAccountModal.tsx +++ b/src/pages/Accounts/AddAccountModal.tsx @@ -118,7 +118,7 @@ export const AddAccountModal = () => { ) const accountIds = Object.keys(accountMetadataByAccountId) accountIds.forEach(accountId => { - dispatch(getAccount.initiate({ accountId, upsertOnFetch: true }, opts)) + dispatch(getAccount.initiate({ accountId }, opts)) dispatch(portfolio.actions.enableAccountId(accountId)) }) const assetId = getChainAdapterManager().get(selectedChainId)!.getFeeAssetId() diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index e2332f6bde6..ccda2c83f1c 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -187,7 +187,7 @@ export const portfolioApi = createApi({ reducerPath: 'portfolioApi', endpoints: build => ({ getAccount: build.query({ - queryFn: async ({ accountId, upsertOnFetch }, { dispatch, getState }) => { + queryFn: async ({ accountId }, { dispatch, getState }) => { if (!accountId) return { data: cloneDeep(initialState) } const state: ReduxState = getState() as any const assetIds = state.assets.ids @@ -332,7 +332,7 @@ export const portfolioApi = createApi({ return accountToPortfolio({ portfolioAccounts, assetIds, nftCollectionsById }) })() - upsertOnFetch && dispatch(portfolio.actions.upsertPortfolio(data)) + dispatch(portfolio.actions.upsertPortfolio(data)) return { data } } catch (e) { console.error(e) From ce1172bb748a39e0eebbab5fe04ab3b69cb8ea40 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:39:59 +0200 Subject: [PATCH 03/22] feat: don't mark upsertPortfolio as low prio action --- src/state/slices/portfolioSlice/portfolioSlice.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index ccda2c83f1c..4aa660106df 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -130,6 +130,7 @@ export const portfolio = createSlice({ upsertPortfolio: { reducer: (draftState, { payload }: { payload: Portfolio }) => { + console.log('upserting portfolio', { payload }) // upsert all draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) draftState.accounts.ids = Object.keys(draftState.accounts.byId) @@ -140,10 +141,6 @@ export const portfolio = createSlice({ ) draftState.accountBalances.ids = Object.keys(draftState.accountBalances.byId) }, - - // Use the `prepareAutoBatched` utility to automatically - // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs - prepare: prepareAutoBatched(), }, /** * Explicitly enable an account by its `AccountId`. Necessary where `use-strict` toggles twice @@ -209,6 +206,8 @@ export const portfolioApi = createApi({ }) const account = portfolioAccounts[pubkey] as Account + console.log({ account }) + const assets = (account.chainSpecific.tokens ?? []).reduce( (prev, token) => { const isSpam = [token.name, token.symbol].some(text => { From 06ca68fd992bf94262b5493adeec6d73a2e8a9c0 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:45:53 +0200 Subject: [PATCH 04/22] feat: rm prepare --- .../slices/portfolioSlice/portfolioSlice.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index 4aa660106df..945e26ce41e 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -127,20 +127,17 @@ export const portfolio = createSlice({ // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs prepare: prepareAutoBatched(), }, - - upsertPortfolio: { - reducer: (draftState, { payload }: { payload: Portfolio }) => { - console.log('upserting portfolio', { payload }) - // upsert all - draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) - draftState.accounts.ids = Object.keys(draftState.accounts.byId) - - draftState.accountBalances.byId = merge( - draftState.accountBalances.byId, - payload.accountBalances.byId, - ) - draftState.accountBalances.ids = Object.keys(draftState.accountBalances.byId) - }, + upsertPortfolio: (draftState, { payload }: { payload: Portfolio }) => { + console.log('upserting portfolio', { payload }) + // upsert all + draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) + draftState.accounts.ids = Object.keys(draftState.accounts.byId) + + draftState.accountBalances.byId = merge( + draftState.accountBalances.byId, + payload.accountBalances.byId, + ) + draftState.accountBalances.ids = Object.keys(draftState.accountBalances.byId) }, /** * Explicitly enable an account by its `AccountId`. Necessary where `use-strict` toggles twice From 57a04c88c8d6c25cf5c707500f48545c64e031a2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:46:08 +0200 Subject: [PATCH 05/22] fix: isPortfolioLoaded heuristics --- src/components/Equity/Equity.tsx | 4 ++-- src/components/Layout/Header/Header.tsx | 4 ++-- .../Modals/FiatRamps/views/FiatForm.tsx | 4 ++-- src/components/Modals/Nfts/NftModal.tsx | 4 ++-- src/components/Modals/Settings/ClearCache.tsx | 4 ++-- src/components/Nfts/hooks/useNfts.tsx | 4 ++-- .../hooks/useFetchOpportunities.tsx | 4 ++-- .../components/DefaultAssetList.tsx | 4 ++-- src/context/AppProvider/AppContext.tsx | 4 ++-- .../TransactionsProvider.tsx | 4 ++-- .../CosmosManager/Deposit/CosmosDeposit.tsx | 4 ++-- .../Deposit/FoxFarmingDeposit.tsx | 4 ++-- .../Withdraw/FoxFarmingWithdraw.tsx | 4 ++-- .../FoxyManager/Deposit/FoxyDeposit.tsx | 4 ++-- .../FoxyManager/Withdraw/FoxyWithdraw.tsx | 4 ++-- .../Deposit/ThorchainSaversDeposit.tsx | 4 ++-- .../UniV2Manager/Deposit/UniV2Deposit.tsx | 4 ++-- .../UniV2Manager/Withdraw/UniV2Withdraw.tsx | 4 ++-- .../Accounts/AccountToken/AccountToken.tsx | 4 ++-- src/pages/Accounts/Accounts.tsx | 5 +++-- .../components/AccountList/AccountTable.tsx | 4 ++-- .../Dashboard/components/DashboardChart.tsx | 4 ++-- .../DashboardHeader/WalletBalance.tsx | 4 ++-- .../hooks/useAllLendingPositionsData.tsx | 4 ++-- src/state/slices/common-selectors.ts | 17 +++++++++++++---- .../resolvers/cosmosSdk/index.ts | 4 ++-- .../selectors/stakingSelectors.ts | 6 +++--- src/state/slices/portfolioSlice/selectors.ts | 16 ++++++++++------ src/state/slices/tradeInputSlice/selectors.ts | 6 +++--- src/state/slices/txHistorySlice/selectors.ts | 6 +++--- 30 files changed, 83 insertions(+), 69 deletions(-) diff --git a/src/components/Equity/Equity.tsx b/src/components/Equity/Equity.tsx index 74c781156ba..b0b55da1da6 100644 --- a/src/components/Equity/Equity.tsx +++ b/src/components/Equity/Equity.tsx @@ -20,8 +20,8 @@ import { selectAssetEquityItemsByFilter, selectAssets, selectEquityTotalBalance, + selectIsPortfolioLoading, selectOpportunityApiPending, - selectPortfolioLoading, selectUnderlyingLpAssetsWithBalancesAndIcons, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -41,7 +41,7 @@ const stackDividerStyle = { marginLeft: 14 } export const Equity = ({ assetId, accountId }: EquityProps) => { const translate = useTranslate() - const portfolioLoading = useSelector(selectPortfolioLoading) + const portfolioLoading = useSelector(selectIsPortfolioLoading) const opportunitiesLoading = useAppSelector(selectOpportunityApiPending) const isLoading = portfolioLoading || opportunitiesLoading const assets = useAppSelector(selectAssets) diff --git a/src/components/Layout/Header/Header.tsx b/src/components/Layout/Header/Header.tsx index 2092fb3e619..9b65911278b 100644 --- a/src/components/Layout/Header/Header.tsx +++ b/src/components/Layout/Header/Header.tsx @@ -16,9 +16,9 @@ import { useWallet } from 'hooks/useWallet/useWallet' import { isUtxoAccountId } from 'lib/utils/utxo' import { portfolio } from 'state/slices/portfolioSlice/portfolioSlice' import { + selectEnabledWalletAccountIds, selectPortfolioDegradedState, selectShowSnapsModal, - selectWalletAccountIds, selectWalletId, } from 'state/slices/selectors' import { useAppDispatch } from 'state/store' @@ -98,7 +98,7 @@ export const Header = memo(() => { ) const currentWalletId = useSelector(selectWalletId) - const walletAccountIds = useSelector(selectWalletAccountIds) + const walletAccountIds = useSelector(selectEnabledWalletAccountIds) const hasUtxoAccountIds = useMemo( () => walletAccountIds.some(accountId => isUtxoAccountId(accountId)), [walletAccountIds], diff --git a/src/components/Modals/FiatRamps/views/FiatForm.tsx b/src/components/Modals/FiatRamps/views/FiatForm.tsx index d69ed0a37f4..366f2969de7 100644 --- a/src/components/Modals/FiatRamps/views/FiatForm.tsx +++ b/src/components/Modals/FiatRamps/views/FiatForm.tsx @@ -12,9 +12,9 @@ import { parseAddressInputWithChainId } from 'lib/address/address' import { useGetFiatRampsQuery } from 'state/apis/fiatRamps/fiatRamps' import { selectAssetsSortedByMarketCapUserCurrencyBalanceAndName, + selectEnabledWalletAccountIds, selectHighestMarketCapFeeAsset, selectPortfolioAccountMetadata, - selectWalletAccountIds, selectWalletConnectedChainIds, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -35,7 +35,7 @@ export const FiatForm: React.FC = ({ fiatRampAction, accountId: selectedAccountId, }) => { - const walletAccountIds = useSelector(selectWalletAccountIds) + const walletAccountIds = useSelector(selectEnabledWalletAccountIds) const portfolioAccountMetadata = useSelector(selectPortfolioAccountMetadata) const sortedAssets = useSelector(selectAssetsSortedByMarketCapUserCurrencyBalanceAndName) const [accountId, setAccountId] = useState(selectedAccountId) diff --git a/src/components/Modals/Nfts/NftModal.tsx b/src/components/Modals/Nfts/NftModal.tsx index ced702d215b..01b7552ea16 100644 --- a/src/components/Modals/Nfts/NftModal.tsx +++ b/src/components/Modals/Nfts/NftModal.tsx @@ -53,7 +53,7 @@ import { nft, nftApi, useGetNftCollectionQuery } from 'state/apis/nft/nftApi' import { selectNftById, selectNftCollectionById } from 'state/apis/nft/selectors' import { chainIdToOpenseaNetwork } from 'state/apis/nft/utils' import { getMediaType } from 'state/apis/zapper/validators' -import { selectWalletAccountIds, selectWalletId } from 'state/slices/common-selectors' +import { selectEnabledWalletAccountIds, selectWalletId } from 'state/slices/common-selectors' import { selectAssetById } from 'state/slices/selectors' import { useAppDispatch, useAppSelector } from 'state/store' import { breakpoints } from 'theme/theme' @@ -105,7 +105,7 @@ export const NftModal: React.FC = ({ nftAssetId }) => { const modalHeaderBg = useColorModeValue('gray.50', 'gray.785') const [isLargerThanMd] = useMediaQuery(`(min-width: ${breakpoints['md']})`) const walletId = useAppSelector(selectWalletId) - const accountIds = useAppSelector(selectWalletAccountIds) + const accountIds = useAppSelector(selectEnabledWalletAccountIds) useGetNftCollectionQuery( { accountIds, collectionId: nftItem.collectionId }, diff --git a/src/components/Modals/Settings/ClearCache.tsx b/src/components/Modals/Settings/ClearCache.tsx index 5379e921704..2c78f4c38f8 100644 --- a/src/components/Modals/Settings/ClearCache.tsx +++ b/src/components/Modals/Settings/ClearCache.tsx @@ -19,7 +19,7 @@ import { RawText } from 'components/Text' import { reloadWebview } from 'context/WalletProvider/MobileWallet/mobileMessageHandlers' import { useWallet } from 'hooks/useWallet/useWallet' import { isMobile as isMobileApp } from 'lib/globals' -import { selectWalletAccountIds } from 'state/slices/selectors' +import { selectEnabledWalletAccountIds } from 'state/slices/selectors' import { txHistory, txHistoryApi } from 'state/slices/txHistorySlice/txHistorySlice' import { persistor, useAppDispatch, useAppSelector } from 'state/store' @@ -56,7 +56,7 @@ const ClearCacheButton = ({ export const ClearCache = ({ appHistory }: ClearCacheProps) => { const dispatch = useAppDispatch() - const requestedAccountIds = useAppSelector(selectWalletAccountIds) + const requestedAccountIds = useAppSelector(selectEnabledWalletAccountIds) const translate = useTranslate() const history = useHistory() const { disconnect } = useWallet() diff --git a/src/components/Nfts/hooks/useNfts.tsx b/src/components/Nfts/hooks/useNfts.tsx index 053db8a2eb0..e24a210126b 100644 --- a/src/components/Nfts/hooks/useNfts.tsx +++ b/src/components/Nfts/hooks/useNfts.tsx @@ -1,10 +1,10 @@ import { useMemo } from 'react' import { useGetNftUserTokensQuery } from 'state/apis/nft/nftApi' -import { selectWalletAccountIds } from 'state/slices/common-selectors' +import { selectEnabledWalletAccountIds } from 'state/slices/common-selectors' import { useAppSelector } from 'state/store' export const useNfts = () => { - const requestedAccountIds = useAppSelector(selectWalletAccountIds) + const requestedAccountIds = useAppSelector(selectEnabledWalletAccountIds) const { isUninitialized, isLoading, isFetching, data } = useGetNftUserTokensQuery( { diff --git a/src/components/StakingVaults/hooks/useFetchOpportunities.tsx b/src/components/StakingVaults/hooks/useFetchOpportunities.tsx index cdd1be610a4..9eed23146a2 100644 --- a/src/components/StakingVaults/hooks/useFetchOpportunities.tsx +++ b/src/components/StakingVaults/hooks/useFetchOpportunities.tsx @@ -8,18 +8,18 @@ import { useGetZapperUniV2PoolAssetIdsQuery, } from 'state/apis/zapper/zapperApi' import { + selectEnabledWalletAccountIds, selectEvmAccountIds, selectPortfolioAccounts, selectPortfolioAssetIds, selectPortfolioLoadingStatus, - selectWalletAccountIds, } from 'state/slices/selectors' import { useAppDispatch } from 'state/store' export const useFetchOpportunities = () => { const dispatch = useAppDispatch() const portfolioLoadingStatus = useSelector(selectPortfolioLoadingStatus) - const requestedAccountIds = useSelector(selectWalletAccountIds) + const requestedAccountIds = useSelector(selectEnabledWalletAccountIds) const evmAccountIds = useSelector(selectEvmAccountIds) const portfolioAssetIds = useSelector(selectPortfolioAssetIds) const portfolioAccounts = useSelector(selectPortfolioAccounts) diff --git a/src/components/TradeAssetSearch/components/DefaultAssetList.tsx b/src/components/TradeAssetSearch/components/DefaultAssetList.tsx index 79c3fadb1fe..550dfbd174f 100644 --- a/src/components/TradeAssetSearch/components/DefaultAssetList.tsx +++ b/src/components/TradeAssetSearch/components/DefaultAssetList.tsx @@ -1,6 +1,6 @@ import type { Asset } from '@shapeshiftoss/types' import { useMemo } from 'react' -import { selectPortfolioLoading } from 'state/slices/selectors' +import { selectIsPortfolioLoading } from 'state/slices/selectors' import { useAppSelector } from 'state/store' import { useGetPopularAssetsQuery } from '../hooks/useGetPopularAssetsQuery' @@ -17,7 +17,7 @@ export const DefaultAssetList = ({ popularAssets, onAssetClick, }: DefaultAssetListProps) => { - const isPortfolioLoading = useAppSelector(selectPortfolioLoading) + const isPortfolioLoading = useAppSelector(selectIsPortfolioLoading) const { isLoading: isPopularAssetIdsLoading } = useGetPopularAssetsQuery() const groupIsLoading = useMemo(() => { diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index cabd5f21cdc..defcd0b2e1f 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -33,11 +33,11 @@ import { preferences } from 'state/slices/preferencesSlice/preferencesSlice' import { selectAccountIdsByChainId, selectAssetIds, + selectEnabledWalletAccountIds, selectPortfolioAssetIds, selectPortfolioLoadingStatus, selectSelectedCurrency, selectSelectedLocale, - selectWalletAccountIds, } from 'state/slices/selectors' import { txHistoryApi } from 'state/slices/txHistorySlice/txHistorySlice' import { useAppDispatch, useAppSelector } from 'state/store' @@ -59,7 +59,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const { supportedChains } = usePlugins() const wallet = useWallet().state.wallet const assetIds = useSelector(selectAssetIds) - const requestedAccountIds = useSelector(selectWalletAccountIds) + const requestedAccountIds = useSelector(selectEnabledWalletAccountIds) const portfolioLoadingStatus = useSelector(selectPortfolioLoadingStatus) const portfolioAssetIds = useSelector(selectPortfolioAssetIds) const routeAssetId = useRouteAssetId() diff --git a/src/context/TransactionsProvider/TransactionsProvider.tsx b/src/context/TransactionsProvider/TransactionsProvider.tsx index 44a73b70008..726839f5a0d 100644 --- a/src/context/TransactionsProvider/TransactionsProvider.tsx +++ b/src/context/TransactionsProvider/TransactionsProvider.tsx @@ -30,10 +30,10 @@ import { DefiProvider, DefiType } from 'state/slices/opportunitiesSlice/types' import { toOpportunityId } from 'state/slices/opportunitiesSlice/utils' import { portfolioApi } from 'state/slices/portfolioSlice/portfolioSlice' import { + selectEnabledWalletAccountIds, selectPortfolioAccountMetadata, selectPortfolioLoadingStatus, selectStakingOpportunitiesById, - selectWalletAccountIds, } from 'state/slices/selectors' import { txHistory } from 'state/slices/txHistorySlice/txHistorySlice' import { useAppDispatch } from 'state/store' @@ -52,7 +52,7 @@ export const TransactionsProvider: React.FC = ({ chil } = useWallet() const portfolioAccountMetadata = useSelector(selectPortfolioAccountMetadata) const portfolioLoadingStatus = useSelector(selectPortfolioLoadingStatus) - const walletAccountIds = useSelector(selectWalletAccountIds) + const walletAccountIds = useSelector(selectEnabledWalletAccountIds) const { supportedChains } = usePlugins() const stakingOpportunitiesById = useSelector(selectStakingOpportunitiesById) diff --git a/src/features/defi/providers/cosmos/components/CosmosManager/Deposit/CosmosDeposit.tsx b/src/features/defi/providers/cosmos/components/CosmosManager/Deposit/CosmosDeposit.tsx index 53295e1ad05..47256c176e0 100644 --- a/src/features/defi/providers/cosmos/components/CosmosManager/Deposit/CosmosDeposit.tsx +++ b/src/features/defi/providers/cosmos/components/CosmosManager/Deposit/CosmosDeposit.tsx @@ -23,8 +23,8 @@ import { serializeUserStakingId, toValidatorId } from 'state/slices/opportunitie import { selectAssetById, selectEarnUserStakingOpportunityByUserStakingId, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, - selectPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -57,7 +57,7 @@ export const CosmosDeposit: React.FC = ({ // user info const { state: walletState } = useWallet() - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const validatorId = toValidatorId({ chainId, account: validatorAddress }) diff --git a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Deposit/FoxFarmingDeposit.tsx b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Deposit/FoxFarmingDeposit.tsx index dc36f2e5394..baa7f4b4f8a 100644 --- a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Deposit/FoxFarmingDeposit.tsx +++ b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Deposit/FoxFarmingDeposit.tsx @@ -21,8 +21,8 @@ import { useBrowserRouter } from 'hooks/useBrowserRouter/useBrowserRouter' import { toOpportunityId } from 'state/slices/opportunitiesSlice/utils' import { selectAggregatedEarnUserStakingOpportunityByStakingId, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, - selectPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -65,7 +65,7 @@ export const FoxFarmingDeposit: React.FC = ({ const foxFarmingOpportunity = useAppSelector(state => selectAggregatedEarnUserStakingOpportunityByStakingId(state, foxFarmingOpportunityFilter), ) - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) useEffect(() => { ;(() => { diff --git a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Withdraw/FoxFarmingWithdraw.tsx b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Withdraw/FoxFarmingWithdraw.tsx index 86825e588e2..b5daa3b969e 100644 --- a/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Withdraw/FoxFarmingWithdraw.tsx +++ b/src/features/defi/providers/fox-farming/components/FoxFarmingManager/Withdraw/FoxFarmingWithdraw.tsx @@ -19,7 +19,7 @@ import { useBrowserRouter } from 'hooks/useBrowserRouter/useBrowserRouter' import { serializeUserStakingId, toOpportunityId } from 'state/slices/opportunitiesSlice/utils' import { selectEarnUserStakingOpportunityByUserStakingId, - selectPortfolioLoading, + selectIsPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -60,7 +60,7 @@ export const FoxFarmingWithdraw: React.FC = ({ selectEarnUserStakingOpportunityByUserStakingId(state, opportunityDataFilter), ) - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const handleBack = useCallback(() => { history.push({ diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx index d6f4c2b0731..ebbb0a17af1 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Deposit/FoxyDeposit.tsx @@ -27,8 +27,8 @@ import type { StakingId } from 'state/slices/opportunitiesSlice/types' import { selectAssetById, selectBIP44ParamsByAccountId, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, - selectPortfolioLoading, selectStakingOpportunityByFilter, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -75,7 +75,7 @@ export const FoxyDeposit: React.FC<{ const chainAdapterManager = getChainAdapterManager() const { state: walletState } = useWallet() const { data: foxyAprData, isLoading: isFoxyAprLoading } = useGetFoxyAprQuery() - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) useEffect(() => { ;(async () => { diff --git a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/FoxyWithdraw.tsx b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/FoxyWithdraw.tsx index 7d5d11e37e9..f98bc45f0c2 100644 --- a/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/FoxyWithdraw.tsx +++ b/src/features/defi/providers/foxy/components/FoxyManager/Withdraw/FoxyWithdraw.tsx @@ -25,8 +25,8 @@ import { bnOrZero } from 'lib/bignumber/bignumber' import { getFoxyApi } from 'state/apis/foxy/foxyApiSingleton' import { selectBIP44ParamsByAccountId, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, - selectPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -63,7 +63,7 @@ export const FoxyWithdraw: React.FC<{ const chainAdapterManager = getChainAdapterManager() const chainAdapter = chainAdapterManager.get(KnownChainIds.EthereumMainnet) const { state: walletState } = useWallet() - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) useEffect(() => { ;(async () => { diff --git a/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Deposit/ThorchainSaversDeposit.tsx b/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Deposit/ThorchainSaversDeposit.tsx index a734f4535db..aea69ec3d0b 100644 --- a/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Deposit/ThorchainSaversDeposit.tsx +++ b/src/features/defi/providers/thorchain-savers/components/ThorchainSaversManager/Deposit/ThorchainSaversDeposit.tsx @@ -30,9 +30,9 @@ import { selectAssetById, selectEarnUserStakingOpportunityByUserStakingId, selectHighestStakingBalanceAccountIdByStakingId, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, selectPortfolioAccountMetadataByAccountId, - selectPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -72,7 +72,7 @@ export const ThorchainSaversDeposit: React.FC = ({ const isRunePool = assetId === thorchainAssetId // user info - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const opportunityId: StakingId | undefined = useMemo( () => (assetId ? toOpportunityId({ chainId, assetNamespace, assetReference }) : undefined), diff --git a/src/features/defi/providers/univ2/components/UniV2Manager/Deposit/UniV2Deposit.tsx b/src/features/defi/providers/univ2/components/UniV2Manager/Deposit/UniV2Deposit.tsx index 493520bd38c..d19135e12db 100644 --- a/src/features/defi/providers/univ2/components/UniV2Manager/Deposit/UniV2Deposit.tsx +++ b/src/features/defi/providers/univ2/components/UniV2Manager/Deposit/UniV2Deposit.tsx @@ -21,8 +21,8 @@ import type { LpId } from 'state/slices/opportunitiesSlice/types' import { selectAssetById, selectEarnUserLpOpportunity, + selectIsPortfolioLoading, selectMarketDataByAssetIdUserCurrency, - selectPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -72,7 +72,7 @@ export const UniV2Deposit: React.FC = ({ selectMarketDataByAssetIdUserCurrency(state, earnUserLpOpportunity?.underlyingAssetId), ) - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const handleBack = useCallback(() => { history.push({ diff --git a/src/features/defi/providers/univ2/components/UniV2Manager/Withdraw/UniV2Withdraw.tsx b/src/features/defi/providers/univ2/components/UniV2Manager/Withdraw/UniV2Withdraw.tsx index 0a786aca37b..3058e900625 100644 --- a/src/features/defi/providers/univ2/components/UniV2Manager/Withdraw/UniV2Withdraw.tsx +++ b/src/features/defi/providers/univ2/components/UniV2Manager/Withdraw/UniV2Withdraw.tsx @@ -21,7 +21,7 @@ import type { LpId } from 'state/slices/opportunitiesSlice/types' import { selectAssetById, selectEarnUserLpOpportunity, - selectPortfolioLoading, + selectIsPortfolioLoading, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -68,7 +68,7 @@ export const UniV2Withdraw: React.FC = ({ ) // user info - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const handleBack = useCallback(() => { history.push({ diff --git a/src/pages/Accounts/AccountToken/AccountToken.tsx b/src/pages/Accounts/AccountToken/AccountToken.tsx index 89a514d5c91..ddb2d71a76a 100644 --- a/src/pages/Accounts/AccountToken/AccountToken.tsx +++ b/src/pages/Accounts/AccountToken/AccountToken.tsx @@ -9,7 +9,7 @@ import { Equity } from 'components/Equity/Equity' import { MultiHopTrade } from 'components/MultiHopTrade/MultiHopTrade' import { EarnOpportunities } from 'components/StakingVaults/EarnOpportunities' import { AssetTransactionHistory } from 'components/TransactionHistory/AssetTransactionHistory' -import { selectWalletAccountIds } from 'state/slices/selectors' +import { selectEnabledWalletAccountIds } from 'state/slices/selectors' import { AccountBalance } from './AccountBalance' @@ -31,7 +31,7 @@ export const AccountToken = () => { * so we'll redirect user to the "accounts" page, * in order to choose the account from beginning. */ - const accountIds = useSelector(selectWalletAccountIds) + const accountIds = useSelector(selectEnabledWalletAccountIds) const isCurrentAccountIdOwner = Boolean(accountIds.map(toLower).includes(toLower(accountId))) if (!accountIds.length) return null if (!isCurrentAccountIdOwner) return diff --git a/src/pages/Accounts/Accounts.tsx b/src/pages/Accounts/Accounts.tsx index 0826a8c37ae..15a29524c6d 100644 --- a/src/pages/Accounts/Accounts.tsx +++ b/src/pages/Accounts/Accounts.tsx @@ -12,7 +12,7 @@ import { useIsSnapInstalled } from 'hooks/useIsSnapInstalled/useIsSnapInstalled' import { useModal } from 'hooks/useModal/useModal' import { useWallet } from 'hooks/useWallet/useWallet' import { - selectPortfolioLoading, + selectIsPortfolioLoading, selectWalletConnectedChainIdsSorted, selectWalletId, } from 'state/slices/selectors' @@ -88,7 +88,7 @@ const AccountHeader = ({ isLoading }: { isLoading?: boolean }) => { export const Accounts = () => { const { path } = useRouteMatch() const blanks = Array(4).fill(0) - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const portfolioChainIdsSortedUserCurrency = useSelector(selectWalletConnectedChainIdsSorted) const chainRows = useMemo( () => @@ -106,6 +106,7 @@ export const Accounts = () => { )) }, [blanks]) + console.log({ loading }) const renderRows = useMemo(() => { return loading ? blankRows : chainRows }, [blankRows, chainRows, loading]) diff --git a/src/pages/Dashboard/components/AccountList/AccountTable.tsx b/src/pages/Dashboard/components/AccountList/AccountTable.tsx index 8eccd98821e..3990dc13357 100644 --- a/src/pages/Dashboard/components/AccountList/AccountTable.tsx +++ b/src/pages/Dashboard/components/AccountList/AccountTable.tsx @@ -21,7 +21,7 @@ import { Text } from 'components/Text' import { useInfiniteScroll } from 'hooks/useInfiniteScroll/useInfiniteScroll' import { bnOrZero } from 'lib/bignumber/bignumber' import type { AccountRowData } from 'state/slices/selectors' -import { selectPortfolioAccountRows, selectPortfolioLoading } from 'state/slices/selectors' +import { selectIsPortfolioLoading, selectPortfolioAccountRows } from 'state/slices/selectors' import { breakpoints } from 'theme/theme' type RowProps = Row @@ -29,7 +29,7 @@ type RowProps = Row const stackTextAlign: ResponsiveValue = { base: 'right', lg: 'left' } export const AccountTable = memo(() => { - const loading = useSelector(selectPortfolioLoading) + const loading = useSelector(selectIsPortfolioLoading) const rowData = useSelector(selectPortfolioAccountRows) const sortedRows = useMemo(() => { return rowData.sort((a, b) => Number(b.fiatAmount) - Number(a.fiatAmount)) diff --git a/src/pages/Dashboard/components/DashboardChart.tsx b/src/pages/Dashboard/components/DashboardChart.tsx index 3e526ce2c5f..08f1db325d7 100644 --- a/src/pages/Dashboard/components/DashboardChart.tsx +++ b/src/pages/Dashboard/components/DashboardChart.tsx @@ -22,8 +22,8 @@ import { Text } from 'components/Text' import { preferences } from 'state/slices/preferencesSlice/preferencesSlice' import { selectChartTimeframe, + selectIsPortfolioLoading, selectPortfolioAssetIds, - selectPortfolioLoading, selectPortfolioTotalUserCurrencyBalanceExcludeEarnDupes, } from 'state/slices/selectors' import { useAppDispatch, useAppSelector } from 'state/store' @@ -66,7 +66,7 @@ export const DashboardChart = () => { const portfolioTotalUserCurrencyBalance = useAppSelector( selectPortfolioTotalUserCurrencyBalanceExcludeEarnDupes, ) - const loading = useAppSelector(selectPortfolioLoading) + const loading = useAppSelector(selectIsPortfolioLoading) const isLoaded = !loading const [isRainbowChart, setIsRainbowChart] = useState(false) diff --git a/src/pages/Dashboard/components/DashboardHeader/WalletBalance.tsx b/src/pages/Dashboard/components/DashboardHeader/WalletBalance.tsx index f72b9dfdae2..6b21acf89e3 100644 --- a/src/pages/Dashboard/components/DashboardHeader/WalletBalance.tsx +++ b/src/pages/Dashboard/components/DashboardHeader/WalletBalance.tsx @@ -10,7 +10,7 @@ import { Text } from 'components/Text' import { selectClaimableRewards, selectEarnBalancesUserCurrencyAmountFull, - selectPortfolioLoading, + selectIsPortfolioLoading, selectPortfolioTotalUserCurrencyBalanceExcludeEarnDupes, } from 'state/slices/selectors' import { useAppSelector } from 'state/store' @@ -32,7 +32,7 @@ type WalletBalanceProps = { export const WalletBalance: React.FC = memo( ({ label = 'defi.netWorth', alignItems }) => { const { isLoading: isOpportunitiesLoading } = useFetchOpportunities() - const isPortfolioLoading = useAppSelector(selectPortfolioLoading) + const isPortfolioLoading = useAppSelector(selectIsPortfolioLoading) const claimableRewardsUserCurrencyBalanceFilter = useMemo(() => ({}), []) const claimableRewardsUserCurrencyBalance = useAppSelector(state => selectClaimableRewards(state, claimableRewardsUserCurrencyBalanceFilter), diff --git a/src/pages/Lending/hooks/useAllLendingPositionsData.tsx b/src/pages/Lending/hooks/useAllLendingPositionsData.tsx index 5f481818033..85b5a32807b 100644 --- a/src/pages/Lending/hooks/useAllLendingPositionsData.tsx +++ b/src/pages/Lending/hooks/useAllLendingPositionsData.tsx @@ -7,9 +7,9 @@ import { getThorchainLendingPosition } from 'lib/utils/thorchain/lending' import type { Borrower } from 'lib/utils/thorchain/lending/types' import { selectAccountIdsByAssetId, + selectEnabledWalletAccountIds, selectUserCurrencyRateByAssetId, selectUserCurrencyToUsdRate, - selectWalletAccountIds, } from 'state/slices/selectors' import { store, useAppSelector } from 'state/store' @@ -24,7 +24,7 @@ export const useAllLendingPositionsData = ({ assetId }: UseAllLendingPositionsDa type: 'collateral', }) - const accountIds = useAppSelector(selectWalletAccountIds) + const accountIds = useAppSelector(selectEnabledWalletAccountIds) const accounts = useMemo( () => (lendingSupportedAssets ?? []) diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index 2878c4796fe..c113b594b1c 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -38,7 +38,7 @@ export const selectWalletEnabledAccountIds = createDeepEqualOutputSelector( }, ) -export const selectWalletAccountIds = createDeepEqualOutputSelector( +export const selectEnabledWalletAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.wallet.byId, selectWalletEnabledAccountIds, @@ -48,13 +48,22 @@ export const selectWalletAccountIds = createDeepEqualOutputSelector( }, ) +export const selectWalletAccountIds = createDeepEqualOutputSelector( + selectWalletId, + (state: ReduxState) => state.portfolio.wallet.byId, + (walletId, walletById): AccountId[] => { + const walletAccountIds = (walletId && walletById[walletId]) ?? [] + return walletAccountIds + }, +) + export const selectEvmAccountIds = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, accountIds => accountIds.filter(accountId => isEvmChainId(fromAccountId(accountId).chainId)), ) export const selectWalletConnectedChainIds = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, accountIds => { const chainIds = accountIds.reduce((acc, accountId) => { const { chainId } = fromAccountId(accountId) @@ -66,7 +75,7 @@ export const selectWalletConnectedChainIds = createDeepEqualOutputSelector( ) export const selectPortfolioAccountBalancesBaseUnit = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (state: ReduxState): PortfolioAccountBalancesById => state.portfolio.accountBalances.byId, (walletAccountIds, accountBalancesById) => pickBy(accountBalancesById, (_balances, accountId: AccountId) => diff --git a/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/index.ts b/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/index.ts index 009e156f93c..55da6813f13 100644 --- a/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/index.ts +++ b/src/state/slices/opportunitiesSlice/resolvers/cosmosSdk/index.ts @@ -5,7 +5,7 @@ import { accountIdToFeeAssetId } from 'lib/utils/accounts' import { assertGetCosmosSdkChainAdapter } from 'lib/utils/cosmosSdk' import type { ReduxState } from 'state/reducer' import { selectAssetById } from 'state/slices/assetsSlice/selectors' -import { selectWalletAccountIds } from 'state/slices/common-selectors' +import { selectEnabledWalletAccountIds } from 'state/slices/common-selectors' import { selectMarketDataByAssetIdUserCurrency } from 'state/slices/marketDataSlice/selectors' import type { @@ -31,7 +31,7 @@ export const cosmosSdkOpportunityIdsResolver = async ({ }> => { const state = reduxApi.getState() as ReduxState - const portfolioAccountIds = selectWalletAccountIds(state) + const portfolioAccountIds = selectEnabledWalletAccountIds(state) const cosmosSdkChainIdsWhitelist = [cosmosChainId] // Not AccountIds of all Cosmos SDK chains but only a subset of current and future Cosmos SDK chains we support/may support diff --git a/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts b/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts index 003a655939e..69b8a71af0f 100644 --- a/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts +++ b/src/state/slices/opportunitiesSlice/selectors/stakingSelectors.ts @@ -23,9 +23,9 @@ import { import { selectAssetByFilter, selectAssets } from '../../assetsSlice/selectors' import { + selectEnabledWalletAccountIds, selectPortfolioAssetBalancesBaseUnit, selectPortfolioUserCurrencyBalances, - selectWalletAccountIds, } from '../../common-selectors' import { selectMarketDataByFilter, @@ -61,7 +61,7 @@ export const selectStakingIds = createDeepEqualOutputSelector( ) export const selectUserStakingIds = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (state: ReduxState) => state.opportunities.userStaking.ids, (walletAccountIds, userStakingIds): UserStakingId[] => userStakingIds.filter(userStakingId => @@ -75,7 +75,7 @@ export const selectStakingOpportunitiesByAccountId = createDeepEqualOutputSelect ) export const selectUserStakingOpportunitiesById = createSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (state: ReduxState) => state.opportunities.userStaking.byId, (walletAccountIds, userStakingById) => pickBy(userStakingById, (_userStaking, userStakingId) => diff --git a/src/state/slices/portfolioSlice/selectors.ts b/src/state/slices/portfolioSlice/selectors.ts index 572c160a037..86e31185aac 100644 --- a/src/state/slices/portfolioSlice/selectors.ts +++ b/src/state/slices/portfolioSlice/selectors.ts @@ -50,6 +50,7 @@ import { selectBalanceThreshold } from 'state/slices/preferencesSlice/selectors' import { selectAssets } from '../assetsSlice/selectors' import { + selectEnabledWalletAccountIds, selectPortfolioAccountBalancesBaseUnit, selectPortfolioAssetBalancesBaseUnit, selectPortfolioUserCurrencyBalances, @@ -81,7 +82,7 @@ import { AssetEquityType } from './portfolioSliceCommon' import { findAccountsByAssetId } from './utils' export const selectPortfolioAccounts = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (state: ReduxState) => state.portfolio.accounts.byId, (walletAccountIds, accountsById): PortfolioAccounts['byId'] => { return pickBy(accountsById, (_account, accountId: AccountId) => @@ -116,7 +117,7 @@ export const selectPortfolioAssetIds = createDeepEqualOutputSelector( export const selectPortfolioAccountMetadata = createDeepEqualOutputSelector( (state: ReduxState): AccountMetadataById => state.portfolio.accountMetadata.byId, - selectWalletAccountIds, + selectEnabledWalletAccountIds, (accountMetadata, walletAccountIds): AccountMetadataById => { return pickBy(accountMetadata, (_, accountId: AccountId) => walletAccountIds.includes(accountId), @@ -267,7 +268,7 @@ export const selectPortfolioUserCurrencyBalanceByFilter = createCachedSelector( )((_s: ReduxState, filter) => `${filter?.accountId ?? 'accountId'}-${filter?.assetId ?? 'assetId'}`) export const selectFirstAccountIdByChainId = createCachedSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (_s: ReduxState, chainId: ChainId) => chainId, getFirstAccountIdByChainId, )((_s: ReduxState, chainId) => chainId ?? 'chainId') @@ -278,7 +279,7 @@ export const selectFirstAccountIdByChainId = createCachedSelector( * but can contain it */ export const selectPortfolioAccountIdsByAssetIdFilter = createDeepEqualOutputSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, selectAssetIdParamFromFilter, selectWalletId, (accountIds, assetId, walletId): AccountId[] => { @@ -375,7 +376,10 @@ export const selectBalanceChartCryptoBalancesByAccountIdAboveThreshold = ) // we only set ids when chain adapters responds, so if these are present, the portfolio has loaded -export const selectPortfolioLoading = createSelector( +export const selectIsPortfolioLoading = createSelector( + // If at least one AccountId is loaded, consider the portfolio as loaded. + // This may not be true in the case the user has custom account management and their first fetched account is disabled, + // but this will display insta-loaded state for all (most) other cases selectWalletAccountIds, (ids): boolean => !Boolean(ids.length), ) @@ -920,7 +924,7 @@ export const selectPortfolioAnonymized = createDeepEqualOutputSelector( ) export const selectAccountIdByAccountNumberAndChainId = createSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, selectPortfolioAccountMetadata, (walletAccountIds, accountMetadata): PartialRecord> => { const result: PartialRecord> = {} diff --git a/src/state/slices/tradeInputSlice/selectors.ts b/src/state/slices/tradeInputSlice/selectors.ts index 99d37e42d82..88a6c336e3a 100644 --- a/src/state/slices/tradeInputSlice/selectors.ts +++ b/src/state/slices/tradeInputSlice/selectors.ts @@ -8,8 +8,8 @@ import type { ReduxState } from 'state/reducer' import { createDeepEqualOutputSelector } from 'state/selector-utils' import { + selectEnabledWalletAccountIds, selectPortfolioCryptoBalanceBaseUnitByFilter, - selectWalletAccountIds, } from '../common-selectors' import { selectMarketDataUsd, selectUserCurrencyToUsdRate } from '../marketDataSlice/selectors' import { @@ -87,7 +87,7 @@ export const selectFirstHopSellAccountId = createSelector( selectTradeInput, selectInputSellAsset, selectPortfolioAssetAccountBalancesSortedUserCurrency, - selectWalletAccountIds, + selectEnabledWalletAccountIds, (tradeInput, sellAsset, accountIdAssetValues, accountIds) => { // return the users selection if it exists if (tradeInput.sellAssetAccountId) return tradeInput.sellAssetAccountId @@ -107,7 +107,7 @@ export const selectFirstHopSellAccountId = createSelector( export const selectLastHopBuyAccountId = createSelector( selectTradeInput, selectInputBuyAsset, - selectWalletAccountIds, + selectEnabledWalletAccountIds, selectAccountIdByAccountNumberAndChainId, selectFirstHopSellAccountId, selectPortfolioAccountMetadata, diff --git a/src/state/slices/txHistorySlice/selectors.ts b/src/state/slices/txHistorySlice/selectors.ts index f5d0526add4..41faad969a2 100644 --- a/src/state/slices/txHistorySlice/selectors.ts +++ b/src/state/slices/txHistorySlice/selectors.ts @@ -27,7 +27,7 @@ import { } from 'state/selectors' import { selectAssets } from '../assetsSlice/selectors' -import { selectWalletAccountIds, selectWalletEnabledAccountIds } from '../common-selectors' +import { selectEnabledWalletAccountIds, selectWalletEnabledAccountIds } from '../common-selectors' import { selectPortfolioAccountMetadata } from '../portfolioSlice/selectors' import type { Tx, TxId, TxIdsByAccountIdAssetId } from './txHistorySlice' @@ -94,7 +94,7 @@ const selectMatchingAssetsParamFromFilter = (_state: ReduxState, filter: TxHisto filter?.matchingAssets const selectWalletTxIdsByAccountIdAssetId = createSelector( - selectWalletAccountIds, + selectEnabledWalletAccountIds, (state: ReduxState) => state.txHistory.txs.byAccountIdAssetId, (accountIds, txsByAccountIdAssetId): TxIdsByAccountIdAssetId => pickBy(txsByAccountIdAssetId, (_, accountId) => accountIds.includes(accountId)), @@ -356,7 +356,7 @@ export const selectTxsByQuery = createCachedSelector( export const selectIsTxHistoryAvailableByFilter = createCachedSelector( (state: ReduxState) => state.txHistory.hydrationMeta, - selectWalletAccountIds, + selectEnabledWalletAccountIds, selectAccountIdParamFromFilter, selectTimeframeParamFromFilter, (hydrationMeta, walletAccountIds, accountId, timeframe) => { From 190de45e91fd3e3e7c7652305d627869306e14b2 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:32:18 +0200 Subject: [PATCH 06/22] feat: make metadata upsertion blazingly fast --- src/context/AppProvider/AppContext.tsx | 134 ++++++++++++++++--------- src/state/slices/common-selectors.ts | 1 + 2 files changed, 89 insertions(+), 46 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index defcd0b2e1f..468a259384d 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -1,5 +1,6 @@ import { usePrevious, useToast } from '@chakra-ui/react' -import { fromAccountId } from '@shapeshiftoss/caip' +import type { AccountId, ChainNamespace } from '@shapeshiftoss/caip' +import { CHAIN_NAMESPACE, fromAccountId, fromChainId } from '@shapeshiftoss/caip' import type { LedgerOpenAppEventArgs } from '@shapeshiftoss/chain-adapters' import { emitter } from '@shapeshiftoss/chain-adapters' import { isLedger } from '@shapeshiftoss/hdwallet-ledger' @@ -189,59 +190,100 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { Object.assign(accountMetadataByAccountId, accountIdsAndMetadata) const { getAccount } = portfolioApi.endpoints - const accountPromises = accountIds.map(accountId => - dispatch(getAccount.initiate({ accountId }, { forceRefetch: true })), + + const groupAccountIdsByNamespace = ( + _accountIds: AccountId[], + ): Record => { + return _accountIds.reduce( + (acc, _accountId) => { + const { chainId } = fromAccountId(_accountId) + const namespace = fromChainId(chainId).chainNamespace + + // This should not happen but... + if (!acc[namespace]) { + acc[namespace] = [] + } + acc[namespace].push(_accountId) + + return acc + }, + { + [CHAIN_NAMESPACE.Evm]: [], + [CHAIN_NAMESPACE.CosmosSdk]: [], + [CHAIN_NAMESPACE.Solana]: [], + [CHAIN_NAMESPACE.Utxo]: [], + } as Record, + ) + } + + // Groups promises by chain namespaces. We need all of them to settle *for a given chain family* to detect activity, at least for UTXOs + // Doing the account meta upsertion side effects right after EVM chains / others are settled allow us to immediately start upserting meta, + // which in turn will make the app mark said accounts as loaded + const chainNamespacePromises = Object.values(groupAccountIdsByNamespace(accountIds)).map( + async accountIds => { + const results = await Promise.allSettled( + accountIds.map(async id => { + const result = await dispatch( + getAccount.initiate({ accountId: id }, { forceRefetch: true }), + ) + return result + }), + ) + + results.forEach((res, idx) => { + if (res.status === 'rejected') return + + const { data: account } = res.value + if (!account) return + + const accountId = accountIds[idx] + const { chainId } = fromAccountId(accountId) + + const { hasActivity } = account.accounts.byId[accountId] + + const accountNumberHasChainActivity = !isUtxoChainId(chainId) + ? hasActivity + : // For UTXO AccountIds, we need to check if *any* of the scriptTypes have activity, not only the current one + // else, we might end up with partial account data, with only the first 1 or 2 out of 3 scriptTypes + // being upserted for BTC and LTC + results.some((res, _idx) => { + if (res.status === 'rejected') return false + const { data: account } = res.value + if (!account) return false + const accountId = accountIds[_idx] + const { chainId: _chainId } = fromAccountId(accountId) + if (chainId !== _chainId) return false + return account.accounts.byId[accountId].hasActivity + }) + + // don't add accounts with no activity past account 0 + if (accountNumber > 0 && !accountNumberHasChainActivity) + return delete accountMetadataByAccountId[accountId] + + // unique set to handle utxo chains with multiple account types per account + chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) + + dispatch(portfolio.actions.upsertPortfolio(account)) + }) + + dispatch( + portfolio.actions.upsertAccountMetadata({ + accountMetadataByAccountId, + walletId: await wallet.getDeviceID(), + }), + ) + + return results + }, ) - const accountResults = await Promise.allSettled(accountPromises) + await Promise.allSettled(chainNamespacePromises) let chainIdsWithActivity: string[] = [] - accountResults.forEach((res, idx) => { - if (res.status === 'rejected') return - - const { data: account } = res.value - if (!account) return - - const accountId = accountIds[idx] - const { chainId } = fromAccountId(accountId) - - const { hasActivity } = account.accounts.byId[accountId] - - const accountNumberHasChainActivity = !isUtxoChainId(chainId) - ? hasActivity - : // For UTXO AccountIds, we need to check if *any* of the scriptTypes have activity, not only the current one - // else, we might end up with partial account data, with only the first 1 or 2 out of 3 scriptTypes - // being upserted for BTC and LTC - accountResults.some((res, _idx) => { - if (res.status === 'rejected') return false - const { data: account } = res.value - if (!account) return false - const accountId = accountIds[_idx] - const { chainId: _chainId } = fromAccountId(accountId) - if (chainId !== _chainId) return false - return account.accounts.byId[accountId].hasActivity - }) - - // don't add accounts with no activity past account 0 - if (accountNumber > 0 && !accountNumberHasChainActivity) - return delete accountMetadataByAccountId[accountId] - - // unique set to handle utxo chains with multiple account types per account - chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) - - dispatch(portfolio.actions.upsertPortfolio(account)) - }) chainIds = chainIdsWithActivity } - dispatch( - portfolio.actions.upsertAccountMetadata({ - accountMetadataByAccountId, - walletId: await wallet.getDeviceID(), - }), - ) - for (const accountId of Object.keys(accountMetadataByAccountId)) { dispatch(portfolio.actions.enableAccountId(accountId)) } diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index c113b594b1c..602951fdc61 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -52,6 +52,7 @@ export const selectWalletAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.wallet.byId, (walletId, walletById): AccountId[] => { + console.log({ walletId, walletById }) const walletAccountIds = (walletId && walletById[walletId]) ?? [] return walletAccountIds }, From 3507515f3347f10cc3d7695846fd83b57a9f8355 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:42:44 +0200 Subject: [PATCH 07/22] fix: multi-account derp --- src/context/AppProvider/AppContext.tsx | 4 ++-- src/state/slices/portfolioSlice/portfolioSlice.ts | 2 -- src/state/slices/txHistorySlice/txHistorySlice.ts | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 468a259384d..e4f1636b05d 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -216,6 +216,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { ) } + let chainIdsWithActivity: string[] = [] + // Groups promises by chain namespaces. We need all of them to settle *for a given chain family* to detect activity, at least for UTXOs // Doing the account meta upsertion side effects right after EVM chains / others are settled allow us to immediately start upserting meta, // which in turn will make the app mark said accounts as loaded @@ -279,8 +281,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { await Promise.allSettled(chainNamespacePromises) - let chainIdsWithActivity: string[] = [] - chainIds = chainIdsWithActivity } diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index 945e26ce41e..d25cb940c0b 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -203,8 +203,6 @@ export const portfolioApi = createApi({ }) const account = portfolioAccounts[pubkey] as Account - console.log({ account }) - const assets = (account.chainSpecific.tokens ?? []).reduce( (prev, token) => { const isSpam = [token.name, token.symbol].some(text => { diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 06889532c70..3d47e8e9ed7 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -235,7 +235,6 @@ export const txHistoryApi = createApi({ pubkey, pageSize, }) - console.log({ transactions }) const state = getState() as State const txsById = state.txHistory.txs.byId From eee8fd72a4699c0238d06cebf95db5caebd28537 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:29:39 +0200 Subject: [PATCH 08/22] fix: incremental load --- src/context/AppProvider/AppContext.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index e4f1636b05d..2c77a335cac 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -274,6 +274,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { walletId: await wallet.getDeviceID(), }), ) + for (const accountId of Object.keys(accountMetadataByAccountId)) { + dispatch(portfolio.actions.enableAccountId(accountId)) + } return results }, @@ -283,10 +286,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { chainIds = chainIdsWithActivity } - - for (const accountId of Object.keys(accountMetadataByAccountId)) { - dispatch(portfolio.actions.enableAccountId(accountId)) - } } finally { dispatch(portfolio.actions.setIsAccountMetadataLoading(false)) } From 10189e6c27078d76bbf5eafe5e5747743d49b399 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Thu, 26 Sep 2024 22:47:35 +0200 Subject: [PATCH 09/22] fix: filter out chains without history for account number, and revert back p-queue --- .../src/cosmossdk/CosmosSdkBaseAdapter.ts | 18 ++++++++++------ .../thorchain/ThorchainChainAdapter.ts | 20 +++++++++++------- .../chain-adapters/src/evm/EvmBaseAdapter.ts | 19 +++++++++++------ packages/chain-adapters/src/types.ts | 2 ++ .../src/utxo/UtxoBaseAdapter.ts | 21 ++++++++++++------- src/context/AppProvider/AppContext.tsx | 21 ++++++++++--------- .../slices/txHistorySlice/txHistorySlice.ts | 4 ++++ 7 files changed, 69 insertions(+), 36 deletions(-) diff --git a/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts b/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts index 3ccc056bc4d..6c9ae25acf5 100644 --- a/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts @@ -4,6 +4,7 @@ import type { BIP44Params, CosmosSdkChainId } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import * as unchained from '@shapeshiftoss/unchained-client' import { bech32 } from 'bech32' +import PQueue from 'p-queue' import type { ChainAdapter as IChainAdapter } from '../api' import { ErrorHandler } from '../error/ErrorHandler' @@ -228,14 +229,19 @@ export abstract class CosmosSdkBaseAdapter implement } async getTxHistory(input: TxHistoryInput): Promise { + const requestQueue = input.requestQueue ?? new PQueue() try { - const data = await this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }) + const data = await requestQueue.add(() => + this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }), + ) - const txs = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) + const txs = await Promise.all( + data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), + ) return { cursor: data.cursor, diff --git a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts index 31d2cd79635..20de32274ad 100644 --- a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts @@ -6,6 +6,7 @@ import type { BIP44Params } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import * as unchained from '@shapeshiftoss/unchained-client' import { bech32 } from 'bech32' +import PQueue from 'p-queue' import { ErrorHandler } from '../../error/ErrorHandler' import type { @@ -144,14 +145,19 @@ export class ChainAdapter extends CosmosSdkBaseAdapter { + const requestQueue = input.requestQueue ?? new PQueue() try { - const data = await this.httpV1.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }) - - const txs = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) + const data = await requestQueue.add(() => + this.httpV1.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }), + ) + + const txs = await Promise.all( + data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), + ) return { cursor: data.cursor, diff --git a/packages/chain-adapters/src/evm/EvmBaseAdapter.ts b/packages/chain-adapters/src/evm/EvmBaseAdapter.ts index 52e33916585..e4d031ca04f 100644 --- a/packages/chain-adapters/src/evm/EvmBaseAdapter.ts +++ b/packages/chain-adapters/src/evm/EvmBaseAdapter.ts @@ -22,6 +22,7 @@ import type { BIP44Params, EvmChainId } from '@shapeshiftoss/types' import { KnownChainIds } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' import BigNumber from 'bignumber.js' +import PQueue from 'p-queue' import { isAddress, toHex } from 'viem' import type { ChainAdapter as IChainAdapter } from '../api' @@ -384,13 +385,19 @@ export abstract class EvmBaseAdapter implements IChainAdap } async getTxHistory(input: TxHistoryInput): Promise { - const data = await this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }) + const requestQueue = input.requestQueue ?? new PQueue() + + const data = await requestQueue.add(() => + this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }), + ) - const transactions = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) + const transactions = await Promise.all( + data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), + ) return { cursor: data.cursor ?? '', diff --git a/packages/chain-adapters/src/types.ts b/packages/chain-adapters/src/types.ts index ee97cd378f7..29cc7a7e4fe 100644 --- a/packages/chain-adapters/src/types.ts +++ b/packages/chain-adapters/src/types.ts @@ -13,6 +13,7 @@ import type { UtxoChainId, } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' +import type PQueue from 'p-queue' import type * as cosmossdk from './cosmossdk/types' import type * as evm from './evm/types' @@ -250,6 +251,7 @@ export interface TxHistoryInput { readonly cursor?: string readonly pubkey: string readonly pageSize?: number + readonly requestQueue?: PQueue } export type GetAddressInputBase = { diff --git a/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts b/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts index 9ab164c85c2..44e65a25e67 100644 --- a/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts +++ b/packages/chain-adapters/src/utxo/UtxoBaseAdapter.ts @@ -14,6 +14,7 @@ import type { BIP44Params, UtxoChainId } from '@shapeshiftoss/types' import { KnownChainIds, UtxoAccountType } from '@shapeshiftoss/types' import type * as unchained from '@shapeshiftoss/unchained-client' import WAValidator from 'multicoin-address-validator' +import PQueue from 'p-queue' import type { ChainAdapter as IChainAdapter } from '../api' import { ErrorHandler } from '../error/ErrorHandler' @@ -471,17 +472,23 @@ export abstract class UtxoBaseAdapter implements IChainAd } async getTxHistory(input: TxHistoryInput): Promise { + const requestQueue = input.requestQueue ?? new PQueue() + if (!this.accountAddresses[input.pubkey]) { - await this.getAccount(input.pubkey) + await requestQueue.add(() => this.getAccount(input.pubkey)) } - const data = await this.providers.http.getTxHistory({ - pubkey: input.pubkey, - pageSize: input.pageSize, - cursor: input.cursor, - }) + const data = await requestQueue.add(() => + this.providers.http.getTxHistory({ + pubkey: input.pubkey, + pageSize: input.pageSize, + cursor: input.cursor, + }), + ) - const transactions = await Promise.all(data.txs.map(tx => this.parseTx(tx, input.pubkey))) + const transactions = await Promise.all( + data.txs.map(tx => requestQueue.add(() => this.parseTx(tx, input.pubkey))), + ) return { cursor: data.cursor ?? '', diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 2c77a335cac..64cbb6db388 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -145,9 +145,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // This ensures that we have fresh portfolio data, but accounts added through account management are not accidentally blown away. if (hasManagedAccounts) { requestedAccountIds.forEach(accountId => { - dispatch( - portfolioApi.endpoints.getAccount.initiate({ accountId }, { forceRefetch: true }), - ) + dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId })) }) return @@ -217,6 +215,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } let chainIdsWithActivity: string[] = [] + debugger // Groups promises by chain namespaces. We need all of them to settle *for a given chain family* to detect activity, at least for UTXOs // Doing the account meta upsertion side effects right after EVM chains / others are settled allow us to immediately start upserting meta, @@ -225,9 +224,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { async accountIds => { const results = await Promise.allSettled( accountIds.map(async id => { - const result = await dispatch( - getAccount.initiate({ accountId: id }, { forceRefetch: true }), - ) + const result = await dispatch(getAccount.initiate({ accountId: id })) return result }), ) @@ -259,11 +256,15 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { }) // don't add accounts with no activity past account 0 - if (accountNumber > 0 && !accountNumberHasChainActivity) + if (accountNumber > 0 && !accountNumberHasChainActivity) { + chainIdsWithActivity = chainIdsWithActivity.filter( + _chainId => _chainId !== chainId, + ) return delete accountMetadataByAccountId[accountId] - - // unique set to handle utxo chains with multiple account types per account - chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) + } else { + // unique set to handle utxo chains with multiple account types per account + chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) + } dispatch(portfolio.actions.upsertPortfolio(account)) }) diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 3d47e8e9ed7..92790ba1162 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -5,6 +5,7 @@ import { fromAccountId, isNft, thorchainChainId } from '@shapeshiftoss/caip' import type { ChainAdapter, thorchain, Transaction } from '@shapeshiftoss/chain-adapters' import type { PartialRecord, UtxoAccountType } from '@shapeshiftoss/types' import orderBy from 'lodash/orderBy' +import PQueue from 'p-queue' import { PURGE } from 'redux-persist' import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton' import { deepUpsertArray } from 'lib/utils' @@ -211,6 +212,8 @@ export const txHistoryApi = createApi({ endpoints: build => ({ getAllTxHistory: build.query({ queryFn: async (accountIds, { dispatch, getState }) => { + const requestQueue = new PQueue({ concurrency: 2 }) + await Promise.all( accountIds.map(async accountId => { const { chainId, account: pubkey } = fromAccountId(accountId) @@ -234,6 +237,7 @@ export const txHistoryApi = createApi({ cursor: requestCursor, pubkey, pageSize, + requestQueue, }) const state = getState() as State From da7d40518ecbc27043c61c8e2542fc06196eac4c Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:00:46 +0200 Subject: [PATCH 10/22] feat: improve chunks grouping --- src/context/AppProvider/AppContext.tsx | 150 ++++++++++++------------- 1 file changed, 74 insertions(+), 76 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 64cbb6db388..80089a9a865 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -1,6 +1,6 @@ import { usePrevious, useToast } from '@chakra-ui/react' -import type { AccountId, ChainNamespace } from '@shapeshiftoss/caip' -import { CHAIN_NAMESPACE, fromAccountId, fromChainId } from '@shapeshiftoss/caip' +import type { AccountId, ChainId } from '@shapeshiftoss/caip' +import { fromAccountId } from '@shapeshiftoss/caip' import type { LedgerOpenAppEventArgs } from '@shapeshiftoss/chain-adapters' import { emitter } from '@shapeshiftoss/chain-adapters' import { isLedger } from '@shapeshiftoss/hdwallet-ledger' @@ -189,101 +189,99 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const { getAccount } = portfolioApi.endpoints - const groupAccountIdsByNamespace = ( + // Chunks of AccountIds + // For UTXOs, each chain gets its own chunk to detect chain activity over other scriptTypes + // For others, no need to use chunks, but for the sake of consistency, we keep the same structure + const accountNumberAccountIdChunks = ( _accountIds: AccountId[], - ): Record => { + ): Record => { return _accountIds.reduce( (acc, _accountId) => { const { chainId } = fromAccountId(_accountId) - const namespace = fromChainId(chainId).chainNamespace - // This should not happen but... - if (!acc[namespace]) { - acc[namespace] = [] + if (isUtxoChainId(chainId)) { + acc[chainId] = [_accountId] + return acc } - acc[namespace].push(_accountId) + + if (!acc[chainId]) { + acc[chainId] = [] + } + acc[chainId].push(_accountId) return acc }, - { - [CHAIN_NAMESPACE.Evm]: [], - [CHAIN_NAMESPACE.CosmosSdk]: [], - [CHAIN_NAMESPACE.Solana]: [], - [CHAIN_NAMESPACE.Utxo]: [], - } as Record, + {} as Record, ) } let chainIdsWithActivity: string[] = [] - debugger // Groups promises by chain namespaces. We need all of them to settle *for a given chain family* to detect activity, at least for UTXOs // Doing the account meta upsertion side effects right after EVM chains / others are settled allow us to immediately start upserting meta, // which in turn will make the app mark said accounts as loaded - const chainNamespacePromises = Object.values(groupAccountIdsByNamespace(accountIds)).map( - async accountIds => { - const results = await Promise.allSettled( - accountIds.map(async id => { - const result = await dispatch(getAccount.initiate({ accountId: id })) - return result - }), - ) - - results.forEach((res, idx) => { - if (res.status === 'rejected') return - - const { data: account } = res.value - if (!account) return - - const accountId = accountIds[idx] - const { chainId } = fromAccountId(accountId) - - const { hasActivity } = account.accounts.byId[accountId] - - const accountNumberHasChainActivity = !isUtxoChainId(chainId) - ? hasActivity - : // For UTXO AccountIds, we need to check if *any* of the scriptTypes have activity, not only the current one - // else, we might end up with partial account data, with only the first 1 or 2 out of 3 scriptTypes - // being upserted for BTC and LTC - results.some((res, _idx) => { - if (res.status === 'rejected') return false - const { data: account } = res.value - if (!account) return false - const accountId = accountIds[_idx] - const { chainId: _chainId } = fromAccountId(accountId) - if (chainId !== _chainId) return false - return account.accounts.byId[accountId].hasActivity - }) - - // don't add accounts with no activity past account 0 - if (accountNumber > 0 && !accountNumberHasChainActivity) { - chainIdsWithActivity = chainIdsWithActivity.filter( - _chainId => _chainId !== chainId, - ) - return delete accountMetadataByAccountId[accountId] - } else { - // unique set to handle utxo chains with multiple account types per account - chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) - } + const accountNumberAccountIdsPromises = Object.values( + accountNumberAccountIdChunks(accountIds), + ).map(async accountIds => { + const results = await Promise.allSettled( + accountIds.map(async id => { + const result = await dispatch(getAccount.initiate({ accountId: id })) + return result + }), + ) - dispatch(portfolio.actions.upsertPortfolio(account)) - }) - - dispatch( - portfolio.actions.upsertAccountMetadata({ - accountMetadataByAccountId, - walletId: await wallet.getDeviceID(), - }), - ) - for (const accountId of Object.keys(accountMetadataByAccountId)) { - dispatch(portfolio.actions.enableAccountId(accountId)) + results.forEach((res, idx) => { + if (res.status === 'rejected') return + + const { data: account } = res.value + if (!account) return + + const accountId = accountIds[idx] + const { chainId } = fromAccountId(accountId) + + const { hasActivity } = account.accounts.byId[accountId] + + const accountNumberHasChainActivity = !isUtxoChainId(chainId) + ? hasActivity + : // For UTXO AccountIds, we need to check if *any* of the scriptTypes have activity, not only the current one + // else, we might end up with partial account data, with only the first 1 or 2 out of 3 scriptTypes + // being upserted for BTC and LTC + results.some((res, _idx) => { + if (res.status === 'rejected') return false + const { data: account } = res.value + if (!account) return false + const accountId = accountIds[_idx] + const { chainId: _chainId } = fromAccountId(accountId) + if (chainId !== _chainId) return false + return account.accounts.byId[accountId].hasActivity + }) + + // don't add accounts with no activity past account 0 + if (accountNumber > 0 && !accountNumberHasChainActivity) { + chainIdsWithActivity = chainIdsWithActivity.filter(_chainId => _chainId !== chainId) + return delete accountMetadataByAccountId[accountId] + } else { + // unique set to handle utxo chains with multiple account types per account + chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) } - return results - }, - ) + dispatch(portfolio.actions.upsertPortfolio(account)) + }) + + dispatch( + portfolio.actions.upsertAccountMetadata({ + accountMetadataByAccountId, + walletId: await wallet.getDeviceID(), + }), + ) + for (const accountId of Object.keys(accountMetadataByAccountId)) { + dispatch(portfolio.actions.enableAccountId(accountId)) + } + + return results + }) - await Promise.allSettled(chainNamespacePromises) + await Promise.allSettled(accountNumberAccountIdsPromises) chainIds = chainIdsWithActivity } From b6dc216a43b5a635198fde7caa948330a33c14b4 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:11:16 +0200 Subject: [PATCH 11/22] feat: bring back upsertOnFetch --- src/context/AppProvider/AppContext.tsx | 2 +- src/state/slices/portfolioSlice/portfolioSlice.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 80089a9a865..bbc51dc24b5 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -145,7 +145,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // This ensures that we have fresh portfolio data, but accounts added through account management are not accidentally blown away. if (hasManagedAccounts) { requestedAccountIds.forEach(accountId => { - dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId })) + dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId, upsertOnFetch: true })) }) return diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index d25cb940c0b..eac8e50f3c6 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -128,7 +128,6 @@ export const portfolio = createSlice({ prepare: prepareAutoBatched(), }, upsertPortfolio: (draftState, { payload }: { payload: Portfolio }) => { - console.log('upserting portfolio', { payload }) // upsert all draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) draftState.accounts.ids = Object.keys(draftState.accounts.byId) @@ -174,6 +173,7 @@ export const portfolio = createSlice({ type GetAccountArgs = { accountId: AccountId + upsertOnFetch?: boolean } export const portfolioApi = createApi({ @@ -181,7 +181,7 @@ export const portfolioApi = createApi({ reducerPath: 'portfolioApi', endpoints: build => ({ getAccount: build.query({ - queryFn: async ({ accountId }, { dispatch, getState }) => { + queryFn: async ({ accountId, upsertOnFetch }, { dispatch, getState }) => { if (!accountId) return { data: cloneDeep(initialState) } const state: ReduxState = getState() as any const assetIds = state.assets.ids @@ -326,7 +326,7 @@ export const portfolioApi = createApi({ return accountToPortfolio({ portfolioAccounts, assetIds, nftCollectionsById }) })() - dispatch(portfolio.actions.upsertPortfolio(data)) + upsertOnFetch && dispatch(portfolio.actions.upsertPortfolio(data)) return { data } } catch (e) { console.error(e) From 2b4defc9544a0968cf7f214e777eabb0e41593a7 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:41:01 +0200 Subject: [PATCH 12/22] feat: fetch txHistory *after* all are done --- src/context/AppProvider/AppContext.tsx | 16 ++++------------ src/state/slices/common-selectors.ts | 4 ++-- src/state/slices/txHistorySlice/selectors.ts | 4 ++-- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index bbc51dc24b5..29586edaec4 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -287,6 +287,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } } finally { dispatch(portfolio.actions.setIsAccountMetadataLoading(false)) + // Only fetch and upsert Tx history once all are loaded, otherwise big main thread rug + const { getAllTxHistory } = txHistoryApi.endpoints + + await dispatch(getAllTxHistory.initiate(requestedAccountIds)) } })() }, [ @@ -308,18 +312,6 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { ) }, [dispatch, portfolioLoadingStatus]) - // once portfolio is done loading, fetch all transaction history - useEffect(() => { - ;(async () => { - if (!requestedAccountIds.length) return - if (portfolioLoadingStatus === 'loading') return - - const { getAllTxHistory } = txHistoryApi.endpoints - - await dispatch(getAllTxHistory.initiate(requestedAccountIds)) - })() - }, [dispatch, requestedAccountIds, portfolioLoadingStatus]) - const marketDataPollingInterval = 60 * 15 * 1000 // refetch data every 15 minutes useQueries({ queries: portfolioAssetIds.map(assetId => ({ diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index 602951fdc61..4c572c57677 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -29,7 +29,7 @@ export const selectIsWalletConnected = (state: ReduxState) => state.portfolio.connectedWallet !== undefined export const selectWalletSupportedChainIds = (state: ReduxState) => state.portfolio.connectedWallet?.supportedChainIds ?? [] -export const selectWalletEnabledAccountIds = createDeepEqualOutputSelector( +export const selectEnabledAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.enabledAccountIds, (walletId, enabledAccountIds) => { @@ -41,7 +41,7 @@ export const selectWalletEnabledAccountIds = createDeepEqualOutputSelector( export const selectEnabledWalletAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.wallet.byId, - selectWalletEnabledAccountIds, + selectEnabledAccountIds, (walletId, walletById, enabledAccountIds): AccountId[] => { const walletAccountIds = (walletId && walletById[walletId]) ?? [] return walletAccountIds.filter(accountId => (enabledAccountIds ?? []).includes(accountId)) diff --git a/src/state/slices/txHistorySlice/selectors.ts b/src/state/slices/txHistorySlice/selectors.ts index 41faad969a2..dd86ba54a1c 100644 --- a/src/state/slices/txHistorySlice/selectors.ts +++ b/src/state/slices/txHistorySlice/selectors.ts @@ -27,7 +27,7 @@ import { } from 'state/selectors' import { selectAssets } from '../assetsSlice/selectors' -import { selectEnabledWalletAccountIds, selectWalletEnabledAccountIds } from '../common-selectors' +import { selectEnabledAccountIds, selectEnabledWalletAccountIds } from '../common-selectors' import { selectPortfolioAccountMetadata } from '../portfolioSlice/selectors' import type { Tx, TxId, TxIdsByAccountIdAssetId } from './txHistorySlice' @@ -396,7 +396,7 @@ export const selectIsTxHistoryAvailableByFilter = createCachedSelector( export const selectErroredTxHistoryAccounts = createDeepEqualOutputSelector( (state: ReduxState) => state.txHistory.hydrationMeta, - selectWalletEnabledAccountIds, + selectEnabledAccountIds, (hydrationMeta, walletEnabledAccountIds) => { return Object.entries(hydrationMeta) .filter(([_accountId, hydrationMetaForAccountId]) => hydrationMetaForAccountId?.isErrored) From 27462847b25e78167a0c46265ee247a9020a1306 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:52:58 +0200 Subject: [PATCH 13/22] feat: split getAllTxHistory by AccountIds --- src/components/Modals/Settings/ClearCache.tsx | 4 +- src/context/AppProvider/AppContext.tsx | 6 +- .../slices/txHistorySlice/txHistorySlice.ts | 164 +++++++++--------- 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/src/components/Modals/Settings/ClearCache.tsx b/src/components/Modals/Settings/ClearCache.tsx index 2c78f4c38f8..e8adb588b20 100644 --- a/src/components/Modals/Settings/ClearCache.tsx +++ b/src/components/Modals/Settings/ClearCache.tsx @@ -81,7 +81,9 @@ export const ClearCache = ({ appHistory }: ClearCacheProps) => { const handleClearTxHistory = useCallback(() => { dispatch(txHistory.actions.clear()) dispatch(txHistoryApi.util.resetApiState()) - dispatch(txHistoryApi.endpoints.getAllTxHistory.initiate(requestedAccountIds)) + requestedAccountIds.forEach(requestedAccountId => + dispatch(txHistoryApi.endpoints.getAllTxHistory.initiate(requestedAccountId)), + ) }, [dispatch, requestedAccountIds]) return ( diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 29586edaec4..05798ff180a 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -290,7 +290,11 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // Only fetch and upsert Tx history once all are loaded, otherwise big main thread rug const { getAllTxHistory } = txHistoryApi.endpoints - await dispatch(getAllTxHistory.initiate(requestedAccountIds)) + await Promise.all( + requestedAccountIds.map(requestedAccountId => + dispatch(getAllTxHistory.initiate(requestedAccountId)), + ), + ) } })() }, [ diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 92790ba1162..2203ac0639d 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -206,95 +206,91 @@ export const txHistory = createSlice({ }, }) +// Exported as a paranoia to ensure module-time evaluation as a singleton +export const requestQueue = new PQueue({ concurrency: 2 }) + export const txHistoryApi = createApi({ ...BASE_RTK_CREATE_API_CONFIG, reducerPath: 'txHistoryApi', endpoints: build => ({ - getAllTxHistory: build.query({ - queryFn: async (accountIds, { dispatch, getState }) => { - const requestQueue = new PQueue({ concurrency: 2 }) - - await Promise.all( - accountIds.map(async accountId => { - const { chainId, account: pubkey } = fromAccountId(accountId) - const adapter = getChainAdapterManager().get(chainId) - - if (!adapter) { - const data = `getAllTxHistory: no adapter available for chainId ${chainId}` - return { error: { data, status: 400 } } - } - - const fetch = async (getTxHistoryFns: ChainAdapter['getTxHistory'][]) => { - for await (const getTxHistory of getTxHistoryFns) { - try { - let currentCursor = '' - - do { - const pageSize = 10 - const requestCursor = currentCursor - - const { cursor, transactions } = await getTxHistory({ - cursor: requestCursor, - pubkey, - pageSize, - requestQueue, - }) - - const state = getState() as State - const txsById = state.txHistory.txs.byId - - const hasTx = transactions.some( - tx => !!txsById[serializeTxIndex(accountId, tx.txid, tx.pubkey, tx.data)], - ) - - const results: TransactionsByAccountId = { [accountId]: transactions } - dispatch(txHistory.actions.upsertTxsByAccountId(results)) - - /** - * We have run into a transaction that already exists in the store, stop fetching more history - * - * Two edge cases exist currently: - * - * 1) If there was an error fetching transaction history after at least one page was fetched, - * the user would be missing all transactions thereafter. The next time we fetch tx history, - * we will think we ran into the latest existing transaction in the store and stop fetching. - * This means we will never upsert those missing transactions until the cache is cleared and - * they are successfully fetched again. - * - * We should be able to use state.txHistory.hydrationMetadata[accountId].isErrored to trigger a refetch in this instance. - * - * 2) Cached pending transactions will not be updated to completed if they are older than the - * last page found containing cached transactions. - * - * We can either invalidate tx history and refetch all, or refetch on a per transaction basis any cached pending txs - */ - if (hasTx) return - - currentCursor = cursor - } while (currentCursor) - - // Mark this account as hydrated so downstream can determine the difference between an - // account starting part-way thru a time period and "still hydrating". - dispatch(txHistory.actions.setAccountIdHydrated(accountId)) - } catch (err) { - console.error(err) - dispatch(txHistory.actions.setAccountIdErrored(accountId)) - } - } + getAllTxHistory: build.query({ + queryFn: async (accountId, { dispatch, getState }) => { + const { chainId, account: pubkey } = fromAccountId(accountId) + const adapter = getChainAdapterManager().get(chainId) + + if (!adapter) { + const data = `getAllTxHistory: no adapter available for chainId ${chainId}` + return { error: { data, status: 400 } } + } + + const fetch = async (getTxHistoryFns: ChainAdapter['getTxHistory'][]) => { + for await (const getTxHistory of getTxHistoryFns) { + try { + let currentCursor = '' + + do { + const pageSize = 10 + const requestCursor = currentCursor + + const { cursor, transactions } = await getTxHistory({ + cursor: requestCursor, + pubkey, + pageSize, + requestQueue, + }) + + const state = getState() as State + const txsById = state.txHistory.txs.byId + + const hasTx = transactions.some( + tx => !!txsById[serializeTxIndex(accountId, tx.txid, tx.pubkey, tx.data)], + ) + + const results: TransactionsByAccountId = { [accountId]: transactions } + dispatch(txHistory.actions.upsertTxsByAccountId(results)) + + /** + * We have run into a transaction that already exists in the store, stop fetching more history + * + * Two edge cases exist currently: + * + * 1) If there was an error fetching transaction history after at least one page was fetched, + * the user would be missing all transactions thereafter. The next time we fetch tx history, + * we will think we ran into the latest existing transaction in the store and stop fetching. + * This means we will never upsert those missing transactions until the cache is cleared and + * they are successfully fetched again. + * + * We should be able to use state.txHistory.hydrationMetadata[accountId].isErrored to trigger a refetch in this instance. + * + * 2) Cached pending transactions will not be updated to completed if they are older than the + * last page found containing cached transactions. + * + * We can either invalidate tx history and refetch all, or refetch on a per transaction basis any cached pending txs + */ + if (hasTx) return + + currentCursor = cursor + } while (currentCursor) + + // Mark this account as hydrated so downstream can determine the difference between an + // account starting part-way thru a time period and "still hydrating". + dispatch(txHistory.actions.setAccountIdHydrated(accountId)) + } catch (err) { + console.error(err) + dispatch(txHistory.actions.setAccountIdErrored(accountId)) } - - if (chainId === thorchainChainId) { - // fetch transaction history for both thorchain-1 (mainnet) and thorchain-mainnet-v1 (legacy) - await fetch([ - adapter.getTxHistory.bind(adapter), - (adapter as thorchain.ChainAdapter).getTxHistoryV1.bind(adapter), - ]) - } else { - await fetch([adapter.getTxHistory.bind(adapter)]) - } - }), - ) - + } + } + + if (chainId === thorchainChainId) { + // fetch transaction history for both thorchain-1 (mainnet) and thorchain-mainnet-v1 (legacy) + await fetch([ + adapter.getTxHistory.bind(adapter), + (adapter as thorchain.ChainAdapter).getTxHistoryV1.bind(adapter), + ]) + } else { + await fetch([adapter.getTxHistory.bind(adapter)]) + } return { data: null } }, }), From ed84af5f529f485707d19a4cdbcffba8ff8dd29d Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:44:04 +0200 Subject: [PATCH 14/22] fix: upsert only per-chain accounts --- src/context/AppProvider/AppContext.tsx | 37 ++++++++++++------- .../slices/portfolioSlice/portfolioSlice.ts | 3 ++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 05798ff180a..996f90d3f23 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -153,6 +153,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { if (!wallet || isLedger(wallet)) return + const walletId = await wallet.getDeviceID() + let chainIds = supportedChains.filter(chainId => { return walletSupportsChain({ chainId, @@ -259,25 +261,34 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // don't add accounts with no activity past account 0 if (accountNumber > 0 && !accountNumberHasChainActivity) { chainIdsWithActivity = chainIdsWithActivity.filter(_chainId => _chainId !== chainId) - return delete accountMetadataByAccountId[accountId] + delete accountMetadataByAccountId[accountId] } else { // unique set to handle utxo chains with multiple account types per account chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) - } - dispatch(portfolio.actions.upsertPortfolio(account)) + dispatch(portfolio.actions.upsertPortfolio(account)) + const chainIdAccountMetadata = Object.entries(accountMetadataByAccountId).reduce( + (acc, [accountId, metadata]) => { + const { chainId: _chainId } = fromAccountId(accountId) + if (chainId === _chainId) { + acc[accountId] = metadata + } + return acc + }, + {} as AccountMetadataById, + ) + dispatch( + portfolio.actions.upsertAccountMetadata({ + accountMetadataByAccountId: chainIdAccountMetadata, + walletId, + }), + ) + for (const accountId of Object.keys(accountMetadataByAccountId)) { + dispatch(portfolio.actions.enableAccountId(accountId)) + } + } }) - dispatch( - portfolio.actions.upsertAccountMetadata({ - accountMetadataByAccountId, - walletId: await wallet.getDeviceID(), - }), - ) - for (const accountId of Object.keys(accountMetadataByAccountId)) { - dispatch(portfolio.actions.enableAccountId(accountId)) - } - return results }) diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index eac8e50f3c6..08a50d7d725 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -131,6 +131,9 @@ export const portfolio = createSlice({ // upsert all draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) draftState.accounts.ids = Object.keys(draftState.accounts.byId) + if (draftState.accounts.ids.includes('eip155:1:0xd764ea00bbf008592829a4bc2dfd2538a522e23a')) { + debugger + } draftState.accountBalances.byId = merge( draftState.accountBalances.byId, From 847369c2b88762898043b3a9ca823b3acd8ddf17 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:33:08 +0200 Subject: [PATCH 15/22] chore: rm logs and debugger --- src/pages/Accounts/Accounts.tsx | 1 - src/state/slices/common-selectors.ts | 1 - src/state/slices/portfolioSlice/portfolioSlice.ts | 4 ---- 3 files changed, 6 deletions(-) diff --git a/src/pages/Accounts/Accounts.tsx b/src/pages/Accounts/Accounts.tsx index 15a29524c6d..e618b840460 100644 --- a/src/pages/Accounts/Accounts.tsx +++ b/src/pages/Accounts/Accounts.tsx @@ -106,7 +106,6 @@ export const Accounts = () => { )) }, [blanks]) - console.log({ loading }) const renderRows = useMemo(() => { return loading ? blankRows : chainRows }, [blankRows, chainRows, loading]) diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index 4c572c57677..8fcf02ce22e 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -52,7 +52,6 @@ export const selectWalletAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.wallet.byId, (walletId, walletById): AccountId[] => { - console.log({ walletId, walletById }) const walletAccountIds = (walletId && walletById[walletId]) ?? [] return walletAccountIds }, diff --git a/src/state/slices/portfolioSlice/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index 08a50d7d725..7ddd73a22c2 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -131,10 +131,6 @@ export const portfolio = createSlice({ // upsert all draftState.accounts.byId = merge(draftState.accounts.byId, payload.accounts.byId) draftState.accounts.ids = Object.keys(draftState.accounts.byId) - if (draftState.accounts.ids.includes('eip155:1:0xd764ea00bbf008592829a4bc2dfd2538a522e23a')) { - debugger - } - draftState.accountBalances.byId = merge( draftState.accountBalances.byId, payload.accountBalances.byId, From 84303c33d6dd4b1780e4a6a7a30ed47a757f5df3 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:34:22 +0200 Subject: [PATCH 16/22] feat: revert DegradedStateBanner refetch failed --- src/components/Layout/Header/DegradedStateBanner.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Layout/Header/DegradedStateBanner.tsx b/src/components/Layout/Header/DegradedStateBanner.tsx index 74e237f3ccc..936c5fceecd 100644 --- a/src/components/Layout/Header/DegradedStateBanner.tsx +++ b/src/components/Layout/Header/DegradedStateBanner.tsx @@ -59,7 +59,12 @@ export const DegradedStateBanner = memo(() => { const handleRetry = useCallback(() => { erroredAccountIds.forEach(accountId => - dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId }, { forceRefetch: true })), + dispatch( + portfolioApi.endpoints.getAccount.initiate( + { accountId, upsertOnFetch: true }, + { forceRefetch: true }, + ), + ), ) }, [dispatch, erroredAccountIds]) From 6df58ff36f919fc0516d0cf9b346639e04fe7c95 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:35:30 +0200 Subject: [PATCH 17/22] feat: importAccounts revert upsertOnFetch --- .../ManageAccountsDrawer/components/ImportAccounts.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx index fb217c6767f..10e063e03d3 100644 --- a/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx +++ b/src/components/ManageAccountsDrawer/components/ImportAccounts.tsx @@ -326,7 +326,9 @@ export const ImportAccounts = ({ chainId, onClose }: ImportAccountsProps) => { if (isEnabled) { return } - await dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId })) + await dispatch( + portfolioApi.endpoints.getAccount.initiate({ accountId, upsertOnFetch: true }), + ) }), ) From 150ae0faf5ce0cd93c40f1cae3694698c632bca6 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:40:26 +0200 Subject: [PATCH 18/22] feat: update comment --- src/context/AppProvider/AppContext.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 996f90d3f23..012537510df 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -218,10 +218,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } let chainIdsWithActivity: string[] = [] - - // Groups promises by chain namespaces. We need all of them to settle *for a given chain family* to detect activity, at least for UTXOs - // Doing the account meta upsertion side effects right after EVM chains / others are settled allow us to immediately start upserting meta, - // which in turn will make the app mark said accounts as loaded + // Chunks of AccountIds promises for accountNumber/AccountId combination + // This allows every run of AccountIds per chain/accountNumber to run in parallel vs. all sequentally, so + // we can run each chain's side effects immediately const accountNumberAccountIdsPromises = Object.values( accountNumberAccountIdChunks(accountIds), ).map(async accountIds => { From 5a3b1ae2ce45163b3256aa911641b8801451bb35 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:15:20 +0200 Subject: [PATCH 19/22] feat: rm paranoia --- src/state/slices/txHistorySlice/txHistorySlice.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 2203ac0639d..dcccd7d87b1 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -206,8 +206,7 @@ export const txHistory = createSlice({ }, }) -// Exported as a paranoia to ensure module-time evaluation as a singleton -export const requestQueue = new PQueue({ concurrency: 2 }) +const requestQueue = new PQueue({ concurrency: 2 }) export const txHistoryApi = createApi({ ...BASE_RTK_CREATE_API_CONFIG, From ac172d061701833c55ea5d66ab33ee42e0f7125f Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:17:01 +0200 Subject: [PATCH 20/22] feat: opt-chain selector --- src/state/slices/common-selectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/slices/common-selectors.ts b/src/state/slices/common-selectors.ts index 8fcf02ce22e..3e2b619fa81 100644 --- a/src/state/slices/common-selectors.ts +++ b/src/state/slices/common-selectors.ts @@ -52,7 +52,7 @@ export const selectWalletAccountIds = createDeepEqualOutputSelector( selectWalletId, (state: ReduxState) => state.portfolio.wallet.byId, (walletId, walletById): AccountId[] => { - const walletAccountIds = (walletId && walletById[walletId]) ?? [] + const walletAccountIds = walletById?.[walletId ?? ''] ?? [] return walletAccountIds }, ) From 5c094cc272416ef8132c6ceee0f2b370406c720e Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:21:56 +0200 Subject: [PATCH 21/22] feat: remove chunk terminology and fix grouping of UTXO scriptTypes --- src/context/AppProvider/AppContext.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index 012537510df..e702b8207ec 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -191,21 +191,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const { getAccount } = portfolioApi.endpoints - // Chunks of AccountIds - // For UTXOs, each chain gets its own chunk to detect chain activity over other scriptTypes - // For others, no need to use chunks, but for the sake of consistency, we keep the same structure - const accountNumberAccountIdChunks = ( + const accountNumberAccountIdsByChainId = ( _accountIds: AccountId[], ): Record => { return _accountIds.reduce( (acc, _accountId) => { const { chainId } = fromAccountId(_accountId) - if (isUtxoChainId(chainId)) { - acc[chainId] = [_accountId] - return acc - } - if (!acc[chainId]) { acc[chainId] = [] } @@ -218,11 +210,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { } let chainIdsWithActivity: string[] = [] - // Chunks of AccountIds promises for accountNumber/AccountId combination // This allows every run of AccountIds per chain/accountNumber to run in parallel vs. all sequentally, so - // we can run each chain's side effects immediately + // we can run each item (usually one AccountId, except UTXOs which may contain many because of many scriptTypes) 's side effects immediately const accountNumberAccountIdsPromises = Object.values( - accountNumberAccountIdChunks(accountIds), + accountNumberAccountIdsByChainId(accountIds), ).map(async accountIds => { const results = await Promise.allSettled( accountIds.map(async id => { From 81b6e9b40fc813fcd56b424bd355dfdad66249f1 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:28:45 +0200 Subject: [PATCH 22/22] feat: set over array for cleanliness --- src/context/AppProvider/AppContext.tsx | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index e702b8207ec..8e4d62cf3bc 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -155,19 +155,21 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const walletId = await wallet.getDeviceID() - let chainIds = supportedChains.filter(chainId => { - return walletSupportsChain({ - chainId, - wallet, - isSnapInstalled, - checkConnectedAccountIds: false, // don't check connected account ids, we're detecting runtime support for chains - }) - }) + let chainIds = new Set( + supportedChains.filter(chainId => { + return walletSupportsChain({ + chainId, + wallet, + isSnapInstalled, + checkConnectedAccountIds: false, // don't check connected account ids, we're detecting runtime support for chains + }) + }), + ) const accountMetadataByAccountId: AccountMetadataById = {} const isMultiAccountWallet = wallet.supportsBip44Accounts() const isMetaMaskMultichainWallet = wallet instanceof MetaMaskShapeShiftMultiChainHDWallet - for (let accountNumber = 0; chainIds.length > 0; accountNumber++) { + for (let accountNumber = 0; chainIds.size > 0; accountNumber++) { if ( accountNumber > 0 && // only some wallets support multi account @@ -180,7 +182,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const input = { accountNumber, - chainIds, + chainIds: Array.from(chainIds), wallet, isSnapInstalled: Boolean(isSnapInstalled), } @@ -209,7 +211,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { ) } - let chainIdsWithActivity: string[] = [] + let chainIdsWithActivity: Set = new Set() // This allows every run of AccountIds per chain/accountNumber to run in parallel vs. all sequentally, so // we can run each item (usually one AccountId, except UTXOs which may contain many because of many scriptTypes) 's side effects immediately const accountNumberAccountIdsPromises = Object.values( @@ -250,11 +252,11 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { // don't add accounts with no activity past account 0 if (accountNumber > 0 && !accountNumberHasChainActivity) { - chainIdsWithActivity = chainIdsWithActivity.filter(_chainId => _chainId !== chainId) + chainIdsWithActivity.delete(chainId) delete accountMetadataByAccountId[accountId] } else { - // unique set to handle utxo chains with multiple account types per account - chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) + // handle utxo chains with multiple account types per account + chainIdsWithActivity.add(chainId) dispatch(portfolio.actions.upsertPortfolio(account)) const chainIdAccountMetadata = Object.entries(accountMetadataByAccountId).reduce(