tx-confirmation-options-on-dev [cw-1463] (#3289)
* add note and contact options to tx confirmation screen * fix closing animation for confirm sheet * fix note button for lightning * sizing fix * add local tx info for evm/sol/trx * fix for xmr/zec * merge * fixes
malik1004x committed
Jun 4, 2026 at 11:32 UTC
5099fa3b9cf5911c5e4cc49e045ca10e2cc19e75
22 files changed
+458
-65
analysis_options.yaml
-13
@@ -13,19 +13,6 @@ analyzer:
13
cw_monero/ios/External/**,
14
cw_shared_external/**,
15
shared_external/**,
16
- lib/bitcoin/cw_bitcoin.dart,
17
- lib/bitcoin_cash/cw_bitcoin_cash.dart,
18
- lib/ethereum/cw_ethereum.dart,
19
- lib/haven/cw_haven.dart,
20
- lib/monero/cw_monero.dart,
21
- lib/nano/cw_nano.dart,
22
- lib/polygon/cw_polygon.dart,
23
- lib/solana/cw_solana.dart,
24
- lib/tron/cw_tron.dart,
25
- lib/wownero/cw_wownero.dart,
26
- lib/zano/cw_zano.dart,
27
- lib/base/cw_base.dart,
28
- lib/arbitrum/cw_arbitrum.dart,
16
]
17
language:
18
strict-casts: true
cw_bitcoin/lib/lightning/lightning_wallet.dart
+4
-1
@@ -198,6 +198,7 @@ class LightningWallet {
198
final res = await sdk.sendPayment(
199
request: SendPaymentRequest(prepareResponse: prepareResponse));
200
printV(res.payment.status.name);
201
+ return res.payment.id;
202
} on SdkError_SparkError catch (e) {
203
if (e.field0.contains("AlreadyExists")) {
204
throw Exception("Invoice already paid");
@@ -239,6 +240,7 @@ class LightningWallet {
240
final res =
241
await sdk.lnurlPay(request: LnurlPayRequest(prepareResponse: prepareResponse));
242
printV(res.payment.status.name);
243
+ return res.payment.id;
244
},
245
);
246
} else if (inputType is InputType_BitcoinAddress) {
@@ -278,8 +280,9 @@ class LightningWallet {
280
commitOverride: () async {
281
final options =
282
SendPaymentOptions.bitcoinAddress(confirmationSpeed: onchainConfirmationSpeed);
281
- await sdk.sendPayment(
283
+ final res = await sdk.sendPayment(
284
request: SendPaymentRequest(prepareResponse: prepareResponse, options: options));
285
+ return res.payment.id;
286
},
287
);
288
}
cw_bitcoin/lib/lightning/pending_lightning_transaction.dart
+3
-3
@@ -13,11 +13,11 @@ class PendingLightningTransaction with PendingTransaction {
13
final int amount;
14
final int fee;
15
final bool isSendAll;
16
- Future<void> Function() commitOverride;
16
+ Future<String> Function() commitOverride;
17
final List<void Function()> _listeners =[];
18
19
@override
20
- final String id;
20
+ String id;
21
22
@override
23
String get hex => "";
@@ -36,7 +36,7 @@ class PendingLightningTransaction with PendingTransaction {
36
37
@override
38
Future<void> commit() async {
39
- await commitOverride.call();
39
+ id = await commitOverride.call();
40
_listeners.forEach((e) => e.call());
41
}
42
cw_evm/lib/evm_chain_wallet.dart
+4
@@ -1289,6 +1289,10 @@ abstract class EVMChainWalletBase
1289
!_hasEvmTokenContractAddress(existingTxInfo)) {
1290
result[transactionModel.hash] = newTxInfo;
1291
}
1292
+
1293
+ else if (existingTxInfo.isPending) {
1294
+ result[transactionModel.hash] = newTxInfo;
1295
+ }
1296
}
1297
1298
return result;
cw_tron/lib/pending_tron_transaction.dart
+2
-1
@@ -14,6 +14,7 @@ class PendingTronTransaction with PendingTransaction {
14
required this.signedTransaction,
15
required this.fee,
16
required this.amount,
17
+ required this.id,
18
});
19
20
@override
@@ -32,7 +33,7 @@ class PendingTronTransaction with PendingTransaction {
33
String get hex => bytesToHex(signedTransaction);
34
35
@override
35
- String get id => '';
36
+ String id;
37
38
@override
39
Future<Map<String, String>> commitUR() {
cw_tron/lib/tron_client.dart
+1
@@ -341,6 +341,7 @@ class TronClient {
341
amount: totalAmount,
342
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
343
sendTransaction: sendTx,
344
+ id: rawTransaction.txID
345
);
346
}
347
lib/di.dart
+6
-4
@@ -1417,10 +1417,12 @@ Future<void> setup({
1417
}
1418
);
1419
1420
- getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, void>(
1421
- (transactionInfo, _) => TransactionDetailsModal(transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
1422
- param1: [transactionInfo, false]))
1423
- );
1420
+ getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, bool?>(
1421
+ (transactionInfo, highlightNoteField) => TransactionDetailsModal(
1422
+ transactionDetailsViewModel:
1423
+ getIt.get<TransactionDetailsViewModel>(param1: [transactionInfo, false]),
1424
+ highlightNoteField: highlightNoteField ?? false,
1425
+ ));
1426
1427
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
1428
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
lib/evm/cw_evm.dart
+40
@@ -143,6 +143,39 @@ class CWEVM extends EVM {
143
);
144
}
145
146
+ @override
147
+ TransactionInfo getTransactionInfo(
148
+
149
+ {
150
+ required String id,
151
+ required int height,
152
+ required BigInt ethAmount,
153
+ required BigInt ethFee,
154
+ required String tokenSymbol,
155
+ int exponent = 18,
156
+ required TransactionDirection direction,
157
+ required bool isPending,
158
+ required DateTime date,
159
+ required int confirmations,
160
+ String? to,
161
+ String? from,
162
+ String? evmSignatureName,
163
+ String? contractAddress,
164
+ required int chainId,
165
+ }) =>
166
+ EVMChainTransactionInfo(id: id,
167
+ height: height,
168
+ ethAmount: ethAmount,
169
+ ethFee: ethFee,
170
+ tokenSymbol: tokenSymbol,
171
+ direction: direction,
172
+ isPending: isPending,
173
+ date: date,
174
+ confirmations: confirmations,
175
+ to: to,
176
+ from: from,
177
+ chainId: chainId);
178
+
179
@override
180
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
181
@@ -432,6 +465,13 @@ class CWEVM extends EVM {
465
return null;
466
}
467
468
+
469
+ @override
470
+ BigInt getPendingTransactionFee(PendingTransaction tx) => (tx as PendingEVMChainTransaction).fee;
471
+
472
+ @override
473
+ String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingEVMChainTransaction).amount;
474
+
475
// Registry helper methods
476
static final EvmChainRegistry _registry = EvmChainRegistry();
477
lib/new-ui/pages/send_page.dart
+1
-1
@@ -1315,7 +1315,7 @@ class _NewSendPageState extends State<NewSendPage> {
1315
// height: MediaQuery.of(context).size.height*0.4,
1316
child: ModalNavigator(
1317
parentContext: modalContext,
1318
- heightMode: ModalHeightModes.autoLock,
1318
+ heightMode: ModalHeightModes.natural,
1319
rootPage: Material(
1320
child: NewPicker(
1321
title: S.of(context).set_fees,
lib/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart
+6
-1
@@ -16,9 +16,10 @@ import 'package:flutter/services.dart';
16
import 'package:flutter_mobx/flutter_mobx.dart';
17
18
class TransactionDetailsModal extends StatefulWidget {
19
- const TransactionDetailsModal({super.key, required this.transactionDetailsViewModel});
19
+ const TransactionDetailsModal({super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
20
21
final TransactionDetailsViewModel transactionDetailsViewModel;
22
+ final bool highlightNoteField;
23
24
@override
25
State<TransactionDetailsModal> createState() => _TransactionDetailsModalState();
@@ -38,6 +39,10 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
39
widget.transactionDetailsViewModel.updateNote(noteController.text);
40
}
41
});
42
+
43
+ if(widget.highlightNoteField) {
44
+ noteFocusNode.requestFocus();
45
+ }
46
}
47
48
@override
lib/new-ui/widgets/send_page/send_confirm_sheet.dart
+182
-36
@@ -2,17 +2,25 @@ import 'dart:io';
2
3
import 'package:cake_wallet/core/address_validator.dart';
4
import 'package:cake_wallet/core/utilities.dart';
5
+import 'package:cake_wallet/di.dart';
6
+import 'package:cake_wallet/entities/contact.dart';
7
+import 'package:cake_wallet/entities/contact_record.dart';
8
import 'package:cake_wallet/generated/i18n.dart';
9
import 'package:cake_wallet/new-ui/widgets/animated_dropdown.dart';
10
+import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart';
11
+import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
12
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
13
import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart';
14
+import 'package:cake_wallet/routes.dart';
15
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
16
import 'package:cake_wallet/utils/address_formatter.dart';
17
import 'package:cake_wallet/view_model/send/send_view_model.dart';
18
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
19
import 'package:cake_wallet/bitcoin/bitcoin.dart';
20
+import 'package:cw_core/cake_hive.dart';
21
import 'package:cw_core/crypto_amount_format.dart';
22
import 'package:cw_core/crypto_currency.dart';
23
+import 'package:flutter/cupertino.dart';
24
import 'package:flutter/material.dart';
25
import 'package:flutter_mobx/flutter_mobx.dart';
26
import 'package:flutter_svg/flutter_svg.dart';
@@ -31,12 +39,14 @@ class SendConfirmSheet extends StatefulWidget {
39
}
40
41
class _SendConfirmSheetState extends State<SendConfirmSheet> {
42
+ bool _committed = false;
43
+
44
void initState() {
45
super.initState();
36
- reaction((context) => widget.sendViewModel.state, (state) {
37
- if (state is TransactionCommitted) {
38
- Future.delayed(const Duration(seconds: 2), () {
39
- if (mounted) Navigator.of(context).maybePop();
46
+ reaction((_)=>widget.sendViewModel.state, (state){
47
+ if(state is TransactionCommitted) {
48
+ setState(() {
49
+ _committed = true;
50
});
51
}
52
});
@@ -60,33 +70,48 @@ class _SendConfirmSheetState extends State<SendConfirmSheet> {
70
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
71
),
72
child: SafeArea(
63
- child: Observer(
73
+ child: Observer(
74
builder: (_) {
65
- final commited = widget.sendViewModel.state is TransactionCommitted;
66
- return Stack(
67
- fit: StackFit.loose,
68
- children: [
69
- Positioned.fill(
75
+ return AnimatedSize(
76
+ duration: const Duration(milliseconds: 300),
77
+ curve: Curves.easeOutCubic,
78
+ alignment: Alignment.topCenter,
79
+ clipBehavior: Clip.hardEdge,
80
+ child: Stack(
81
+ clipBehavior: Clip.none,
82
+ children: [
83
+ Align(
84
+ alignment: Alignment.topCenter,
85
+ heightFactor: _committed ? 0.0 : 1.0,
86
child: AnimatedSlide(
71
- offset: commited ? Offset.zero : const Offset(1, 0),
72
- duration: const Duration(milliseconds: 300),
73
- curve: Curves.easeOutCubic,
74
- child: const TransactionCommitedScreen(),
75
- )),
76
- AnimatedSlide(
77
- offset: commited ? const Offset(-1, 0) : Offset.zero,
78
- duration: const Duration(milliseconds: 300),
79
- curve: Curves.easeOutCubic,
80
- child: SendTransactionDetails(
81
- sendViewModel: widget.sendViewModel,
82
- isPage: widget.isPage,
83
- title: widget.title,
84
- iconPath: widget.iconPath),
85
- ),
86
- ],
87
+ offset: _committed ? const Offset(-1, 0) : Offset.zero,
88
+ duration: const Duration(milliseconds: 300),
89
+ curve: Curves.easeOutCubic,
90
+ child: SendTransactionDetails(
91
+ sendViewModel: widget.sendViewModel,
92
+ isPage: widget.isPage,
93
+ title: widget.title,
94
+ iconPath: widget.iconPath,
95
+ ),
96
+ ),
97
+ ),
98
+ Align(
99
+ alignment: Alignment.topCenter,
100
+ heightFactor: _committed ? 1.0 : 0.0,
101
+ child: AnimatedSlide(
102
+ offset: _committed ? Offset.zero : const Offset(1, 0),
103
+ duration: const Duration(milliseconds: 300),
104
+ curve: Curves.easeOutCubic,
105
+ child: TransactionCommitedScreen(
106
+ sendViewModel: widget.sendViewModel,
107
+ ),
108
+ ),
109
+ ),
110
+ ],
111
+ ),
112
);
113
},
89
- ),
114
+ )
115
),
116
),
117
),
@@ -178,7 +203,7 @@ class SendTransactionDetails extends StatelessWidget {
203
}),
204
sendViewModel.selectedCryptoCurrency)
205
: sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
181
- formatAmount(transaction.amountFormatted), sendViewModel.selectedCryptoCurrency);
206
+ formatAmount(transaction.amountFormatted), sendViewModel.selectedCryptoCurrency).withMaxDecimals(8);
207
208
final fee =
209
"${(transaction == null) ? sendViewModel.amountParsingProxy.getDisplayCryptoStringFromBigInt(sumByBigInt(
@@ -404,29 +429,150 @@ class SendTransactionDetails extends StatelessWidget {
429
}
430
}
431
407
-class TransactionCommitedScreen extends StatelessWidget {
408
- const TransactionCommitedScreen({super.key});
432
+class TransactionCommitedScreen extends StatefulWidget {
433
+ const TransactionCommitedScreen({super.key, this.sendViewModel});
434
+
435
+ final SendViewModel? sendViewModel;
436
+
437
+ @override
438
+ State<TransactionCommitedScreen> createState() => _TransactionCommitedScreenState();
439
+}
440
+
441
+class _TransactionCommitedScreenState extends State<TransactionCommitedScreen> {
442
+ bool _isNoteButtonLoading = false;
443
444
@override
445
Widget build(BuildContext context) {
412
- return Padding(
413
- padding: const EdgeInsets.symmetric(vertical: 24.0),
414
- child: Column(
446
+ return Observer(
447
+ builder: (_) => Column(
448
spacing: 12,
416
- mainAxisSize: MainAxisSize.max,
417
- mainAxisAlignment: MainAxisAlignment.center,
449
+ mainAxisSize: MainAxisSize.min,
450
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
451
children: [
452
+ SizedBox(height: 12,),
453
Text(
454
S.of(context).transaction_sent_new,
455
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
456
),
423
- Image.asset(width: 256, height: 256, "assets/images/birthday_cake.png")
457
+ SizedBox(),
458
+ CakeImageWidget(width: 200, height: 200, imageUrl: "assets/new-ui/birthday_cake.svg"),
459
+ Padding(
460
+ padding: const EdgeInsets.symmetric(horizontal: 24.0),
461
+ child: Column(
462
+ spacing: 12,
463
+ children: [
464
+ if (widget.sendViewModel != null)
465
+ Row(
466
+ spacing: 8,
467
+ children: [
468
+ if (!(widget.sendViewModel!.checkIfAddressIsAContact(
469
+ widget.sendViewModel!.outputs.first.address)) &&
470
+ !(widget.sendViewModel!.outputs.first.isParsedAddress))
471
+ TransactionCommittedScreenActionButton(
472
+ text: S.of(context).save_contact,
473
+ iconPath: "assets/new-ui/save_contact.svg",
474
+ onTap: () {
475
+ Navigator.of(context).pushNamed(Routes.addressBookAddContact,
476
+ arguments: ContactRecord(
477
+ CakeHive.box<Contact>(Contact.boxName),
478
+ Contact(
479
+ name: "",
480
+ address: widget.sendViewModel!.outputs.first.address,
481
+ type: widget.sendViewModel!.wallet.currency)));
482
+ }),
483
+ // lightning has to be hacked in here as it doesn't get added to tx history for a few secs after committing.
484
+ if (widget.sendViewModel!.transactionInfo != null ||
485
+ widget.sendViewModel!.currency == CryptoCurrency.btcln)
486
+ TransactionCommittedScreenActionButton(
487
+ text: S.of(context).add_a_note,
488
+ iconPath: "assets/new-ui/add_note.svg",
489
+ isLoading: _isNoteButtonLoading,
490
+ onTap: () async {
491
+ setState(() {
492
+ _isNoteButtonLoading = true;
493
+ });
494
+
495
+ // for ln, we want to show the button and just have it wait until it appears in tx history
496
+ // for other currs this is instant
497
+ await asyncWhen((_) => widget.sendViewModel!.transactionInfo != null);
498
+
499
+ setState(() {
500
+ _isNoteButtonLoading = false;
501
+ });
502
+
503
+ final page = getIt.get<TransactionDetailsModal>(
504
+ param1: widget.sendViewModel!.transactionInfo!, param2: true);
505
+ showModalBottomSheet(
506
+ isScrollControlled: true,
507
+ context: context,
508
+ builder: (context) => page);
509
+ }),
510
+ ],
511
+ ),
512
+ NewPrimaryButton(
513
+ onPressed: Navigator.of(context).maybePop,
514
+ text: S.of(context).done,
515
+ color: Theme.of(context).colorScheme.primary,
516
+ textColor: Theme.of(context).colorScheme.onPrimary),
517
+ SizedBox(height: 12,)
518
+ ],
519
+ ),
520
+ ),
521
],
522
),
523
);
524
}
525
}
526
527
+class TransactionCommittedScreenActionButton extends StatelessWidget {
528
+ const TransactionCommittedScreenActionButton(
529
+ {super.key,
530
+ required this.text,
531
+ required this.iconPath,
532
+ required this.onTap,
533
+ this.isLoading = false});
534
+
535
+ final String text;
536
+ final String iconPath;
537
+ final VoidCallback onTap;
538
+ final bool isLoading;
539
+
540
+ @override
541
+ Widget build(BuildContext context) {
542
+ return Flexible(
543
+ child: GestureDetector(
544
+ onTap: onTap,
545
+ child: Container(
546
+ decoration: BoxDecoration(
547
+ borderRadius: BorderRadius.circular(16),
548
+ color: Theme.of(context).colorScheme.surfaceContainer),
549
+ child: Padding(
550
+ padding: EdgeInsets.all(16),
551
+ child: Row(
552
+ mainAxisAlignment: MainAxisAlignment.center,
553
+ spacing: 10,
554
+ children: [
555
+ isLoading
556
+ ? CupertinoActivityIndicator()
557
+ : CakeImageWidget(
558
+ imageUrl: iconPath,
559
+ width: 24,
560
+ height: 24,
561
+ colorFilter: ColorFilter.mode(
562
+ Theme.of(context).colorScheme.primary, BlendMode.srcIn),
563
+ ),
564
+ Text(
565
+ text,
566
+ style: TextStyle(
567
+ color: Theme.of(context).colorScheme.primary,
568
+ fontWeight: FontWeight.w500),
569
+ )
570
+ ],
571
+ ),
572
+ ),
573
+ )));
574
+ }
575
+}
576
577
class MultiSendAddressPreview extends StatefulWidget {
578
const MultiSendAddressPreview(
lib/solana/cw_solana.dart
+29
@@ -409,4 +409,33 @@ class CWSolana extends Solana {
409
await Future.wait(tokenChecks);
410
} catch (_) {}
411
}
412
+
413
+ @override
414
+ TransactionInfo getTransactionInfo({
415
+ required String id,
416
+ required DateTime blockTime,
417
+ required String to,
418
+ required String from,
419
+ String? tokenSymbol,
420
+ required TransactionDirection direction,
421
+ required double solAmount,
422
+ required bool isPending,
423
+ required double txFee,
424
+ }) =>
425
+ SolanaTransactionInfo(
426
+ id: id,
427
+ blockTime: blockTime,
428
+ to: to,
429
+ from: from,
430
+ tokenSymbol: tokenSymbol ?? "SOL",
431
+ direction: direction,
432
+ solAmount: solAmount,
433
+ isPending: isPending,
434
+ txFee: txFee);
435
+
436
+ @override
437
+ double getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingSolanaTransaction).amount;
438
+
439
+ @override
440
+ double getPendingTransactionFee(PendingTransaction tx) => (tx as PendingSolanaTransaction).fee;
441
}
lib/tron/cw_tron.dart
+28
@@ -153,4 +153,32 @@ class CWTron extends Tron {
153
return tronWallet.tronTokenCurrencies
154
.any((element) => element.contractAddress == contractAddress);
155
}
156
+
157
+ @override
158
+ TransactionInfo getTransactionInfo({
159
+ required String id,
160
+ required BigInt tronAmount,
161
+ int? txFee,
162
+ String? tokenSymbol,
163
+ required TransactionDirection direction,
164
+ required DateTime blockTime,
165
+ String? to,
166
+ String? from,
167
+ required bool isPending,
168
+ }) =>
169
+ TronTransactionInfo(id: id,
170
+ tronAmount: tronAmount,
171
+ tokenSymbol: tokenSymbol ?? "TRX",
172
+ txFee: txFee,
173
+ direction: direction,
174
+ blockTime: blockTime,
175
+ to: to,
176
+ from: from,
177
+ isPending: isPending);
178
+
179
+ @override
180
+ String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
181
+
182
+ @override
183
+ String getPendingTransactionFee(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
184
}
lib/view_model/dashboard/dashboard_view_model.dart
+19
-4
@@ -326,6 +326,10 @@ abstract class DashboardViewModelBase with Store {
326
appStore.wallet!.transactionHistory.transactions.values.last.confirmations +
327
1;
328
} catch (_) {}
329
+ } else {
330
+ confirmations = appStore.wallet!.transactionHistory.transactions.values
331
+ .map((item) => item.isPending)
332
+ .fold(0, (val, pending) => pending ? val + 1 : val);
333
}
334
return length * confirmations;
335
}, _transactionDisposerCallback, delay: 300);
@@ -490,16 +494,23 @@ abstract class DashboardViewModelBase with Store {
494
}
495
// printV("Transaction disposer callback (relevantTxs: ${relevantTxs.length} current: ${transactions.length})");
496
497
+ // TODO(malik) update this in a saner way during the vm refactor
498
String _txIdentityString(String txHash, TransactionDirection direction) => "${txHash}_$direction";
494
- String _txIdentityStringConfirmations(String txHash, TransactionDirection direction, int confirmations) => "${txHash}_${direction}_$confirmations";
495
-
499
+ String _txIdentityStringConfirmations(
500
+ String txHash, TransactionDirection direction, int confirmations, bool isPending) =>
501
+ "${txHash}_${direction}_${confirmations}_$isPending";
502
503
final existingKeys = transactions
498
- .map((item) => _txIdentityStringConfirmations(item.transaction.txHash, item.transaction.direction, item.transaction.confirmations))
504
+ .map((item) => _txIdentityStringConfirmations(
505
+ item.transaction.txHash,
506
+ item.transaction.direction,
507
+ item.transaction.confirmations,
508
+ item.transaction.isPending))
509
.toSet();
510
511
final newTransactions = relevantTxs
502
- .where((tx) => !existingKeys.contains(_txIdentityStringConfirmations(tx.txHash, tx.direction, tx.confirmations)))
512
+ .where((tx) => !existingKeys.contains(_txIdentityStringConfirmations(
513
+ tx.txHash, tx.direction, tx.confirmations, tx.isPending)))
514
.map((tx) => TransactionListItem(
515
transaction: tx,
516
balanceViewModel: balanceViewModel,
@@ -1291,6 +1302,10 @@ abstract class DashboardViewModelBase with Store {
1302
appStore.wallet!.transactionHistory.transactions.values.last.confirmations +
1303
1;
1304
} catch (_) {}
1305
+ } else {
1306
+ confirmations = appStore.wallet!.transactionHistory.transactions.values
1307
+ .map((item) => item.isPending)
1308
+ .fold(0, (val, pending) => pending ? val + 1 : val);
1309
}
1310
return length * confirmations;
1311
}, _transactionDisposerCallback, delay: 300);
lib/view_model/dashboard/transaction_list_item.dart
+1
-1
@@ -218,7 +218,7 @@ class TransactionListItem extends ActionListItem with Keyable {
218
case WalletType.base:
219
case WalletType.arbitrum:
220
case WalletType.bsc:
221
- final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
221
+ final asset = assetOfTransaction;
222
final price = balanceViewModel.fiatConversionStore.prices[asset];
223
amount = calculateFiatAmountRaw(
224
cryptoAmount: evm!.formatterEVMAmountToDouble(transaction: transaction),
lib/view_model/send/send_view_model.dart
+63
@@ -8,6 +8,7 @@ import 'package:cake_wallet/core/execution_state.dart';
8
import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart';
9
import 'package:cake_wallet/core/open_crypto_pay/models.dart';
10
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
11
+import 'package:cake_wallet/core/utilities.dart';
12
import 'package:cake_wallet/core/validator.dart';
13
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
14
import 'package:cake_wallet/decred/decred.dart';
@@ -59,6 +60,7 @@ import 'package:cw_core/exceptions.dart';
60
import 'package:cw_core/lnurl.dart';
61
import 'package:cw_core/pending_transaction.dart';
62
import 'package:cw_core/sync_status.dart';
63
+import 'package:cw_core/transaction_direction.dart';
64
import 'package:cw_core/transaction_info.dart';
65
import 'package:cw_core/transaction_priority.dart';
66
import 'package:cw_core/unspent_coin_type.dart';
@@ -488,6 +490,25 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
490
bool get shouldDisplayTOTP2FAForSendsToInternalWallet =>
491
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
492
493
+ @computed
494
+ TransactionInfo? get transactionInfo {
495
+ if(isEVMCompatibleChain(walletType)) {
496
+ return wallet.transactionHistory.transactions[pendingTransaction?.evmTxHashFromRawHex];
497
+ }
498
+ if (walletType == WalletType.monero) {
499
+ // monero tx history keys are in format txhash_amount_accountindex_addressindex
500
+ return wallet.transactionHistory.transactions[wallet.transactionHistory.transactions.keys
501
+ .firstWhereOrNull((item) => item.split("_").first == pendingTransaction?.id)];
502
+ }
503
+ if (walletType == WalletType.zcash) {
504
+ // zcash has a txHash getter, but the pendingTransaction id contains quotation marks for whatever reason?
505
+ final txIdNoQuotes = pendingTransaction?.id.replaceAll("\"", "");
506
+ return wallet.transactionHistory.transactions.values
507
+ .firstWhereOrNull((item) => item.txHash == txIdNoQuotes);
508
+ }
509
+ return wallet.transactionHistory.transactions[pendingTransaction?.id];
510
+ }
511
+
512
//* Still open to further optimize these checks
513
//* It works but can be made better
514
@action
@@ -1078,6 +1099,48 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1099
});
1100
}
1101
1102
+ // FIXME(malik) ideally, this should be done wallet-side.
1103
+ // it is required because evm, solana and tron don't actually save the transaction info when you send something.
1104
+ // instead, they rely on the tx to eventually get fetched at sync time, which can take a while
1105
+ if(isEVMWallet) {
1106
+ wallet.transactionHistory.addOne(evm!.getTransactionInfo(
1107
+ id: pendingTransaction!.evmTxHashFromRawHex!,
1108
+ height: 0,
1109
+ // FIXME(malik) this is even more critical given all the issues we had with decimal point parsing. with money it'll be easy
1110
+ ethAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
1111
+ ethFee: CryptoCurrency.eth.parseAmount(pendingTransaction!.feeFormattedValue),
1112
+ tokenSymbol: selectedCryptoCurrency.title,
1113
+ direction: TransactionDirection.outgoing,
1114
+ isPending: true,
1115
+ date: DateTime.now(),
1116
+ confirmations: 0,
1117
+ chainId: wallet.chainId ?? 0));
1118
+ }
1119
+
1120
+ if(walletType == WalletType.solana) {
1121
+ wallet.transactionHistory.addOne(solana!.getTransactionInfo(
1122
+ id: pendingTransaction!.id,
1123
+ blockTime: DateTime.now(),
1124
+ tokenSymbol: selectedCryptoCurrency.title,
1125
+ to: "",
1126
+ from: "",
1127
+ direction: TransactionDirection.outgoing,
1128
+ solAmount: solana!.getPendingTransactionAmount(pendingTransaction!),
1129
+ isPending: true,
1130
+ txFee: solana!.getPendingTransactionFee(pendingTransaction!)));
1131
+ }
1132
+
1133
+ if (walletType == WalletType.tron) {
1134
+ wallet.transactionHistory.addOne(tron!.getTransactionInfo(
1135
+ id: pendingTransaction!.id,
1136
+ blockTime: DateTime.now(),
1137
+ tokenSymbol: selectedCryptoCurrency.title,
1138
+ direction: TransactionDirection.outgoing,
1139
+ tronAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
1140
+ isPending: true,
1141
+ txFee: CryptoCurrency.trx.parseAmount(outputs.first.estimatedFee).toInt()));
1142
+ }
1143
+
1144
if (pendingTransaction!.id.isNotEmpty) {
1145
_addTransactionDescription();
1146
}
lib/view_model/transaction_details_view_model.dart
+2
@@ -127,6 +127,7 @@ class TxDetailRowDefinition {
127
.firstOrNull ??
128
"";
129
case WalletType.tron:
130
+ if(vm.transactionInfo.to != null)
131
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
132
default:
133
break;
@@ -315,6 +316,7 @@ abstract class TransactionDetailsViewModelBase with Store {
316
TransactionPriority? transactionPriority;
317
318
CryptoCurrency get transactionAsset {
319
+
320
if (isEVMCompatibleChain(wallet.type)) {
321
return evm!.assetOfTransaction(wallet, transactionInfo);
322
}
res/pictures/add_note.svg
new
+3
@@ -0,0 +1,3 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M15.275 12.475L11.525 8.7L14.3 5.95L13.575 5.225L8.1 10.7L6.7 9.3L12.15 3.825C12.55 3.425 13.0208 3.225 13.5625 3.225C14.1042 3.225 14.575 3.425 14.975 3.825L15.7 4.55L16.95 3.3C17.15 3.1 17.3875 3 17.6625 3C17.9375 3 18.175 3.1 18.375 3.3L20.7 5.625C20.9 5.825 21 6.0625 21 6.3375C21 6.6125 20.9 6.85 20.7 7.05L15.275 12.475ZM6.75 21H3V17.25L10.1 10.125L13.875 13.875L6.75 21Z" fill="white"/>
3
+</svg>
res/pictures/birthday_cake.svg
new
+1
@@ -0,0 +1 @@
1
+<svg xmlns="http://www.w3.org/2000/svg" width="195" height="180" fill="none" viewBox="0 0 195 180"><path stroke="#d0d0f6" stroke-linecap="round" stroke-width="3" d="M63.5 19v11"/><path stroke="#d0d0e5" stroke-linecap="round" stroke-width="3" d="M63.5 19v11"/><path stroke="#e3e3f9" stroke-linecap="round" stroke-width="3" d="M63.5 19v11"/><path stroke="#d0d0f6" stroke-linecap="round" stroke-width="3" d="M97 19v11"/><path stroke="#d0d0e5" stroke-linecap="round" stroke-width="3" d="M97 19v11"/><path stroke="#e3e3f9" stroke-linecap="round" stroke-width="3" d="M97 19v11"/><path stroke="#d0d0f6" stroke-linecap="round" stroke-width="3" d="M131 19v11"/><path stroke="#d0d0e5" stroke-linecap="round" stroke-width="3" d="M131 19v11"/><path stroke="#e3e3f9" stroke-linecap="round" stroke-width="3" d="M131 19v11"/><g filter="url(#a)"><path fill="#14c846" d="M136.67 51.991h-11.485a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.801-1.79 1.79-1.79h11.485a1.79 1.79 0 0 1 1.791 1.79v27.436a1.79 1.79 0 0 1-1.791 1.79"/><path fill="url(#b)" d="M136.67 51.991h-11.485a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.801-1.79 1.79-1.79h11.485a1.79 1.79 0 0 1 1.791 1.79v27.436a1.79 1.79 0 0 1-1.791 1.79"/><path fill="#14c846" d="M102.994 51.991H91.509a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.801-1.79 1.79-1.79h11.485a1.79 1.79 0 0 1 1.791 1.79v27.436a1.79 1.79 0 0 1-1.791 1.79"/><path fill="url(#c)" d="M102.994 51.991H91.509a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.801-1.79 1.79-1.79h11.485a1.79 1.79 0 0 1 1.791 1.79v27.436a1.79 1.79 0 0 1-1.791 1.79"/><path fill="#14c846" d="M69.319 51.991H57.832a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.802-1.79 1.79-1.79H69.32c.988 0 1.79.801 1.79 1.79v27.436a1.79 1.79 0 0 1-1.79 1.79"/><path fill="url(#d)" d="M69.319 51.991H57.832a1.79 1.79 0 0 1-1.79-1.79V22.765c0-.989.802-1.79 1.79-1.79H69.32c.988 0 1.79.801 1.79 1.79v27.436a1.79 1.79 0 0 1-1.79 1.79"/></g><path fill="#fc914a" d="M135.674 5.237c-1.466-2.953-4.301-7.863-7.557-3.517-2.472 3.3-4.866 9.324-1.034 12.558 2.06 1.738 5.624 1.741 7.688 0 2.73-2.303 2.313-6.202.903-9.04"/><path fill="url(#e)" d="M135.674 5.237c-1.466-2.953-4.301-7.863-7.557-3.517-2.472 3.3-4.866 9.324-1.034 12.558 2.06 1.738 5.624 1.741 7.688 0 2.73-2.303 2.313-6.202.903-9.04"/><path fill="#fc914a" d="M101.999 5.237c-1.467-2.953-4.301-7.863-7.558-3.517-2.472 3.3-4.866 9.324-1.034 12.558 2.06 1.738 5.624 1.741 7.688 0 2.73-2.303 2.313-6.202.904-9.04"/><path fill="url(#f)" d="M101.999 5.237c-1.467-2.953-4.301-7.863-7.558-3.517-2.472 3.3-4.866 9.324-1.034 12.558 2.06 1.738 5.624 1.741 7.688 0 2.73-2.303 2.313-6.202.904-9.04"/><path fill="#fc914a" d="M68.323 5.237c-1.466-2.953-4.301-7.863-7.557-3.517-2.472 3.3-4.867 9.324-1.034 12.558 2.06 1.738 5.623 1.741 7.687 0 2.73-2.303 2.313-6.202.904-9.04"/><path fill="url(#g)" d="M68.323 5.237c-1.466-2.953-4.301-7.863-7.557-3.517-2.472 3.3-4.867 9.324-1.034 12.558 2.06 1.738 5.623 1.741 7.687 0 2.73-2.303 2.313-6.202.904-9.04"/><path fill="url(#h)" d="M66.339 8.361c-.877-1.895-2.572-5.046-4.52-2.257-1.477 2.118-2.91 5.984-.618 8.059 1.232 1.115 3.363 1.117 4.598 0 1.632-1.478 1.382-3.98.54-5.802"/><path fill="url(#i)" d="M100.339 8.361c-.877-1.895-2.572-5.046-4.52-2.257-1.478 2.118-2.91 5.984-.618 8.059 1.232 1.115 3.363 1.117 4.598 0 1.632-1.478 1.382-3.98.54-5.802"/><path fill="url(#j)" d="M133.339 8.361c-.877-1.895-2.572-5.046-4.519-2.257-1.479 2.118-2.91 5.984-.619 8.059 1.232 1.115 3.363 1.117 4.597 0 1.633-1.478 1.383-3.98.541-5.802"/><g filter="url(#k)"><path fill="#a06047" d="M163.293 108.917H31.207V55.203c0-5.728 4.644-10.372 10.372-10.372h111.343c5.728 0 10.371 4.644 10.371 10.372z"/><path fill="url(#l)" d="M163.293 108.917H31.207V55.203c0-5.728 4.644-10.372 10.372-10.372h111.343c5.728 0 10.371 4.644 10.371 10.372z"/></g><path fill="#ffd57b" d="M163.292 78.528H31.207v12.888h132.085z"/><g filter="url(#m)"><path fill="#2194ff" d="M163.293 55.203v5.484c-2.451 2.01-4.795 4.11-9.69 4.11-9.391 0-9.391-7.733-18.783-7.733s-9.392 7.733-18.788 7.733c-9.392 0-9.392-7.733-18.784-7.733s-9.392 7.733-18.784 7.733-9.392-7.733-18.783-7.733-9.392 7.733-18.784 7.733c-4.892 0-7.235-2.1-9.686-4.11v-5.484c0-5.729 4.644-10.373 10.373-10.373H152.92c5.729 0 10.373 4.644 10.373 10.373"/><path fill="url(#n)" d="M163.293 55.203v5.484c-2.451 2.01-4.795 4.11-9.69 4.11-9.391 0-9.391-7.733-18.783-7.733s-9.392 7.733-18.788 7.733c-9.392 0-9.392-7.733-18.784-7.733s-9.392 7.733-18.784 7.733-9.392-7.733-18.783-7.733-9.392 7.733-18.784 7.733c-4.892 0-7.235-2.1-9.686-4.11v-5.484c0-5.729 4.644-10.373 10.373-10.373H152.92c5.729 0 10.373 4.644 10.373 10.373"/><path fill="url(#o)" d="M163.293 55.203v5.484c-2.451 2.01-4.795 4.11-9.69 4.11-9.391 0-9.391-7.733-18.783-7.733s-9.392 7.733-18.788 7.733c-9.392 0-9.392-7.733-18.784-7.733s-9.392 7.733-18.784 7.733-9.392-7.733-18.783-7.733-9.392 7.733-18.784 7.733c-4.892 0-7.235-2.1-9.686-4.11v-5.484c0-5.729 4.644-10.373 10.373-10.373H152.92c5.729 0 10.373 4.644 10.373 10.373"/></g><g filter="url(#p)"><path fill="url(#q)" d="M178.503 172.999H16v-53.713c0-5.728 4.644-10.372 10.372-10.372H168.13c5.728 0 10.371 4.644 10.371 10.372z"/></g><path fill="#ffac82" d="M178.501 142.615H16v12.888h162.501z"/><g filter="url(#r)"><path fill="#2194ff" d="M178.504 120.114v4.657c-3.016 2.01-5.9 4.11-11.92 4.11-11.558 0-11.558-7.733-23.115-7.733-11.553 0-11.553 7.733-23.11 7.733-11.554 0-11.554-7.733-23.11-7.733-11.554 0-11.554 7.733-23.111 7.733-11.554 0-11.554-7.733-23.11-7.733-11.554 0-11.554 7.733-23.111 7.733-6.02 0-8.901-2.099-11.917-4.11v-5.484c0-5.729 4.644-10.373 10.373-10.373h140.932c6.185 0 11.199 3.586 11.199 11.2"/><path fill="url(#s)" d="M178.504 120.114v4.657c-3.016 2.01-5.9 4.11-11.92 4.11-11.558 0-11.558-7.733-23.115-7.733-11.553 0-11.553 7.733-23.11 7.733-11.554 0-11.554-7.733-23.11-7.733-11.554 0-11.554 7.733-23.111 7.733-11.554 0-11.554-7.733-23.11-7.733-11.554 0-11.554 7.733-23.111 7.733-6.02 0-8.901-2.099-11.917-4.11v-5.484c0-5.729 4.644-10.373 10.373-10.373h140.932c6.185 0 11.199 3.586 11.199 11.2"/></g><g filter="url(#t)"><rect width="195" height="6" y="173" fill="#d9d9d9" rx="3"/><rect width="195" height="6" y="173" fill="url(#u)" rx="3"/></g><defs><linearGradient id="b" x1="131.704" x2="126.405" y1="22.935" y2="42.767" gradientUnits="userSpaceOnUse"><stop stop-color="#efeff8"/><stop offset="1" stop-color="#c3c3f6"/></linearGradient><linearGradient id="c" x1="98.028" x2="92.729" y1="22.935" y2="42.767" gradientUnits="userSpaceOnUse"><stop stop-color="#efeff8"/><stop offset="1" stop-color="#c3c3f6"/></linearGradient><linearGradient id="d" x1="64.353" x2="59.053" y1="22.935" y2="42.767" gradientUnits="userSpaceOnUse"><stop stop-color="#efeff8"/><stop offset="1" stop-color="#c3c3f6"/></linearGradient><linearGradient id="e" x1="128.521" x2="121.868" y1="-2" y2="2.666" gradientUnits="userSpaceOnUse"><stop stop-color="#fe9c12"/><stop offset="1" stop-color="#ff6a00"/></linearGradient><linearGradient id="f" x1="94.845" x2="88.193" y1="-2" y2="2.666" gradientUnits="userSpaceOnUse"><stop stop-color="#fe9c12"/><stop offset="1" stop-color="#ff6a00"/></linearGradient><linearGradient id="g" x1="61.169" x2="54.517" y1="-2" y2="2.666" gradientUnits="userSpaceOnUse"><stop stop-color="#fe9c12"/><stop offset="1" stop-color="#ff6a00"/></linearGradient><linearGradient id="h" x1="63.5" x2="63.5" y1="5" y2="15" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="i" x1="97.5" x2="97.5" y1="5" y2="15" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="j" x1="130.5" x2="130.5" y1="5" y2="15" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="l" x1="48.883" x2="169.302" y1="47.846" y2="112.452" gradientUnits="userSpaceOnUse"><stop offset=".354" stop-color="#ffc414"/><stop offset="1" stop-color="#ff9447"/></linearGradient><linearGradient id="n" x1="51.937" x2="136.726" y1="42.916" y2="82.605" gradientUnits="userSpaceOnUse"><stop offset=".128" stop-color="#61c5ff"/><stop offset="1" stop-color="#2a92fa"/></linearGradient><linearGradient id="o" x1="13.998" x2="147.998" y1="45" y2="72" gradientUnits="userSpaceOnUse"><stop offset=".128" stop-color="#61c5ff"/><stop offset="1" stop-color="#2a92fa"/></linearGradient><linearGradient id="q" x1="58.776" x2="156.591" y1="110.019" y2="245.711" gradientUnits="userSpaceOnUse"><stop stop-color="#fe6412"/><stop offset="1" stop-color="#eb4600"/></linearGradient><linearGradient id="s" x1="41.5" x2="137" y1="107" y2="162" gradientUnits="userSpaceOnUse"><stop offset=".128" stop-color="#61c5ff"/><stop offset="1" stop-color="#2a92fa"/></linearGradient><linearGradient id="u" x1="198" x2="84.121" y1="176" y2="273.876" gradientUnits="userSpaceOnUse"><stop stop-color="#c3c3f6"/><stop offset=".553" stop-color="#efeff8"/><stop offset="1" stop-color="#c3c3f6"/></linearGradient><filter id="a" width="82.418" height="34.017" x="56.043" y="17.975" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-8"/><feGaussianBlur stdDeviation="1.5"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/><feBlend in2="shape" result="effect1_innerShadow_19634_811896"/></filter><filter id="k" width="132.086" height="64.086" x="31.207" y="44.831" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-2"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 0"/><feBlend in2="shape" result="effect1_innerShadow_19634_811896"/></filter><filter id="m" width="136.882" height="27.067" x="28.811" y="42.13" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="2"/><feGaussianBlur stdDeviation="1.2"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0.913462 0 0 0 0 0.578452 0 0 0 0 0.0395248 0 0 0 0.4 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_19634_811896"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_19634_811896" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-3"/><feGaussianBlur stdDeviation="1.35"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.610577 0 0 0 0 0.83125 0 0 0 0 1 0 0 0 1 0"/><feBlend in2="shape" result="effect2_innerShadow_19634_811896"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-1"/><feGaussianBlur stdDeviation="1.35"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.107064 0 0 0 0 0.531767 0 0 0 0 0.927885 0 0 0 1 0"/><feBlend in2="effect2_innerShadow_19634_811896" result="effect3_innerShadow_19634_811896"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.663462 0 0 0 0 0.859615 0 0 0 0 1 0 0 0 1 0"/><feBlend in2="effect3_innerShadow_19634_811896" result="effect4_innerShadow_19634_811896"/></filter><filter id="p" width="162.504" height="64.085" x="16" y="108.914" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-2"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/><feBlend in2="shape" result="effect1_innerShadow_19634_811896"/></filter><filter id="r" width="167.304" height="27.067" x="13.6" y="106.214" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="2"/><feGaussianBlur stdDeviation="1.2"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0.860577 0 0 0 0 0.291016 0 0 0 0 0 0 0 0 0.5 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_19634_811896"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_19634_811896" result="shape"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-3"/><feGaussianBlur stdDeviation="1.35"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.610577 0 0 0 0 0.83125 0 0 0 0 1 0 0 0 1 0"/><feBlend in2="shape" result="effect2_innerShadow_19634_811896"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-1"/><feGaussianBlur stdDeviation="1.35"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.107064 0 0 0 0 0.531767 0 0 0 0 0.927885 0 0 0 1 0"/><feBlend in2="effect2_innerShadow_19634_811896" result="effect3_innerShadow_19634_811896"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1"/><feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/><feColorMatrix values="0 0 0 0 0.663462 0 0 0 0 0.859615 0 0 0 0 1 0 0 0 1 0"/><feBlend in2="effect3_innerShadow_19634_811896" result="effect4_innerShadow_19634_811896"/></filter><filter id="t" width="195" height="7" x="0" y="173" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="1"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0.476493 0 0 0 0 0.476493 0 0 0 0 0.639423 0 0 0 1 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_19634_811896"/><feBlend in="SourceGraphic" in2="effect1_dropShadow_19634_811896" result="shape"/></filter></defs></svg>
\ No newline at end of file
res/pictures/save_contact.svg
new
+3
@@ -0,0 +1,3 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M9.175 10.825C8.39167 10.0417 8 9.1 8 8C8 6.9 8.39167 5.95833 9.175 5.175C9.95833 4.39167 10.9 4 12 4C13.1 4 14.0417 4.39167 14.825 5.175C15.6083 5.95833 16 6.9 16 8C16 9.1 15.6083 10.0417 14.825 10.825C14.0417 11.6083 13.1 12 12 12C10.9 12 9.95833 11.6083 9.175 10.825ZM4 20V17.2C4 16.6333 4.14583 16.1125 4.4375 15.6375C4.72917 15.1625 5.11667 14.8 5.6 14.55C6.63333 14.0333 7.68333 13.6458 8.75 13.3875C9.81667 13.1292 10.9 13 12 13C13.1 13 14.1833 13.1292 15.25 13.3875C16.3167 13.6458 17.3667 14.0333 18.4 14.55C18.8833 14.8 19.2708 15.1625 19.5625 15.6375C19.8542 16.1125 20 16.6333 20 17.2V20H4Z" fill="white"/>
3
+</svg>
res/values/strings_en.arb
+1
@@ -910,6 +910,7 @@
910
"save": "Save",
911
"save_backup_password": "Please make sure that you have saved your backup password. You will not be able to import your backup files without it.",
912
"save_backup_password_alert": "Save backup password",
913
+ "save_contact": "Save Contact",
914
"save_to_downloads": "Save to Downloads",
915
"saved_the_trade_id": "I've saved the trade ID",
916
"savings": "Savings",
tool/configure.dart
+59
@@ -938,6 +938,7 @@ import 'package:cw_core/wallet_credentials.dart';
938
import 'package:cw_core/wallet_info.dart';
939
import 'package:cw_core/wallet_service.dart';
940
import 'package:cw_core/spl_token.dart';
941
+import 'package:cw_core/transaction_direction.dart';
942
943
""";
944
const solanaCWHeaders = """
@@ -1033,6 +1034,23 @@ abstract class Solana {
1034
});
1035
1036
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
1037
+
1038
+ TransactionInfo getTransactionInfo(
1039
+{
1040
+ required String id,
1041
+ required DateTime blockTime,
1042
+ required String to,
1043
+ required String from,
1044
+ String? tokenSymbol,
1045
+ required TransactionDirection direction,
1046
+ required double solAmount,
1047
+ required bool isPending,
1048
+ required double txFee,
1049
+ }
1050
+ );
1051
+
1052
+ double getPendingTransactionAmount(PendingTransaction tx);
1053
+ double getPendingTransactionFee(PendingTransaction tx);
1054
}
1055
1056
class JupiterSwapFailedException implements Exception {
@@ -1076,6 +1094,7 @@ Future<void> generateTron(bool hasImplementation) async {
1094
const tronCommonHeaders = """
1095
import 'package:cake_wallet/view_model/send/output.dart';
1096
import 'package:cw_core/crypto_currency.dart';
1097
+import 'package:cw_core/pending_transaction.dart';
1098
import 'package:cw_core/output_info.dart';
1099
import 'package:cw_core/transaction_info.dart';
1100
import 'package:cw_core/wallet_base.dart';
@@ -1084,6 +1103,7 @@ import 'package:cw_core/wallet_info.dart';
1103
import 'package:cw_core/wallet_service.dart';
1104
import 'package:cw_core/tron_token.dart';
1105
import 'package:hive/hive.dart';
1106
+import 'package:cw_core/transaction_direction.dart';
1107
1108
""";
1109
const tronCWHeaders = """
@@ -1091,6 +1111,7 @@ import 'package:cw_evm/evm_chain_mnemonics.dart';
1111
import 'package:cw_tron/tron_transaction_credentials.dart';
1112
import 'package:cw_tron/tron_transaction_info.dart';
1113
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
1114
+import 'package:cw_tron/pending_tron_transaction.dart';
1115
1116
import 'package:cw_tron/tron_client.dart';
1117
import 'package:cw_tron/tron_wallet.dart';
@@ -1131,6 +1152,19 @@ abstract class Tron {
1152
List<String> getDefaultTokenContractAddresses();
1153
List<String> getDefaultTokenSymbols();
1154
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
1155
+ TransactionInfo getTransactionInfo({
1156
+ required String id,
1157
+ required BigInt tronAmount,
1158
+ int? txFee,
1159
+ String? tokenSymbol,
1160
+ required TransactionDirection direction,
1161
+ required DateTime blockTime,
1162
+ String? to,
1163
+ String? from,
1164
+ required bool isPending,
1165
+ });
1166
+ String getPendingTransactionFee(PendingTransaction tx);
1167
+ String getPendingTransactionAmount(PendingTransaction tx);
1168
}
1169
""";
1170
@@ -1361,6 +1395,7 @@ Future<void> generateEVM(bool hasImplementation) async {
1395
import 'dart:math' as math;
1396
import 'package:cake_wallet/core/utilities.dart';
1397
import 'package:cake_wallet/view_model/send/output.dart';
1398
+import 'package:cw_core/pending_transaction.dart';
1399
import 'package:cw_core/crypto_currency.dart';
1400
import 'package:cw_core/erc20_token.dart';
1401
import 'package:cw_core/hardware/hardware_account_data.dart';
@@ -1379,6 +1414,7 @@ import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
1414
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
1415
import 'package:trezor_connect/trezor_connect.dart' as trezor;
1416
import 'package:web3dart/web3dart.dart';
1417
+import 'package:cw_core/transaction_direction.dart';
1418
1419
""";
1420
const evmCWHeaders = """
@@ -1389,6 +1425,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
1425
import 'package:cake_wallet/store/settings_store.dart';
1426
import 'package:cw_evm/utils/evm_chain_formatter.dart';
1427
import 'package:cw_evm/evm_chain_mnemonics.dart';
1428
+import 'package:cw_evm/pending_evm_chain_transaction.dart';
1429
import 'package:cw_evm/evm_chain_registry.dart';
1430
import 'package:cw_evm/evm_erc20_balance.dart';
1431
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
@@ -1608,6 +1645,28 @@ abstract class EVM {
1645
WalletBase wallet,
1646
TransactionPriority priority,
1647
);
1648
+
1649
+ TransactionInfo getTransactionInfo(
1650
+ {
1651
+ required String id,
1652
+ required int height,
1653
+ required BigInt ethAmount,
1654
+ required BigInt ethFee,
1655
+ required String tokenSymbol,
1656
+ int exponent = 18,
1657
+ required TransactionDirection direction,
1658
+ required bool isPending,
1659
+ required DateTime date,
1660
+ required int confirmations,
1661
+ String? to,
1662
+ String? from,
1663
+ String? evmSignatureName,
1664
+ String? contractAddress,
1665
+ required int chainId,
1666
+ }
1667
+ );
1668
+ BigInt getPendingTransactionFee(PendingTransaction tx);
1669
+ String getPendingTransactionAmount(PendingTransaction tx);
1670
1671
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
1672
}