diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 5fa31171b..1e95fa958 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -300,6 +300,46 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen return CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!; } + static CryptoCurrency? safeParseCurrencyFromString(String? raw, {CryptoCurrency? walletCurrency}) { + if (raw == null || raw.isEmpty) return null; + + try { + return CryptoCurrency.fromString(raw, walletCurrency: walletCurrency); + } catch (_) {} + + // try cleaned (keep only A–Z/0–9) + final cleaned = raw.trim().toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), ''); + try { + return CryptoCurrency.fromString(cleaned, walletCurrency: walletCurrency); + } catch (_) {} + + return null; + } + + CryptoCurrency copyWith({ + String? title, + int? raw, + String? name, + String? fullName, + String? iconPath, + String? tag, + int? decimals, + bool? enabled, + bool? isPotentialScam, + }) { + return CryptoCurrency( + title: title ?? this.title, + raw: raw ?? this.raw, + name: name ?? this.name, + fullName: fullName ?? this.fullName, + iconPath: iconPath ?? this.iconPath, + tag: tag ?? this.tag, + decimals: decimals ?? this.decimals, + enabled: enabled ?? this.enabled, + isPotentialScam: isPotentialScam ?? this.isPotentialScam, + ); + } + @override String toString() => title; } diff --git a/cw_solana/lib/spl_token.dart b/cw_core/lib/spl_token.dart similarity index 100% rename from cw_solana/lib/spl_token.dart rename to cw_core/lib/spl_token.dart diff --git a/cw_tron/lib/tron_token.dart b/cw_core/lib/tron_token.dart similarity index 100% rename from cw_tron/lib/tron_token.dart rename to cw_core/lib/tron_token.dart diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 9f998f714..805241d6a 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -248,10 +248,17 @@ abstract class EVMChainWalletBase // For fixing wrongly classified tokens if (!isPotentialScam && token.isPotentialScam) { token.isPotentialScam = false; - final iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - token.iconPath = iconPath; + + if (token.iconPath == null || token.iconPath!.isEmpty) { + try { + token.iconPath = CryptoCurrency.all + .firstWhere((e) => e.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) { + printV("Token ${token.symbol} does not have an icon path"); + } + } + await token.save(); } } diff --git a/cw_solana/lib/default_spl_tokens.dart b/cw_solana/lib/default_spl_tokens.dart index f7ce60aac..4f1a9b29b 100644 --- a/cw_solana/lib/default_spl_tokens.dart +++ b/cw_solana/lib/default_spl_tokens.dart @@ -1,5 +1,5 @@ import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_solana/spl_token.dart'; +import 'package:cw_core/spl_token.dart'; class DefaultSPLTokens { final List _defaultTokens = [ @@ -99,6 +99,448 @@ class DefaultSPLTokens { iconPath: 'assets/images/avdo_icon.png', enabled: false, ), + SPLToken( + name: 'Abbott xStock', + symbol: 'ABTx', + mintAddress: 'XsHtf5RpxsQ7jeJ9ivNewouZKJHbPxhPoEy6yYvULr7', + decimal: 8, + mint: 'abtx', + enabled: false), + SPLToken( + name: 'AbbVie xStock', + symbol: 'ABBVx', + mintAddress: 'XswbinNKyPmzTa5CskMbCPvMW6G5CMnZXZEeQSSQoie', + decimal: 8, + mint: 'abbvx', + enabled: false), + SPLToken( + name: 'Accenture xStock', + symbol: 'ACNx', + mintAddress: 'Xs5UJzmCRQ8DWZjskExdSQDnbE6iLkRu2jjrRAB1JSU', + decimal: 8, + mint: 'acnx', + enabled: false), + SPLToken( + name: 'Alphabet xStock', + symbol: 'GOOGLx', + mintAddress: 'XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN', + decimal: 8, + mint: 'googlx', + enabled: false), + SPLToken( + name: 'Amazon xStock', + symbol: 'AMZNx', + mintAddress: 'Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg', + decimal: 8, + mint: 'amznx', + enabled: false), + SPLToken( + name: 'Amber xStock', + symbol: 'AMBRx', + mintAddress: 'XsaQTCgebC2KPbf27KUhdv5JFvHhQ4GDAPURwrEhAzb', + decimal: 8, + mint: 'ambrx', + enabled: false), + SPLToken( + name: 'Apple xStock', + symbol: 'AAPLx', + mintAddress: 'XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp', + decimal: 8, + mint: 'aaplx', + enabled: false), + SPLToken( + name: 'AppLovin xStock', + symbol: 'APPx', + mintAddress: 'XsPdAVBi8Zc1xvv53k4JcMrQaEDTgkGqKYeh7AYgPHV', + decimal: 8, + mint: 'appx', + enabled: false), + SPLToken( + name: 'AstraZeneca xStock', + symbol: 'AZNx', + mintAddress: 'Xs3ZFkPYT2BN7qBMqf1j1bfTeTm1rFzEFSsQ1z3wAKU', + decimal: 8, + mint: 'aznx', + enabled: false), + SPLToken( + name: 'Bank of America xStock', + symbol: 'BACx', + mintAddress: 'XswsQk4duEQmCbGzfqUUWYmi7pV7xpJ9eEmLHXCaEQP', + decimal: 8, + mint: 'bacx', + enabled: false), + SPLToken( + name: 'Berkshire Hathaway xStock', + symbol: 'BRK.Bx', + mintAddress: 'Xs6B6zawENwAbWVi7w92rjazLuAr5Az59qgWKcNb45x', + decimal: 8, + mint: 'brkbx', + enabled: false), + SPLToken( + name: 'Broadcom xStock', + symbol: 'AVGOx', + mintAddress: 'XsgSaSvNSqLTtFuyWPBhK9196Xb9Bbdyjj4fH3cPJGo', + decimal: 8, + mint: 'avgox', + enabled: false), + SPLToken( + name: 'Chevron xStock', + symbol: 'CVXx', + mintAddress: 'XsNNMt7WTNA2sV3jrb1NNfNgapxRF5i4i6GcnTRRHts', + decimal: 8, + mint: 'cvxx', + enabled: false), + SPLToken( + name: 'Circle xStock', + symbol: 'CRCLx', + mintAddress: 'XsueG8BtpquVJX9LVLLEGuViXUungE6WmK5YZ3p3bd1', + decimal: 8, + mint: 'crclx', + enabled: false), + SPLToken( + name: 'Cisco xStock', + symbol: 'CSCOx', + mintAddress: 'Xsr3pdLQyXvDJBFgpR5nexCEZwXvigb8wbPYp4YoNFf', + decimal: 8, + mint: 'cscx', + enabled: false), + SPLToken( + name: 'Coca-Cola xStock', + symbol: 'KOx', + mintAddress: 'XsaBXg8dU5cPM6ehmVctMkVqoiRG2ZjMo1cyBJ3AykQ', + decimal: 8, + mint: 'kox', + enabled: false), + SPLToken( + name: 'Coinbase xStock', + symbol: 'COINx', + mintAddress: 'Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu', + decimal: 8, + mint: 'coinx', + enabled: false), + SPLToken( + name: 'Comcast xStock', + symbol: 'CMCSAx', + mintAddress: 'XsvKCaNsxg2GN8jjUmq71qukMJr7Q1c5R2Mk9P8kcS8', + decimal: 8, + mint: 'cmcsax', + enabled: false), + SPLToken( + name: 'CrowdStrike xStock', + symbol: 'CRWDx', + mintAddress: 'Xs7xXqkcK7K8urEqGg52SECi79dRp2cEKKuYjUePYDw', + decimal: 8, + mint: 'crwdx', + enabled: false), + SPLToken( + name: 'Danaher xStock', + symbol: 'DHRx', + mintAddress: 'Xseo8tgCZfkHxWS9xbFYeKFyMSbWEvZGFV1Gh53GtCV', + decimal: 8, + mint: 'dhrx', + enabled: false), + SPLToken( + name: 'DFDV xStock', + symbol: 'DFDVx', + mintAddress: 'Xs2yquAgsHByNzx68WJC55WHjHBvG9JsMB7CWjTLyPy', + decimal: 8, + mint: 'dfdvx', + enabled: false), + SPLToken( + name: 'Eli Lilly xStock', + symbol: 'LLYx', + mintAddress: 'Xsnuv4omNoHozR6EEW5mXkw8Nrny5rB3jVfLqi6gKMH', + decimal: 8, + mint: 'llyx', + enabled: false), + SPLToken( + name: 'Exxon Mobil xStock', + symbol: 'XOMx', + mintAddress: 'XsaHND8sHyfMfsWPj6kSdd5VwvCayZvjYgKmmcNL5qh', + decimal: 8, + mint: 'xomx', + enabled: false), + SPLToken( + name: 'Gamestop xStock', + symbol: 'GMEx', + mintAddress: 'Xsf9mBktVB9BSU5kf4nHxPq5hCBJ2j2ui3ecFGxPRGc', + decimal: 8, + mint: 'gmex', + enabled: false), + SPLToken( + name: 'Gold xStock', + symbol: 'GLDx', + mintAddress: 'Xsv9hRk1z5ystj9MhnA7Lq4vjSsLwzL2nxrwmwtD3re', + decimal: 8, + mint: 'gldx', + enabled: false), + SPLToken( + name: 'Goldman Sachs xStock', + symbol: 'GSx', + mintAddress: 'XsgaUyp4jd1fNBCxgtTKkW64xnnhQcvgaxzsbAq5ZD1', + decimal: 8, + mint: 'gsx', + enabled: false), + SPLToken( + name: 'Home Depot xStock', + symbol: 'HDx', + mintAddress: 'XszjVtyhowGjSC5odCqBpW1CtXXwXjYokymrk7fGKD3', + decimal: 8, + mint: 'hdx', + enabled: false), + SPLToken( + name: 'Honeywell xStock', + symbol: 'HONx', + mintAddress: 'XsRbLZthfABAPAfumWNEJhPyiKDW6TvDVeAeW7oKqA2', + decimal: 8, + mint: 'honx', + enabled: false), + SPLToken( + name: 'Intel xStock', + symbol: 'INTCx', + mintAddress: 'XshPgPdXFRWB8tP1j82rebb2Q9rPgGX37RuqzohmArM', + decimal: 8, + mint: 'intcx', + enabled: false), + SPLToken( + name: 'International Business Machines xStock', + symbol: 'IBMx', + mintAddress: 'XspwhyYPdWVM8XBHZnpS9hgyag9MKjLRyE3tVfmCbSr', + decimal: 8, + mint: 'ibmx', + enabled: false), + SPLToken( + name: 'Johnson & Johnson xStock', + symbol: 'JNJx', + mintAddress: 'XsGVi5eo1Dh2zUpic4qACcjuWGjNv8GCt3dm5XcX6Dn', + decimal: 8, + mint: 'jnjx', + enabled: false), + SPLToken( + name: 'JPMorgan Chase xStock', + symbol: 'JPMx', + mintAddress: 'XsMAqkcKsUewDrzVkait4e5u4y8REgtyS7jWgCpLV2C', + decimal: 8, + mint: 'jpmx', + enabled: false), + SPLToken( + name: 'Linde xStock', + symbol: 'LINx', + mintAddress: 'XsSr8anD1hkvNMu8XQiVcmiaTP7XGvYu7Q58LdmtE8Z', + decimal: 8, + mint: 'linx', + enabled: false), + SPLToken( + name: 'Marvell xStock', + symbol: 'MRVLx', + mintAddress: 'XsuxRGDzbLjnJ72v74b7p9VY6N66uYgTCyfwwRjVCJA', + decimal: 8, + mint: 'mrvlx', + enabled: false), + SPLToken( + name: 'Mastercard xStock', + symbol: 'MAx', + mintAddress: 'XsApJFV9MAktqnAc6jqzsHVujxkGm9xcSUffaBoYLKC', + decimal: 8, + mint: 'max', + enabled: false), + SPLToken( + name: 'McDonald\'s xStock', + symbol: 'MCDx', + mintAddress: 'XsqE9cRRpzxcGKDXj1BJ7Xmg4GRhZoyY1KpmGSxAWT2', + decimal: 8, + mint: 'mcdx', + enabled: false), + SPLToken( + name: 'Medtronic xStock', + symbol: 'MDTx', + mintAddress: 'XsDgw22qRLTv5Uwuzn6T63cW69exG41T6gwQhEK22u2', + decimal: 8, + mint: 'mdtx', + enabled: false), + SPLToken( + name: 'Merck xStock', + symbol: 'MRKx', + mintAddress: 'XsnQnU7AdbRZYe2akqqpibDdXjkieGFfSkbkjX1Sd1X', + decimal: 8, + mint: 'mrkx', + enabled: false), + SPLToken( + name: 'Meta xStock', + symbol: 'METAx', + mintAddress: 'Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu', + decimal: 8, + mint: 'metax', + enabled: false), + SPLToken( + name: 'Microsoft xStock', + symbol: 'MSFTx', + mintAddress: 'XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX', + decimal: 8, + mint: 'msftx', + enabled: false), + SPLToken( + name: 'MicroStrategy xStock', + symbol: 'MSTRx', + mintAddress: 'XsP7xzNPvEHS1m6qfanPUGjNmdnmsLKEoNAnHjdxxyZ', + decimal: 8, + mint: 'mstrx', + enabled: false), + SPLToken( + name: 'Nasdaq xStock', + symbol: 'QQQx', + mintAddress: 'Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ', + decimal: 8, + mint: 'qqqx', + enabled: false), + SPLToken( + name: 'Netflix xStock', + symbol: 'NFLXx', + mintAddress: 'XsEH7wWfJJu2ZT3UCFeVfALnVA6CP5ur7Ee11KmzVpL', + decimal: 8, + mint: 'nflxx', + enabled: false), + SPLToken( + name: 'Novo Nordisk xStock', + symbol: 'NVOx', + mintAddress: 'XsfAzPzYrYjd4Dpa9BU3cusBsvWfVB9gBcyGC87S57n', + decimal: 8, + mint: 'nvox', + enabled: false), + SPLToken( + name: 'NVIDIA xStock', + symbol: 'NVDAx', + mintAddress: 'Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh', + decimal: 8, + mint: 'nvdax', + enabled: false), + SPLToken( + name: 'OPEN xStock', + symbol: 'OPENx', + mintAddress: 'XsGtpmjhmC8kyjVSWL4VicGu36ceq9u55PTgF8bhGv6', + decimal: 8, + mint: 'openx', + enabled: false), + SPLToken( + name: 'Oracle xStock', + symbol: 'ORCLx', + mintAddress: 'XsjFwUPiLofddX5cWFHW35GCbXcSu1BCUGfxoQAQjeL', + decimal: 8, + mint: 'orclx', + enabled: false), + SPLToken( + name: 'Palantir xStock', + symbol: 'PLTRx', + mintAddress: 'XsoBhf2ufR8fTyNSjqfU71DYGaE6Z3SUGAidpzriAA4', + decimal: 8, + mint: 'pltrx', + enabled: false), + SPLToken( + name: 'PepsiCo xStock', + symbol: 'PEPx', + mintAddress: 'Xsv99frTRUeornyvCfvhnDesQDWuvns1M852Pez91vF', + decimal: 8, + mint: 'pepx', + enabled: false), + SPLToken( + name: 'Pfizer xStock', + symbol: 'PFEx', + mintAddress: 'XsAtbqkAP1HJxy7hFDeq7ok6yM43DQ9mQ1Rh861X8rw', + decimal: 8, + mint: 'pfex', + enabled: false), + SPLToken( + name: 'Philip Morris xStock', + symbol: 'PMx', + mintAddress: 'Xsba6tUnSjDae2VcopDB6FGGDaxRrewFCDa5hKn5vT3', + decimal: 8, + mint: 'pmx', + enabled: false), + SPLToken( + name: 'Procter & Gamble xStock', + symbol: 'PGx', + mintAddress: 'XsYdjDjNUygZ7yGKfQaB6TxLh2gC6RRjzLtLAGJrhzV', + decimal: 8, + mint: 'pgx', + enabled: false), + SPLToken( + name: 'Robinhood xStock', + symbol: 'HOODx', + mintAddress: 'XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg', + decimal: 8, + mint: 'hoodx', + enabled: false), + SPLToken( + name: 'Salesforce xStock', + symbol: 'CRMx', + mintAddress: 'XsczbcQ3zfcgAEt9qHQES8pxKAVG5rujPSHQEXi4kaN', + decimal: 8, + mint: 'crmx', + enabled: false), + SPLToken( + name: 'SP500 xStock', + symbol: 'SPYx', + mintAddress: 'XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W', + decimal: 8, + mint: 'spyx', + enabled: false), + SPLToken( + name: 'TBLL xStock', + symbol: 'TBLLx', + mintAddress: 'XsqBC5tcVQLYt8wqGCHRnAUUecbRYXoJCReD6w7QEKp', + decimal: 8, + mint: 'tbllx', + enabled: false), + SPLToken( + name: 'Tesla xStock', + symbol: 'TSLAx', + mintAddress: 'XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB', + decimal: 8, + mint: 'tslax', + enabled: false), + SPLToken( + name: 'Thermo Fisher xStock', + symbol: 'TMOx', + mintAddress: 'Xs8drBWy3Sd5QY3aifG9kt9KFs2K3PGZmx7jWrsrk57', + decimal: 8, + mint: 'tmox', + enabled: false), + SPLToken( + name: 'TQQQ xStock', + symbol: 'TQQQx', + mintAddress: 'XsjQP3iMAaQ3kQScQKthQpx9ALRbjKAjQtHg6TFomoc', + decimal: 8, + mint: 'tqqqx', + enabled: false), + SPLToken( + name: 'UnitedHealth xStock', + symbol: 'UNHx', + mintAddress: 'XszvaiXGPwvk2nwb3o9C1CX4K6zH8sez11E6uyup6fe', + decimal: 8, + mint: 'unhx', + enabled: false), + SPLToken( + name: 'Vanguard xStock', + symbol: 'VTIx', + mintAddress: 'XsssYEQjzxBCFgvYFFNuhJFBeHNdLWYeUSP8F45cDr9', + decimal: 8, + mint: 'vtix', + enabled: false), + SPLToken( + name: 'Visa xStock', + symbol: 'Vx', + mintAddress: 'XsqgsbXwWogGJsNcVZ3TyVouy2MbTkfCFhCGGGcQZ2p', + decimal: 8, + mint: 'vx', + enabled: false), + SPLToken( + name: 'Walmart xStock', + symbol: 'WMTx', + mintAddress: 'Xs151QeqTCiuKtinzfRATnUESM2xTU6V9Wy8Vy538ci', + decimal: 8, + mint: 'wmtx', + enabled: false), + ]; List get initialSPLTokens => _defaultTokens.map((token) { diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index f91f66223..00578952a 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -12,7 +12,7 @@ import 'package:cw_solana/pending_solana_transaction.dart'; import 'package:cw_solana/solana_balance.dart'; import 'package:cw_solana/solana_exceptions.dart'; import 'package:cw_solana/solana_transaction_model.dart'; -import 'package:cw_solana/spl_token.dart'; +import 'package:cw_core/spl_token.dart'; import 'package:on_chain/solana/solana.dart'; import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart'; import 'package:on_chain/solana/src/models/pda/pda.dart'; diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 87acaf4d5..39f4f6b13 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -25,7 +25,7 @@ import 'package:cw_solana/solana_transaction_history.dart'; import 'package:cw_solana/solana_transaction_info.dart'; import 'package:cw_solana/solana_transaction_model.dart'; import 'package:cw_solana/solana_wallet_addresses.dart'; -import 'package:cw_solana/spl_token.dart'; +import 'package:cw_core/spl_token.dart'; import 'package:hex/hex.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; diff --git a/cw_tron/lib/default_tron_tokens.dart b/cw_tron/lib/default_tron_tokens.dart index 6aa6357e6..8e5b9fd34 100644 --- a/cw_tron/lib/default_tron_tokens.dart +++ b/cw_tron/lib/default_tron_tokens.dart @@ -1,5 +1,5 @@ import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_tron/tron_token.dart'; +import 'package:cw_core/tron_token.dart'; class DefaultTronTokens { final List _defaultTokens = [ diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart index c7c530d53..60431674f 100644 --- a/cw_tron/lib/tron_client.dart +++ b/cw_tron/lib/tron_client.dart @@ -10,7 +10,7 @@ import 'package:cw_tron/pending_tron_transaction.dart'; import 'package:cw_tron/tron_abi.dart'; import 'package:cw_tron/tron_balance.dart'; import 'package:cw_tron/tron_http_provider.dart'; -import 'package:cw_tron/tron_token.dart'; +import 'package:cw_core/tron_token.dart'; import 'package:cw_tron/tron_transaction_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index ce1b22d0e..c857d0191 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -24,7 +24,7 @@ import 'package:cw_tron/tron_abi.dart'; import 'package:cw_tron/tron_balance.dart'; import 'package:cw_tron/tron_client.dart'; import 'package:cw_tron/tron_exception.dart'; -import 'package:cw_tron/tron_token.dart'; +import 'package:cw_core/tron_token.dart'; import 'package:cw_tron/tron_transaction_credentials.dart'; import 'package:cw_tron/tron_transaction_history.dart'; import 'package:cw_tron/tron_transaction_info.dart'; diff --git a/lib/core/trade_monitor.dart b/lib/core/trade_monitor.dart index 3b696b88a..408f7faa7 100644 --- a/lib/core/trade_monitor.dart +++ b/lib/core/trade_monitor.dart @@ -184,7 +184,9 @@ class TradeMonitor { trade ..stateRaw = updated.state.raw ..receiveAmount = updated.receiveAmount ?? trade.receiveAmount - ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction; + ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction + ..userCurrencyToRaw = updated.userCurrencyToRaw + ..userCurrencyFromRaw = updated.userCurrencyFromRaw; printV('Trade ${trade.id} updated: ${trade.state}'); await trade.save(); diff --git a/lib/di.dart b/lib/di.dart index e19ee0b3c..84047fa71 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -525,6 +525,7 @@ Future setup({ getIt.get(), getIt.get(), getIt.get(), + _walletInfoSource, ), ); diff --git a/lib/exchange/provider/chainflip_exchange_provider.dart b/lib/exchange/provider/chainflip_exchange_provider.dart index a5f8a171b..4a1f9e664 100644 --- a/lib/exchange/provider/chainflip_exchange_provider.dart +++ b/lib/exchange/provider/chainflip_exchange_provider.dart @@ -167,6 +167,8 @@ class ChainflipExchangeProvider extends ExchangeProvider { receiveAmount: request.toAmount, state: TradeState.waiting, payoutAddress: request.toAddress, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', isSendAll: isSendAll); } catch (e) { printV(e.toString()); @@ -199,6 +201,9 @@ class ChainflipExchangeProvider extends ExchangeProvider { final refundAmount = status['refundEgress']?['amount']?.toString() ?? '0.0'; final isRefund = status['refundEgress'] != null; final amount = isRefund ? refundAmount : receiveAmount; + + final from = status['sourceAsset'].toString(); + final to = status['destinationAsset'].toString(); final newTrade = Trade( id: id, @@ -210,7 +215,10 @@ class ChainflipExchangeProvider extends ExchangeProvider { state: currentState, payoutAddress: status['destinationAddress'].toString(), outputTransaction: status['swapEgress']?['transactionReference']?.toString(), - isRefund: isRefund); + isRefund: isRefund, + userCurrencyFromRaw: '${from.toUpperCase()}' + '_', + userCurrencyToRaw: '${to.toUpperCase()}' + '_', + ); // Find trade and update receiveAmount with the real value received final storedTrade = _getStoredTrade(id); diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index dbc4cc3ab..42a1e4693 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -215,6 +215,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider { receiveAmount: toAmount ?? request.toAmount, state: TradeState.created, payoutAddress: payoutAddress, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', isSendAll: isSendAll, ); } @@ -240,9 +242,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final responseJSON = json.decode(response.body) as Map; final fromCurrency = responseJSON['fromCurrency'] as String; - final from = CryptoCurrency.fromString(fromCurrency); + final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency); final toCurrency = responseJSON['toCurrency'] as String; - final to = CryptoCurrency.fromString(toCurrency); + final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency); final inputAddress = responseJSON['payinAddress'] as String; final expectedSendAmount = responseJSON['expectedAmountFrom'].toString(); final status = responseJSON['status'] as String; @@ -254,17 +256,20 @@ class ChangeNowExchangeProvider extends ExchangeProvider { final expiredAt = DateTime.tryParse(expiredAtRaw ?? '')?.toLocal(); return Trade( - id: id, - from: from, - to: to, - provider: description, - inputAddress: inputAddress, - amount: expectedSendAmount, - state: state, - extraId: extraId, - expiredAt: expiredAt, - outputTransaction: outputTransaction, - payoutAddress: payoutAddress); + id: id, + from: from, + to: to, + provider: description, + inputAddress: inputAddress, + amount: expectedSendAmount, + state: state, + extraId: extraId, + expiredAt: expiredAt, + outputTransaction: outputTransaction, + payoutAddress: payoutAddress, + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', + ); } String _getFlow(bool isFixedRate) => isFixedRate ? 'fixed-rate' : 'standard'; diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart index 0b7491bf5..a3b1b896c 100644 --- a/lib/exchange/provider/exolix_exchange_provider.dart +++ b/lib/exchange/provider/exolix_exchange_provider.dart @@ -214,6 +214,8 @@ class ExolixExchangeProvider extends ExchangeProvider { receiveAmount: receiveAmount ?? request.toAmount, state: TradeState.created, payoutAddress: payoutAddress, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', isSendAll: isSendAll, ); } @@ -249,15 +251,18 @@ class ExolixExchangeProvider extends ExchangeProvider { return Trade( id: id, - from: CryptoCurrency.fromString(coinFrom), - to: CryptoCurrency.fromString(coinTo), + from: CryptoCurrency.safeParseCurrencyFromString(coinFrom), + to: CryptoCurrency.safeParseCurrencyFromString(coinTo), provider: description, inputAddress: inputAddress, amount: amount, state: TradeState.deserialize(raw: _prepareStatus(status)), extraId: extraId, outputTransaction: outputTransaction, - payoutAddress: payoutAddress); + payoutAddress: payoutAddress, + userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_', + userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_', + ); } String _getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float'; diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart index 1fe1e9e35..7aa18aba1 100644 --- a/lib/exchange/provider/letsexchange_exchange_provider.dart +++ b/lib/exchange/provider/letsexchange_exchange_provider.dart @@ -62,7 +62,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { if (networkFrom != null) 'network_from': networkFrom, if (networkTo != null) 'network_to': networkTo, 'amount': '1', - 'affiliate_id': _affiliateId + 'affiliate_id': _affiliateId, + 'float': isFixedRateMode ? 'false' : 'true', }; final responseJSON = await _getInfo(params, isFixedRateMode); @@ -91,7 +92,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { if (networkFrom != null) 'network_from': networkFrom, if (networkTo != null) 'network_to': networkTo, 'amount': amount.toString(), - 'affiliate_id': _affiliateId + 'affiliate_id': _affiliateId, + 'float': isFixedRateMode ? 'false' : 'true', }; final responseJSON = await _getInfo(params, isFixedRateMode); @@ -121,7 +123,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { if (networkFrom != null) 'network_from': networkFrom, if (networkTo != null) 'network_to': networkTo, 'amount': isFixedRateMode ? request.toAmount.toString() : request.fromAmount.toString(), - 'affiliate_id': _affiliateId + 'affiliate_id': _affiliateId, + 'float': isFixedRateMode ? 'false' : 'true', }; final responseInfoJSON = await _getInfo(params, isFixedRateMode); @@ -141,7 +144,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { 'rate_id': rateId, if (networkFrom != null) 'network_from': networkFrom, if (networkTo != null) 'network_to': networkTo, - 'affiliate_id': _affiliateId + 'affiliate_id': _affiliateId, + 'float': isFixedRateMode ? 'false' : 'true', }; final headers = { @@ -151,10 +155,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { }; final uri = Uri.https(_baseUrl, - isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath, tradeParams); + isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath); final response = await ProxyWrapper().post( clearnetUri: uri, headers: headers, + body: json.encode(tradeParams), ); @@ -206,6 +211,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { createdAt: createdAt, expiredAt: expiredAt, extraId: extraId, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } catch (e) { log(e.toString()); @@ -230,7 +237,9 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { } final responseJSON = json.decode(response.body) as Map; final from = responseJSON['coin_from'] as String; + final fromNetwork = responseJSON['coin_from_network'] as String; final to = responseJSON['coin_to'] as String; + final toNetwork = responseJSON['coin_to_network'] as String; final payoutAddress = responseJSON['withdrawal'] as String; final depositAddress = responseJSON['deposit'] as String; final refundAddress = responseJSON['return'] as String; @@ -246,8 +255,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { return Trade( id: id, - from: CryptoCurrency.fromString(from), - to: CryptoCurrency.fromString(to), + from: CryptoCurrency.safeParseCurrencyFromString(from), + to: CryptoCurrency.safeParseCurrencyFromString(to), provider: description, inputAddress: depositAddress, payoutAddress: payoutAddress, @@ -259,6 +268,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { expiredAt: expiredAt, isRefund: status == 'refund', extraId: extraId, + userCurrencyFromRaw: '$from' + '_' + '$fromNetwork', + userCurrencyToRaw: '$to' + '_' + '$toNetwork', ); } @@ -270,10 +281,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider { }; try { - final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath, params); + final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath); final response = await ProxyWrapper().post( clearnetUri: uri, headers: headers, + body: json.encode(params), ); if (response.statusCode != 200) { diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index 3c9b2d951..2c592e246 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -225,7 +225,9 @@ class SideShiftExchangeProvider extends ExchangeProvider { payoutAddress: settleAddress, createdAt: DateTime.now(), isSendAll: isSendAll, - extraId: depositMemo + extraId: depositMemo, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } @@ -264,15 +266,18 @@ class SideShiftExchangeProvider extends ExchangeProvider { return Trade( id: id, - from: CryptoCurrency.fromString(fromCurrency), - to: CryptoCurrency.fromString(toCurrency), + from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency), + to: CryptoCurrency.safeParseCurrencyFromString(toCurrency), provider: description, inputAddress: inputAddress, amount: expectedSendAmount ?? '', state: TradeState.deserialize(raw: status ?? 'created'), expiredAt: expiredAt, payoutAddress: settleAddress, - extraId: depositMemo); + extraId: depositMemo, + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', + ); } Future _createQuote(TradeRequest request) async { diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index cc02ac799..73e644f91 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -175,6 +175,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { payoutAddress: payoutAddress, createdAt: DateTime.now(), isSendAll: isSendAll, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } @@ -211,14 +213,16 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { return Trade( id: id, - from: CryptoCurrency.fromString(fromCurrency), - to: CryptoCurrency.fromString(toCurrency), + from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency), + to: CryptoCurrency.safeParseCurrencyFromString(toCurrency), extraId: extraId, provider: description, inputAddress: inputAddress, amount: expectedSendAmount, state: TradeState.deserialize(raw: status), payoutAddress: payoutAddress, + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', ); } diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart index 8c7efae65..554116def 100644 --- a/lib/exchange/provider/stealth_ex_exchange_provider.dart +++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart @@ -198,6 +198,8 @@ class StealthExExchangeProvider extends ExchangeProvider { createdAt: createdAt, expiredAt: expiredAt, extraId: extraId, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } catch (e) { log(e.toString()); @@ -235,8 +237,8 @@ class StealthExExchangeProvider extends ExchangeProvider { return Trade( id: respId, - from: CryptoCurrency.fromString(from), - to: CryptoCurrency.fromString(to), + from: CryptoCurrency.safeParseCurrencyFromString(from), + to: CryptoCurrency.safeParseCurrencyFromString(to), provider: description, inputAddress: depositAddress, payoutAddress: payoutAddress, @@ -247,6 +249,8 @@ class StealthExExchangeProvider extends ExchangeProvider { createdAt: createdAt, isRefund: status == 'refunded', extraId: extraId, + userCurrencyFromRaw: '${from.toUpperCase()}' + '_', + userCurrencyToRaw: '${to.toUpperCase()}' + '_', ); } diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart index 0e23eb718..2ebbfe790 100644 --- a/lib/exchange/provider/swaptrade_exchange_provider.dart +++ b/lib/exchange/provider/swaptrade_exchange_provider.dart @@ -190,6 +190,8 @@ class SwapTradeExchangeProvider extends ExchangeProvider { state: TradeState.created, payoutAddress: request.toAddress, isSendAll: isSendAll, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } catch (e) { printV("error creating trade: ${e.toString()}"); @@ -224,9 +226,9 @@ class SwapTradeExchangeProvider extends ExchangeProvider { final responseData = responseBody['data'] as Map; final fromCurrency = responseData['coin_send'] as String; - final from = CryptoCurrency.fromString(fromCurrency); + final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency); final toCurrency = responseData['coin_receive'] as String; - final to = CryptoCurrency.fromString(toCurrency); + final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency); final inputAddress = responseData['server_address'] as String; final payoutAddress = responseData['recipient'] as String; final status = responseData['status'] as String; @@ -249,6 +251,8 @@ class SwapTradeExchangeProvider extends ExchangeProvider { receiveAmount: expectedReceiveAmount, memo: memo, createdAt: DateTime.tryParse(createdAt ?? ''), + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_', + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_', ); } catch (e) { printV("error getting trade: ${e.toString()}"); diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart index 16246abac..3557a847b 100644 --- a/lib/exchange/provider/thorchain_exchange.provider.dart +++ b/lib/exchange/provider/thorchain_exchange.provider.dart @@ -157,6 +157,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { payoutAddress: request.toAddress, memo: memo, isSendAll: isSendAll, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } @@ -197,8 +199,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { ? parts[1].split('.')[1].split('-')[0] : ''; - final formattedToChain = CryptoCurrency.fromString(toChain); - final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency: formattedToChain); + final formattedToChain = CryptoCurrency.safeParseCurrencyFromString(toChain); + final toAssetWithChain = CryptoCurrency.safeParseCurrencyFromString(toAsset, walletCurrency: formattedToChain); final plannedOutTxs = responseJSON['planned_out_txs'] as List?; final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false; @@ -214,6 +216,8 @@ class ThorChainExchangeProvider extends ExchangeProvider { state: currentState, memo: memo, isRefund: isRefund, + userCurrencyFromRaw: '${tx['chain'] as String? ?? ''}' + '_', + userCurrencyToRaw: '$toAsset' + '_', ); } diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index cc8d8fa60..f0d2bb140 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -251,6 +251,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { payoutAddress: payoutAddress, isSendAll: isSendAll, extraId: addressProviderMemo, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } @@ -274,10 +276,13 @@ class TrocadorExchangeProvider extends ExchangeProvider { final providerName = responseJSON['provider'] as String; final addressProviderMemo = responseJSON['address_provider_memo'] as String?; + final from = responseJSON['ticker_from'] as String; + final to = responseJSON['ticker_to'] as String; + return Trade( id: id, - from: CryptoCurrency.fromString(responseJSON['ticker_from'] as String), - to: CryptoCurrency.fromString(responseJSON['ticker_to'] as String), + from: CryptoCurrency.safeParseCurrencyFromString(from), + to: CryptoCurrency.safeParseCurrencyFromString(to), provider: description, inputAddress: inputAddress, refundAddress: refundAddress, @@ -289,6 +294,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { providerId: providerId, providerName: providerName, extraId: addressProviderMemo, + userCurrencyFromRaw: '${from.toUpperCase()}' + '_', + userCurrencyToRaw: '${to.toUpperCase()}' + '_', ); }); } diff --git a/lib/exchange/provider/xoswap_exchange_provider.dart b/lib/exchange/provider/xoswap_exchange_provider.dart index 997e163f6..e4c775334 100644 --- a/lib/exchange/provider/xoswap_exchange_provider.dart +++ b/lib/exchange/provider/xoswap_exchange_provider.dart @@ -76,22 +76,26 @@ class XOSwapExchangeProvider extends ExchangeProvider { if (response.statusCode != 200) { throw Exception('Failed to fetch assets for ${currency.title} on ${currency.tag}'); } - final assets = json.decode(response.body) as List; + + final decoded = jsonDecode(response.body); + if (decoded is! List) throw const FormatException('Unexpected response format'); + final assets = decoded.map((e) => Map.from(e as Map)).toList(); + final asset = assets.firstWhere( - (asset) { - final assetSymbol = (asset['symbol'] as String).toUpperCase(); - return assetSymbol == currency.title.toUpperCase(); - }, - orElse: () => null, + (asset) => removeNonAlphanumeric((asset['symbol'] ?? '').toString()) == currency.title, + orElse: () => const {}, ); - return asset != null ? asset['id'] as String : null; + + return asset.isEmpty ? null : asset['id'] as String; } catch (e) { printV(e.toString()); return null; } } + String removeNonAlphanumeric(String str) => str.toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), ''); + Future> getRatesForPair({ required CryptoCurrency from, required CryptoCurrency to, @@ -117,19 +121,24 @@ class XOSwapExchangeProvider extends ExchangeProvider { required CryptoCurrency to, required bool isFixedRateMode, }) async { - final rates = await getRatesForPair(from: from, to: to); - if (rates.isEmpty) return Limits(min: 0, max: 0); + try { + final rates = await getRatesForPair(from: from, to: to); + if (rates.isEmpty) throw Exception('No rates found for $from to $to'); double minLimit = double.infinity; double maxLimit = 0; - for (var rate in rates) { - final double currentMin = double.parse(rate['min']['value'].toString()); - final double currentMax = double.parse(rate['max']['value'].toString()); - if (currentMin < minLimit) minLimit = currentMin; - if (currentMax > maxLimit) maxLimit = currentMax; + for (var rate in rates) { + final double currentMin = double.parse(rate['min']['value'].toString()); + final double currentMax = double.parse(rate['max']['value'].toString()); + if (currentMin < minLimit) minLimit = currentMin; + if (currentMax > maxLimit) maxLimit = currentMax; + } + return Limits(min: minLimit, max: maxLimit); + } catch (e) { + printV(e.toString()); + throw Exception('StealthEx failed to fetch limits'); } - return Limits(min: minLimit, max: maxLimit); } Future fetchRate({ @@ -249,6 +258,8 @@ class XOSwapExchangeProvider extends ExchangeProvider { receiveAmount: receiveAmount.toString(), payoutAddress: payoutAddress, extraId: extraId, + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}', + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}', ); } catch (e) { printV(e.toString()); @@ -275,10 +286,10 @@ class XOSwapExchangeProvider extends ExchangeProvider { final pairId = responseJSON['pairId'] as String; final pairParts = pairId.split('_'); - final CryptoCurrency fromCurrency = - CryptoCurrency.fromString(pairParts.isNotEmpty ? pairParts[0] : ""); - final CryptoCurrency toCurrency = - CryptoCurrency.fromString(pairParts.length > 1 ? pairParts[1] : ""); + final fromAsset = pairParts.isNotEmpty ? pairParts[0] : ''; + final toAsset = pairParts.length > 1 ? pairParts[1] : ''; + final fromCurrency = CryptoCurrency.safeParseCurrencyFromString(fromAsset); + final toCurrency = CryptoCurrency.safeParseCurrencyFromString(toAsset); final amount = responseJSON['amount'] as Map; final toAmount = responseJSON['toAmount'] as Map; @@ -306,6 +317,8 @@ class XOSwapExchangeProvider extends ExchangeProvider { receiveAmount: receiveAmount, payoutAddress: payoutAddress, extraId: extraId, + userCurrencyFromRaw: '$fromAsset' + '_', + userCurrencyToRaw: '$toAsset' + '_', ); } catch (e) { printV(e.toString()); diff --git a/lib/exchange/trade.dart b/lib/exchange/trade.dart index 2482bdc83..9085b3247 100644 --- a/lib/exchange/trade.dart +++ b/lib/exchange/trade.dart @@ -31,6 +31,8 @@ class Trade extends HiveObject { this.isRefund, this.isSendAll, this.router, + this.userCurrencyFromRaw, + this.userCurrencyToRaw, }) { if (provider != null) providerRaw = provider.raw; @@ -126,6 +128,46 @@ class Trade extends HiveObject { @HiveField(23, defaultValue: '') String? receiveAmount; + @HiveField(24, defaultValue: '') + String? userCurrencyFromRaw; + + @HiveField(25, defaultValue: '') + String? userCurrencyToRaw; + + CryptoCurrency? get userCurrencyFrom { + if (userCurrencyFromRaw == null || userCurrencyFromRaw!.isEmpty) { + return null; + } + final underscoreIndex = userCurrencyFromRaw!.indexOf('_'); + final title = userCurrencyFromRaw!.substring(0, underscoreIndex); + final tag = userCurrencyFromRaw!.substring(underscoreIndex + 1); + + return CryptoCurrency( + title: title, + tag: tag.isNotEmpty ? tag : null, + name: '', + raw: -1, + decimals: 1, + ); + } + + CryptoCurrency? get userCurrencyTo { + if (userCurrencyToRaw == null || userCurrencyToRaw!.isEmpty) { + return null; + } + final underscoreIndex = userCurrencyToRaw!.indexOf('_'); + final title = userCurrencyToRaw!.substring(0, underscoreIndex); + final tag = userCurrencyToRaw!.substring(underscoreIndex + 1); + + return CryptoCurrency( + title: title, + tag: tag.isNotEmpty ? tag : null, + name: '', + raw: -1, + decimals: 1, + ); + } + static Trade fromMap(Map map) { return Trade( id: map['id'] as String, @@ -206,6 +248,8 @@ class TradeAdapter extends TypeAdapter { isRefund: fields[20] as bool?, isSendAll: fields[21] as bool?, router: fields[22] as String?, + userCurrencyFromRaw: fields[24] as String?, + userCurrencyToRaw: fields[25] as String?, ) ..providerRaw = fields[1] == null ? 0 : fields[1] as int ..fromRaw = fields[2] == null ? 0 : fields[2] as int @@ -216,7 +260,7 @@ class TradeAdapter extends TypeAdapter { @override void write(BinaryWriter writer, Trade obj) { writer - ..writeByte(24) + ..writeByte(26) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -264,7 +308,11 @@ class TradeAdapter extends TypeAdapter { ..writeByte(22) ..write(obj.router) ..writeByte(23) - ..write(obj.receiveAmount); + ..write(obj.receiveAmount) + ..writeByte(24) + ..write(obj.userCurrencyFromRaw) + ..writeByte(25) + ..write(obj.userCurrencyToRaw); } @override diff --git a/lib/main.dart b/lib/main.dart index f37c69705..13322dd64 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,10 +36,13 @@ import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/address_info.dart'; import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/payjoin_session.dart'; +import 'package:cw_core/spl_token.dart'; +import 'package:cw_core/tron_token.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_logger/memory_proxy_logger.dart'; @@ -206,6 +209,18 @@ Future initializeAppConfigs({bool loadWallet = true}) async { CakeHive.registerAdapter(PayjoinSessionAdapter()); } + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { + CakeHive.registerAdapter(Erc20TokenAdapter()); + } + + if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) { + CakeHive.registerAdapter(SPLTokenAdapter()); + } + + if (!CakeHive.isAdapterRegistered(TronToken.typeId)) { + CakeHive.registerAdapter(TronTokenAdapter()); + } + final secureStorage = secureStorageShared; final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey); diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index a26b62fd2..f0f6e6370 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -125,13 +125,16 @@ class _EditTokenPageBodyState extends State { }); } - void _checkIfTokenIsVerified(String? contractAddress) async { - if (contractAddress != null) { - final isVerified = widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress); - setState(() { - _isTokenVerified = isVerified; - }); - } + void _checkIfTokenIsVerified(String? contractAddress) { + if (contractAddress == null) return; + + final isVerified = + widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress); + + if (!mounted) return; + setState(() { + _isTokenVerified = isVerified; + }); } @override @@ -253,7 +256,9 @@ class _EditTokenPageBodyState extends State { name: _tokenNameController.text, title: _tokenSymbolController.text.toUpperCase(), decimals: int.parse(_tokenDecimalController.text), - iconPath: _tokenIconPathController.text, + iconPath: _tokenIconPathController.text.isNotEmpty + ? _tokenIconPathController.text + : null, isPotentialScam: isPotentialScam, ), contractAddress: _contractAddressController.text, @@ -335,6 +340,8 @@ class _EditTokenPageBodyState extends State { if (_contractAddressController.text.isNotEmpty) { final token = await widget.homeSettingsViewModel.getToken(_contractAddressController.text); + if (!mounted) return; + if (token != null) { final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano; if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name; diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 7a3aa156a..04ce6ddb0 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -164,22 +164,28 @@ class TransactionsPage extends StatelessWidget { if (item is TradeListItem) { final trade = item.trade; - return Observer( - builder: (_) => TradeRow( - key: item.key, - onTap: () => Navigator.of(context) - .pushNamed(Routes.tradeDetails, arguments: trade), - swapState: trade.state, - provider: trade.provider, - from: trade.from, - to: trade.to, - createdAtFormattedDate: trade.createdAt != null - ? DateFormat('HH:mm').format(trade.createdAt!) - : null, - formattedAmount: item.tradeFormattedAmount, - formattedReceiveAmount: item.tradeFormattedReceiveAmount - ), - ); + final tradeFrom = + trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom; + + final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo; + + return tradeFrom != null && tradeTo != null + ? Observer( + builder: (_) => TradeRow( + key: item.key, + onTap: () => Navigator.of(context) + .pushNamed(Routes.tradeDetails, arguments: trade), + swapState: trade.state, + provider: trade.provider, + from: tradeFrom, + to: tradeTo, + createdAtFormattedDate: trade.createdAt != null + ? DateFormat('HH:mm').format(trade.createdAt!) + : null, + formattedAmount: item.tradeFormattedAmount, + formattedReceiveAmount: item.tradeFormattedReceiveAmount), + ) + : Container(); } if (item is OrderListItem) { final order = item.order; diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 56cf80c11..998fc9a0a 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -39,13 +39,11 @@ class CurrencyPickerState extends State { String textFieldValue; bool currencySearchBySubstring(Currency currency, String subString) { - return currency.name.toLowerCase().contains(subString.toLowerCase()) || - (currency.tag != null - ? currency.tag!.toLowerCase().contains(subString.toLowerCase()) - : false) || - (currency.fullName != null - ? currency.fullName!.toLowerCase().contains(subString.toLowerCase()) - : false); + final query = subString.toLowerCase(); + return currency.name.toLowerCase().contains(query) || + currency.toString().toLowerCase().contains(query) || + (currency.tag != null ? currency.tag!.toLowerCase().contains(query) : false) || + (currency.fullName != null ? currency.fullName!.toLowerCase().contains(query) : false); } @override diff --git a/lib/src/screens/exchange/widgets/present_provider_picker.dart b/lib/src/screens/exchange/widgets/present_provider_picker.dart index 3a0ec5e19..f4c21ed28 100644 --- a/lib/src/screens/exchange/widgets/present_provider_picker.dart +++ b/lib/src/screens/exchange/widgets/present_provider_picker.dart @@ -76,7 +76,7 @@ class PresentProviderPicker extends StatelessWidget { (e) => CheckBoxItem( e.title, exchangeViewModel.selectedProviders.contains(e), - isDisabled: !exchangeViewModel.providersForCurrentPair().contains(e), + isDisabled: !exchangeViewModel.providerList.contains(e), ), ) .toList(), diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 8d1a5f8fd..da071553f 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -102,15 +102,17 @@ abstract class HomeSettingsViewModelBase with Store { } if (_balanceViewModel.wallet.type == WalletType.solana) { + final splToken = token.copyWith(enabled: true); await solana!.addSPLToken( _balanceViewModel.wallet, - token, + splToken, contractAddress, ); } if (_balanceViewModel.wallet.type == WalletType.tron) { - await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress); + final tronToken = token.copyWith(enabled: true); + await tron!.addTronToken(_balanceViewModel.wallet, tronToken, contractAddress); } if (_balanceViewModel.wallet.type == WalletType.zano) { diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 50102bdc9..76198c59d 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -159,9 +159,18 @@ abstract class ExchangeTradeViewModelBase with Store { } void _updateItems() { - final tagFrom = - tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : ''; - final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : ''; + final trade = tradesStore.trade!; + final tradeFrom = trade.fromRaw >= 0 + ? trade.from + : trade.userCurrencyFrom; + + final tradeTo = trade.toRaw >= 0 + ? trade.to + : trade.userCurrencyTo; + + final tagFrom = tradeFrom?.tag != null ? '${tradeFrom!.tag}' + ' ' : ''; + final tagTo = tradeTo?.tag != null ? '${tradeTo!.tag}' + ' ' : ''; + items.clear(); if (trade.provider != ExchangeProviderDescription.thorChain) @@ -175,36 +184,40 @@ abstract class ExchangeTradeViewModelBase with Store { ), ); - items.addAll([ - ExchangeTradeItem( - title: S.current.amount, - data: '${trade.amount} ${trade.from}', - isCopied: false, - isReceiveDetail: false, - isExternalSendDetail: true, - ), - ExchangeTradeItem( - title: S.current.you_will_receive_estimated_amount + ':', - data: '${tradesStore.trade?.receiveAmount} ${trade.to}', - isCopied: true, - isReceiveDetail: true, - isExternalSendDetail: false, - ), - ExchangeTradeItem( - title: S.current.send_to_this_address('${tradesStore.trade!.from}', tagFrom) + ':', - data: trade.inputAddress ?? '', - isCopied: false, - isReceiveDetail: false, - isExternalSendDetail: true, - ), - ]); + if (tradeFrom != null || tradeTo != null) { + items.addAll([ + ExchangeTradeItem( + title: S.current.amount, + data: '${trade.amount} ${tradeFrom}', + isCopied: false, + isReceiveDetail: false, + isExternalSendDetail: true, + ), + ExchangeTradeItem( + title: S.current.you_will_receive_estimated_amount + ':', + data: '${tradesStore.trade?.receiveAmount} ${tradeTo}', + isCopied: true, + isReceiveDetail: true, + isExternalSendDetail: false, + ), + ExchangeTradeItem( + title: S.current.send_to_this_address('${tradeFrom}', tagFrom) + ':', + data: trade.inputAddress ?? '', + isCopied: false, + isReceiveDetail: false, + isExternalSendDetail: true, + ), + ]); + } final isExtraIdExist = trade.extraId != null && trade.extraId!.isNotEmpty; if (isExtraIdExist) { - final title = trade.from == CryptoCurrency.xrp + + + final title = tradeFrom == CryptoCurrency.xrp ? S.current.destination_tag - : trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton + : tradeFrom == CryptoCurrency.xlm || tradeFrom == CryptoCurrency.ton ? S.current.memo : S.current.extra_id; @@ -220,7 +233,7 @@ abstract class ExchangeTradeViewModelBase with Store { items.add( ExchangeTradeItem( - title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':', + title: S.current.arrive_in_this_address('${tradeTo}', tagTo) + ':', data: trade.payoutAddress ?? '', isCopied: true, isReceiveDetail: true, @@ -230,23 +243,29 @@ abstract class ExchangeTradeViewModelBase with Store { } static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { + + final trade = tradesStore.trade!; + final tradeFrom = trade.fromRaw >= 0 + ? trade.from + : trade.userCurrencyFrom; + bool _isEthToken() => wallet.currency == CryptoCurrency.eth && tradesStore.trade!.from.tag == CryptoCurrency.eth.title; bool _isPolygonToken() => wallet.currency == CryptoCurrency.maticpoly && - tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag; + tradeFrom?.tag == CryptoCurrency.maticpoly.tag; bool _isTronToken() => wallet.currency == CryptoCurrency.trx && - tradesStore.trade!.from.tag == CryptoCurrency.trx.title; + tradeFrom?.tag == CryptoCurrency.trx.title; bool _isSplToken() => wallet.currency == CryptoCurrency.sol && - tradesStore.trade!.from.tag == CryptoCurrency.sol.title; + tradeFrom?.tag == CryptoCurrency.sol.title; - return tradesStore.trade!.from == wallet.currency || + return tradeFrom == wallet.currency || tradesStore.trade!.provider == ExchangeProviderDescription.xmrto || _isEthToken() || _isPolygonToken() || diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 757da5910..5543534d7 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -37,12 +37,17 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.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/sync_status.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/tron_token.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; import 'package:intl/intl.dart'; @@ -70,6 +75,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with this.contactListViewModel, this.unspentCoinsListViewModel, this.feesViewModel, + this.walletInfoSource, ) : _cryptoNumberFormat = NumberFormat(), isSendAllEnabled = false, isFixedRateMode = false, @@ -81,8 +87,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with isDepositAddressEnabled = false, isReceiveAmountEditable = false, _useTorOnly = false, - receiveCurrencies = [], - depositCurrencies = [], + receiveCurrencies = ObservableList(), + depositCurrencies = ObservableList(), limits = Limits(min: 0, max: 0), tradeState = ExchangeTradeStateInitial(), limitsState = LimitsInitialState(), @@ -138,7 +144,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with receiveAddress = ''; depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : ''; - provider = providersForCurrentPair().first; + + provider = providerList.firstOrNull; final initialProvider = provider; provider!.checkIsAvailable().then((bool isAvailable) { if (!isAvailable && provider == initialProvider) { @@ -149,10 +156,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with }); receiveCurrencies = CryptoCurrency.all .where((cryptoCurrency) => !excludeReceiveCurrencies.contains(cryptoCurrency)) - .toList(); + .toList() + .asObservable(); depositCurrencies = CryptoCurrency.all .where((cryptoCurrency) => !excludeDepositCurrencies.contains(cryptoCurrency)) - .toList(); + .toList() + .asObservable(); + + _injectUserEthTokensIntoCurrencyLists(); + _injectUserSplTokensIntoCurrencyLists(); + _injectUserTronTokensIntoCurrencyLists(); _defineIsReceiveAmountEditable(); loadLimits(); reaction((_) => isFixedRateMode, (Object _) { @@ -165,8 +178,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } } - bool get isElectrumWallet => - [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash,WalletType.dogecoin ].contains(wallet.type); + bool get isElectrumWallet => [ + WalletType.bitcoin, + WalletType.litecoin, + WalletType.bitcoinCash, + WalletType.dogecoin + ].contains(wallet.type); bool get hideAddressAfterExchange => [WalletType.monero, WalletType.wownero].contains(wallet.type); @@ -320,9 +337,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with bool get isMoneroWallet => wallet.type == WalletType.monero; - List receiveCurrencies; + @observable + ObservableList receiveCurrencies; - List depositCurrencies; + @observable + ObservableList depositCurrencies; final NumberFormat _cryptoNumberFormat; @@ -334,6 +353,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with final FeesViewModel feesViewModel; + final Box walletInfoSource; + @observable double bestRate = 0.0; @@ -490,7 +511,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with try { final futures = selectedProviders - .where((provider) => providersForCurrentPair().contains(provider)) + .where((provider) => providerList.contains(provider)) .map((provider) async { final limits = await provider .fetchLimits( @@ -717,16 +738,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with void removeTemplate({required ExchangeTemplate template}) => _exchangeTemplateStore.remove(template: template); - List providersForCurrentPair() => - _providersForPair(from: depositCurrency, to: receiveCurrency); - - List _providersForPair( - {required CryptoCurrency from, required CryptoCurrency to}) => - providerList - .where((provider) => - provider.pairList.where((pair) => pair.from == from && pair.to == to).isNotEmpty) - .toList(); - void _onPairChange() { depositAmount = ''; receiveAmount = ''; @@ -819,7 +830,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @action void addExchangeProvider(ExchangeProvider provider) { selectedProviders.add(provider); - if (providersForCurrentPair().contains(provider)) _tradeAvailableProviders.add(provider); + if (providerList.contains(provider)) _tradeAvailableProviders.add(provider); } @action @@ -863,16 +874,15 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } bool get isAvailableInSelected { - final providersForPair = providersForCurrentPair(); return selectedProviders - .any((element) => element.isAvailable && providersForPair.contains(element)); + .any((element) => element.isAvailable && providerList.contains(element)); } void _setAvailableProviders() { _tradeAvailableProviders.clear(); _tradeAvailableProviders.addAll( - selectedProviders.where((provider) => providersForCurrentPair().contains(provider))); + selectedProviders.where((provider) => providerList.contains(provider))); } void _setProviders() { @@ -989,7 +999,185 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } } - double initialAmountByAssets (CryptoCurrency ticker) { + // Adding user's Erc20 tokens to the list of currencies + + Future> _openEvmTokensBoxFor(WalletInfo walletInfo) async { + final walletKey = walletInfo.name.replaceAll(" ", "_"); + + final boxName = switch (walletInfo.type) { + WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}', + WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}', + _ => '${walletKey}_${Erc20Token.ethereumBoxName}', + }; + + if (CakeHive.isBoxOpen(boxName)) { + return CakeHive.box(boxName); + } + return CakeHive.openBox(boxName); + } + + Future> _loadAllUniqueEvmTokens() async { + final evmWallets = walletInfoSource.values.where( + (w) => w.type == WalletType.ethereum || w.type == WalletType.polygon, + ); + + final seen = {}; + final unique = []; + + for (final wallet in evmWallets) { + final chain = wallet.type == WalletType.ethereum ? 'ETH' : 'POL'; + 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; + } + + + @action + Future _injectUserEthTokensIntoCurrencyLists() async { + final userTokens = await _loadAllUniqueEvmTokens(); + + final toAddReceive = []; + final toAddDeposit = []; + + for (final token in userTokens) { + if (!_listContainsToken(receiveCurrencies, token)) toAddReceive.add(token); + if (!_listContainsToken(depositCurrencies, token)) toAddDeposit.add(token); + } + + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive); + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit); + } + + bool _listContainsToken(List list, Erc20Token token) { + return list.any((item) { + if (item is Erc20Token) { + return item.contractAddress.toLowerCase() == token.contractAddress.toLowerCase(); + } + return item.title.toUpperCase() == token.symbol.toUpperCase() && + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null); + }); + } + + // Adding user's Solana tokens to the list of currencies + + Future> _openSolTokensBoxFor(WalletInfo wallet) async { + final boxName = '${wallet.name.replaceAll(" ", "_")}_${SPLToken.boxName}'; + if (CakeHive.isBoxOpen(boxName)) { + return CakeHive.box(boxName); + } + return CakeHive.openBox(boxName); + } + + Future> _loadAllUniqueSolTokens() async { + final solWallets = walletInfoSource.values.where((wallet) => wallet.type == WalletType.solana); + final tokens = []; + + for (final wallet in solWallets) { + final box = await _openSolTokensBoxFor(wallet); + tokens.addAll(box.values.where((t) => t.enabled)); + } + + final seen = {}; + final unique = []; + for (final token in tokens) { + final key = token.mintAddress.toLowerCase(); + if (!seen.contains(key)) { + seen.add(key); + unique.add(token); + } + } + return unique; + } + + bool _listContainsSplToken(List list, SPLToken token) { + return list.any((item) { + if (item is SPLToken) { + return item.mintAddress.toLowerCase() == token.mintAddress.toLowerCase(); + } + return item.title.toUpperCase() == token.symbol.toUpperCase() && + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null); + }); + } + + @action + Future _injectUserSplTokensIntoCurrencyLists() async { + final userTokens = await _loadAllUniqueSolTokens(); + + final toAddReceive = []; + final toAddDeposit = []; + + for (final token in userTokens) { + if (!_listContainsSplToken(receiveCurrencies, token)) toAddReceive.add(token); + if (!_listContainsSplToken(depositCurrencies, token)) toAddDeposit.add(token); + } + + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive); + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit); + } + + // Adding user's Tron tokens to the list of currencies + + Future> _openTronTokensBoxFor(WalletInfo walletInfo) async { + final boxName = '${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}'; + if (CakeHive.isBoxOpen(boxName)) { + return CakeHive.box(boxName); + } + return CakeHive.openBox(boxName); + } + + Future> _loadAllUniqueTronTokens() async { + final tronWallets = + walletInfoSource.values.where((w) => w.type == WalletType.tron); + + final seen = {}; + final unique = []; + + 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; + } + + bool _listContainsTronToken(List list, TronToken token) { + return list.any((item) { + if (item is TronToken) { + return item.contractAddress.toLowerCase() == token.contractAddress.toLowerCase(); + } + return item.title.toUpperCase() == token.symbol.toUpperCase() && + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null); + }); + } + + @action + Future _injectUserTronTokensIntoCurrencyLists() async { + final userTokens = await _loadAllUniqueTronTokens(); + + final toAddReceive = []; + final toAddDeposit = []; + + for (final token in userTokens) { + if (!_listContainsTronToken(receiveCurrencies, token)) toAddReceive.add(token); + if (!_listContainsTronToken(depositCurrencies, token)) toAddDeposit.add(token); + } + + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive); + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit); + } + + double initialAmountByAssets(CryptoCurrency ticker) { final amount = switch (ticker) { CryptoCurrency.trx => 1000, CryptoCurrency.nano => 10, diff --git a/lib/view_model/trade_details_view_model.dart b/lib/view_model/trade_details_view_model.dart index 4e6ca7a65..64c05c19b 100644 --- a/lib/view_model/trade_details_view_model.dart +++ b/lib/view_model/trade_details_view_model.dart @@ -24,6 +24,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:collection/collection.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; @@ -163,17 +164,22 @@ abstract class TradeDetailsViewModelBase with Store { items.add( DetailsListStatusItem(title: S.current.trade_details_state, value: trade.state.toString())); - items.add(TradeDetailsListCardItem.tradeDetails( - id: trade.id, - extraId: trade.extraId, - createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '', - from: trade.from, - to: trade.to, - onTap: (BuildContext context) { - Clipboard.setData(ClipboardData(text: '${trade.id}')); - showBar(context, S.of(context).copied_to_clipboard); - }, - )); + final tradeFrom = _safeFrom(trade); + final tradeTo = _safeTo(trade); + + if (tradeFrom != null && tradeTo != null) { + items.add(TradeDetailsListCardItem.tradeDetails( + id: trade.id, + extraId: trade.extraId, + createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '', + from: tradeFrom, + to: tradeTo, + onTap: (context) { + Clipboard.setData(ClipboardData(text: trade.id)); + showBar(context, S.of(context).copied_to_clipboard); + }, + )); + } items.add(StandartListItem( title: S.current.trade_details_provider, value: trade.provider.toString())); @@ -207,4 +213,22 @@ abstract class TradeDetailsViewModelBase with Store { launchUrl(uri, mode: LaunchMode.externalApplication); } catch (e) {} } + + CryptoCurrency? _safeFrom(Trade trade) { + try { + final raw = trade.fromRaw; + return raw >= 0 ? trade.from : trade.userCurrencyFrom; + } catch (_) { + return trade.userCurrencyFrom; + } + } + + CryptoCurrency? _safeTo(Trade trade) { + try { + final raw = trade.toRaw; + return raw >= 0 ? trade.to : trade.userCurrencyTo; + } catch (_) { + return trade.userCurrencyTo; + } + } } diff --git a/tool/configure.dart b/tool/configure.dart index ee44fba4f..ea3d3e363 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1113,11 +1113,11 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/spl_token.dart'; import 'package:hive/hive.dart'; """; const solanaCWHeaders = """ -import 'package:cw_solana/spl_token.dart'; import 'package:cw_solana/solana_wallet.dart'; import 'package:cw_solana/solana_mnemonics.dart'; import 'package:cw_solana/solana_wallet_service.dart'; @@ -1199,6 +1199,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/tron_token.dart'; import 'package:hive/hive.dart'; """; @@ -1209,7 +1210,6 @@ import 'package:cw_tron/tron_transaction_info.dart'; import 'package:cw_tron/tron_wallet_creation_credentials.dart'; 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';