Label existing scam tokens (#2164)
* label existing scam tokens because users can get scammed twice ¯\_(ツ)_/¯ * minor ui fix [skip ci]
Omar Hatem committed
Apr 7, 2025 at 18:12 UTC
88ebba9236f9efd47f679c255288d4833882f46c
5 files changed
+89
-53
cw_core/lib/erc20_token.dart
+2
-2
@@ -17,11 +17,11 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
17
@HiveField(4, defaultValue: true)
18
bool _enabled;
19
@HiveField(5)
20
- final String? iconPath;
20
+ String? iconPath;
21
@HiveField(6)
22
final String? tag;
23
@HiveField(7, defaultValue: false)
24
- final bool isPotentialScam;
24
+ bool isPotentialScam;
25
26
bool get enabled => _enabled;
27
cw_ethereum/lib/ethereum_wallet.dart
+5
-1
@@ -76,9 +76,13 @@ class EthereumWallet extends EVMChainWallet {
76
await erc20TokensBox.deleteFromDisk();
77
78
// Add all the previous tokens with configs to the new box
79
- evmChainErc20TokensBox.addAll(allValues);
79
+ await evmChainErc20TokensBox.addAll(allValues);
80
}
81
82
+ @override
83
+ List<String> get getDefaultTokenContractAddresses =>
84
+ DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
85
+
86
@override
87
EVMChainTransactionInfo getTransactionInfo(
88
EVMChainTransactionModel transactionModel, String address) {
cw_evm/lib/evm_chain_wallet.dart
+30
@@ -144,6 +144,8 @@ abstract class EVMChainWalletBase
144
// required WalletInfo walletInfo,
145
// });
146
147
+ List<String> get getDefaultTokenContractAddresses;
148
+
149
Future<void> initErc20TokensBox();
150
151
String getTransactionHistoryFileName();
@@ -171,6 +173,9 @@ abstract class EVMChainWalletBase
173
await walletAddresses.init();
174
await transactionHistory.init();
175
176
+ // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯
177
+ await _checkForExistingScamTokens();
178
+
179
if (walletInfo.isHardwareWallet) {
180
_evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
181
walletAddresses.address = walletInfo.address;
@@ -186,6 +191,31 @@ abstract class EVMChainWalletBase
191
await save();
192
}
193
194
+ Future<void> _checkForExistingScamTokens() async {
195
+ final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
196
+
197
+ for (var token in erc20Currencies) {
198
+ bool isPotentialScam = false;
199
+
200
+ bool isWhitelisted =
201
+ getDefaultTokenContractAddresses.any((element) => element == token.contractAddress);
202
+
203
+ final tokenSymbol = token.title.toUpperCase();
204
+
205
+ // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc):
206
+ // if it is, then it's probably a scam unless it's in the whitelist
207
+ if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && !isWhitelisted) {
208
+ isPotentialScam = true;
209
+ }
210
+
211
+ if (isPotentialScam) {
212
+ token.isPotentialScam = true;
213
+ token.iconPath = null;
214
+ await token.save();
215
+ }
216
+ }
217
+ }
218
+
219
@override
220
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
221
{
cw_polygon/lib/polygon_wallet.dart
+4
@@ -49,6 +49,10 @@ class PolygonWallet extends EVMChainWallet {
49
}
50
}
51
52
+ @override
53
+ List<String> get getDefaultTokenContractAddresses =>
54
+ DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
55
+
56
@override
57
Future<bool> checkIfScanProviderIsEnabled() async {
58
bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true;
lib/src/screens/send/send_page.dart
+48
-50
@@ -98,7 +98,7 @@ class SendPage extends BasePage {
98
return MergeSemantics(
99
child: SizedBox(
100
height: isMobileView ? 37 : 45,
101
- width: isMobileView ? 47: 45,
101
+ width: isMobileView ? 47 : 45,
102
child: ButtonTheme(
103
minWidth: double.minPositive,
104
child: Semantics(
@@ -397,7 +397,6 @@ class SendPage extends BasePage {
397
return LoadingPrimaryButton(
398
key: ValueKey('send_page_send_button_key'),
399
onPressed: () async {
400
-
400
//Request dummy node to get the focus out of the text fields
401
FocusScope.of(context).requestFocus(FocusNode());
402
@@ -507,7 +506,6 @@ class SendPage extends BasePage {
506
Navigator.of(loadingBottomSheetContext!).pop();
507
}
508
510
-
509
if (state is FailureState) {
510
WidgetsBinding.instance.addPostFrameCallback((_) {
511
showPopUp<void>(
@@ -525,7 +523,6 @@ class SendPage extends BasePage {
523
}
524
525
if (state is IsExecutingState) {
528
-
526
// wait a bit to avoid showing the loading dialog if transaction is failed
527
await Future.delayed(const Duration(milliseconds: 300));
528
final currentState = sendViewModel.state;
@@ -584,8 +581,6 @@ class SendPage extends BasePage {
581
});
582
}
583
587
-
588
-
584
if (state is TransactionCommitted) {
585
WidgetsBinding.instance.addPostFrameCallback((_) async {
586
if (!context.mounted) {
@@ -594,7 +589,8 @@ class SendPage extends BasePage {
589
590
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
591
597
- if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) {
592
+ if (newContactAddress?.address != null &&
593
+ isRegularElectrumAddress(newContactAddress!.address)) {
594
newContactAddress = null;
595
}
596
@@ -606,47 +602,51 @@ class SendPage extends BasePage {
602
builder: (BuildContext bottomSheetContext) {
603
return showContactSheet
604
? InfoBottomSheet(
609
- currentTheme: currentTheme,
610
- showDontAskMeCheckbox: true,
611
- onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value),
612
- titleText: S.of(bottomSheetContext).transaction_sent,
613
- contentImage: 'assets/images/contact_icon.svg',
614
- contentImageColor: Theme.of(context)
615
- .extension<CakeTextTheme>()!
616
- .titleColor,
617
- content: S.of(bottomSheetContext).add_contact_to_address_book,
618
- isTwoAction: true,
619
- leftButtonText: 'No',
620
- rightButtonText: 'Yes',
621
- actionLeftButton: () {
622
- Navigator.of(bottomSheetContext).pop();
623
- Navigator.of(context)
624
- .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
625
- RequestReviewHandler.requestReview();
626
- newContactAddress = null;
627
- },
628
- actionRightButton: () {
629
- Navigator.of(bottomSheetContext).pop();
630
- RequestReviewHandler.requestReview();
631
- Navigator.of(context)
632
- .pushNamed(Routes.addressBookAddContact, arguments: newContactAddress);
633
- newContactAddress = null;
634
- },
635
- )
605
+ currentTheme: currentTheme,
606
+ showDontAskMeCheckbox: true,
607
+ onCheckboxChanged: (value) => sendViewModel.setShowAddressBookPopup(!value),
608
+ titleText: S.of(bottomSheetContext).transaction_sent,
609
+ contentImage: 'assets/images/contact_icon.svg',
610
+ contentImageColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
611
+ content: S.of(bottomSheetContext).add_contact_to_address_book,
612
+ isTwoAction: true,
613
+ leftButtonText: 'No',
614
+ rightButtonText: 'Yes',
615
+ actionLeftButton: () {
616
+ Navigator.of(bottomSheetContext).pop();
617
+ if (context.mounted) {
618
+ Navigator.of(context)
619
+ .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
620
+ }
621
+ RequestReviewHandler.requestReview();
622
+ newContactAddress = null;
623
+ },
624
+ actionRightButton: () {
625
+ Navigator.of(bottomSheetContext).pop();
626
+ RequestReviewHandler.requestReview();
627
+ if (context.mounted) {
628
+ Navigator.of(context).pushNamed(Routes.addressBookAddContact,
629
+ arguments: newContactAddress);
630
+ }
631
+ newContactAddress = null;
632
+ },
633
+ )
634
: InfoBottomSheet(
637
- currentTheme: currentTheme,
638
- titleText: S.of(bottomSheetContext).transaction_sent,
639
- contentImage: 'assets/images/birthday_cake.svg',
640
- actionButtonText: S.of(bottomSheetContext).close,
641
- actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
642
- actionButton: () {
643
- Navigator.of(bottomSheetContext).pop();
644
- Navigator.of(context)
645
- .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
646
- RequestReviewHandler.requestReview();
647
- newContactAddress = null;
648
- },
649
- );
635
+ currentTheme: currentTheme,
636
+ titleText: S.of(bottomSheetContext).transaction_sent,
637
+ contentImage: 'assets/images/birthday_cake.svg',
638
+ actionButtonText: S.of(bottomSheetContext).close,
639
+ actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'),
640
+ actionButton: () {
641
+ Navigator.of(bottomSheetContext).pop();
642
+ if (context.mounted) {
643
+ Navigator.of(context)
644
+ .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
645
+ }
646
+ RequestReviewHandler.requestReview();
647
+ newContactAddress = null;
648
+ },
649
+ );
650
},
651
);
652
@@ -678,8 +678,7 @@ class SendPage extends BasePage {
678
currentTheme: currentTheme,
679
titleText: S.of(bottomSheetContext).proceed_on_device,
680
contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png',
681
- contentImageColor:
682
- Theme.of(context).extension<CakeTextTheme>()!.titleColor,
681
+ contentImageColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
682
content: S.of(bottomSheetContext).proceed_on_device_description,
683
isTwoAction: false,
684
actionButtonText: S.of(context).cancel,
@@ -778,5 +777,4 @@ class SendPage extends BasePage {
777
778
return isValid;
779
}
781
-
780
}