Cw 939 whitelist known tokens (#2038)

* [skip-ci] init * don't get price data for potential scam tokens * updates * dont fetch fiat price for scam currencies

Matthew Fosse committed Feb 26, 2025 at 19:49 UTC 57fe3287faffe34d65bd7119f52c9dac9aca0c49
9 files changed +97 -11
cw_core/lib/crypto_currency.dart
+3 -1
@@ -11,7 +11,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
11 this.iconPath,
12 this.tag,
13 this.enabled = false,
14 - })
14 + this.isPotentialScam = false,
15 + })
16 : super(title: title, raw: raw);
17
18 final String name;
@@ -20,6 +21,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
21 final String? iconPath;
22 final int decimals;
23 final bool enabled;
24 + final bool isPotentialScam;
25
26 set enabled(bool value) => this.enabled = value;
27
lib/ethereum/cw_ethereum.dart
+5
@@ -209,4 +209,9 @@ class CWEthereum extends Ethereum {
209 throw err;
210 }
211 }
212 +
213 + @override
214 + List<String> getDefaultTokenContractAddresses() {
215 + return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
216 + }
217 }
lib/polygon/cw_polygon.dart
+5
@@ -208,4 +208,9 @@ class CWPolygon extends Polygon {
208 throw err;
209 }
210 }
211 +
212 + @override
213 + List<String> getDefaultTokenContractAddresses() {
214 + return DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
215 + }
216 }
lib/reactions/fiat_rate_update.dart
+4 -1
@@ -60,9 +60,12 @@ Future<void> startFiatRateUpdate(
60 tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
61 }
62
63 -
63 if (currencies != null) {
64 for (final currency in currencies) {
65 + // skip potential scams:
66 + if (currency.isPotentialScam) {
67 + continue;
68 + }
69 () async {
70 fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
71 crypto: currency,
lib/solana/cw_solana.dart
+5
@@ -154,4 +154,9 @@ class CWSolana extends Solana {
154 double? getEstimateFees(WalletBase wallet) {
155 return (wallet as SolanaWallet).estimatedFee;
156 }
157 +
158 + @override
159 + List<String> getDefaultTokenContractAddresses() {
160 + return DefaultSPLTokens().initialSPLTokens.map((e) => e.mintAddress).toList();
161 + }
162 }
lib/src/screens/dashboard/edit_token_page.dart
+21 -3
@@ -211,6 +211,21 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
211 .checkIfERC20TokenContractAddressIsAPotentialScamAddress(
212 _contractAddressController.text,
213 );
214 +
215 + final isWhitelisted = await widget.homeSettingsViewModel
216 + .checkIfTokenIsWhitelisted(_contractAddressController.text);
217 +
218 + bool isPotentialScam = hasPotentialError;
219 + final tokenSymbol = _tokenSymbolController.text.toUpperCase();
220 +
221 + // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc):
222 + // if it is, then it's probably a scam unless it's in the whitelist
223 + final baseCurrencySymbols =
224 + CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
225 + if (baseCurrencySymbols.contains(tokenSymbol) && !isWhitelisted) {
226 + isPotentialScam = true;
227 + }
228 +
229 final actionCall = () async {
230 try {
231 await widget.homeSettingsViewModel.addToken(
@@ -219,6 +234,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
234 title: _tokenSymbolController.text.toUpperCase(),
235 decimals: int.parse(_tokenDecimalController.text),
236 iconPath: _tokenIconPathController.text,
237 + isPotentialScam: isPotentialScam,
238 ),
239 contractAddress: _contractAddressController.text,
240 );
@@ -226,7 +242,6 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
242 if (mounted) {
243 Navigator.pop(context);
244 }
229 -
245 } catch (e) {
246 showPopUp<void>(
247 context: context,
@@ -303,7 +318,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
318 if (token != null) {
319 final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano;
320 if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name;
306 - if (_tokenSymbolController.text.isEmpty || isZano) _tokenSymbolController.text = token.title;
321 + if (_tokenSymbolController.text.isEmpty || isZano)
322 + _tokenSymbolController.text = token.title;
323 if (_tokenIconPathController.text.isEmpty)
324 _tokenIconPathController.text = token.iconPath ?? '';
325 if (_tokenDecimalController.text.isEmpty || isZano)
@@ -338,7 +354,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
354 placeholder: S.of(context).token_contract_address,
355 options: [AddressTextFieldOption.paste],
356 buttonColor: Theme.of(context).hintColor,
341 - validator: widget.homeSettingsViewModel.walletType == WalletType.zano ? null : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call,
357 + validator: widget.homeSettingsViewModel.walletType == WalletType.zano
358 + ? null
359 + : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call,
360 onPushPasteButton: (_) {
361 _pasteText();
362 },
lib/tron/cw_tron.dart
+5
@@ -133,4 +133,9 @@ class CWTron extends Tron {
133 void updateTronGridUsageState(WalletBase wallet, bool isEnabled) {
134 (wallet as TronWallet).updateScanProviderUsageState(isEnabled);
135 }
136 +
137 + @override
138 + List<String> getDefaultTokenContractAddresses() {
139 + return DefaultTronTokens().initialTronTokens.map((e) => e.contractAddress).toList();
140 + }
141 }
lib/view_model/dashboard/home_settings_view_model.dart
+41 -6
@@ -114,13 +114,12 @@ abstract class HomeSettingsViewModelBase with Store {
114 if (_balanceViewModel.wallet.type == WalletType.zano) {
115 await zano!.addZanoAssetById(_balanceViewModel.wallet, contractAddress);
116 }
117 -
117 +
118 _updateTokensList();
119 _updateFiatPrices(token);
120 } catch (e) {
121 throw e;
122 - }
123 - finally {
122 + } finally {
123 isAddingToken = false;
124 }
125 }
@@ -189,6 +188,40 @@ abstract class HomeSettingsViewModelBase with Store {
188 }
189 }
190
191 + bool checkIfTokenIsWhitelisted(String contractAddress) {
192 + // get the default tokens for each currency type:
193 + List<String> defaultTokenAddresses = [];
194 + switch (_balanceViewModel.wallet.type) {
195 + case WalletType.ethereum:
196 + defaultTokenAddresses = ethereum!.getDefaultTokenContractAddresses();
197 + break;
198 + case WalletType.polygon:
199 + defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses();
200 + break;
201 + case WalletType.solana:
202 + defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
203 + break;
204 + case WalletType.tron:
205 + defaultTokenAddresses = tron!.getDefaultTokenContractAddresses();
206 + break;
207 + case WalletType.zano:
208 + case WalletType.banano:
209 + case WalletType.monero:
210 + case WalletType.none:
211 + case WalletType.bitcoin:
212 + case WalletType.litecoin:
213 + case WalletType.haven:
214 + case WalletType.nano:
215 + case WalletType.wownero:
216 + case WalletType.bitcoinCash:
217 + return false;
218 + }
219 +
220 + // check if the contractAddress is in the defaultTokenAddresses
221 + bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress);
222 + return isInWhitelist;
223 + }
224 +
225 Future<bool> _isPotentialScamTokenViaMoralis(
226 String contractAddress,
227 String chainName,
@@ -363,6 +396,7 @@ abstract class HomeSettingsViewModelBase with Store {
396 CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
397
398 void _updateFiatPrices(CryptoCurrency token) async {
399 + if (token.isPotentialScam) return; // don't fetch price data for potential scam tokens
400 try {
401 _balanceViewModel.fiatConvertationStore.prices[token] =
402 await FiatConversionService.fetchPrice(
@@ -455,9 +489,10 @@ abstract class HomeSettingsViewModelBase with Store {
489 }
490
491 if (_balanceViewModel.wallet.type == WalletType.zano) {
458 - tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet)
459 - .where((element) => _matchesSearchText(element))
460 - .toList()
492 + tokens.addAll(zano!
493 + .getZanoAssets(_balanceViewModel.wallet)
494 + .where((element) => _matchesSearchText(element))
495 + .toList()
496 ..sort(_sortFunc));
497 }
498 }
tool/configure.dart
+8
@@ -872,6 +872,7 @@ import 'package:cw_evm/evm_chain_wallet.dart';
872 import 'package:cw_ethereum/ethereum_client.dart';
873 import 'package:cw_ethereum/ethereum_wallet.dart';
874 import 'package:cw_ethereum/ethereum_wallet_service.dart';
875 +import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
876
877 import 'package:eth_sig_util/util/utils.dart';
878
@@ -922,6 +923,7 @@ abstract class Ethereum {
923
924 void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
925 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
926 + List<String> getDefaultTokenContractAddresses();
927 }
928 """;
929
@@ -977,6 +979,7 @@ import 'package:cw_evm/evm_chain_wallet.dart';
979 import 'package:cw_polygon/polygon_client.dart';
980 import 'package:cw_polygon/polygon_wallet.dart';
981 import 'package:cw_polygon/polygon_wallet_service.dart';
982 +import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
983
984 import 'package:eth_sig_util/util/utils.dart';
985
@@ -1027,6 +1030,7 @@ abstract class Polygon {
1030
1031 void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
1032 Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
1033 + List<String> getDefaultTokenContractAddresses();
1034 }
1035 """;
1036
@@ -1269,6 +1273,7 @@ import 'package:cw_solana/solana_wallet_service.dart';
1273 import 'package:cw_solana/solana_transaction_info.dart';
1274 import 'package:cw_solana/solana_transaction_credentials.dart';
1275 import 'package:cw_solana/solana_wallet_creation_credentials.dart';
1276 +import 'package:cw_solana/default_spl_tokens.dart';
1277 """;
1278 const solanaCwPart = "part 'cw_solana.dart';";
1279 const solanaContent = """
@@ -1310,6 +1315,7 @@ abstract class Solana {
1315 String getTokenAddress(CryptoCurrency asset);
1316 List<int>? getValidationLength(CryptoCurrency type);
1317 double? getEstimateFees(WalletBase wallet);
1318 + List<String> getDefaultTokenContractAddresses();
1319 }
1320
1321 """;
@@ -1355,6 +1361,7 @@ import 'package:cw_tron/tron_client.dart';
1361 import 'package:cw_tron/tron_token.dart';
1362 import 'package:cw_tron/tron_wallet.dart';
1363 import 'package:cw_tron/tron_wallet_service.dart';
1364 +import 'package:cw_tron/default_tron_tokens.dart';
1365
1366 """;
1367 const tronCwPart = "part 'cw_tron.dart';";
@@ -1386,6 +1393,7 @@ abstract class Tron {
1393 String? getTronTRC20EstimatedFee(WalletBase wallet);
1394
1395 void updateTronGridUsageState(WalletBase wallet, bool isEnabled);
1396 + List<String> getDefaultTokenContractAddresses();
1397 }
1398 """;
1399