Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/components/AssetSearch/AssetSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useNavigate } from 'react-router-dom'
import { AssetList } from './components/AssetList'

import { ChainList } from '@/components/TradeAssetSearch/Chains/ChainList'
import { filterAssetsBySearchTerm } from '@/components/TradeAssetSearch/helpers/filterAssetsBySearchTerm/filterAssetsBySearchTerm'
import { searchAssets } from '@/lib/assetSearch'
import { sortChainIdsByDisplayName } from '@/lib/utils'
import { selectWalletConnectedChainIds } from '@/state/slices/selectors'
import { useAppSelector } from '@/state/store'
Expand Down Expand Up @@ -79,9 +79,7 @@ export const AssetSearch: FC<AssetSearchProps> = ({
const searching = useMemo(() => searchString.length > 0, [searchString])
useEffect(() => {
if (filteredAssets) {
setSearchTermAssets(
searching ? filterAssetsBySearchTerm(searchString, filteredAssets) : filteredAssets,
)
setSearchTermAssets(searching ? searchAssets(searchString, filteredAssets) : filteredAssets)
}
}, [searchString, searching, filteredAssets])
const listAssets = searching ? searchTermAssets : filteredAssets
Expand Down
2 changes: 1 addition & 1 deletion src/components/StakingVaults/PositionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { RawText } from '@/components/Text'
import { useIsSnapInstalled } from '@/hooks/useIsSnapInstalled/useIsSnapInstalled'
import { useWallet } from '@/hooks/useWallet/useWallet'
import { walletSupportsChain } from '@/hooks/useWalletSupportsChain/useWalletSupportsChain'
import { isEthAddress } from '@/lib/address/utils'
import { bnOrZero } from '@/lib/bignumber/bignumber'
import { isEthAddress } from '@/lib/utils/ethAddress'
import type { AggregatedOpportunitiesByAssetIdReturn } from '@/state/slices/opportunitiesSlice/types'
import {
selectAccountIdsByChainId,
Expand Down
49 changes: 30 additions & 19 deletions src/components/TradeAssetSearch/TradeAssetSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import type { Asset } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'
import type { FC, FormEvent } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { useTranslate } from 'react-polyglot'
import { useNavigate } from 'react-router-dom'

import { CustomAssetAcknowledgement } from './components/CustomAssetAcknowledgement'
import { DefaultAssetList } from './components/DefaultAssetList'
import { SearchTermAssetList } from './components/SearchTermAssetList'
import { useAssetSearchWorker } from './hooks/useAssetSearchWorker'
import { useGetPopularAssetsQuery } from './hooks/useGetPopularAssetsQuery'

import { AssetMenuButton } from '@/components/AssetSelection/components/AssetMenuButton'
Expand Down Expand Up @@ -54,6 +54,7 @@ export type TradeAssetSearchProps = {
selectedChainId?: ChainId | 'All'
onSelectedChainIdChange?: (chainId: ChainId | 'All') => void
}

export const TradeAssetSearch: FC<TradeAssetSearchProps> = ({
onAssetClick,
formProps,
Expand Down Expand Up @@ -97,29 +98,23 @@ export const TradeAssetSearch: FC<TradeAssetSearchProps> = ({
[navigate],
)
const handleAssetClick = onAssetClick ?? defaultClickHandler
const { register, watch } = useForm<{ search: string }>({
mode: 'onChange',
defaultValues: {
search: '',
},
})
const searchString = watch('search').trim()
const isSearching = useMemo(() => searchString.length > 0, [searchString])

const inputProps: InputProps = useMemo(
const assetWorkerParams = useMemo(
() => ({
...register('search'),
autoFocus: !window.matchMedia('(pointer: coarse)').matches, // Don't auto bust open the keyboard on mobile
type: 'text',
placeholder: translate('common.searchNameOrAddress'),
pl: 10,
variant: 'filled',
borderWidth: 0,
autoComplete: 'off',
activeChainId,
allowWalletUnsupportedAssets,
walletConnectedChainIds,
hasWallet,
}),
[register, translate],
[activeChainId, allowWalletUnsupportedAssets, hasWallet, walletConnectedChainIds],
)

// Asset search worker hook
const { searchString, workerSearchState, handleSearchChange } =
useAssetSearchWorker(assetWorkerParams)

const isSearching = useMemo(() => searchString.length > 0, [searchString])

const handleSubmit = useCallback((e: FormEvent<unknown>) => e.preventDefault(), [])

const handleSelectedChainIdChange = useCallback(
Expand Down Expand Up @@ -228,6 +223,21 @@ export const TradeAssetSearch: FC<TradeAssetSearchProps> = ({
setShouldShowWarningAcknowledgement(true)
}, [])

const inputProps: InputProps = useMemo(
() => ({
value: searchString,
onChange: handleSearchChange,
autoFocus: !window.matchMedia('(pointer: coarse)').matches, // Don't auto bust open the keyboard on mobile
type: 'text',
placeholder: translate('common.searchNameOrAddress'),
pl: 10,
variant: 'filled',
borderWidth: 0,
autoComplete: 'off',
}),
[searchString, handleSearchChange, translate],
)

return (
<>
<CustomAssetAcknowledgement
Expand Down Expand Up @@ -278,6 +288,7 @@ export const TradeAssetSearch: FC<TradeAssetSearchProps> = ({
isLoading={isPopularAssetIdsLoading}
assetFilterPredicate={assetFilterPredicate}
allowWalletUnsupportedAssets={!hasWallet || allowWalletUnsupportedAssets}
workerSearchState={workerSearchState}
/>
) : (
<DefaultAssetList
Expand Down
105 changes: 70 additions & 35 deletions src/components/TradeAssetSearch/components/SearchTermAssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { bnOrZero, getAssetNamespaceFromChainId, makeAsset } from '@shapeshiftos
import { orderBy } from 'lodash'
import { useMemo } from 'react'

import { filterAssetsBySearchTerm } from '../helpers/filterAssetsBySearchTerm/filterAssetsBySearchTerm'
import type { WorkerSearchState } from '../hooks/useAssetSearchWorker'
import { useGetCustomTokensQuery } from '../hooks/useGetCustomTokensQuery'
import { GroupedAssetList } from './GroupedAssetList/GroupedAssetList'

import { ALCHEMY_SDK_SUPPORTED_CHAIN_IDS } from '@/lib/alchemySdkInstance'
import { searchAssets } from '@/lib/assetSearch'
import { isSome } from '@/lib/utils'
import {
selectAssetsSortedByMarketCapUserCurrencyBalanceCryptoPrecisionAndName,
Expand All @@ -28,6 +29,7 @@ export type SearchTermAssetListProps = {
assetFilterPredicate?: (assetId: AssetId) => boolean
onAssetClick: (asset: Asset) => void
onImportClick: (asset: Asset) => void
workerSearchState: WorkerSearchState
}

export const SearchTermAssetList = ({
Expand All @@ -38,17 +40,20 @@ export const SearchTermAssetList = ({
assetFilterPredicate,
onAssetClick: handleAssetClick,
onImportClick,
workerSearchState,
}: SearchTermAssetListProps) => {
const assets = useAppSelector(
selectAssetsSortedByMarketCapUserCurrencyBalanceCryptoPrecisionAndName,
)
const portfolioUserCurrencyBalances = useAppSelector(selectPortfolioUserCurrencyBalances)
const assetsById = useAppSelector(selectAssets)
const walletConnectedChainIds = useAppSelector(selectWalletConnectedChainIds)

const customTokenSupportedChainIds = useMemo(() => {
// Solana _is_ supported by Alchemy, but not by the SDK
return [...ALCHEMY_SDK_SUPPORTED_CHAIN_IDS, solanaChainId]
}, [])

const chainIds = useMemo(() => {
if (activeChainId === 'All') {
return customTokenSupportedChainIds
Expand Down Expand Up @@ -85,35 +90,58 @@ export const SearchTermAssetList = ({
assetFilterPredicate,
])

const customAssets: Asset[] = useMemo(
() =>
(customTokens ?? [])
.map(metaData => {
if (!metaData) return null
const { name, symbol, decimals, logo } = metaData
// If we can't get all the information we need to create an Asset, don't allow the custom token
if (!name || !symbol || !decimals) return null
const assetId = toAssetId({
chainId: metaData.chainId,
assetNamespace: getAssetNamespaceFromChainId(metaData.chainId as KnownChainIds),
assetReference: metaData.contractAddress,
})
const minimalAsset: MinimalAsset = {
assetId,
name,
symbol,
precision: decimals,
icon: logo ?? undefined,
}
return makeAsset(assetsById, minimalAsset)
// Build a Set of existing asset IDs once when assetsForChain changes
const assetIdMap = useMemo(() => {
const assetLookup: Record<AssetId, Asset> = {}
for (const asset of assetsForChain) {
assetLookup[asset.assetId] = asset
}
return assetLookup
}, [assetsForChain])

const customAssets: Asset[] = useMemo(() => {
return (customTokens ?? [])
.map(metaData => {
if (!metaData) return null
const { name, symbol, decimals, logo } = metaData
// If we can't get all the information we need to create an Asset, don't allow the custom token
if (!name || !symbol || !decimals) return null
const assetId = toAssetId({
chainId: metaData.chainId,
assetNamespace: getAssetNamespaceFromChainId(metaData.chainId as KnownChainIds),
assetReference: metaData.contractAddress,
})
.filter(isSome),
[assetsById, customTokens],
)

// We only want to show custom assets that aren't already in the asset list
// Skip if we already have this asset
if (assetIdMap[assetId] !== undefined) return null

const minimalAsset: MinimalAsset = {
assetId,
name,
symbol,
precision: decimals,
icon: logo ?? undefined,
}
return makeAsset(assetsById, minimalAsset)
})
.filter(isSome)
}, [assetIdMap, assetsById, customTokens])

const searchTermAssets = useMemo(() => {
const filteredAssets = filterAssetsBySearchTerm(searchString, assetsForChain)
const filteredAssets: Asset[] = (() => {
// Main thread search due to dead worker
if (workerSearchState.workerState === 'failed') {
return searchAssets(searchString, assetsForChain)
}

// Use the results from the worker
if (workerSearchState.workerState === 'ready' && workerSearchState.searchResults) {
return workerSearchState.searchResults.map(assetId => assetIdMap[assetId]).filter(isSome)
}

return []
})()

const existingAssetIds = new Set(filteredAssets.map(asset => asset.assetId))
const uniqueCustomAssets = customAssets.filter(asset => !existingAssetIds.has(asset.assetId))
const assetsWithCustomAssets = filteredAssets.concat(uniqueCustomAssets)
Expand All @@ -125,15 +153,22 @@ export const SearchTermAssetList = ({
[getAssetBalance],
['desc'],
)
}, [assetsForChain, customAssets, searchString, portfolioUserCurrencyBalances])
}, [
customAssets,
workerSearchState.workerState,
workerSearchState.searchResults,
searchString,
assetsForChain,
assetIdMap,
portfolioUserCurrencyBalances,
])

const { groups, groupCounts, groupIsLoading } = useMemo(() => {
return {
groups: ['modals.assetSearch.searchResults'],
groupCounts: [searchTermAssets.length],
groupIsLoading: [isLoadingCustomTokens || isAssetListLoading],
}
}, [isAssetListLoading, isLoadingCustomTokens, searchTermAssets.length])
const groups = useMemo(() => ['modals.assetSearch.searchResults'], [])
const groupCounts = useMemo(() => [searchTermAssets.length], [searchTermAssets.length])
const groupIsLoading = useMemo(
() => [isLoadingCustomTokens || isAssetListLoading || workerSearchState.isSearching],
[isLoadingCustomTokens, isAssetListLoading, workerSearchState.isSearching],
)

return (
<GroupedAssetList
Expand Down

This file was deleted.

Loading