CW-1228: Automatically Fetch All Tokens (#2869)
What changed, and why it matters
This commit changes how Cake Wallet automatically finds and displays Ethereum-compatible tokens in a user's wallet. Previously, the app likely added discovered tokens more readily. Now it applies several safety filters: tokens flagged as possible spam by the data provider, tokens with suspicious properties, tokens without a valid USD price, and tokens worth less than about 10 cents are disabled by default. The change also fixes a chain-name label for BNB Smart Chain and adds a helper that carries token balances through the discovery process so the app can check the dollar value. Overall this is a defensive, anti-scam improvement rather than a vulnerability fix.
No urgent action required. This is a defensive improvement. Users and auditors should verify that the existing isTokenPropertiesSuspicious heuristics and the $0.10 threshold behave as intended across chains, and that the Moralis 'possibleSpam' flag is not overly aggressive for legitimate low-cap tokens.
Security signals we found
Anti-scam hardening: automatically disables tokens flagged as possible spam by Moralis
Anti-scam hardening: disables tokens whose on-chain/name/symbol properties are deemed suspicious by existing wallet heuristics
Anti-scam hardening: disables tokens for which no positive USD fiat price can be fetched
Anti-scam hardening: disables tokens with a computed USD balance below 0.1 USD
Homoglyph/spoofing detection in token symbols is referenced in commit message but not visible in the provided diff
BSC chain symbol corrected from 'BNB' to 'BSC' for Moralis API queries
Evidence from the diff
The patch refactors EVM token discovery in cw_evm/lib/evm_chain_wallet.dart to return a MoralisDiscoveryResult containing DiscoveredToken objects (token + balanceWei) instead of a plain list of Erc20Token. In lib/evm/cw_evm.dart, discoverAndAddWalletTokens now computes each token’s USD value from balanceWei and a USD fiat price, marks tokens as potential scams if either the wallet’s existing heuristics flag them or no valid USD price exists, and enables only tokens whose USD value is at least $0.10 and which are not flagged as spam. The public checkTokenFiatPrice method is replaced by a private _getTokenUsdValueAndFiatCheck. A minor fix in cw_evm/lib/utils/evm_chain_utils.dart changes the BNB Smart Chain symbol returned for chainId 56 from ‘BNB’ to ‘BSC’. tool/configure.dart updates generated imports and adds BSC to the cwEVM package inclusion condition.
Changed components
cw_evm/lib/evm_chain_wallet.dartcw_evm/lib/utils/evm_chain_utils.dartlib/evm/cw_evm.darttool/configure.dartInspect captured patch +61 / −27
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index b9fa6dcd..c4404207 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -509,17 +509,17 @@ abstract class EVMChainWalletBase
}
}
- Future<List<Erc20Token>> discoverTokensFromMoralis() async {
+ Future<MoralisDiscoveryResult> discoverTokensFromMoralis() async {
try {
- if (!evmChainErc20TokensBox.isOpen) return [];
+ if (!evmChainErc20TokensBox.isOpen) return MoralisDiscoveryResult.empty;
final address = walletAddresses.address;
- if (address.isEmpty) return [];
+ if (address.isEmpty) return MoralisDiscoveryResult.empty;
final chainName = EVMChainUtils.getDefaultTokenSymbol(selectedChainId).toLowerCase();
final walletTokens = await _client.fetchWalletTokensFromMoralis(address, chainName);
- if (walletTokens.isEmpty) return [];
+ if (walletTokens.isEmpty) return MoralisDiscoveryResult.empty;
final existingTokenAddresses = {
for (final token in evmChainErc20TokensBox.values)
@@ -529,7 +529,7 @@ abstract class EVMChainWalletBase
final whitelistedTokenAddresses =
getDefaultTokenContractAddresses.map((a) => a.toLowerCase()).toSet();
- final List<Erc20Token> newTokens = [];
+ final newTokens = <DiscoveredToken>[];
for (final token in walletTokens) {
final addr = token.contractAddress.toLowerCase();
@@ -554,13 +554,18 @@ abstract class EVMChainWalletBase
isPotentialScam: token.possibleSpam,
);
- newTokens.add(newToken);
+ newTokens.add(
+ DiscoveredToken(
+ token: newToken,
+ balanceWei: token.balanceWei,
+ ),
+ );
}
- return newTokens;
+ return MoralisDiscoveryResult(newTokens: newTokens);
} catch (e) {
printV('Error discovering tokens from Moralis: ${e.toString()}');
- return [];
+ return MoralisDiscoveryResult.empty;
}
}
@@ -1535,3 +1540,21 @@ class GasParamsHandler {
);
}
}
+
+class DiscoveredToken {
+ final Erc20Token token;
+ final BigInt balanceWei;
+
+ const DiscoveredToken({
+ required this.token,
+ required this.balanceWei,
+ });
+}
+
+class MoralisDiscoveryResult {
+ final List<DiscoveredToken> newTokens;
+
+ const MoralisDiscoveryResult({required this.newTokens});
+
+ static const MoralisDiscoveryResult empty = MoralisDiscoveryResult(newTokens: []);
+}
diff --git a/cw_evm/lib/utils/evm_chain_utils.dart b/cw_evm/lib/utils/evm_chain_utils.dart
index 2f882c20..634d2a1f 100644
--- a/cw_evm/lib/utils/evm_chain_utils.dart
+++ b/cw_evm/lib/utils/evm_chain_utils.dart
@@ -86,7 +86,7 @@ class EVMChainUtils {
137 => 'MATIC',
8453 => 'BASE',
42161 => 'ARBITRUM',
- 56 => 'BNB',
+ 56 => 'BSC',
_ => 'ETH',
};
}
diff --git a/lib/evm/cw_evm.dart b/lib/evm/cw_evm.dart
index eff7c40c..1aef384a 100644
--- a/lib/evm/cw_evm.dart
+++ b/lib/evm/cw_evm.dart
@@ -514,49 +514,59 @@ class CWEVM extends EVM {
@override
bool hasPriorityFee(int chainId) => EVMChainUtils.hasPriorityFee(chainId);
- @override
- Future<bool> checkTokenFiatPrice(WalletBase wallet, Erc20Token token) async {
+ Future<({double usdValue, bool hasValidFiatPrice})> _getTokenUsdValueAndFiatCheck(
+ Erc20Token token,
+ BigInt balanceWei,
+ ) async {
try {
final settingsStore = getIt.get<SettingsStore>();
- final fiatCurrency = settingsStore.fiatCurrency;
final torOnly = settingsStore.fiatApiMode == FiatApiMode.torOnly;
final price = await FiatConversionService.fetchPrice(
crypto: token,
- fiat: fiatCurrency,
+ fiat: FiatCurrency.usd,
torOnly: torOnly,
);
- return price > 0;
+ final hasValidFiatPrice = price > 0;
+
+ final decimals = token.decimal;
+ final balance = balanceWei.toDouble() / math.pow(10, decimals);
+ final usdValue = balance * price;
+
+ return (usdValue: usdValue, hasValidFiatPrice: hasValidFiatPrice);
} catch (e) {
- return false;
+ return (usdValue: 0.0, hasValidFiatPrice: false);
}
}
+ static const _minTokenUsdValue = 0.1;
@override
Future<void> discoverAndAddWalletTokens(WalletBase wallet) async {
if (wallet is! EVMChainWallet) return;
try {
- final discoveredTokens = await wallet.discoverTokensFromMoralis();
+ final result = await wallet.discoverTokensFromMoralis();
- if (discoveredTokens.isEmpty) return;
+ if (result.newTokens.isEmpty) return;
final List<Future<void>> tokenChecks = [];
- for (final token in discoveredTokens) {
+ for (final item in result.newTokens) {
tokenChecks.add((() async {
- final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token);
+ final token = item.token;
- bool hasValidFiatPrice = true;
- if (!isPropertiesSuspicious) {
- hasValidFiatPrice = await checkTokenFiatPrice(wallet, token);
- }
+ final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token);
- final isSpam = isPropertiesSuspicious || !hasValidFiatPrice;
+ final fiatResult = await _getTokenUsdValueAndFiatCheck(
+ token,
+ item.balanceWei,
+ );
+ final isSpam = isPropertiesSuspicious || !fiatResult.hasValidFiatPrice;
token.isPotentialScam = isSpam;
- token.enabled = !isSpam;
+
+ token.enabled = (fiatResult.usdValue >= _minTokenUsdValue) && !isSpam;
await wallet.addErc20Token(token);
})());
diff --git a/tool/configure.dart b/tool/configure.dart
index 074c3f1c..aa43adb9 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -1322,6 +1322,7 @@ abstract class DogeCoin {
Future<void> generateEVM(bool hasImplementation) async {
final outputFile = File(evmOutputPath);
const evmCommonHeaders = """
+import 'dart:math' as math;
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -1348,6 +1349,7 @@ import 'package:web3dart/web3dart.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
+import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_evm/utils/evm_chain_formatter.dart';
import 'package:cw_evm/evm_chain_mnemonics.dart';
@@ -1523,7 +1525,6 @@ abstract class EVM {
bool hasPriorityFee(int chainId);
- Future<bool> checkTokenFiatPrice(WalletBase wallet, Erc20Token token);
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
}
@@ -1792,7 +1793,7 @@ Future<void> generatePubspec({
output += '\n$flutterSecureStorage\n';
}
- if (hasEthereum || hasPolygon || hasBase || hasArbitrum) {
+ if (hasEthereum || hasPolygon || hasBase || hasArbitrum || hasBsc) {
output += '\n$cwEVM';
}
Why this scored 53/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.