part of 'evm.dart'; class CWEVM extends EVM { @override List getEVMWordList(String language) => EVMChainMnemonics.englishWordlist; @override WalletService createEVMWalletService(WalletType walletType, bool isDirect) { return EVMChainWalletService(isDirect); } @override WalletCredentials createEVMNewWalletCredentials({ required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase, }) { return EVMChainNewWalletCredentials( name: name, walletInfo: walletInfo, password: password, mnemonic: mnemonic, passphrase: passphrase, ); } @override WalletCredentials createEVMRestoreWalletFromSeedCredentials({ required String name, required String mnemonic, required String password, String? passphrase, }) { return EVMChainRestoreWalletFromSeedCredentials( name: name, password: password, mnemonic: mnemonic, passphrase: passphrase, ); } @override WalletCredentials createEVMRestoreWalletFromPrivateKey({ required String name, required String privateKey, required String password, }) { return EVMChainRestoreWalletFromPrivateKey( name: name, password: password, privateKey: privateKey, ); } @override WalletCredentials createEVMHardwareWalletCredentials({ required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo, }) { return EVMChainRestoreWalletFromHardware( name: name, hwAccountData: hwAccountData, walletInfo: walletInfo, ); } @override String getAddress(WalletBase wallet) => (wallet as EVMChainWallet).walletAddresses.address; @override String getPrivateKey(WalletBase wallet) { final privateKeyHolder = (wallet as EVMChainWallet).evmChainPrivateKey; if (privateKeyHolder is EthPrivateKey) { return bytesToHex(privateKeyHolder.privateKey); } return ""; } @override String getPublicKey(WalletBase wallet) { final privateKeyInUnitInt = (wallet as EVMChainWallet).evmChainPrivateKey; return privateKeyInUnitInt.address.hex; } @override TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium; @override TransactionPriority getEVMTransactionPrioritySlow() => EVMChainTransactionPriority.slow; @override List getTransactionPriorities() => EVMChainTransactionPriority.all; @override TransactionPriority deserializeEVMTransactionPriority(int raw) => EVMChainTransactionPriority.deserialize(raw: raw); @override Object createEVMTransactionCredentials( List outputs, { required CryptoCurrency currency, TransactionPriority? priority, int? feeRate, bool useBlinkProtection = true, }) { return EVMChainTransactionCredentials( outputs .map((out) => OutputInfo( fiatAmount: out.fiatAmount, cryptoAmount: out.cryptoAmount, address: out.address, note: out.note, sendAll: out.sendAll, extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount, memo: out.memo)) .toList(), priority: priority as EVMChainTransactionPriority?, currency: currency, feeRate: feeRate, useBlinkProtection: useBlinkProtection, ); } @override Object createEVMTransactionCredentialsRaw( List outputs, { TransactionPriority? priority, required CryptoCurrency currency, required int feeRate, bool useBlinkProtection = true, }) { return EVMChainTransactionCredentials( outputs, priority: priority as EVMChainTransactionPriority?, currency: currency, feeRate: feeRate, useBlinkProtection: useBlinkProtection, ); } @override int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount); @override double formatterEVMAmountToDouble({ TransactionInfo? transaction, BigInt? amount, int exponent = 18, }) { assert(transaction != null || amount != null); if (transaction != null) { transaction as EVMChainTransactionInfo; return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); } else { return (amount!) / BigInt.from(10).pow(exponent); } } @override List getERC20Currencies(WalletBase wallet) => (wallet as EVMChainWallet).erc20Currencies; @override Future addErc20Token(WalletBase wallet, CryptoCurrency token) => (wallet as EVMChainWallet).addErc20Token(token as Erc20Token); @override Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) => (wallet as EVMChainWallet).deleteErc20Token(token as Erc20Token); @override Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) => (wallet as EVMChainWallet).removeTokenTransactionsInHistory(token as Erc20Token); @override Future getErc20Token(WalletBase wallet, String contractAddress) { final evmWallet = wallet as EVMChainWallet; final chainName = EVMChainUtils.getDefaultTokenSymbol(evmWallet.selectedChainId).toLowerCase(); return evmWallet.getErc20Token(contractAddress, chainName); } @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { transaction as EVMChainTransactionInfo; final evmWallet = wallet as EVMChainWallet; final nativeCurrency = evmWallet.currency; final nativeCurrencyTitle = nativeCurrency.title; final currentChainId = evmWallet.selectedChainId; // If transaction is from a different chain, we will return native currency as fallback // This can happen during chain switching when old transactions are still visible if (transaction.chainId != currentChainId) { return nativeCurrency; } if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title || transaction.tokenSymbol == "MATIC") { return CryptoCurrency.maticpoly; } if (transaction.tokenSymbol == nativeCurrencyTitle) { return nativeCurrency; } // Otherwise, it's an ERC20 token // Also using firstWhereOrNull to handle cases where token isn't found (e.g., during chain switch) final erc20Token = evmWallet.erc20Currencies.firstWhereOrNull( (element) => transaction.contractAddress?.toLowerCase() == element.contractAddress.toLowerCase(), ); return erc20Token ?? nativeCurrency; } @override void updateScanProviderUsageState(WalletBase wallet, bool isEnabled) => (wallet as EVMChainWallet).updateScanProviderUsageState(isEnabled); @override Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EVMChainWallet).getWeb3Client(); @override Future getTransactionReceipt(WalletBase wallet, String txHash) async { final client = getWeb3Client(wallet); if (client == null) return null; try { final receipt = await client.getTransactionReceipt(txHash); if (receipt == null) return null; return receipt.status; } catch (_) { return null; } } @override String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override Future isApprovalRequired( WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount, ) => (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount); @override Future getAllowance(WalletBase wallet, String tokenContract, String spender) => (wallet as EVMChainWallet).getAllowance(tokenContract, spender); @override Future createTokenApproval( WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority? priority, { bool useBlinkProtection = true, }) { final evmWallet = wallet as EVMChainWallet; final feeCurrency = EVMChainUtils.getFeeCurrency(evmWallet.selectedChainId); return evmWallet.createApprovalTransaction( amount, spender, token, priority as EVMChainTransactionPriority?, feeCurrency, useBlinkProtection: useBlinkProtection, ); } @override Future createRawCallDataTransaction( WalletBase wallet, String to, String dataHex, BigInt valueWei, TransactionPriority? priority, { bool useBlinkProtection = true, String? sourceTokenAddress, BigInt? sourceTokenAmount, }) => (wallet as EVMChainWallet).createCallDataTransaction( to, dataHex, valueWei, priority as EVMChainTransactionPriority?, sourceTokenAddress, sourceTokenAmount, useBlinkProtection: useBlinkProtection, ); @override Future setHardwareWalletService( WalletBase wallet, HardwareWalletService service, ) async { final evmWallet = wallet as EVMChainWallet; final privateKey = evmWallet.evmChainPrivateKey; final derivationPath = (await wallet.walletInfo.getDerivationInfo()).derivationPath; if (service is EVMChainLedgerService) { (privateKey as EvmLedgerCredentials) .setLedgerConnection(service.ledgerConnection, derivationPath); } else if (service is EVMChainBitboxService) { (privateKey as EvmBitboxCredentials).setBitbox(service.manager, derivationPath); } else if (service is EVMChainTrezorService) { (privateKey as EvmTrezorCredentials).setTrezorConnect(service.connect, derivationPath); } } @override HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => EVMChainLedgerService(connection); @override HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) => EVMChainBitboxService(manager); @override HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) => EVMChainTrezorService(connect); @override List getDefaultTokensByChainId(int chainId) => EVMChainDefaultTokens.getDefaultTokensByChainId(chainId); @override List getDefaultTokenContractAddresses(WalletBase wallet) { final chainId = getSelectedChainId(wallet); if (chainId == null) return []; return EVMChainDefaultTokens.getDefaultTokenAddresses(chainId); } @override List getDefaultTokenSymbols(WalletBase wallet) { final chainId = getSelectedChainId(wallet); if (chainId == null) return []; return EVMChainDefaultTokens.getDefaultTokenSymbols(chainId); } @override bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) { final evmWallet = wallet as EVMChainWallet; return evmWallet.erc20Currencies .any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase()); } @override String? getEVMNativeEstimatedFee(WalletBase wallet) => (wallet as EVMChainWallet).nativeTxEstimatedFee; @override String? getEVMERC20EstimatedFee(WalletBase wallet) => (wallet as EVMChainWallet).erc20TxEstimatedFee; // Chain-specific integrations (only for Ethereum) @override Future? getDEuroSavingsBalance(WalletBase wallet) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).savingsBalance; } return null; } @override Future? getDEuroAccruedInterest(WalletBase wallet) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).accruedInterest; } return null; } @override Future? getDEuroInterestRate(WalletBase wallet) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).interestRate; } return null; } @override Future? getDEuroSavingsApproved(WalletBase wallet) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).approvedBalance; } return null; } @override Future? addDEuroSaving( WalletBase wallet, BigInt amount, TransactionPriority priority) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).depositSavings(amount, priority as EVMChainTransactionPriority); } return null; } @override Future? removeDEuroSaving( WalletBase wallet, BigInt amount, TransactionPriority priority) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).withdrawSavings(amount, priority as EVMChainTransactionPriority); } return null; } @override Future? reinvestDEuroInterest( WalletBase wallet, TransactionPriority priority) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).reinvestInterest(priority as EVMChainTransactionPriority); } return null; } @override Future? enableDEuroSaving(WalletBase wallet, TransactionPriority priority) { if (wallet.chainId == 1 && wallet is EVMChainWallet) { return DEuro(wallet).enableSavings(priority as EVMChainTransactionPriority); } return null; } // Registry helper methods static final EvmChainRegistry _registry = EvmChainRegistry(); @override int getChainIdByWalletType(WalletType walletType) { final config = _registry.getChainConfigByWalletType(walletType); return config?.chainId ?? 1; // Default to Ethereum } @override String getChainNameByWalletType(WalletType walletType) { final config = _registry.getChainConfigByWalletType(walletType); return config?.shortCode ?? 'eth'; } @override String getTokenNameByWalletType(WalletType walletType) { final config = _registry.getChainConfigByWalletType(walletType); return config?.nativeCurrency.title ?? 'ETH'; } @override String getCaip2ByChainId(int chainId) { final config = _registry.getChainConfig(chainId); return config?.caip2 ?? 'eip155:1'; } @override String getChainNameByChainId(int chainId) { final config = _registry.getChainConfig(chainId); return config?.shortCode ?? 'eth'; } @override String getTokenNameByChainId(int chainId) { final config = _registry.getChainConfig(chainId); return config?.nativeCurrency.title ?? 'ETH'; } @override int? getChainIdByTag(String tag) { final config = _registry.getChainConfigByTag(tag); return config?.chainId; } @override int? getChainIdByTitle(String title) { // Try as tag first (uppercase) final tagResult = getChainIdByTag(title.toUpperCase()); if (tagResult != null) return tagResult; // Try as lowercase title return getChainIdByTag(title.toLowerCase()); } @override WalletType? getWalletTypeByChainId(int chainId) { return _registry.getWalletTypeByChainId(chainId); } @override BigInt? getERC20AvailableBalance(Object balance) { if (balance is EVMChainERC20Balance) { return balance.balance; } return null; } @override List getAllChains() { final allChains = _registry.getAllChains(); return allChains .map((config) => ChainInfo( chainId: config.chainId, name: config.name, shortCode: config.shortCode, currency: config.nativeCurrency, )) .toList(); } @override ChainInfo? getChainInfoByChainId(int chainId) { final config = _registry.getChainConfig(chainId); if (config == null) return null; return ChainInfo( chainId: config.chainId, name: config.name, shortCode: config.shortCode, currency: config.nativeCurrency, ); } @override ChainInfo? getCurrentChain(WalletBase wallet) { if (wallet is EVMChainWallet) { final config = wallet.selectedChainConfig; if (config == null) return null; return ChainInfo( chainId: config.chainId, name: config.name, shortCode: config.shortCode, currency: config.nativeCurrency, ); } return null; } @override int? getSelectedChainId(WalletBase wallet) { if (wallet is EVMChainWallet) { return wallet.selectedChainId; } return null; } @override Future selectChain(WalletBase wallet, int chainId, {required Node node}) async { if (wallet is EVMChainWallet) { await wallet.selectChain(chainId, node: node); } } @override String? getExplorerUrlForChainId(int chainId, {bool showProtocol = true}) { final config = _registry.getChainConfig(chainId); if (config != null && config.explorerUrls.isNotEmpty) { final url = config.explorerUrls.first; return showProtocol ? url : url.replaceAll('https://', '').replaceAll('http://', '').split('/')[0]; } return null; } @override bool hasPriorityFee(int chainId) => EVMChainUtils.hasPriorityFee(chainId); @override bool isUSDT0Token(WalletBase wallet, CryptoCurrency token) { if (token is! Erc20Token) return false; final chainId = getSelectedChainId(wallet); if (chainId == null) return false; return USDT0Config.isUSDT0Token(token, chainId); } @override List getUSDT0DestinationChains(WalletBase wallet) { final currentChainId = getSelectedChainId(wallet); if (currentChainId == null) return []; final result = []; for (final config in _registry.getAllChains()) { if (USDT0Config.isChainSupported(config.chainId) && config.chainId != currentChainId) { result.add(ChainInfo( chainId: config.chainId, name: config.name, shortCode: config.shortCode, currency: config.nativeCurrency, )); } } return result; } @override Future quoteUSDT0Transfer({ required WalletBase wallet, required int sourceChainId, required int destinationChainId, required BigInt amount, required String recipientAddress, }) async { final evmWallet = wallet as EVMChainWallet; final client = evmWallet.getWeb3Client(); if (client == null) { throw StateError('Wallet not connected'); } final quote = await USDT0Service.quoteCrossChainTransfer( client: client, sourceChainId: sourceChainId, destinationChainId: destinationChainId, amount: amount, recipientAddress: recipientAddress, ); return BridgeQuote( nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee, ); } @override Future executeUSDT0Transfer({ required WalletBase wallet, required CryptoCurrency token, required int sourceChainId, required int destinationChainId, required BigInt amount, required String recipientAddress, required BridgeQuote quote, required TransactionPriority priority, bool useBlinkProtection = true, }) { final evmWallet = wallet as EVMChainWallet; final tokenErc20 = token as Erc20Token; return USDT0Service.executeCrossChainTransfer( wallet: evmWallet, sourceChainId: sourceChainId, destinationChainId: destinationChainId, amount: amount, recipientAddress: recipientAddress, quote: USDT0Quote(nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee), token: tokenErc20, priority: priority as EVMChainTransactionPriority, useBlinkProtection: useBlinkProtection, ); } Future getWCBufferedFeeQuote( WalletBase wallet, TransactionPriority priority, ) async { if (wallet is! EVMChainWallet) return null; final data = await wallet.getWCBufferedFeeQuote(priority); if (data == null) return null; return EvmWalletConnectFeeQuote( maxFeePerGasWei: data.maxFeePerGasWei, maxPriorityFeePerGasWei: data.maxPriorityFeePerGasWei, latestBaseFeeWei: data.latestBaseFeeWei, ); } Future<({double usdValue, bool hasValidFiatPrice})> _getTokenUsdValueAndFiatCheck( Erc20Token token, BigInt balanceWei, ) async { try { final settingsStore = getIt.get(); final torOnly = settingsStore.fiatApiMode == FiatApiMode.torOnly; final price = await FiatConversionService.fetchPrice( crypto: token, fiat: FiatCurrency.usd, torOnly: torOnly, ); final hasValidFiatPrice = price > 0; final decimals = token.decimal; final balance = balanceWei.toDouble() / math.pow(10, decimals); final usdValue = balance * price; return (usdValue: usdValue, hasValidFiatPrice: hasValidFiatPrice); } catch (e) { return (usdValue: 0.0, hasValidFiatPrice: false); } } static const _minTokenUsdValue = 0.1; @override Future discoverAndAddWalletTokens(WalletBase wallet) async { if (wallet is! EVMChainWallet) return; try { final result = await wallet.discoverTokensFromMoralis(); if (result.newTokens.isEmpty) return; final List> tokenChecks = []; for (final item in result.newTokens) { tokenChecks.add((() async { final token = item.token; final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token); final fiatResult = await _getTokenUsdValueAndFiatCheck( token, item.balanceWei, ); final isSpam = isPropertiesSuspicious || !fiatResult.hasValidFiatPrice; token.isPotentialScam = isSpam; token.enabled = (fiatResult.usdValue >= _minTokenUsdValue) && !isSpam; await wallet.addErc20Token(token); })()); } await Future.wait(tokenChecks); } catch (_) {} } }