"use client" import * as React from 'react'; import { cn } from '../../../lib/utils'; export interface TokenIconProps extends React.HTMLAttributes { symbol: string variant?: 'branded' | 'mono' size?: number network?: string address?: string } /** * Get all available token symbols from @web3icons/react * Uses dynamic import to avoid bundling all icons */ export async function getAllTokenSymbols(): Promise { try { const { tokenIcons } = await import('@web3icons/react') return Object.keys(tokenIcons).map(key => key.replace(/^Token/, '').replace(/_/g, '-') ).sort() } catch { return [] } } /** * Search tokens by symbol */ export async function searchTokens(query: string): Promise { const allTokens = await getAllTokenSymbols() const lowerQuery = query.toLowerCase() return allTokens.filter(symbol => symbol.toLowerCase().includes(lowerQuery) ) } /** * Get popular tokens grouped by category */ export interface TokenCategory { name: string tokens: string[] } export async function getTokensByCategory(): Promise { const allTokens = await getAllTokenSymbols() // Top tokens by market cap const topTokens = ['BTC', 'ETH', 'USDT', 'BNB', 'SOL', 'USDC', 'XRP', 'DOGE', 'ADA', 'TRX'] .filter(t => allTokens.includes(t)) const stablecoins = ['USDT', 'USDC', 'BUSD', 'DAI', 'TUSD', 'USDP', 'GUSD', 'FRAX'] .filter(t => allTokens.includes(t)) const defi = ['UNI', 'AAVE', 'MKR', 'CRV', 'CAKE', 'SUSHI', 'BAL', 'COMP', 'YFI', 'SNX', 'LINK'] .filter(t => allTokens.includes(t)) const layer1 = ['ETH', 'BNB', 'SOL', 'ADA', 'AVAX', 'DOT', 'ATOM', 'NEAR', 'ALGO', 'FTM'] .filter(t => allTokens.includes(t)) const layer2 = ['MATIC', 'OP', 'ARB', 'IMX', 'LRC', 'METIS'] .filter(t => allTokens.includes(t)) const memeCoins = ['DOGE', 'SHIB', 'PEPE', 'FLOKI', 'ELON', 'BONK'] .filter(t => allTokens.includes(t)) return [ { name: 'Top 10', tokens: topTokens }, { name: 'Stablecoins', tokens: stablecoins }, { name: 'DeFi', tokens: defi }, { name: 'Layer 1', tokens: layer1 }, { name: 'Layer 2', tokens: layer2 }, { name: 'Meme Coins', tokens: memeCoins }, ].filter(cat => cat.tokens.length > 0) } export type TokenSymbol = string // Dynamic import for @web3icons/react tokens const tokenIconCache = new Map>() async function getTokenIcon(symbol: string): Promise | null> { const normalizedSymbol = symbol.toUpperCase().replace(/[^A-Z0-9]/g, '_') const componentName = `Token${normalizedSymbol}` if (tokenIconCache.has(componentName)) { return tokenIconCache.get(componentName)! } try { const module = await import('@web3icons/react') const Component = (module as any)[componentName] if (Component) { tokenIconCache.set(componentName, Component) return Component } } catch (e) { // Token not found } return null } export function TokenIcon({ symbol, variant = 'branded', size = 24, className, network, address, ...props }: TokenIconProps) { const [IconComponent, setIconComponent] = React.useState | null>(null) const [_loading, setLoading] = React.useState(true) React.useEffect(() => { let mounted = true getTokenIcon(symbol).then((component) => { if (mounted) { setIconComponent(() => component) setLoading(false) } }) return () => { mounted = false } }, [symbol]) // Pre-compute fallback values const fallbackFontSize = Math.max(8, size * 0.4) const truncatedSymbol = symbol?.slice(0, 3) || '?' return (
{IconComponent ? ( ) : ( // Fallback: show symbol as text {truncatedSymbol} )}
) }