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..e8adb588b20 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() @@ -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/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 5ebc09e7679..8e4d62cf3bc 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -1,4 +1,5 @@ import { usePrevious, useToast } from '@chakra-ui/react' +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' @@ -33,11 +34,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 +60,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() @@ -144,12 +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, upsertOnFetch: true }, - { forceRefetch: true }, - ), - ) + dispatch(portfolioApi.endpoints.getAccount.initiate({ accountId, upsertOnFetch: true })) }) return @@ -157,19 +153,23 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { if (!wallet || isLedger(wallet)) return - 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 - }) - }) + const walletId = await wallet.getDeviceID() + + 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 @@ -182,7 +182,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { const input = { accountNumber, - chainIds, + chainIds: Array.from(chainIds), wallet, isSnapInstalled: Boolean(isSnapInstalled), } @@ -192,64 +192,112 @@ 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 accountResults = await Promise.allSettled(accountPromises) - - 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] + const accountNumberAccountIdsByChainId = ( + _accountIds: AccountId[], + ): Record => { + return _accountIds.reduce( + (acc, _accountId) => { + const { chainId } = fromAccountId(_accountId) + + if (!acc[chainId]) { + acc[chainId] = [] + } + acc[chainId].push(_accountId) + + return acc + }, + {} as Record, + ) + } - // unique set to handle utxo chains with multiple account types per account - chainIdsWithActivity = Array.from(new Set([...chainIdsWithActivity, chainId])) + 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( + accountNumberAccountIdsByChainId(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)) + 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.delete(chainId) + delete accountMetadataByAccountId[accountId] + } else { + // handle utxo chains with multiple account types per account + chainIdsWithActivity.add(chainId) + + 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)) + } + } + }) + + return results }) - chainIds = chainIdsWithActivity - } - - dispatch( - portfolio.actions.upsertAccountMetadata({ - accountMetadataByAccountId, - walletId: await wallet.getDeviceID(), - }), - ) + await Promise.allSettled(accountNumberAccountIdsPromises) - for (const accountId of Object.keys(accountMetadataByAccountId)) { - dispatch(portfolio.actions.enableAccountId(accountId)) + chainIds = chainIdsWithActivity } } 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 Promise.all( + requestedAccountIds.map(requestedAccountId => + dispatch(getAllTxHistory.initiate(requestedAccountId)), + ), + ) } })() }, [ @@ -271,18 +319,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/context/TransactionsProvider/TransactionsProvider.tsx b/src/context/TransactionsProvider/TransactionsProvider.tsx index 878ea4aa5d0..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) @@ -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/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..e618b840460 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( () => 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/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..3e2b619fa81 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) => { @@ -38,23 +38,32 @@ export const selectWalletEnabledAccountIds = createDeepEqualOutputSelector( }, ) -export const selectWalletAccountIds = 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)) }, ) +export const selectWalletAccountIds = createDeepEqualOutputSelector( + selectWalletId, + (state: ReduxState) => state.portfolio.wallet.byId, + (walletId, walletById): AccountId[] => { + const walletAccountIds = 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/portfolioSlice.ts b/src/state/slices/portfolioSlice/portfolioSlice.ts index 2ddec6fe6a8..7ddd73a22c2 100644 --- a/src/state/slices/portfolioSlice/portfolioSlice.ts +++ b/src/state/slices/portfolioSlice/portfolioSlice.ts @@ -127,23 +127,15 @@ export const portfolio = createSlice({ // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs prepare: prepareAutoBatched(), }, - - upsertPortfolio: { - reducer: (draftState, { payload }: { payload: Portfolio }) => { - // 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) - }, - - // Use the `prepareAutoBatched` utility to automatically - // add the `action.meta[SHOULD_AUTOBATCH]` field the enhancer needs - prepare: prepareAutoBatched(), + upsertPortfolio: (draftState, { payload }: { payload: Portfolio }) => { + // 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 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..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 { selectWalletAccountIds, selectWalletEnabledAccountIds } from '../common-selectors' +import { selectEnabledAccountIds, selectEnabledWalletAccountIds } 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) => { @@ -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) diff --git a/src/state/slices/txHistorySlice/txHistorySlice.ts b/src/state/slices/txHistorySlice/txHistorySlice.ts index 92790ba1162..dcccd7d87b1 100644 --- a/src/state/slices/txHistorySlice/txHistorySlice.ts +++ b/src/state/slices/txHistorySlice/txHistorySlice.ts @@ -206,95 +206,90 @@ export const txHistory = createSlice({ }, }) +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 } }, }),