Cw 1161 add user added tokens to swap currencies (#2455)

* Add user ETH and SOL tokens to exchange currency lists * Update default_spl_tokens.dart * fix solana token icon issue * add user currency to Trade and update usage * add support for user Tron and Polygon tokens in exchange * removing the providersForCurrentPair method and related logic * Improve currency search * prevent crash in EVM wallets for unknown token symbols * Improve asset lookup in XOSwap provider * revert token enabled state for tron solana * Move SPL and Tron token classes to cw_core * fix updating trades for user tokens * Improve token matching by including tag comparison * safer currency parsing across all exchange providers * Update edit_token_page.dart

Serhii committed Aug 21, 2025 at 03:34 UTC f6dc65424d7316d105048a3c14c713594f05ae20
34 files changed +1046 -176
cw_core/lib/crypto_currency.dart
+40
@@ -300,6 +300,46 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
300 return CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!;
301 }
302
303 + static CryptoCurrency? safeParseCurrencyFromString(String? raw, {CryptoCurrency? walletCurrency}) {
304 + if (raw == null || raw.isEmpty) return null;
305 +
306 + try {
307 + return CryptoCurrency.fromString(raw, walletCurrency: walletCurrency);
308 + } catch (_) {}
309 +
310 + // try cleaned (keep only A–Z/0–9)
311 + final cleaned = raw.trim().toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), '');
312 + try {
313 + return CryptoCurrency.fromString(cleaned, walletCurrency: walletCurrency);
314 + } catch (_) {}
315 +
316 + return null;
317 + }
318 +
319 + CryptoCurrency copyWith({
320 + String? title,
321 + int? raw,
322 + String? name,
323 + String? fullName,
324 + String? iconPath,
325 + String? tag,
326 + int? decimals,
327 + bool? enabled,
328 + bool? isPotentialScam,
329 + }) {
330 + return CryptoCurrency(
331 + title: title ?? this.title,
332 + raw: raw ?? this.raw,
333 + name: name ?? this.name,
334 + fullName: fullName ?? this.fullName,
335 + iconPath: iconPath ?? this.iconPath,
336 + tag: tag ?? this.tag,
337 + decimals: decimals ?? this.decimals,
338 + enabled: enabled ?? this.enabled,
339 + isPotentialScam: isPotentialScam ?? this.isPotentialScam,
340 + );
341 + }
342 +
343 @override
344 String toString() => title;
345 }
cw_core/lib/spl_token.dart renamed
cw_core/lib/tron_token.dart renamed
cw_evm/lib/evm_chain_wallet.dart
+11 -4
@@ -248,10 +248,17 @@ abstract class EVMChainWalletBase
248 // For fixing wrongly classified tokens
249 if (!isPotentialScam && token.isPotentialScam) {
250 token.isPotentialScam = false;
251 - final iconPath = CryptoCurrency.all
252 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
253 - .iconPath;
254 - token.iconPath = iconPath;
251 +
252 + if (token.iconPath == null || token.iconPath!.isEmpty) {
253 + try {
254 + token.iconPath = CryptoCurrency.all
255 + .firstWhere((e) => e.title.toUpperCase() == token.symbol.toUpperCase())
256 + .iconPath;
257 + } catch (_) {
258 + printV("Token ${token.symbol} does not have an icon path");
259 + }
260 + }
261 +
262 await token.save();
263 }
264 }
cw_solana/lib/default_spl_tokens.dart
+443 -1
@@ -1,5 +1,5 @@
1 import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_solana/spl_token.dart';
2 +import 'package:cw_core/spl_token.dart';
3
4 class DefaultSPLTokens {
5 final List<SPLToken> _defaultTokens = [
@@ -99,6 +99,448 @@ class DefaultSPLTokens {
99 iconPath: 'assets/images/avdo_icon.png',
100 enabled: false,
101 ),
102 + SPLToken(
103 + name: 'Abbott xStock',
104 + symbol: 'ABTx',
105 + mintAddress: 'XsHtf5RpxsQ7jeJ9ivNewouZKJHbPxhPoEy6yYvULr7',
106 + decimal: 8,
107 + mint: 'abtx',
108 + enabled: false),
109 + SPLToken(
110 + name: 'AbbVie xStock',
111 + symbol: 'ABBVx',
112 + mintAddress: 'XswbinNKyPmzTa5CskMbCPvMW6G5CMnZXZEeQSSQoie',
113 + decimal: 8,
114 + mint: 'abbvx',
115 + enabled: false),
116 + SPLToken(
117 + name: 'Accenture xStock',
118 + symbol: 'ACNx',
119 + mintAddress: 'Xs5UJzmCRQ8DWZjskExdSQDnbE6iLkRu2jjrRAB1JSU',
120 + decimal: 8,
121 + mint: 'acnx',
122 + enabled: false),
123 + SPLToken(
124 + name: 'Alphabet xStock',
125 + symbol: 'GOOGLx',
126 + mintAddress: 'XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN',
127 + decimal: 8,
128 + mint: 'googlx',
129 + enabled: false),
130 + SPLToken(
131 + name: 'Amazon xStock',
132 + symbol: 'AMZNx',
133 + mintAddress: 'Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg',
134 + decimal: 8,
135 + mint: 'amznx',
136 + enabled: false),
137 + SPLToken(
138 + name: 'Amber xStock',
139 + symbol: 'AMBRx',
140 + mintAddress: 'XsaQTCgebC2KPbf27KUhdv5JFvHhQ4GDAPURwrEhAzb',
141 + decimal: 8,
142 + mint: 'ambrx',
143 + enabled: false),
144 + SPLToken(
145 + name: 'Apple xStock',
146 + symbol: 'AAPLx',
147 + mintAddress: 'XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp',
148 + decimal: 8,
149 + mint: 'aaplx',
150 + enabled: false),
151 + SPLToken(
152 + name: 'AppLovin xStock',
153 + symbol: 'APPx',
154 + mintAddress: 'XsPdAVBi8Zc1xvv53k4JcMrQaEDTgkGqKYeh7AYgPHV',
155 + decimal: 8,
156 + mint: 'appx',
157 + enabled: false),
158 + SPLToken(
159 + name: 'AstraZeneca xStock',
160 + symbol: 'AZNx',
161 + mintAddress: 'Xs3ZFkPYT2BN7qBMqf1j1bfTeTm1rFzEFSsQ1z3wAKU',
162 + decimal: 8,
163 + mint: 'aznx',
164 + enabled: false),
165 + SPLToken(
166 + name: 'Bank of America xStock',
167 + symbol: 'BACx',
168 + mintAddress: 'XswsQk4duEQmCbGzfqUUWYmi7pV7xpJ9eEmLHXCaEQP',
169 + decimal: 8,
170 + mint: 'bacx',
171 + enabled: false),
172 + SPLToken(
173 + name: 'Berkshire Hathaway xStock',
174 + symbol: 'BRK.Bx',
175 + mintAddress: 'Xs6B6zawENwAbWVi7w92rjazLuAr5Az59qgWKcNb45x',
176 + decimal: 8,
177 + mint: 'brkbx',
178 + enabled: false),
179 + SPLToken(
180 + name: 'Broadcom xStock',
181 + symbol: 'AVGOx',
182 + mintAddress: 'XsgSaSvNSqLTtFuyWPBhK9196Xb9Bbdyjj4fH3cPJGo',
183 + decimal: 8,
184 + mint: 'avgox',
185 + enabled: false),
186 + SPLToken(
187 + name: 'Chevron xStock',
188 + symbol: 'CVXx',
189 + mintAddress: 'XsNNMt7WTNA2sV3jrb1NNfNgapxRF5i4i6GcnTRRHts',
190 + decimal: 8,
191 + mint: 'cvxx',
192 + enabled: false),
193 + SPLToken(
194 + name: 'Circle xStock',
195 + symbol: 'CRCLx',
196 + mintAddress: 'XsueG8BtpquVJX9LVLLEGuViXUungE6WmK5YZ3p3bd1',
197 + decimal: 8,
198 + mint: 'crclx',
199 + enabled: false),
200 + SPLToken(
201 + name: 'Cisco xStock',
202 + symbol: 'CSCOx',
203 + mintAddress: 'Xsr3pdLQyXvDJBFgpR5nexCEZwXvigb8wbPYp4YoNFf',
204 + decimal: 8,
205 + mint: 'cscx',
206 + enabled: false),
207 + SPLToken(
208 + name: 'Coca-Cola xStock',
209 + symbol: 'KOx',
210 + mintAddress: 'XsaBXg8dU5cPM6ehmVctMkVqoiRG2ZjMo1cyBJ3AykQ',
211 + decimal: 8,
212 + mint: 'kox',
213 + enabled: false),
214 + SPLToken(
215 + name: 'Coinbase xStock',
216 + symbol: 'COINx',
217 + mintAddress: 'Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu',
218 + decimal: 8,
219 + mint: 'coinx',
220 + enabled: false),
221 + SPLToken(
222 + name: 'Comcast xStock',
223 + symbol: 'CMCSAx',
224 + mintAddress: 'XsvKCaNsxg2GN8jjUmq71qukMJr7Q1c5R2Mk9P8kcS8',
225 + decimal: 8,
226 + mint: 'cmcsax',
227 + enabled: false),
228 + SPLToken(
229 + name: 'CrowdStrike xStock',
230 + symbol: 'CRWDx',
231 + mintAddress: 'Xs7xXqkcK7K8urEqGg52SECi79dRp2cEKKuYjUePYDw',
232 + decimal: 8,
233 + mint: 'crwdx',
234 + enabled: false),
235 + SPLToken(
236 + name: 'Danaher xStock',
237 + symbol: 'DHRx',
238 + mintAddress: 'Xseo8tgCZfkHxWS9xbFYeKFyMSbWEvZGFV1Gh53GtCV',
239 + decimal: 8,
240 + mint: 'dhrx',
241 + enabled: false),
242 + SPLToken(
243 + name: 'DFDV xStock',
244 + symbol: 'DFDVx',
245 + mintAddress: 'Xs2yquAgsHByNzx68WJC55WHjHBvG9JsMB7CWjTLyPy',
246 + decimal: 8,
247 + mint: 'dfdvx',
248 + enabled: false),
249 + SPLToken(
250 + name: 'Eli Lilly xStock',
251 + symbol: 'LLYx',
252 + mintAddress: 'Xsnuv4omNoHozR6EEW5mXkw8Nrny5rB3jVfLqi6gKMH',
253 + decimal: 8,
254 + mint: 'llyx',
255 + enabled: false),
256 + SPLToken(
257 + name: 'Exxon Mobil xStock',
258 + symbol: 'XOMx',
259 + mintAddress: 'XsaHND8sHyfMfsWPj6kSdd5VwvCayZvjYgKmmcNL5qh',
260 + decimal: 8,
261 + mint: 'xomx',
262 + enabled: false),
263 + SPLToken(
264 + name: 'Gamestop xStock',
265 + symbol: 'GMEx',
266 + mintAddress: 'Xsf9mBktVB9BSU5kf4nHxPq5hCBJ2j2ui3ecFGxPRGc',
267 + decimal: 8,
268 + mint: 'gmex',
269 + enabled: false),
270 + SPLToken(
271 + name: 'Gold xStock',
272 + symbol: 'GLDx',
273 + mintAddress: 'Xsv9hRk1z5ystj9MhnA7Lq4vjSsLwzL2nxrwmwtD3re',
274 + decimal: 8,
275 + mint: 'gldx',
276 + enabled: false),
277 + SPLToken(
278 + name: 'Goldman Sachs xStock',
279 + symbol: 'GSx',
280 + mintAddress: 'XsgaUyp4jd1fNBCxgtTKkW64xnnhQcvgaxzsbAq5ZD1',
281 + decimal: 8,
282 + mint: 'gsx',
283 + enabled: false),
284 + SPLToken(
285 + name: 'Home Depot xStock',
286 + symbol: 'HDx',
287 + mintAddress: 'XszjVtyhowGjSC5odCqBpW1CtXXwXjYokymrk7fGKD3',
288 + decimal: 8,
289 + mint: 'hdx',
290 + enabled: false),
291 + SPLToken(
292 + name: 'Honeywell xStock',
293 + symbol: 'HONx',
294 + mintAddress: 'XsRbLZthfABAPAfumWNEJhPyiKDW6TvDVeAeW7oKqA2',
295 + decimal: 8,
296 + mint: 'honx',
297 + enabled: false),
298 + SPLToken(
299 + name: 'Intel xStock',
300 + symbol: 'INTCx',
301 + mintAddress: 'XshPgPdXFRWB8tP1j82rebb2Q9rPgGX37RuqzohmArM',
302 + decimal: 8,
303 + mint: 'intcx',
304 + enabled: false),
305 + SPLToken(
306 + name: 'International Business Machines xStock',
307 + symbol: 'IBMx',
308 + mintAddress: 'XspwhyYPdWVM8XBHZnpS9hgyag9MKjLRyE3tVfmCbSr',
309 + decimal: 8,
310 + mint: 'ibmx',
311 + enabled: false),
312 + SPLToken(
313 + name: 'Johnson & Johnson xStock',
314 + symbol: 'JNJx',
315 + mintAddress: 'XsGVi5eo1Dh2zUpic4qACcjuWGjNv8GCt3dm5XcX6Dn',
316 + decimal: 8,
317 + mint: 'jnjx',
318 + enabled: false),
319 + SPLToken(
320 + name: 'JPMorgan Chase xStock',
321 + symbol: 'JPMx',
322 + mintAddress: 'XsMAqkcKsUewDrzVkait4e5u4y8REgtyS7jWgCpLV2C',
323 + decimal: 8,
324 + mint: 'jpmx',
325 + enabled: false),
326 + SPLToken(
327 + name: 'Linde xStock',
328 + symbol: 'LINx',
329 + mintAddress: 'XsSr8anD1hkvNMu8XQiVcmiaTP7XGvYu7Q58LdmtE8Z',
330 + decimal: 8,
331 + mint: 'linx',
332 + enabled: false),
333 + SPLToken(
334 + name: 'Marvell xStock',
335 + symbol: 'MRVLx',
336 + mintAddress: 'XsuxRGDzbLjnJ72v74b7p9VY6N66uYgTCyfwwRjVCJA',
337 + decimal: 8,
338 + mint: 'mrvlx',
339 + enabled: false),
340 + SPLToken(
341 + name: 'Mastercard xStock',
342 + symbol: 'MAx',
343 + mintAddress: 'XsApJFV9MAktqnAc6jqzsHVujxkGm9xcSUffaBoYLKC',
344 + decimal: 8,
345 + mint: 'max',
346 + enabled: false),
347 + SPLToken(
348 + name: 'McDonald\'s xStock',
349 + symbol: 'MCDx',
350 + mintAddress: 'XsqE9cRRpzxcGKDXj1BJ7Xmg4GRhZoyY1KpmGSxAWT2',
351 + decimal: 8,
352 + mint: 'mcdx',
353 + enabled: false),
354 + SPLToken(
355 + name: 'Medtronic xStock',
356 + symbol: 'MDTx',
357 + mintAddress: 'XsDgw22qRLTv5Uwuzn6T63cW69exG41T6gwQhEK22u2',
358 + decimal: 8,
359 + mint: 'mdtx',
360 + enabled: false),
361 + SPLToken(
362 + name: 'Merck xStock',
363 + symbol: 'MRKx',
364 + mintAddress: 'XsnQnU7AdbRZYe2akqqpibDdXjkieGFfSkbkjX1Sd1X',
365 + decimal: 8,
366 + mint: 'mrkx',
367 + enabled: false),
368 + SPLToken(
369 + name: 'Meta xStock',
370 + symbol: 'METAx',
371 + mintAddress: 'Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu',
372 + decimal: 8,
373 + mint: 'metax',
374 + enabled: false),
375 + SPLToken(
376 + name: 'Microsoft xStock',
377 + symbol: 'MSFTx',
378 + mintAddress: 'XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX',
379 + decimal: 8,
380 + mint: 'msftx',
381 + enabled: false),
382 + SPLToken(
383 + name: 'MicroStrategy xStock',
384 + symbol: 'MSTRx',
385 + mintAddress: 'XsP7xzNPvEHS1m6qfanPUGjNmdnmsLKEoNAnHjdxxyZ',
386 + decimal: 8,
387 + mint: 'mstrx',
388 + enabled: false),
389 + SPLToken(
390 + name: 'Nasdaq xStock',
391 + symbol: 'QQQx',
392 + mintAddress: 'Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ',
393 + decimal: 8,
394 + mint: 'qqqx',
395 + enabled: false),
396 + SPLToken(
397 + name: 'Netflix xStock',
398 + symbol: 'NFLXx',
399 + mintAddress: 'XsEH7wWfJJu2ZT3UCFeVfALnVA6CP5ur7Ee11KmzVpL',
400 + decimal: 8,
401 + mint: 'nflxx',
402 + enabled: false),
403 + SPLToken(
404 + name: 'Novo Nordisk xStock',
405 + symbol: 'NVOx',
406 + mintAddress: 'XsfAzPzYrYjd4Dpa9BU3cusBsvWfVB9gBcyGC87S57n',
407 + decimal: 8,
408 + mint: 'nvox',
409 + enabled: false),
410 + SPLToken(
411 + name: 'NVIDIA xStock',
412 + symbol: 'NVDAx',
413 + mintAddress: 'Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh',
414 + decimal: 8,
415 + mint: 'nvdax',
416 + enabled: false),
417 + SPLToken(
418 + name: 'OPEN xStock',
419 + symbol: 'OPENx',
420 + mintAddress: 'XsGtpmjhmC8kyjVSWL4VicGu36ceq9u55PTgF8bhGv6',
421 + decimal: 8,
422 + mint: 'openx',
423 + enabled: false),
424 + SPLToken(
425 + name: 'Oracle xStock',
426 + symbol: 'ORCLx',
427 + mintAddress: 'XsjFwUPiLofddX5cWFHW35GCbXcSu1BCUGfxoQAQjeL',
428 + decimal: 8,
429 + mint: 'orclx',
430 + enabled: false),
431 + SPLToken(
432 + name: 'Palantir xStock',
433 + symbol: 'PLTRx',
434 + mintAddress: 'XsoBhf2ufR8fTyNSjqfU71DYGaE6Z3SUGAidpzriAA4',
435 + decimal: 8,
436 + mint: 'pltrx',
437 + enabled: false),
438 + SPLToken(
439 + name: 'PepsiCo xStock',
440 + symbol: 'PEPx',
441 + mintAddress: 'Xsv99frTRUeornyvCfvhnDesQDWuvns1M852Pez91vF',
442 + decimal: 8,
443 + mint: 'pepx',
444 + enabled: false),
445 + SPLToken(
446 + name: 'Pfizer xStock',
447 + symbol: 'PFEx',
448 + mintAddress: 'XsAtbqkAP1HJxy7hFDeq7ok6yM43DQ9mQ1Rh861X8rw',
449 + decimal: 8,
450 + mint: 'pfex',
451 + enabled: false),
452 + SPLToken(
453 + name: 'Philip Morris xStock',
454 + symbol: 'PMx',
455 + mintAddress: 'Xsba6tUnSjDae2VcopDB6FGGDaxRrewFCDa5hKn5vT3',
456 + decimal: 8,
457 + mint: 'pmx',
458 + enabled: false),
459 + SPLToken(
460 + name: 'Procter & Gamble xStock',
461 + symbol: 'PGx',
462 + mintAddress: 'XsYdjDjNUygZ7yGKfQaB6TxLh2gC6RRjzLtLAGJrhzV',
463 + decimal: 8,
464 + mint: 'pgx',
465 + enabled: false),
466 + SPLToken(
467 + name: 'Robinhood xStock',
468 + symbol: 'HOODx',
469 + mintAddress: 'XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg',
470 + decimal: 8,
471 + mint: 'hoodx',
472 + enabled: false),
473 + SPLToken(
474 + name: 'Salesforce xStock',
475 + symbol: 'CRMx',
476 + mintAddress: 'XsczbcQ3zfcgAEt9qHQES8pxKAVG5rujPSHQEXi4kaN',
477 + decimal: 8,
478 + mint: 'crmx',
479 + enabled: false),
480 + SPLToken(
481 + name: 'SP500 xStock',
482 + symbol: 'SPYx',
483 + mintAddress: 'XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W',
484 + decimal: 8,
485 + mint: 'spyx',
486 + enabled: false),
487 + SPLToken(
488 + name: 'TBLL xStock',
489 + symbol: 'TBLLx',
490 + mintAddress: 'XsqBC5tcVQLYt8wqGCHRnAUUecbRYXoJCReD6w7QEKp',
491 + decimal: 8,
492 + mint: 'tbllx',
493 + enabled: false),
494 + SPLToken(
495 + name: 'Tesla xStock',
496 + symbol: 'TSLAx',
497 + mintAddress: 'XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB',
498 + decimal: 8,
499 + mint: 'tslax',
500 + enabled: false),
501 + SPLToken(
502 + name: 'Thermo Fisher xStock',
503 + symbol: 'TMOx',
504 + mintAddress: 'Xs8drBWy3Sd5QY3aifG9kt9KFs2K3PGZmx7jWrsrk57',
505 + decimal: 8,
506 + mint: 'tmox',
507 + enabled: false),
508 + SPLToken(
509 + name: 'TQQQ xStock',
510 + symbol: 'TQQQx',
511 + mintAddress: 'XsjQP3iMAaQ3kQScQKthQpx9ALRbjKAjQtHg6TFomoc',
512 + decimal: 8,
513 + mint: 'tqqqx',
514 + enabled: false),
515 + SPLToken(
516 + name: 'UnitedHealth xStock',
517 + symbol: 'UNHx',
518 + mintAddress: 'XszvaiXGPwvk2nwb3o9C1CX4K6zH8sez11E6uyup6fe',
519 + decimal: 8,
520 + mint: 'unhx',
521 + enabled: false),
522 + SPLToken(
523 + name: 'Vanguard xStock',
524 + symbol: 'VTIx',
525 + mintAddress: 'XsssYEQjzxBCFgvYFFNuhJFBeHNdLWYeUSP8F45cDr9',
526 + decimal: 8,
527 + mint: 'vtix',
528 + enabled: false),
529 + SPLToken(
530 + name: 'Visa xStock',
531 + symbol: 'Vx',
532 + mintAddress: 'XsqgsbXwWogGJsNcVZ3TyVouy2MbTkfCFhCGGGcQZ2p',
533 + decimal: 8,
534 + mint: 'vx',
535 + enabled: false),
536 + SPLToken(
537 + name: 'Walmart xStock',
538 + symbol: 'WMTx',
539 + mintAddress: 'Xs151QeqTCiuKtinzfRATnUESM2xTU6V9Wy8Vy538ci',
540 + decimal: 8,
541 + mint: 'wmtx',
542 + enabled: false),
543 +
544 ];
545
546 List<SPLToken> get initialSPLTokens => _defaultTokens.map((token) {
cw_solana/lib/solana_client.dart
+1 -1
@@ -12,7 +12,7 @@ import 'package:cw_solana/pending_solana_transaction.dart';
12 import 'package:cw_solana/solana_balance.dart';
13 import 'package:cw_solana/solana_exceptions.dart';
14 import 'package:cw_solana/solana_transaction_model.dart';
15 -import 'package:cw_solana/spl_token.dart';
15 +import 'package:cw_core/spl_token.dart';
16 import 'package:on_chain/solana/solana.dart';
17 import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart';
18 import 'package:on_chain/solana/src/models/pda/pda.dart';
cw_solana/lib/solana_wallet.dart
+1 -1
@@ -25,7 +25,7 @@ import 'package:cw_solana/solana_transaction_history.dart';
25 import 'package:cw_solana/solana_transaction_info.dart';
26 import 'package:cw_solana/solana_transaction_model.dart';
27 import 'package:cw_solana/solana_wallet_addresses.dart';
28 -import 'package:cw_solana/spl_token.dart';
28 +import 'package:cw_core/spl_token.dart';
29 import 'package:hex/hex.dart';
30 import 'package:hive/hive.dart';
31 import 'package:mobx/mobx.dart';
cw_tron/lib/default_tron_tokens.dart
+1 -1
@@ -1,5 +1,5 @@
1 import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_tron/tron_token.dart';
2 +import 'package:cw_core/tron_token.dart';
3
4 class DefaultTronTokens {
5 final List<TronToken> _defaultTokens = [
cw_tron/lib/tron_client.dart
+1 -1
@@ -10,7 +10,7 @@ import 'package:cw_tron/pending_tron_transaction.dart';
10 import 'package:cw_tron/tron_abi.dart';
11 import 'package:cw_tron/tron_balance.dart';
12 import 'package:cw_tron/tron_http_provider.dart';
13 -import 'package:cw_tron/tron_token.dart';
13 +import 'package:cw_core/tron_token.dart';
14 import 'package:cw_tron/tron_transaction_model.dart';
15 import 'package:flutter/foundation.dart';
16 import 'package:flutter/services.dart';
cw_tron/lib/tron_wallet.dart
+1 -1
@@ -24,7 +24,7 @@ import 'package:cw_tron/tron_abi.dart';
24 import 'package:cw_tron/tron_balance.dart';
25 import 'package:cw_tron/tron_client.dart';
26 import 'package:cw_tron/tron_exception.dart';
27 -import 'package:cw_tron/tron_token.dart';
27 +import 'package:cw_core/tron_token.dart';
28 import 'package:cw_tron/tron_transaction_credentials.dart';
29 import 'package:cw_tron/tron_transaction_history.dart';
30 import 'package:cw_tron/tron_transaction_info.dart';
lib/core/trade_monitor.dart
+3 -1
@@ -184,7 +184,9 @@ class TradeMonitor {
184 trade
185 ..stateRaw = updated.state.raw
186 ..receiveAmount = updated.receiveAmount ?? trade.receiveAmount
187 - ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction;
187 + ..outputTransaction = updated.outputTransaction ?? trade.outputTransaction
188 + ..userCurrencyToRaw = updated.userCurrencyToRaw
189 + ..userCurrencyFromRaw = updated.userCurrencyFromRaw;
190 printV('Trade ${trade.id} updated: ${trade.state}');
191 await trade.save();
192
lib/di.dart
+1
@@ -525,6 +525,7 @@ Future<void> setup({
525 getIt.get<ContactListViewModel>(),
526 getIt.get<UnspentCoinsListViewModel>(),
527 getIt.get<FeesViewModel>(),
528 + _walletInfoSource,
529 ),
530 );
531
lib/exchange/provider/chainflip_exchange_provider.dart
+9 -1
@@ -167,6 +167,8 @@ class ChainflipExchangeProvider extends ExchangeProvider {
167 receiveAmount: request.toAmount,
168 state: TradeState.waiting,
169 payoutAddress: request.toAddress,
170 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
171 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
172 isSendAll: isSendAll);
173 } catch (e) {
174 printV(e.toString());
@@ -199,6 +201,9 @@ class ChainflipExchangeProvider extends ExchangeProvider {
201 final refundAmount = status['refundEgress']?['amount']?.toString() ?? '0.0';
202 final isRefund = status['refundEgress'] != null;
203 final amount = isRefund ? refundAmount : receiveAmount;
204 +
205 + final from = status['sourceAsset'].toString();
206 + final to = status['destinationAsset'].toString();
207
208 final newTrade = Trade(
209 id: id,
@@ -210,7 +215,10 @@ class ChainflipExchangeProvider extends ExchangeProvider {
215 state: currentState,
216 payoutAddress: status['destinationAddress'].toString(),
217 outputTransaction: status['swapEgress']?['transactionReference']?.toString(),
213 - isRefund: isRefund);
218 + isRefund: isRefund,
219 + userCurrencyFromRaw: '${from.toUpperCase()}' + '_',
220 + userCurrencyToRaw: '${to.toUpperCase()}' + '_',
221 + );
222
223 // Find trade and update receiveAmount with the real value received
224 final storedTrade = _getStoredTrade(id);
lib/exchange/provider/changenow_exchange_provider.dart
+18 -13
@@ -215,6 +215,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
215 receiveAmount: toAmount ?? request.toAmount,
216 state: TradeState.created,
217 payoutAddress: payoutAddress,
218 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
219 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
220 isSendAll: isSendAll,
221 );
222 }
@@ -240,9 +242,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
242
243 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
244 final fromCurrency = responseJSON['fromCurrency'] as String;
243 - final from = CryptoCurrency.fromString(fromCurrency);
245 + final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency);
246 final toCurrency = responseJSON['toCurrency'] as String;
245 - final to = CryptoCurrency.fromString(toCurrency);
247 + final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency);
248 final inputAddress = responseJSON['payinAddress'] as String;
249 final expectedSendAmount = responseJSON['expectedAmountFrom'].toString();
250 final status = responseJSON['status'] as String;
@@ -254,17 +256,20 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
256 final expiredAt = DateTime.tryParse(expiredAtRaw ?? '')?.toLocal();
257
258 return Trade(
257 - id: id,
258 - from: from,
259 - to: to,
260 - provider: description,
261 - inputAddress: inputAddress,
262 - amount: expectedSendAmount,
263 - state: state,
264 - extraId: extraId,
265 - expiredAt: expiredAt,
266 - outputTransaction: outputTransaction,
267 - payoutAddress: payoutAddress);
259 + id: id,
260 + from: from,
261 + to: to,
262 + provider: description,
263 + inputAddress: inputAddress,
264 + amount: expectedSendAmount,
265 + state: state,
266 + extraId: extraId,
267 + expiredAt: expiredAt,
268 + outputTransaction: outputTransaction,
269 + payoutAddress: payoutAddress,
270 + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
271 + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
272 + );
273 }
274
275 String _getFlow(bool isFixedRate) => isFixedRate ? 'fixed-rate' : 'standard';
lib/exchange/provider/exolix_exchange_provider.dart
+8 -3
@@ -214,6 +214,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
214 receiveAmount: receiveAmount ?? request.toAmount,
215 state: TradeState.created,
216 payoutAddress: payoutAddress,
217 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
218 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
219 isSendAll: isSendAll,
220 );
221 }
@@ -249,15 +251,18 @@ class ExolixExchangeProvider extends ExchangeProvider {
251
252 return Trade(
253 id: id,
252 - from: CryptoCurrency.fromString(coinFrom),
253 - to: CryptoCurrency.fromString(coinTo),
254 + from: CryptoCurrency.safeParseCurrencyFromString(coinFrom),
255 + to: CryptoCurrency.safeParseCurrencyFromString(coinTo),
256 provider: description,
257 inputAddress: inputAddress,
258 amount: amount,
259 state: TradeState.deserialize(raw: _prepareStatus(status)),
260 extraId: extraId,
261 outputTransaction: outputTransaction,
260 - payoutAddress: payoutAddress);
262 + payoutAddress: payoutAddress,
263 + userCurrencyFromRaw: '${coinFrom.toUpperCase()}' + '_',
264 + userCurrencyToRaw: '${coinTo.toUpperCase()}' + '_',
265 + );
266 }
267
268 String _getRateType(bool isFixedRate) => isFixedRate ? 'fixed' : 'float';
lib/exchange/provider/letsexchange_exchange_provider.dart
+20 -8
@@ -62,7 +62,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
62 if (networkFrom != null) 'network_from': networkFrom,
63 if (networkTo != null) 'network_to': networkTo,
64 'amount': '1',
65 - 'affiliate_id': _affiliateId
65 + 'affiliate_id': _affiliateId,
66 + 'float': isFixedRateMode ? 'false' : 'true',
67 };
68
69 final responseJSON = await _getInfo(params, isFixedRateMode);
@@ -91,7 +92,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
92 if (networkFrom != null) 'network_from': networkFrom,
93 if (networkTo != null) 'network_to': networkTo,
94 'amount': amount.toString(),
94 - 'affiliate_id': _affiliateId
95 + 'affiliate_id': _affiliateId,
96 + 'float': isFixedRateMode ? 'false' : 'true',
97 };
98
99 final responseJSON = await _getInfo(params, isFixedRateMode);
@@ -121,7 +123,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
123 if (networkFrom != null) 'network_from': networkFrom,
124 if (networkTo != null) 'network_to': networkTo,
125 'amount': isFixedRateMode ? request.toAmount.toString() : request.fromAmount.toString(),
124 - 'affiliate_id': _affiliateId
126 + 'affiliate_id': _affiliateId,
127 + 'float': isFixedRateMode ? 'false' : 'true',
128 };
129
130 final responseInfoJSON = await _getInfo(params, isFixedRateMode);
@@ -141,7 +144,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
144 'rate_id': rateId,
145 if (networkFrom != null) 'network_from': networkFrom,
146 if (networkTo != null) 'network_to': networkTo,
144 - 'affiliate_id': _affiliateId
147 + 'affiliate_id': _affiliateId,
148 + 'float': isFixedRateMode ? 'false' : 'true',
149 };
150
151 final headers = {
@@ -151,10 +155,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
155 };
156
157 final uri = Uri.https(_baseUrl,
154 - isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath, tradeParams);
158 + isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath);
159 final response = await ProxyWrapper().post(
160 clearnetUri: uri,
161 headers: headers,
162 + body: json.encode(tradeParams),
163 );
164
165
@@ -206,6 +211,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
211 createdAt: createdAt,
212 expiredAt: expiredAt,
213 extraId: extraId,
214 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
215 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
216 );
217 } catch (e) {
218 log(e.toString());
@@ -230,7 +237,9 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
237 }
238 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
239 final from = responseJSON['coin_from'] as String;
240 + final fromNetwork = responseJSON['coin_from_network'] as String;
241 final to = responseJSON['coin_to'] as String;
242 + final toNetwork = responseJSON['coin_to_network'] as String;
243 final payoutAddress = responseJSON['withdrawal'] as String;
244 final depositAddress = responseJSON['deposit'] as String;
245 final refundAddress = responseJSON['return'] as String;
@@ -246,8 +255,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
255
256 return Trade(
257 id: id,
249 - from: CryptoCurrency.fromString(from),
250 - to: CryptoCurrency.fromString(to),
258 + from: CryptoCurrency.safeParseCurrencyFromString(from),
259 + to: CryptoCurrency.safeParseCurrencyFromString(to),
260 provider: description,
261 inputAddress: depositAddress,
262 payoutAddress: payoutAddress,
@@ -259,6 +268,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
268 expiredAt: expiredAt,
269 isRefund: status == 'refund',
270 extraId: extraId,
271 + userCurrencyFromRaw: '$from' + '_' + '$fromNetwork',
272 + userCurrencyToRaw: '$to' + '_' + '$toNetwork',
273 );
274 }
275
@@ -270,10 +281,11 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
281 };
282
283 try {
273 - final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath, params);
284 + final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath);
285 final response = await ProxyWrapper().post(
286 clearnetUri: uri,
287 headers: headers,
288 + body: json.encode(params),
289 );
290
291 if (response.statusCode != 200) {
lib/exchange/provider/sideshift_exchange_provider.dart
+9 -4
@@ -225,7 +225,9 @@ class SideShiftExchangeProvider extends ExchangeProvider {
225 payoutAddress: settleAddress,
226 createdAt: DateTime.now(),
227 isSendAll: isSendAll,
228 - extraId: depositMemo
228 + extraId: depositMemo,
229 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
230 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
231 );
232 }
233
@@ -264,15 +266,18 @@ class SideShiftExchangeProvider extends ExchangeProvider {
266
267 return Trade(
268 id: id,
267 - from: CryptoCurrency.fromString(fromCurrency),
268 - to: CryptoCurrency.fromString(toCurrency),
269 + from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency),
270 + to: CryptoCurrency.safeParseCurrencyFromString(toCurrency),
271 provider: description,
272 inputAddress: inputAddress,
273 amount: expectedSendAmount ?? '',
274 state: TradeState.deserialize(raw: status ?? 'created'),
275 expiredAt: expiredAt,
276 payoutAddress: settleAddress,
275 - extraId: depositMemo);
277 + extraId: depositMemo,
278 + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
279 + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
280 + );
281 }
282
283 Future<String> _createQuote(TradeRequest request) async {
lib/exchange/provider/simpleswap_exchange_provider.dart
+6 -2
@@ -175,6 +175,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
175 payoutAddress: payoutAddress,
176 createdAt: DateTime.now(),
177 isSendAll: isSendAll,
178 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
179 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
180 );
181 }
182
@@ -211,14 +213,16 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
213
214 return Trade(
215 id: id,
214 - from: CryptoCurrency.fromString(fromCurrency),
215 - to: CryptoCurrency.fromString(toCurrency),
216 + from: CryptoCurrency.safeParseCurrencyFromString(fromCurrency),
217 + to: CryptoCurrency.safeParseCurrencyFromString(toCurrency),
218 extraId: extraId,
219 provider: description,
220 inputAddress: inputAddress,
221 amount: expectedSendAmount,
222 state: TradeState.deserialize(raw: status),
223 payoutAddress: payoutAddress,
224 + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
225 + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
226 );
227 }
228
lib/exchange/provider/stealth_ex_exchange_provider.dart
+6 -2
@@ -198,6 +198,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
198 createdAt: createdAt,
199 expiredAt: expiredAt,
200 extraId: extraId,
201 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
202 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
203 );
204 } catch (e) {
205 log(e.toString());
@@ -235,8 +237,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
237
238 return Trade(
239 id: respId,
238 - from: CryptoCurrency.fromString(from),
239 - to: CryptoCurrency.fromString(to),
240 + from: CryptoCurrency.safeParseCurrencyFromString(from),
241 + to: CryptoCurrency.safeParseCurrencyFromString(to),
242 provider: description,
243 inputAddress: depositAddress,
244 payoutAddress: payoutAddress,
@@ -247,6 +249,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
249 createdAt: createdAt,
250 isRefund: status == 'refunded',
251 extraId: extraId,
252 + userCurrencyFromRaw: '${from.toUpperCase()}' + '_',
253 + userCurrencyToRaw: '${to.toUpperCase()}' + '_',
254 );
255 }
256
lib/exchange/provider/swaptrade_exchange_provider.dart
+6 -2
@@ -190,6 +190,8 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
190 state: TradeState.created,
191 payoutAddress: request.toAddress,
192 isSendAll: isSendAll,
193 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
194 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
195 );
196 } catch (e) {
197 printV("error creating trade: ${e.toString()}");
@@ -224,9 +226,9 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
226
227 final responseData = responseBody['data'] as Map<String, dynamic>;
228 final fromCurrency = responseData['coin_send'] as String;
227 - final from = CryptoCurrency.fromString(fromCurrency);
229 + final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency);
230 final toCurrency = responseData['coin_receive'] as String;
229 - final to = CryptoCurrency.fromString(toCurrency);
231 + final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency);
232 final inputAddress = responseData['server_address'] as String;
233 final payoutAddress = responseData['recipient'] as String;
234 final status = responseData['status'] as String;
@@ -249,6 +251,8 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
251 receiveAmount: expectedReceiveAmount,
252 memo: memo,
253 createdAt: DateTime.tryParse(createdAt ?? ''),
254 + userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_',
255 + userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_',
256 );
257 } catch (e) {
258 printV("error getting trade: ${e.toString()}");
lib/exchange/provider/thorchain_exchange.provider.dart
+6 -2
@@ -157,6 +157,8 @@ class ThorChainExchangeProvider extends ExchangeProvider {
157 payoutAddress: request.toAddress,
158 memo: memo,
159 isSendAll: isSendAll,
160 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
161 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
162 );
163 }
164
@@ -197,8 +199,8 @@ class ThorChainExchangeProvider extends ExchangeProvider {
199 ? parts[1].split('.')[1].split('-')[0]
200 : '';
201
200 - final formattedToChain = CryptoCurrency.fromString(toChain);
201 - final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency: formattedToChain);
202 + final formattedToChain = CryptoCurrency.safeParseCurrencyFromString(toChain);
203 + final toAssetWithChain = CryptoCurrency.safeParseCurrencyFromString(toAsset, walletCurrency: formattedToChain);
204
205 final plannedOutTxs = responseJSON['planned_out_txs'] as List<dynamic>?;
206 final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false;
@@ -214,6 +216,8 @@ class ThorChainExchangeProvider extends ExchangeProvider {
216 state: currentState,
217 memo: memo,
218 isRefund: isRefund,
219 + userCurrencyFromRaw: '${tx['chain'] as String? ?? ''}' + '_',
220 + userCurrencyToRaw: '$toAsset' + '_',
221 );
222 }
223
lib/exchange/provider/trocador_exchange_provider.dart
+9 -2
@@ -251,6 +251,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
251 payoutAddress: payoutAddress,
252 isSendAll: isSendAll,
253 extraId: addressProviderMemo,
254 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
255 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
256 );
257 }
258
@@ -274,10 +276,13 @@ class TrocadorExchangeProvider extends ExchangeProvider {
276 final providerName = responseJSON['provider'] as String;
277 final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
278
279 + final from = responseJSON['ticker_from'] as String;
280 + final to = responseJSON['ticker_to'] as String;
281 +
282 return Trade(
283 id: id,
279 - from: CryptoCurrency.fromString(responseJSON['ticker_from'] as String),
280 - to: CryptoCurrency.fromString(responseJSON['ticker_to'] as String),
284 + from: CryptoCurrency.safeParseCurrencyFromString(from),
285 + to: CryptoCurrency.safeParseCurrencyFromString(to),
286 provider: description,
287 inputAddress: inputAddress,
288 refundAddress: refundAddress,
@@ -289,6 +294,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
294 providerId: providerId,
295 providerName: providerName,
296 extraId: addressProviderMemo,
297 + userCurrencyFromRaw: '${from.toUpperCase()}' + '_',
298 + userCurrencyToRaw: '${to.toUpperCase()}' + '_',
299 );
300 });
301 }
lib/exchange/provider/xoswap_exchange_provider.dart
+32 -19
@@ -76,22 +76,26 @@ class XOSwapExchangeProvider extends ExchangeProvider {
76 if (response.statusCode != 200) {
77 throw Exception('Failed to fetch assets for ${currency.title} on ${currency.tag}');
78 }
79 - final assets = json.decode(response.body) as List<dynamic>;
79 +
80 + final decoded = jsonDecode(response.body);
81 + if (decoded is! List) throw const FormatException('Unexpected response format');
82 + final assets = decoded.map((e) => Map<String, dynamic>.from(e as Map)).toList();
83 +
84
85 final asset = assets.firstWhere(
82 - (asset) {
83 - final assetSymbol = (asset['symbol'] as String).toUpperCase();
84 - return assetSymbol == currency.title.toUpperCase();
85 - },
86 - orElse: () => null,
86 + (asset) => removeNonAlphanumeric((asset['symbol'] ?? '').toString()) == currency.title,
87 + orElse: () => const {},
88 );
88 - return asset != null ? asset['id'] as String : null;
89 +
90 + return asset.isEmpty ? null : asset['id'] as String;
91 } catch (e) {
92 printV(e.toString());
93 return null;
94 }
95 }
96
97 + String removeNonAlphanumeric(String str) => str.toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), '');
98 +
99 Future<List<dynamic>> getRatesForPair({
100 required CryptoCurrency from,
101 required CryptoCurrency to,
@@ -117,19 +121,24 @@ class XOSwapExchangeProvider extends ExchangeProvider {
121 required CryptoCurrency to,
122 required bool isFixedRateMode,
123 }) async {
120 - final rates = await getRatesForPair(from: from, to: to);
121 - if (rates.isEmpty) return Limits(min: 0, max: 0);
124 + try {
125 + final rates = await getRatesForPair(from: from, to: to);
126 + if (rates.isEmpty) throw Exception('No rates found for $from to $to');
127
128 double minLimit = double.infinity;
129 double maxLimit = 0;
130
126 - for (var rate in rates) {
127 - final double currentMin = double.parse(rate['min']['value'].toString());
128 - final double currentMax = double.parse(rate['max']['value'].toString());
129 - if (currentMin < minLimit) minLimit = currentMin;
130 - if (currentMax > maxLimit) maxLimit = currentMax;
131 + for (var rate in rates) {
132 + final double currentMin = double.parse(rate['min']['value'].toString());
133 + final double currentMax = double.parse(rate['max']['value'].toString());
134 + if (currentMin < minLimit) minLimit = currentMin;
135 + if (currentMax > maxLimit) maxLimit = currentMax;
136 + }
137 + return Limits(min: minLimit, max: maxLimit);
138 + } catch (e) {
139 + printV(e.toString());
140 + throw Exception('StealthEx failed to fetch limits');
141 }
132 - return Limits(min: minLimit, max: maxLimit);
142 }
143
144 Future<double> fetchRate({
@@ -249,6 +258,8 @@ class XOSwapExchangeProvider extends ExchangeProvider {
258 receiveAmount: receiveAmount.toString(),
259 payoutAddress: payoutAddress,
260 extraId: extraId,
261 + userCurrencyFromRaw: '${request.fromCurrency.title}_${request.fromCurrency.tag ?? ''}',
262 + userCurrencyToRaw: '${request.toCurrency.title}_${request.toCurrency.tag ?? ''}',
263 );
264 } catch (e) {
265 printV(e.toString());
@@ -275,10 +286,10 @@ class XOSwapExchangeProvider extends ExchangeProvider {
286
287 final pairId = responseJSON['pairId'] as String;
288 final pairParts = pairId.split('_');
278 - final CryptoCurrency fromCurrency =
279 - CryptoCurrency.fromString(pairParts.isNotEmpty ? pairParts[0] : "");
280 - final CryptoCurrency toCurrency =
281 - CryptoCurrency.fromString(pairParts.length > 1 ? pairParts[1] : "");
289 + final fromAsset = pairParts.isNotEmpty ? pairParts[0] : '';
290 + final toAsset = pairParts.length > 1 ? pairParts[1] : '';
291 + final fromCurrency = CryptoCurrency.safeParseCurrencyFromString(fromAsset);
292 + final toCurrency = CryptoCurrency.safeParseCurrencyFromString(toAsset);
293
294 final amount = responseJSON['amount'] as Map<String, dynamic>;
295 final toAmount = responseJSON['toAmount'] as Map<String, dynamic>;
@@ -306,6 +317,8 @@ class XOSwapExchangeProvider extends ExchangeProvider {
317 receiveAmount: receiveAmount,
318 payoutAddress: payoutAddress,
319 extraId: extraId,
320 + userCurrencyFromRaw: '$fromAsset' + '_',
321 + userCurrencyToRaw: '$toAsset' + '_',
322 );
323 } catch (e) {
324 printV(e.toString());
lib/exchange/trade.dart
+50 -2
@@ -31,6 +31,8 @@ class Trade extends HiveObject {
31 this.isRefund,
32 this.isSendAll,
33 this.router,
34 + this.userCurrencyFromRaw,
35 + this.userCurrencyToRaw,
36 }) {
37 if (provider != null) providerRaw = provider.raw;
38
@@ -126,6 +128,46 @@ class Trade extends HiveObject {
128 @HiveField(23, defaultValue: '')
129 String? receiveAmount;
130
131 + @HiveField(24, defaultValue: '')
132 + String? userCurrencyFromRaw;
133 +
134 + @HiveField(25, defaultValue: '')
135 + String? userCurrencyToRaw;
136 +
137 + CryptoCurrency? get userCurrencyFrom {
138 + if (userCurrencyFromRaw == null || userCurrencyFromRaw!.isEmpty) {
139 + return null;
140 + }
141 + final underscoreIndex = userCurrencyFromRaw!.indexOf('_');
142 + final title = userCurrencyFromRaw!.substring(0, underscoreIndex);
143 + final tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
144 +
145 + return CryptoCurrency(
146 + title: title,
147 + tag: tag.isNotEmpty ? tag : null,
148 + name: '',
149 + raw: -1,
150 + decimals: 1,
151 + );
152 + }
153 +
154 + CryptoCurrency? get userCurrencyTo {
155 + if (userCurrencyToRaw == null || userCurrencyToRaw!.isEmpty) {
156 + return null;
157 + }
158 + final underscoreIndex = userCurrencyToRaw!.indexOf('_');
159 + final title = userCurrencyToRaw!.substring(0, underscoreIndex);
160 + final tag = userCurrencyToRaw!.substring(underscoreIndex + 1);
161 +
162 + return CryptoCurrency(
163 + title: title,
164 + tag: tag.isNotEmpty ? tag : null,
165 + name: '',
166 + raw: -1,
167 + decimals: 1,
168 + );
169 + }
170 +
171 static Trade fromMap(Map<String, Object?> map) {
172 return Trade(
173 id: map['id'] as String,
@@ -206,6 +248,8 @@ class TradeAdapter extends TypeAdapter<Trade> {
248 isRefund: fields[20] as bool?,
249 isSendAll: fields[21] as bool?,
250 router: fields[22] as String?,
251 + userCurrencyFromRaw: fields[24] as String?,
252 + userCurrencyToRaw: fields[25] as String?,
253 )
254 ..providerRaw = fields[1] == null ? 0 : fields[1] as int
255 ..fromRaw = fields[2] == null ? 0 : fields[2] as int
@@ -216,7 +260,7 @@ class TradeAdapter extends TypeAdapter<Trade> {
260 @override
261 void write(BinaryWriter writer, Trade obj) {
262 writer
219 - ..writeByte(24)
263 + ..writeByte(26)
264 ..writeByte(0)
265 ..write(obj.id)
266 ..writeByte(1)
@@ -264,7 +308,11 @@ class TradeAdapter extends TypeAdapter<Trade> {
308 ..writeByte(22)
309 ..write(obj.router)
310 ..writeByte(23)
267 - ..write(obj.receiveAmount);
311 + ..write(obj.receiveAmount)
312 + ..writeByte(24)
313 + ..write(obj.userCurrencyFromRaw)
314 + ..writeByte(25)
315 + ..write(obj.userCurrencyToRaw);
316 }
317
318 @override
lib/main.dart
+15
@@ -36,10 +36,13 @@ import 'package:cake_wallet/view_model/link_view_model.dart';
36 import 'package:cake_wallet/utils/responsive_layout_util.dart';
37 import 'package:cw_core/address_info.dart';
38 import 'package:cw_core/cake_hive.dart';
39 +import 'package:cw_core/erc20_token.dart';
40 import 'package:cw_core/hive_type_ids.dart';
41 import 'package:cw_core/mweb_utxo.dart';
42 import 'package:cw_core/node.dart';
43 import 'package:cw_core/payjoin_session.dart';
44 +import 'package:cw_core/spl_token.dart';
45 +import 'package:cw_core/tron_token.dart';
46 import 'package:cw_core/unspent_coins_info.dart';
47 import 'package:cw_core/utils/print_verbose.dart';
48 import 'package:cw_core/utils/proxy_logger/memory_proxy_logger.dart';
@@ -206,6 +209,18 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
209 CakeHive.registerAdapter(PayjoinSessionAdapter());
210 }
211
212 + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
213 + CakeHive.registerAdapter(Erc20TokenAdapter());
214 + }
215 +
216 + if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
217 + CakeHive.registerAdapter(SPLTokenAdapter());
218 + }
219 +
220 + if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
221 + CakeHive.registerAdapter(TronTokenAdapter());
222 + }
223 +
224 final secureStorage = secureStorageShared;
225 final transactionDescriptionsBoxKey =
226 await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
lib/src/screens/dashboard/edit_token_page.dart
+15 -8
@@ -125,13 +125,16 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
125 });
126 }
127
128 - void _checkIfTokenIsVerified(String? contractAddress) async {
129 - if (contractAddress != null) {
130 - final isVerified = widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress);
131 - setState(() {
132 - _isTokenVerified = isVerified;
133 - });
134 - }
128 + void _checkIfTokenIsVerified(String? contractAddress) {
129 + if (contractAddress == null) return;
130 +
131 + final isVerified =
132 + widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress);
133 +
134 + if (!mounted) return;
135 + setState(() {
136 + _isTokenVerified = isVerified;
137 + });
138 }
139
140 @override
@@ -253,7 +256,9 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
256 name: _tokenNameController.text,
257 title: _tokenSymbolController.text.toUpperCase(),
258 decimals: int.parse(_tokenDecimalController.text),
256 - iconPath: _tokenIconPathController.text,
259 + iconPath: _tokenIconPathController.text.isNotEmpty
260 + ? _tokenIconPathController.text
261 + : null,
262 isPotentialScam: isPotentialScam,
263 ),
264 contractAddress: _contractAddressController.text,
@@ -335,6 +340,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
340 if (_contractAddressController.text.isNotEmpty) {
341 final token = await widget.homeSettingsViewModel.getToken(_contractAddressController.text);
342
343 + if (!mounted) return;
344 +
345 if (token != null) {
346 final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano;
347 if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name;
lib/src/screens/dashboard/pages/transactions_page.dart
+22 -16
@@ -164,22 +164,28 @@ class TransactionsPage extends StatelessWidget {
164 if (item is TradeListItem) {
165 final trade = item.trade;
166
167 - return Observer(
168 - builder: (_) => TradeRow(
169 - key: item.key,
170 - onTap: () => Navigator.of(context)
171 - .pushNamed(Routes.tradeDetails, arguments: trade),
172 - swapState: trade.state,
173 - provider: trade.provider,
174 - from: trade.from,
175 - to: trade.to,
176 - createdAtFormattedDate: trade.createdAt != null
177 - ? DateFormat('HH:mm').format(trade.createdAt!)
178 - : null,
179 - formattedAmount: item.tradeFormattedAmount,
180 - formattedReceiveAmount: item.tradeFormattedReceiveAmount
181 - ),
182 - );
167 + final tradeFrom =
168 + trade.fromRaw >= 0 ? trade.from : trade.userCurrencyFrom;
169 +
170 + final tradeTo = trade.toRaw >= 0 ? trade.to : trade.userCurrencyTo;
171 +
172 + return tradeFrom != null && tradeTo != null
173 + ? Observer(
174 + builder: (_) => TradeRow(
175 + key: item.key,
176 + onTap: () => Navigator.of(context)
177 + .pushNamed(Routes.tradeDetails, arguments: trade),
178 + swapState: trade.state,
179 + provider: trade.provider,
180 + from: tradeFrom,
181 + to: tradeTo,
182 + createdAtFormattedDate: trade.createdAt != null
183 + ? DateFormat('HH:mm').format(trade.createdAt!)
184 + : null,
185 + formattedAmount: item.tradeFormattedAmount,
186 + formattedReceiveAmount: item.tradeFormattedReceiveAmount),
187 + )
188 + : Container();
189 }
190 if (item is OrderListItem) {
191 final order = item.order;
lib/src/screens/exchange/widgets/currency_picker.dart
+5 -7
@@ -39,13 +39,11 @@ class CurrencyPickerState extends State<CurrencyPicker> {
39 String textFieldValue;
40
41 bool currencySearchBySubstring(Currency currency, String subString) {
42 - return currency.name.toLowerCase().contains(subString.toLowerCase()) ||
43 - (currency.tag != null
44 - ? currency.tag!.toLowerCase().contains(subString.toLowerCase())
45 - : false) ||
46 - (currency.fullName != null
47 - ? currency.fullName!.toLowerCase().contains(subString.toLowerCase())
48 - : false);
42 + final query = subString.toLowerCase();
43 + return currency.name.toLowerCase().contains(query) ||
44 + currency.toString().toLowerCase().contains(query) ||
45 + (currency.tag != null ? currency.tag!.toLowerCase().contains(query) : false) ||
46 + (currency.fullName != null ? currency.fullName!.toLowerCase().contains(query) : false);
47 }
48
49 @override
lib/src/screens/exchange/widgets/present_provider_picker.dart
+1 -1
@@ -76,7 +76,7 @@ class PresentProviderPicker extends StatelessWidget {
76 (e) => CheckBoxItem(
77 e.title,
78 exchangeViewModel.selectedProviders.contains(e),
79 - isDisabled: !exchangeViewModel.providersForCurrentPair().contains(e),
79 + isDisabled: !exchangeViewModel.providerList.contains(e),
80 ),
81 )
82 .toList(),
lib/view_model/dashboard/home_settings_view_model.dart
+4 -2
@@ -102,15 +102,17 @@ abstract class HomeSettingsViewModelBase with Store {
102 }
103
104 if (_balanceViewModel.wallet.type == WalletType.solana) {
105 + final splToken = token.copyWith(enabled: true);
106 await solana!.addSPLToken(
107 _balanceViewModel.wallet,
107 - token,
108 + splToken,
109 contractAddress,
110 );
111 }
112
113 if (_balanceViewModel.wallet.type == WalletType.tron) {
113 - await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress);
114 + final tronToken = token.copyWith(enabled: true);
115 + await tron!.addTronToken(_balanceViewModel.wallet, tronToken, contractAddress);
116 }
117
118 if (_balanceViewModel.wallet.type == WalletType.zano) {
lib/view_model/exchange/exchange_trade_view_model.dart
+52 -33
@@ -159,9 +159,18 @@ abstract class ExchangeTradeViewModelBase with Store {
159 }
160
161 void _updateItems() {
162 - final tagFrom =
163 - tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
164 - final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
162 + final trade = tradesStore.trade!;
163 + final tradeFrom = trade.fromRaw >= 0
164 + ? trade.from
165 + : trade.userCurrencyFrom;
166 +
167 + final tradeTo = trade.toRaw >= 0
168 + ? trade.to
169 + : trade.userCurrencyTo;
170 +
171 + final tagFrom = tradeFrom?.tag != null ? '${tradeFrom!.tag}' + ' ' : '';
172 + final tagTo = tradeTo?.tag != null ? '${tradeTo!.tag}' + ' ' : '';
173 +
174 items.clear();
175
176 if (trade.provider != ExchangeProviderDescription.thorChain)
@@ -175,36 +184,40 @@ abstract class ExchangeTradeViewModelBase with Store {
184 ),
185 );
186
178 - items.addAll([
179 - ExchangeTradeItem(
180 - title: S.current.amount,
181 - data: '${trade.amount} ${trade.from}',
182 - isCopied: false,
183 - isReceiveDetail: false,
184 - isExternalSendDetail: true,
185 - ),
186 - ExchangeTradeItem(
187 - title: S.current.you_will_receive_estimated_amount + ':',
188 - data: '${tradesStore.trade?.receiveAmount} ${trade.to}',
189 - isCopied: true,
190 - isReceiveDetail: true,
191 - isExternalSendDetail: false,
192 - ),
193 - ExchangeTradeItem(
194 - title: S.current.send_to_this_address('${tradesStore.trade!.from}', tagFrom) + ':',
195 - data: trade.inputAddress ?? '',
196 - isCopied: false,
197 - isReceiveDetail: false,
198 - isExternalSendDetail: true,
199 - ),
200 - ]);
187 + if (tradeFrom != null || tradeTo != null) {
188 + items.addAll([
189 + ExchangeTradeItem(
190 + title: S.current.amount,
191 + data: '${trade.amount} ${tradeFrom}',
192 + isCopied: false,
193 + isReceiveDetail: false,
194 + isExternalSendDetail: true,
195 + ),
196 + ExchangeTradeItem(
197 + title: S.current.you_will_receive_estimated_amount + ':',
198 + data: '${tradesStore.trade?.receiveAmount} ${tradeTo}',
199 + isCopied: true,
200 + isReceiveDetail: true,
201 + isExternalSendDetail: false,
202 + ),
203 + ExchangeTradeItem(
204 + title: S.current.send_to_this_address('${tradeFrom}', tagFrom) + ':',
205 + data: trade.inputAddress ?? '',
206 + isCopied: false,
207 + isReceiveDetail: false,
208 + isExternalSendDetail: true,
209 + ),
210 + ]);
211 + }
212
213 final isExtraIdExist = trade.extraId != null && trade.extraId!.isNotEmpty;
214
215 if (isExtraIdExist) {
205 - final title = trade.from == CryptoCurrency.xrp
216 +
217 +
218 + final title = tradeFrom == CryptoCurrency.xrp
219 ? S.current.destination_tag
207 - : trade.from == CryptoCurrency.xlm || trade.from == CryptoCurrency.ton
220 + : tradeFrom == CryptoCurrency.xlm || tradeFrom == CryptoCurrency.ton
221 ? S.current.memo
222 : S.current.extra_id;
223
@@ -220,7 +233,7 @@ abstract class ExchangeTradeViewModelBase with Store {
233
234 items.add(
235 ExchangeTradeItem(
223 - title: S.current.arrive_in_this_address('${tradesStore.trade!.to}', tagTo) + ':',
236 + title: S.current.arrive_in_this_address('${tradeTo}', tagTo) + ':',
237 data: trade.payoutAddress ?? '',
238 isCopied: true,
239 isReceiveDetail: true,
@@ -230,23 +243,29 @@ abstract class ExchangeTradeViewModelBase with Store {
243 }
244
245 static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) {
246 +
247 + final trade = tradesStore.trade!;
248 + final tradeFrom = trade.fromRaw >= 0
249 + ? trade.from
250 + : trade.userCurrencyFrom;
251 +
252 bool _isEthToken() =>
253 wallet.currency == CryptoCurrency.eth &&
254 tradesStore.trade!.from.tag == CryptoCurrency.eth.title;
255
256 bool _isPolygonToken() =>
257 wallet.currency == CryptoCurrency.maticpoly &&
239 - tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag;
258 + tradeFrom?.tag == CryptoCurrency.maticpoly.tag;
259
260 bool _isTronToken() =>
261 wallet.currency == CryptoCurrency.trx &&
243 - tradesStore.trade!.from.tag == CryptoCurrency.trx.title;
262 + tradeFrom?.tag == CryptoCurrency.trx.title;
263
264 bool _isSplToken() =>
265 wallet.currency == CryptoCurrency.sol &&
247 - tradesStore.trade!.from.tag == CryptoCurrency.sol.title;
266 + tradeFrom?.tag == CryptoCurrency.sol.title;
267
249 - return tradesStore.trade!.from == wallet.currency ||
268 + return tradeFrom == wallet.currency ||
269 tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
270 _isEthToken() ||
271 _isPolygonToken() ||
lib/view_model/exchange/exchange_view_model.dart
+213 -25
@@ -37,12 +37,17 @@ import 'package:cake_wallet/utils/feature_flag.dart';
37 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
38 import 'package:cake_wallet/view_model/send/fees_view_model.dart';
39 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
40 +import 'package:cw_core/cake_hive.dart';
41 import 'package:cw_core/crypto_currency.dart';
42 +import 'package:cw_core/erc20_token.dart';
43 +import 'package:cw_core/spl_token.dart';
44 import 'package:cw_core/sync_status.dart';
45 import 'package:cw_core/transaction_priority.dart';
46 +import 'package:cw_core/tron_token.dart';
47 import 'package:cw_core/unspent_coin_type.dart';
48 import 'package:cw_core/utils/print_verbose.dart';
49 import 'package:cw_core/utils/proxy_wrapper.dart';
50 +import 'package:cw_core/wallet_info.dart';
51 import 'package:cw_core/wallet_type.dart';
52 import 'package:hive/hive.dart';
53 import 'package:intl/intl.dart';
@@ -70,6 +75,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
75 this.contactListViewModel,
76 this.unspentCoinsListViewModel,
77 this.feesViewModel,
78 + this.walletInfoSource,
79 ) : _cryptoNumberFormat = NumberFormat(),
80 isSendAllEnabled = false,
81 isFixedRateMode = false,
@@ -81,8 +87,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
87 isDepositAddressEnabled = false,
88 isReceiveAmountEditable = false,
89 _useTorOnly = false,
84 - receiveCurrencies = <CryptoCurrency>[],
85 - depositCurrencies = <CryptoCurrency>[],
90 + receiveCurrencies = ObservableList<CryptoCurrency>(),
91 + depositCurrencies = ObservableList<CryptoCurrency>(),
92 limits = Limits(min: 0, max: 0),
93 tradeState = ExchangeTradeStateInitial(),
94 limitsState = LimitsInitialState(),
@@ -138,7 +144,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
144 receiveAddress = '';
145 depositAddress =
146 depositCurrency == wallet.currency ? wallet.walletAddresses.addressForExchange : '';
141 - provider = providersForCurrentPair().first;
147 +
148 + provider = providerList.firstOrNull;
149 final initialProvider = provider;
150 provider!.checkIsAvailable().then((bool isAvailable) {
151 if (!isAvailable && provider == initialProvider) {
@@ -149,10 +156,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
156 });
157 receiveCurrencies = CryptoCurrency.all
158 .where((cryptoCurrency) => !excludeReceiveCurrencies.contains(cryptoCurrency))
152 - .toList();
159 + .toList()
160 + .asObservable();
161 depositCurrencies = CryptoCurrency.all
162 .where((cryptoCurrency) => !excludeDepositCurrencies.contains(cryptoCurrency))
155 - .toList();
163 + .toList()
164 + .asObservable();
165 +
166 + _injectUserEthTokensIntoCurrencyLists();
167 + _injectUserSplTokensIntoCurrencyLists();
168 + _injectUserTronTokensIntoCurrencyLists();
169 _defineIsReceiveAmountEditable();
170 loadLimits();
171 reaction((_) => isFixedRateMode, (Object _) {
@@ -165,8 +178,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
178 }
179 }
180
168 - bool get isElectrumWallet =>
169 - [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash,WalletType.dogecoin ].contains(wallet.type);
181 + bool get isElectrumWallet => [
182 + WalletType.bitcoin,
183 + WalletType.litecoin,
184 + WalletType.bitcoinCash,
185 + WalletType.dogecoin
186 + ].contains(wallet.type);
187
188 bool get hideAddressAfterExchange =>
189 [WalletType.monero, WalletType.wownero].contains(wallet.type);
@@ -320,9 +337,11 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
337
338 bool get isMoneroWallet => wallet.type == WalletType.monero;
339
323 - List<CryptoCurrency> receiveCurrencies;
340 + @observable
341 + ObservableList<CryptoCurrency> receiveCurrencies;
342
325 - List<CryptoCurrency> depositCurrencies;
343 + @observable
344 + ObservableList<CryptoCurrency> depositCurrencies;
345
346 final NumberFormat _cryptoNumberFormat;
347
@@ -334,6 +353,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
353
354 final FeesViewModel feesViewModel;
355
356 + final Box<WalletInfo> walletInfoSource;
357 +
358 @observable
359 double bestRate = 0.0;
360
@@ -490,7 +511,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
511
512 try {
513 final futures = selectedProviders
493 - .where((provider) => providersForCurrentPair().contains(provider))
514 + .where((provider) => providerList.contains(provider))
515 .map((provider) async {
516 final limits = await provider
517 .fetchLimits(
@@ -717,16 +738,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
738 void removeTemplate({required ExchangeTemplate template}) =>
739 _exchangeTemplateStore.remove(template: template);
740
720 - List<ExchangeProvider> providersForCurrentPair() =>
721 - _providersForPair(from: depositCurrency, to: receiveCurrency);
722 -
723 - List<ExchangeProvider> _providersForPair(
724 - {required CryptoCurrency from, required CryptoCurrency to}) =>
725 - providerList
726 - .where((provider) =>
727 - provider.pairList.where((pair) => pair.from == from && pair.to == to).isNotEmpty)
728 - .toList();
729 -
741 void _onPairChange() {
742 depositAmount = '';
743 receiveAmount = '';
@@ -819,7 +830,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
830 @action
831 void addExchangeProvider(ExchangeProvider provider) {
832 selectedProviders.add(provider);
822 - if (providersForCurrentPair().contains(provider)) _tradeAvailableProviders.add(provider);
833 + if (providerList.contains(provider)) _tradeAvailableProviders.add(provider);
834 }
835
836 @action
@@ -863,16 +874,15 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
874 }
875
876 bool get isAvailableInSelected {
866 - final providersForPair = providersForCurrentPair();
877 return selectedProviders
868 - .any((element) => element.isAvailable && providersForPair.contains(element));
878 + .any((element) => element.isAvailable && providerList.contains(element));
879 }
880
881 void _setAvailableProviders() {
882 _tradeAvailableProviders.clear();
883
884 _tradeAvailableProviders.addAll(
875 - selectedProviders.where((provider) => providersForCurrentPair().contains(provider)));
885 + selectedProviders.where((provider) => providerList.contains(provider)));
886 }
887
888 void _setProviders() {
@@ -989,7 +999,185 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
999 }
1000 }
1001
992 - double initialAmountByAssets (CryptoCurrency ticker) {
1002 + // Adding user's Erc20 tokens to the list of currencies
1003 +
1004 + Future<Box<Erc20Token>> _openEvmTokensBoxFor(WalletInfo walletInfo) async {
1005 + final walletKey = walletInfo.name.replaceAll(" ", "_");
1006 +
1007 + final boxName = switch (walletInfo.type) {
1008 + WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
1009 + WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
1010 + _ => '${walletKey}_${Erc20Token.ethereumBoxName}',
1011 + };
1012 +
1013 + if (CakeHive.isBoxOpen(boxName)) {
1014 + return CakeHive.box<Erc20Token>(boxName);
1015 + }
1016 + return CakeHive.openBox<Erc20Token>(boxName);
1017 + }
1018 +
1019 + Future<List<Erc20Token>> _loadAllUniqueEvmTokens() async {
1020 + final evmWallets = walletInfoSource.values.where(
1021 + (w) => w.type == WalletType.ethereum || w.type == WalletType.polygon,
1022 + );
1023 +
1024 + final seen = <String>{};
1025 + final unique = <Erc20Token>[];
1026 +
1027 + for (final wallet in evmWallets) {
1028 + final chain = wallet.type == WalletType.ethereum ? 'ETH' : 'POL';
1029 + final box = await _openEvmTokensBoxFor(wallet);
1030 +
1031 + for (final t in box.values.where((t) => t.enabled)) {
1032 + final key = '$chain|${t.contractAddress.toLowerCase()}';
1033 + if (seen.add(key)) {
1034 + unique.add(t);
1035 + }
1036 + }
1037 + }
1038 +
1039 + return unique;
1040 + }
1041 +
1042 +
1043 + @action
1044 + Future<void> _injectUserEthTokensIntoCurrencyLists() async {
1045 + final userTokens = await _loadAllUniqueEvmTokens();
1046 +
1047 + final toAddReceive = <CryptoCurrency>[];
1048 + final toAddDeposit = <CryptoCurrency>[];
1049 +
1050 + for (final token in userTokens) {
1051 + if (!_listContainsToken(receiveCurrencies, token)) toAddReceive.add(token);
1052 + if (!_listContainsToken(depositCurrencies, token)) toAddDeposit.add(token);
1053 + }
1054 +
1055 + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
1056 + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit);
1057 + }
1058 +
1059 + bool _listContainsToken(List<CryptoCurrency> list, Erc20Token token) {
1060 + return list.any((item) {
1061 + if (item is Erc20Token) {
1062 + return item.contractAddress.toLowerCase() == token.contractAddress.toLowerCase();
1063 + }
1064 + return item.title.toUpperCase() == token.symbol.toUpperCase() &&
1065 + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null);
1066 + });
1067 + }
1068 +
1069 + // Adding user's Solana tokens to the list of currencies
1070 +
1071 + Future<Box<SPLToken>> _openSolTokensBoxFor(WalletInfo wallet) async {
1072 + final boxName = '${wallet.name.replaceAll(" ", "_")}_${SPLToken.boxName}';
1073 + if (CakeHive.isBoxOpen(boxName)) {
1074 + return CakeHive.box<SPLToken>(boxName);
1075 + }
1076 + return CakeHive.openBox<SPLToken>(boxName);
1077 + }
1078 +
1079 + Future<List<SPLToken>> _loadAllUniqueSolTokens() async {
1080 + final solWallets = walletInfoSource.values.where((wallet) => wallet.type == WalletType.solana);
1081 + final tokens = <SPLToken>[];
1082 +
1083 + for (final wallet in solWallets) {
1084 + final box = await _openSolTokensBoxFor(wallet);
1085 + tokens.addAll(box.values.where((t) => t.enabled));
1086 + }
1087 +
1088 + final seen = <String>{};
1089 + final unique = <SPLToken>[];
1090 + for (final token in tokens) {
1091 + final key = token.mintAddress.toLowerCase();
1092 + if (!seen.contains(key)) {
1093 + seen.add(key);
1094 + unique.add(token);
1095 + }
1096 + }
1097 + return unique;
1098 + }
1099 +
1100 + bool _listContainsSplToken(List<CryptoCurrency> list, SPLToken token) {
1101 + return list.any((item) {
1102 + if (item is SPLToken) {
1103 + return item.mintAddress.toLowerCase() == token.mintAddress.toLowerCase();
1104 + }
1105 + return item.title.toUpperCase() == token.symbol.toUpperCase() &&
1106 + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null);
1107 + });
1108 + }
1109 +
1110 + @action
1111 + Future<void> _injectUserSplTokensIntoCurrencyLists() async {
1112 + final userTokens = await _loadAllUniqueSolTokens();
1113 +
1114 + final toAddReceive = <CryptoCurrency>[];
1115 + final toAddDeposit = <CryptoCurrency>[];
1116 +
1117 + for (final token in userTokens) {
1118 + if (!_listContainsSplToken(receiveCurrencies, token)) toAddReceive.add(token);
1119 + if (!_listContainsSplToken(depositCurrencies, token)) toAddDeposit.add(token);
1120 + }
1121 +
1122 + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
1123 + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit);
1124 + }
1125 +
1126 + // Adding user's Tron tokens to the list of currencies
1127 +
1128 + Future<Box<TronToken>> _openTronTokensBoxFor(WalletInfo walletInfo) async {
1129 + final boxName = '${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}';
1130 + if (CakeHive.isBoxOpen(boxName)) {
1131 + return CakeHive.box<TronToken>(boxName);
1132 + }
1133 + return CakeHive.openBox<TronToken>(boxName);
1134 + }
1135 +
1136 + Future<List<TronToken>> _loadAllUniqueTronTokens() async {
1137 + final tronWallets =
1138 + walletInfoSource.values.where((w) => w.type == WalletType.tron);
1139 +
1140 + final seen = <String>{};
1141 + final unique = <TronToken>[];
1142 +
1143 + for (final wallet in tronWallets) {
1144 + final box = await _openTronTokensBoxFor(wallet);
1145 + for (final t in box.values.where((t) => t.enabled)) {
1146 + final key = t.contractAddress.toLowerCase();
1147 + if (seen.add(key)) unique.add(t);
1148 + }
1149 + }
1150 +
1151 + return unique;
1152 + }
1153 +
1154 + bool _listContainsTronToken(List<CryptoCurrency> list, TronToken token) {
1155 + return list.any((item) {
1156 + if (item is TronToken) {
1157 + return item.contractAddress.toLowerCase() == token.contractAddress.toLowerCase();
1158 + }
1159 + return item.title.toUpperCase() == token.symbol.toUpperCase() &&
1160 + (item.tag?.toUpperCase() == token.tag?.toUpperCase() || item.tag == null);
1161 + });
1162 + }
1163 +
1164 + @action
1165 + Future<void> _injectUserTronTokensIntoCurrencyLists() async {
1166 + final userTokens = await _loadAllUniqueTronTokens();
1167 +
1168 + final toAddReceive = <CryptoCurrency>[];
1169 + final toAddDeposit = <CryptoCurrency>[];
1170 +
1171 + for (final token in userTokens) {
1172 + if (!_listContainsTronToken(receiveCurrencies, token)) toAddReceive.add(token);
1173 + if (!_listContainsTronToken(depositCurrencies, token)) toAddDeposit.add(token);
1174 + }
1175 +
1176 + if (toAddReceive.isNotEmpty) receiveCurrencies.addAll(toAddReceive);
1177 + if (toAddDeposit.isNotEmpty) depositCurrencies.addAll(toAddDeposit);
1178 + }
1179 +
1180 + double initialAmountByAssets(CryptoCurrency ticker) {
1181 final amount = switch (ticker) {
1182 CryptoCurrency.trx => 1000,
1183 CryptoCurrency.nano => 10,
lib/view_model/trade_details_view_model.dart
+35 -11
@@ -24,6 +24,7 @@ import 'package:cake_wallet/store/app_store.dart';
24 import 'package:cake_wallet/utils/date_formatter.dart';
25 import 'package:cake_wallet/utils/show_bar.dart';
26 import 'package:collection/collection.dart';
27 +import 'package:cw_core/crypto_currency.dart';
28 import 'package:cw_core/utils/print_verbose.dart';
29 import 'package:flutter/cupertino.dart';
30 import 'package:flutter/services.dart';
@@ -163,17 +164,22 @@ abstract class TradeDetailsViewModelBase with Store {
164 items.add(
165 DetailsListStatusItem(title: S.current.trade_details_state, value: trade.state.toString()));
166
166 - items.add(TradeDetailsListCardItem.tradeDetails(
167 - id: trade.id,
168 - extraId: trade.extraId,
169 - createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '',
170 - from: trade.from,
171 - to: trade.to,
172 - onTap: (BuildContext context) {
173 - Clipboard.setData(ClipboardData(text: '${trade.id}'));
174 - showBar<void>(context, S.of(context).copied_to_clipboard);
175 - },
176 - ));
167 + final tradeFrom = _safeFrom(trade);
168 + final tradeTo = _safeTo(trade);
169 +
170 + if (tradeFrom != null && tradeTo != null) {
171 + items.add(TradeDetailsListCardItem.tradeDetails(
172 + id: trade.id,
173 + extraId: trade.extraId,
174 + createdAt: trade.createdAt != null ? dateFormat.format(trade.createdAt!) : '',
175 + from: tradeFrom,
176 + to: tradeTo,
177 + onTap: (context) {
178 + Clipboard.setData(ClipboardData(text: trade.id));
179 + showBar<void>(context, S.of(context).copied_to_clipboard);
180 + },
181 + ));
182 + }
183
184 items.add(StandartListItem(
185 title: S.current.trade_details_provider, value: trade.provider.toString()));
@@ -207,4 +213,22 @@ abstract class TradeDetailsViewModelBase with Store {
213 launchUrl(uri, mode: LaunchMode.externalApplication);
214 } catch (e) {}
215 }
216 +
217 + CryptoCurrency? _safeFrom(Trade trade) {
218 + try {
219 + final raw = trade.fromRaw;
220 + return raw >= 0 ? trade.from : trade.userCurrencyFrom;
221 + } catch (_) {
222 + return trade.userCurrencyFrom;
223 + }
224 + }
225 +
226 + CryptoCurrency? _safeTo(Trade trade) {
227 + try {
228 + final raw = trade.toRaw;
229 + return raw >= 0 ? trade.to : trade.userCurrencyTo;
230 + } catch (_) {
231 + return trade.userCurrencyTo;
232 + }
233 + }
234 }
tool/configure.dart
+2 -2
@@ -1113,11 +1113,11 @@ import 'package:cw_core/wallet_base.dart';
1113 import 'package:cw_core/wallet_credentials.dart';
1114 import 'package:cw_core/wallet_info.dart';
1115 import 'package:cw_core/wallet_service.dart';
1116 +import 'package:cw_core/spl_token.dart';
1117 import 'package:hive/hive.dart';
1118
1119 """;
1120 const solanaCWHeaders = """
1120 -import 'package:cw_solana/spl_token.dart';
1121 import 'package:cw_solana/solana_wallet.dart';
1122 import 'package:cw_solana/solana_mnemonics.dart';
1123 import 'package:cw_solana/solana_wallet_service.dart';
@@ -1199,6 +1199,7 @@ import 'package:cw_core/wallet_base.dart';
1199 import 'package:cw_core/wallet_credentials.dart';
1200 import 'package:cw_core/wallet_info.dart';
1201 import 'package:cw_core/wallet_service.dart';
1202 +import 'package:cw_core/tron_token.dart';
1203 import 'package:hive/hive.dart';
1204
1205 """;
@@ -1209,7 +1210,6 @@ import 'package:cw_tron/tron_transaction_info.dart';
1210 import 'package:cw_tron/tron_wallet_creation_credentials.dart';
1211
1212 import 'package:cw_tron/tron_client.dart';
1212 -import 'package:cw_tron/tron_token.dart';
1213 import 'package:cw_tron/tron_wallet.dart';
1214 import 'package:cw_tron/tron_wallet_service.dart';
1215 import 'package:cw_tron/default_tron_tokens.dart';