dev
dart 474 lines 13.9 KB
Raw
1 import 'package:cake_wallet/reactions/wallet_connect.dart';
2 import 'package:cake_wallet/evm/evm.dart';
3 import 'package:cake_wallet/solana/solana.dart';
4 import 'package:cake_wallet/tron/tron.dart';
5 import 'package:cw_core/cake_hive.dart';
6 import 'package:cw_core/crypto_currency.dart';
7 import 'package:cw_core/currency_for_wallet_type.dart';
8 import 'package:cw_core/erc20_token.dart';
9 import 'package:cw_core/spl_token.dart';
10 import 'package:cw_core/tron_token.dart';
11 import "package:cw_core/utils/print_verbose.dart";
12 import 'package:cw_core/wallet_base.dart';
13 import 'package:cw_core/wallet_info.dart';
14 import 'package:cw_core/wallet_type.dart';
15 import 'package:hive/hive.dart';
16
17 class TokenUtilities {
18 static Future<List<Erc20Token>> loadAllUniqueEvmTokens() async {
19 final allWi = await WalletInfo.getAll();
20 final evmWallets = allWi.where(
21 (w) => isEVMCompatibleChain(w.type),
22 );
23
24 final seen = <String>{};
25 final unique = <Erc20Token>[];
26
27 for (final wallet in evmWallets) {
28 final chain = getTokenNameBasedOnWalletType(wallet.type);
29 final tokens = await Erc20Token.getAllForWallet(wallet.name, _getDefaultChainId(wallet.type));
30
31 for (final t in tokens.where((t) => t.enabled)) {
32 final key = "$chain|${t.contractAddress.toLowerCase()}";
33 if (seen.add(key)) {
34 unique.add(t);
35 }
36 }
37 }
38
39 return unique;
40 }
41
42 static Future<List<SPLToken>> loadAllUniqueSolTokens() async {
43 final allWi = await WalletInfo.getAll();
44 final solWallets = allWi.where(
45 (w) => w.type == WalletType.solana,
46 );
47
48 final tokens = <SPLToken>[];
49 for (final wallet in solWallets) {
50 final walletTokens = await SPLToken.getAllForWallet(wallet.name);
51 tokens.addAll(walletTokens.where((t) => t.enabled));
52 }
53
54 final seen = <String>{};
55 final unique = <SPLToken>[];
56 for (final token in tokens) {
57 final key = token.mintAddress.toLowerCase();
58 if (seen.add(key)) {
59 unique.add(token);
60 }
61 }
62 return unique;
63 }
64
65 static Future<List<TronToken>> loadAllUniqueTronTokens() async {
66 final allWi = await WalletInfo.getAll();
67 final tronWallets = allWi.where(
68 (w) => w.type == WalletType.tron,
69 );
70
71 final seen = <String>{};
72 final unique = <TronToken>[];
73 for (final wallet in tronWallets) {
74 final walletTokens = await TronToken.getAllForWallet(wallet.name);
75 for (final t in walletTokens.where((t) => t.enabled)) {
76 final key = t.contractAddress.toLowerCase();
77 if (seen.add(key)) {
78 unique.add(t);
79 }
80 }
81 }
82 return unique;
83 }
84
85 static List<Erc20Token> loadDefaultEvmTokensForSwap() {
86 if (evm == null) {
87 return [];
88 }
89
90 final tokens = <Erc20Token>[];
91 final seen = <String>{};
92
93 for (final chain in evm!.getAllChains()) {
94 for (final token in evm!.getDefaultTokensByChainId(chain.chainId)) {
95 final key = "${chain.chainId}|${token.contractAddress.toLowerCase()}";
96 if (seen.add(key)) {
97 tokens.add(token);
98 }
99 }
100 }
101
102 return tokens;
103 }
104
105 static List<SPLToken> loadDefaultSolTokensForSwap() =>
106 solana != null ? solana!.getDefaultSPLTokens() : [];
107
108 static List<TronToken> loadDefaultTronTokensForSwap() =>
109 tron != null ? tron!.getDefaultTronTokens() : [];
110
111 static Future<List<Erc20Token>> loadEvmTokensForSwap() async {
112 final defaultTokens = loadDefaultEvmTokensForSwap();
113 final userTokens = await loadAllUniqueEvmTokens();
114
115 final seen = <String>{};
116 final result = <Erc20Token>[];
117
118 for (final t in [...defaultTokens, ...userTokens]) {
119 final key = '${t.tag ?? 'ETH'}|${t.contractAddress.toLowerCase()}';
120 if (seen.add(key)) {
121 result.add(t);
122 }
123 }
124
125 return result;
126 }
127
128 static Future<List<SPLToken>> loadSolTokensForSwap() async {
129 final defaultTokens = loadDefaultSolTokensForSwap();
130 final userTokens = await loadAllUniqueSolTokens();
131
132 final seen = <String>{};
133 final result = <SPLToken>[];
134
135 for (final t in [...defaultTokens, ...userTokens]) {
136 final key = t.mintAddress.toLowerCase();
137 if (seen.add(key)) {
138 result.add(t);
139 }
140 }
141
142 return result;
143 }
144
145 static Future<List<TronToken>> loadTronTokensForSwap() async {
146 final defaultTokens = loadDefaultTronTokensForSwap();
147 final userTokens = await loadAllUniqueTronTokens();
148
149 final seen = <String>{};
150 final result = <TronToken>[];
151
152 for (final t in [...defaultTokens, ...userTokens]) {
153 final key = t.contractAddress.toLowerCase();
154 if (seen.add(key)) {
155 result.add(t);
156 }
157 }
158
159 return result;
160 }
161
162 /// Finds a token by address for the given [walletType].
163 /// - EVM chains: match by contractAddress
164 /// - Solana: match by mintAddress
165 /// - Tron: match by contractAddress
166 static Future<CryptoCurrency?> findTokenByAddress({
167 required WalletType walletType,
168 required String address,
169 }) async {
170 if (address.isEmpty) {
171 return null;
172 }
173 final lower = address.toLowerCase();
174 final tokens = await getAvailableTokensForNetwork(walletType);
175 for (final t in tokens) {
176 if (t is Erc20Token && t.contractAddress.toLowerCase() == lower) {
177 return t;
178 }
179 if (t is SPLToken && t.mintAddress.toLowerCase() == lower) {
180 return t;
181 }
182 if (t is TronToken && t.contractAddress.toLowerCase() == lower) {
183 return t;
184 }
185 }
186 return null;
187 }
188
189 static int _getDefaultChainId(WalletType walletType) => switch (walletType) {
190 WalletType.ethereum => 1,
191 WalletType.polygon => 137,
192 WalletType.base => 8453,
193 WalletType.arbitrum => 42161,
194 WalletType.bsc => 56,
195 _ => 1,
196 };
197
198 static Future<int?> findEvmChainIdForContract(
199 String contractAddress, {
200 int? excludingChainId,
201 }) async {
202 if (evm == null || contractAddress.isEmpty) {
203 return null;
204 }
205
206 try {
207 for (final chain in evm!.getAllChains()) {
208 if (chain.chainId == excludingChainId) {
209 continue;
210 }
211
212 final walletType = evm!.getWalletTypeByChainId(chain.chainId);
213 if (walletType == null) {
214 continue;
215 }
216
217 final token = await findTokenByAddress(walletType: walletType, address: contractAddress);
218 if (token != null) {
219 return chain.chainId;
220 }
221 }
222 } catch (e) {
223 printV("findEvmChainIdForContract failed: $e");
224 }
225
226 return null;
227 }
228
229 static Erc20Token? findErc20Token(CryptoCurrency currency, WalletBase wallet) {
230 if (currency is Erc20Token) {
231 return currency;
232 }
233
234 // More of a fallback for us
235 for (final balanceCurrency in wallet.balance.keys) {
236 if (balanceCurrency is Erc20Token && _matchesCurrency(balanceCurrency, currency)) {
237 return balanceCurrency;
238 }
239 }
240
241 return null;
242 }
243
244 static Erc20Token? findErc20TokenForSwap(CryptoCurrency currency) {
245 if (currency is Erc20Token) {
246 return currency;
247 }
248
249 for (final token in loadDefaultEvmTokensForSwap()) {
250 if (_matchesCurrency(token, currency)) {
251 return token;
252 }
253 }
254 return null;
255 }
256
257 static String? findSolanaTokenMint(CryptoCurrency currency) {
258 if (currency is SPLToken) return currency.mintAddress;
259
260 for (final token in loadDefaultSolTokensForSwap()) {
261 if (_matchesCurrency(token, currency)) return token.mintAddress;
262 }
263 return null;
264 }
265
266 static String? findTronTokenContract(CryptoCurrency currency) {
267 if (currency is TronToken) return currency.contractAddress;
268
269 for (final token in loadDefaultTronTokensForSwap()) {
270 if (_matchesCurrency(token, currency)) return token.contractAddress;
271 }
272 return null;
273 }
274
275 static bool isNativeToken(CryptoCurrency currency) {
276 final title = currency.title.toLowerCase();
277 final tag = currency.tag?.toLowerCase();
278
279 return title == "eth" ||
280 title == "ethereum" ||
281 title == "matic" ||
282 title == "polygon" ||
283 title == "base" ||
284 title == "arbitrum" ||
285 title == "bnb" ||
286 title == "bsc" ||
287 title == "avax" ||
288 title == "avalanche" ||
289 tag == "polygon" ||
290 tag == "bsc" ||
291 tag == "avalanche";
292 }
293
294 static int getChainId(CryptoCurrency currency) {
295 final tag = currency.tag?.toUpperCase();
296 final title = currency.title.toLowerCase();
297
298 // Only check EVM registry for currencies that might be EVM-related
299 final isPotentialEVM = isNativeToken(currency) ||
300 (tag != null &&
301 (tag == "ETH" || tag == "POL" || tag == "BASE" || tag == "ARB" || tag == "BSC"));
302
303 if (isPotentialEVM) {
304 // Try by tag first if available (e.g., 'POL', 'BASE', 'ARB')
305 if (tag != null) {
306 final chainId = evm?.getChainIdByTag(tag);
307 if (chainId != null) {
308 return chainId;
309 }
310 }
311
312 // Try by title (case-insensitive)
313 final titleChainId = evm?.getChainIdByTitle(title);
314 if (titleChainId != null) {
315 return titleChainId;
316 }
317 }
318
319 // Fallback to hardcoded values for chains not in registry yet
320 if (title == "avalanche" || title == "avax" || tag == "AVALANCHE") {
321 return 43114;
322 }
323
324 if (title == "optimism" || title == "op" || tag == "OPTIMISM") {
325 return 10;
326 }
327
328 if (title == "fantom" || title == "ftm" || tag == "FANTOM") {
329 return 250;
330 }
331
332 return 1;
333 }
334
335 static Future<List<CryptoCurrency>> getAvailableTokensForNetwork(
336 WalletType network,
337 ) async {
338 final baseCurrency = walletTypeToCryptoCurrency(network);
339 final allTokens = <CryptoCurrency>[baseCurrency];
340 final addedAddresses = <String>{};
341
342 if (isEVMCompatibleChain(network)) {
343 final userTokens = await _getUserTokensForNetwork(baseCurrency);
344 for (final token in userTokens) {
345 if (token is Erc20Token) {
346 final address = token.contractAddress.toLowerCase();
347 if (addedAddresses.add(address)) {
348 allTokens.add(token);
349 }
350 }
351 }
352
353 if (evm != null) {
354 final chainId = evm!.getChainIdByWalletType(network);
355 for (final token in evm!.getDefaultTokensByChainId(chainId)) {
356 final address = token.contractAddress.toLowerCase();
357 if (addedAddresses.add(address)) {
358 allTokens.add(token);
359 }
360 }
361 }
362
363 // Add tokens from CryptoCurrency.all that don't duplicate user tokens
364 for (final currency in CryptoCurrency.all) {
365 // Match by tag for POL/BASE, match by title==tag for ETH
366 final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) ||
367 (baseCurrency.tag != null &&
368 currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase());
369
370 if (matches) {
371 if (currency is Erc20Token) {
372 final address = currency.contractAddress.toLowerCase();
373 if (addedAddresses.add(address)) {
374 allTokens.add(currency);
375 }
376 } else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
377 allTokens.add(currency);
378 }
379 }
380 }
381 } else if (network == WalletType.solana) {
382 final userSolTokens = await loadAllUniqueSolTokens();
383 for (final token in userSolTokens) {
384 final mintAddress = token.mintAddress.toLowerCase();
385 if (addedAddresses.add(mintAddress)) {
386 allTokens.add(token);
387 }
388 }
389
390 for (final token in loadDefaultSolTokensForSwap()) {
391 final mintAddress = token.mintAddress.toLowerCase();
392 if (addedAddresses.add(mintAddress)) {
393 allTokens.add(token);
394 }
395 }
396
397 for (final currency in CryptoCurrency.all) {
398 if (currency.tag?.toLowerCase() == "sol") {
399 if (currency is SPLToken) {
400 final mintAddress = currency.mintAddress.toLowerCase();
401 if (addedAddresses.add(mintAddress)) {
402 allTokens.add(currency);
403 }
404 } else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
405 allTokens.add(currency);
406 }
407 }
408 }
409 } else if (network == WalletType.tron) {
410 final userTronTokens = await loadAllUniqueTronTokens();
411 for (final token in userTronTokens) {
412 final contractAddress = token.contractAddress.toLowerCase();
413 if (addedAddresses.add(contractAddress)) {
414 allTokens.add(token);
415 }
416 }
417
418 for (final token in loadDefaultTronTokensForSwap()) {
419 final contractAddress = token.contractAddress.toLowerCase();
420 if (addedAddresses.add(contractAddress)) {
421 allTokens.add(token);
422 }
423 }
424
425 for (final currency in CryptoCurrency.all) {
426 if (currency.tag?.toLowerCase() == "trx") {
427 if (currency is TronToken) {
428 final contractAddress = currency.contractAddress.toLowerCase();
429 if (addedAddresses.add(contractAddress)) {
430 allTokens.add(currency);
431 }
432 } else if (!allTokens.any((t) => _matchesCurrency(t, currency))) {
433 allTokens.add(currency);
434 }
435 }
436 }
437 }
438
439 return allTokens;
440 }
441
442 static bool _matchesCurrency(CryptoCurrency a, CryptoCurrency b) =>
443 a.title.toUpperCase() == b.title.toUpperCase() &&
444 (a.tag?.toUpperCase() == b.tag?.toUpperCase());
445
446 static Future<List<CryptoCurrency>> _getUserTokensForNetwork(CryptoCurrency baseCurrency) async {
447 final walletType = cryptoCurrencyOrTokenToWalletType(baseCurrency);
448 if (walletType == null) {
449 return [];
450 }
451
452 if (isEVMCompatibleChain(walletType)) {
453 final tokens = await TokenUtilities.loadAllUniqueEvmTokens();
454
455 return tokens.where((token) {
456 if (baseCurrency.tag == null) {
457 return token.tag == baseCurrency.title;
458 }
459
460 return token.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase();
461 }).toList();
462 }
463
464 if (walletType == WalletType.solana) {
465 return loadAllUniqueSolTokens();
466 }
467
468 if (walletType == WalletType.tron) {
469 return loadAllUniqueTronTokens();
470 }
471
472 return [];
473 }
474 }