diff --git a/components/TokenSelector.tsx b/components/TokenSelector.tsx index 313f23d..6088794 100644 --- a/components/TokenSelector.tsx +++ b/components/TokenSelector.tsx @@ -1,8 +1,8 @@ "use client"; import { useState, useEffect, useMemo } from "react"; -import { useChainId } from "wagmi"; -import { getChainNameForTokenList } from "@/utils/chainMapping"; +import { useChainId, useChains } from "wagmi"; +import { getTokenListInfo } from "@/utils/chainMapping"; import { Input } from "@/components/ui/input"; export interface Token { @@ -31,12 +31,14 @@ const TokenSelector: React.FC = ({ required = false, }) => { const chainId = useChainId(); + const chains = useChains(); const [isModalOpen, setIsModalOpen] = useState(false); const [tokens, setTokens] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [isManualInput, setIsManualInput] = useState(false); const [manualAddress, setManualAddress] = useState(value); + const [fetchError, setFetchError] = useState(null); // Sync manualAddress with value prop useEffect(() => { @@ -53,19 +55,25 @@ const TokenSelector: React.FC = ({ }, [value, tokens]); // Get chain name for token list URL - const chainName = useMemo(() => getChainNameForTokenList(chainId), [chainId]); + const tokenListInfo = useMemo(() => getTokenListInfo(chainId), [chainId]); + const activeChainName = useMemo( + () => chains.find((chain) => chain.id === chainId)?.name ?? `Chain ${chainId}`, + [chainId, chains] + ); // Fetch tokens from TokenList repository useEffect(() => { const fetchTokens = async () => { - if (!chainName) { - console.warn(`No token list available for chain ID ${chainId}`); + if (!tokenListInfo) { + setTokens([]); + setFetchError(null); return; } setLoading(true); + setFetchError(null); try { - const url = `https://raw.githubusercontent.com/StabilityNexus/TokenList/main/${chainName}-tokens.json`; + const url = `https://raw.githubusercontent.com/StabilityNexus/TokenList/main/${tokenListInfo.tokenListName}-tokens.json`; const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch tokens: ${response.statusText}`); @@ -77,13 +85,16 @@ const TokenSelector: React.FC = ({ } catch (error) { console.error("Error fetching tokens:", error); setTokens([]); + setFetchError( + `Couldn't load the curated token list for ${tokenListInfo.chainLabel}. You can still enter a token address manually.` + ); } finally { setLoading(false); } }; fetchTokens(); - }, [chainName, chainId]); + }, [tokenListInfo]); // Filter tokens based on search query const filteredTokens = useMemo(() => { @@ -129,6 +140,19 @@ const TokenSelector: React.FC = ({ ); }, [value, tokens, isManualInput]); + const emptyStateMessage = useMemo(() => { + if (searchQuery) { + return "No tokens found matching your search"; + } + if (fetchError) { + return fetchError; + } + if (!tokenListInfo) { + return `No curated token list is configured for ${activeChainName}. Enter a token address manually for this network.`; + } + return `No tokens are currently available in the curated list for ${tokenListInfo.chainLabel}.`; + }, [activeChainName, fetchError, searchQuery, tokenListInfo]); + return (
@@ -203,6 +227,11 @@ const TokenSelector: React.FC = ({ {/* Search Bar */}
+

+ {tokenListInfo + ? `Showing curated tokens for ${tokenListInfo.chainLabel}.` + : `No curated token list is available for ${activeChainName}.`} +

= ({
) : filteredTokens.length === 0 ? (
- {searchQuery - ? "No tokens found matching your search" - : "No tokens available for this chain"} + {emptyStateMessage}
) : (
@@ -296,4 +323,3 @@ const TokenSelector: React.FC = ({ }; export default TokenSelector; - diff --git a/utils/chainMapping.ts b/utils/chainMapping.ts index 3f6a7de..dd20631 100644 --- a/utils/chainMapping.ts +++ b/utils/chainMapping.ts @@ -1,32 +1,32 @@ -/** - * Maps chain IDs to their corresponding token list names - * Used to fetch tokens from the Stability Nexus TokenList repository - */ -export const getChainNameForTokenList = (chainId: number): string | null => { - // Map of chain IDs to token list names - const chainMapping: Record = { - // Ethereum Mainnet - 1: "ethereum", - // Ethereum Sepolia - 11155111: "ethereum", - // Ethereum Classic - 61: "ethereum-classic", - // Polygon PoS - 137: "polygon-pos", - // Polygon Mumbai - 80001: "polygon-pos", - // Binance Smart Chain - 56: "binance-smart-chain", - // BSC Testnet - 97: "binance-smart-chain", - // Base - 8453: "base", - // Base Sepolia - 84532: "base", - // Scroll Sepolia - 534351: "ethereum", // Fallback to ethereum for now - }; +export interface TokenListInfo { + chainLabel: string; + tokenListName: string; +} - return chainMapping[chainId] || null; +// Only expose token lists that match the active chain exactly. +const TOKEN_LISTS_BY_CHAIN: Record = { + 1: { + chainLabel: "Ethereum", + tokenListName: "ethereum", + }, + 61: { + chainLabel: "Ethereum Classic", + tokenListName: "ethereum-classic", + }, + 137: { + chainLabel: "Polygon", + tokenListName: "polygon-pos", + }, + 56: { + chainLabel: "BNB Smart Chain", + tokenListName: "binance-smart-chain", + }, + 8453: { + chainLabel: "Base", + tokenListName: "base", + }, }; +export const getTokenListInfo = (chainId: number): TokenListInfo | null => { + return TOKEN_LISTS_BY_CHAIN[chainId] ?? null; +};