diff --git a/packages/widget-v2/package.json b/packages/widget-v2/package.json index ec65eb125..adbb4d2af 100644 --- a/packages/widget-v2/package.json +++ b/packages/widget-v2/package.json @@ -60,6 +60,7 @@ "jotai": "^2.9.1", "jotai-tanstack-query": "^0.8.6", "lodash.debounce": "^4.0.8", + "match-sorter": "^6.3.4", "pluralize": "^8.0.0", "rc-virtual-list": "^3.14.5", "react-error-boundary": "^4.0.13", diff --git a/packages/widget-v2/src/components/AssetChainInput.tsx b/packages/widget-v2/src/components/AssetChainInput.tsx index 87a2bba0f..1bc2fb36a 100644 --- a/packages/widget-v2/src/components/AssetChainInput.tsx +++ b/packages/widget-v2/src/components/AssetChainInput.tsx @@ -6,7 +6,7 @@ import { useTheme } from "styled-components"; import { CogIcon } from "@/icons/CogIcon"; import { Button, GhostButton } from "@/components/Button"; import { useAtom } from "jotai"; -import { skipAssets } from "@/state/skipClient"; +import { skipAssetsAtom, skipChainsAtom } from "@/state/skipClient"; import { useUsdValue } from "@/utils/useUsdValue"; import { formatUSD } from "@/utils/intl"; @@ -26,12 +26,17 @@ export const AssetChainInput = ({ handleChangeChain, }: AssetChainInputProps) => { const theme = useTheme(); - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); + const [{ data: chains }] = useAtom(skipChainsAtom) const selectedAsset = assets?.find( (asset) => asset.denom === selectedAssetDenom ); + const selectedChain = chains?.find( + (chain) => chain.chainID === selectedAsset?.chainID + ); + const usdValue = useUsdValue({ ...selectedAsset, value }); return ( @@ -50,7 +55,7 @@ export const AssetChainInput = ({ {selectedAsset ? ( - {selectedAsset?.name} + {selectedAsset?.recommendedSymbol} ) : ( @@ -73,7 +78,7 @@ export const AssetChainInput = ({ secondary gap={4} > - on {selectedAsset?.chainName} + on {selectedChain?.prettyName} ) : ( diff --git a/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx b/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx index e9d0da66d..cfcc9a56b 100644 --- a/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx +++ b/packages/widget-v2/src/modals/ManualAddressModal/ManualAddressModal.tsx @@ -12,18 +12,18 @@ import { WALLET_LIST } from "@/modals/WalletSelectorModal/WalletSelectorFlow"; import { Button } from "@/components/Button"; import { SmallText, Text } from "@/components/Typography"; import { destinationAssetAtom, destinationWalletAtom } from "@/state/swapPage"; -import { useAtom } from "jotai"; -import { getChain } from "@/state/skipClient"; +import { useAtom, useAtomValue } from "jotai"; +import { skipChainsAtom } from "@/state/skipClient"; export const ManualAddressModal = createModal((modalProps: ModalProps) => { const { theme } = modalProps; const modal = useModal(); const [destinationAsset] = useAtom(destinationAssetAtom); const [, setDestinationWallet] = useAtom(destinationWalletAtom); - const chain = getChain(destinationAsset?.chainID ?? ""); - const chainName = destinationAsset?.chainName; - const chainImage = chain.images?.find((image) => image.svg ?? image.png); - const chainLogo = chainImage?.svg ?? chainImage?.png; + const { data: chains } = useAtomValue(skipChainsAtom); + const chain = chains?.find(c => c.chainID === destinationAsset?.chainID) + const chainName = chain?.prettyName + const chainLogo = chain?.logoURI const [showManualAddressInput, setShowManualAddressInput] = useState(false); const [manualWalletAddress, setManualWalletAddress] = useState(""); @@ -63,7 +63,7 @@ export const ManualAddressModal = createModal((modalProps: ModalProps) => { {showManualAddressInput ? ( setShowManualAddressInput(false)} rightContent={() => ( @@ -155,8 +155,8 @@ const StyledAddressValidatorDot = styled.div<{ validAddress?: boolean }>` validAddress === true ? `background-color: ${theme.success.text}` : validAddress === false - ? `background-color: ${theme.error.text}` - : ""}; + ? `background-color: ${theme.error.text}` + : ""}; top: calc(50% - 11px / 2); right: 20px; diff --git a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx index c4fcfe987..c77d823bb 100644 --- a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx +++ b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal.tsx @@ -1,16 +1,17 @@ import { createModal, ModalProps, useModal } from "@/components/Modal"; import { Column } from "@/components/Layout"; import { styled } from "styled-components"; -import { useAtom } from "jotai"; -import { ChainWithAsset, ClientAsset, skipAssets } from "@/state/skipClient"; +import { useAtomValue } from "jotai"; +import { ChainWithAsset, ClientAsset, skipAssetsAtom, skipChainsAtom } from "@/state/skipClient"; import { useCallback, useEffect, useMemo, useState } from "react"; import { VirtualList } from "@/components/VirtualList"; import { - isChainWithAsset, TokenAndChainSelectorModalRowItem, Skeleton, + isClientAsset, } from "./TokenAndChainSelectorModalRowItem"; import { TokenAndChainSelectorModalSearchInput } from "./TokenAndChainSelectorModalSearchInput"; +import { matchSorter } from "match-sorter"; export type TokenAndChainSelectorModalProps = ModalProps & { onSelect: (token: ClientAsset | null) => void; @@ -22,37 +23,36 @@ export const TokenAndChainSelectorModal = createModal( (modalProps: TokenAndChainSelectorModalProps) => { const modal = useModal(); const { onSelect, chainsContainingAsset, asset } = modalProps; - const [{ data: assets, isPending }] = useAtom(skipAssets); + const { data: assets, isLoading: isAssetsLoading } = useAtomValue(skipAssetsAtom); + const { isLoading: isChainsLoading } = useAtomValue(skipChainsAtom) + const isLoading = isAssetsLoading || isChainsLoading const [showSkeleton, setShowSkeleton] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const filteredAssets = useMemo(() => { - if (!assets) return; - const filtered = assets.filter((asset) => - asset.symbol?.toLowerCase().includes(searchQuery.toLowerCase()) - ); - return filtered; - }, [searchQuery, assets]); + if (!assets) return + return matchSorter(assets, searchQuery, { + keys: ["recommendedSymbol", "symbol", "denom"], + }); + }, [assets, searchQuery]); const filteredChains = useMemo(() => { - if (!chainsContainingAsset) return; - const filtered = chainsContainingAsset.filter( - (chain) => - chain.chain_name?.toLowerCase().includes(searchQuery.toLowerCase()) || - chain.pretty_name?.toLowerCase().includes(searchQuery.toLowerCase()) - ); - return filtered; - }, [searchQuery, chainsContainingAsset]); + if (!chainsContainingAsset) return + return matchSorter(chainsContainingAsset, searchQuery, { + keys: ["chainID", "chainName", "prettyName"], + }); + }, [chainsContainingAsset, searchQuery]); + useEffect(() => { - if (!isPending && assets) { + if (!isLoading && assets) { const timer = setTimeout(() => { setShowSkeleton(false); }, 100); return () => clearTimeout(timer); } - }, [isPending, assets]); + }, [isLoading, assets]); useEffect(() => { setSearchQuery(""); @@ -93,13 +93,13 @@ export const TokenAndChainSelectorModal = createModal( listItems={filteredChains ?? filteredAssets ?? []} height={530} itemHeight={70} - renderItem={renderItem} itemKey={(item) => { - if (isChainWithAsset(item)) { - return `${item.chain_id}${item.chain_name}`; + if (isClientAsset(item)) { + return `${item.denom}-${item.chainID}-${item.recommendedSymbol}` } - return `${item.chainID}${item.denom}`; + return `${item.chainID}-${item.asset?.denom}` }} + renderItem={renderItem} /> )} diff --git a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx index d338d441e..3d4bbc0f1 100644 --- a/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx +++ b/packages/widget-v2/src/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModalRowItem.tsx @@ -1,10 +1,11 @@ import { Row } from "@/components/Layout"; import { ModalRowItem } from "@/components/ModalRowItem"; import { SmallText, Text } from "@/components/Typography"; -import { ChainWithAsset, ClientAsset } from "@/state/skipClient"; +import { ChainWithAsset, ClientAsset, skipChainsAtom } from "@/state/skipClient"; import { CircleSkeletonElement, SkeletonElement } from "@/components/Skeleton"; import { styled } from "styled-components"; -import { Chain } from "@chain-registry/types"; +import { useAtomValue } from "jotai"; +import { Chain } from "@skip-go/client"; export const isClientAsset = ( item: ClientAsset | ChainWithAsset @@ -15,7 +16,7 @@ export const isClientAsset = ( export const isChainWithAsset = ( item: ClientAsset | ChainWithAsset ): item is ChainWithAsset => { - return (item as Chain).chain_id !== undefined; + return (item as Chain).chainID !== undefined; }; export type TokenAndChainSelectorModalRowItemProps = { @@ -31,9 +32,12 @@ export const TokenAndChainSelectorModalRowItem = ({ skeleton, onSelect, }: TokenAndChainSelectorModalRowItemProps) => { - if (!item) return skeleton; + const { isLoading: isChainsLoading, data: chains } = useAtomValue(skipChainsAtom) + + if (!item || isChainsLoading) return skeleton; if (isClientAsset(item)) { + const chain = chains?.find((chain) => chain.chainID === item.chainID) return ( {item.symbol} - {item.chainName ?? item.originChainID ?? item.chainID} + {chain?.prettyName} } @@ -58,9 +62,11 @@ export const TokenAndChainSelectorModalRowItem = ({ } if (isChainWithAsset(item)) { + + const chain = chains?.find((chain) => chain.chainID === item.chainID) return ( onSelect(item?.asset || null)} style={{ margin: "5px 0" }} leftContent={ @@ -68,11 +74,11 @@ export const TokenAndChainSelectorModalRowItem = ({ - {item.pretty_name} - {item.chain_name} + {chain?.prettyName} + {item.chainID} } /> diff --git a/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx b/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx index f23b14426..d1df7a2b6 100644 --- a/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx +++ b/packages/widget-v2/src/modals/TransactionHistoryModal/TransactionHistoryModalItem.tsx @@ -1,7 +1,7 @@ import { RouteResponse } from "@skip-go/client"; import { SmallText } from "@/components/Typography"; import { useAtom } from "jotai"; -import { skipAssets, getChain, ClientAsset } from "@/state/skipClient"; +import { skipAssetsAtom, ClientAsset, skipChainsAtom } from "@/state/skipClient"; import { Column, Row } from "@/components/Layout"; import styled, { useTheme } from "styled-components"; import { getFormattedAssetAmount } from "@/utils/crypto"; @@ -56,25 +56,22 @@ export const TransactionHistoryModalItem = ({ timestamp, status, } = txHistoryItem; - const [{ data: assets }] = useAtom(skipAssets); - const sourceChain = getChain(sourceAssetChainID ?? ""); - const sourceChainImage = sourceChain.images?.find( - (image) => image.svg ?? image.png - ); + const [{ data: assets }] = useAtom(skipAssetsAtom); + const [{ data: chains }] = useAtom(skipChainsAtom) + const sourceChain = chains?.find(c => c.chainID === sourceAssetChainID) + const sourceChainImage = sourceChain?.logoURI const source = { amount: amountIn, asset: assets?.find((asset) => asset.denom === sourceAssetDenom), - chainImage: sourceChainImage?.svg ?? sourceChainImage?.png ?? "", + chainImage: sourceChainImage ?? "", }; - const destinationChain = getChain(destAssetChainID ?? ""); - const destinationChainImage = destinationChain.images?.find( - (image) => image.svg ?? image.png - ); + const destinationChain = chains?.find(c => c.chainID === destAssetChainID) + const destinationChainImage = destinationChain?.logoURI const destination = { amount: amountOut, asset: assets?.find((asset) => asset.denom === destAssetDenom), - chainImage: destinationChainImage?.svg ?? destinationChainImage?.png ?? "", + chainImage: destinationChainImage ?? "", }; const renderStatus = useMemo(() => { @@ -119,7 +116,7 @@ export const TransactionHistoryModalItem = ({ - on {destinationChain?.pretty_name ?? destinationChain?.chain_name} + on {destinationChain?.prettyName ?? destinationChain?.chainName} @@ -130,9 +127,9 @@ export const TransactionHistoryModalItem = ({ {showDetails && ( ` +const StyledHistoryContainer = styled(Column) <{ showDetails?: boolean }>` background-color: ${({ theme, showDetails }) => showDetails && theme.secondary.background.normal}; &:hover { diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx index 4a0998d75..36caff9a5 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteDetailedRow.tsx @@ -1,7 +1,7 @@ import { useAtom } from "jotai"; import { Row } from "@/components/Layout"; import { SmallText } from "@/components/Typography"; -import { getChain, skipAssets } from "@/state/skipClient"; +import { skipAssetsAtom, skipChainsAtom } from "@/state/skipClient"; import { getFormattedAssetAmount } from "@/utils/crypto"; import { css, styled, useTheme } from "styled-components"; import React from "react"; @@ -56,12 +56,13 @@ export const SwapExecutionPageRouteDetailedRow = ({ ...props }: SwapExecutionPageRouteDetailedRowProps) => { const theme = useTheme(); - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); + const [{ data: chains }] = useAtom(skipChainsAtom); const asset = assets?.find((asset) => asset.denom === denom); - const chain = getChain(chainID ?? ""); - const chainImage = chain.images?.find((image) => image.svg ?? image.png); + const chain = chains?.find(c => c.chainID === chainID) + const chainImage = chain?.logoURI return ( @@ -75,7 +76,7 @@ export const SwapExecutionPageRouteDetailedRow = ({ @@ -151,7 +152,7 @@ export const StyledAnimatedBorder = ({ ); -const StyledLoadingContainer = styled(Row)<{ +const StyledLoadingContainer = styled(Row) <{ height: number; width: number; borderSize: number; @@ -178,11 +179,11 @@ const StyledLoadingContainer = styled(Row)<{ &:before { content: ''; position: absolute; - height: ${({ height }) => `${height + 20}px;`} - width: ${({ width }) => `${width + 20}px;`} + height: ${({ height }) => `${height + 20}px;`}; + width: ${({ width }) => `${width + 20}px;`}; ${({ txState, backgroundColor, theme }) => - txState === "broadcasted" && - css` + txState === "broadcasted" && + css` background-image: conic-gradient( transparent, transparent, @@ -202,7 +203,7 @@ const StyledLoadingContainer = styled(Row)<{ } `; -const StyledLoadingOverlay = styled(Row)<{ +const StyledLoadingOverlay = styled(Row) <{ backgroundColor?: string; width: number; height: number; diff --git a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx index 4dd21e297..bc82eca7d 100644 --- a/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx +++ b/packages/widget-v2/src/pages/SwapExecutionPage/SwapExecutionPageRouteSimpleRow.tsx @@ -2,7 +2,7 @@ import { useTheme } from "styled-components"; import { Button } from "@/components/Button"; import { Column, Row } from "@/components/Layout"; import { SmallText, Text } from "@/components/Typography"; -import { getChain, skipAssets } from "@/state/skipClient"; +import { skipAssetsAtom, skipChainsAtom } from "@/state/skipClient"; import { getFormattedAssetAmount } from "@/utils/crypto"; import { Wallet } from "@/components/RenderWalletList"; import { iconMap, ICONS } from "@/icons"; @@ -45,11 +45,12 @@ export const SwapExecutionPageRouteSimpleRow = ({ "mount"; }, []); const theme = useTheme(); - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); + const [{ data: chains }] = useAtom(skipChainsAtom); const asset = assets?.find((asset) => asset.denom === denom); - const chain = getChain(chainID ?? ""); - const chainImage = chain.images?.find((image) => image.svg ?? image.png); + const chain = chains?.find((chain) => chain.chainID === chainID); + const chainImage = chain?.logoURI if (!asset) { throw new Error(`Asset not found for denom: ${denom}`); @@ -80,7 +81,7 @@ export const SwapExecutionPageRouteSimpleRow = ({ backgroundColor={theme.success.text} txState={txStateOfAnimatedBorder} > - + )} diff --git a/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx b/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx index cc50824d7..ba3df03dc 100644 --- a/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx +++ b/packages/widget-v2/src/pages/SwapPage/SwapPage.tsx @@ -5,7 +5,7 @@ import { Column } from "@/components/Layout"; import { MainButton } from "@/components/MainButton"; import { SmallText } from "@/components/Typography"; import { ICONS } from "@/icons"; -import { skipAssets, getChainsContainingAsset } from "@/state/skipClient"; +import { skipAssetsAtom, getChainsContainingAsset, skipChainsAtom } from "@/state/skipClient"; import { sourceAssetAtom, destinationAssetAtom } from "@/state/swapPage"; import { TokenAndChainSelectorModal } from "@/modals/TokenAndChainSelectorModal/TokenAndChainSelectorModal"; import { SwapPageSettings } from "./SwapPageSettings"; @@ -20,23 +20,24 @@ export const SwapPage = () => { const [container, setContainer] = useState(); const [drawerOpen, setDrawerOpen] = useState(false); const [sourceAsset, setSourceAsset] = useAtom(sourceAssetAtom); - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); + const [{ data: chains }] = useAtom(skipChainsAtom); const [destinationAsset, setDestinationAsset] = useAtom(destinationAssetAtom); const swapFlowSettings = useModal(SwapPageSettings); const tokenAndChainSelectorFlow = useModal(TokenAndChainSelectorModal); const chainsContainingSourceAsset = useMemo(() => { - if (!assets || !sourceAsset?.symbol) return; - const chains = getChainsContainingAsset(sourceAsset?.symbol, assets); - return chains; - }, [assets, sourceAsset?.symbol]); + if (!chains || !assets || !sourceAsset?.symbol) return; + const result = getChainsContainingAsset(sourceAsset?.symbol, assets, chains); + return result; + }, [assets, sourceAsset?.symbol, chains]); const chainsContainingDestinationAsset = useMemo(() => { - if (!assets || !destinationAsset?.symbol) return; - const chains = getChainsContainingAsset(destinationAsset?.symbol, assets); - return chains; - }, [assets, destinationAsset?.symbol]); + if (!chains || !assets || !destinationAsset?.symbol) return; + const result = getChainsContainingAsset(destinationAsset?.symbol, assets, chains); + return result; + }, [assets, destinationAsset?.symbol, chains]); const handleChangeSourceAsset = useCallback(() => { tokenAndChainSelectorFlow.show({ diff --git a/packages/widget-v2/src/state/chains.ts b/packages/widget-v2/src/state/chains.ts new file mode 100644 index 000000000..563d16eee --- /dev/null +++ b/packages/widget-v2/src/state/chains.ts @@ -0,0 +1,43 @@ + +import { Chain, AssetList } from "@chain-registry/types"; +import { + chains as chainsChainRegistry, + assets as assetsChainRegistry, +} from "chain-registry"; +import { + chains as chainsInitiaRegistry, + assets as assetsInitiaRegistry, +} from "@initia/initia-registry"; + +export const chains = [ + ...chainsChainRegistry, + ...chainsInitiaRegistry, +] as Chain[]; +export const assets = [ + ...assetsChainRegistry, + ...assetsInitiaRegistry, +] as AssetList[]; + +export function getChain(chainId: string): Chain { + const chain = chains.find((c) => c.chain_id === chainId); + if (!chain) { + throw new Error(`chain '${chainId}' does not exist in chainRecord`); + } + return chain; +} + +export function chainIdToName(chainId: string): string { + return getChain(chainId).chain_name; +} + +export function getAssets(chainId: string) { + const chainName = chainIdToName(chainId); + const assetsFoundForChain = assets.find( + (a) => a.chain_name === chainName + )?.assets; + + if (!assetsFoundForChain) { + throw new Error(`chain '${chainId}' does not exist in assetsRecord`); + } + return assetsFoundForChain; +} diff --git a/packages/widget-v2/src/state/skipClient.ts b/packages/widget-v2/src/state/skipClient.ts index 40e33d6c9..6f4d36b45 100644 --- a/packages/widget-v2/src/state/skipClient.ts +++ b/packages/widget-v2/src/state/skipClient.ts @@ -1,24 +1,7 @@ import { atom } from "jotai"; -import { Asset, SkipClient } from "@skip-go/client"; -import { Chain, AssetList } from "@chain-registry/types"; -import { - chains as chainsChainRegistry, - assets as assetsChainRegistry, -} from "chain-registry"; -import { - chains as chainsInitiaRegistry, - assets as assetsInitiaRegistry, -} from "@initia/initia-registry"; +import { Asset, SkipClient, Chain } from "@skip-go/client"; import { atomWithQuery } from "jotai-tanstack-query"; -export const chains = [ - ...chainsChainRegistry, - ...chainsInitiaRegistry, -] as Chain[]; -export const assets = [ - ...assetsChainRegistry, - ...assetsInitiaRegistry, -] as AssetList[]; export const skipClient = atom(new SkipClient()); @@ -27,16 +10,16 @@ export type ClientAsset = Asset & { chainName: string; }; -const flattenData = (data: Record) => { +const flattenData = (data: Record, chains?: Chain[]) => { const flattenedData: ClientAsset[] = []; for (const chainKey in data) { data[chainKey].forEach((asset: Asset) => { - const chain = chains.find((c) => c.chain_id === asset.chainID); + const chain = chains?.find((c) => c.chainID === asset.chainID); flattenedData.push({ ...asset, chain_key: chainKey, - chainName: chain?.pretty_name ?? chain?.chain_name ?? "", + chainName: chain?.prettyName ?? chain?.chainName ?? asset.chainID ?? "--", }); }); } @@ -44,8 +27,10 @@ const flattenData = (data: Record) => { return flattenedData; }; -export const skipAssets = atomWithQuery((get) => { +export const skipAssetsAtom = atomWithQuery((get) => { const skip = get(skipClient); + const chains = get(skipChainsAtom); + return { queryKey: ["skipAssets"], queryFn: async () => { @@ -55,57 +40,47 @@ export const skipAssets = atomWithQuery((get) => { includeCW20Assets: true, includeSvmAssets: true, }) - .then(flattenData); + .then((v) => flattenData(v, chains.data)); }, }; }); +export const skipChainsAtom = atomWithQuery((get) => { + const skip = get(skipClient); + return { + queryKey: ["skipChains"], + queryFn: async () => { + return skip.chains({ + includeEVM: true, + includeSVM: true, + }) + } + } +}) + export type ChainWithAsset = Chain & { asset?: ClientAsset; }; export const getChainsContainingAsset = ( assetSymbol: string, - assets: ClientAsset[] + assets: ClientAsset[], + chains: Chain[] ): ChainWithAsset[] => { if (!assets) return []; const chainIDs = assets .filter((asset) => asset.symbol === assetSymbol) .map((asset) => asset.chainID); const chainsContainingAsset = chains - .filter((chain) => chainIDs?.includes(chain.chain_id)) + .filter((chain) => chainIDs?.includes(chain.chainID)) .map((chain) => { return { ...chain, asset: assets.find( (asset) => - asset.chainID === chain.chain_id && asset.symbol === assetSymbol + asset.chainID === chain.chainID && asset.symbol === assetSymbol ), }; }); return chainsContainingAsset; }; - -export function getChain(chainId: string): Chain { - const chain = chains.find((c) => c.chain_id === chainId); - if (!chain) { - throw new Error(`chain '${chainId}' does not exist in chainRecord`); - } - return chain; -} - -export function chainIdToName(chainId: string): string { - return getChain(chainId).chain_name; -} - -export function getAssets(chainId: string) { - const chainName = chainIdToName(chainId); - const assetsFoundForChain = assets.find( - (a) => a.chain_name === chainName - )?.assets; - - if (!assetsFoundForChain) { - throw new Error(`chain '${chainId}' does not exist in assetsRecord`); - } - return assetsFoundForChain; -} diff --git a/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx b/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx index 799370abc..9af0e3e87 100644 --- a/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx +++ b/packages/widget-v2/src/stories/ManualAddressModals.stories.tsx @@ -4,7 +4,7 @@ import { Row } from "@/components/Layout"; import { defaultTheme, lightTheme } from "@/widget/theme"; import { ManualAddressModal } from "@/modals/ManualAddressModal/ManualAddressModal"; import { useEffect, useState } from "react"; -import { skipAssets } from "@/state/skipClient"; +import { skipAssetsAtom } from "@/state/skipClient"; import { destinationAssetAtom } from "@/state/swapPage"; import { useAtom } from "jotai"; @@ -24,7 +24,7 @@ export const ManualAddressModalsExample = () => { const [destinationAsset, setDestinationAsset] = useAtom(destinationAssetAtom); const [shouldRender, setShouldRender] = useState(false); - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); const asset = assets?.find((asset) => asset.denom === "uatom"); diff --git a/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx b/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx index 6f5e4ce51..112d76097 100644 --- a/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx +++ b/packages/widget-v2/src/stories/SwapExecutionPage.stories.tsx @@ -5,7 +5,7 @@ import NiceModal from "@ebay/nice-modal-react"; import { destinationAssetAtom } from "@/state/swapPage"; import { useAtom } from "jotai"; import operations from "@/pages/SwapExecutionPage/operations.json"; -import { skipAssets } from "@/state/skipClient"; +import { skipAssetsAtom } from "@/state/skipClient"; import { useEffect, useState } from "react"; const meta = { @@ -17,7 +17,7 @@ const meta = { const firstOperation = operations[0]; const lastOperation = operations[operations.length - 1]; - const [{ data: assets }] = useAtom(skipAssets); + const [{ data: assets }] = useAtom(skipAssetsAtom); const sourceAsset = assets?.find( (asset) => asset.denom === firstOperation.denomIn diff --git a/packages/widget-v2/src/utils/useUsdValue.ts b/packages/widget-v2/src/utils/useUsdValue.ts index aa4c680cf..ab8991996 100644 --- a/packages/widget-v2/src/utils/useUsdValue.ts +++ b/packages/widget-v2/src/utils/useUsdValue.ts @@ -1,7 +1,8 @@ -import { useQuery } from "@tanstack/react-query"; +import { skipAssetsAtom } from "@/state/skipClient"; +import { useQuery, UseQueryResult } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { useMemo } from "react"; import { z } from "zod"; -import { getAssets } from "@/state/skipClient"; type Args = { coingeckoID: string; @@ -37,33 +38,23 @@ const priceResponseSchema = z.object({ }); export type Asset = { - chainID: string; denom: string; - coingeckoID?: string; value: string; }; -const getCoinGeckoId = (asset: Asset) => { - if (asset.coingeckoID) { - return asset.coingeckoID; - } else { - const assets = getAssets(asset.chainID); - const assetFound = assets.find((a) => a.base === asset.denom); - if (!assetFound?.coingecko_id) { - throw new Error( - `getUsdValue error: ${asset.denom} does not have a 'coingecko_id' in ${asset.chainID}` - ); - } - return assetFound.coingecko_id; - } -}; -async function getUsdValue(asset: Asset) { - const usd = await getUsdPrice({ coingeckoID: getCoinGeckoId(asset) }); + +async function getUsdValue(asset: Asset, coingeckoID?: string) { + if (!coingeckoID) { + throw new Error(`getUsdValue error: ${asset.denom} does not have a 'coingeckoID'`); + } + const usd = await getUsdPrice({ coingeckoID }); return parseFloat(asset.value) * usd; } -export function useUsdValue(asset?: Partial) { + +export function useUsdValue(asset?: Partial): UseQueryResult { + const {data: assets} = useAtomValue(skipAssetsAtom) const queryKey = useMemo(() => ["USE_USD_VALUE", asset] as const, [asset]); const enabled = useMemo(() => { @@ -78,7 +69,8 @@ export function useUsdValue(asset?: Partial) { asset ? [key, ...Object.values(asset)].join("-") : key, queryFn: async ({ queryKey: [, asset] }) => { if (asset?.value) { - return getUsdValue(asset as Asset); + const coingeckoID = assets?.find((a) => a.denom === asset.denom)?.coingeckoID + return getUsdValue(asset as Asset, coingeckoID); } }, staleTime: 1000 * 60, // 1 minute diff --git a/yarn.lock b/yarn.lock index 789793a01..a3a99feaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28835,6 +28835,7 @@ __metadata: jotai: ^2.9.1 jotai-tanstack-query: ^0.8.6 lodash.debounce: ^4.0.8 + match-sorter: ^6.3.4 pluralize: ^8.0.0 rc-virtual-list: ^3.14.5 react-error-boundary: ^4.0.13