import 'package:hash_wallet/reactions/wallet_connect.dart'; import 'package:hash_wallet/evm/evm.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/spl_token.dart'; import 'package:cw_core/tron_token.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; class TokenUtilities { static Future> loadAllUniqueEvmTokens() async { final allWi = await WalletInfo.getAll(); final evmWallets = allWi.where( (w) => isEVMCompatibleChain(w.type), ); final seen = {}; final unique = []; for (final wallet in evmWallets) { final chain = getTokenNameBasedOnWalletType(wallet.type); final box = await _openEvmTokensBoxFor(wallet); for (final t in box.values.where((t) => t.enabled)) { final key = '$chain|${t.contractAddress.toLowerCase()}'; if (seen.add(key)) { unique.add(t); } } } return unique; } static Future> loadAllUniqueSolTokens() async { return []; } static Future> loadAllUniqueTronTokens() async { return []; } static List loadDefaultEvmTokensForSwap() { if (evm == null) return []; final tokens = []; final seen = {}; for (final chain in evm!.getAllChains()) { for (final token in evm!.getDefaultTokensByChainId(chain.chainId)) { final key = '${chain.chainId}|${token.contractAddress.toLowerCase()}'; if (seen.add(key)) tokens.add(token); } } return tokens; } static List loadDefaultSolTokensForSwap() => []; static List loadDefaultTronTokensForSwap() => []; static Future> loadEvmTokensForSwap() async { final defaultTokens = loadDefaultEvmTokensForSwap(); final userTokens = await loadAllUniqueEvmTokens(); final seen = {}; final result = []; for (final t in [...defaultTokens, ...userTokens]) { final key = '${t.tag ?? 'ETH'}|${t.contractAddress.toLowerCase()}'; if (seen.add(key)) result.add(t); } return result; } static Future> loadSolTokensForSwap() async { final defaultTokens = loadDefaultSolTokensForSwap(); final userTokens = await loadAllUniqueSolTokens(); final seen = {}; final result = []; for (final t in [...defaultTokens, ...userTokens]) { final key = t.mintAddress.toLowerCase(); if (seen.add(key)) result.add(t); } return result; } static Future> loadTronTokensForSwap() async { final defaultTokens = loadDefaultTronTokensForSwap(); final userTokens = await loadAllUniqueTronTokens(); final seen = {}; final result = []; for (final t in [...defaultTokens, ...userTokens]) { final key = t.contractAddress.toLowerCase(); if (seen.add(key)) result.add(t); } return result; } /// Finds a token by address across wallets depending on [walletType] /// - EVM chains: match by contractAddress static Future findTokenByAddress({ required WalletType walletType, required String address, }) async { if(address.isEmpty) return null; final lower = address.toLowerCase(); switch (walletType) { case WalletType.ethereum: case WalletType.polygon: case WalletType.base: case WalletType.arbitrum: case WalletType.bsc: final tokens = await loadAllUniqueEvmTokens(); for (final t in tokens) { if (t.contractAddress.toLowerCase() == lower) return t; } return null; default: return null; } } static Future> _openEvmTokensBoxFor(WalletInfo walletInfo) async { final walletKey = walletInfo.name.replaceAll(' ', '_'); final boxName = _getErc20TokensBoxName(walletKey, walletInfo.type); if (CakeHive.isBoxOpen(boxName)) { return CakeHive.box(boxName); } return CakeHive.openBox(boxName); } static String _getErc20TokensBoxName(String walletKey, WalletType walletType) { return switch (walletType) { WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}', WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}', WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}', WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}', WalletType.bsc => '${walletKey}_${Erc20Token.bscBoxName}', _ => '${walletKey}_${Erc20Token.ethereumBoxName}', }; } static Erc20Token? findErc20Token(CryptoCurrency currency, WalletBase wallet) { if (currency is Erc20Token) return currency; // More of a fallback for us for (final balanceCurrency in wallet.balance.keys) { if (balanceCurrency is Erc20Token && _matchesCurrency(balanceCurrency, currency)) { return balanceCurrency; } } return null; } static Erc20Token? findErc20TokenForSwap(CryptoCurrency currency) { if (currency is Erc20Token) return currency; for (final token in loadDefaultEvmTokensForSwap()) { if (_matchesCurrency(token, currency)) return token; } return null; } static bool isNativeToken(CryptoCurrency currency) { final title = currency.title.toLowerCase(); final tag = currency.tag?.toLowerCase(); return title == 'eth' || title == 'ethereum' || title == 'matic' || title == 'polygon' || title == 'base' || title == 'arbitrum' || title == 'bnb' || title == 'bsc' || title == 'avax' || title == 'avalanche' || tag == 'polygon' || tag == 'bsc' || tag == 'avalanche'; } static int getChainId(CryptoCurrency currency) { final tag = currency.tag?.toUpperCase(); final title = currency.title.toLowerCase(); // Only check EVM registry for currencies that might be EVM-related final isPotentialEVM = isNativeToken(currency) || (tag != null && (tag == 'ETH' || tag == 'POL' || tag == 'BASE' || tag == 'ARB' || tag == 'BSC')); if (isPotentialEVM) { // Try by tag first if available (e.g., 'POL', 'BASE', 'ARB') if (tag != null) { final chainId = evm?.getChainIdByTag(tag); if (chainId != null) return chainId; } // Try by title (case-insensitive) final titleChainId = evm?.getChainIdByTitle(title); if (titleChainId != null) return titleChainId; } // Fallback to hardcoded values for chains not in registry yet // Avalanche C-Chain if (title == 'avalanche' || title == 'avax' || tag == 'AVALANCHE') { return 43114; } // Optimism if (title == 'optimism' || title == 'op' || tag == 'OPTIMISM') { return 10; } // Fantom Opera if (title == 'fantom' || title == 'ftm' || tag == 'FANTOM') { return 250; } // Default to Ethereum mainnet return 1; } static Future> getAvailableTokensForNetwork( WalletType network, ) async { final baseCurrency = walletTypeToCryptoCurrency(network); final allTokens = [baseCurrency]; final addedAddresses = {}; // Handle EVM networks if (isEVMCompatibleChain(network)) { // First, collect all user tokens final userTokens = await _getUserTokensForNetwork(baseCurrency); for (final token in userTokens) { if (token is Erc20Token) { final address = token.contractAddress.toLowerCase(); if (addedAddresses.add(address)) { allTokens.add(token); } } } // Then add tokens from CryptoCurrency.all that don't duplicate user tokens for (final currency in CryptoCurrency.all) { // Match by tag for POL/BASE, match by title==tag for ETH final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) || (baseCurrency.tag != null && currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase()); if (matches) { if (currency is Erc20Token) { final address = currency.contractAddress.toLowerCase(); if (addedAddresses.add(address)) { allTokens.add(currency); } } else if (!allTokens.any((t) => _matchesCurrency(t, currency))) { allTokens.add(currency); } } } } return allTokens; } static bool _matchesCurrency(CryptoCurrency a, CryptoCurrency b) { return a.title.toUpperCase() == b.title.toUpperCase() && (a.tag?.toUpperCase() == b.tag?.toUpperCase()); } static Future> _getUserTokensForNetwork(CryptoCurrency baseCurrency) async { final walletType = cryptoCurrencyOrTokenToWalletType(baseCurrency); if (walletType == null) return []; if (isEVMCompatibleChain(walletType)) { final tokens = await TokenUtilities.loadAllUniqueEvmTokens(); return tokens.where((token) { if (baseCurrency.tag == null) return token.tag == baseCurrency.title; return token.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase(); }).toList(); } return []; } }