Generic Token Fixes (#2873)
What changed, and why it matters
This commit improves protections in Cake Wallet against scam or fake tokens on EVM blockchains. It tightens how the app decides a token looks suspicious, adds more spam keywords (like 'reward', 'claim', 'airdrop'), checks whether a token is pretending to be the chain's native coin (e.g., a fake 'ETH' on Ethereum), and disables low-value or suspicious tokens when wallets are imported. It also fixes a minor screen layout issue where long token names could overflow.
Review the full PR #2873 diff and any related tests to confirm the additional protections described in the commit message (homoglyph normalization, fiat-value thresholds, whitelist behavior) are implemented correctly. Validate that the native-symbol check does not produce false positives for legitimate wrapped/bridged tokens and that the suspicious-string list does not over-flag legitimate airdrop or reward tokens.
Security signals we found
Token impersonation / spoofing detection (symbol matching native currency)
Expansion of suspicious keyword filters for scam tokens
Automatic marking of suspicious ERC-20 tokens on add/import
UI overflow fix for long token names (non-security hardening)
Commit message references additional anti-scam measures (homoglyph normalization, fiat balance checks, auto-detected tokens) not present in supplied diff
Evidence from the diff
The patch refines EVMChainWalletBase.isTokenPropertiesSuspicious(): it removes a broad check against all base currency symbols and instead compares only against the wallet’s native currency title, narrows the whitelist exception, and expands the suspicious-string list. addErc20Token() now marks tokens as potential scams before fetching icons. The edit token page applies the same native-symbol impersonation check. A UI overflow fix is included in the balance row widget. The commit message mentions additional changes (homoglyph normalization, fiat-value checks, auto-detected tokens, whitelisting) that are not visible in the supplied 60-line diff, so those cannot be verified from the diff alone.
Changed components
cw_evm/lib/evm_chain_wallet.dartlib/src/screens/dashboard/edit_token_page.dartlib/src/screens/dashboard/pages/balance/balance_row_widget.dartInspect captured patch +60 / −55
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index c440420..c3ecd5b 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -442,8 +442,6 @@ abstract class EVMChainWalletBase
}
bool isTokenPropertiesSuspicious(Erc20Token token) {
- final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
-
bool isTokenWhitelisted = getDefaultTokenContractAddresses
.any((element) => element.toLowerCase() == token.contractAddress.toLowerCase());
@@ -459,10 +457,18 @@ abstract class EVMChainWalletBase
'http',
'https',
'.com',
+ '.org',
+ '.top',
+ '.live',
'airdrop',
+ 'reward',
+ 'distribution',
'www',
'.xyz',
'🎁',
+ 'bot',
+ 'claim',
+ 'reward',
];
final hasSuspiciousData = suspiciousStrings.any(
@@ -472,11 +478,10 @@ abstract class EVMChainWalletBase
normalizedTitle.toLowerCase().contains(element),
);
- // 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.
- final hasSuspiciousSymbol = baseCurrencySymbols.contains(normalizedSymbol);
+ final nativeSymbol = currency.title.toUpperCase();
+ final hasSuspiciousSymbol = normalizedSymbol == nativeSymbol && !isTokenWhitelisted;
- return hasSuspiciousData || (hasSuspiciousSymbol && !isTokenWhitelisted);
+ return hasSuspiciousData || hasSuspiciousSymbol;
}
Future<void> _checkForExistingScamTokens() async {
@@ -1287,6 +1292,9 @@ abstract class EVMChainWalletBase
}
Future<void> addErc20Token(Erc20Token token) async {
+ final isSuspicious = isTokenPropertiesSuspicious(token);
+ token.isPotentialScam = token.isPotentialScam || isSuspicious;
+
String? iconPath;
if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) {
diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart
index 7399ac0..f436845 100644
--- a/lib/src/screens/dashboard/edit_token_page.dart
+++ b/lib/src/screens/dashboard/edit_token_page.dart
@@ -230,19 +230,18 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
);
bool isPotentialScam = hasPotentialError && !isWhitelisted;
- final tokenSymbol = _tokenSymbolController.text.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
-
- // ugh, should it be commented out?
- // because there are some tokens that has the name of original currencies
- // like: 0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6
-
- final baseCurrencySymbols =
- CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
- if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) &&
- !isWhitelisted) {
+ // 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();
+ if (tokenSymbol == nativeSymbol && !isWhitelisted) {
isPotentialScam = true;
}
diff --git a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart
index e2fa670..b9e1705 100644
--- a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart
+++ b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart
@@ -173,46 +173,44 @@ class BalanceRowWidget extends StatelessWidget {
],
),
),
- SizedBox(
- //width: min(MediaQuery.of(context).size.width * 0.2, 100),
- child: Center(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- CakeImageWidget(
- imageUrl: currency.iconPath,
- height: 40,
- width: 40,
- errorWidget: Container(
- height: 30.0,
- width: 30.0,
- child: Center(
- child: Text(
- currency.title.substring(0, min(currency.title.length, 2)),
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- fontSize: 11,
- color: Theme.of(context).colorScheme.onSurfaceVariant,
- ),
- ),
- ),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: Theme.of(context).colorScheme.surfaceContainer,
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ CakeImageWidget(
+ imageUrl: currency.iconPath,
+ height: 40,
+ width: 40,
+ errorWidget: Container(
+ height: 30.0,
+ width: 30.0,
+ child: Center(
+ child: Text(
+ currency.title.substring(0, min(currency.title.length, 2)),
+ style: Theme.of(context).textTheme.bodySmall?.copyWith(
+ fontSize: 11,
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
+ ),
),
),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ ),
),
- const SizedBox(height: 10),
- Text(
- currency.title,
- style: Theme.of(context).textTheme.bodyMedium?.copyWith(
- fontSize: 16,
- fontWeight: FontWeight.w700,
- color: Theme.of(context).colorScheme.onSurface,
- height: 1,
- ),
- ),
- ],
- ),
+ ),
+ const SizedBox(height: 10),
+ Text(
+ currency.title,
+ overflow: TextOverflow.ellipsis,
+ style: Theme.of(context).textTheme.bodyMedium?.copyWith(
+ fontSize: 16,
+ fontWeight: FontWeight.w700,
+ color: Theme.of(context).colorScheme.onSurface,
+ height: 1,
+ ),
+ ),
+ ],
),
),
],
Why this scored 63/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.