mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Generic Token Fixes (#2874)
* feat: Automatically detect wallet tokens for EVM chains * feat: Disable potential tg scam tokens by default * feat: Add homoglyph normalization to detect spoofing attacks in token symbols * refactor: Improve scam token detection and apply to automatically fetched EVM tokens * refactor: Enhance scam detection for automatically detected tokens in evm wallets * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Enable whitelisted tokens with balance when importing wallet tokens * feat: Disable imported tokens with balance less than 0.1 usd * feat: Disable imported tokens with balance less than 0.1 usd - Update configure file * fixes: - default tokens marked as potential scam - ui overflow for token name on balance page - add more filters for spam tokens * - detect and flag default token impersonators - add homoglyph normalization to prevent spoofing attacks on new token symbols - fix bnb not coming up in receive currency list for swap page * - detect and flag default token impersonators - add homoglyph normalization to prevent spoofing attacks on new token symbols - fix bnb not coming up in receive currency list for swap page * Revert Exclude BNB from receive currencies list [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
parent
174216df17
commit
326ef316c3
@@ -5,7 +5,7 @@ import 'package:cw_evm/tokens/bsc_tokens.dart';
|
||||
import 'package:cw_evm/tokens/ethereum_tokens.dart';
|
||||
import 'package:cw_evm/tokens/polygon_tokens.dart';
|
||||
|
||||
/// Default ERC20 tokens for each EVM chain
|
||||
/// Default ERC20 tokens for each EVM chain and utility methods for interacting with them
|
||||
class EVMChainDefaultTokens {
|
||||
static List<Erc20Token> getDefaultTokensByChainId(int chainId) {
|
||||
return switch (chainId) {
|
||||
@@ -21,4 +21,8 @@ class EVMChainDefaultTokens {
|
||||
static List<String> getDefaultTokenAddresses(int chainId) {
|
||||
return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
|
||||
}
|
||||
|
||||
static List<String> getDefaultTokenSymbols(int chainId) {
|
||||
return getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,6 +445,8 @@ abstract class EVMChainWalletBase
|
||||
bool isTokenWhitelisted = getDefaultTokenContractAddresses
|
||||
.any((element) => element.toLowerCase() == token.contractAddress.toLowerCase());
|
||||
|
||||
final defaultTokenSymbols = EVMChainDefaultTokens.getDefaultTokenSymbols(selectedChainId);
|
||||
|
||||
// Normalize the token data to check for homoglyph spoofing attack, characters that look like ASCII (Cyrillic, Greek, etc.)
|
||||
final normalizedName = normalizeHomoglyphs(token.name.trim().toUpperCase());
|
||||
final normalizedSymbol = normalizeHomoglyphs(token.symbol.trim().toUpperCase());
|
||||
@@ -479,9 +481,12 @@ abstract class EVMChainWalletBase
|
||||
);
|
||||
|
||||
final nativeSymbol = currency.title.toUpperCase();
|
||||
final hasSuspiciousSymbol = normalizedSymbol == nativeSymbol && !isTokenWhitelisted;
|
||||
final hasSuspiciousNativeSymbol = normalizedSymbol == nativeSymbol && !isTokenWhitelisted;
|
||||
|
||||
return hasSuspiciousData || hasSuspiciousSymbol;
|
||||
final hasSuspiciousDefaultTokenSymbol =
|
||||
defaultTokenSymbols.contains(normalizedSymbol) && !isTokenWhitelisted;
|
||||
|
||||
return hasSuspiciousData || hasSuspiciousNativeSymbol || hasSuspiciousDefaultTokenSymbol;
|
||||
}
|
||||
|
||||
Future<void> _checkForExistingScamTokens() async {
|
||||
|
||||
@@ -314,6 +314,13 @@ class CWEVM extends EVM {
|
||||
return EVMChainDefaultTokens.getDefaultTokenAddresses(chainId);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenSymbols(WalletBase wallet) {
|
||||
final chainId = getSelectedChainId(wallet);
|
||||
if (chainId == null) return [];
|
||||
return EVMChainDefaultTokens.getDefaultTokenSymbols(chainId);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
|
||||
final evmWallet = wallet as EVMChainWallet;
|
||||
|
||||
@@ -177,6 +177,11 @@ class CWSolana extends Solana {
|
||||
return DefaultSPLTokens().initialSPLTokens.map((e) => e.mintAddress).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenSymbols() {
|
||||
return DefaultSPLTokens().initialSPLTokens.map((e) => e.symbol.toUpperCase()).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
|
||||
final solanaWallet = wallet as SolanaWallet;
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:cake_wallet/themes/core/theme_extension.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/homoglyph_normalizer.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -231,20 +232,29 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
|
||||
bool isPotentialScam = hasPotentialError && !isWhitelisted;
|
||||
|
||||
// Normalize to catch homoglyph spoofing attacks
|
||||
final tokenSymbol = normalizeHomoglyphs(
|
||||
_tokenSymbolController.text.trim().toUpperCase(),
|
||||
);
|
||||
|
||||
// check if the token symbol is the same as the native token symbol
|
||||
// to prevent token impersonation
|
||||
// (e.g. fake ETH on Ethereum, fake SOL on Solana)
|
||||
final tokenSymbol = _tokenSymbolController.text
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
final nativeSymbol = widget.homeSettingsViewModel
|
||||
.nativeToken
|
||||
.title
|
||||
.toUpperCase();
|
||||
final nativeSymbol =
|
||||
widget.homeSettingsViewModel.nativeToken.title.toUpperCase();
|
||||
if (tokenSymbol == nativeSymbol && !isWhitelisted) {
|
||||
isPotentialScam = true;
|
||||
}
|
||||
|
||||
// check if the token symbol is the same as any of the default token symbols
|
||||
// (e.g. fake USDC, USDT with wrong contract address)
|
||||
if (widget.homeSettingsViewModel.checkIfTokenSymbolMatchesDefaultToken(
|
||||
tokenSymbol,
|
||||
) &&
|
||||
!isWhitelisted) {
|
||||
isPotentialScam = true;
|
||||
}
|
||||
|
||||
final actionCall = () async {
|
||||
try {
|
||||
await widget.homeSettingsViewModel.addToken(
|
||||
|
||||
@@ -139,9 +139,15 @@ class CWTron extends Tron {
|
||||
return DefaultTronTokens().initialTronTokens.map((e) => e.contractAddress).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getDefaultTokenSymbols() {
|
||||
return DefaultTronTokens().initialTronTokens.map((e) => e.symbol.toUpperCase()).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
|
||||
final tronWallet = wallet as TronWallet;
|
||||
return tronWallet.tronTokenCurrencies.any((element) => element.contractAddress == contractAddress);
|
||||
return tronWallet.tronTokenCurrencies
|
||||
.any((element) => element.contractAddress == contractAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/utils/homoglyph_normalizer.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -242,6 +243,44 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||
return isInWhitelist;
|
||||
}
|
||||
|
||||
bool checkIfTokenSymbolMatchesDefaultToken(String symbol) {
|
||||
final normalizedSymbol = normalizeHomoglyphs(symbol.trim().toUpperCase());
|
||||
if (normalizedSymbol.isEmpty) return false;
|
||||
|
||||
List<String> defaultTokenSymbols = [];
|
||||
switch (_balanceViewModel.wallet.type) {
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.base:
|
||||
case WalletType.arbitrum:
|
||||
case WalletType.bsc:
|
||||
defaultTokenSymbols = evm!.getDefaultTokenSymbols(_balanceViewModel.wallet);
|
||||
break;
|
||||
case WalletType.solana:
|
||||
defaultTokenSymbols = solana!.getDefaultTokenSymbols();
|
||||
break;
|
||||
case WalletType.tron:
|
||||
defaultTokenSymbols = tron!.getDefaultTokenSymbols();
|
||||
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:
|
||||
case WalletType.decred:
|
||||
case WalletType.dogecoin:
|
||||
case WalletType.zcash:
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultTokenSymbols.any((s) => s.toUpperCase() == normalizedSymbol);
|
||||
}
|
||||
|
||||
Future<bool> _isPotentialScamTokenViaMoralis(
|
||||
String contractAddress,
|
||||
String chainName,
|
||||
|
||||
@@ -969,6 +969,7 @@ abstract class Solana {
|
||||
List<int>? getValidationLength(CryptoCurrency type);
|
||||
double? getEstimateFees(WalletBase wallet);
|
||||
List<String> getDefaultTokenContractAddresses();
|
||||
List<String> getDefaultTokenSymbols();
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
|
||||
// Jupiter swap transaction handling
|
||||
@@ -1094,6 +1095,7 @@ abstract class Tron {
|
||||
|
||||
void updateTronGridUsageState(WalletBase wallet, bool isEnabled);
|
||||
List<String> getDefaultTokenContractAddresses();
|
||||
List<String> getDefaultTokenSymbols();
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
}
|
||||
""";
|
||||
@@ -1489,6 +1491,7 @@ abstract class EVM {
|
||||
|
||||
// Utility methods
|
||||
List<String> getDefaultTokenContractAddresses(WalletBase wallet);
|
||||
List<String> getDefaultTokenSymbols(WalletBase wallet);
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
String? getEVMNativeEstimatedFee(WalletBase wallet);
|
||||
String? getEVMERC20EstimatedFee(WalletBase wallet);
|
||||
|
||||
Reference in New Issue
Block a user