From 88ebba9236f9efd47f679c255288d4833882f46c Mon Sep 17 00:00:00 2001 From: Omar Hatem Date: Mon, 7 Apr 2025 18:12:39 +0200 Subject: [PATCH] Label existing scam tokens (#2164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * label existing scam tokens because users can get scammed twice ¯\_(ツ)_/¯ * minor ui fix [skip ci] --- cw_core/lib/erc20_token.dart | 4 +- cw_ethereum/lib/ethereum_wallet.dart | 6 +- cw_evm/lib/evm_chain_wallet.dart | 30 +++++++++ cw_polygon/lib/polygon_wallet.dart | 4 ++ lib/src/screens/send/send_page.dart | 98 ++++++++++++++-------------- 5 files changed, 89 insertions(+), 53 deletions(-) diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index e47488143..fd76d28fc 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -17,11 +17,11 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { @HiveField(4, defaultValue: true) bool _enabled; @HiveField(5) - final String? iconPath; + String? iconPath; @HiveField(6) final String? tag; @HiveField(7, defaultValue: false) - final bool isPotentialScam; + bool isPotentialScam; bool get enabled => _enabled; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index ae6158557..7cc140c5a 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -76,9 +76,13 @@ class EthereumWallet extends EVMChainWallet { await erc20TokensBox.deleteFromDisk(); // Add all the previous tokens with configs to the new box - evmChainErc20TokensBox.addAll(allValues); + await evmChainErc20TokensBox.addAll(allValues); } + @override + List get getDefaultTokenContractAddresses => + DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); + @override EVMChainTransactionInfo getTransactionInfo( EVMChainTransactionModel transactionModel, String address) { diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 77331758c..d640f8c14 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -144,6 +144,8 @@ abstract class EVMChainWalletBase // required WalletInfo walletInfo, // }); + List get getDefaultTokenContractAddresses; + Future initErc20TokensBox(); String getTransactionHistoryFileName(); @@ -171,6 +173,9 @@ abstract class EVMChainWalletBase await walletAddresses.init(); await transactionHistory.init(); + // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯ + await _checkForExistingScamTokens(); + if (walletInfo.isHardwareWallet) { _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address); walletAddresses.address = walletInfo.address; @@ -186,6 +191,31 @@ abstract class EVMChainWalletBase await save(); } + Future _checkForExistingScamTokens() async { + final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList(); + + for (var token in erc20Currencies) { + bool isPotentialScam = false; + + bool isWhitelisted = + getDefaultTokenContractAddresses.any((element) => element == token.contractAddress); + + final tokenSymbol = token.title.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 + if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && !isWhitelisted) { + isPotentialScam = true; + } + + if (isPotentialScam) { + token.isPotentialScam = true; + token.iconPath = null; + await token.save(); + } + } + } + @override int calculateEstimatedFee(TransactionPriority priority, int? amount) { { diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index d2b62ca4d..b2bf064b1 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -49,6 +49,10 @@ class PolygonWallet extends EVMChainWallet { } } + @override + List get getDefaultTokenContractAddresses => + DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList(); + @override Future checkIfScanProviderIsEnabled() async { bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 499bdb200..9fd55af14 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -98,7 +98,7 @@ class SendPage extends BasePage { return MergeSemantics( child: SizedBox( height: isMobileView ? 37 : 45, - width: isMobileView ? 47: 45, + width: isMobileView ? 47 : 45, child: ButtonTheme( minWidth: double.minPositive, child: Semantics( @@ -397,7 +397,6 @@ class SendPage extends BasePage { return LoadingPrimaryButton( key: ValueKey('send_page_send_button_key'), onPressed: () async { - //Request dummy node to get the focus out of the text fields FocusScope.of(context).requestFocus(FocusNode()); @@ -507,7 +506,6 @@ class SendPage extends BasePage { Navigator.of(loadingBottomSheetContext!).pop(); } - if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -525,7 +523,6 @@ class SendPage extends BasePage { } if (state is IsExecutingState) { - // wait a bit to avoid showing the loading dialog if transaction is failed await Future.delayed(const Duration(milliseconds: 300)); final currentState = sendViewModel.state; @@ -584,8 +581,6 @@ class SendPage extends BasePage { }); } - - if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { if (!context.mounted) { @@ -594,7 +589,8 @@ class SendPage extends BasePage { newContactAddress = newContactAddress ?? sendViewModel.newContactAddress(); - if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) { + if (newContactAddress?.address != null && + isRegularElectrumAddress(newContactAddress!.address)) { newContactAddress = null; } @@ -606,47 +602,51 @@ class SendPage extends BasePage { builder: (BuildContext bottomSheetContext) { return showContactSheet ? InfoBottomSheet( - currentTheme: currentTheme, - showDontAskMeCheckbox: true, - onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value), - titleText: S.of(bottomSheetContext).transaction_sent, - contentImage: 'assets/images/contact_icon.svg', - contentImageColor: Theme.of(context) - .extension()! - .titleColor, - content: S.of(bottomSheetContext).add_contact_to_address_book, - isTwoAction: true, - leftButtonText: 'No', - rightButtonText: 'Yes', - actionLeftButton: () { - Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - RequestReviewHandler.requestReview(); - newContactAddress = null; - }, - actionRightButton: () { - Navigator.of(bottomSheetContext).pop(); - RequestReviewHandler.requestReview(); - Navigator.of(context) - .pushNamed(Routes.addressBookAddContact, arguments: newContactAddress); - newContactAddress = null; - }, - ) + currentTheme: currentTheme, + showDontAskMeCheckbox: true, + onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value), + titleText: S.of(bottomSheetContext).transaction_sent, + contentImage: 'assets/images/contact_icon.svg', + contentImageColor: Theme.of(context).extension()!.titleColor, + content: S.of(bottomSheetContext).add_contact_to_address_book, + isTwoAction: true, + leftButtonText: 'No', + rightButtonText: 'Yes', + actionLeftButton: () { + Navigator.of(bottomSheetContext).pop(); + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }, + actionRightButton: () { + Navigator.of(bottomSheetContext).pop(); + RequestReviewHandler.requestReview(); + if (context.mounted) { + Navigator.of(context).pushNamed(Routes.addressBookAddContact, + arguments: newContactAddress); + } + newContactAddress = null; + }, + ) : InfoBottomSheet( - currentTheme: currentTheme, - titleText: S.of(bottomSheetContext).transaction_sent, - contentImage: 'assets/images/birthday_cake.svg', - actionButtonText: S.of(bottomSheetContext).close, - actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), - actionButton: () { - Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - RequestReviewHandler.requestReview(); - newContactAddress = null; - }, - ); + currentTheme: currentTheme, + titleText: S.of(bottomSheetContext).transaction_sent, + contentImage: 'assets/images/birthday_cake.svg', + actionButtonText: S.of(bottomSheetContext).close, + actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), + actionButton: () { + Navigator.of(bottomSheetContext).pop(); + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }, + ); }, ); @@ -678,8 +678,7 @@ class SendPage extends BasePage { currentTheme: currentTheme, titleText: S.of(bottomSheetContext).proceed_on_device, contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png', - contentImageColor: - Theme.of(context).extension()!.titleColor, + contentImageColor: Theme.of(context).extension()!.titleColor, content: S.of(bottomSheetContext).proceed_on_device_description, isTwoAction: false, actionButtonText: S.of(context).cancel, @@ -778,5 +777,4 @@ class SendPage extends BasePage { return isValid; } - }