Label existing scam tokens (#2164)

* label existing scam tokens because users can get scammed twice ¯\_(ツ)_/¯

* minor ui fix [skip ci]
This commit is contained in:
Omar Hatem 2025-04-07 18:12:39 +02:00 committed by GitHub
parent 9ac784db5c
commit 88ebba9236
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89 additions and 53 deletions

View file

@ -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;

View file

@ -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<String> get getDefaultTokenContractAddresses =>
DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
@override
EVMChainTransactionInfo getTransactionInfo(
EVMChainTransactionModel transactionModel, String address) {

View file

@ -144,6 +144,8 @@ abstract class EVMChainWalletBase
// required WalletInfo walletInfo,
// });
List<String> get getDefaultTokenContractAddresses;
Future<void> 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<void> _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) {
{

View file

@ -49,6 +49,10 @@ class PolygonWallet extends EVMChainWallet {
}
}
@override
List<String> get getDefaultTokenContractAddresses =>
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
@override
Future<bool> checkIfScanProviderIsEnabled() async {
bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true;

View file

@ -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<void>(
@ -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<CakeTextTheme>()!
.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<CakeTextTheme>()!.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<CakeTextTheme>()!.titleColor,
contentImageColor: Theme.of(context).extension<CakeTextTheme>()!.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;
}
}