mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
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
This commit is contained in:
@@ -13,19 +13,6 @@ analyzer:
|
||||
cw_monero/ios/External/**,
|
||||
cw_shared_external/**,
|
||||
shared_external/**,
|
||||
lib/bitcoin/cw_bitcoin.dart,
|
||||
lib/bitcoin_cash/cw_bitcoin_cash.dart,
|
||||
lib/ethereum/cw_ethereum.dart,
|
||||
lib/haven/cw_haven.dart,
|
||||
lib/monero/cw_monero.dart,
|
||||
lib/nano/cw_nano.dart,
|
||||
lib/polygon/cw_polygon.dart,
|
||||
lib/solana/cw_solana.dart,
|
||||
lib/tron/cw_tron.dart,
|
||||
lib/wownero/cw_wownero.dart,
|
||||
lib/zano/cw_zano.dart,
|
||||
lib/base/cw_base.dart,
|
||||
lib/arbitrum/cw_arbitrum.dart,
|
||||
]
|
||||
language:
|
||||
strict-casts: true
|
||||
|
||||
@@ -198,6 +198,7 @@ class LightningWallet {
|
||||
final res = await sdk.sendPayment(
|
||||
request: SendPaymentRequest(prepareResponse: prepareResponse));
|
||||
printV(res.payment.status.name);
|
||||
return res.payment.id;
|
||||
} on SdkError_SparkError catch (e) {
|
||||
if (e.field0.contains("AlreadyExists")) {
|
||||
throw Exception("Invoice already paid");
|
||||
@@ -239,6 +240,7 @@ class LightningWallet {
|
||||
final res =
|
||||
await sdk.lnurlPay(request: LnurlPayRequest(prepareResponse: prepareResponse));
|
||||
printV(res.payment.status.name);
|
||||
return res.payment.id;
|
||||
},
|
||||
);
|
||||
} else if (inputType is InputType_BitcoinAddress) {
|
||||
@@ -278,8 +280,9 @@ class LightningWallet {
|
||||
commitOverride: () async {
|
||||
final options =
|
||||
SendPaymentOptions.bitcoinAddress(confirmationSpeed: onchainConfirmationSpeed);
|
||||
await sdk.sendPayment(
|
||||
final res = await sdk.sendPayment(
|
||||
request: SendPaymentRequest(prepareResponse: prepareResponse, options: options));
|
||||
return res.payment.id;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ class PendingLightningTransaction with PendingTransaction {
|
||||
final int amount;
|
||||
final int fee;
|
||||
final bool isSendAll;
|
||||
Future<void> Function() commitOverride;
|
||||
Future<String> Function() commitOverride;
|
||||
final List<void Function()> _listeners =[];
|
||||
|
||||
@override
|
||||
final String id;
|
||||
String id;
|
||||
|
||||
@override
|
||||
String get hex => "";
|
||||
@@ -36,7 +36,7 @@ class PendingLightningTransaction with PendingTransaction {
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
await commitOverride.call();
|
||||
id = await commitOverride.call();
|
||||
_listeners.forEach((e) => e.call());
|
||||
}
|
||||
|
||||
|
||||
@@ -1289,6 +1289,10 @@ abstract class EVMChainWalletBase
|
||||
!_hasEvmTokenContractAddress(existingTxInfo)) {
|
||||
result[transactionModel.hash] = newTxInfo;
|
||||
}
|
||||
|
||||
else if (existingTxInfo.isPending) {
|
||||
result[transactionModel.hash] = newTxInfo;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -14,6 +14,7 @@ class PendingTronTransaction with PendingTransaction {
|
||||
required this.signedTransaction,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -32,7 +33,7 @@ class PendingTronTransaction with PendingTransaction {
|
||||
String get hex => bytesToHex(signedTransaction);
|
||||
|
||||
@override
|
||||
String get id => '';
|
||||
String id;
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
|
||||
@@ -341,6 +341,7 @@ class TronClient {
|
||||
amount: totalAmount,
|
||||
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
|
||||
sendTransaction: sendTx,
|
||||
id: rawTransaction.txID
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -1417,10 +1417,12 @@ Future<void> setup({
|
||||
}
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, void>(
|
||||
(transactionInfo, _) => TransactionDetailsModal(transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
|
||||
param1: [transactionInfo, false]))
|
||||
);
|
||||
getIt.registerFactoryParam<TransactionDetailsModal, TransactionInfo, bool?>(
|
||||
(transactionInfo, highlightNoteField) => TransactionDetailsModal(
|
||||
transactionDetailsViewModel:
|
||||
getIt.get<TransactionDetailsViewModel>(param1: [transactionInfo, false]),
|
||||
highlightNoteField: highlightNoteField ?? false,
|
||||
));
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
|
||||
|
||||
@@ -143,6 +143,39 @@ class CWEVM extends EVM {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionInfo getTransactionInfo(
|
||||
|
||||
{
|
||||
required String id,
|
||||
required int height,
|
||||
required BigInt ethAmount,
|
||||
required BigInt ethFee,
|
||||
required String tokenSymbol,
|
||||
int exponent = 18,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
String? to,
|
||||
String? from,
|
||||
String? evmSignatureName,
|
||||
String? contractAddress,
|
||||
required int chainId,
|
||||
}) =>
|
||||
EVMChainTransactionInfo(id: id,
|
||||
height: height,
|
||||
ethAmount: ethAmount,
|
||||
ethFee: ethFee,
|
||||
tokenSymbol: tokenSymbol,
|
||||
direction: direction,
|
||||
isPending: isPending,
|
||||
date: date,
|
||||
confirmations: confirmations,
|
||||
to: to,
|
||||
from: from,
|
||||
chainId: chainId);
|
||||
|
||||
@override
|
||||
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
|
||||
|
||||
@@ -432,6 +465,13 @@ class CWEVM extends EVM {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
BigInt getPendingTransactionFee(PendingTransaction tx) => (tx as PendingEVMChainTransaction).fee;
|
||||
|
||||
@override
|
||||
String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingEVMChainTransaction).amount;
|
||||
|
||||
// Registry helper methods
|
||||
static final EvmChainRegistry _registry = EvmChainRegistry();
|
||||
|
||||
|
||||
@@ -1315,7 +1315,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
// height: MediaQuery.of(context).size.height*0.4,
|
||||
child: ModalNavigator(
|
||||
parentContext: modalContext,
|
||||
heightMode: ModalHeightModes.autoLock,
|
||||
heightMode: ModalHeightModes.natural,
|
||||
rootPage: Material(
|
||||
child: NewPicker(
|
||||
title: S.of(context).set_fees,
|
||||
|
||||
@@ -16,9 +16,10 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class TransactionDetailsModal extends StatefulWidget {
|
||||
const TransactionDetailsModal({super.key, required this.transactionDetailsViewModel});
|
||||
const TransactionDetailsModal({super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
|
||||
|
||||
final TransactionDetailsViewModel transactionDetailsViewModel;
|
||||
final bool highlightNoteField;
|
||||
|
||||
@override
|
||||
State<TransactionDetailsModal> createState() => _TransactionDetailsModalState();
|
||||
@@ -38,6 +39,10 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
widget.transactionDetailsViewModel.updateNote(noteController.text);
|
||||
}
|
||||
});
|
||||
|
||||
if(widget.highlightNoteField) {
|
||||
noteFocusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,17 +2,25 @@ import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/animated_dropdown.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/utils/address_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -31,12 +39,14 @@ class SendConfirmSheet extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SendConfirmSheetState extends State<SendConfirmSheet> {
|
||||
bool _committed = false;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
reaction((context) => widget.sendViewModel.state, (state) {
|
||||
if (state is TransactionCommitted) {
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (mounted) Navigator.of(context).maybePop();
|
||||
reaction((_)=>widget.sendViewModel.state, (state){
|
||||
if(state is TransactionCommitted) {
|
||||
setState(() {
|
||||
_committed = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -60,33 +70,48 @@ class _SendConfirmSheetState extends State<SendConfirmSheet> {
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Observer(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final commited = widget.sendViewModel.state is TransactionCommitted;
|
||||
return Stack(
|
||||
fit: StackFit.loose,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
return AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutCubic,
|
||||
alignment: Alignment.topCenter,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _committed ? 0.0 : 1.0,
|
||||
child: AnimatedSlide(
|
||||
offset: commited ? Offset.zero : const Offset(1, 0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: const TransactionCommitedScreen(),
|
||||
)),
|
||||
AnimatedSlide(
|
||||
offset: commited ? const Offset(-1, 0) : Offset.zero,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: SendTransactionDetails(
|
||||
sendViewModel: widget.sendViewModel,
|
||||
isPage: widget.isPage,
|
||||
title: widget.title,
|
||||
iconPath: widget.iconPath),
|
||||
),
|
||||
],
|
||||
offset: _committed ? const Offset(-1, 0) : Offset.zero,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: SendTransactionDetails(
|
||||
sendViewModel: widget.sendViewModel,
|
||||
isPage: widget.isPage,
|
||||
title: widget.title,
|
||||
iconPath: widget.iconPath,
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: _committed ? 1.0 : 0.0,
|
||||
child: AnimatedSlide(
|
||||
offset: _committed ? Offset.zero : const Offset(1, 0),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: TransactionCommitedScreen(
|
||||
sendViewModel: widget.sendViewModel,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -178,7 +203,7 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
}),
|
||||
sendViewModel.selectedCryptoCurrency)
|
||||
: sendViewModel.amountParsingProxy.getDisplayCryptoAmount(
|
||||
formatAmount(transaction.amountFormatted), sendViewModel.selectedCryptoCurrency);
|
||||
formatAmount(transaction.amountFormatted), sendViewModel.selectedCryptoCurrency).withMaxDecimals(8);
|
||||
|
||||
final fee =
|
||||
"${(transaction == null) ? sendViewModel.amountParsingProxy.getDisplayCryptoStringFromBigInt(sumByBigInt(
|
||||
@@ -404,29 +429,150 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionCommitedScreen extends StatelessWidget {
|
||||
const TransactionCommitedScreen({super.key});
|
||||
class TransactionCommitedScreen extends StatefulWidget {
|
||||
const TransactionCommitedScreen({super.key, this.sendViewModel});
|
||||
|
||||
final SendViewModel? sendViewModel;
|
||||
|
||||
@override
|
||||
State<TransactionCommitedScreen> createState() => _TransactionCommitedScreenState();
|
||||
}
|
||||
|
||||
class _TransactionCommitedScreenState extends State<TransactionCommitedScreen> {
|
||||
bool _isNoteButtonLoading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: Column(
|
||||
return Observer(
|
||||
builder: (_) => Column(
|
||||
spacing: 12,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
SizedBox(height: 12,),
|
||||
Text(
|
||||
S.of(context).transaction_sent_new,
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600),
|
||||
),
|
||||
Image.asset(width: 256, height: 256, "assets/images/birthday_cake.png")
|
||||
SizedBox(),
|
||||
CakeImageWidget(width: 200, height: 200, imageUrl: "assets/new-ui/birthday_cake.svg"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (widget.sendViewModel != null)
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (!(widget.sendViewModel!.checkIfAddressIsAContact(
|
||||
widget.sendViewModel!.outputs.first.address)) &&
|
||||
!(widget.sendViewModel!.outputs.first.isParsedAddress))
|
||||
TransactionCommittedScreenActionButton(
|
||||
text: S.of(context).save_contact,
|
||||
iconPath: "assets/new-ui/save_contact.svg",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(Routes.addressBookAddContact,
|
||||
arguments: ContactRecord(
|
||||
CakeHive.box<Contact>(Contact.boxName),
|
||||
Contact(
|
||||
name: "",
|
||||
address: widget.sendViewModel!.outputs.first.address,
|
||||
type: widget.sendViewModel!.wallet.currency)));
|
||||
}),
|
||||
// lightning has to be hacked in here as it doesn't get added to tx history for a few secs after committing.
|
||||
if (widget.sendViewModel!.transactionInfo != null ||
|
||||
widget.sendViewModel!.currency == CryptoCurrency.btcln)
|
||||
TransactionCommittedScreenActionButton(
|
||||
text: S.of(context).add_a_note,
|
||||
iconPath: "assets/new-ui/add_note.svg",
|
||||
isLoading: _isNoteButtonLoading,
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_isNoteButtonLoading = true;
|
||||
});
|
||||
|
||||
// for ln, we want to show the button and just have it wait until it appears in tx history
|
||||
// for other currs this is instant
|
||||
await asyncWhen((_) => widget.sendViewModel!.transactionInfo != null);
|
||||
|
||||
setState(() {
|
||||
_isNoteButtonLoading = false;
|
||||
});
|
||||
|
||||
final page = getIt.get<TransactionDetailsModal>(
|
||||
param1: widget.sendViewModel!.transactionInfo!, param2: true);
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => page);
|
||||
}),
|
||||
],
|
||||
),
|
||||
NewPrimaryButton(
|
||||
onPressed: Navigator.of(context).maybePop,
|
||||
text: S.of(context).done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary),
|
||||
SizedBox(height: 12,)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TransactionCommittedScreenActionButton extends StatelessWidget {
|
||||
const TransactionCommittedScreenActionButton(
|
||||
{super.key,
|
||||
required this.text,
|
||||
required this.iconPath,
|
||||
required this.onTap,
|
||||
this.isLoading = false});
|
||||
|
||||
final String text;
|
||||
final String iconPath;
|
||||
final VoidCallback onTap;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 10,
|
||||
children: [
|
||||
isLoading
|
||||
? CupertinoActivityIndicator()
|
||||
: CakeImageWidget(
|
||||
imageUrl: iconPath,
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w500),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
class MultiSendAddressPreview extends StatefulWidget {
|
||||
const MultiSendAddressPreview(
|
||||
|
||||
@@ -409,4 +409,33 @@ class CWSolana extends Solana {
|
||||
await Future.wait(tokenChecks);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionInfo getTransactionInfo({
|
||||
required String id,
|
||||
required DateTime blockTime,
|
||||
required String to,
|
||||
required String from,
|
||||
String? tokenSymbol,
|
||||
required TransactionDirection direction,
|
||||
required double solAmount,
|
||||
required bool isPending,
|
||||
required double txFee,
|
||||
}) =>
|
||||
SolanaTransactionInfo(
|
||||
id: id,
|
||||
blockTime: blockTime,
|
||||
to: to,
|
||||
from: from,
|
||||
tokenSymbol: tokenSymbol ?? "SOL",
|
||||
direction: direction,
|
||||
solAmount: solAmount,
|
||||
isPending: isPending,
|
||||
txFee: txFee);
|
||||
|
||||
@override
|
||||
double getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingSolanaTransaction).amount;
|
||||
|
||||
@override
|
||||
double getPendingTransactionFee(PendingTransaction tx) => (tx as PendingSolanaTransaction).fee;
|
||||
}
|
||||
|
||||
@@ -153,4 +153,32 @@ class CWTron extends Tron {
|
||||
return tronWallet.tronTokenCurrencies
|
||||
.any((element) => element.contractAddress == contractAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionInfo getTransactionInfo({
|
||||
required String id,
|
||||
required BigInt tronAmount,
|
||||
int? txFee,
|
||||
String? tokenSymbol,
|
||||
required TransactionDirection direction,
|
||||
required DateTime blockTime,
|
||||
String? to,
|
||||
String? from,
|
||||
required bool isPending,
|
||||
}) =>
|
||||
TronTransactionInfo(id: id,
|
||||
tronAmount: tronAmount,
|
||||
tokenSymbol: tokenSymbol ?? "TRX",
|
||||
txFee: txFee,
|
||||
direction: direction,
|
||||
blockTime: blockTime,
|
||||
to: to,
|
||||
from: from,
|
||||
isPending: isPending);
|
||||
|
||||
@override
|
||||
String getPendingTransactionAmount(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
|
||||
|
||||
@override
|
||||
String getPendingTransactionFee(PendingTransaction tx) => (tx as PendingTronTransaction).amount;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,10 @@ abstract class DashboardViewModelBase with Store {
|
||||
appStore.wallet!.transactionHistory.transactions.values.last.confirmations +
|
||||
1;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
confirmations = appStore.wallet!.transactionHistory.transactions.values
|
||||
.map((item) => item.isPending)
|
||||
.fold(0, (val, pending) => pending ? val + 1 : val);
|
||||
}
|
||||
return length * confirmations;
|
||||
}, _transactionDisposerCallback, delay: 300);
|
||||
@@ -490,16 +494,23 @@ abstract class DashboardViewModelBase with Store {
|
||||
}
|
||||
// printV("Transaction disposer callback (relevantTxs: ${relevantTxs.length} current: ${transactions.length})");
|
||||
|
||||
// TODO(malik) update this in a saner way during the vm refactor
|
||||
String _txIdentityString(String txHash, TransactionDirection direction) => "${txHash}_$direction";
|
||||
String _txIdentityStringConfirmations(String txHash, TransactionDirection direction, int confirmations) => "${txHash}_${direction}_$confirmations";
|
||||
|
||||
String _txIdentityStringConfirmations(
|
||||
String txHash, TransactionDirection direction, int confirmations, bool isPending) =>
|
||||
"${txHash}_${direction}_${confirmations}_$isPending";
|
||||
|
||||
final existingKeys = transactions
|
||||
.map((item) => _txIdentityStringConfirmations(item.transaction.txHash, item.transaction.direction, item.transaction.confirmations))
|
||||
.map((item) => _txIdentityStringConfirmations(
|
||||
item.transaction.txHash,
|
||||
item.transaction.direction,
|
||||
item.transaction.confirmations,
|
||||
item.transaction.isPending))
|
||||
.toSet();
|
||||
|
||||
final newTransactions = relevantTxs
|
||||
.where((tx) => !existingKeys.contains(_txIdentityStringConfirmations(tx.txHash, tx.direction, tx.confirmations)))
|
||||
.where((tx) => !existingKeys.contains(_txIdentityStringConfirmations(
|
||||
tx.txHash, tx.direction, tx.confirmations, tx.isPending)))
|
||||
.map((tx) => TransactionListItem(
|
||||
transaction: tx,
|
||||
balanceViewModel: balanceViewModel,
|
||||
@@ -1291,6 +1302,10 @@ abstract class DashboardViewModelBase with Store {
|
||||
appStore.wallet!.transactionHistory.transactions.values.last.confirmations +
|
||||
1;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
confirmations = appStore.wallet!.transactionHistory.transactions.values
|
||||
.map((item) => item.isPending)
|
||||
.fold(0, (val, pending) => pending ? val + 1 : val);
|
||||
}
|
||||
return length * confirmations;
|
||||
}, _transactionDisposerCallback, delay: 300);
|
||||
|
||||
@@ -218,7 +218,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
case WalletType.base:
|
||||
case WalletType.arbitrum:
|
||||
case WalletType.bsc:
|
||||
final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
final asset = assetOfTransaction;
|
||||
final price = balanceViewModel.fiatConversionStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: evm!.formatterEVMAmountToDouble(transaction: transaction),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/models.dart';
|
||||
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
@@ -59,6 +60,7 @@ import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/lnurl.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/unspent_coin_type.dart';
|
||||
@@ -488,6 +490,25 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
bool get shouldDisplayTOTP2FAForSendsToInternalWallet =>
|
||||
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
|
||||
|
||||
@computed
|
||||
TransactionInfo? get transactionInfo {
|
||||
if(isEVMCompatibleChain(walletType)) {
|
||||
return wallet.transactionHistory.transactions[pendingTransaction?.evmTxHashFromRawHex];
|
||||
}
|
||||
if (walletType == WalletType.monero) {
|
||||
// monero tx history keys are in format txhash_amount_accountindex_addressindex
|
||||
return wallet.transactionHistory.transactions[wallet.transactionHistory.transactions.keys
|
||||
.firstWhereOrNull((item) => item.split("_").first == pendingTransaction?.id)];
|
||||
}
|
||||
if (walletType == WalletType.zcash) {
|
||||
// zcash has a txHash getter, but the pendingTransaction id contains quotation marks for whatever reason?
|
||||
final txIdNoQuotes = pendingTransaction?.id.replaceAll("\"", "");
|
||||
return wallet.transactionHistory.transactions.values
|
||||
.firstWhereOrNull((item) => item.txHash == txIdNoQuotes);
|
||||
}
|
||||
return wallet.transactionHistory.transactions[pendingTransaction?.id];
|
||||
}
|
||||
|
||||
//* Still open to further optimize these checks
|
||||
//* It works but can be made better
|
||||
@action
|
||||
@@ -1078,6 +1099,48 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME(malik) ideally, this should be done wallet-side.
|
||||
// it is required because evm, solana and tron don't actually save the transaction info when you send something.
|
||||
// instead, they rely on the tx to eventually get fetched at sync time, which can take a while
|
||||
if(isEVMWallet) {
|
||||
wallet.transactionHistory.addOne(evm!.getTransactionInfo(
|
||||
id: pendingTransaction!.evmTxHashFromRawHex!,
|
||||
height: 0,
|
||||
// FIXME(malik) this is even more critical given all the issues we had with decimal point parsing. with money it'll be easy
|
||||
ethAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
|
||||
ethFee: CryptoCurrency.eth.parseAmount(pendingTransaction!.feeFormattedValue),
|
||||
tokenSymbol: selectedCryptoCurrency.title,
|
||||
direction: TransactionDirection.outgoing,
|
||||
isPending: true,
|
||||
date: DateTime.now(),
|
||||
confirmations: 0,
|
||||
chainId: wallet.chainId ?? 0));
|
||||
}
|
||||
|
||||
if(walletType == WalletType.solana) {
|
||||
wallet.transactionHistory.addOne(solana!.getTransactionInfo(
|
||||
id: pendingTransaction!.id,
|
||||
blockTime: DateTime.now(),
|
||||
tokenSymbol: selectedCryptoCurrency.title,
|
||||
to: "",
|
||||
from: "",
|
||||
direction: TransactionDirection.outgoing,
|
||||
solAmount: solana!.getPendingTransactionAmount(pendingTransaction!),
|
||||
isPending: true,
|
||||
txFee: solana!.getPendingTransactionFee(pendingTransaction!)));
|
||||
}
|
||||
|
||||
if (walletType == WalletType.tron) {
|
||||
wallet.transactionHistory.addOne(tron!.getTransactionInfo(
|
||||
id: pendingTransaction!.id,
|
||||
blockTime: DateTime.now(),
|
||||
tokenSymbol: selectedCryptoCurrency.title,
|
||||
direction: TransactionDirection.outgoing,
|
||||
tronAmount: selectedCryptoCurrency.parseAmount(outputs.first.cryptoAmount),
|
||||
isPending: true,
|
||||
txFee: CryptoCurrency.trx.parseAmount(outputs.first.estimatedFee).toInt()));
|
||||
}
|
||||
|
||||
if (pendingTransaction!.id.isNotEmpty) {
|
||||
_addTransactionDescription();
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ class TxDetailRowDefinition {
|
||||
.firstOrNull ??
|
||||
"";
|
||||
case WalletType.tron:
|
||||
if(vm.transactionInfo.to != null)
|
||||
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
|
||||
default:
|
||||
break;
|
||||
@@ -315,6 +316,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
TransactionPriority? transactionPriority;
|
||||
|
||||
CryptoCurrency get transactionAsset {
|
||||
|
||||
if (isEVMCompatibleChain(wallet.type)) {
|
||||
return evm!.assetOfTransaction(wallet, transactionInfo);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 510 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 735 B |
@@ -910,6 +910,7 @@
|
||||
"save": "Save",
|
||||
"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.",
|
||||
"save_backup_password_alert": "Save backup password",
|
||||
"save_contact": "Save Contact",
|
||||
"save_to_downloads": "Save to Downloads",
|
||||
"saved_the_trade_id": "I've saved the trade ID",
|
||||
"savings": "Savings",
|
||||
|
||||
@@ -938,6 +938,7 @@ import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
||||
""";
|
||||
const solanaCWHeaders = """
|
||||
@@ -1033,6 +1034,23 @@ abstract class Solana {
|
||||
});
|
||||
|
||||
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
|
||||
|
||||
TransactionInfo getTransactionInfo(
|
||||
{
|
||||
required String id,
|
||||
required DateTime blockTime,
|
||||
required String to,
|
||||
required String from,
|
||||
String? tokenSymbol,
|
||||
required TransactionDirection direction,
|
||||
required double solAmount,
|
||||
required bool isPending,
|
||||
required double txFee,
|
||||
}
|
||||
);
|
||||
|
||||
double getPendingTransactionAmount(PendingTransaction tx);
|
||||
double getPendingTransactionFee(PendingTransaction tx);
|
||||
}
|
||||
|
||||
class JupiterSwapFailedException implements Exception {
|
||||
@@ -1076,6 +1094,7 @@ Future<void> generateTron(bool hasImplementation) async {
|
||||
const tronCommonHeaders = """
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
@@ -1084,6 +1103,7 @@ import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
||||
""";
|
||||
const tronCWHeaders = """
|
||||
@@ -1091,6 +1111,7 @@ import 'package:cw_evm/evm_chain_mnemonics.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:cw_tron/pending_tron_transaction.dart';
|
||||
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
@@ -1131,6 +1152,19 @@ abstract class Tron {
|
||||
List<String> getDefaultTokenContractAddresses();
|
||||
List<String> getDefaultTokenSymbols();
|
||||
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
|
||||
TransactionInfo getTransactionInfo({
|
||||
required String id,
|
||||
required BigInt tronAmount,
|
||||
int? txFee,
|
||||
String? tokenSymbol,
|
||||
required TransactionDirection direction,
|
||||
required DateTime blockTime,
|
||||
String? to,
|
||||
String? from,
|
||||
required bool isPending,
|
||||
});
|
||||
String getPendingTransactionFee(PendingTransaction tx);
|
||||
String getPendingTransactionAmount(PendingTransaction tx);
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -1361,6 +1395,7 @@ Future<void> generateEVM(bool hasImplementation) async {
|
||||
import 'dart:math' as math;
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
@@ -1379,6 +1414,7 @@ import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
||||
""";
|
||||
const evmCWHeaders = """
|
||||
@@ -1389,6 +1425,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_evm/utils/evm_chain_formatter.dart';
|
||||
import 'package:cw_evm/evm_chain_mnemonics.dart';
|
||||
import 'package:cw_evm/pending_evm_chain_transaction.dart';
|
||||
import 'package:cw_evm/evm_chain_registry.dart';
|
||||
import 'package:cw_evm/evm_erc20_balance.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
|
||||
@@ -1608,6 +1645,28 @@ abstract class EVM {
|
||||
WalletBase wallet,
|
||||
TransactionPriority priority,
|
||||
);
|
||||
|
||||
TransactionInfo getTransactionInfo(
|
||||
{
|
||||
required String id,
|
||||
required int height,
|
||||
required BigInt ethAmount,
|
||||
required BigInt ethFee,
|
||||
required String tokenSymbol,
|
||||
int exponent = 18,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
String? to,
|
||||
String? from,
|
||||
String? evmSignatureName,
|
||||
String? contractAddress,
|
||||
required int chainId,
|
||||
}
|
||||
);
|
||||
BigInt getPendingTransactionFee(PendingTransaction tx);
|
||||
String getPendingTransactionAmount(PendingTransaction tx);
|
||||
|
||||
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user