From 57fe3287faffe34d65bd7119f52c9dac9aca0c49 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Wed, 26 Feb 2025 19:49:57 -0800 Subject: [PATCH] Cw 939 whitelist known tokens (#2038) * [skip-ci] init * don't get price data for potential scam tokens * updates * dont fetch fiat price for scam currencies --- cw_core/lib/crypto_currency.dart | 4 +- lib/ethereum/cw_ethereum.dart | 5 ++ lib/polygon/cw_polygon.dart | 5 ++ lib/reactions/fiat_rate_update.dart | 5 +- lib/solana/cw_solana.dart | 5 ++ .../screens/dashboard/edit_token_page.dart | 24 ++++++++-- lib/tron/cw_tron.dart | 5 ++ .../dashboard/home_settings_view_model.dart | 47 ++++++++++++++++--- tool/configure.dart | 8 ++++ 9 files changed, 97 insertions(+), 11 deletions(-) diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 29dc03f9e..00d49c288 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -11,7 +11,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen this.iconPath, this.tag, this.enabled = false, - }) + this.isPotentialScam = false, + }) : super(title: title, raw: raw); final String name; @@ -20,6 +21,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen final String? iconPath; final int decimals; final bool enabled; + final bool isPotentialScam; set enabled(bool value) => this.enabled = value; diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 413cafc30..dc91e4fc2 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -209,4 +209,9 @@ class CWEthereum extends Ethereum { throw err; } } + + @override + List getDefaultTokenContractAddresses() { + return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); + } } diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index 7c447406d..b8f78e9e2 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -208,4 +208,9 @@ class CWPolygon extends Polygon { throw err; } } + + @override + List getDefaultTokenContractAddresses() { + return DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList(); + } } diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index 62710c515..de3dea4a2 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -60,9 +60,12 @@ Future startFiatRateUpdate( tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled); } - if (currencies != null) { for (final currency in currencies) { + // skip potential scams: + if (currency.isPotentialScam) { + continue; + } () async { fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( crypto: currency, diff --git a/lib/solana/cw_solana.dart b/lib/solana/cw_solana.dart index 7894f77ed..d8257396f 100644 --- a/lib/solana/cw_solana.dart +++ b/lib/solana/cw_solana.dart @@ -154,4 +154,9 @@ class CWSolana extends Solana { double? getEstimateFees(WalletBase wallet) { return (wallet as SolanaWallet).estimatedFee; } + + @override + List getDefaultTokenContractAddresses() { + return DefaultSPLTokens().initialSPLTokens.map((e) => e.mintAddress).toList(); + } } diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index bb0d4a3e2..11a584b39 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -211,6 +211,21 @@ class _EditTokenPageBodyState extends State { .checkIfERC20TokenContractAddressIsAPotentialScamAddress( _contractAddressController.text, ); + + final isWhitelisted = await widget.homeSettingsViewModel + .checkIfTokenIsWhitelisted(_contractAddressController.text); + + bool isPotentialScam = hasPotentialError; + final tokenSymbol = _tokenSymbolController.text.toUpperCase(); + + // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc): + // if it is, then it's probably a scam unless it's in the whitelist + final baseCurrencySymbols = + CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList(); + if (baseCurrencySymbols.contains(tokenSymbol) && !isWhitelisted) { + isPotentialScam = true; + } + final actionCall = () async { try { await widget.homeSettingsViewModel.addToken( @@ -219,6 +234,7 @@ class _EditTokenPageBodyState extends State { title: _tokenSymbolController.text.toUpperCase(), decimals: int.parse(_tokenDecimalController.text), iconPath: _tokenIconPathController.text, + isPotentialScam: isPotentialScam, ), contractAddress: _contractAddressController.text, ); @@ -226,7 +242,6 @@ class _EditTokenPageBodyState extends State { if (mounted) { Navigator.pop(context); } - } catch (e) { showPopUp( context: context, @@ -303,7 +318,8 @@ class _EditTokenPageBodyState extends State { if (token != null) { final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano; if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name; - if (_tokenSymbolController.text.isEmpty || isZano) _tokenSymbolController.text = token.title; + if (_tokenSymbolController.text.isEmpty || isZano) + _tokenSymbolController.text = token.title; if (_tokenIconPathController.text.isEmpty) _tokenIconPathController.text = token.iconPath ?? ''; if (_tokenDecimalController.text.isEmpty || isZano) @@ -338,7 +354,9 @@ class _EditTokenPageBodyState extends State { placeholder: S.of(context).token_contract_address, options: [AddressTextFieldOption.paste], buttonColor: Theme.of(context).hintColor, - validator: widget.homeSettingsViewModel.walletType == WalletType.zano ? null : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call, + validator: widget.homeSettingsViewModel.walletType == WalletType.zano + ? null + : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call, onPushPasteButton: (_) { _pasteText(); }, diff --git a/lib/tron/cw_tron.dart b/lib/tron/cw_tron.dart index 8bceafe01..bf2fac590 100644 --- a/lib/tron/cw_tron.dart +++ b/lib/tron/cw_tron.dart @@ -133,4 +133,9 @@ class CWTron extends Tron { void updateTronGridUsageState(WalletBase wallet, bool isEnabled) { (wallet as TronWallet).updateScanProviderUsageState(isEnabled); } + + @override + List getDefaultTokenContractAddresses() { + return DefaultTronTokens().initialTronTokens.map((e) => e.contractAddress).toList(); + } } diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 197d550c3..e3e02a045 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -114,13 +114,12 @@ abstract class HomeSettingsViewModelBase with Store { if (_balanceViewModel.wallet.type == WalletType.zano) { await zano!.addZanoAssetById(_balanceViewModel.wallet, contractAddress); } - + _updateTokensList(); _updateFiatPrices(token); } catch (e) { throw e; - } - finally { + } finally { isAddingToken = false; } } @@ -189,6 +188,40 @@ abstract class HomeSettingsViewModelBase with Store { } } + bool checkIfTokenIsWhitelisted(String contractAddress) { + // get the default tokens for each currency type: + List defaultTokenAddresses = []; + switch (_balanceViewModel.wallet.type) { + case WalletType.ethereum: + defaultTokenAddresses = ethereum!.getDefaultTokenContractAddresses(); + break; + case WalletType.polygon: + defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses(); + break; + case WalletType.solana: + defaultTokenAddresses = solana!.getDefaultTokenContractAddresses(); + break; + case WalletType.tron: + defaultTokenAddresses = tron!.getDefaultTokenContractAddresses(); + break; + case WalletType.zano: + case WalletType.banano: + case WalletType.monero: + case WalletType.none: + case WalletType.bitcoin: + case WalletType.litecoin: + case WalletType.haven: + case WalletType.nano: + case WalletType.wownero: + case WalletType.bitcoinCash: + return false; + } + + // check if the contractAddress is in the defaultTokenAddresses + bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress); + return isInWhitelist; + } + Future _isPotentialScamTokenViaMoralis( String contractAddress, String chainName, @@ -363,6 +396,7 @@ abstract class HomeSettingsViewModelBase with Store { CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency; void _updateFiatPrices(CryptoCurrency token) async { + if (token.isPotentialScam) return; // don't fetch price data for potential scam tokens try { _balanceViewModel.fiatConvertationStore.prices[token] = await FiatConversionService.fetchPrice( @@ -455,9 +489,10 @@ abstract class HomeSettingsViewModelBase with Store { } if (_balanceViewModel.wallet.type == WalletType.zano) { - tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet) - .where((element) => _matchesSearchText(element)) - .toList() + tokens.addAll(zano! + .getZanoAssets(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() ..sort(_sortFunc)); } } diff --git a/tool/configure.dart b/tool/configure.dart index 7fb72d5e4..214288078 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -872,6 +872,7 @@ import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_wallet.dart'; import 'package:cw_ethereum/ethereum_wallet_service.dart'; +import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; import 'package:eth_sig_util/util/utils.dart'; @@ -922,6 +923,7 @@ abstract class Ethereum { void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + List getDefaultTokenContractAddresses(); } """; @@ -977,6 +979,7 @@ import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_wallet.dart'; import 'package:cw_polygon/polygon_wallet_service.dart'; +import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; import 'package:eth_sig_util/util/utils.dart'; @@ -1027,6 +1030,7 @@ abstract class Polygon { void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + List getDefaultTokenContractAddresses(); } """; @@ -1269,6 +1273,7 @@ import 'package:cw_solana/solana_wallet_service.dart'; import 'package:cw_solana/solana_transaction_info.dart'; import 'package:cw_solana/solana_transaction_credentials.dart'; import 'package:cw_solana/solana_wallet_creation_credentials.dart'; +import 'package:cw_solana/default_spl_tokens.dart'; """; const solanaCwPart = "part 'cw_solana.dart';"; const solanaContent = """ @@ -1310,6 +1315,7 @@ abstract class Solana { String getTokenAddress(CryptoCurrency asset); List? getValidationLength(CryptoCurrency type); double? getEstimateFees(WalletBase wallet); + List getDefaultTokenContractAddresses(); } """; @@ -1355,6 +1361,7 @@ import 'package:cw_tron/tron_client.dart'; import 'package:cw_tron/tron_token.dart'; import 'package:cw_tron/tron_wallet.dart'; import 'package:cw_tron/tron_wallet_service.dart'; +import 'package:cw_tron/default_tron_tokens.dart'; """; const tronCwPart = "part 'cw_tron.dart';"; @@ -1386,6 +1393,7 @@ abstract class Tron { String? getTronTRC20EstimatedFee(WalletBase wallet); void updateTronGridUsageState(WalletBase wallet, bool isEnabled); + List getDefaultTokenContractAddresses(); } """;