Files
hash-wallet/lib/utils/token_utilities.dart
T
2a74317aff Add Base Wallet (#2558)
* chore: Create Base package template

* chore: Update configuration scripts and dependencies to include 'base' coin support

* chore: Update pubspec.yaml, adjust SDK constraints, and add new dependencies

* feat: Add support for base wallet type and cryptocurrency in related configurations

* feat: Implement BaseWallet and DefaultBaseTokens classes for managing base cryptocurrency transactions

* feat: Add BaseTransactionHistory and BaseTransactionInfo classes for handling base cryptocurrency transaction data

* feat: Implement BaseClient, BaseWalletService, and BaseMnemonicIsIncorrectException

* feat: Setup base proxy for main package and adjust token class name

* feat: Setup initial base node configuration

* feat: Integrate support for base wallet type across various functions and services

* feat: Add base support to exchange and outputs

* feat: Add base wallet type integration to wallet creation and node management

* feat: Add base support to transaction details and transaction list item

* feat: Implement BaseURI class and add base support across wallet creation and keys view models

* feat: Add support for BaseScan preference in settings store

* feat: Integrate Base support into various workflows and documentation

* feat: Addingbase config to windows and secret key support

* feat: Add base support to settings and address handling

* chore: Move walletTypeToCurrency to another file and adjust across codebase

* feat: Add 'basescan_history' string localization

* feat: Remove Cryptocurrency.base, it doesn't have a base currency

* feat: Set eth as native currency

* feat: Enhance Base support across various sending, exchange, transaction details, fees, dashboard and balance

* feat: Integrate Base wallet support in advanced privacy settings and home settings view models

* feat: Add Base wallet support with icon and integration across providers, default settings migration, preferences, wallet connect, settings and token utilities

* feat: Update currency handling to use baseEth for Base wallet integration across various components

* fix: Update default token symbol to ETH in Base wallet transaction model

* refactor: Change default currency references from BASE to ETH across client, transaction info, and wallet components

* feat: Introduce normalizedIconPath for CryptoCurrency to standardize icon retrieval across wallet components

* fix: Include baseEth as a native token in EVMChainClient for currency handling

* chore: Update base_icon.png

* feat: Add more nodes to base_node_list.yml

* refactor: Update wallet icon retrieval to use normalizedIconPath

* chore: Remove unused cases and cleanup impl.

* feat: Add base proxy generation to configure

* refactor: Update base proxy and generation code in configure.dart

* feat: Add missing wallet and mono images

* fix: Wrong image path for pr and add missing base qr image

* feat: Enhance EVMTransactionErrorFeesHandler to parse new insufficient funds error format and calculate transaction fees in ETH and USD

* feat: Add gas price safety buffer for Base chain transactions to improve fee estimation accuracy, also fixes send all not calculating correctly

* Update model_generator.sh [skip ci]

* Update assets/base_node_list.yml [skip ci]

* Update cw_base/lib/default_base_erc20_tokens.dart [skip ci]

* Review fixes

* fixes

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-10-11 04:24:32 +03:00

230 lines
6.8 KiB
Dart

import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.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(
Box<WalletInfo> walletInfoSource,
) async {
final evmWallets = walletInfoSource.values.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(
Box<WalletInfo> walletInfoSource,
) async {
final solWallets = walletInfoSource.values.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(
Box<WalletInfo> walletInfoSource,
) async {
final tronWallets = walletInfoSource.values.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 Box<WalletInfo> walletInfoSource,
required String address,
}) async {
final lower = address.toLowerCase();
switch (walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
final tokens = await loadAllUniqueEvmTokens(walletInfoSource);
for (final t in tokens) {
if (t.contractAddress.toLowerCase() == lower) return t;
}
return null;
case WalletType.solana:
final solTokens = await loadAllUniqueSolTokens(walletInfoSource);
for (final t in solTokens) {
if (t.mintAddress.toLowerCase() == lower) return t;
}
return null;
case WalletType.tron:
final tronTokens = await loadAllUniqueTronTokens(walletInfoSource);
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 = switch (walletInfo.type) {
WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
_ => '${walletKey}_${Erc20Token.ethereumBoxName}',
};
if (CakeHive.isBoxOpen(boxName)) {
return CakeHive.box<Erc20Token>(boxName);
}
return CakeHive.openBox<Erc20Token>(boxName);
}
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 && _matchesToken(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 == 'bnb' ||
title == 'bsc' ||
title == 'avax' ||
title == 'avalanche' ||
tag == 'polygon' ||
tag == 'bsc' ||
tag == 'avalanche';
}
static int getChainId(CryptoCurrency currency) {
final title = currency.title.toLowerCase();
final tag = currency.tag?.toLowerCase();
// Polygon
if (title == 'polygon' || title == 'matic' || tag == 'polygon') {
return 137;
}
// BSC (Binance Smart Chain)
if (title == 'bsc' || title == 'bnb' || tag == 'bsc') {
return 56;
}
// Avalanche C-Chain
if (title == 'avalanche' || title == 'avax' || tag == 'avalanche') {
return 43114;
}
// Arbitrum One
if (title == 'arbitrum' || title == 'arb' || tag == 'arbitrum') {
return 42161;
}
// Optimism
if (title == 'optimism' || title == 'op' || tag == 'optimism') {
return 10;
}
// Base
if (title == 'base' || tag == 'base') {
return 8453;
}
// Fantom Opera
if (title == 'fantom' || title == 'ftm' || tag == 'fantom') {
return 250;
}
// Default to Ethereum mainnet
return 1;
}
/// Checks if two currencies match (by title and tag)
static bool _matchesToken(Erc20Token token, CryptoCurrency currency) {
return token.title.toLowerCase() == currency.title.toLowerCase() &&
(token.tag?.toLowerCase() == currency.tag?.toLowerCase() ||
(token.tag == null && currency.tag == null));
}
}