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