mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
CW-1228: Automatically Fetch All Tokens (#2869)
* feat: Automatically detect wallet tokens for EVM chains * feat: Disable potential tg scam tokens by default * feat: Add homoglyph normalization to detect spoofing attacks in token symbols * refactor: Improve scam token detection and apply to automatically fetched EVM tokens * refactor: Enhance scam detection for automatically detected tokens in evm wallets * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Add fiat check to scam checks on automatically imported token and disable tokens that fail the check * feat: Enable whitelisted tokens with balance when importing wallet tokens * feat: Disable imported tokens with balance less than 0.1 usd * feat: Disable imported tokens with balance less than 0.1 usd - Update configure file
This commit is contained in:
@@ -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: []);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class EVMChainUtils {
|
||||
137 => 'MATIC',
|
||||
8453 => 'BASE',
|
||||
42161 => 'ARBITRUM',
|
||||
56 => 'BNB',
|
||||
56 => 'BSC',
|
||||
_ => 'ETH',
|
||||
};
|
||||
}
|
||||
|
||||
+26
-16
@@ -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 token = item.token;
|
||||
|
||||
final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token);
|
||||
|
||||
bool hasValidFiatPrice = true;
|
||||
if (!isPropertiesSuspicious) {
|
||||
hasValidFiatPrice = await checkTokenFiatPrice(wallet, 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);
|
||||
})());
|
||||
|
||||
+3
-2
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user