-
Notifications
You must be signed in to change notification settings - Fork 635
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
comment out favorites until I can match the types
- Loading branch information
Showing
3 changed files
with
529 additions
and
9 deletions.
There are no files selected for viewing
393 changes: 393 additions & 0 deletions
393
src/__swaps__/screens/Swap/hooks/useSearchCurrencyLists.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,393 @@ | ||
import { isAddress } from '@ethersproject/address'; | ||
import { rankings } from 'match-sorter'; | ||
import { useCallback, useMemo } from 'react'; | ||
|
||
import { SUPPORTED_CHAINS } from '@/references'; | ||
import { useTokenSearch } from '../resources/search'; | ||
import { ParsedSearchAsset } from '../types/assets'; | ||
import { ChainId } from '../types/chains'; | ||
import { SearchAsset, TokenSearchAssetKey, TokenSearchListId, TokenSearchThreshold } from '../types/search'; | ||
import { getChainName } from '../utils/chains'; | ||
import { addHexPrefix } from '../utils/hex'; | ||
import { isLowerCaseMatch } from '../utils/strings'; | ||
|
||
import { filterList } from '@/utils'; | ||
|
||
import { isSameAsset } from './useSwapAssets'; | ||
import { useFavorites } from '@/resources/favorites'; | ||
|
||
const VERIFIED_ASSETS_PAYLOAD: { | ||
keys: TokenSearchAssetKey[]; | ||
list: TokenSearchListId; | ||
threshold: TokenSearchThreshold; | ||
query: string; | ||
} = { | ||
keys: ['symbol', 'name'], | ||
list: 'verifiedAssets', | ||
threshold: 'CONTAINS', | ||
query: '', | ||
}; | ||
|
||
export type AssetToBuySectionId = 'bridge' | 'favorites' | 'verified' | 'unverified' | 'other_networks'; | ||
|
||
export interface AssetToBuySection { | ||
data: SearchAsset[]; | ||
id: AssetToBuySectionId; | ||
} | ||
|
||
const filterBridgeAsset = ({ asset, filter = '' }: { asset?: SearchAsset; filter?: string }) => | ||
asset?.address?.toLowerCase()?.startsWith(filter?.toLowerCase()) || | ||
asset?.name?.toLowerCase()?.startsWith(filter?.toLowerCase()) || | ||
asset?.symbol?.toLowerCase()?.startsWith(filter?.toLowerCase()); | ||
|
||
export function useSearchCurrencyLists({ | ||
assetToSell, | ||
inputChainId, | ||
outputChainId, | ||
searchQuery, | ||
bridge, | ||
}: { | ||
assetToSell: SearchAsset | ParsedSearchAsset | null; | ||
// should be provided when swap input currency is selected | ||
inputChainId?: ChainId; | ||
// target chain id of current search | ||
outputChainId: ChainId; | ||
searchQuery?: string; | ||
// only show same asset on multiple chains | ||
bridge?: boolean; | ||
}) { | ||
const query = searchQuery?.toLowerCase() || ''; | ||
const enableUnverifiedSearch = query.trim().length > 2; | ||
|
||
const isCrosschainSearch = useMemo(() => { | ||
return inputChainId && inputChainId !== outputChainId; | ||
}, [inputChainId, outputChainId]); | ||
|
||
// provided during swap to filter token search by available routes | ||
const fromChainId = useMemo(() => { | ||
return isCrosschainSearch ? inputChainId : undefined; | ||
}, [inputChainId, isCrosschainSearch]); | ||
|
||
const queryIsAddress = useMemo(() => isAddress(query), [query]); | ||
|
||
const keys: TokenSearchAssetKey[] = useMemo(() => (queryIsAddress ? ['address'] : ['name', 'symbol']), [queryIsAddress]); | ||
|
||
const threshold: TokenSearchThreshold = useMemo(() => (queryIsAddress ? 'CASE_SENSITIVE_EQUAL' : 'CONTAINS'), [queryIsAddress]); | ||
|
||
// static search data | ||
const { data: mainnetVerifiedAssets, isLoading: mainnetVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.mainnet, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: optimismVerifiedAssets, isLoading: optimismVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.optimism, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: bscVerifiedAssets, isLoading: bscVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.bsc, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: polygonVerifiedAssets, isLoading: polygonVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.polygon, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: arbitrumVerifiedAssets, isLoading: arbitrumVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.arbitrum, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: baseVerifiedAssets, isLoading: baseVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.base, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: zoraVerifiedAssets, isLoading: zoraVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.zora, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: avalancheVerifiedAssets, isLoading: avalancheVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.avalanche, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
const { data: blastVerifiedAssets, isLoading: blastVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: ChainId.blast, | ||
...VERIFIED_ASSETS_PAYLOAD, | ||
fromChainId, | ||
}); | ||
|
||
// current search | ||
const { data: targetVerifiedAssets, isLoading: targetVerifiedAssetsLoading } = useTokenSearch({ | ||
chainId: outputChainId, | ||
keys, | ||
list: 'verifiedAssets', | ||
threshold, | ||
query, | ||
fromChainId, | ||
}); | ||
const { data: targetUnverifiedAssets, isLoading: targetUnverifiedAssetsLoading } = useTokenSearch( | ||
{ | ||
chainId: outputChainId, | ||
keys, | ||
list: 'highLiquidityAssets', | ||
threshold, | ||
query, | ||
fromChainId, | ||
}, | ||
{ | ||
enabled: enableUnverifiedSearch, | ||
} | ||
); | ||
|
||
const { favoritesMetadata: favorites } = useFavorites(); | ||
|
||
const favoritesList = useMemo(() => { | ||
const unfilteredFavorites = Object.values(favorites); | ||
if (query === '') { | ||
return unfilteredFavorites; | ||
} else { | ||
const formattedQuery = queryIsAddress ? addHexPrefix(query).toLowerCase() : query; | ||
return filterList(unfilteredFavorites || [], formattedQuery, keys, { | ||
threshold: queryIsAddress ? rankings.CASE_SENSITIVE_EQUAL : rankings.CONTAINS, | ||
}); | ||
} | ||
}, [favorites, keys, query, queryIsAddress]); | ||
|
||
// static verified asset lists prefetched to display curated lists | ||
// we only display crosschain exact matches if located here | ||
const verifiedAssets = useMemo( | ||
() => ({ | ||
[ChainId.mainnet]: { | ||
assets: mainnetVerifiedAssets, | ||
loading: mainnetVerifiedAssetsLoading, | ||
}, | ||
[ChainId.optimism]: { | ||
assets: optimismVerifiedAssets, | ||
loading: optimismVerifiedAssetsLoading, | ||
}, | ||
[ChainId.bsc]: { | ||
assets: bscVerifiedAssets, | ||
loading: bscVerifiedAssetsLoading, | ||
}, | ||
[ChainId.polygon]: { | ||
assets: polygonVerifiedAssets, | ||
loading: polygonVerifiedAssetsLoading, | ||
}, | ||
[ChainId.arbitrum]: { | ||
assets: arbitrumVerifiedAssets, | ||
loading: arbitrumVerifiedAssetsLoading, | ||
}, | ||
[ChainId.base]: { | ||
assets: baseVerifiedAssets, | ||
loading: baseVerifiedAssetsLoading, | ||
}, | ||
[ChainId.zora]: { | ||
assets: zoraVerifiedAssets, | ||
loading: zoraVerifiedAssetsLoading, | ||
}, | ||
[ChainId.avalanche]: { | ||
assets: avalancheVerifiedAssets, | ||
loading: avalancheVerifiedAssetsLoading, | ||
}, | ||
[ChainId.blast]: { | ||
assets: blastVerifiedAssets, | ||
loading: blastVerifiedAssetsLoading, | ||
}, | ||
}), | ||
[ | ||
mainnetVerifiedAssets, | ||
mainnetVerifiedAssetsLoading, | ||
optimismVerifiedAssets, | ||
optimismVerifiedAssetsLoading, | ||
bscVerifiedAssets, | ||
bscVerifiedAssetsLoading, | ||
polygonVerifiedAssets, | ||
polygonVerifiedAssetsLoading, | ||
arbitrumVerifiedAssets, | ||
arbitrumVerifiedAssetsLoading, | ||
baseVerifiedAssets, | ||
baseVerifiedAssetsLoading, | ||
zoraVerifiedAssets, | ||
zoraVerifiedAssetsLoading, | ||
avalancheVerifiedAssets, | ||
avalancheVerifiedAssetsLoading, | ||
blastVerifiedAssets, | ||
blastVerifiedAssetsLoading, | ||
] | ||
); | ||
|
||
const getCuratedAssets = useCallback( | ||
(chainId: ChainId) => verifiedAssets[chainId]?.assets?.filter(({ isRainbowCurated }) => isRainbowCurated), | ||
[verifiedAssets] | ||
); | ||
|
||
const bridgeAsset = useMemo(() => { | ||
const curatedAssets = getCuratedAssets(outputChainId); | ||
const bridgeAsset = curatedAssets?.find(asset => | ||
isLowerCaseMatch(asset.mainnetAddress, assetToSell?.[assetToSell?.chainId === ChainId.mainnet ? 'address' : 'mainnetAddress']) | ||
); | ||
const filteredBridgeAsset = filterBridgeAsset({ | ||
asset: bridgeAsset, | ||
filter: query, | ||
}) | ||
? bridgeAsset | ||
: null; | ||
return outputChainId === assetToSell?.chainId ? null : filteredBridgeAsset; | ||
}, [assetToSell, getCuratedAssets, outputChainId, query]); | ||
|
||
const loading = useMemo(() => { | ||
return query === '' ? verifiedAssets[outputChainId]?.loading : targetVerifiedAssetsLoading || targetUnverifiedAssetsLoading; | ||
}, [outputChainId, targetUnverifiedAssetsLoading, targetVerifiedAssetsLoading, query, verifiedAssets]); | ||
|
||
// displayed when no search query is present | ||
const curatedAssets = useMemo( | ||
() => ({ | ||
[ChainId.mainnet]: getCuratedAssets(ChainId.mainnet), | ||
[ChainId.optimism]: getCuratedAssets(ChainId.optimism), | ||
[ChainId.bsc]: getCuratedAssets(ChainId.bsc), | ||
[ChainId.polygon]: getCuratedAssets(ChainId.polygon), | ||
[ChainId.arbitrum]: getCuratedAssets(ChainId.arbitrum), | ||
[ChainId.base]: getCuratedAssets(ChainId.base), | ||
[ChainId.zora]: getCuratedAssets(ChainId.zora), | ||
[ChainId.avalanche]: getCuratedAssets(ChainId.avalanche), | ||
[ChainId.blast]: getCuratedAssets(ChainId.blast), | ||
}), | ||
[getCuratedAssets] | ||
); | ||
|
||
const bridgeList = ( | ||
bridge && assetToSell?.networks | ||
? Object.entries(assetToSell.networks).map(([_chainId, assetOnNetworkOverrides]) => { | ||
if (!assetOnNetworkOverrides) return; | ||
const chainId = +_chainId as unknown as ChainId; // Object.entries messes the type | ||
|
||
const chainName = getChainName({ chainId }); | ||
const { address, decimals } = assetOnNetworkOverrides; | ||
// filter out the asset we're selling already | ||
if (isSameAsset(assetToSell, { chainId, address }) || !SUPPORTED_CHAINS.some(n => n.id === chainId)) return; | ||
return { | ||
...assetToSell, | ||
chainId, | ||
chainName: chainName, | ||
uniqueId: `${address}-${chainId}`, | ||
address, | ||
decimals, | ||
}; | ||
}) | ||
: [] | ||
).filter(Boolean) as SearchAsset[]; | ||
|
||
const crosschainExactMatches = Object.values(verifiedAssets) | ||
?.map(verifiedList => { | ||
return verifiedList?.assets?.filter(t => { | ||
const symbolMatch = isLowerCaseMatch(t?.symbol, query); | ||
const nameMatch = isLowerCaseMatch(t?.name, query); | ||
return symbolMatch || nameMatch; | ||
}); | ||
}) | ||
.flat() | ||
.filter(Boolean) as SearchAsset[]; | ||
|
||
const filterAssetsFromBridgeAndAssetToSell = useCallback( | ||
(assets?: SearchAsset[]) => | ||
assets?.filter( | ||
curatedAsset => | ||
!isLowerCaseMatch(curatedAsset?.address, bridgeAsset?.address) && !isLowerCaseMatch(curatedAsset?.address, assetToSell?.address) | ||
) || [], | ||
[assetToSell?.address, bridgeAsset?.address] | ||
); | ||
|
||
const filterAssetsFromFavoritesBridgeAndAssetToSell = useCallback( | ||
(assets?: SearchAsset[]) => | ||
filterAssetsFromBridgeAndAssetToSell(assets)?.filter( | ||
curatedAsset => !favoritesList?.map(fav => fav.address).includes(curatedAsset.address) | ||
) || [], | ||
[favoritesList, filterAssetsFromBridgeAndAssetToSell] | ||
); | ||
|
||
// the lists below should be filtered by favorite/bridge asset match | ||
const results = useMemo(() => { | ||
const sections: AssetToBuySection[] = []; | ||
if (bridge) { | ||
sections.push({ data: bridgeList || [], id: 'bridge' }); | ||
return sections; | ||
} | ||
|
||
if (bridgeAsset) { | ||
sections.push({ | ||
data: [bridgeAsset], | ||
id: 'bridge', | ||
}); | ||
} | ||
|
||
// TODO: Migrate favorites over to SearchAsset type | ||
// if (favoritesList?.length) { | ||
// sections.push({ | ||
// data: filterAssetsFromBridgeAndAssetToSell(favoritesList), | ||
// id: 'favorites', | ||
// }); | ||
// } | ||
|
||
if (query === '') { | ||
sections.push({ | ||
data: filterAssetsFromFavoritesBridgeAndAssetToSell(curatedAssets[outputChainId]), | ||
id: 'verified', | ||
}); | ||
} else { | ||
if (targetVerifiedAssets?.length) { | ||
sections.push({ | ||
data: filterAssetsFromFavoritesBridgeAndAssetToSell(targetVerifiedAssets), | ||
id: 'verified', | ||
}); | ||
} | ||
|
||
if (targetUnverifiedAssets?.length && enableUnverifiedSearch) { | ||
sections.push({ | ||
data: filterAssetsFromFavoritesBridgeAndAssetToSell(targetUnverifiedAssets), | ||
id: 'unverified', | ||
}); | ||
} | ||
|
||
if (!sections.length && crosschainExactMatches?.length) { | ||
sections.push({ | ||
data: filterAssetsFromFavoritesBridgeAndAssetToSell(crosschainExactMatches), | ||
id: 'other_networks', | ||
}); | ||
} | ||
} | ||
|
||
return sections; | ||
}, [ | ||
bridgeAsset, | ||
favoritesList, | ||
query, | ||
filterAssetsFromBridgeAndAssetToSell, | ||
filterAssetsFromFavoritesBridgeAndAssetToSell, | ||
curatedAssets, | ||
outputChainId, | ||
targetVerifiedAssets, | ||
targetUnverifiedAssets, | ||
crosschainExactMatches, | ||
bridgeList, | ||
bridge, | ||
enableUnverifiedSearch, | ||
]); | ||
|
||
return { | ||
loading, | ||
results, | ||
}; | ||
} |
Oops, something went wrong.