15
import 'package:cw_core/sync_status.dart';
16
import 'package:cw_core/transaction_direction.dart';
17
import 'package:cw_core/transaction_priority.dart';
18
+import 'package:cw_core/utils/homoglyph_normalizer.dart';
19
import 'package:cw_core/utils/print_verbose.dart';
20
import 'package:cw_core/wallet_addresses.dart';
21
import 'package:cw_core/wallet_base.dart';
441
await save();
442
}
443
443
- Future<void> _checkForExistingScamTokens() async {
444
+ bool isTokenPropertiesSuspicious(Erc20Token token) {
445
final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
446
446
- for (var token in erc20Currencies) {
447
- bool isPotentialScam = false;
447
+ bool isTokenWhitelisted = getDefaultTokenContractAddresses
448
+ .any((element) => element.toLowerCase() == token.contractAddress.toLowerCase());
449
+
450
+ // Normalize the token data to check for homoglyph spoofing attack, characters that look like ASCII (Cyrillic, Greek, etc.)
451
+ final normalizedName = normalizeHomoglyphs(token.name.trim().toUpperCase());
452
+ final normalizedSymbol = normalizeHomoglyphs(token.symbol.trim().toUpperCase());
453
+ final normalizedTitle = normalizeHomoglyphs(token.title.trim().toUpperCase());
454
+
455
+ final suspiciousStrings = [
456
+ 't.me',
457
+ '.me',
458
+ 'telegram',
459
+ 'http',
460
+ 'https',
461
+ '.com',
462
+ 'airdrop',
463
+ 'www',
464
+ '.xyz',
465
+ '🎁',
466
+ ];
467
+
468
+ final hasSuspiciousData = suspiciousStrings.any(
469
+ (element) =>
470
+ normalizedName.toLowerCase().contains(element) ||
471
+ normalizedSymbol.toLowerCase().contains(element) ||
472
+ normalizedTitle.toLowerCase().contains(element),
473
+ );
474
449
- bool isWhitelisted = getDefaultTokenContractAddresses
450
- .any((element) => element.toLowerCase() == token.contractAddress.toLowerCase());
475
+ // Check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc).
476
+ // If it is, then it's probably a scam unless it's in the whitelist.
477
+ final hasSuspiciousSymbol = baseCurrencySymbols.contains(normalizedSymbol);
478
452
- final tokenSymbol = token.title.toUpperCase();
479
+ return hasSuspiciousData || (hasSuspiciousSymbol && !isTokenWhitelisted);
480
+ }
481
454
- // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc):
455
- // if it is, then it's probably a scam unless it's in the whitelist
456
- if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && !isWhitelisted) {
457
- isPotentialScam = true;
458
- }
482
+ Future<void> _checkForExistingScamTokens() async {
483
+ for (var token in erc20Currencies) {
484
+ bool isPotentialScam = false;
485
460
- if (isPotentialScam) {
486
+ if (isTokenPropertiesSuspicious(token)) {
487
+ isPotentialScam = true;
488
token.isPotentialScam = true;
489
token.iconPath = null;
490
await token.save();
509
}
510
}
511
512
+ Future<List<Erc20Token>> discoverTokensFromMoralis() async {
513
+ try {
514
+ if (!evmChainErc20TokensBox.isOpen) return [];
515
+
516
+ final address = walletAddresses.address;
517
+ if (address.isEmpty) return [];
518
+
519
+ final chainName = EVMChainUtils.getDefaultTokenSymbol(selectedChainId).toLowerCase();
520
+
521
+ final walletTokens = await _client.fetchWalletTokensFromMoralis(address, chainName);
522
+ if (walletTokens.isEmpty) return [];
523
+
524
+ final existingTokenAddresses = {
525
+ for (final token in evmChainErc20TokensBox.values)
526
+ token.contractAddress.toLowerCase(): token,
527
+ };
528
+
529
+ final whitelistedTokenAddresses =
530
+ getDefaultTokenContractAddresses.map((a) => a.toLowerCase()).toSet();
531
+
532
+ final List<Erc20Token> newTokens = [];
533
+
534
+ for (final token in walletTokens) {
535
+ final addr = token.contractAddress.toLowerCase();
536
+
537
+ final existingToken = existingTokenAddresses[addr];
538
+ if (existingToken != null) {
539
+ if (whitelistedTokenAddresses.contains(addr) && !existingToken.enabled) {
540
+ existingToken.enabled = true;
541
+ await existingToken.save();
542
+ await addErc20Token(existingToken);
543
+ }
544
+ continue;
545
+ }
546
+
547
+ final newToken = Erc20Token(
548
+ name: token.name,
549
+ symbol: token.symbol,
550
+ contractAddress: addr,
551
+ decimal: token.decimals,
552
+ iconPath: token.iconUrl,
553
+ tag: EVMChainUtils.getDefaultTokenTag(selectedChainId),
554
+ isPotentialScam: token.possibleSpam,
555
+ );
556
+
557
+ newTokens.add(newToken);
558
+ }
559
+
560
+ return newTokens;
561
+ } catch (e) {
562
+ printV('Error discovering tokens from Moralis: ${e.toString()}');
563
+ return [];
564
+ }
565
+ }
566
+
567
@override
568
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
569