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
This commit is contained in:
Matthew Fosse 2025-02-26 19:49:57 -08:00 committed by GitHub
parent 3a56277c27
commit 57fe3287fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 97 additions and 11 deletions

View file

@ -11,6 +11,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
this.iconPath, this.iconPath,
this.tag, this.tag,
this.enabled = false, this.enabled = false,
this.isPotentialScam = false,
}) })
: super(title: title, raw: raw); : super(title: title, raw: raw);
@ -20,6 +21,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
final String? iconPath; final String? iconPath;
final int decimals; final int decimals;
final bool enabled; final bool enabled;
final bool isPotentialScam;
set enabled(bool value) => this.enabled = value; set enabled(bool value) => this.enabled = value;

View file

@ -209,4 +209,9 @@ class CWEthereum extends Ethereum {
throw err; throw err;
} }
} }
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
}
} }

View file

@ -208,4 +208,9 @@ class CWPolygon extends Polygon {
throw err; throw err;
} }
} }
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
}
} }

View file

@ -60,9 +60,12 @@ Future<void> startFiatRateUpdate(
tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled); tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
} }
if (currencies != null) { if (currencies != null) {
for (final currency in currencies) { for (final currency in currencies) {
// skip potential scams:
if (currency.isPotentialScam) {
continue;
}
() async { () async {
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
crypto: currency, crypto: currency,

View file

@ -154,4 +154,9 @@ class CWSolana extends Solana {
double? getEstimateFees(WalletBase wallet) { double? getEstimateFees(WalletBase wallet) {
return (wallet as SolanaWallet).estimatedFee; return (wallet as SolanaWallet).estimatedFee;
} }
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultSPLTokens().initialSPLTokens.map((e) => e.mintAddress).toList();
}
} }

View file

@ -211,6 +211,21 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
.checkIfERC20TokenContractAddressIsAPotentialScamAddress( .checkIfERC20TokenContractAddressIsAPotentialScamAddress(
_contractAddressController.text, _contractAddressController.text,
); );
final isWhitelisted = await widget.homeSettingsViewModel
.checkIfTokenIsWhitelisted(_contractAddressController.text);
bool isPotentialScam = hasPotentialError;
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
final baseCurrencySymbols =
CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList();
if (baseCurrencySymbols.contains(tokenSymbol) && !isWhitelisted) {
isPotentialScam = true;
}
final actionCall = () async { final actionCall = () async {
try { try {
await widget.homeSettingsViewModel.addToken( await widget.homeSettingsViewModel.addToken(
@ -219,6 +234,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
title: _tokenSymbolController.text.toUpperCase(), title: _tokenSymbolController.text.toUpperCase(),
decimals: int.parse(_tokenDecimalController.text), decimals: int.parse(_tokenDecimalController.text),
iconPath: _tokenIconPathController.text, iconPath: _tokenIconPathController.text,
isPotentialScam: isPotentialScam,
), ),
contractAddress: _contractAddressController.text, contractAddress: _contractAddressController.text,
); );
@ -226,7 +242,6 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (mounted) { if (mounted) {
Navigator.pop(context); Navigator.pop(context);
} }
} catch (e) { } catch (e) {
showPopUp<void>( showPopUp<void>(
context: context, context: context,
@ -303,7 +318,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (token != null) { if (token != null) {
final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano; final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano;
if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name; if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name;
if (_tokenSymbolController.text.isEmpty || isZano) _tokenSymbolController.text = token.title; if (_tokenSymbolController.text.isEmpty || isZano)
_tokenSymbolController.text = token.title;
if (_tokenIconPathController.text.isEmpty) if (_tokenIconPathController.text.isEmpty)
_tokenIconPathController.text = token.iconPath ?? ''; _tokenIconPathController.text = token.iconPath ?? '';
if (_tokenDecimalController.text.isEmpty || isZano) if (_tokenDecimalController.text.isEmpty || isZano)
@ -338,7 +354,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
placeholder: S.of(context).token_contract_address, placeholder: S.of(context).token_contract_address,
options: [AddressTextFieldOption.paste], options: [AddressTextFieldOption.paste],
buttonColor: Theme.of(context).hintColor, buttonColor: Theme.of(context).hintColor,
validator: widget.homeSettingsViewModel.walletType == WalletType.zano ? null : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call, validator: widget.homeSettingsViewModel.walletType == WalletType.zano
? null
: AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call,
onPushPasteButton: (_) { onPushPasteButton: (_) {
_pasteText(); _pasteText();
}, },

View file

@ -133,4 +133,9 @@ class CWTron extends Tron {
void updateTronGridUsageState(WalletBase wallet, bool isEnabled) { void updateTronGridUsageState(WalletBase wallet, bool isEnabled) {
(wallet as TronWallet).updateScanProviderUsageState(isEnabled); (wallet as TronWallet).updateScanProviderUsageState(isEnabled);
} }
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultTronTokens().initialTronTokens.map((e) => e.contractAddress).toList();
}
} }

View file

@ -119,8 +119,7 @@ abstract class HomeSettingsViewModelBase with Store {
_updateFiatPrices(token); _updateFiatPrices(token);
} catch (e) { } catch (e) {
throw e; throw e;
} } finally {
finally {
isAddingToken = false; isAddingToken = false;
} }
} }
@ -189,6 +188,40 @@ abstract class HomeSettingsViewModelBase with Store {
} }
} }
bool checkIfTokenIsWhitelisted(String contractAddress) {
// get the default tokens for each currency type:
List<String> defaultTokenAddresses = [];
switch (_balanceViewModel.wallet.type) {
case WalletType.ethereum:
defaultTokenAddresses = ethereum!.getDefaultTokenContractAddresses();
break;
case WalletType.polygon:
defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses();
break;
case WalletType.solana:
defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
break;
case WalletType.tron:
defaultTokenAddresses = tron!.getDefaultTokenContractAddresses();
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:
return false;
}
// check if the contractAddress is in the defaultTokenAddresses
bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress);
return isInWhitelist;
}
Future<bool> _isPotentialScamTokenViaMoralis( Future<bool> _isPotentialScamTokenViaMoralis(
String contractAddress, String contractAddress,
String chainName, String chainName,
@ -363,6 +396,7 @@ abstract class HomeSettingsViewModelBase with Store {
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency; CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
void _updateFiatPrices(CryptoCurrency token) async { void _updateFiatPrices(CryptoCurrency token) async {
if (token.isPotentialScam) return; // don't fetch price data for potential scam tokens
try { try {
_balanceViewModel.fiatConvertationStore.prices[token] = _balanceViewModel.fiatConvertationStore.prices[token] =
await FiatConversionService.fetchPrice( await FiatConversionService.fetchPrice(
@ -455,7 +489,8 @@ abstract class HomeSettingsViewModelBase with Store {
} }
if (_balanceViewModel.wallet.type == WalletType.zano) { if (_balanceViewModel.wallet.type == WalletType.zano) {
tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet) tokens.addAll(zano!
.getZanoAssets(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element)) .where((element) => _matchesSearchText(element))
.toList() .toList()
..sort(_sortFunc)); ..sort(_sortFunc));

View file

@ -872,6 +872,7 @@ import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_client.dart';
import 'package:cw_ethereum/ethereum_wallet.dart'; import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart'; import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
@ -922,6 +923,7 @@ abstract class Ethereum {
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
List<String> getDefaultTokenContractAddresses();
} }
"""; """;
@ -977,6 +979,7 @@ import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_wallet.dart'; import 'package:cw_polygon/polygon_wallet.dart';
import 'package:cw_polygon/polygon_wallet_service.dart'; import 'package:cw_polygon/polygon_wallet_service.dart';
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
@ -1027,6 +1030,7 @@ abstract class Polygon {
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
List<String> getDefaultTokenContractAddresses();
} }
"""; """;
@ -1269,6 +1273,7 @@ import 'package:cw_solana/solana_wallet_service.dart';
import 'package:cw_solana/solana_transaction_info.dart'; import 'package:cw_solana/solana_transaction_info.dart';
import 'package:cw_solana/solana_transaction_credentials.dart'; import 'package:cw_solana/solana_transaction_credentials.dart';
import 'package:cw_solana/solana_wallet_creation_credentials.dart'; import 'package:cw_solana/solana_wallet_creation_credentials.dart';
import 'package:cw_solana/default_spl_tokens.dart';
"""; """;
const solanaCwPart = "part 'cw_solana.dart';"; const solanaCwPart = "part 'cw_solana.dart';";
const solanaContent = """ const solanaContent = """
@ -1310,6 +1315,7 @@ abstract class Solana {
String getTokenAddress(CryptoCurrency asset); String getTokenAddress(CryptoCurrency asset);
List<int>? getValidationLength(CryptoCurrency type); List<int>? getValidationLength(CryptoCurrency type);
double? getEstimateFees(WalletBase wallet); double? getEstimateFees(WalletBase wallet);
List<String> getDefaultTokenContractAddresses();
} }
"""; """;
@ -1355,6 +1361,7 @@ import 'package:cw_tron/tron_client.dart';
import 'package:cw_tron/tron_token.dart'; import 'package:cw_tron/tron_token.dart';
import 'package:cw_tron/tron_wallet.dart'; import 'package:cw_tron/tron_wallet.dart';
import 'package:cw_tron/tron_wallet_service.dart'; import 'package:cw_tron/tron_wallet_service.dart';
import 'package:cw_tron/default_tron_tokens.dart';
"""; """;
const tronCwPart = "part 'cw_tron.dart';"; const tronCwPart = "part 'cw_tron.dart';";
@ -1386,6 +1393,7 @@ abstract class Tron {
String? getTronTRC20EstimatedFee(WalletBase wallet); String? getTronTRC20EstimatedFee(WalletBase wallet);
void updateTronGridUsageState(WalletBase wallet, bool isEnabled); void updateTronGridUsageState(WalletBase wallet, bool isEnabled);
List<String> getDefaultTokenContractAddresses();
} }
"""; """;