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';