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 }