mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
* feat: Add Binance Smart Chain support to wallet types and EVM chain registry * feat: Add BSC node list and support in wallet type and chain ID mappings * feat: Add BSC default tokens and update evm utility functions * feat: Integrate BSC support in node settings and management * feat: Add BSC support to config scripts and wallet type generation * feat: Add BSC support to EVM chain ID and switcher * feat: Extend BSC support in wallet utilities and chain type checks * feat: Add BSC support to different wallet functions * feat: Add BSCURI class and implement for uri handling * feat: Update Android and iOS configurations to support BSC URL schemes * feat: Add BSC support to different functionalities and settings * refactor: Update wallet type name from'Binance Smart Chain' to BNB Smart Chain * feat: Add switcher icon and updates token utils * Review fixes * More changes to BSC name, wallet listing and images * Add more default tokens
346 lines
11 KiB
Dart
346 lines
11 KiB
Dart
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
|
import 'package:cake_wallet/evm/evm.dart';
|
|
import 'package:cw_core/cake_hive.dart';
|
|
import 'package:cw_core/crypto_currency.dart';
|
|
import 'package:cw_core/currency_for_wallet_type.dart';
|
|
import 'package:cw_core/erc20_token.dart';
|
|
import 'package:cw_core/spl_token.dart';
|
|
import 'package:cw_core/tron_token.dart';
|
|
import 'package:cw_core/wallet_base.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_type.dart';
|
|
import 'package:hive/hive.dart';
|
|
|
|
class TokenUtilities {
|
|
static Future<List<Erc20Token>> loadAllUniqueEvmTokens() async {
|
|
final allWi = await WalletInfo.getAll();
|
|
final evmWallets = allWi.where(
|
|
(w) => isEVMCompatibleChain(w.type),
|
|
);
|
|
|
|
final seen = <String>{};
|
|
final unique = <Erc20Token>[];
|
|
|
|
for (final wallet in evmWallets) {
|
|
final chain = getTokenNameBasedOnWalletType(wallet.type);
|
|
final box = await _openEvmTokensBoxFor(wallet);
|
|
|
|
for (final t in box.values.where((t) => t.enabled)) {
|
|
final key = '$chain|${t.contractAddress.toLowerCase()}';
|
|
if (seen.add(key)) {
|
|
unique.add(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
return unique;
|
|
}
|
|
|
|
static Future<List<SPLToken>> loadAllUniqueSolTokens() async {
|
|
final allWi = await WalletInfo.getAll();
|
|
final solWallets = allWi.where(
|
|
(w) => w.type == WalletType.solana,
|
|
);
|
|
|
|
final tokens = <SPLToken>[];
|
|
for (final wallet in solWallets) {
|
|
final box = await _openSolTokensBoxFor(wallet);
|
|
tokens.addAll(box.values.where((t) => t.enabled));
|
|
}
|
|
|
|
final seen = <String>{};
|
|
final unique = <SPLToken>[];
|
|
for (final token in tokens) {
|
|
final key = token.mintAddress.toLowerCase();
|
|
if (seen.add(key)) unique.add(token);
|
|
}
|
|
return unique;
|
|
}
|
|
|
|
static Future<List<TronToken>> loadAllUniqueTronTokens() async {
|
|
final allWi = await WalletInfo.getAll();
|
|
final tronWallets = allWi.where(
|
|
(w) => w.type == WalletType.tron,
|
|
);
|
|
|
|
final seen = <String>{};
|
|
final unique = <TronToken>[];
|
|
for (final wallet in tronWallets) {
|
|
final box = await _openTronTokensBoxFor(wallet);
|
|
for (final t in box.values.where((t) => t.enabled)) {
|
|
final key = t.contractAddress.toLowerCase();
|
|
if (seen.add(key)) unique.add(t);
|
|
}
|
|
}
|
|
return unique;
|
|
}
|
|
|
|
/// Finds a token by address across wallets depending on [walletType]
|
|
/// - EVM chains: match by contractAddress
|
|
/// - Solana: match by mintAddress
|
|
/// - Tron: match by contractAddress
|
|
static Future<CryptoCurrency?> findTokenByAddress({
|
|
required WalletType walletType,
|
|
required String address,
|
|
}) async {
|
|
final lower = address.toLowerCase();
|
|
switch (walletType) {
|
|
case WalletType.ethereum:
|
|
case WalletType.polygon:
|
|
case WalletType.base:
|
|
case WalletType.arbitrum:
|
|
case WalletType.bsc:
|
|
final tokens = await loadAllUniqueEvmTokens();
|
|
for (final t in tokens) {
|
|
if (t.contractAddress.toLowerCase() == lower) return t;
|
|
}
|
|
return null;
|
|
case WalletType.solana:
|
|
final solTokens = await loadAllUniqueSolTokens();
|
|
for (final t in solTokens) {
|
|
if (t.mintAddress.toLowerCase() == lower) return t;
|
|
}
|
|
return null;
|
|
case WalletType.tron:
|
|
final tronTokens = await loadAllUniqueTronTokens();
|
|
for (final t in tronTokens) {
|
|
if (t.contractAddress.toLowerCase() == lower) return t;
|
|
}
|
|
return null;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static Future<Box<Erc20Token>> _openEvmTokensBoxFor(WalletInfo walletInfo) async {
|
|
final walletKey = walletInfo.name.replaceAll(' ', '_');
|
|
final boxName = _getErc20TokensBoxName(walletKey, walletInfo.type);
|
|
|
|
if (CakeHive.isBoxOpen(boxName)) {
|
|
return CakeHive.box<Erc20Token>(boxName);
|
|
}
|
|
return CakeHive.openBox<Erc20Token>(boxName);
|
|
}
|
|
|
|
static String _getErc20TokensBoxName(String walletKey, WalletType walletType) {
|
|
return switch (walletType) {
|
|
WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
|
|
WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
|
|
WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
|
|
WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}',
|
|
WalletType.bsc => '${walletKey}_${Erc20Token.bscBoxName}',
|
|
_ => '${walletKey}_${Erc20Token.ethereumBoxName}',
|
|
};
|
|
}
|
|
|
|
static Future<Box<SPLToken>> _openSolTokensBoxFor(WalletInfo wallet) async {
|
|
final boxName = '${wallet.name.replaceAll(' ', '_')}_${SPLToken.boxName}';
|
|
if (CakeHive.isBoxOpen(boxName)) {
|
|
return CakeHive.box<SPLToken>(boxName);
|
|
}
|
|
return CakeHive.openBox<SPLToken>(boxName);
|
|
}
|
|
|
|
static Future<Box<TronToken>> _openTronTokensBoxFor(WalletInfo walletInfo) async {
|
|
final boxName = '${walletInfo.name.replaceAll(' ', '_')}_${TronToken.boxName}';
|
|
if (CakeHive.isBoxOpen(boxName)) {
|
|
return CakeHive.box<TronToken>(boxName);
|
|
}
|
|
return CakeHive.openBox<TronToken>(boxName);
|
|
}
|
|
|
|
static Erc20Token? findErc20Token(CryptoCurrency currency, WalletBase wallet) {
|
|
if (currency is Erc20Token) return currency;
|
|
|
|
// More of a fallback for us
|
|
for (final balanceCurrency in wallet.balance.keys) {
|
|
if (balanceCurrency is Erc20Token && _matchesCurrency(balanceCurrency, currency)) {
|
|
return balanceCurrency;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static bool isNativeToken(CryptoCurrency currency) {
|
|
final title = currency.title.toLowerCase();
|
|
final tag = currency.tag?.toLowerCase();
|
|
|
|
return title == 'eth' ||
|
|
title == 'ethereum' ||
|
|
title == 'matic' ||
|
|
title == 'polygon' ||
|
|
title == 'base' ||
|
|
title == 'arbitrum' ||
|
|
title == 'bnb' ||
|
|
title == 'bsc' ||
|
|
title == 'avax' ||
|
|
title == 'avalanche' ||
|
|
tag == 'polygon' ||
|
|
tag == 'bsc' ||
|
|
tag == 'avalanche';
|
|
}
|
|
|
|
static int getChainId(CryptoCurrency currency) {
|
|
final tag = currency.tag?.toUpperCase();
|
|
final title = currency.title.toLowerCase();
|
|
|
|
// Only check EVM registry for currencies that might be EVM-related
|
|
final isPotentialEVM = isNativeToken(currency) ||
|
|
(tag != null &&
|
|
(tag == 'ETH' || tag == 'POL' || tag == 'BASE' || tag == 'ARB' || tag == 'BSC'));
|
|
|
|
if (isPotentialEVM) {
|
|
// Try by tag first if available (e.g., 'POL', 'BASE', 'ARB')
|
|
if (tag != null) {
|
|
final chainId = evm!.getChainIdByTag(tag);
|
|
if (chainId != null) return chainId;
|
|
}
|
|
|
|
// Try by title (case-insensitive)
|
|
final titleChainId = evm!.getChainIdByTitle(title);
|
|
if (titleChainId != null) return titleChainId;
|
|
}
|
|
|
|
// Fallback to hardcoded values for chains not in registry yet
|
|
// Avalanche C-Chain
|
|
if (title == 'avalanche' || title == 'avax' || tag == 'AVALANCHE') {
|
|
return 43114;
|
|
}
|
|
|
|
// Optimism
|
|
if (title == 'optimism' || title == 'op' || tag == 'OPTIMISM') {
|
|
return 10;
|
|
}
|
|
|
|
// Fantom Opera
|
|
if (title == 'fantom' || title == 'ftm' || tag == 'FANTOM') {
|
|
return 250;
|
|
}
|
|
|
|
// Default to Ethereum mainnet
|
|
return 1;
|
|
}
|
|
|
|
static Future<List<CryptoCurrency>> getAvailableTokensForNetwork(
|
|
WalletType network,
|
|
) async {
|
|
final baseCurrency = walletTypeToCryptoCurrency(network);
|
|
final allTokens = <CryptoCurrency>[baseCurrency];
|
|
final addedAddresses = <String>{};
|
|
|
|
// Handle EVM networks
|
|
if (isEVMCompatibleChain(network)) {
|
|
// First, collect all user tokens
|
|
final userTokens = await _getUserTokensForNetwork(baseCurrency);
|
|
for (final token in userTokens) {
|
|
if (token is Erc20Token) {
|
|
final address = token.contractAddress.toLowerCase();
|
|
if (addedAddresses.add(address)) {
|
|
allTokens.add(token);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then add tokens from CryptoCurrency.all that don't duplicate user tokens
|
|
for (final currency in CryptoCurrency.all) {
|
|
// Match by tag for POL/BASE, match by title==tag for ETH
|
|
final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) ||
|
|
(baseCurrency.tag != null &&
|
|
currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase());
|
|
|
|
if (matches) {
|
|
if (currency is Erc20Token) {
|
|
final address = currency.contractAddress.toLowerCase();
|
|
if (addedAddresses.add(address)) {
|
|
allTokens.add(currency);
|
|
}
|
|
} else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
|
|
allTokens.add(currency);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle Solana network
|
|
else if (network == WalletType.solana) {
|
|
final userSolTokens = await loadAllUniqueSolTokens();
|
|
for (final token in userSolTokens) {
|
|
final mintAddress = token.mintAddress.toLowerCase();
|
|
if (addedAddresses.add(mintAddress)) {
|
|
allTokens.add(token);
|
|
}
|
|
}
|
|
|
|
for (final currency in CryptoCurrency.all) {
|
|
if (currency.tag?.toLowerCase() == 'sol') {
|
|
if (currency is SPLToken) {
|
|
final mintAddress = currency.mintAddress.toLowerCase();
|
|
if (addedAddresses.add(mintAddress)) {
|
|
allTokens.add(currency);
|
|
}
|
|
} else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
|
|
allTokens.add(currency);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle Tron network
|
|
else if (network == WalletType.tron) {
|
|
final userTronTokens = await loadAllUniqueTronTokens();
|
|
for (final token in userTronTokens) {
|
|
final contractAddress = token.contractAddress.toLowerCase();
|
|
if (addedAddresses.add(contractAddress)) {
|
|
allTokens.add(token);
|
|
}
|
|
}
|
|
|
|
for (final currency in CryptoCurrency.all) {
|
|
if (currency.tag?.toLowerCase() == 'trx') {
|
|
if (currency is TronToken) {
|
|
final contractAddress = currency.contractAddress.toLowerCase();
|
|
if (addedAddresses.add(contractAddress)) {
|
|
allTokens.add(currency);
|
|
}
|
|
} else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
|
|
allTokens.add(currency);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return allTokens;
|
|
}
|
|
|
|
static bool _matchesCurrency(CryptoCurrency a, CryptoCurrency b) {
|
|
return a.title.toUpperCase() == b.title.toUpperCase() &&
|
|
(a.tag?.toUpperCase() == b.tag?.toUpperCase());
|
|
}
|
|
|
|
static Future<List<CryptoCurrency>> _getUserTokensForNetwork(CryptoCurrency baseCurrency) async {
|
|
final walletType = cryptoCurrencyToWalletType(baseCurrency);
|
|
if (walletType == null) return [];
|
|
|
|
if (isEVMCompatibleChain(walletType)) {
|
|
final tokens = await TokenUtilities.loadAllUniqueEvmTokens();
|
|
|
|
return tokens.where((token) {
|
|
if (baseCurrency.tag == null) return token.tag == baseCurrency.title;
|
|
|
|
return token.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase();
|
|
}).toList();
|
|
}
|
|
|
|
if (walletType == WalletType.solana) {
|
|
return await loadAllUniqueSolTokens();
|
|
}
|
|
|
|
if (walletType == WalletType.tron) {
|
|
return await loadAllUniqueTronTokens();
|
|
}
|
|
|
|
return [];
|
|
}
|
|
}
|