diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 5d9ed25c7..a3544ade4 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -359,10 +359,16 @@ class _ConfirmChangeNowSendViewState mainAxisAlignment: MainAxisAlignment.end, children: [ Text( - "${(transactionInfo["fee"] as int).toAmountAsRaw( - fractionDigits: ref.watch( - managerProvider - .select((value) => value.coin.decimals), + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: ref.watch( + managerProvider + .select((value) => value.coin.decimals), + ), + )).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), )} ${ref.watch( managerProvider.select((value) => value.coin), @@ -401,10 +407,12 @@ class _ConfirmChangeNowSendViewState final coin = ref.watch( managerProvider.select((value) => value.coin), ); - final fee = - (transactionInfo["fee"] as int).toAmountAsRaw( - fractionDigits: coin.decimals, - ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw( + fractionDigits: coin.decimals, + ); final amount = transactionInfo["recipientAmt"] as Amount; final total = amount + fee; @@ -637,17 +645,17 @@ class _ConfirmChangeNowSendViewState style: STextStyles.smallMed12(context), ), Text( - "${(transactionInfo["fee"] as int).toAmountAsRaw(fractionDigits: ref.watch( - managerProvider.select( - (value) => value.coin.decimals, - ), - )).localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider.select( - (value) => value.locale, - ), - ), - )} ${ref.watch( + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw(fractionDigits: ref.watch( + managerProvider.select( + (value) => value.coin.decimals, + ), + ))).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -733,10 +741,11 @@ class _ConfirmChangeNowSendViewState final coin = ref.watch( managerProvider.select((value) => value.coin), ); - final fee = - (transactionInfo["fee"] as int).toAmountAsRaw( - fractionDigits: coin.decimals, - ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: coin.decimals, + ); final amount = transactionInfo["recipientAmt"] as Amount; final total = amount + fee; diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index bb5c5b71c..a46f86666 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -427,18 +427,18 @@ class _ConfirmTransactionViewState style: STextStyles.smallMed12(context), ), Text( - "${(transactionInfo["fee"] as int).toAmountAsRaw( - fractionDigits: ref.watch( - managerProvider.select( - (value) => value.coin.decimals, - ), + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: ref.watch( + managerProvider.select( + (value) => value.coin.decimals, ), - ).localizedStringAsFixed( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - )} ${ref.watch( + ), + )).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -552,10 +552,22 @@ class _ConfirmTransactionViewState String fiatAmount = "N/A"; if (externalCalls) { - final price = ref - .read(priceAnd24hChangeNotifierProvider) - .getPrice(coin) - .item1; + final price = widget.isTokenTx + ? ref + .read( + priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref + .read(tokenServiceProvider)! + .tokenContract + .address, + ) + .item1 + : ref + .read( + priceAnd24hChangeNotifierProvider) + .getPrice(coin) + .item1; if (price > Decimal.zero) { fiatAmount = (amount.decimal * price) .toAmount(fractionDigits: 2) @@ -678,10 +690,12 @@ class _ConfirmTransactionViewState value.getManager(walletId))) .coin; - final fee = (transactionInfo["fee"] as int) - .toAmountAsRaw( - fractionDigits: coin.decimals, - ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw( + fractionDigits: coin.decimals, + ); return Text( "${fee.localizedStringAsFixed( @@ -857,9 +871,11 @@ class _ConfirmTransactionViewState .select((value) => value.getManager(walletId))) .coin; - final fee = (transactionInfo["fee"] as int).toAmountAsRaw( - fractionDigits: coin.decimals, - ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: coin.decimals, + ); return Text( "${fee.localizedStringAsFixed( @@ -879,56 +895,28 @@ class _ConfirmTransactionViewState SizedBox( height: isDesktop ? 23 : 12, ), - Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - horizontal: 32, - ) - : const EdgeInsets.all(0), - child: RoundedContainer( + if (!widget.isTokenTx) + Padding( padding: isDesktop ? const EdgeInsets.symmetric( - horizontal: 16, - vertical: 18, + horizontal: 32, ) - : const EdgeInsets.all(12), - color: Theme.of(context) - .extension()! - .snackBarBackSuccess, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - isDesktop ? "Total amount to send" : "Total amount", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ) - : STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - ), - Builder(builder: (context) { - final coin = ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletId).coin)); - final fee = (transactionInfo["fee"] as int) - .toAmountAsRaw(fractionDigits: coin.decimals); - final locale = ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ); - final amount = transactionInfo["recipientAmt"] as Amount; - return Text( - "${(amount + fee).localizedStringAsFixed( - locale: locale, - )} ${ref.watch( - managerProvider.select((value) => value.coin), - ).ticker}", + : const EdgeInsets.all(0), + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ) + : const EdgeInsets.all(12), + color: Theme.of(context) + .extension()! + .snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isDesktop ? "Total amount to send" : "Total amount", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) .copyWith( @@ -936,18 +924,51 @@ class _ConfirmTransactionViewState .extension()! .textConfirmTotalAmount, ) - : STextStyles.itemSubtitle12(context).copyWith( + : STextStyles.titleBold12(context).copyWith( color: Theme.of(context) .extension()! .textConfirmTotalAmount, ), - textAlign: TextAlign.right, - ); - }), - ], + ), + Builder(builder: (context) { + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).coin)); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw(fractionDigits: coin.decimals); + final locale = ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ); + final amount = + transactionInfo["recipientAmt"] as Amount; + return Text( + "${(amount + fee).localizedStringAsFixed( + locale: locale, + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }), + ], + ), ), ), - ), SizedBox( height: isDesktop ? 28 : 16, ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart index 469aeed71..a37572573 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart @@ -2,6 +2,7 @@ import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart'; import 'package:stackwallet/pages/token_view/token_view.dart'; @@ -89,7 +90,10 @@ class _DesktopTokenViewState extends ConsumerState { .extension()! .topNavIconPrimary, ), - onPressed: Navigator.of(context).pop, + onPressed: () { + ref.refresh(feeSheetSessionCacheProvider); + Navigator.of(context).pop(); + }, ), const SizedBox( width: 15, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart index 9bd260a34..fc049e093 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; @@ -22,9 +23,11 @@ class DesktopFeeDropDown extends ConsumerStatefulWidget { const DesktopFeeDropDown({ Key? key, required this.walletId, + this.isToken = false, }) : super(key: key); final String walletId; + final bool isToken; @override ConsumerState createState() => _DesktopFeeDropDownState(); @@ -52,66 +55,84 @@ class _DesktopFeeDropDownState extends ConsumerState { switch (feeRateType) { case FeeRateType.fast: if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.fast.raw!); - ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.fast.raw!); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - await manager.estimateFeeFor(amount, feeRate); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).fast[amount]!; case FeeRateType.average: if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.regular.raw!); - ref.read(feeSheetSessionCacheProvider).average[amount] = fee; - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).average[amount] = - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.regular.raw!); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).average[amount] = - await manager.estimateFeeFor(amount, feeRate); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).average[amount]!; case FeeRateType.slow: if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.slow.raw!); - ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.slow.raw!); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - await manager.estimateFeeFor(amount, feeRate); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).slow[amount]!; @@ -321,14 +342,16 @@ class FeeDropDownChild extends ConsumerWidget { ), if (feeObject != null) Text( - estimatedTimeToBeIncludedInNextBlock( - Constants.targetBlockTimeInSeconds(manager.coin), - feeRateType == FeeRateType.fast - ? feeObject!.numberOfBlocksFast - : feeRateType == FeeRateType.slow - ? feeObject!.numberOfBlocksSlow - : feeObject!.numberOfBlocksAverage, - ), + manager.coin == Coin.ethereum + ? "" + : estimatedTimeToBeIncludedInNextBlock( + Constants.targetBlockTimeInSeconds(manager.coin), + feeRateType == FeeRateType.fast + ? feeObject!.numberOfBlocksFast + : feeRateType == FeeRateType.slow + ? feeObject!.numberOfBlocksSlow + : feeObject!.numberOfBlocksAverage, + ), style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index 5710be78b..b4b10fa95 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1412,7 +1412,7 @@ class _DesktopSendState extends ConsumerState { ), if (coin != Coin.epicCash) Text( - "Transaction fee (estimated)", + "Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart index 97be1da9a..e418c4283 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart @@ -71,12 +71,14 @@ class _DesktopTokenSendState extends ConsumerState { late TextEditingController sendToController; late TextEditingController cryptoAmountController; late TextEditingController baseAmountController; + late TextEditingController nonceController; late final SendViewAutoFillData? _data; final _addressFocusNode = FocusNode(); final _cryptoFocus = FocusNode(); final _baseFocus = FocusNode(); + final _nonceFocusNode = FocusNode(); String? _note; @@ -229,6 +231,7 @@ class _DesktopTokenSendState extends ConsumerState { amount: amount, args: { "feeRate": ref.read(feeRateTypeStateProvider), + "nonce": int.tryParse(nonceController.text), }, ); @@ -305,7 +308,7 @@ class _DesktopTokenSendState extends ConsumerState { padding: const EdgeInsets.only( right: 32, ), - child: Text( + child: SelectableText( e.toString(), textAlign: TextAlign.left, style: STextStyles.desktopTextExtraExtraSmall(context) @@ -370,8 +373,12 @@ class _DesktopTokenSendState extends ConsumerState { level: LogLevel.Info); _cachedAmountToSend = _amountToSend; - final price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + final price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(tokenServiceProvider)!.tokenContract.address, + ) + .item1; if (price > Decimal.zero) { final String fiatAmountString = Amount.fromDecimal( @@ -512,8 +519,12 @@ class _DesktopTokenSendState extends ConsumerState { .toAmount(fractionDigits: 2) : Decimal.parse(baseAmountString).toAmount(fractionDigits: 2); - final Decimal _price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + final Decimal _price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(tokenServiceProvider)!.tokenContract.address, + ) + .item1; if (_price == Decimal.zero) { _amountToSend = Decimal.zero.toAmount(fractionDigits: tokenDecimals); @@ -577,6 +588,7 @@ class _DesktopTokenSendState extends ConsumerState { sendToController = TextEditingController(); cryptoAmountController = TextEditingController(); baseAmountController = TextEditingController(); + nonceController = TextEditingController(); // feeController = TextEditingController(); onCryptoAmountChanged = _cryptoAmountChanged; @@ -621,11 +633,13 @@ class _DesktopTokenSendState extends ConsumerState { sendToController.dispose(); cryptoAmountController.dispose(); baseAmountController.dispose(); + nonceController.dispose(); // feeController.dispose(); _addressFocusNode.dispose(); _cryptoFocus.dispose(); _baseFocus.dispose(); + _nonceFocusNode.dispose(); super.dispose(); } @@ -979,7 +993,7 @@ class _DesktopTokenSendState extends ConsumerState { height: 20, ), Text( - "Transaction fee (estimated)", + "Transaction fee (max)", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! @@ -990,9 +1004,59 @@ class _DesktopTokenSendState extends ConsumerState { const SizedBox( height: 10, ), - // TODO mod this for token fees DesktopFeeDropDown( walletId: walletId, + isToken: true, + ), + const SizedBox( + height: 20, + ), + Text( + "Nonce", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 1, + key: const Key("sendViewNonceFieldKey"), + controller: nonceController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + keyboardType: const TextInputType.numberWithOptions(), + focusNode: _nonceFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Leave empty to auto select nonce", + _nonceFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + ), + ), ), const SizedBox( height: 36, diff --git a/lib/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart index 797388f67..184b67cdb 100644 --- a/lib/services/coins/ethereum/ethereum_wallet.dart +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -923,7 +923,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { // precision may be lost here hence the following amountString amount: (txData["recipientAmt"] as Amount).raw.toInt(), amountString: (txData["recipientAmt"] as Amount).toJsonString(), - fee: txData["fee"] as int, + fee: (txData["fee"] as Amount).raw.toInt(), height: null, isCancelled: false, isLelantus: false, diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart index d31bcfd40..fcc5de192 100644 --- a/lib/services/ethereum/ethereum_api.dart +++ b/lib/services/ethereum/ethereum_api.dart @@ -449,7 +449,7 @@ abstract class EthereumAPI { }) async { try { final uri = Uri.parse( - "$stackBaseServer/state?addrs=$address&parts=nonce", + "$stackBaseServer/state?addrs=$address&parts=all", ); final response = await get(uri); @@ -469,7 +469,7 @@ abstract class EthereumAPI { } } else { throw EthApiException( - "getWalletTokenBalance($address) failed with status code: " + "getAddressNonce($address) failed with status code: " "${response.statusCode}", ); } @@ -480,7 +480,7 @@ abstract class EthereumAPI { ); } catch (e, s) { Logging.instance.log( - "getWalletTokenBalance(): $e\n$s", + "getAddressNonce(): $e\n$s", level: LogLevel.Error, ); return EthereumResponse( @@ -501,12 +501,19 @@ abstract class EthereumAPI { if (response.statusCode == 200) { final json = jsonDecode(response.body) as Map; if (json["success"] == true) { - return EthereumResponse( - GasTracker.fromJson( - Map.from(json["result"] as Map), - ), - null, - ); + try { + return EthereumResponse( + GasTracker.fromJson( + Map.from(json["result"]["result"] as Map), + ), + null, + ); + } catch (_) { + throw EthApiException( + "getGasOracle() failed with response: " + "${response.body}", + ); + } } else { throw EthApiException( "getGasOracle() failed with response: " diff --git a/lib/services/ethereum/ethereum_token_service.dart b/lib/services/ethereum/ethereum_token_service.dart index 48eff2f00..624b6b59a 100644 --- a/lib/services/ethereum/ethereum_token_service.dart +++ b/lib/services/ethereum/ethereum_token_service.dart @@ -90,30 +90,10 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { final myAddress = await currentReceivingAddress; final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress); - final est = await client.estimateGas( - sender: myWeb3Address, - to: web3dart.EthereumAddress.fromHex(address), - data: _sendFunction - .encodeCall([web3dart.EthereumAddress.fromHex(address), amount.raw]), - gasPrice: web3dart.EtherAmount.fromUnitAndValue( - web3dart.EtherUnit.wei, - fee, - ), - amountOfGas: BigInt.from(_gasLimit), - ); - final nonce = args?["nonce"] as int? ?? await client.getTransactionCount(myWeb3Address, atBlock: const web3dart.BlockNum.pending()); - final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); - print("=============================================================="); - print("TOKEN client.estimateGas: $est"); - print("TOKEN estimateFeeFor : $feeEstimate"); - print("TOKEN nonce custom response: $nResponse"); - print("TOKEN actual nonce : $nonce"); - print("=============================================================="); - final tx = web3dart.Transaction.callContract( contract: _deployedContract, function: _sendFunction, @@ -179,7 +159,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { // precision may be lost here hence the following amountString amount: (txData["recipientAmt"] as Amount).raw.toInt(), amountString: (txData["recipientAmt"] as Amount).toJsonString(), - fee: txData["fee"] as int, + fee: (txData["fee"] as Amount).raw.toInt(), height: null, isCancelled: false, isLelantus: false, diff --git a/lib/services/tokens_service.dart b/lib/services/tokens_service.dart deleted file mode 100644 index 166a97cda..000000000 --- a/lib/services/tokens_service.dart +++ /dev/null @@ -1,425 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:stackwallet/db/hive/db.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - -class TokenInfo { - final Coin coin; - final String walletId; - final String contractAddress; - - const TokenInfo( - {required this.coin, - required this.walletId, - required this.contractAddress}); - - factory TokenInfo.fromJson(Map jsonObject) { - return TokenInfo( - coin: Coin.values.byName(jsonObject["coin"] as String), - walletId: jsonObject["id"] as String, - contractAddress: jsonObject["contractAddress"] as String, - ); - } - - Map toMap() { - return { - "contractAddress": contractAddress, - "walletId": walletId, - "coin": coin.name, - }; - } - - String toJsonString() { - return jsonEncode(toMap()); - } - - @override - String toString() { - return "TokenInfo: ${toJsonString()}"; - } -} - -class TokensService extends ChangeNotifier { - late final SecureStorageInterface _secureStore; - - // Future>? _walletNames; - // Future> get walletNames => - // _walletNames ??= _fetchWalletNames(); - - TokensService({ - required SecureStorageInterface secureStorageInterface, - }) { - _secureStore = secureStorageInterface; - } - - // Future getWalletCryptoCurrency({required String walletName}) async { - // final id = await getWalletId(walletName); - // final currency = DB.instance.get( - // boxName: DB.boxNameAllWalletsData, key: "${id}_cryptoCurrency"); - // return Coin.values.byName(currency as String); - // } - - // Future renameWallet({ - // required String from, - // required String to, - // required bool shouldNotifyListeners, - // }) async { - // if (from == to) { - // return true; - // } - // - // final walletInfo = DB.instance - // .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map; - // - // final info = walletInfo.values.firstWhere( - // (element) => element['name'] == from, - // orElse: () => {}) as Map; - // - // if (info.isEmpty) { - // // tried to rename a non existing wallet - // Logging.instance - // .log("Tried to rename a non existing wallet!", level: LogLevel.Error); - // return false; - // } - // - // if (from != to && - // (walletInfo.values.firstWhere((element) => element['name'] == to, - // orElse: () => {}) as Map) - // .isNotEmpty) { - // // name already exists - // Logging.instance.log("wallet with name \"$to\" already exists!", - // level: LogLevel.Error); - // return false; - // } - // - // info["name"] = to; - // walletInfo[info['id']] = info; - // - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, key: 'names', value: walletInfo); - // await refreshWallets(shouldNotifyListeners); - // return true; - // } - - // Future> _fetchWalletNames() async { - // final names = DB.instance - // .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?; - // if (names == null) { - // Logging.instance.log( - // "Fetched wallet 'names' returned null. Setting initializing 'names'", - // level: LogLevel.Info); - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: 'names', - // value: {}); - // return {}; - // } - // Logging.instance.log("Fetched wallet names: $names", level: LogLevel.Info); - // final mapped = Map.from(names); - // mapped.removeWhere((name, dyn) { - // final jsonObject = Map.from(dyn as Map); - // try { - // Coin.values.byName(jsonObject["coin"] as String); - // return false; - // } catch (e, s) { - // Logging.instance.log("Error, ${jsonObject["coin"]} does not exist", - // level: LogLevel.Error); - // return true; - // } - // }); - // - // return mapped.map((name, dyn) => MapEntry( - // name, WalletInfo.fromJson(Map.from(dyn as Map)))); - // } - - // Future addExistingStackWallet({ - // required String name, - // required String walletId, - // required Coin coin, - // required bool shouldNotifyListeners, - // }) async { - // final _names = DB.instance - // .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?; - // - // Map names; - // if (_names == null) { - // names = {}; - // } else { - // names = Map.from(_names); - // } - // - // if (names.keys.contains(walletId)) { - // throw Exception("Wallet with walletId \"$walletId\" already exists!"); - // } - // if (names.values.where((element) => element['name'] == name).isNotEmpty) { - // throw Exception("Wallet with name \"$name\" already exists!"); - // } - // - // names[walletId] = { - // "id": walletId, - // "coin": coin.name, - // "name": name, - // }; - // - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, key: 'names', value: names); - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_cryptoCurrency", - // value: coin.name); - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_mnemonicHasBeenVerified", - // value: false); - // await DB.instance.addWalletBox(walletId: walletId); - // await refreshWallets(shouldNotifyListeners); - // } - - // /// returns the new walletId if successful, otherwise null - // Future addNewWallet({ - // required String name, - // required Coin coin, - // required bool shouldNotifyListeners, - // }) async { - // final _names = DB.instance - // .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?; - // - // Map names; - // if (_names == null) { - // names = {}; - // } else { - // names = Map.from(_names); - // } - // - // // Prevent overwriting or storing empty names - // if (name.isEmpty || - // names.values.where((element) => element['name'] == name).isNotEmpty) { - // return null; - // } - // - // final id = const Uuid().v1(); - // names[id] = { - // "id": id, - // "coin": coin.name, - // "name": name, - // }; - // - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, key: 'names', value: names); - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: "${id}_cryptoCurrency", - // value: coin.name); - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: "${id}_mnemonicHasBeenVerified", - // value: false); - // await DB.instance.addWalletBox(walletId: id); - // await refreshWallets(shouldNotifyListeners); - // return id; - // } - - // Future> getFavoriteWalletIds() async { - // return DB.instance - // .values(boxName: DB.boxNameFavoriteWallets) - // .toList(); - // } - - // Future saveFavoriteWalletIds(List walletIds) async { - // await DB.instance.deleteAll(boxName: DB.boxNameFavoriteWallets); - // await DB.instance - // .addAll(boxName: DB.boxNameFavoriteWallets, values: walletIds); - // debugPrint("saveFavoriteWalletIds list: $walletIds"); - // } - // - // Future addFavorite(String walletId) async { - // final list = await getFavoriteWalletIds(); - // if (!list.contains(walletId)) { - // list.add(walletId); - // } - // await saveFavoriteWalletIds(list); - // } - // - // Future removeFavorite(String walletId) async { - // final list = await getFavoriteWalletIds(); - // list.remove(walletId); - // await saveFavoriteWalletIds(list); - // } - // - // Future moveFavorite({ - // required int fromIndex, - // required int toIndex, - // }) async { - // final list = await getFavoriteWalletIds(); - // if (fromIndex < toIndex) { - // toIndex -= 1; - // } - // final walletId = list.removeAt(fromIndex); - // list.insert(toIndex, walletId); - // await saveFavoriteWalletIds(list); - // } - // - // Future checkForDuplicate(String name) async { - // final names = DB.instance - // .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?; - // if (names == null) return false; - // - // return names.values.where((element) => element['name'] == name).isNotEmpty; - // } - - Future getWalletId(String walletName) async { - final names = DB.instance - .get(boxName: DB.boxNameAllWalletsData, key: 'names') as Map; - final shells = - names.values.where((element) => element['name'] == walletName); - if (shells.isEmpty) { - return null; - } - return shells.first["id"] as String; - } - - // Future isMnemonicVerified({required String walletId}) async { - // final isVerified = DB.instance.get( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_mnemonicHasBeenVerified") as bool?; - // - // if (isVerified == null) { - // Logging.instance.log( - // "isMnemonicVerified(walletId: $walletId) returned null which should never happen!", - // level: LogLevel.Error, - // ); - // throw Exception( - // "isMnemonicVerified(walletId: $walletId) returned null which should never happen!"); - // } else { - // return isVerified; - // } - // } - // - // Future setMnemonicVerified({required String walletId}) async { - // final isVerified = DB.instance.get( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_mnemonicHasBeenVerified") as bool?; - // - // if (isVerified == null) { - // Logging.instance.log( - // "setMnemonicVerified(walletId: $walletId) tried running on non existent wallet!", - // level: LogLevel.Error, - // ); - // throw Exception( - // "setMnemonicVerified(walletId: $walletId) tried running on non existent wallet!"); - // } else if (isVerified) { - // Logging.instance.log( - // "setMnemonicVerified(walletId: $walletId) tried running on already verified wallet!", - // level: LogLevel.Error, - // ); - // throw Exception( - // "setMnemonicVerified(walletId: $walletId) tried running on already verified wallet!"); - // } else { - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_mnemonicHasBeenVerified", - // value: true); - // Logging.instance.log( - // "setMnemonicVerified(walletId: $walletId) successful", - // level: LogLevel.Error, - // ); - // } - // } - // - // // pin + mnemonic as well as anything else in secureStore - // Future deleteWallet(String name, bool shouldNotifyListeners) async { - // final names = DB.instance.get( - // boxName: DB.boxNameAllWalletsData, key: 'names') as Map? ?? - // {}; - // - // final walletId = await getWalletId(name); - // if (walletId == null) { - // return 3; - // } - // - // Logging.instance.log( - // "deleteWallet called with name=$name and id=$walletId", - // level: LogLevel.Warning, - // ); - // - // final shell = names.remove(walletId); - // - // if (shell == null) { - // return 0; - // } - // - // // TODO delete derivations!!! - // await _secureStore.delete(key: "${walletId}_pin"); - // await _secureStore.delete(key: "${walletId}_mnemonic"); - // - // await DB.instance.delete( - // boxName: DB.boxNameAllWalletsData, key: "${walletId}_cryptoCurrency"); - // await DB.instance.delete( - // boxName: DB.boxNameAllWalletsData, - // key: "${walletId}_mnemonicHasBeenVerified"); - // if (coinFromPrettyName(shell['coin'] as String) == Coin.wownero) { - // final wowService = - // wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - // await wowService.remove(walletId); - // Logging.instance - // .log("monero wallet: $walletId deleted", level: LogLevel.Info); - // } else if (coinFromPrettyName(shell['coin'] as String) == Coin.monero) { - // final xmrService = - // monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - // await xmrService.remove(walletId); - // Logging.instance - // .log("monero wallet: $walletId deleted", level: LogLevel.Info); - // } else if (coinFromPrettyName(shell['coin'] as String) == Coin.epicCash) { - // final deleteResult = - // await deleteEpicWallet(walletId: walletId, secureStore: _secureStore); - // Logging.instance.log( - // "epic wallet: $walletId deleted with result: $deleteResult", - // level: LogLevel.Info); - // } - // - // // box data may currently still be read/written to if wallet was refreshing - // // when delete was requested so instead of deleting now we mark the wallet - // // as needs delete by adding it's id to a list which gets checked on app start - // await DB.instance.add( - // boxName: DB.boxNameWalletsToDeleteOnStart, value: walletId); - // - // final lookupService = TradeSentFromStackService(); - // for (final lookup in lookupService.all) { - // if (lookup.walletIds.contains(walletId)) { - // // update lookup data to reflect deleted wallet - // await lookupService.save( - // tradeWalletLookup: lookup.copyWith( - // walletIds: lookup.walletIds.where((id) => id != walletId).toList(), - // ), - // ); - // } - // } - // - // // delete notifications tied to deleted wallet - // for (final notification in NotificationsService.instance.notifications) { - // if (notification.walletId == walletId) { - // await NotificationsService.instance.delete(notification, false); - // } - // } - // - // if (names.isEmpty) { - // await DB.instance.deleteAll(boxName: DB.boxNameAllWalletsData); - // _walletNames = Future(() => {}); - // notifyListeners(); - // return 2; // error code no wallets on device - // } - // - // await DB.instance.put( - // boxName: DB.boxNameAllWalletsData, key: 'names', value: names); - // await refreshWallets(shouldNotifyListeners); - // return 0; - // } - // - // Future refreshWallets(bool shouldNotifyListeners) async { - // final newNames = await _fetchWalletNames(); - // _walletNames = Future(() => newNames); - // if (shouldNotifyListeners) notifyListeners(); - // } -} diff --git a/lib/utilities/eth_commons.dart b/lib/utilities/eth_commons.dart index 9b74cdbfe..466a4aff0 100644 --- a/lib/utilities/eth_commons.dart +++ b/lib/utilities/eth_commons.dart @@ -15,7 +15,7 @@ class GasTracker { final int numberOfBlocksAverage; final int numberOfBlocksSlow; - final int timestamp; + final String lastBlock; const GasTracker({ required this.average, @@ -24,19 +24,20 @@ class GasTracker { required this.numberOfBlocksFast, required this.numberOfBlocksAverage, required this.numberOfBlocksSlow, - required this.timestamp, + required this.lastBlock, }); factory GasTracker.fromJson(Map json) { final targetTime = Constants.targetBlockTimeInSeconds(Coin.ethereum); return GasTracker( - average: Decimal.parse(json["average"]["price"].toString()), - fast: Decimal.parse(json["fast"]["price"].toString()), - slow: Decimal.parse(json["slow"]["price"].toString()), - numberOfBlocksAverage: (json["average"]["time"] as int) ~/ targetTime, - numberOfBlocksFast: (json["fast"]["time"] as int) ~/ targetTime, - numberOfBlocksSlow: (json["slow"]["time"] as int) ~/ targetTime, - timestamp: json["timestamp"] as int, + fast: Decimal.parse(json["FastGasPrice"].toString()), + average: Decimal.parse(json["ProposeGasPrice"].toString()), + slow: Decimal.parse(json["SafeGasPrice"].toString()), + // TODO fix hardcoded + numberOfBlocksFast: 30 ~/ targetTime, + numberOfBlocksAverage: 180 ~/ targetTime, + numberOfBlocksSlow: 240 ~/ targetTime, + lastBlock: json["LastBlock"] as String, ); } }