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

View file

@ -209,4 +209,9 @@ class CWEthereum extends Ethereum {
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;
}
}
@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);
}
if (currencies != null) {
for (final currency in currencies) {
// skip potential scams:
if (currency.isPotentialScam) {
continue;
}
() async {
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
crypto: currency,

View file

@ -154,4 +154,9 @@ class CWSolana extends Solana {
double? getEstimateFees(WalletBase wallet) {
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(
_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 {
try {
await widget.homeSettingsViewModel.addToken(
@ -219,6 +234,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
title: _tokenSymbolController.text.toUpperCase(),
decimals: int.parse(_tokenDecimalController.text),
iconPath: _tokenIconPathController.text,
isPotentialScam: isPotentialScam,
),
contractAddress: _contractAddressController.text,
);
@ -226,7 +242,6 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (mounted) {
Navigator.pop(context);
}
} catch (e) {
showPopUp<void>(
context: context,
@ -303,7 +318,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (token != null) {
final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano;
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)
_tokenIconPathController.text = token.iconPath ?? '';
if (_tokenDecimalController.text.isEmpty || isZano)
@ -338,7 +354,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
placeholder: S.of(context).token_contract_address,
options: [AddressTextFieldOption.paste],
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: (_) {
_pasteText();
},

View file

@ -133,4 +133,9 @@ class CWTron extends Tron {
void updateTronGridUsageState(WalletBase wallet, bool 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);
} catch (e) {
throw e;
}
finally {
} finally {
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(
String contractAddress,
String chainName,
@ -363,6 +396,7 @@ abstract class HomeSettingsViewModelBase with Store {
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
void _updateFiatPrices(CryptoCurrency token) async {
if (token.isPotentialScam) return; // don't fetch price data for potential scam tokens
try {
_balanceViewModel.fiatConvertationStore.prices[token] =
await FiatConversionService.fetchPrice(
@ -455,9 +489,10 @@ abstract class HomeSettingsViewModelBase with Store {
}
if (_balanceViewModel.wallet.type == WalletType.zano) {
tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
tokens.addAll(zano!
.getZanoAssets(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..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_wallet.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';
@ -922,6 +923,7 @@ abstract class Ethereum {
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
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_wallet.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';
@ -1027,6 +1030,7 @@ abstract class Polygon {
void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection);
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_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 solanaContent = """
@ -1310,6 +1315,7 @@ abstract class Solana {
String getTokenAddress(CryptoCurrency asset);
List<int>? getValidationLength(CryptoCurrency type);
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_wallet.dart';
import 'package:cw_tron/tron_wallet_service.dart';
import 'package:cw_tron/default_tron_tokens.dart';
""";
const tronCwPart = "part 'cw_tron.dart';";
@ -1386,6 +1393,7 @@ abstract class Tron {
String? getTronTRC20EstimatedFee(WalletBase wallet);
void updateTronGridUsageState(WalletBase wallet, bool isEnabled);
List<String> getDefaultTokenContractAddresses();
}
""";