From c107a7511c396d38a177b2dbedf68b86fd3b6969 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 15 Oct 2024 19:18:33 -0400 Subject: [PATCH 01/18] stash --- ios/Podfile.lock | 2 +- package.json | 2 +- src/model/migrations.ts | 37 +++++++++++++++++++++++-- src/resources/assets/UserAssetsQuery.ts | 2 +- src/resources/assets/assets.ts | 24 ---------------- src/state/assets/userAssets.ts | 34 ++++++++++++++++++++--- src/state/sync/UserAssetsSync.tsx | 2 ++ src/utils/ethereumUtils.ts | 8 ++---- 8 files changed, 72 insertions(+), 39 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 10d554bc320..af7eb5f5407 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2525,7 +2525,7 @@ SPEC CHECKSUMS: TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d + Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c PODFILE CHECKSUM: 98c3fc206d7041ac7388693bb0753109d1884b57 diff --git a/package.json b/package.json index 44f1186cef1..2a78840269b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "install-pods-fast": "cd ios && bundle exec pod install && cd ..", "install-pods-fast-jsc": "cd ios && bundle exec env USE_HERMES=NO pod install && cd ..", "install-pods-no-flipper": "cd ios && bundle exec env SKIP_FLIPPER=true pod install --repo-update && cd ..", - "ios": "react-native run-ios --simulator='iPhone 16 Pro'", + "ios": "react-native run-ios --simulator='iPhone 15 Pro'", "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 561500aa58a..8ba8bb0ee38 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -1,6 +1,6 @@ import path from 'path'; import { captureException } from '@sentry/react-native'; -import { findKey, isNumber, keys } from 'lodash'; +import { findKey, isEmpty, isNumber, keys } from 'lodash'; import uniq from 'lodash/uniq'; import RNFS from 'react-native-fs'; import { MMKV } from 'react-native-mmkv'; @@ -38,7 +38,9 @@ import { favoritesQueryKey } from '@/resources/favorites'; import { EthereumAddress, RainbowToken } from '@/entities'; import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps'; -import { getUniqueIdNetwork } from '@/utils/ethereumUtils'; +import { getAddressAndChainIdFromUniqueId, getUniqueId, getUniqueIdNetwork } from '@/utils/ethereumUtils'; +import { userAssetsStore } from '@/state/assets/userAssets'; +import { UniqueId } from '@/__swaps__/types/assets'; export default async function runMigrations() { // get current version @@ -663,6 +665,35 @@ export default async function runMigrations() { migrations.push(v20); + /** + *************** Migration v21 ****************** + * Migrate hidden coins from MMKV to Zustand + */ + const v21 = async () => { + const { wallets } = store.getState().wallets; + if (!wallets) return; + + for (const wallet of Object.values(wallets)) { + for (const { address } of (wallet as RainbowWallet).addresses) { + const hiddenCoins = JSON.parse(mmkv.getString('hidden-coins-obj-' + address) ?? '{}'); + if (isEmpty(hiddenCoins)) continue; + + const hiddenAssets = Object.keys(hiddenCoins).reduce((acc: UniqueId[], key) => { + // we need to run it through this funciton because users could have legacy coins when we had network-based uniqueId + const { address, chainId } = getAddressAndChainIdFromUniqueId(key); + const uniqueId = getUniqueId(address, chainId); + acc.push(uniqueId); + return acc; + }, []); + + console.log(hiddenAssets); + userAssetsStore.getState(address).setHiddenAssets(hiddenAssets); + } + } + }; + + migrations.push(v21); + logger.debug(`[runMigrations]: ready to run migrations starting on number ${currentVersion}`); // await setMigrationVersion(17); if (migrations.length === currentVersion) { @@ -675,6 +706,6 @@ export default async function runMigrations() { // @ts-expect-error await migrations[i].apply(null); logger.debug(`[runMigrations]: Migration ${i} completed succesfully`); - await setMigrationVersion(i + 1); + // await setMigrationVersion(i + 1); } } diff --git a/src/resources/assets/UserAssetsQuery.ts b/src/resources/assets/UserAssetsQuery.ts index c584448a355..e9c345ca8d2 100644 --- a/src/resources/assets/UserAssetsQuery.ts +++ b/src/resources/assets/UserAssetsQuery.ts @@ -150,7 +150,7 @@ const retryErroredChainIds = async ( queryClient.setQueryData(userAssetsQueryKey({ address, currency, connectedToHardhat }), parsedSuccessResults); }; -type UserAssetsResult = QueryFunctionResult; +export type UserAssetsResult = QueryFunctionResult; interface AssetsAndMetadata { erroredChainIds: number[]; diff --git a/src/resources/assets/assets.ts b/src/resources/assets/assets.ts index b6c6b761cf7..f9533d04736 100644 --- a/src/resources/assets/assets.ts +++ b/src/resources/assets/assets.ts @@ -4,10 +4,7 @@ import { MMKV } from 'react-native-mmkv'; import { NativeCurrencyKey, ParsedAddressAsset } from '@/entities'; import { isNativeAsset } from '@/handlers/assets'; import { convertRawAmountToBalance } from '@/helpers/utilities'; -import { BooleanMap } from '@/hooks/useCoinListEditOptions'; import { queryClient } from '@/react-query'; -import { setHiddenCoins } from '@/redux/editOptions'; -import store from '@/redux/store'; import { positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { RainbowPositions } from '@/resources/defi/types'; import { AddysAddressAsset, AddysAsset, ParsedAsset, RainbowAddressAssets } from './types'; @@ -15,8 +12,6 @@ import { getUniqueId } from '@/utils/ethereumUtils'; import { ChainId } from '@/chains/types'; import { chainsIdByName } from '@/chains'; -const storage = new MMKV(); - export const filterPositionsData = ( address: string, currency: NativeCurrencyKey, @@ -83,22 +78,3 @@ export function parseAddressAsset({ assetData }: { assetData: AddysAddressAsset balance: convertRawAmountToBalance(quantity, asset), }; } - -/** - * Adds new hidden coins for an address and updates key-value storage. - * - * @param coins New coin IDs. - * @param address The address to hide coins for. - */ -function addHiddenCoins(coins: string[], address: string) { - const { dispatch } = store; - const storageKey = 'hidden-coins-obj-' + address; - const storageEntity = storage.getString(storageKey); - const list = Object.keys(storageEntity ? JSON.parse(storageEntity) : {}); - const newHiddenCoins = [...list.filter((i: string) => !coins.includes(i)), ...coins].reduce((acc, curr) => { - acc[curr] = true; - return acc; - }, {} as BooleanMap); - dispatch(setHiddenCoins(newHiddenCoins)); - storage.set(storageKey, JSON.stringify(newHiddenCoins)); -} diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 1ac63f8e1de..4bbc1373b94 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -47,6 +47,9 @@ export interface UserAssetsState { setSearchCache: (queryKey: string, filteredIds: UniqueId[]) => void; setSearchQuery: (query: string) => void; setUserAssets: (userAssets: Map | ParsedSearchAsset[]) => void; + + hiddenAssets: Set; + setHiddenAssets: (uniqueIds: UniqueId[]) => void; } // NOTE: We are serializing Map as an Array<[UniqueId, ParsedSearchAsset]> @@ -329,6 +332,26 @@ export const createUserAssetsStore = (address: Address | string) => userAssets: userAssetsMap, }; }), + + hiddenAssets: new Set(), + + setHiddenAssets: (uniqueIds: UniqueId[]) => { + // if the uniqueId was already hidden, remove it from the set + // otherwise, add it to the set + + set(prev => { + const hiddenAssets = new Set(prev.hiddenAssets); + uniqueIds.forEach(uniqueId => { + if (hiddenAssets.has(uniqueId)) { + hiddenAssets.delete(uniqueId); + } else { + hiddenAssets.add(uniqueId); + } + }); + + return { hiddenAssets }; + }); + }, }), { storageKey: `userAssets_${address}`, @@ -353,8 +376,11 @@ function getOrCreateStore(address?: Address | string): UserAssetsStoreType { const { stores } = storeManager.getState(); let store = stores.get(accountAddress); + console.log({ address, store }); + if (!store) { store = createUserAssetsStore(accountAddress); + console.log({ store }); storeManager.setState(state => ({ stores: new Map(state.stores).set(accountAddress, store as UserAssetsStoreType), })); @@ -364,15 +390,15 @@ function getOrCreateStore(address?: Address | string): UserAssetsStoreType { } export const userAssetsStore = { - getState: () => getOrCreateStore().getState(), - setState: (partial: Partial | ((state: UserAssetsState) => Partial)) => - getOrCreateStore().setState(partial), + getState: (address?: Address | string) => getOrCreateStore(address).getState(), + setState: (partial: Partial | ((state: UserAssetsState) => Partial), address?: Address | string) => + getOrCreateStore(address).setState(partial), }; export function useUserAssetsStore(selector: (state: UserAssetsState) => T) { const address = useSelector((state: AppState) => state.settings.accountAddress); const store = getOrCreateStore(address); - return useStore(store, useCallback(selector, [])); + return useStore(store, useCallback(selector, [selector])); } function getCurrentSearchCache(): Map | undefined { diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 0eb9dff87be..1bb3796ebaf 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -11,6 +11,8 @@ export const UserAssetsSync = function UserAssetsSync() { const isSwapsOpen = useSwapsStore(state => state.isSwapsOpen); + console.log('accountAddress', accountAddress); + useUserAssets( { address: accountAddress, diff --git a/src/utils/ethereumUtils.ts b/src/utils/ethereumUtils.ts index 89549ce63f2..74e8b7ad2b4 100644 --- a/src/utils/ethereumUtils.ts +++ b/src/utils/ethereumUtils.ts @@ -452,12 +452,10 @@ export const getAddressAndChainIdFromUniqueId = (uniqueId: string): { address: A return { address: parts[0] as AddressOrEth, chainId: ChainId.mainnet }; } - // If the unique ID contains '_', the last part is the network and the rest is the address - const network = parts[1] as Network; // Assuming the last part is a valid Network enum value - const address = parts[0] as AddressOrEth; - const chainId = chainsIdByName[network]; + const [address, chainIdOrNetwork] = parts; + const chainId = isNaN(+chainIdOrNetwork) ? chainsIdByName[chainIdOrNetwork] : +chainIdOrNetwork; - return { address, chainId }; + return { address: address as AddressOrEth, chainId }; }; const calculateL1FeeOptimism = async ( From 28e17a3c0aa34f31c0c13d5f136e224f595415d0 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 15 Oct 2024 21:28:38 -0400 Subject: [PATCH 02/18] fix up hiding and unhiding assets --- .../FastComponents/FastBalanceCoinRow.tsx | 8 ++-- .../core/RawRecyclerList.tsx | 5 +-- .../core/useMemoBriefSectionData.ts | 17 +++------ .../chart/ChartContextButton.js | 9 +++-- src/helpers/assets.ts | 19 ++++++---- src/helpers/buildWalletSections.tsx | 8 ++-- src/hooks/useCoinListEditOptions.ts | 37 +++++++------------ src/hooks/useWalletSectionsData.ts | 9 +++-- src/model/migrations.ts | 6 +-- src/state/assets/userAssets.ts | 26 +++++++++---- src/state/sync/UserAssetsSync.tsx | 2 - 11 files changed, 74 insertions(+), 72 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index e597de4bf30..03060d5a728 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -13,6 +13,7 @@ import { borders, colors, padding, shadow } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { NativeCurrencyKey } from '@/entities'; import { ChainId } from '@/chains/types'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; interface CoinCheckButtonProps { isHidden: boolean; @@ -144,8 +145,7 @@ const MemoizedBalanceCoinRow = React.memo( MemoizedBalanceCoinRow.displayName = 'MemoizedBalanceCoinRow'; export default React.memo(function BalanceCoinRow({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) { - const { theme, nativeCurrencySymbol, navigate, nativeCurrency, hiddenCoins, pinnedCoins, toggleSelectedCoin, isCoinListEdited } = - extendedState; + const { theme, nativeCurrencySymbol, navigate, nativeCurrency, pinnedCoins, toggleSelectedCoin, isCoinListEdited } = extendedState; const onPress = useCallback(() => { toggleSelectedCoin(uniqueId); @@ -156,7 +156,9 @@ export default React.memo(function BalanceCoinRow({ uniqueId, extendedState }: { const maybeCallback = useRef void)>(null); maybeCallback.current = isCoinListEdited ? onPress : null; - const isHidden = hiddenCoins[uniqueId]; + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + + const isHidden = hiddenAssets.has(uniqueId); const isPinned = pinnedCoins[uniqueId]; return ( diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index ffdfcf3cc6a..4a54fafcccf 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -34,7 +34,6 @@ export type ExtendedState = { nativeCurrency: NativeCurrencyKey; navigate: any; isCoinListEdited: boolean; - hiddenCoins: BooleanMap; pinnedCoins: BooleanMap; toggleSelectedCoin: (id: string) => void; setIsCoinListEdited: SetterOrUpdater; @@ -129,14 +128,13 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ const theme = useTheme(); const { nativeCurrencySymbol, nativeCurrency } = useAccountSettings(); - const { hiddenCoinsObj: hiddenCoins, pinnedCoinsObj: pinnedCoins, toggleSelectedCoin } = useCoinListEditOptions(); + const { pinnedCoinsObj: pinnedCoins, toggleSelectedCoin } = useCoinListEditOptions(); const { navigate } = useNavigation(); const mergedExtendedState = useMemo(() => { return { ...extendedState, - hiddenCoins, isCoinListEdited, nativeCurrency, nativeCurrencySymbol, @@ -153,7 +151,6 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ nativeCurrencySymbol, nativeCurrency, pinnedCoins, - hiddenCoins, toggleSelectedCoin, isCoinListEdited, setIsCoinListEdited, diff --git a/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts b/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts index 4ef83d223a9..abe1847a674 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts +++ b/src/components/asset-list/RecyclerAssetList2/core/useMemoBriefSectionData.ts @@ -2,17 +2,10 @@ import { useMemo } from 'react'; import { useDeepCompareMemo } from 'use-deep-compare'; import { AssetListType } from '..'; import { CellType, CoinExtraData, NFTFamilyExtraData } from './ViewTypes'; -import { - useCoinListEdited, - useCoinListEditOptions, - useExternalWalletSectionsData, - useOpenFamilies, - useOpenSmallBalances, - useWalletSectionsData, -} from '@/hooks'; +import { useCoinListEdited, useExternalWalletSectionsData, useOpenFamilies, useOpenSmallBalances, useWalletSectionsData } from '@/hooks'; import useOpenPositionCards from '@/hooks/useOpenPositionCards'; -import * as ls from '@/storage'; import useOpenClaimables from '@/hooks/useOpenClaimables'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; const FILTER_TYPES = { 'ens-profile': [CellType.NFT_SPACE_AFTER, CellType.NFT, CellType.FAMILY_HEADER], @@ -50,7 +43,7 @@ export default function useMemoBriefSectionData({ const { isPositionCardsOpen } = useOpenPositionCards(); const { isClaimablesOpen } = useOpenClaimables(); const { isCoinListEdited } = useCoinListEdited(); - const { hiddenCoinsObj } = useCoinListEditOptions(); + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); const { openFamilies } = useOpenFamilies(); const result = useMemo(() => { @@ -81,7 +74,7 @@ export default function useMemoBriefSectionData({ if (data.type === CellType.COIN && !isSmallBalancesOpen && !isCoinListEdited && afterDivider) { return false; } - if (data.type === CellType.COIN && hiddenCoinsObj[(data as CoinExtraData).uniqueId] && !isCoinListEdited) { + if (data.type === CellType.COIN && hiddenAssets.has((data as CoinExtraData).uniqueId) && !isCoinListEdited) { return false; } @@ -120,7 +113,7 @@ export default function useMemoBriefSectionData({ return { type: cellType, uid }; }); return briefSectionsDataFiltered; - }, [sectionsDataToUse, type, isCoinListEdited, isSmallBalancesOpen, hiddenCoinsObj, isPositionCardsOpen, isClaimablesOpen, openFamilies]); + }, [sectionsDataToUse, type, isCoinListEdited, isSmallBalancesOpen, hiddenAssets, isPositionCardsOpen, isClaimablesOpen, openFamilies]); const memoizedResult = useDeepCompareMemo(() => result, [result]); const additionalData = useDeepCompareMemo( () => diff --git a/src/components/expanded-state/chart/ChartContextButton.js b/src/components/expanded-state/chart/ChartContextButton.js index aa596fee7c4..b4cf2760135 100644 --- a/src/components/expanded-state/chart/ChartContextButton.js +++ b/src/components/expanded-state/chart/ChartContextButton.js @@ -5,13 +5,16 @@ import { ContextCircleButton } from '../../context-menu'; import EditAction from '@/helpers/EditAction'; import { useCoinListEditOptions, useCoinListFinishEditingOptions } from '@/hooks'; import { ethereumUtils } from '@/utils'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; +import { getUniqueId } from '@/utils/ethereumUtils'; const emojiSpacing = ios ? '' : ' '; export default function ChartContextButton({ asset, color }) { const { clearSelectedCoins, pushSelectedCoin } = useCoinListEditOptions(); + const setHiddenAssets = useUserAssetsStore(state => state.setHiddenAssets); - const { currentAction, setHiddenCoins, setPinnedCoins } = useCoinListFinishEditingOptions(); + const { currentAction, setPinnedCoins } = useCoinListFinishEditingOptions(); useEffect(() => { // Ensure this expanded state's asset is always actively inside @@ -29,13 +32,13 @@ export default function ChartContextButton({ asset, color }) { setPinnedCoins(); } else if (buttonIndex === 1) { // 🙈️ Hide - setHiddenCoins(); + setHiddenAssets([getUniqueId(asset.address, asset.chainId)]); } else if (buttonIndex === 2 && !asset?.isNativeAsset) { // 🔍 View on Etherscan ethereumUtils.openTokenEtherscanURL({ address: asset?.address, chainId: asset?.chainId }); } }, - [asset?.address, asset?.isNativeAsset, asset?.chainId, setHiddenCoins, setPinnedCoins] + [asset?.address, asset?.isNativeAsset, asset?.chainId, setHiddenAssets, setPinnedCoins] ); const options = useMemo( diff --git a/src/helpers/assets.ts b/src/helpers/assets.ts index b6cf424d7e8..d34eddb7c76 100644 --- a/src/helpers/assets.ts +++ b/src/helpers/assets.ts @@ -7,6 +7,7 @@ import { getUniqueTokenFormat, getUniqueTokenType } from '@/utils'; import * as i18n from '@/languages'; import { UniqueAsset } from '@/entities'; import { NftCollectionSortCriterion } from '@/graphql/__generated__/arc'; +import { UniqueId } from '@/__swaps__/types/assets'; const COINS_TO_SHOW = 5; @@ -25,7 +26,13 @@ const getTotal = (assets: any) => return add(acc, balance); }, 0); -export const buildCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinListEdited: any, pinnedCoins: any, hiddenCoins: any) => { +export const buildCoinsList = ( + sortedAssets: any, + nativeCurrency: any, + isCoinListEdited: any, + pinnedCoins: any, + hiddenCoins: Set +) => { if (!sortedAssets.length) { return { assets: [], @@ -41,7 +48,7 @@ export const buildCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinLis // separate into standard, pinned, small balances, hidden assets sortedAssets?.forEach((asset: any) => { - if (!!hiddenCoins && hiddenCoins[asset.uniqueId]) { + if (hiddenCoins.has(asset.uniqueId)) { hiddenAssets.push({ isCoin: true, isHidden: true, @@ -117,13 +124,13 @@ export const buildCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinLis }; // TODO make it better -export const buildBriefCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinListEdited: any, pinnedCoins: any, hiddenCoins: any) => { +export const buildBriefCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinListEdited: any, pinnedCoins: any, hiddenAssets: any) => { const { assets, smallBalancesValue, totalBalancesValue } = buildCoinsList( sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, - hiddenCoins + hiddenAssets ); const briefAssets = []; if (assets) { @@ -156,10 +163,6 @@ export const buildBriefCoinsList = (sortedAssets: any, nativeCurrency: any, isCo return { briefAssets, totalBalancesValue }; }; -interface Dictionary { - [index: string]: T; -} - export const buildUniqueTokenList = (uniqueTokens: any, selectedShowcaseTokens: any[] = []) => { let rows: any = []; const showcaseTokens = []; diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx index 833b154fad7..03bebf2684c 100644 --- a/src/helpers/buildWalletSections.tsx +++ b/src/helpers/buildWalletSections.tsx @@ -41,7 +41,7 @@ const ONLY_NFTS_CONTENT = [{ type: 'ETH_CARD', uid: 'eth-card' }]; const sortedAssetsSelector = (state: any) => state.sortedAssets; const accountBalanceDisplaySelector = (state: any) => state.accountBalanceDisplay; -const hiddenCoinsSelector = (state: any) => state.hiddenCoins; +const hiddenAssetsSelector = (state: any) => state.hiddenAssets; const isCoinListEditedSelector = (state: any) => state.isCoinListEdited; const isLoadingUserAssetsSelector = (state: any) => state.isLoadingUserAssets; const isLoadingBalanceSelector = (state: any) => state.isLoadingBalance; @@ -162,10 +162,10 @@ const withBriefBalanceSection = ( nativeCurrency: NativeCurrencyKey, isCoinListEdited: boolean, pinnedCoins: any, - hiddenCoins: any, + hiddenAssets: any, collectibles: any ) => { - const { briefAssets } = buildBriefCoinsList(sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, hiddenCoins); + const { briefAssets } = buildBriefCoinsList(sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, hiddenAssets); const hasTokens = briefAssets?.length; const hasNFTs = collectibles?.length; @@ -275,7 +275,7 @@ const briefBalanceSectionSelector = createSelector( nativeCurrencySelector, isCoinListEditedSelector, pinnedCoinsSelector, - hiddenCoinsSelector, + hiddenAssetsSelector, uniqueTokensSelector, ], withBriefBalanceSection diff --git a/src/hooks/useCoinListEditOptions.ts b/src/hooks/useCoinListEditOptions.ts index 26202ab1921..e7a4e16f16c 100644 --- a/src/hooks/useCoinListEditOptions.ts +++ b/src/hooks/useCoinListEditOptions.ts @@ -1,11 +1,10 @@ import { difference } from 'lodash'; import { useCallback, useMemo, useRef } from 'react'; import { useMMKVObject } from 'react-native-mmkv'; -import { useDispatch } from 'react-redux'; import { atom, useRecoilState, useSetRecoilState } from 'recoil'; import useAccountSettings from './useAccountSettings'; import EditAction from '@/helpers/EditAction'; -import { setHiddenCoins as reduxSetHiddenCoins } from '@/redux/editOptions'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; const selectedItemsAtom = atom({ default: [], @@ -20,7 +19,6 @@ export default function useCoinListEditOptions() { const { accountAddress } = useAccountSettings(); const setSelectedItems = useSetRecoilState(selectedItemsAtom); - const [hiddenCoins = {}] = useMMKVObject('hidden-coins-obj-' + accountAddress); const [pinnedCoins = {}] = useMMKVObject('pinned-coins-obj-' + accountAddress); const pushSelectedCoin = useCallback( @@ -49,7 +47,6 @@ export default function useCoinListEditOptions() { return { clearSelectedCoins, - hiddenCoinsObj: hiddenCoins, pinnedCoinsObj: pinnedCoins, pushSelectedCoin, removeSelectedCoin, @@ -59,13 +56,13 @@ export default function useCoinListEditOptions() { export function useCoinListFinishEditingOptions() { const { accountAddress } = useAccountSettings(); + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + const setHiddenAssets = useUserAssetsStore(state => state.setHiddenAssets); const [selectedItems, setSelectedItems] = useRecoilState(selectedItemsAtom); const selectedItemsNonReactive = useRef(); selectedItemsNonReactive.current = selectedItems; - const [hiddenCoins = {}, setHiddenCoinsObject] = useMMKVObject('hidden-coins-obj-' + accountAddress); - const [pinnedCoins = {}, setPinnedCoinsObject] = useMMKVObject('pinned-coins-obj-' + accountAddress); const currentAction = useMemo(() => { @@ -75,7 +72,7 @@ export function useCoinListFinishEditingOptions() { return EditAction.none; } else if ( newSelectedCoinsLength > 0 && - difference(Object.keys(hiddenCoins), selectedItems).length === Object.keys(hiddenCoins).length - newSelectedCoinsLength + difference(Array.from(hiddenAssets), selectedItems).length === hiddenAssets.size - newSelectedCoinsLength ) { return EditAction.unhide; } else if ( @@ -86,7 +83,7 @@ export function useCoinListFinishEditingOptions() { } else { return EditAction.standard; } - }, [hiddenCoins, pinnedCoins, selectedItems]); + }, [hiddenAssets, pinnedCoins, selectedItems]); const currentActionNonReactive = useRef(); currentActionNonReactive.current = currentAction; @@ -114,24 +111,18 @@ export function useCoinListFinishEditingOptions() { setSelectedItems([]); }, [setSelectedItems, setPinnedCoinsObject]); - const dispatch = useDispatch(); - const setHiddenCoins = useCallback(() => { - setHiddenCoinsObject((hiddenCoins: BooleanMap | undefined) => { - const safeHiddenCoins = hiddenCoins ?? {}; - const newList = [ - ...Object.keys(safeHiddenCoins).filter(i => !selectedItemsNonReactive.current?.includes(i)), - ...(currentActionNonReactive.current === EditAction.standard ? selectedItemsNonReactive.current || [] : []), - ].reduce((acc, curr) => { - acc[curr] = true; - return acc; - }, {} as BooleanMap); - dispatch(reduxSetHiddenCoins(newList)); - return newList; - }); + if ( + !currentActionNonReactive.current || + currentActionNonReactive.current === EditAction.none || + currentActionNonReactive.current === EditAction.unpin + ) + return; + + setHiddenAssets([...(selectedItemsNonReactive.current || [])]); setSelectedItems([]); - }, [dispatch, setSelectedItems, setHiddenCoinsObject]); + }, [setHiddenAssets, setSelectedItems]); return { currentAction, diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index abd0653cb88..d9584576a6a 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -16,6 +16,7 @@ import { useRemoteConfig } from '@/model/remoteConfig'; import { usePositions } from '@/resources/defi/PositionsQuery'; import { useClaimables } from '@/resources/addys/claimables/query'; import { useExperimentalConfig } from '@/config/experimentalHooks'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; export default function useWalletSectionsData({ type, @@ -52,13 +53,15 @@ export default function useWalletSectionsData({ const remoteConfig = useRemoteConfig(); const experimentalConfig = useExperimentalConfig(); - const { hiddenCoinsObj: hiddenCoins, pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + + const { pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); const { isCoinListEdited } = useCoinListEdited(); const walletSections = useMemo(() => { const accountInfo = { - hiddenCoins, + hiddenAssets, isCoinListEdited, isLoadingUserAssets, language, @@ -96,7 +99,7 @@ export default function useWalletSectionsData({ briefSectionsData, }; }, [ - hiddenCoins, + hiddenAssets, isCoinListEdited, isLoadingUserAssets, language, diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 8ba8bb0ee38..ce465d6f936 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -39,8 +39,9 @@ import { EthereumAddress, RainbowToken } from '@/entities'; import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps'; import { getAddressAndChainIdFromUniqueId, getUniqueId, getUniqueIdNetwork } from '@/utils/ethereumUtils'; -import { userAssetsStore } from '@/state/assets/userAssets'; +// import { userAssetsStore } from '@/state/assets/userAssets'; import { UniqueId } from '@/__swaps__/types/assets'; +import { userAssetsStore } from '@/state/assets/userAssets'; export default async function runMigrations() { // get current version @@ -686,7 +687,6 @@ export default async function runMigrations() { return acc; }, []); - console.log(hiddenAssets); userAssetsStore.getState(address).setHiddenAssets(hiddenAssets); } } @@ -706,6 +706,6 @@ export default async function runMigrations() { // @ts-expect-error await migrations[i].apply(null); logger.debug(`[runMigrations]: Migration ${i} completed succesfully`); - // await setMigrationVersion(i + 1); + await setMigrationVersion(i + 1); } } diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 4bbc1373b94..e051ce1247c 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -53,10 +53,11 @@ export interface UserAssetsState { } // NOTE: We are serializing Map as an Array<[UniqueId, ParsedSearchAsset]> -type UserAssetsStateWithTransforms = Omit, 'chainBalances' | 'idsByChain' | 'userAssets'> & { +type UserAssetsStateWithTransforms = Omit, 'chainBalances' | 'idsByChain' | 'userAssets' | 'hiddenAssets'> & { chainBalances: Array<[ChainId, number]>; idsByChain: Array<[UserAssetFilter, UniqueId[]]>; userAssets: Array<[UniqueId, ParsedSearchAsset]>; + hiddenAssets: UniqueId[]; }; function serializeUserAssetsState(state: Partial, version?: number) { @@ -66,6 +67,7 @@ function serializeUserAssetsState(state: Partial, version?: num chainBalances: state.chainBalances ? Array.from(state.chainBalances.entries()) : [], idsByChain: state.idsByChain ? Array.from(state.idsByChain.entries()) : [], userAssets: state.userAssets ? Array.from(state.userAssets.entries()) : [], + hiddenAssets: state.hiddenAssets ? Array.from(state.hiddenAssets.values()) : [], }; return JSON.stringify({ @@ -116,12 +118,22 @@ function deserializeUserAssetsState(serializedState: string) { logger.error(new RainbowError(`[userAssetsStore]: Failed to convert userAssets from user assets storage`), { error }); } + let hiddenAssets = new Set(); + try { + if (state.hiddenAssets) { + hiddenAssets = new Set(state.hiddenAssets); + } + } catch (error) { + logger.error(new RainbowError(`[userAssetsStore]: Failed to convert hiddenAssets from user assets storage`), { error }); + } + return { state: { ...state, chainBalances, idsByChain, userAssets: userAssetsData, + hiddenAssets, }, version, }; @@ -336,19 +348,22 @@ export const createUserAssetsStore = (address: Address | string) => hiddenAssets: new Set(), setHiddenAssets: (uniqueIds: UniqueId[]) => { - // if the uniqueId was already hidden, remove it from the set - // otherwise, add it to the set - set(prev => { const hiddenAssets = new Set(prev.hiddenAssets); + console.log('Current hidden assets:', Array.from(hiddenAssets)); + console.log('Setting hidden assets:', uniqueIds); + uniqueIds.forEach(uniqueId => { if (hiddenAssets.has(uniqueId)) { + console.log('Removing hidden asset:', uniqueId); hiddenAssets.delete(uniqueId); } else { + console.log('Adding hidden asset:', uniqueId); hiddenAssets.add(uniqueId); } }); + console.log('Updated hidden assets:', Array.from(hiddenAssets)); return { hiddenAssets }; }); }, @@ -376,11 +391,8 @@ function getOrCreateStore(address?: Address | string): UserAssetsStoreType { const { stores } = storeManager.getState(); let store = stores.get(accountAddress); - console.log({ address, store }); - if (!store) { store = createUserAssetsStore(accountAddress); - console.log({ store }); storeManager.setState(state => ({ stores: new Map(state.stores).set(accountAddress, store as UserAssetsStoreType), })); diff --git a/src/state/sync/UserAssetsSync.tsx b/src/state/sync/UserAssetsSync.tsx index 1bb3796ebaf..0eb9dff87be 100644 --- a/src/state/sync/UserAssetsSync.tsx +++ b/src/state/sync/UserAssetsSync.tsx @@ -11,8 +11,6 @@ export const UserAssetsSync = function UserAssetsSync() { const isSwapsOpen = useSwapsStore(state => state.isSwapsOpen); - console.log('accountAddress', accountAddress); - useUserAssets( { address: accountAddress, From e5029ef22239bbf310fc73dd8fea521ff55e257c Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 15 Oct 2024 21:29:51 -0400 Subject: [PATCH 03/18] change back to 16 pro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a78840269b..44f1186cef1 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "install-pods-fast": "cd ios && bundle exec pod install && cd ..", "install-pods-fast-jsc": "cd ios && bundle exec env USE_HERMES=NO pod install && cd ..", "install-pods-no-flipper": "cd ios && bundle exec env SKIP_FLIPPER=true pod install --repo-update && cd ..", - "ios": "react-native run-ios --simulator='iPhone 15 Pro'", + "ios": "react-native run-ios --simulator='iPhone 16 Pro'", "format": "prettier --write .", "format:check": "prettier --check .", "lint": "yarn format:check && yarn lint:ts && yarn lint:js", From 616b37a7cf54dafc6df92253e2a93c6dd5f1f051 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 15 Oct 2024 21:32:51 -0400 Subject: [PATCH 04/18] rm commented out line --- src/model/migrations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/model/migrations.ts b/src/model/migrations.ts index ce465d6f936..046cff5e38a 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -39,7 +39,6 @@ import { EthereumAddress, RainbowToken } from '@/entities'; import { standardizeUrl, useFavoriteDappsStore } from '@/state/browser/favoriteDappsStore'; import { useLegacyFavoriteDappsStore } from '@/state/legacyFavoriteDapps'; import { getAddressAndChainIdFromUniqueId, getUniqueId, getUniqueIdNetwork } from '@/utils/ethereumUtils'; -// import { userAssetsStore } from '@/state/assets/userAssets'; import { UniqueId } from '@/__swaps__/types/assets'; import { userAssetsStore } from '@/state/assets/userAssets'; From 0135c4bfc825bb7fd4c35949d3dc897511ada796 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Tue, 15 Oct 2024 21:33:27 -0400 Subject: [PATCH 05/18] revert podlock --- ios/Podfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index af7eb5f5407..10d554bc320 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2525,7 +2525,7 @@ SPEC CHECKSUMS: TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 VisionCamera: 2af28201c3de77245f8c58b7a5274d5979df70df - Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c + Yoga: 88480008ccacea6301ff7bf58726e27a72931c8d PODFILE CHECKSUM: 98c3fc206d7041ac7388693bb0753109d1884b57 From c720f4314b7e1f4cfe7945b0a29dca8c89ae829a Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 10:38:19 -0400 Subject: [PATCH 06/18] add in balance calculation --- src/components/change-wallet/AddressRow.tsx | 25 ++++- src/hooks/useWalletBalances.ts | 32 +++++- src/hooks/useWalletSectionsData.ts | 5 +- src/hooks/useWalletsHiddenBalances.ts | 106 ++++++++++++++++++++ src/hooks/useWalletsWithBalancesAndNames.ts | 18 ++-- src/screens/SendSheet.tsx | 6 +- src/state/assets/userAssets.ts | 11 +- 7 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 src/hooks/useWalletsHiddenBalances.ts diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index a67479f22a6..d56ab9c1cdb 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -23,6 +23,8 @@ import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { useForegroundColor } from '@/design-system'; +import { convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; +import { useAccountSettings } from '@/hooks'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; const NOOP = () => undefined; @@ -119,14 +121,33 @@ interface AddressRowProps { export default function AddressRow({ contextMenuActions, data, editMode, onPress }: AddressRowProps) { const notificationsEnabled = useExperimentalFlag(NOTIFICATIONS); + const { nativeCurrency } = useAccountSettings(); - const { address, balances, color: accountColor, ens, image: accountImage, isSelected, isReadOnly, isLedger, label, walletId } = data; + const { + address, + balances, + hiddenBalances, + color: accountColor, + ens, + image: accountImage, + isSelected, + isReadOnly, + isLedger, + label, + walletId, + } = data; const { colors, isDarkMode } = useTheme(); const labelQuaternary = useForegroundColor('labelQuaternary'); - const balanceText = balances ? balances.totalBalanceDisplay : lang.t('wallet.change_wallet.loading_balance'); + const balanceText = useMemo(() => { + if (!balances) { + return lang.t('wallet.change_wallet.loading_balance'); + } + + return convertAmountToNativeDisplay(subtract(balances.totalBalanceAmount, hiddenBalances), nativeCurrency); + }, [balances, hiddenBalances, nativeCurrency]); const cleanedUpLabel = useMemo(() => removeFirstEmojiFromString(label), [label]); diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 695c4faaa92..03b516b0f92 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -6,8 +6,11 @@ import { useAddysSummary } from '@/resources/summary/summary'; import { useQueries } from '@tanstack/react-query'; import { fetchPositions, positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { RainbowPositions } from '@/resources/defi/types'; -import { add, convertAmountToNativeDisplay } from '@/helpers/utilities'; +import { add, convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; import { queryClient } from '@/react-query'; +import { userAssetsStore } from '@/state/assets/userAssets'; +import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery'; +import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes'; const QUERY_CONFIG = { staleTime: 1000 * 60 * 2, // 2 minutes @@ -28,6 +31,32 @@ export type WalletBalanceResult = { isLoading: boolean; }; +const getHiddenAssetBalance = ({ + address, + nativeCurrency, + connectedToHardhat, +}: { + address: Address; + nativeCurrency: NativeCurrencyKey; + connectedToHardhat: boolean; +}) => { + const { hiddenAssets } = userAssetsStore.getState(address); + const assetData = queryClient.getQueryData( + userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) + ); + + return Array.from(hiddenAssets).reduce((acc, uniqueId) => { + const asset = assetData?.[uniqueId]; + let sum = acc; + if (asset) { + const priceUnit = asset.price?.value ?? 0; + const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); + sum = add(sum, nativeDisplay.amount); + } + return sum; + }, '0'); +}; + const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); @@ -63,7 +92,6 @@ const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { for (const address of allAddresses) { const lowerCaseAddress = address.toLowerCase() as Address; const assetBalance = summaryData?.data?.addresses?.[lowerCaseAddress]?.summary?.asset_value?.toString() || '0'; - const positionData = queryClient.getQueryData(positionsQueryKey({ address, currency: nativeCurrency })); const positionsBalance = positionData?.totals?.total?.amount || '0'; const totalAccountBalance = add(assetBalance, positionsBalance); diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index d9584576a6a..f994a0775be 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -17,6 +17,7 @@ import { usePositions } from '@/resources/defi/PositionsQuery'; import { useClaimables } from '@/resources/addys/claimables/query'; import { useExperimentalConfig } from '@/config/experimentalHooks'; import { useUserAssetsStore } from '@/state/assets/userAssets'; +import { convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; export default function useWalletSectionsData({ type, @@ -54,6 +55,8 @@ export default function useWalletSectionsData({ const experimentalConfig = useExperimentalConfig(); const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + const hiddenBalance = accountWithBalance?.hiddenBalances ?? '0'; + const totalBalance = accountWithBalance?.balances?.totalBalanceAmount ?? '0'; const { pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); @@ -70,7 +73,7 @@ export default function useWalletSectionsData({ pinnedCoins, sendableUniqueTokens, sortedAssets, - accountBalanceDisplay: accountWithBalance?.balances?.totalBalanceDisplay, + accountBalanceDisplay: convertAmountToNativeDisplay(subtract(totalBalance, hiddenBalance), nativeCurrency), isLoadingBalance: !accountWithBalance?.balances, // @ts-expect-error ts-migrate(2698) FIXME: Spread types may only be created from object types... Remove this comment to see the full error message ...isWalletEthZero, diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts new file mode 100644 index 00000000000..811ecf242bd --- /dev/null +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -0,0 +1,106 @@ +import { AllRainbowWallets } from '@/model/wallet'; +import { useMemo, useState, useEffect, useCallback } from 'react'; +import { Address } from 'viem'; +import useAccountSettings from './useAccountSettings'; +import { useConnectedToHardhatStore } from '@/state/connectedToHardhat'; +import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes'; +import { userAssetsStore } from '@/state/assets/userAssets'; +import { queryClient } from '@/react-query'; +import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery'; +import { convertAmountAndPriceToNativeDisplay, add } from '@/helpers/utilities'; +import { isEqual } from 'lodash'; + +export type WalletBalanceResult = { + hiddenBalances: Record; +}; + +const getHiddenAssetBalance = ({ + address, + nativeCurrency, + connectedToHardhat, +}: { + address: Address; + nativeCurrency: NativeCurrencyKey; + connectedToHardhat: boolean; +}) => { + const { hiddenAssets } = userAssetsStore.getState(address); + const assetData = queryClient.getQueryData( + userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) + ); + + return Array.from(hiddenAssets).reduce((acc, uniqueId) => { + const asset = assetData?.[uniqueId]; + let sum = acc; + if (asset) { + const priceUnit = asset.price?.value ?? 0; + const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); + sum = add(sum, nativeDisplay.amount); + } + return sum; + }, '0'); +}; + +const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { + const { nativeCurrency } = useAccountSettings(); + const connectedToHardhat = useConnectedToHardhatStore(state => state.connectedToHardhat); + const [hiddenBalances, setHiddenBalances] = useState>({}); + + const allAddresses = useMemo( + () => Object.values(wallets).flatMap(wallet => (wallet.addresses || []).map(account => account.address as Address)), + [wallets] + ); + + const calculateHiddenBalanceForAddress = useCallback( + (address: Address) => { + const lowerCaseAddress = address.toLowerCase() as Address; + const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat }); + + console.log('calculateHiddenBalanceForAddress', address, hiddenAssetBalance); + setHiddenBalances(prev => ({ + ...prev, + [lowerCaseAddress]: hiddenAssetBalance, + })); + }, + [nativeCurrency, connectedToHardhat] + ); + + useEffect(() => { + allAddresses.forEach(calculateHiddenBalanceForAddress); + }, [allAddresses, calculateHiddenBalanceForAddress]); + + // TODO: This is not working as intended. .subscribe() does not trigger when the value changes. + useEffect(() => { + console.log('Setting up subscriptions for addresses:', allAddresses); + const subscriptions = allAddresses.map(address => { + console.log('Setting up subscription for address:', address); + return userAssetsStore.subscribe( + state => { + console.log('Current state for address:', address, state.hiddenAssets); + return { hiddenAssets: state.hiddenAssets }; + }, + (newState, oldState) => { + console.log('Checking for changes:', address, oldState, newState); + if (!isEqual(oldState.hiddenAssets, newState.hiddenAssets)) { + console.log('Detected change in user hidden assets for address:', address); + calculateHiddenBalanceForAddress(address); + } + }, + { + equalityFn: (a, b) => isEqual(a.hiddenAssets, b.hiddenAssets), + fireImmediately: true, + } + ); + }); + + return () => { + console.log('Cleaning up subscriptions'); + subscriptions.forEach(sub => sub()); + }; + }, [allAddresses, calculateHiddenBalanceForAddress]); + + return { + hiddenBalances, + }; +}; + +export default useWalletsHiddenBalances; diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index 51ec2a03b12..306cff9e6f5 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -1,24 +1,30 @@ import mapValues from 'lodash/mapValues'; import { useMemo } from 'react'; import useWalletBalances from './useWalletBalances'; +import useWalletsHiddenBalances from './useWalletsHiddenBalances'; import useWallets from './useWallets'; import { Address } from 'viem'; export default function useWalletsWithBalancesAndNames() { const { walletNames, wallets } = useWallets(); const { balances } = useWalletBalances(wallets || {}); + const { hiddenBalances } = useWalletsHiddenBalances(wallets || {}); const walletsWithBalancesAndNames = useMemo( () => mapValues(wallets, wallet => { - const updatedAccounts = (wallet.addresses || []).map(account => ({ - ...account, - balances: balances[account.address.toLowerCase() as Address], - ens: walletNames[account.address], - })); + const updatedAccounts = (wallet.addresses || []).map(account => { + const lowerCaseAddress = account.address.toLowerCase() as Address; + return { + ...account, + balances: balances[lowerCaseAddress], + hiddenBalances: hiddenBalances[lowerCaseAddress], + ens: walletNames[account.address], + }; + }); return { ...wallet, addresses: updatedAccounts }; }), - [balances, walletNames, wallets] + [balances, hiddenBalances, walletNames, wallets] ); return walletsWithBalancesAndNames; diff --git a/src/screens/SendSheet.tsx b/src/screens/SendSheet.tsx index 5ad35e5c58c..980f04c43b8 100644 --- a/src/screens/SendSheet.tsx +++ b/src/screens/SendSheet.tsx @@ -70,6 +70,7 @@ import { RootStackParamList } from '@/navigation/types'; import { ThemeContextProps, useTheme } from '@/theme'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { Contact } from '@/redux/contacts'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; const sheetHeight = deviceUtils.dimensions.height - (IS_ANDROID ? 30 : 10); const statusBarHeight = IS_IOS ? safeAreaInsetValues.top : StatusBar.currentHeight; @@ -143,7 +144,8 @@ export default function SendSheet() { step: REGISTRATION_STEPS.TRANSFER, }); - const { hiddenCoinsObj, pinnedCoinsObj } = useCoinListEditOptions(); + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + const { pinnedCoinsObj } = useCoinListEditOptions(); const [toAddress, setToAddress] = useState(''); const [amountDetails, setAmountDetails] = useState({ assetAmount: '', @@ -933,7 +935,7 @@ export default function SendSheet() { {showAssetList && (!isEmptyWallet ? ( getOrCreateStore(address).getState(), setState: (partial: Partial | ((state: UserAssetsState) => Partial), address?: Address | string) => getOrCreateStore(address).setState(partial), + subscribe: ( + selector: (state: UserAssetsState) => Partial, + listener: (state: UserAssetsState, prevState: UserAssetsState) => void, + options?: { + equalityFn?: (a: UserAssetsState, b: UserAssetsState) => boolean; + fireImmediately?: boolean; + }, + address?: Address | string + ) => getOrCreateStore(address).subscribe(selector, listener, options), }; export function useUserAssetsStore(selector: (state: UserAssetsState) => T) { const address = useSelector((state: AppState) => state.settings.accountAddress); const store = getOrCreateStore(address); - return useStore(store, useCallback(selector, [selector])); + return useStore(store, selector); } function getCurrentSearchCache(): Map | undefined { From d856900859691b71ce202ddbebf3f0414b97289d Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 10:48:59 -0400 Subject: [PATCH 07/18] revert a few changes --- .../FastComponents/FastBalanceCoinRow.tsx | 5 ++- .../core/RawRecyclerList.tsx | 16 +++++++--- src/helpers/assets.ts | 8 ++++- src/helpers/buildWalletSections.tsx | 3 +- src/hooks/useWalletBalances.ts | 31 +------------------ src/state/assets/userAssets.ts | 2 +- 6 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 03060d5a728..2ea13954a44 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -145,7 +145,8 @@ const MemoizedBalanceCoinRow = React.memo( MemoizedBalanceCoinRow.displayName = 'MemoizedBalanceCoinRow'; export default React.memo(function BalanceCoinRow({ uniqueId, extendedState }: { uniqueId: string; extendedState: ExtendedState }) { - const { theme, nativeCurrencySymbol, navigate, nativeCurrency, pinnedCoins, toggleSelectedCoin, isCoinListEdited } = extendedState; + const { theme, nativeCurrencySymbol, navigate, nativeCurrency, hiddenAssets, pinnedCoins, toggleSelectedCoin, isCoinListEdited } = + extendedState; const onPress = useCallback(() => { toggleSelectedCoin(uniqueId); @@ -156,8 +157,6 @@ export default React.memo(function BalanceCoinRow({ uniqueId, extendedState }: { const maybeCallback = useRef void)>(null); maybeCallback.current = isCoinListEdited ? onPress : null; - const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); - const isHidden = hiddenAssets.has(uniqueId); const isPinned = pinnedCoins[uniqueId]; diff --git a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx index 4a54fafcccf..833fb1e3d31 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RawRecyclerList.tsx @@ -23,6 +23,8 @@ import { useRoute } from '@react-navigation/native'; import Routes from '@/navigation/routesNames'; import { useRemoteConfig } from '@/model/remoteConfig'; import { useExperimentalConfig } from '@/config/experimentalHooks'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; +import { UniqueId } from '@/__swaps__/types/assets'; const dataProvider = new DataProvider((r1, r2) => { return r1.uid !== r2.uid; @@ -34,6 +36,7 @@ export type ExtendedState = { nativeCurrency: NativeCurrencyKey; navigate: any; isCoinListEdited: boolean; + hiddenAssets: Set; pinnedCoins: BooleanMap; toggleSelectedCoin: (id: string) => void; setIsCoinListEdited: SetterOrUpdater; @@ -60,6 +63,7 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ const currentDataProvider = useMemoOne(() => dataProvider.cloneWithRows(briefSectionsData), [briefSectionsData]); const { isCoinListEdited, setIsCoinListEdited } = useCoinListEdited(); const y = useRecyclerAssetListPosition()!; + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); const { name } = useRoute(); const getCardIdsForScreen = remoteCardsStore(state => state.getCardIdsForScreen); @@ -139,6 +143,7 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ nativeCurrency, nativeCurrencySymbol, navigate, + hiddenAssets, pinnedCoins, setIsCoinListEdited, theme, @@ -146,14 +151,15 @@ const RawMemoRecyclerAssetList = React.memo(function RawRecyclerAssetList({ }; }, [ extendedState, - theme, - navigate, - nativeCurrencySymbol, + isCoinListEdited, nativeCurrency, + nativeCurrencySymbol, + navigate, + hiddenAssets, pinnedCoins, - toggleSelectedCoin, - isCoinListEdited, setIsCoinListEdited, + theme, + toggleSelectedCoin, ]); return ( diff --git a/src/helpers/assets.ts b/src/helpers/assets.ts index d34eddb7c76..a1f62aeda2b 100644 --- a/src/helpers/assets.ts +++ b/src/helpers/assets.ts @@ -124,7 +124,13 @@ export const buildCoinsList = ( }; // TODO make it better -export const buildBriefCoinsList = (sortedAssets: any, nativeCurrency: any, isCoinListEdited: any, pinnedCoins: any, hiddenAssets: any) => { +export const buildBriefCoinsList = ( + sortedAssets: any, + nativeCurrency: any, + isCoinListEdited: any, + pinnedCoins: any, + hiddenAssets: Set +) => { const { assets, smallBalancesValue, totalBalancesValue } = buildCoinsList( sortedAssets, nativeCurrency, diff --git a/src/helpers/buildWalletSections.tsx b/src/helpers/buildWalletSections.tsx index 03bebf2684c..3fd216c7c27 100644 --- a/src/helpers/buildWalletSections.tsx +++ b/src/helpers/buildWalletSections.tsx @@ -9,6 +9,7 @@ import { Claimable } from '@/resources/addys/claimables/types'; import { add, convertAmountToNativeDisplay } from './utilities'; import { RainbowConfig } from '@/model/remoteConfig'; import { IS_TEST } from '@/env'; +import { UniqueId } from '@/__swaps__/types/assets'; const CONTENT_PLACEHOLDER = [ { type: 'LOADING_ASSETS', uid: 'loadings-asset-1' }, @@ -162,7 +163,7 @@ const withBriefBalanceSection = ( nativeCurrency: NativeCurrencyKey, isCoinListEdited: boolean, pinnedCoins: any, - hiddenAssets: any, + hiddenAssets: Set, collectibles: any ) => { const { briefAssets } = buildBriefCoinsList(sortedAssets, nativeCurrency, isCoinListEdited, pinnedCoins, hiddenAssets); diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 03b516b0f92..20f30e9d93b 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -6,11 +6,8 @@ import { useAddysSummary } from '@/resources/summary/summary'; import { useQueries } from '@tanstack/react-query'; import { fetchPositions, positionsQueryKey } from '@/resources/defi/PositionsQuery'; import { RainbowPositions } from '@/resources/defi/types'; -import { add, convertAmountAndPriceToNativeDisplay, convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; +import { add, convertAmountToNativeDisplay } from '@/helpers/utilities'; import { queryClient } from '@/react-query'; -import { userAssetsStore } from '@/state/assets/userAssets'; -import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery'; -import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes'; const QUERY_CONFIG = { staleTime: 1000 * 60 * 2, // 2 minutes @@ -31,32 +28,6 @@ export type WalletBalanceResult = { isLoading: boolean; }; -const getHiddenAssetBalance = ({ - address, - nativeCurrency, - connectedToHardhat, -}: { - address: Address; - nativeCurrency: NativeCurrencyKey; - connectedToHardhat: boolean; -}) => { - const { hiddenAssets } = userAssetsStore.getState(address); - const assetData = queryClient.getQueryData( - userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) - ); - - return Array.from(hiddenAssets).reduce((acc, uniqueId) => { - const asset = assetData?.[uniqueId]; - let sum = acc; - if (asset) { - const priceUnit = asset.price?.value ?? 0; - const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); - sum = add(sum, nativeDisplay.amount); - } - return sum; - }, '0'); -}; - const useWalletBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { const { nativeCurrency } = useAccountSettings(); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 1d5a8aca9a0..37634b63063 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -370,7 +370,7 @@ export const createUserAssetsStore = (address: Address | string) => }), { storageKey: `userAssets_${address}`, - version: 0, + version: 1, serializer: serializeUserAssetsState, deserializer: deserializeUserAssetsState, } From ce35df6176b5d05c78804eb34ba7dd3f8a8bff4b Mon Sep 17 00:00:00 2001 From: Ben Goldberg Date: Wed, 16 Oct 2024 11:44:26 -0400 Subject: [PATCH 08/18] fix userAssetsStore subscription --- src/hooks/useWalletsHiddenBalances.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 811ecf242bd..b7e8ff8bbaf 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -88,7 +88,8 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu { equalityFn: (a, b) => isEqual(a.hiddenAssets, b.hiddenAssets), fireImmediately: true, - } + }, + address ); }); From 3d6eb2c3148f95a4f6aa06357e59f584752a5045 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 13:06:40 -0400 Subject: [PATCH 09/18] clean up --- src/hooks/useWalletsHiddenBalances.ts | 71 ++++++++++++--------- src/hooks/useWalletsWithBalancesAndNames.ts | 2 +- src/state/assets/userAssets.ts | 6 -- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index b7e8ff8bbaf..420d2f46ba8 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -7,8 +7,8 @@ import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes'; import { userAssetsStore } from '@/state/assets/userAssets'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery'; -import { convertAmountAndPriceToNativeDisplay, add } from '@/helpers/utilities'; -import { isEqual } from 'lodash'; +import { convertAmountAndPriceToNativeDisplay, add, isEqual } from '@/helpers/utilities'; +import { isEqual as _isEqual } from 'lodash'; export type WalletBalanceResult = { hiddenBalances: Record; @@ -18,17 +18,18 @@ const getHiddenAssetBalance = ({ address, nativeCurrency, connectedToHardhat, + data, }: { address: Address; nativeCurrency: NativeCurrencyKey; connectedToHardhat: boolean; + data?: UserAssetsResult; }) => { const { hiddenAssets } = userAssetsStore.getState(address); - const assetData = queryClient.getQueryData( - userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) - ); + const assetData = + data ?? queryClient.getQueryData(userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat })); - return Array.from(hiddenAssets).reduce((acc, uniqueId) => { + const balance = Array.from(hiddenAssets).reduce((acc, uniqueId) => { const asset = assetData?.[uniqueId]; let sum = acc; if (asset) { @@ -38,6 +39,8 @@ const getHiddenAssetBalance = ({ } return sum; }, '0'); + + return balance; }; const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResult => { @@ -51,42 +54,52 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu ); const calculateHiddenBalanceForAddress = useCallback( - (address: Address) => { + (address: Address, data?: UserAssetsResult) => { const lowerCaseAddress = address.toLowerCase() as Address; - const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat }); + const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat, data }); - console.log('calculateHiddenBalanceForAddress', address, hiddenAssetBalance); - setHiddenBalances(prev => ({ - ...prev, - [lowerCaseAddress]: hiddenAssetBalance, - })); + setHiddenBalances(prev => { + const newBalance = hiddenAssetBalance; + if (!prev[lowerCaseAddress] || !isEqual(prev[lowerCaseAddress], newBalance)) { + return { + ...prev, + [lowerCaseAddress]: newBalance, + }; + } + return prev; + }); }, [nativeCurrency, connectedToHardhat] ); useEffect(() => { - allAddresses.forEach(calculateHiddenBalanceForAddress); + allAddresses.forEach(address => { + calculateHiddenBalanceForAddress(address); + }); }, [allAddresses, calculateHiddenBalanceForAddress]); - // TODO: This is not working as intended. .subscribe() does not trigger when the value changes. useEffect(() => { - console.log('Setting up subscriptions for addresses:', allAddresses); + const assetSubscriptions = allAddresses.map(address => { + return queryClient.getQueryCache().subscribe(event => { + if ( + _isEqual(event.query.queryKey, userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat })) && + event.query.isStale() + ) { + calculateHiddenBalanceForAddress(address, event.query.state.data); + } + }); + }); + const subscriptions = allAddresses.map(address => { - console.log('Setting up subscription for address:', address); return userAssetsStore.subscribe( - state => { - console.log('Current state for address:', address, state.hiddenAssets); - return { hiddenAssets: state.hiddenAssets }; - }, + state => ({ hiddenAssets: state.hiddenAssets }), (newState, oldState) => { - console.log('Checking for changes:', address, oldState, newState); - if (!isEqual(oldState.hiddenAssets, newState.hiddenAssets)) { - console.log('Detected change in user hidden assets for address:', address); + if (!_isEqual(oldState.hiddenAssets, newState.hiddenAssets)) { calculateHiddenBalanceForAddress(address); } }, { - equalityFn: (a, b) => isEqual(a.hiddenAssets, b.hiddenAssets), + equalityFn: (a, b) => _isEqual(a.hiddenAssets, b.hiddenAssets), fireImmediately: true, }, address @@ -94,14 +107,12 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu }); return () => { - console.log('Cleaning up subscriptions'); subscriptions.forEach(sub => sub()); + assetSubscriptions.forEach(sub => sub()); }; - }, [allAddresses, calculateHiddenBalanceForAddress]); + }, [allAddresses, calculateHiddenBalanceForAddress, connectedToHardhat, nativeCurrency]); - return { - hiddenBalances, - }; + return { hiddenBalances }; }; export default useWalletsHiddenBalances; diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index 306cff9e6f5..952dbfdeec0 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -18,7 +18,7 @@ export default function useWalletsWithBalancesAndNames() { return { ...account, balances: balances[lowerCaseAddress], - hiddenBalances: hiddenBalances[lowerCaseAddress], + hiddenBalances: hiddenBalances[lowerCaseAddress] ?? '0', ens: walletNames[account.address], }; }); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 37634b63063..73fbec81941 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -350,20 +350,14 @@ export const createUserAssetsStore = (address: Address | string) => setHiddenAssets: (uniqueIds: UniqueId[]) => { set(prev => { const hiddenAssets = new Set(prev.hiddenAssets); - console.log('Current hidden assets:', Array.from(hiddenAssets)); - console.log('Setting hidden assets:', uniqueIds); - uniqueIds.forEach(uniqueId => { if (hiddenAssets.has(uniqueId)) { - console.log('Removing hidden asset:', uniqueId); hiddenAssets.delete(uniqueId); } else { - console.log('Adding hidden asset:', uniqueId); hiddenAssets.add(uniqueId); } }); - console.log('Updated hidden assets:', Array.from(hiddenAssets)); return { hiddenAssets }; }); }, From b1e51d9bd2aa8f073f31a6dfeb4d546d6ebdbd7b Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 13:23:21 -0400 Subject: [PATCH 10/18] remove old storage once we migrate --- .../RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx | 1 - src/model/migrations.ts | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 2ea13954a44..4aea3213bd9 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -13,7 +13,6 @@ import { borders, colors, padding, shadow } from '@/styles'; import RainbowCoinIcon from '@/components/coin-icon/RainbowCoinIcon'; import { NativeCurrencyKey } from '@/entities'; import { ChainId } from '@/chains/types'; -import { useUserAssetsStore } from '@/state/assets/userAssets'; interface CoinCheckButtonProps { isHidden: boolean; diff --git a/src/model/migrations.ts b/src/model/migrations.ts index 046cff5e38a..c4d2c706db7 100644 --- a/src/model/migrations.ts +++ b/src/model/migrations.ts @@ -687,6 +687,9 @@ export default async function runMigrations() { }, []); userAssetsStore.getState(address).setHiddenAssets(hiddenAssets); + + // remove the old hidden coins obj storage + mmkv.delete('hidden-coins-obj-' + address); } } }; From e13f1a1db8f12c90aba555c4c2aa5c30951edba1 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 14:16:08 -0400 Subject: [PATCH 11/18] fix lint --- src/hooks/useSwappableUserAssets.ts | 5 +++-- src/hooks/useWalletsHiddenBalances.ts | 2 +- src/state/assets/userAssets.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hooks/useSwappableUserAssets.ts b/src/hooks/useSwappableUserAssets.ts index e1d501fec48..4b6fff4f15e 100644 --- a/src/hooks/useSwappableUserAssets.ts +++ b/src/hooks/useSwappableUserAssets.ts @@ -7,6 +7,7 @@ import { EthereumAddress, ETH_ADDRESS as ETH_ADDRESS_AGGREGATORS } from '@rainbo import { useCallback, useEffect, useMemo, useRef } from 'react'; import { ChainId } from '@/chains/types'; import { supportedSwapChainIds } from '@/chains'; +import { useUserAssetsStore } from '@/state/assets/userAssets'; type SwappableAddresses = Record; @@ -14,7 +15,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset const { outputCurrency } = params; const { data: sortedAssets } = useSortedUserAssets(); const assetsInWallet = sortedAssets as SwappableAsset[]; - const { hiddenCoinsObj } = useCoinListEditOptions(); + const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); const swappableAssetsRef = useRef( supportedSwapChainIds.reduce((acc, chainId) => { @@ -25,7 +26,7 @@ export const useSwappableUserAssets = (params: { outputCurrency: SwappableAsset const filteredAssetsInWallet = (assetsInWallet || []).filter(asset => { // filter out hidden tokens - if (hiddenCoinsObj[asset.uniqueId]) return true; + if (hiddenAssets.has(asset.uniqueId)) return true; // filter out networks where swaps are not enabled if (supportedSwapChainIds.includes(asset.chainId)) return true; diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 420d2f46ba8..721b59d18b9 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -92,7 +92,7 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu const subscriptions = allAddresses.map(address => { return userAssetsStore.subscribe( - state => ({ hiddenAssets: state.hiddenAssets }), + state => state, (newState, oldState) => { if (!_isEqual(oldState.hiddenAssets, newState.hiddenAssets)) { calculateHiddenBalanceForAddress(address); diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 73fbec81941..7f96f49ede6 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -400,7 +400,7 @@ export const userAssetsStore = { setState: (partial: Partial | ((state: UserAssetsState) => Partial), address?: Address | string) => getOrCreateStore(address).setState(partial), subscribe: ( - selector: (state: UserAssetsState) => Partial, + selector: (state: UserAssetsState) => UserAssetsState, listener: (state: UserAssetsState, prevState: UserAssetsState) => void, options?: { equalityFn?: (a: UserAssetsState, b: UserAssetsState) => boolean; From 9258a0f889001afe8390f93fa7d94c6aa18f7837 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 14:59:48 -0400 Subject: [PATCH 12/18] subscribe to query cache once and use address from arg --- src/hooks/useWalletBalances.ts | 3 ++- src/hooks/useWalletsHiddenBalances.ts | 27 +++++++++++---------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/hooks/useWalletBalances.ts b/src/hooks/useWalletBalances.ts index 20f30e9d93b..401efec777f 100644 --- a/src/hooks/useWalletBalances.ts +++ b/src/hooks/useWalletBalances.ts @@ -10,8 +10,9 @@ import { add, convertAmountToNativeDisplay } from '@/helpers/utilities'; import { queryClient } from '@/react-query'; const QUERY_CONFIG = { - staleTime: 1000 * 60 * 2, // 2 minutes + staleTime: 60_000, // 1 minute cacheTime: 1000 * 60 * 60 * 24, // 24 hours + refetchInterval: 120_000, // 2 minutes }; export type WalletBalance = { diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 721b59d18b9..0e0ff38cf36 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -18,16 +18,15 @@ const getHiddenAssetBalance = ({ address, nativeCurrency, connectedToHardhat, - data, }: { address: Address; nativeCurrency: NativeCurrencyKey; connectedToHardhat: boolean; - data?: UserAssetsResult; }) => { const { hiddenAssets } = userAssetsStore.getState(address); - const assetData = - data ?? queryClient.getQueryData(userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat })); + const assetData = queryClient.getQueryData( + userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) + ); const balance = Array.from(hiddenAssets).reduce((acc, uniqueId) => { const asset = assetData?.[uniqueId]; @@ -54,9 +53,9 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu ); const calculateHiddenBalanceForAddress = useCallback( - (address: Address, data?: UserAssetsResult) => { + (address: Address) => { const lowerCaseAddress = address.toLowerCase() as Address; - const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat, data }); + const hiddenAssetBalance = getHiddenAssetBalance({ address, nativeCurrency, connectedToHardhat }); setHiddenBalances(prev => { const newBalance = hiddenAssetBalance; @@ -79,15 +78,11 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu }, [allAddresses, calculateHiddenBalanceForAddress]); useEffect(() => { - const assetSubscriptions = allAddresses.map(address => { - return queryClient.getQueryCache().subscribe(event => { - if ( - _isEqual(event.query.queryKey, userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat })) && - event.query.isStale() - ) { - calculateHiddenBalanceForAddress(address, event.query.state.data); - } - }); + const unsubscribeFromQueryCache = queryClient.getQueryCache().subscribe(event => { + const [args, key] = event.query.queryKey; + if (key === 'userAssets') { + calculateHiddenBalanceForAddress(args.address); + } }); const subscriptions = allAddresses.map(address => { @@ -108,7 +103,7 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu return () => { subscriptions.forEach(sub => sub()); - assetSubscriptions.forEach(sub => sub()); + unsubscribeFromQueryCache(); }; }, [allAddresses, calculateHiddenBalanceForAddress, connectedToHardhat, nativeCurrency]); From 4ec8a5f7382063f0f4684b87d4b8851da8171ba3 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 15:06:05 -0400 Subject: [PATCH 13/18] add array of hidden assets to user assets store --- src/hooks/useCoinListEditOptions.ts | 4 ++-- src/hooks/useWalletsHiddenBalances.ts | 4 ++-- src/state/assets/userAssets.ts | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hooks/useCoinListEditOptions.ts b/src/hooks/useCoinListEditOptions.ts index e7a4e16f16c..c153ede8a0b 100644 --- a/src/hooks/useCoinListEditOptions.ts +++ b/src/hooks/useCoinListEditOptions.ts @@ -56,7 +56,7 @@ export default function useCoinListEditOptions() { export function useCoinListFinishEditingOptions() { const { accountAddress } = useAccountSettings(); - const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); + const hiddenAssets = useUserAssetsStore(state => state.getHiddenAssetsIds()); const setHiddenAssets = useUserAssetsStore(state => state.setHiddenAssets); const [selectedItems, setSelectedItems] = useRecoilState(selectedItemsAtom); @@ -72,7 +72,7 @@ export function useCoinListFinishEditingOptions() { return EditAction.none; } else if ( newSelectedCoinsLength > 0 && - difference(Array.from(hiddenAssets), selectedItems).length === hiddenAssets.size - newSelectedCoinsLength + difference(hiddenAssets, selectedItems).length === hiddenAssets.length - newSelectedCoinsLength ) { return EditAction.unhide; } else if ( diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 0e0ff38cf36..7b30e6b17e3 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -23,12 +23,12 @@ const getHiddenAssetBalance = ({ nativeCurrency: NativeCurrencyKey; connectedToHardhat: boolean; }) => { - const { hiddenAssets } = userAssetsStore.getState(address); + const hiddenAssetIds = userAssetsStore.getState(address).getHiddenAssetsIds(); const assetData = queryClient.getQueryData( userAssetsQueryKey({ address, currency: nativeCurrency, connectedToHardhat }) ); - const balance = Array.from(hiddenAssets).reduce((acc, uniqueId) => { + const balance = hiddenAssetIds.reduce((acc, uniqueId) => { const asset = assetData?.[uniqueId]; let sum = acc; if (asset) { diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 7f96f49ede6..3bafb1e052b 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -49,6 +49,7 @@ export interface UserAssetsState { setUserAssets: (userAssets: Map | ParsedSearchAsset[]) => void; hiddenAssets: Set; + getHiddenAssetsIds: () => UniqueId[]; setHiddenAssets: (uniqueIds: UniqueId[]) => void; } @@ -347,6 +348,8 @@ export const createUserAssetsStore = (address: Address | string) => hiddenAssets: new Set(), + getHiddenAssetsIds: () => Array.from(get().hiddenAssets), + setHiddenAssets: (uniqueIds: UniqueId[]) => { set(prev => { const hiddenAssets = new Set(prev.hiddenAssets); From f63b7f27b7b428d43966f6bc1b8cb5572ef545e2 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Wed, 16 Oct 2024 16:49:55 -0400 Subject: [PATCH 14/18] add dapp browser control panel and consolidate conversion to happen once in the wallet data instead of elsewhere --- .../control-panel/ControlPanel.tsx | 4 +++- src/components/change-wallet/AddressRow.tsx | 21 +++---------------- src/hooks/useWalletSectionsData.ts | 10 +++------ src/hooks/useWalletsWithBalancesAndNames.ts | 9 +++++++- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/components/DappBrowser/control-panel/ControlPanel.tsx b/src/components/DappBrowser/control-panel/ControlPanel.tsx index a7867ee6f68..b3bc680a78a 100644 --- a/src/components/DappBrowser/control-panel/ControlPanel.tsx +++ b/src/components/DappBrowser/control-panel/ControlPanel.tsx @@ -144,7 +144,9 @@ export const ControlPanel = () => { (wallet.addresses || []) .filter(account => account.visible) .forEach(account => { - const balanceText = account.balances ? account.balances.totalBalanceDisplay : i18n.t(i18n.l.wallet.change_wallet.loading_balance); + const balanceText = account.balancesMinusHiddenBalances + ? account.balancesMinusHiddenBalances + : i18n.t(i18n.l.wallet.change_wallet.loading_balance); const item: ControlPanelMenuItemProps = { IconComponent: account.image ? ( diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index d56ab9c1cdb..df2e54709d7 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -23,8 +23,6 @@ import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; import { ContextMenu } from '../context-menu'; import { useForegroundColor } from '@/design-system'; -import { convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; -import { useAccountSettings } from '@/hooks'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; const NOOP = () => undefined; @@ -121,21 +119,8 @@ interface AddressRowProps { export default function AddressRow({ contextMenuActions, data, editMode, onPress }: AddressRowProps) { const notificationsEnabled = useExperimentalFlag(NOTIFICATIONS); - const { nativeCurrency } = useAccountSettings(); - const { - address, - balances, - hiddenBalances, - color: accountColor, - ens, - image: accountImage, - isSelected, - isReadOnly, - isLedger, - label, - walletId, - } = data; + const { address, balances, color: accountColor, ens, image: accountImage, isSelected, isReadOnly, isLedger, label, walletId } = data; const { colors, isDarkMode } = useTheme(); @@ -146,8 +131,8 @@ export default function AddressRow({ contextMenuActions, data, editMode, onPress return lang.t('wallet.change_wallet.loading_balance'); } - return convertAmountToNativeDisplay(subtract(balances.totalBalanceAmount, hiddenBalances), nativeCurrency); - }, [balances, hiddenBalances, nativeCurrency]); + return balances.balancesMinusHiddenBalances; + }, [balances]); const cleanedUpLabel = useMemo(() => removeFirstEmojiFromString(label), [label]); diff --git a/src/hooks/useWalletSectionsData.ts b/src/hooks/useWalletSectionsData.ts index 7f61f813ac0..5587637be40 100644 --- a/src/hooks/useWalletSectionsData.ts +++ b/src/hooks/useWalletSectionsData.ts @@ -17,7 +17,6 @@ import { usePositions } from '@/resources/defi/PositionsQuery'; import { useClaimables } from '@/resources/addys/claimables/query'; import { useExperimentalConfig } from '@/config/experimentalHooks'; import { useUserAssetsStore } from '@/state/assets/userAssets'; -import { convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; import { analyticsV2 } from '@/analytics'; import { Claimable } from '@/resources/addys/claimables/types'; import { throttle } from 'lodash'; @@ -93,8 +92,6 @@ export default function useWalletSectionsData({ const experimentalConfig = useExperimentalConfig(); const hiddenAssets = useUserAssetsStore(state => state.hiddenAssets); - const hiddenBalance = accountWithBalance?.hiddenBalances ?? '0'; - const totalBalance = accountWithBalance?.balances?.totalBalanceAmount ?? '0'; const { pinnedCoinsObj: pinnedCoins } = useCoinListEditOptions(); @@ -111,8 +108,8 @@ export default function useWalletSectionsData({ pinnedCoins, sendableUniqueTokens, sortedAssets, - accountBalanceDisplay: convertAmountToNativeDisplay(subtract(totalBalance, hiddenBalance), nativeCurrency), - isLoadingBalance: !accountWithBalance?.balances, + accountBalanceDisplay: accountWithBalance?.balancesMinusHiddenBalances, + isLoadingBalance: !accountWithBalance?.balancesMinusHiddenBalances, // @ts-expect-error ts-migrate(2698) FIXME: Spread types may only be created from object types... Remove this comment to see the full error message ...isWalletEthZero, hiddenTokens, @@ -149,8 +146,7 @@ export default function useWalletSectionsData({ pinnedCoins, sendableUniqueTokens, sortedAssets, - totalBalance, - hiddenBalance, + accountWithBalance?.balancesMinusHiddenBalances, accountWithBalance?.balances, isWalletEthZero, hiddenTokens, diff --git a/src/hooks/useWalletsWithBalancesAndNames.ts b/src/hooks/useWalletsWithBalancesAndNames.ts index 952dbfdeec0..387c4cd221e 100644 --- a/src/hooks/useWalletsWithBalancesAndNames.ts +++ b/src/hooks/useWalletsWithBalancesAndNames.ts @@ -4,8 +4,11 @@ import useWalletBalances from './useWalletBalances'; import useWalletsHiddenBalances from './useWalletsHiddenBalances'; import useWallets from './useWallets'; import { Address } from 'viem'; +import { convertAmountToNativeDisplay, subtract } from '@/helpers/utilities'; +import { useAccountSettings } from '.'; export default function useWalletsWithBalancesAndNames() { + const { nativeCurrency } = useAccountSettings(); const { walletNames, wallets } = useWallets(); const { balances } = useWalletBalances(wallets || {}); const { hiddenBalances } = useWalletsHiddenBalances(wallets || {}); @@ -19,12 +22,16 @@ export default function useWalletsWithBalancesAndNames() { ...account, balances: balances[lowerCaseAddress], hiddenBalances: hiddenBalances[lowerCaseAddress] ?? '0', + balancesMinusHiddenBalances: convertAmountToNativeDisplay( + subtract(balances[lowerCaseAddress]?.totalBalanceAmount, hiddenBalances[lowerCaseAddress] ?? '0'), + nativeCurrency + ), ens: walletNames[account.address], }; }); return { ...wallet, addresses: updatedAccounts }; }), - [balances, hiddenBalances, walletNames, wallets] + [balances, hiddenBalances, nativeCurrency, walletNames, wallets] ); return walletsWithBalancesAndNames; From 5a3a02380f7a30ccee64ca4e9a474bd4672fa790 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 31 Oct 2024 09:43:34 -0400 Subject: [PATCH 15/18] remove duplicate equality check --- src/hooks/useWalletsHiddenBalances.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 7b30e6b17e3..499357c2a5f 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -88,11 +88,7 @@ const useWalletsHiddenBalances = (wallets: AllRainbowWallets): WalletBalanceResu const subscriptions = allAddresses.map(address => { return userAssetsStore.subscribe( state => state, - (newState, oldState) => { - if (!_isEqual(oldState.hiddenAssets, newState.hiddenAssets)) { - calculateHiddenBalanceForAddress(address); - } - }, + () => calculateHiddenBalanceForAddress(address), { equalityFn: (a, b) => _isEqual(a.hiddenAssets, b.hiddenAssets), fireImmediately: true, From 3b0f1bbd344cb02763459e4ea1f6fd9dd18d49e3 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Thu, 31 Oct 2024 20:46:44 -0400 Subject: [PATCH 16/18] Update src/hooks/useWalletsHiddenBalances.ts Co-authored-by: gregs --- src/hooks/useWalletsHiddenBalances.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 499357c2a5f..72cac128ce0 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -30,13 +30,9 @@ const getHiddenAssetBalance = ({ const balance = hiddenAssetIds.reduce((acc, uniqueId) => { const asset = assetData?.[uniqueId]; - let sum = acc; - if (asset) { - const priceUnit = asset.price?.value ?? 0; - const nativeDisplay = convertAmountAndPriceToNativeDisplay(asset?.balance?.amount ?? 0, priceUnit, nativeCurrency); - sum = add(sum, nativeDisplay.amount); - } - return sum; + if (!asset) return acc + const assetNativeBalance = multiply(asset.price?.value || 0, asset.balance?.amount || 0) + return add(acc, assetNativeBalance); }, '0'); return balance; From c49547847b1e201f3ef0c5dd1ac5917faa10a01d Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 1 Nov 2024 10:10:41 -0400 Subject: [PATCH 17/18] fix lint --- src/hooks/useWalletsHiddenBalances.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useWalletsHiddenBalances.ts b/src/hooks/useWalletsHiddenBalances.ts index 72cac128ce0..724ff266ab5 100644 --- a/src/hooks/useWalletsHiddenBalances.ts +++ b/src/hooks/useWalletsHiddenBalances.ts @@ -7,7 +7,7 @@ import { NativeCurrencyKey } from '@/entities/nativeCurrencyTypes'; import { userAssetsStore } from '@/state/assets/userAssets'; import { queryClient } from '@/react-query'; import { userAssetsQueryKey, UserAssetsResult } from '@/resources/assets/UserAssetsQuery'; -import { convertAmountAndPriceToNativeDisplay, add, isEqual } from '@/helpers/utilities'; +import { convertAmountAndPriceToNativeDisplay, add, isEqual, multiply } from '@/helpers/utilities'; import { isEqual as _isEqual } from 'lodash'; export type WalletBalanceResult = { @@ -30,8 +30,8 @@ const getHiddenAssetBalance = ({ const balance = hiddenAssetIds.reduce((acc, uniqueId) => { const asset = assetData?.[uniqueId]; - if (!asset) return acc - const assetNativeBalance = multiply(asset.price?.value || 0, asset.balance?.amount || 0) + if (!asset) return acc; + const assetNativeBalance = multiply(asset.price?.value || 0, asset.balance?.amount || 0); return add(acc, assetNativeBalance); }, '0'); From 548bbeccdd3ddea3c678b35105425f0280dc7711 Mon Sep 17 00:00:00 2001 From: Matthew Wall Date: Fri, 1 Nov 2024 13:05:54 -0400 Subject: [PATCH 18/18] formatter fix --- src/state/assets/userAssets.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/state/assets/userAssets.ts b/src/state/assets/userAssets.ts index 48707e3a9ce..035054af8ac 100644 --- a/src/state/assets/userAssets.ts +++ b/src/state/assets/userAssets.ts @@ -5,7 +5,6 @@ import reduxStore, { AppState } from '@/redux/store'; import { ETH_ADDRESS, supportedNativeCurrencies } from '@/references'; import { createRainbowStore } from '@/state/internal/createRainbowStore'; import { useStore } from 'zustand'; -import { useCallback } from 'react'; import { swapsStore } from '@/state/swaps/swapsStore'; import { ChainId } from '@/chains/types'; import { SUPPORTED_CHAIN_IDS } from '@/chains'; @@ -72,7 +71,10 @@ type UserAssetsStateToPersist = Omit< >; // NOTE: We are serializing Map as an Array<[UniqueId, ParsedSearchAsset]> -type UserAssetsStateToPersistWithTransforms = Omit & { +type UserAssetsStateToPersistWithTransforms = Omit< + UserAssetsStateToPersist, + 'chainBalances' | 'idsByChain' | 'userAssets' | 'hiddenAssets' +> & { chainBalances: Array<[ChainId, number]>; idsByChain: Array<[UserAssetFilter, UniqueId[]]>; userAssets: Array<[UniqueId, ParsedSearchAsset]>;