diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index 7b325030b..081ae89d8 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit 7b325030bce46a423aa46497d1a608b7a8a58976 +Subproject commit 081ae89d8f47f5575a19dc03663727cf0d271f2c diff --git a/lib/dto/ethereum/eth_token_tx_dto.dart b/lib/dto/ethereum/eth_token_tx_dto.dart index 51a9a7449..0a63c6106 100644 --- a/lib/dto/ethereum/eth_token_tx_dto.dart +++ b/lib/dto/ethereum/eth_token_tx_dto.dart @@ -28,23 +28,32 @@ class EthTokenTxDto { required this.articulatedLog, required this.transactionHash, required this.transactionIndex, + required this.blockHash, + required this.timestamp, + required this.nonce, + required this.gasUsed, + required this.gasPrice, }); EthTokenTxDto.fromMap(Map map) - : address = map['address'] as String, - blockNumber = map['blockNumber'] as int, - logIndex = map['logIndex'] as int, - topics = List.from(map['topics'] as List), - data = map['data'] as String, - articulatedLog = map['articulatedLog'] == null - ? null - : ArticulatedLog.fromMap( - Map.from( - map['articulatedLog'] as Map, - ), + : address = map['address'] as String, + blockNumber = map['blockNumber'] as int, + logIndex = map['logIndex'] as int, + topics = List.from(map['topics'] as List), + data = map['data'] as String, + articulatedLog = + map['articulatedLog'] == null + ? null + : ArticulatedLog.fromMap( + Map.from(map['articulatedLog'] as Map), ), - transactionHash = map['transactionHash'] as String, - transactionIndex = map['transactionIndex'] as int; + transactionHash = map['transactionHash'] as String, + transactionIndex = map['transactionIndex'] as int, + blockHash = map['blockHash'] as String?, + timestamp = map['timestamp'] as int, + nonce = map['nonce'] as int?, + gasUsed = map['gasUsed'] as int?, + gasPrice = BigInt.tryParse(map['gasPrice'].toString()); final String address; final int blockNumber; @@ -54,6 +63,11 @@ class EthTokenTxDto { final ArticulatedLog? articulatedLog; final String transactionHash; final int transactionIndex; + final String? blockHash; + final int timestamp; + final int? nonce; + final int? gasUsed; + final BigInt? gasPrice; EthTokenTxDto copyWith({ String? address, @@ -65,17 +79,26 @@ class EthTokenTxDto { String? compressedLog, String? transactionHash, int? transactionIndex, - }) => - EthTokenTxDto( - address: address ?? this.address, - blockNumber: blockNumber ?? this.blockNumber, - logIndex: logIndex ?? this.logIndex, - topics: topics ?? this.topics, - data: data ?? this.data, - articulatedLog: articulatedLog ?? this.articulatedLog, - transactionHash: transactionHash ?? this.transactionHash, - transactionIndex: transactionIndex ?? this.transactionIndex, - ); + String? blockHash, + int? timestamp, + int? nonce, + int? gasUsed, + BigInt? gasPrice, + }) => EthTokenTxDto( + address: address ?? this.address, + blockNumber: blockNumber ?? this.blockNumber, + logIndex: logIndex ?? this.logIndex, + topics: topics ?? this.topics, + data: data ?? this.data, + articulatedLog: articulatedLog ?? this.articulatedLog, + transactionHash: transactionHash ?? this.transactionHash, + transactionIndex: transactionIndex ?? this.transactionIndex, + blockHash: blockHash ?? this.blockHash, + timestamp: timestamp ?? this.timestamp, + nonce: nonce ?? this.nonce, + gasUsed: gasUsed ?? this.gasUsed, + gasPrice: gasPrice ?? this.gasPrice, + ); Map toMap() { final map = {}; @@ -87,6 +110,11 @@ class EthTokenTxDto { map['articulatedLog'] = articulatedLog?.toMap(); map['transactionHash'] = transactionHash; map['transactionIndex'] = transactionIndex; + map['blockHash'] = blockHash; + map['timestamp'] = timestamp; + map['nonce'] = nonce; + map['gasPrice'] = gasPrice; + map['gasUsed'] = gasUsed; return map; } @@ -100,30 +128,17 @@ class EthTokenTxDto { /// inputs : {"_amount":"3291036540000000000","_from":"0x3a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597","_to":"0xc5e81fc2401b8104966637d5334cbce92f01dbf7"} class ArticulatedLog { - ArticulatedLog({ - required this.name, - required this.inputs, - }); + ArticulatedLog({required this.name, required this.inputs}); ArticulatedLog.fromMap(Map map) - : name = map['name'] as String, - inputs = Inputs.fromMap( - Map.from( - map['inputs'] as Map, - ), - ); + : name = map['name'] as String, + inputs = Inputs.fromMap(Map.from(map['inputs'] as Map)); final String name; final Inputs inputs; - ArticulatedLog copyWith({ - String? name, - Inputs? inputs, - }) => - ArticulatedLog( - name: name ?? this.name, - inputs: inputs ?? this.inputs, - ); + ArticulatedLog copyWith({String? name, Inputs? inputs}) => + ArticulatedLog(name: name ?? this.name, inputs: inputs ?? this.inputs); Map toMap() { final map = {}; @@ -138,31 +153,22 @@ class ArticulatedLog { /// _to : "0xc5e81fc2401b8104966637d5334cbce92f01dbf7" /// class Inputs { - Inputs({ - required this.amount, - required this.from, - required this.to, - }); + Inputs({required this.amount, required this.from, required this.to}); Inputs.fromMap(Map map) - : amount = map['_amount'] as String, - from = map['_from'] as String, - to = map['_to'] as String; + : amount = map['_amount'] as String, + from = map['_from'] as String, + to = map['_to'] as String; final String amount; final String from; final String to; - Inputs copyWith({ - String? amount, - String? from, - String? to, - }) => - Inputs( - amount: amount ?? this.amount, - from: from ?? this.from, - to: to ?? this.to, - ); + Inputs copyWith({String? amount, String? from, String? to}) => Inputs( + amount: amount ?? this.amount, + from: from ?? this.from, + to: to ?? this.to, + ); Map toMap() { final map = {}; diff --git a/lib/dto/ethereum/eth_token_tx_extra_dto.dart b/lib/dto/ethereum/eth_token_tx_extra_dto.dart deleted file mode 100644 index 401ea7122..000000000 --- a/lib/dto/ethereum/eth_token_tx_extra_dto.dart +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:convert'; - -import '../../utilities/amount/amount.dart'; -import '../../wallets/crypto_currency/crypto_currency.dart'; - -class EthTokenTxExtraDTO { - EthTokenTxExtraDTO({ - required this.blockHash, - required this.blockNumber, - required this.from, - required this.gas, - required this.gasCost, - required this.gasPrice, - required this.gasUsed, - required this.hash, - required this.input, - required this.nonce, - required this.timestamp, - required this.to, - required this.transactionIndex, - required this.value, - }); - - factory EthTokenTxExtraDTO.fromMap(Map map) => - EthTokenTxExtraDTO( - hash: map['hash'] as String, - blockHash: map['blockHash'] as String, - blockNumber: map['blockNumber'] as int, - transactionIndex: map['transactionIndex'] as int, - timestamp: map['timestamp'] as int, - from: map['from'] as String, - to: map['to'] as String, - value: Amount( - rawValue: BigInt.parse(map['value'] as String), - fractionDigits: Ethereum(CryptoCurrencyNetwork.main).fractionDigits, - ), - gas: _amountFromJsonNum(map['gas']), - gasPrice: _amountFromJsonNum(map['gasPrice']), - nonce: map['nonce'] as int?, - input: map['input'] as String, - gasCost: _amountFromJsonNum(map['gasCost']), - gasUsed: _amountFromJsonNum(map['gasUsed']), - ); - - final String hash; - final String blockHash; - final int blockNumber; - final int transactionIndex; - final int timestamp; - final String from; - final String to; - final Amount value; - final Amount gas; - final Amount gasPrice; - final String input; - final int? nonce; - final Amount gasCost; - final Amount gasUsed; - - static Amount _amountFromJsonNum(dynamic json) { - return Amount( - rawValue: BigInt.from(json as num), - fractionDigits: Ethereum(CryptoCurrencyNetwork.main).fractionDigits, - ); - } - - EthTokenTxExtraDTO copyWith({ - String? hash, - String? blockHash, - int? blockNumber, - int? transactionIndex, - int? timestamp, - String? from, - String? to, - Amount? value, - Amount? gas, - Amount? gasPrice, - int? nonce, - String? input, - Amount? gasCost, - Amount? gasUsed, - }) => - EthTokenTxExtraDTO( - hash: hash ?? this.hash, - blockHash: blockHash ?? this.blockHash, - blockNumber: blockNumber ?? this.blockNumber, - transactionIndex: transactionIndex ?? this.transactionIndex, - timestamp: timestamp ?? this.timestamp, - from: from ?? this.from, - to: to ?? this.to, - value: value ?? this.value, - gas: gas ?? this.gas, - gasPrice: gasPrice ?? this.gasPrice, - nonce: nonce ?? this.nonce, - input: input ?? this.input, - gasCost: gasCost ?? this.gasCost, - gasUsed: gasUsed ?? this.gasUsed, - ); - - Map toMap() { - final map = {}; - map['hash'] = hash; - map['blockHash'] = blockHash; - map['blockNumber'] = blockNumber; - map['transactionIndex'] = transactionIndex; - map['timestamp'] = timestamp; - map['from'] = from; - map['to'] = to; - map['value'] = value.toJsonString(); - map['gas'] = gas.toJsonString(); - map['gasPrice'] = gasPrice.toJsonString(); - map['input'] = input; - map['nonce'] = nonce; - map['gasCost'] = gasCost.toJsonString(); - map['gasUsed'] = gasUsed.toJsonString(); - return map; - } - - @override - String toString() => jsonEncode(toMap()); -} diff --git a/lib/dto/ethereum/eth_tx_dto.dart b/lib/dto/ethereum/eth_tx_dto.dart index 10a46d740..0801d7050 100644 --- a/lib/dto/ethereum/eth_tx_dto.dart +++ b/lib/dto/ethereum/eth_tx_dto.dart @@ -31,26 +31,28 @@ class EthTxDTO { required this.hasToken, required this.gasCost, required this.gasUsed, + required this.nonce, }); factory EthTxDTO.fromMap(Map map) => EthTxDTO( - hash: map['hash'] as String, - blockHash: map['blockHash'] as String, - blockNumber: map['blockNumber'] as int, - transactionIndex: map['transactionIndex'] as int, - timestamp: map['timestamp'] as int, - from: map['from'] as String, - to: map['to'] as String, - value: _amountFromJsonNum(map['value'])!, - gas: _amountFromJsonNum(map['gas'])!, - gasPrice: _amountFromJsonNum(map['gasPrice'])!, - maxFeePerGas: _amountFromJsonNum(map['maxFeePerGas']), - maxPriorityFeePerGas: _amountFromJsonNum(map['maxPriorityFeePerGas']), - isError: map['isError'] as bool? ?? false, - hasToken: map['hasToken'] as bool? ?? false, - gasCost: _amountFromJsonNum(map['gasCost'])!, - gasUsed: _amountFromJsonNum(map['gasUsed'])!, - ); + hash: map['hash'] as String, + blockHash: map['blockHash'] as String, + blockNumber: map['blockNumber'] as int, + transactionIndex: map['transactionIndex'] as int, + timestamp: map['timestamp'] as int, + from: map['from'] as String, + to: map['to'] as String, + value: _amountFromJsonNum(map['value'])!, + gas: _amountFromJsonNum(map['gas'])!, + gasPrice: _amountFromJsonNum(map['gasPrice'])!, + maxFeePerGas: _amountFromJsonNum(map['maxFeePerGas']), + maxPriorityFeePerGas: _amountFromJsonNum(map['maxPriorityFeePerGas']), + isError: map['isError'] as bool? ?? false, + hasToken: map['hasToken'] as bool? ?? false, + gasCost: _amountFromJsonNum(map['gasCost'])!, + gasUsed: _amountFromJsonNum(map['gasUsed'])!, + nonce: map['nonce'] as int?, + ); final String hash; final String blockHash; @@ -68,6 +70,7 @@ class EthTxDTO { final bool hasToken; final Amount gasCost; final Amount gasUsed; + final int? nonce; static Amount? _amountFromJsonNum(dynamic json) { if (json == null) { @@ -97,25 +100,26 @@ class EthTxDTO { String? compressedTx, Amount? gasCost, Amount? gasUsed, - }) => - EthTxDTO( - hash: hash ?? this.hash, - blockHash: blockHash ?? this.blockHash, - blockNumber: blockNumber ?? this.blockNumber, - transactionIndex: transactionIndex ?? this.transactionIndex, - timestamp: timestamp ?? this.timestamp, - from: from ?? this.from, - to: to ?? this.to, - value: value ?? this.value, - gas: gas ?? this.gas, - gasPrice: gasPrice ?? this.gasPrice, - maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, - maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, - isError: isError ?? this.isError, - hasToken: hasToken ?? this.hasToken, - gasCost: gasCost ?? this.gasCost, - gasUsed: gasUsed ?? this.gasUsed, - ); + int? nonce, + }) => EthTxDTO( + hash: hash ?? this.hash, + blockHash: blockHash ?? this.blockHash, + blockNumber: blockNumber ?? this.blockNumber, + transactionIndex: transactionIndex ?? this.transactionIndex, + timestamp: timestamp ?? this.timestamp, + from: from ?? this.from, + to: to ?? this.to, + value: value ?? this.value, + gas: gas ?? this.gas, + gasPrice: gasPrice ?? this.gasPrice, + maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, + isError: isError ?? this.isError, + hasToken: hasToken ?? this.hasToken, + gasCost: gasCost ?? this.gasCost, + gasUsed: gasUsed ?? this.gasUsed, + nonce: nonce ?? this.nonce, + ); Map toMap() { final map = {}; @@ -135,6 +139,7 @@ class EthTxDTO { map['hasToken'] = hasToken; map['gasCost'] = gasCost.toString(); map['gasUsed'] = gasUsed.toString(); + map['nonce'] = nonce; return map; } diff --git a/lib/dto/ethereum/pending_eth_tx_dto.dart b/lib/dto/ethereum/pending_eth_tx_dto.dart deleted file mode 100644 index a19a95f61..000000000 --- a/lib/dto/ethereum/pending_eth_tx_dto.dart +++ /dev/null @@ -1,160 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -/// blockHash : null -/// blockNumber : null -/// from : "0x..." -/// gas : "0x7e562" -/// maxPriorityFeePerGas : "0x444380" -/// maxFeePerGas : "0x342570c00" -/// hash : "0x...da64e4" -/// input : "....." -/// nonce : "0x70" -/// to : "0x00....." -/// transactionIndex : null -/// value : "0x0" -/// type : "0x2" -/// accessList : [] -/// chainId : "0x1" -/// v : "0x0" -/// r : "0xd..." -/// s : "0x17d...6e6" - -class PendingEthTxDto { - PendingEthTxDto({ - required this.blockHash, - required this.blockNumber, - required this.from, - required this.gas, - required this.maxPriorityFeePerGas, - required this.maxFeePerGas, - required this.hash, - required this.input, - required this.nonce, - required this.to, - required this.transactionIndex, - required this.value, - required this.type, - required this.accessList, - required this.chainId, - required this.v, - required this.r, - required this.s, - }); - - factory PendingEthTxDto.fromMap(Map map) => PendingEthTxDto( - blockHash: map['blockHash'] as String?, - blockNumber: map['blockNumber'] as int?, - from: map['from'] as String, - gas: map['gas'] as String, - maxPriorityFeePerGas: map['maxPriorityFeePerGas'] as String, - maxFeePerGas: map['maxFeePerGas'] as String, - hash: map['hash'] as String, - input: map['input'] as String, - nonce: map['nonce'] as String, - to: map['to'] as String, - transactionIndex: map['transactionIndex'] as int?, - value: map['value'] as String, - type: map['type'] as String, - accessList: map['accessList'] as List? ?? [], - chainId: map['chainId'] as String, - v: map['v'] as String, - r: map['r'] as String, - s: map['s'] as String, - ); - - final String? blockHash; - final int? blockNumber; - final String from; - final String gas; - final String maxPriorityFeePerGas; - final String maxFeePerGas; - final String hash; - final String input; - final String nonce; - final String to; - final int? transactionIndex; - final String value; - final String type; - final List accessList; - final String chainId; - final String v; - final String r; - final String s; - - PendingEthTxDto copyWith({ - String? blockHash, - int? blockNumber, - String? from, - String? gas, - String? maxPriorityFeePerGas, - String? maxFeePerGas, - String? hash, - String? input, - String? nonce, - String? to, - int? transactionIndex, - String? value, - String? type, - List? accessList, - String? chainId, - String? v, - String? r, - String? s, - }) => - PendingEthTxDto( - blockHash: blockHash ?? this.blockHash, - blockNumber: blockNumber ?? this.blockNumber, - from: from ?? this.from, - gas: gas ?? this.gas, - maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, - maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, - hash: hash ?? this.hash, - input: input ?? this.input, - nonce: nonce ?? this.nonce, - to: to ?? this.to, - transactionIndex: transactionIndex ?? this.transactionIndex, - value: value ?? this.value, - type: type ?? this.type, - accessList: accessList ?? this.accessList, - chainId: chainId ?? this.chainId, - v: v ?? this.v, - r: r ?? this.r, - s: s ?? this.s, - ); - - Map toMap() { - final map = {}; - map['blockHash'] = blockHash; - map['blockNumber'] = blockNumber; - map['from'] = from; - map['gas'] = gas; - map['maxPriorityFeePerGas'] = maxPriorityFeePerGas; - map['maxFeePerGas'] = maxFeePerGas; - map['hash'] = hash; - map['input'] = input; - map['nonce'] = nonce; - map['to'] = to; - map['transactionIndex'] = transactionIndex; - map['value'] = value; - map['type'] = type; - map['accessList'] = accessList; - map['chainId'] = chainId; - map['v'] = v; - map['r'] = r; - map['s'] = s; - return map; - } - - @override - String toString() { - return toMap().toString(); - } -} diff --git a/lib/main.dart b/lib/main.dart index ccf2ecf6e..ba3583668 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -200,6 +200,7 @@ void main(List args) async { await Logging.instance.initialize( (await StackFileSystem.applicationLogsDirectory(Prefs.instance)).path, level: Prefs.instance.logLevel, + debugConsoleLevel: kDebugMode ? Level.trace : null, ); await xelis_api.setUpRustLogger(); diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index b7a92c26e..378e93d0f 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -135,8 +135,9 @@ class TransactionV2 { return Amount.zeroWith(fractionDigits: fractionDigits); } - final inSum = - inputs.map((e) => e.value).reduce((value, element) => value += element); + final inSum = inputs + .map((e) => e.value) + .reduce((value, element) => value += element); final outSum = outputs .map((e) => e.value) .reduce((value, element) => value += element); @@ -161,15 +162,20 @@ class TransactionV2 { } Amount getAmountSparkSelfMinted({required int fractionDigits}) { - final outSum = outputs.where((e) { - final op = e.scriptPubKeyHex.substring(0, 2).toUint8ListFromHex.first; - return e.walletOwns && (op == OP_SPARKMINT); - }).fold(BigInt.zero, (p, e) => p + e.value); + final outSum = outputs + .where((e) { + final op = e.scriptPubKeyHex.substring(0, 2).toUint8ListFromHex.first; + return e.walletOwns && (op == OP_SPARKMINT); + }) + .fold(BigInt.zero, (p, e) => p + e.value); return Amount(rawValue: outSum, fractionDigits: fractionDigits); } - Amount getAmountSentFromThisWallet({required int fractionDigits}) { + Amount getAmountSentFromThisWallet({ + required int fractionDigits, + required bool subtractFee, + }) { if (_isMonero()) { if (type == TransactionType.outgoing) { return _getMoneroAmount()!; @@ -182,15 +188,11 @@ class TransactionV2 { .where((e) => e.walletOwns) .fold(BigInt.zero, (p, e) => p + e.value); - Amount amount = Amount( - rawValue: inSum, - fractionDigits: fractionDigits, - ) - - getAmountReceivedInThisWallet( - fractionDigits: fractionDigits, - ); + Amount amount = + Amount(rawValue: inSum, fractionDigits: fractionDigits) - + getAmountReceivedInThisWallet(fractionDigits: fractionDigits); - if (subType != TransactionSubType.ethToken) { + if (subtractFee) { amount = amount - getFee(fractionDigits: fractionDigits); } @@ -204,9 +206,9 @@ class TransactionV2 { } Set associatedAddresses() => { - ...inputs.map((e) => e.addresses).expand((e) => e), - ...outputs.map((e) => e.addresses).expand((e) => e), - }; + ...inputs.map((e) => e.addresses).expand((e) => e), + ...outputs.map((e) => e.addresses).expand((e) => e), + }; Amount? _getOverrideFee() { try { @@ -238,7 +240,8 @@ class TransactionV2 { required int minConfirms, required int minCoinbaseConfirms, }) { - String prettyConfirms() => "(" + String prettyConfirms() => + "(" "${getConfirmations(currentChainHeight)}" "/" "${(isCoinbase() ? minCoinbaseConfirms : minConfirms)}" diff --git a/lib/models/paymint/fee_object_model.dart b/lib/models/paymint/fee_object_model.dart index 3745f997d..0c00f807f 100644 --- a/lib/models/paymint/fee_object_model.dart +++ b/lib/models/paymint/fee_object_model.dart @@ -9,9 +9,9 @@ */ class FeeObject { - final int fast; - final int medium; - final int slow; + final BigInt fast; + final BigInt medium; + final BigInt slow; final int numberOfBlocksFast; final int numberOfBlocksAverage; @@ -26,19 +26,22 @@ class FeeObject { required this.slow, }); - factory FeeObject.fromJson(Map json) { - return FeeObject( - fast: json['fast'] as int, - medium: json['average'] as int, - slow: json['slow'] as int, - numberOfBlocksFast: json['numberOfBlocksFast'] as int, - numberOfBlocksAverage: json['numberOfBlocksAverage'] as int, - numberOfBlocksSlow: json['numberOfBlocksSlow'] as int, - ); - } - @override String toString() { return "{fast: $fast, medium: $medium, slow: $slow, numberOfBlocksFast: $numberOfBlocksFast, numberOfBlocksAverage: $numberOfBlocksAverage, numberOfBlocksSlow: $numberOfBlocksSlow}"; } } + +class EthFeeObject extends FeeObject { + final BigInt suggestBaseFee; + + EthFeeObject({ + required this.suggestBaseFee, + required super.numberOfBlocksFast, + required super.numberOfBlocksAverage, + required super.numberOfBlocksSlow, + required super.fast, + required super.medium, + required super.slow, + }); +} diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 239b56db4..d28bc8277 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -19,7 +19,6 @@ import '../../models/isar/models/isar_models.dart'; import '../../models/trade_wallet_lookup.dart'; import '../../notifications/show_flush_bar.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; -import '../../providers/db/main_db_provider.dart'; import '../../providers/providers.dart'; import '../../route_generator.dart'; import '../../themes/stack_colors.dart'; @@ -564,24 +563,32 @@ class _ConfirmChangeNowSendViewState (value) => value.getPrice(coin), ), ); - final amountWithoutChange = - widget.txData.amountWithoutChange!; - final value = (price.item1 * - amountWithoutChange.decimal) - .toAmount(fractionDigits: 2); - final currency = ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, - ), - ); - final locale = ref.watch( - localeServiceChangeNotifierProvider.select( - (value) => value.locale, - ), - ); + final String extra; + if (price == null) { + extra = ""; + } else { + final amountWithoutChange = + widget.txData.amountWithoutChange!; + final value = (price.value * + amountWithoutChange.decimal) + .toAmount(fractionDigits: 2); + final currency = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), + ); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); + + extra = + " | ${value.fiatString(locale: locale)} $currency"; + } return Text( - " | ${value.fiatString(locale: locale)} $currency", + extra, style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( diff --git a/lib/pages/namecoin_names/confirm_name_transaction_view.dart b/lib/pages/namecoin_names/confirm_name_transaction_view.dart index ec0fc926a..11dbb5c16 100644 --- a/lib/pages/namecoin_names/confirm_name_transaction_view.dart +++ b/lib/pages/namecoin_names/confirm_name_transaction_view.dart @@ -33,6 +33,7 @@ import '../../utilities/constants.dart'; import '../../utilities/logger.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/coins/ethereum.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; import '../../wallets/models/tx_data.dart'; import '../../wallets/wallet/impl/namecoin_wallet.dart'; @@ -96,11 +97,7 @@ class _ConfirmNameTransactionViewState ), ); - final time = Future.delayed( - const Duration( - milliseconds: 2500, - ), - ); + final time = Future.delayed(const Duration(milliseconds: 2500)); final List txids = []; Future txDataFuture; @@ -111,10 +108,7 @@ class _ConfirmNameTransactionViewState txDataFuture = wallet.confirmSend(txData: widget.txData); // await futures in parallel - final futureResults = await Future.wait([ - txDataFuture, - time, - ]); + final futureResults = await Future.wait([txDataFuture, time]); final txData = (futureResults.first as TxData); @@ -126,7 +120,9 @@ class _ConfirmNameTransactionViewState Future.delayed(const Duration(seconds: 5)), // associated name data for reg tx - ref.read(secureStoreProvider).write( + ref + .read(secureStoreProvider) + .write( key: nameSaltKeyBuilder( txData.txid!, walletId, @@ -141,16 +137,16 @@ class _ConfirmNameTransactionViewState ]); txids.add(txData.txid!); - ref.refresh(desktopUseUTXOs); + if (coin is! Ethereum) { + ref.refresh(desktopUseUTXOs); + } // save note for (final txid in txids) { - await ref.read(mainDBProvider).putTransactionNote( - TransactionNote( - walletId: walletId, - txid: txid, - value: note, - ), + await ref + .read(mainDBProvider) + .putTransactionNote( + TransactionNote(walletId: walletId, txid: txid, value: note), ); } @@ -192,13 +188,8 @@ class _ConfirmNameTransactionViewState mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - niceError, - style: STextStyles.desktopH3(context), - ), - const SizedBox( - height: 24, - ), + Text(niceError, style: STextStyles.desktopH3(context)), + const SizedBox(height: 24), Flexible( child: SingleChildScrollView( child: SelectableText( @@ -207,9 +198,7 @@ class _ConfirmNameTransactionViewState ), ), ), - const SizedBox( - height: 56, - ), + const SizedBox(height: 56), Row( children: [ const Spacer(), @@ -237,9 +226,10 @@ class _ConfirmNameTransactionViewState child: Text( "Ok", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), onPressed: () { @@ -284,82 +274,79 @@ class _ConfirmNameTransactionViewState return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Confirm transaction", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), ), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: child, + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), ), ), - ), - ), - ); - }, + ); + }, + ), + ), ), - ), - ), child: ConditionalParent( condition: isDesktop, - builder: (child) => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Row( + builder: + (child) => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, children: [ - AppBarBackButton( - size: 40, - iconSize: 24, - onPressed: () => Navigator.of( - context, - rootNavigator: true, - ).pop(), - ), - Text( - "Confirm transaction", - style: STextStyles.desktopH3(context), + Row( + children: [ + AppBarBackButton( + size: 40, + iconSize: 24, + onPressed: + () => + Navigator.of(context, rootNavigator: true).pop(), + ), + Text( + "Confirm transaction", + style: STextStyles.desktopH3(context), + ), + ], ), + Flexible(child: SingleChildScrollView(child: child)), ], ), - Flexible( - child: SingleChildScrollView( - child: child, - ), - ), - ], - ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, @@ -372,20 +359,13 @@ class _ConfirmNameTransactionViewState "Confirm Name transaction", style: STextStyles.pageTitleH1(context), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - "Name", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), + Text("Name", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), Text( widget.txData.opNameState!.name, style: STextStyles.itemSubtitle12(context), @@ -393,20 +373,13 @@ class _ConfirmNameTransactionViewState ], ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - "Value", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), + Text("Value", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), Text( widget.txData.opNameState!.value, style: STextStyles.itemSubtitle12(context), @@ -414,9 +387,7 @@ class _ConfirmNameTransactionViewState ], ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -425,9 +396,7 @@ class _ConfirmNameTransactionViewState "Recipient", style: STextStyles.smallMed12(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Text( widget.txData.recipients!.first.address, style: STextStyles.itemSubtitle12(context), @@ -435,30 +404,23 @@ class _ConfirmNameTransactionViewState ], ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - "Amount", - style: STextStyles.smallMed12(context), - ), + Text("Amount", style: STextStyles.smallMed12(context)), SelectableText( - ref.watch(pAmountFormatter(coin)).format( - amountWithoutChange, - ), + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange), style: STextStyles.itemSubtitle12(context), textAlign: TextAlign.right, ), ], ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), RoundedWhiteContainer( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -476,9 +438,7 @@ class _ConfirmNameTransactionViewState ), ), if (widget.txData.fee != null && widget.txData.vSize != null) - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), if (widget.txData.fee != null && widget.txData.vSize != null) RoundedWhiteContainer( child: Row( @@ -488,9 +448,7 @@ class _ConfirmNameTransactionViewState "sats/vByte", style: STextStyles.smallMed12(context), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SelectableText( "~${fee.raw.toInt() ~/ widget.txData.vSize!}", style: STextStyles.itemSubtitle12(context), @@ -500,22 +458,15 @@ class _ConfirmNameTransactionViewState ), if (widget.txData.note != null && widget.txData.note!.isNotEmpty) - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), if (widget.txData.note != null && widget.txData.note!.isNotEmpty) RoundedWhiteContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - "Note", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 4, - ), + Text("Note", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), SelectableText( widget.txData.note!, style: STextStyles.itemSubtitle12(context), @@ -543,9 +494,10 @@ class _ConfirmNameTransactionViewState children: [ Container( decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .background, + color: + Theme.of( + context, + ).extension()!.background, borderRadius: BorderRadius.only( topLeft: Radius.circular( Constants.size.circularBorderRadius, @@ -573,9 +525,7 @@ class _ConfirmNameTransactionViewState width: 32, height: 32, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Text( "Send $unit Name transaction", style: STextStyles.desktopTextMedium(context), @@ -596,17 +546,16 @@ class _ConfirmNameTransactionViewState context, ), ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), SelectableText( widget.txData.opNameState!.name, style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, + color: + Theme.of( + context, + ).extension()!.textDark, ), ), ], @@ -614,9 +563,10 @@ class _ConfirmNameTransactionViewState ), Container( height: 1, - color: Theme.of(context) - .extension()! - .background, + color: + Theme.of( + context, + ).extension()!.background, ), Padding( padding: const EdgeInsets.all(12), @@ -630,17 +580,16 @@ class _ConfirmNameTransactionViewState context, ), ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), SelectableText( widget.txData.opNameState!.value, style: STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, + color: + Theme.of( + context, + ).extension()!.textDark, ), ), ], @@ -652,27 +601,24 @@ class _ConfirmNameTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - ), + padding: const EdgeInsets.only(left: 32, right: 32), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ SelectableText( "Note (optional)", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -684,11 +630,13 @@ class _ConfirmNameTransactionViewState enableSuggestions: isDesktop ? false : true, controller: noteController, focusNode: _noteFocusNode, - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, height: 1.8, ), onChanged: (_) => setState(() {}), @@ -704,41 +652,37 @@ class _ConfirmNameTransactionViewState bottom: 12, right: 5, ), - suffixIcon: noteController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState( - () => noteController.text = "", - ); - }, - ), - ], + suffixIcon: + noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState( + () => noteController.text = "", + ); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, ), ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), ], ), ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 16, - left: 32, - ), + padding: const EdgeInsets.only(top: 16, left: 32), child: Text( "Amount", style: STextStyles.desktopTextExtraExtraSmall(context), @@ -746,19 +690,16 @@ class _ConfirmNameTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, - ), + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, child: Builder( builder: (context) { final externalCalls = ref.watch( @@ -769,21 +710,21 @@ class _ConfirmNameTransactionViewState String fiatAmount = "N/A"; if (externalCalls) { - final price = ref - .read( - priceAnd24hChangeNotifierProvider, - ) - .getPrice(coin) - .item1; - if (price > Decimal.zero) { + final price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getPrice(coin) + ?.value; + if (price != null && price > Decimal.zero) { fiatAmount = (amountWithoutChange.decimal * price) .toAmount(fractionDigits: 2) .fiatString( - locale: ref - .read( - localeServiceChangeNotifierProvider, - ) - .locale, + locale: + ref + .read( + localeServiceChangeNotifierProvider, + ) + .locale, ); } } @@ -791,30 +732,20 @@ class _ConfirmNameTransactionViewState return Row( children: [ SelectableText( - ref.watch(pAmountFormatter(coin)).format( - amountWithoutChange, - ), - style: STextStyles.itemSubtitle( - context, - ), + ref + .watch(pAmountFormatter(coin)) + .format(amountWithoutChange), + style: STextStyles.itemSubtitle(context), ), if (externalCalls) Text( " | ", - style: STextStyles.itemSubtitle( - context, - ), + style: STextStyles.itemSubtitle(context), ), if (externalCalls) SelectableText( - "~$fiatAmount ${ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, - ), - )}", - style: STextStyles.itemSubtitle( - context, - ), + "~$fiatAmount ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.itemSubtitle(context), ), ], ); @@ -824,10 +755,7 @@ class _ConfirmNameTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 16, - left: 32, - ), + padding: const EdgeInsets.only(top: 16, left: 32), child: Text( "Recipient", style: STextStyles.desktopTextExtraExtraSmall(context), @@ -835,19 +763,16 @@ class _ConfirmNameTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, - ), + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, child: SelectableText( widget.txData.recipients!.first.address, style: STextStyles.itemSubtitle(context), @@ -857,10 +782,7 @@ class _ConfirmNameTransactionViewState // todo amoutn here if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 16, - left: 32, - ), + padding: const EdgeInsets.only(top: 16, left: 32), child: Text( "Transaction fee", style: STextStyles.desktopTextExtraExtraSmall(context), @@ -868,19 +790,16 @@ class _ConfirmNameTransactionViewState ), if (isDesktop) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, - ), + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, child: SelectableText( ref.watch(pAmountFormatter(coin)).format(fee!), style: STextStyles.itemSubtitle(context), @@ -891,10 +810,7 @@ class _ConfirmNameTransactionViewState widget.txData.fee != null && widget.txData.vSize != null) Padding( - padding: const EdgeInsets.only( - top: 16, - left: 32, - ), + padding: const EdgeInsets.only(top: 16, left: 32), child: Text( "sats/vByte", style: STextStyles.desktopTextExtraExtraSmall(context), @@ -904,19 +820,16 @@ class _ConfirmNameTransactionViewState widget.txData.fee != null && widget.txData.vSize != null) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, - ), + padding: const EdgeInsets.only(top: 10, left: 32, right: 32), child: RoundedContainer( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 18, ), - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, child: SelectableText( "~${fee!.raw.toInt() ~/ widget.txData.vSize!}", style: STextStyles.itemSubtitle(context), @@ -924,74 +837,78 @@ class _ConfirmNameTransactionViewState ), ), if (!isDesktop) const Spacer(), - SizedBox( - height: isDesktop ? 23 : 12, - ), + SizedBox(height: isDesktop ? 23 : 12), Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - horizontal: 32, - ) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.symmetric(horizontal: 32) + : 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, + 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( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ) - : STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.titleBold12(context).copyWith( + color: + Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), ), SelectableText( ref .watch(pAmountFormatter(coin)) .format(amountWithoutChange + fee!), - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ) - : STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), + 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, - ), + SizedBox(height: isDesktop ? 28 : 16), Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - horizontal: 32, - ) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.symmetric(horizontal: 32) + : const EdgeInsets.all(0), child: PrimaryButton( label: "Send", buttonHeight: isDesktop ? ButtonHeight.l : null, @@ -1001,31 +918,28 @@ class _ConfirmNameTransactionViewState if (isDesktop) { unlocked = await showDialog( context: context, - builder: (context) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Row( - mainAxisAlignment: MainAxisAlignment.end, + builder: + (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - DesktopDialogCloseButton(), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [DesktopDialogCloseButton()], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopAuthSend(coin: coin), + ), ], ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: DesktopAuthSend( - coin: coin, - ), - ), - ], - ), - ), + ), ); } else { unlocked = await Navigator.push( @@ -1033,18 +947,21 @@ class _ConfirmNameTransactionViewState RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => const LockscreenView( - showBackButton: true, - popOnSuccess: true, - routeOnSuccessArguments: true, - routeOnSuccess: "", - biometricsCancelButtonString: "CANCEL", - biometricsLocalizedReason: - "Authenticate to send transaction", - biometricsAuthenticationTitle: "Confirm Transaction", + builder: + (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: + "Authenticate to send transaction", + biometricsAuthenticationTitle: + "Confirm Transaction", + ), + settings: const RouteSettings( + name: "/confirmsendlockscreen", ), - settings: - const RouteSettings(name: "/confirmsendlockscreen"), ), ); } @@ -1057,9 +974,10 @@ class _ConfirmNameTransactionViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: Util.isDesktop - ? "Invalid passphrase" - : "Invalid PIN", + message: + Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", context: context, ), ); @@ -1069,10 +987,7 @@ class _ConfirmNameTransactionViewState }, ), ), - if (isDesktop) - const SizedBox( - height: 32, - ), + if (isDesktop) const SizedBox(height: 32), ], ), ), diff --git a/lib/pages/paynym/subwidgets/desktop_paynym_details.dart b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart index 6b08b84db..3185144f2 100644 --- a/lib/pages/paynym/subwidgets/desktop_paynym_details.dart +++ b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart @@ -62,12 +62,11 @@ class _PaynymDetailsPopupState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => WillPopScope( - onWillPop: () async => canPop, - child: const LoadingIndicator( - width: 200, - ), - ), + builder: + (context) => WillPopScope( + onWillPop: () async => canPop, + child: const LoadingIndicator(width: 200), + ), ), ); @@ -111,45 +110,47 @@ class _PaynymDetailsPopupState extends ConsumerState { // show info pop up await showDialog( context: context, - builder: (context) => ConfirmPaynymConnectDialog( - nymName: widget.accountLite.nymName, - locale: ref.read(localeServiceChangeNotifierProvider).locale, - onConfirmPressed: () { - Navigator.of(context, rootNavigator: true).pop(); - unawaited( - showDialog( - context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: ConfirmTransactionView( - walletId: widget.walletId, - isPaynymNotificationTransaction: true, - txData: preparedTx, - onSuccess: () { - // do nothing extra - }, - onSuccessInsteadOfRouteOnSuccess: () { - Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context, rootNavigator: true).pop(); - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: - "Connection initiated to ${widget.accountLite.nymName}", - iconAsset: Assets.svg.copy, - context: context, + builder: + (context) => ConfirmPaynymConnectDialog( + nymName: widget.accountLite.nymName, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + onConfirmPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: ConfirmTransactionView( + walletId: widget.walletId, + isPaynymNotificationTransaction: true, + txData: preparedTx, + onSuccess: () { + // do nothing extra + }, + onSuccessInsteadOfRouteOnSuccess: () { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: + "Connection initiated to ${widget.accountLite.nymName}", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), ), - ); - }, ), - ), - ), - ); - }, - amount: preparedTx.amount! + preparedTx.fee!, - coin: ref.read(pWalletCoin(widget.walletId)), - ), + ); + }, + amount: preparedTx.amount! + preparedTx.fee!, + coin: ref.read(pWalletCoin(widget.walletId)), + ), ); } } @@ -157,10 +158,11 @@ class _PaynymDetailsPopupState extends ConsumerState { Future _onSend() async { await showDialog( context: context, - builder: (context) => DesktopPaynymSendDialog( - walletId: widget.walletId, - accountLite: widget.accountLite, - ), + builder: + (context) => DesktopPaynymSendDialog( + walletId: widget.walletId, + accountLite: widget.accountLite, + ), ); } @@ -185,9 +187,7 @@ class _PaynymDetailsPopupState extends ConsumerState { paymentCodeString: widget.accountLite.code, size: 36, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -196,8 +196,9 @@ class _PaynymDetailsPopupState extends ConsumerState { style: STextStyles.desktopTextSmall(context), ), FutureBuilder( - future: paynymWallet - .hasConnected(widget.accountLite.code), + future: paynymWallet.hasConnected( + widget.accountLite.code, + ), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && @@ -205,16 +206,16 @@ class _PaynymDetailsPopupState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), Text( "Connected", - style: STextStyles.desktopTextSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorGreen, + style: STextStyles.desktopTextSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorGreen, ), ), ], @@ -228,15 +229,14 @@ class _PaynymDetailsPopupState extends ConsumerState { ), ], ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Row( children: [ Expanded( child: FutureBuilder( - future: - paynymWallet.hasConnected(widget.accountLite.code), + future: paynymWallet.hasConnected( + widget.accountLite.code, + ), builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && @@ -249,9 +249,10 @@ class _PaynymDetailsPopupState extends ConsumerState { Assets.svg.circleArrowUpRight, width: 16, height: 16, - color: Theme.of(context) - .extension()! - .buttonTextPrimary, + color: + Theme.of(context) + .extension()! + .buttonTextPrimary, ), iconSpacing: 6, onPressed: _onSend, @@ -264,9 +265,10 @@ class _PaynymDetailsPopupState extends ConsumerState { Assets.svg.circlePlusFilled, width: 16, height: 16, - color: Theme.of(context) - .extension()! - .buttonTextPrimary, + color: + Theme.of(context) + .extension()! + .buttonTextPrimary, ), iconSpacing: 6, onPressed: _onConnectPressed, @@ -281,44 +283,41 @@ class _PaynymDetailsPopupState extends ConsumerState { }, ), ), - const SizedBox( - width: 20, - ), + const SizedBox(width: 20), kDisableFollowing ? const Spacer() : Expanded( - child: PaynymFollowToggleButton( - walletId: widget.walletId, - paymentCodeStringToFollow: - widget.accountLite.code, - style: - PaynymFollowToggleButtonStyle.detailsDesktop, - ), + child: PaynymFollowToggleButton( + walletId: widget.walletId, + paymentCodeStringToFollow: widget.accountLite.code, + style: PaynymFollowToggleButtonStyle.detailsDesktop, ), + ), ], ), if (_showInsufficientFundsInfo) Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), RoundedContainer( - color: Theme.of(context) - .extension()! - .warningBackground, + color: + Theme.of( + context, + ).extension()!.warningBackground, child: Text( "Adding a PayNym to your contacts requires a one-time " "transaction fee for creating the record on the " "blockchain. Please deposit more " "${ref.watch(pWalletCoin(widget.walletId)).ticker} " "into your wallet and try again.", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.warningForeground, ), ), ), @@ -341,9 +340,7 @@ class _PaynymDetailsPopupState extends ConsumerState { "PayNym address", style: STextStyles.desktopTextExtraExtraSmall(context), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Row( children: [ Expanded( @@ -351,18 +348,18 @@ class _PaynymDetailsPopupState extends ConsumerState { constraints: const BoxConstraints(minHeight: 100), child: Text( widget.accountLite.code, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, ), ), ), ), - const SizedBox( - width: 20, - ), + const SizedBox(width: 20), QR( padding: const EdgeInsets.all(0), size: 100, @@ -370,16 +367,12 @@ class _PaynymDetailsPopupState extends ConsumerState { ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), CustomTextButton( text: "Copy", onTap: () async { await Clipboard.setData( - ClipboardData( - text: widget.accountLite.code, - ), + ClipboardData(text: widget.accountLite.code), ); unawaited( showFloatingFlushBar( diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index 6d40734b0..1e0242cbc 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -21,7 +21,6 @@ import '../../models/isar/models/transaction_note.dart'; import '../../notifications/show_flush_bar.dart'; import '../../pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; -import '../../providers/db/main_db_provider.dart'; import '../../providers/providers.dart'; import '../../providers/wallet/public_private_balance_state_provider.dart'; import '../../route_generator.dart'; @@ -33,6 +32,7 @@ import '../../utilities/constants.dart'; import '../../utilities/text_styles.dart'; import '../../utilities/util.dart'; import '../../wallets/crypto_currency/coins/epiccash.dart'; +import '../../wallets/crypto_currency/coins/ethereum.dart'; import '../../wallets/crypto_currency/intermediate/nano_currency.dart'; import '../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; @@ -181,7 +181,9 @@ class _ConfirmTransactionViewState } else { txids.add((results.first as TxData).txid!); } - ref.refresh(desktopUseUTXOs); + if (coin is! Ethereum) { + ref.refresh(desktopUseUTXOs); + } // save note for (final txid in txids) { @@ -201,7 +203,7 @@ class _ConfirmTransactionViewState widget.onSuccess.call(); // pop back to wallet - if (mounted) { + if (context.mounted) { if (widget.onSuccessInsteadOfRouteOnSuccess == null) { Navigator.of( context, @@ -211,7 +213,7 @@ class _ConfirmTransactionViewState } } } on BadEpicHttpAddressException catch (_) { - if (mounted) { + if (context.mounted) { // pop building dialog Navigator.of(context).pop(); unawaited( @@ -228,77 +230,79 @@ class _ConfirmTransactionViewState //todo: comeback to this debugPrint("$e\n$s"); // pop sending dialog - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - if (isDesktop) { - return DesktopDialog( - maxWidth: 450, - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Broadcast transaction failed", - style: STextStyles.desktopH3(context), - ), - const SizedBox(height: 24), - Flexible( - child: SingleChildScrollView( - child: SelectableText( - e.toString(), - style: STextStyles.smallMed14(context), - ), + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Broadcast transaction failed", + style: STextStyles.desktopH3(context), ), - ), - const SizedBox(height: 56), - Row( - children: [ - const Spacer(), - Expanded( - child: PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Ok", - onPressed: Navigator.of(context).pop, + const SizedBox(height: 24), + Flexible( + child: SingleChildScrollView( + child: SelectableText( + e.toString(), + style: STextStyles.smallMed14(context), ), ), - ], - ), - ], + ), + const SizedBox(height: 56), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ], + ), ), - ), - ); - } else { - return StackDialog( - title: "Broadcast transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonStyle(context), - child: Text( - "Ok", - style: STextStyles.button(context).copyWith( - color: - Theme.of( - context, - ).extension()!.accentColorDark, + ); + } else { + return StackDialog( + title: "Broadcast transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: + Theme.of( + context, + ).extension()!.accentColorDark, + ), ), + onPressed: () { + Navigator.of(context).pop(); + }, ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - } - }, - ); + ); + } + }, + ); + } } } @@ -533,6 +537,21 @@ class _ConfirmTransactionViewState ], ), ), + if (coin is Ethereum) const SizedBox(height: 12), + if (coin is Ethereum) + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Nonce", style: STextStyles.smallMed12(context)), + SelectableText( + widget.txData.nonce.toString(), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), if (widget.txData.fee != null && widget.txData.vSize != null) const SizedBox(height: 12), if (widget.txData.fee != null && widget.txData.vSize != null) @@ -685,14 +704,14 @@ class _ConfirmTransactionViewState .tokenContract .address, ) - .item1 + ?.value : ref .read( priceAnd24hChangeNotifierProvider, ) .getPrice(coin) - .item1; - if (price > Decimal.zero) { + ?.value; + if (price != null && price > Decimal.zero) { fiatAmount = (amountWithoutChange.decimal * price) .toAmount(fractionDigits: 2) @@ -836,6 +855,42 @@ class _ConfirmTransactionViewState ], ), ), + if (coin is Ethereum) + Container( + height: 1, + color: + Theme.of( + context, + ).extension()!.background, + ), + if (coin is Ethereum) + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Nonce", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 2), + SelectableText( + widget.txData.nonce.toString(), + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ), + ), + ], + ), + ), // Container( // height: 1, // color: Theme.of(context) @@ -1211,7 +1266,9 @@ class _ConfirmTransactionViewState unawaited( showFloatingFlushBar( type: FlushBarType.warning, - message: "Invalid PIN", + message: Util.isDesktop + ? "Invalid passphrase" + : "Invalid PIN", context: context, ), ); diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index 57a78ad08..3c907605e 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -40,6 +40,7 @@ import '../../utilities/barcode_scanner_interface.dart'; import '../../utilities/clipboard_interface.dart'; import '../../utilities/constants.dart'; import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../utilities/eth_commons.dart'; import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; import '../../utilities/prefs.dart'; @@ -58,6 +59,7 @@ import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/custom_buttons/blue_text_button.dart'; import '../../widgets/dialogs/firo_exchange_address_dialog.dart'; +import '../../widgets/eth_fee_form.dart'; import '../../widgets/fee_slider.dart'; import '../../widgets/icon_widgets/addressbook_icon.dart'; import '../../widgets/icon_widgets/clipboard_icon.dart'; @@ -99,6 +101,13 @@ class SendView extends ConsumerStatefulWidget { } class _SendViewState extends ConsumerState { + static const stringsToLoopThrough = [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ]; + late final String walletId; late final CryptoCurrency coin; late final ClipboardInterface clipboard; @@ -123,6 +132,7 @@ class _SendViewState extends ConsumerState { late final bool isStellar; late final bool isFiro; + late final bool isEth; Amount? _cachedAmountToSend; String? _address; @@ -314,10 +324,10 @@ class _SendViewState extends ConsumerState { ); final Amount? amount; if (baseAmount != null) { - final Decimal _price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + final _price = + ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin)?.value; - if (_price == Decimal.zero) { + if (_price == null || _price == Decimal.zero) { amount = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits); } else { amount = @@ -367,9 +377,9 @@ class _SendViewState extends ConsumerState { _cachedAmountToSend = amount; final price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin)?.value; - if (price > Decimal.zero) { + if (price != null && price > Decimal.zero) { baseAmountController.text = (amount.decimal * price) .toAmount(fractionDigits: 2) .fiatString( @@ -511,9 +521,9 @@ class _SendViewState extends ConsumerState { final wallet = ref.read(pWallets).getWallet(walletId); final feeObject = await wallet.fees; - late final int feeRate; + late final BigInt feeRate; - switch (ref.read(feeRateTypeStateProvider.state).state) { + switch (ref.read(feeRateTypeMobileStateProvider.state).state) { case FeeRateType.fast: feeRate = feeObject.fast; break; @@ -524,13 +534,13 @@ class _SendViewState extends ConsumerState { feeRate = feeObject.slow; break; default: - feeRate = -1; + feeRate = BigInt.from(-1); } Amount fee; if (coin is Monero) { lib_monero.TransactionPriority specialMoneroId; - switch (ref.read(feeRateTypeStateProvider.state).state) { + switch (ref.read(feeRateTypeMobileStateProvider.state).state) { case FeeRateType.fast: specialMoneroId = lib_monero.TransactionPriority.high; break; @@ -544,7 +554,10 @@ class _SendViewState extends ConsumerState { throw ArgumentError("custom fee not available for monero"); } - fee = await wallet.estimateFeeFor(amount, specialMoneroId.value); + fee = await wallet.estimateFeeFor( + amount, + BigInt.from(specialMoneroId.value), + ); cachedFees[amount] = ref .read(pAmountFormatter(coin)) .format(fee, withUnitName: true, indicatePrecisionLoss: false); @@ -699,7 +712,7 @@ class _SendViewState extends ConsumerState { Future txDataFuture; if (isPaynymSend) { - final feeRate = ref.read(feeRateTypeStateProvider); + final feeRate = ref.read(feeRateTypeMobileStateProvider); txDataFuture = (wallet as PaynymInterface).preparePaymentCodeSend( txData: TxData( paynymAccountLite: widget.accountLite!, @@ -710,7 +723,7 @@ class _SendViewState extends ConsumerState { isChange: false, ), ], - satsPerVByte: isCustomFee ? customFeeRate : null, + satsPerVByte: isCustomFee.value ? customFeeRate : null, feeRateType: feeRate, utxos: (wallet is CoinControlInterface && @@ -734,8 +747,8 @@ class _SendViewState extends ConsumerState { isChange: false, ), ], - feeRateType: ref.read(feeRateTypeStateProvider), - satsPerVByte: isCustomFee ? customFeeRate : null, + feeRateType: ref.read(feeRateTypeMobileStateProvider), + satsPerVByte: isCustomFee.value ? customFeeRate : null, utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty) ? selectedUTXOs @@ -748,8 +761,8 @@ class _SendViewState extends ConsumerState { recipients: [ (address: _address!, amount: amount, isChange: false), ], - feeRateType: ref.read(feeRateTypeStateProvider), - satsPerVByte: isCustomFee ? customFeeRate : null, + feeRateType: ref.read(feeRateTypeMobileStateProvider), + satsPerVByte: isCustomFee.value ? customFeeRate : null, utxos: (coinControlEnabled && selectedUTXOs.isNotEmpty) ? selectedUTXOs @@ -799,8 +812,9 @@ class _SendViewState extends ConsumerState { txData: TxData( recipients: [(address: _address!, amount: amount, isChange: false)], memo: memo, - feeRateType: ref.read(feeRateTypeStateProvider), - satsPerVByte: isCustomFee ? customFeeRate : null, + feeRateType: ref.read(feeRateTypeMobileStateProvider), + satsPerVByte: isCustomFee.value ? customFeeRate : null, + ethEIP1559Fee: ethFee, utxos: (wallet is CoinControlInterface && coinControlEnabled && @@ -947,9 +961,80 @@ class _SendViewState extends ConsumerState { bool get isPaynymSend => widget.accountLite != null; - bool isCustomFee = false; - + final isCustomFee = ValueNotifier(false); int customFeeRate = 1; + EthEIP1559Fee? ethFee; + + late final bool hasFees; + + void _onSendToAddressPasteButtonPressed() async { + final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring(0, content.indexOf("\n")); + } + + if (coin is Epiccash) { + // strip http:// and https:// if content contains @ + content = AddressUtils().formatAddress(content); + } + + final trimmed = content.trim(); + final parsed = AddressUtils.parsePaymentUri( + trimmed, + logging: Logging.instance, + ); + if (parsed != null) { + _applyUri(parsed); + } else { + sendToController.text = content; + _address = content; + + _setValidAddressProviders(_address); + + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } + } + + void _onFeeSelectPressed() { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: + (_) => TransactionFeeSelectionSheet( + walletId: walletId, + amount: (Decimal.tryParse(cryptoAmountController.text) ?? + ref.watch(pSendAmount)?.decimal ?? + Decimal.zero) + .toAmount(fractionDigits: coin.fractionDigits), + updateChosen: (String fee) { + if (fee == "custom") { + if (!isCustomFee.value) { + setState(() { + isCustomFee.value = true; + }); + } + return; + } + + _setCurrentFee(fee, true); + setState(() { + _calculateFeesFuture = Future(() => fee); + if (isCustomFee.value) { + isCustomFee.value = false; + } + }); + }, + ), + ); + } @override void initState() { @@ -958,6 +1043,10 @@ class _SendViewState extends ConsumerState { ref.refresh(feeSheetSessionCacheProvider); ref.refresh(pIsExchangeAddress); }); + isCustomFee.addListener(() { + if (!isCustomFee.value) ethFee = null; + }); + hasFees = coin is! Epiccash && coin is! NanoCurrency && coin is! Tezos; _currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits); _calculateFeesFuture = calculateFees( @@ -969,6 +1058,7 @@ class _SendViewState extends ConsumerState { scanner = widget.barcodeScanner; isStellar = coin is Stellar; isFiro = coin is Firo; + isEth = coin is Ethereum; sendToController = TextEditingController(); cryptoAmountController = TextEditingController(); @@ -1066,6 +1156,7 @@ class _SendViewState extends ConsumerState { _cryptoFocus.dispose(); _baseFocus.dispose(); _memoFocus.dispose(); + isCustomFee.dispose(); super.dispose(); } @@ -1137,6 +1228,15 @@ class _SendViewState extends ConsumerState { }); } + Decimal? price; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin)?.value, + ), + ); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -1297,13 +1397,14 @@ class _SendViewState extends ConsumerState { ).copyWith(fontSize: 10), textAlign: TextAlign.right, ), - Text( - "${(amount.decimal * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1))).toAmount(fractionDigits: 2).fiatString(locale: locale)} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.subtitle( - context, - ).copyWith(fontSize: 8), - textAlign: TextAlign.right, - ), + if (price != null) + Text( + "${(amount.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: locale)} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle( + context, + ).copyWith(fontSize: 8), + textAlign: TextAlign.right, + ), ], ), ), @@ -2127,21 +2228,18 @@ class _SendViewState extends ConsumerState { ), ), const SizedBox(height: 12), - if (coin is! Epiccash && - coin is! NanoCurrency && - coin is! Tezos) + if (hasFees) Text( - "Transaction fee (estimated)", + "Transaction fee ${isEth + ? isCustomFee.value + ? "" + : "(max)" + : "(estimated)"}", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - if (coin is! Epiccash && - coin is! NanoCurrency && - coin is! Tezos) - const SizedBox(height: 8), - if (coin is! Epiccash && - coin is! NanoCurrency && - coin is! Tezos) + if (hasFees) const SizedBox(height: 8), + if (hasFees) Stack( children: [ TextField( @@ -2176,68 +2274,7 @@ class _SendViewState extends ConsumerState { .state != FiroType.public ? null - : () { - showModalBottomSheet( - backgroundColor: - Colors.transparent, - context: context, - shape: - const RoundedRectangleBorder( - borderRadius: - BorderRadius.vertical( - top: - Radius.circular( - 20, - ), - ), - ), - builder: - ( - _, - ) => TransactionFeeSelectionSheet( - walletId: walletId, - amount: (Decimal.tryParse( - cryptoAmountController - .text, - ) ?? - ref - .watch( - pSendAmount, - ) - ?.decimal ?? - Decimal.zero) - .toAmount( - fractionDigits: - coin.fractionDigits, - ), - updateChosen: ( - String fee, - ) { - if (fee == "custom") { - if (!isCustomFee) { - setState(() { - isCustomFee = - true; - }); - } - return; - } - - _setCurrentFee( - fee, - true, - ); - setState(() { - _calculateFeesFuture = - Future(() => fee); - if (isCustomFee) { - isCustomFee = false; - } - }); - }, - ), - ); - }, + : _onFeeSelectPressed, child: (isFiro && ref @@ -2270,12 +2307,7 @@ class _SendViewState extends ConsumerState { } else { return AnimatedText( stringsToLoopThrough: - const [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ], + stringsToLoopThrough, style: STextStyles.itemSubtitle( context, @@ -2296,7 +2328,7 @@ class _SendViewState extends ConsumerState { Text( ref .watch( - feeRateTypeStateProvider + feeRateTypeMobileStateProvider .state, ) .state @@ -2323,7 +2355,7 @@ class _SendViewState extends ConsumerState { false, ); return Text( - isCustomFee + isCustomFee.value ? "" : "~${snapshot.data!}", style: @@ -2334,12 +2366,7 @@ class _SendViewState extends ConsumerState { } else { return AnimatedText( stringsToLoopThrough: - const [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ], + stringsToLoopThrough, style: STextStyles.itemSubtitle( context, @@ -2367,7 +2394,7 @@ class _SendViewState extends ConsumerState { ), ], ), - if (isCustomFee) + if (isCustomFee.value && !isEth) Padding( padding: const EdgeInsets.only( bottom: 12, @@ -2380,6 +2407,13 @@ class _SendViewState extends ConsumerState { }, ), ), + if (isCustomFee.value && isEth) + const SizedBox(height: 12), + if (isCustomFee.value && isEth) + EthFeeForm( + minGasLimit: kEthereumMinGasLimit, + stateChanged: (fee) => ethFee = fee, + ), const Spacer(), const SizedBox(height: 12), TextButton( @@ -2400,7 +2434,7 @@ class _SendViewState extends ConsumerState { style: STextStyles.button(context), ), ), - const SizedBox(height: 4), + const SizedBox(height: 16), ], ), ), diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 2586d88ee..41cdac17e 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -8,8 +8,8 @@ * */ -import 'package:flutter/material.dart'; import 'package:cs_monero/cs_monero.dart' as lib_monero; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../models/paymint/fee_object_model.dart'; @@ -32,8 +32,8 @@ import '../../../widgets/animated_text.dart'; final feeSheetSessionCacheProvider = ChangeNotifierProvider((ref) { - return FeeSheetSessionCache(); -}); + return FeeSheetSessionCache(); + }); class FeeSheetSessionCache extends ChangeNotifier { final Map fast = {}; @@ -79,7 +79,7 @@ class _TransactionFeeSelectionSheetState Future feeFor({ required Amount amount, required FeeRateType feeRateType, - required int feeRate, + required BigInt feeRate, required CryptoCurrency coin, }) async { switch (feeRateType) { @@ -91,27 +91,31 @@ class _TransactionFeeSelectionSheetState if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.high.value, + BigInt.from(lib_monero.TransactionPriority.high.value), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - await wallet.estimateFeeFor(amount, feeRate); + ref.read(feeSheetSessionCacheProvider).fast[amount] = await wallet + .estimateFeeFor(amount, feeRate); } } else { final tokenWallet = ref.read(pCurrentTokenWallet)!; @@ -128,21 +132,25 @@ class _TransactionFeeSelectionSheetState if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.medium.value, + BigInt.from(lib_monero.TransactionPriority.medium.value), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else { @@ -164,26 +172,30 @@ class _TransactionFeeSelectionSheetState if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.normal.value, + BigInt.from(lib_monero.TransactionPriority.normal.value), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - await wallet.estimateFeeFor(amount, feeRate); + ref.read(feeSheetSessionCacheProvider).slow[amount] = await wallet + .estimateFeeFor(amount, feeRate); } } else { final tokenWallet = ref.read(pCurrentTokenWallet)!; @@ -243,17 +255,10 @@ class _TransactionFeeSelectionSheetState return Container( decoration: BoxDecoration( color: Theme.of(context).extension()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), child: Padding( - padding: const EdgeInsets.only( - left: 24, - right: 24, - top: 10, - bottom: 0, - ), + padding: const EdgeInsets.only(left: 24, right: 24, top: 10, bottom: 0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -261,9 +266,10 @@ class _TransactionFeeSelectionSheetState Center( child: Container( decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), @@ -272,13 +278,12 @@ class _TransactionFeeSelectionSheetState height: 4, ), ), - const SizedBox( - height: 36, - ), + const SizedBox(height: 36), FutureBuilder( - future: widget.isToken - ? ref.read(pCurrentTokenWallet)!.fees - : wallet.fees, + future: + widget.isToken + ? ref.read(pCurrentTokenWallet)!.fees + : wallet.fees, builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { @@ -293,19 +298,21 @@ class _TransactionFeeSelectionSheetState style: STextStyles.pageTitleH2(context), textAlign: TextAlign.left, ), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), GestureDetector( onTap: () { final state = - ref.read(feeRateTypeStateProvider.state).state; + ref + .read(feeRateTypeMobileStateProvider.state) + .state; if (state != FeeRateType.fast) { - ref.read(feeRateTypeStateProvider.state).state = + ref.read(feeRateTypeMobileStateProvider.state).state = FeeRateType.fast; } - final String? fee = - getAmount(FeeRateType.fast, wallet.info.coin); + final String? fee = getAmount( + FeeRateType.fast, + wallet.info.coin, + ); if (fee != null) { widget.updateChosen(fee); } @@ -323,16 +330,24 @@ class _TransactionFeeSelectionSheetState width: 20, height: 20, child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, + activeColor: + Theme.of(context) + .extension()! + .radioButtonIconEnabled, value: FeeRateType.fast, - groupValue: ref - .watch(feeRateTypeStateProvider.state) - .state, + groupValue: + ref + .watch( + feeRateTypeMobileStateProvider + .state, + ) + .state, onChanged: (x) { ref - .read(feeRateTypeStateProvider.state) + .read( + feeRateTypeMobileStateProvider + .state, + ) .state = FeeRateType.fast; Navigator.of(context).pop(); @@ -341,9 +356,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -355,15 +368,14 @@ class _TransactionFeeSelectionSheetState style: STextStyles.titleBold12(context), textAlign: TextAlign.left, ), - const SizedBox( - width: 2, - ), + const SizedBox(width: 2), if (feeObject == null) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, - style: - STextStyles.itemSubtitle(context), + style: STextStyles.itemSubtitle( + context, + ), ), if (feeObject != null) FutureBuilder( @@ -381,15 +393,7 @@ class _TransactionFeeSelectionSheetState ConnectionState.done && snapshot.hasData) { return Text( - "(~${ref.watch( - pAmountFormatter( - coin, - ), - ).format( - snapshot.data!, - indicatePrecisionLoss: - false, - )})", + "(~${ref.watch(pAmountFormatter(coin)).format(snapshot.data!, indicatePrecisionLoss: false)})", style: STextStyles.itemSubtitle( context, ), @@ -408,9 +412,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (feeObject == null && coin is! Ethereum) AnimatedText( stringsToLoopThrough: @@ -433,19 +435,21 @@ class _TransactionFeeSelectionSheetState ), ), ), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), GestureDetector( onTap: () { final state = - ref.read(feeRateTypeStateProvider.state).state; + ref + .read(feeRateTypeMobileStateProvider.state) + .state; if (state != FeeRateType.average) { - ref.read(feeRateTypeStateProvider.state).state = + ref.read(feeRateTypeMobileStateProvider.state).state = FeeRateType.average; } - final String? fee = - getAmount(FeeRateType.average, coin); + final String? fee = getAmount( + FeeRateType.average, + coin, + ); if (fee != null) { widget.updateChosen(fee); } @@ -462,16 +466,24 @@ class _TransactionFeeSelectionSheetState width: 20, height: 20, child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, + activeColor: + Theme.of(context) + .extension()! + .radioButtonIconEnabled, value: FeeRateType.average, - groupValue: ref - .watch(feeRateTypeStateProvider.state) - .state, + groupValue: + ref + .watch( + feeRateTypeMobileStateProvider + .state, + ) + .state, onChanged: (x) { ref - .read(feeRateTypeStateProvider.state) + .read( + feeRateTypeMobileStateProvider + .state, + ) .state = FeeRateType.average; Navigator.of(context).pop(); }, @@ -479,9 +491,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -493,15 +503,14 @@ class _TransactionFeeSelectionSheetState style: STextStyles.titleBold12(context), textAlign: TextAlign.left, ), - const SizedBox( - width: 2, - ), + const SizedBox(width: 2), if (feeObject == null) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, - style: - STextStyles.itemSubtitle(context), + style: STextStyles.itemSubtitle( + context, + ), ), if (feeObject != null) FutureBuilder( @@ -519,15 +528,7 @@ class _TransactionFeeSelectionSheetState ConnectionState.done && snapshot.hasData) { return Text( - "(~${ref.watch( - pAmountFormatter( - coin, - ), - ).format( - snapshot.data!, - indicatePrecisionLoss: - false, - )})", + "(~${ref.watch(pAmountFormatter(coin)).format(snapshot.data!, indicatePrecisionLoss: false)})", style: STextStyles.itemSubtitle( context, ), @@ -546,9 +547,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (feeObject == null && coin is! Ethereum) AnimatedText( stringsToLoopThrough: @@ -571,15 +570,15 @@ class _TransactionFeeSelectionSheetState ), ), ), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), GestureDetector( onTap: () { final state = - ref.read(feeRateTypeStateProvider.state).state; + ref + .read(feeRateTypeMobileStateProvider.state) + .state; if (state != FeeRateType.slow) { - ref.read(feeRateTypeStateProvider.state).state = + ref.read(feeRateTypeMobileStateProvider.state).state = FeeRateType.slow; } final String? fee = getAmount(FeeRateType.slow, coin); @@ -599,16 +598,24 @@ class _TransactionFeeSelectionSheetState width: 20, height: 20, child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, + activeColor: + Theme.of(context) + .extension()! + .radioButtonIconEnabled, value: FeeRateType.slow, - groupValue: ref - .watch(feeRateTypeStateProvider.state) - .state, + groupValue: + ref + .watch( + feeRateTypeMobileStateProvider + .state, + ) + .state, onChanged: (x) { ref - .read(feeRateTypeStateProvider.state) + .read( + feeRateTypeMobileStateProvider + .state, + ) .state = FeeRateType.slow; Navigator.of(context).pop(); }, @@ -616,9 +623,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -630,15 +635,14 @@ class _TransactionFeeSelectionSheetState style: STextStyles.titleBold12(context), textAlign: TextAlign.left, ), - const SizedBox( - width: 2, - ), + const SizedBox(width: 2), if (feeObject == null) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, - style: - STextStyles.itemSubtitle(context), + style: STextStyles.itemSubtitle( + context, + ), ), if (feeObject != null) FutureBuilder( @@ -656,15 +660,7 @@ class _TransactionFeeSelectionSheetState ConnectionState.done && snapshot.hasData) { return Text( - "(~${ref.watch( - pAmountFormatter( - coin, - ), - ).format( - snapshot.data!, - indicatePrecisionLoss: - false, - )})", + "(~${ref.watch(pAmountFormatter(coin)).format(snapshot.data!, indicatePrecisionLoss: false)})", style: STextStyles.itemSubtitle( context, ), @@ -683,9 +679,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (feeObject == null && coin is! Ethereum) AnimatedText( stringsToLoopThrough: @@ -708,17 +702,18 @@ class _TransactionFeeSelectionSheetState ), ), ), - const SizedBox( - height: 24, - ), - if (wallet is ElectrumXInterface) + const SizedBox(height: 24), + if (wallet is ElectrumXInterface || coin is Ethereum) GestureDetector( onTap: () { final state = - ref.read(feeRateTypeStateProvider.state).state; + ref + .read(feeRateTypeMobileStateProvider.state) + .state; if (state != FeeRateType.custom) { - ref.read(feeRateTypeStateProvider.state).state = - FeeRateType.custom; + ref + .read(feeRateTypeMobileStateProvider.state) + .state = FeeRateType.custom; } widget.updateChosen("custom"); @@ -735,17 +730,23 @@ class _TransactionFeeSelectionSheetState width: 20, height: 20, child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, + activeColor: + Theme.of(context) + .extension()! + .radioButtonIconEnabled, value: FeeRateType.custom, - groupValue: ref - .watch(feeRateTypeStateProvider.state) - .state, + groupValue: + ref + .watch( + feeRateTypeMobileStateProvider + .state, + ) + .state, onChanged: (x) { ref .read( - feeRateTypeStateProvider.state, + feeRateTypeMobileStateProvider + .state, ) .state = FeeRateType.custom; Navigator.of(context).pop(); @@ -754,9 +755,7 @@ class _TransactionFeeSelectionSheetState ), ], ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -765,15 +764,14 @@ class _TransactionFeeSelectionSheetState children: [ Text( FeeRateType.custom.prettyName, - style: - STextStyles.titleBold12(context), + style: STextStyles.titleBold12( + context, + ), textAlign: TextAlign.left, ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), ], ), ), @@ -781,10 +779,8 @@ class _TransactionFeeSelectionSheetState ), ), ), - if (wallet is ElectrumXInterface) - const SizedBox( - height: 24, - ), + if (wallet is ElectrumXInterface || coin is Ethereum) + const SizedBox(height: 24), ], ); }, @@ -800,7 +796,9 @@ class _TransactionFeeSelectionSheetState switch (feeRateType) { case FeeRateType.fast: if (ref.read(feeSheetSessionCacheProvider).fast[amount] != null) { - return ref.read(pAmountFormatter(coin)).format( + return ref + .read(pAmountFormatter(coin)) + .format( ref.read(feeSheetSessionCacheProvider).fast[amount]!, indicatePrecisionLoss: false, withUnitName: false, @@ -810,7 +808,9 @@ class _TransactionFeeSelectionSheetState case FeeRateType.average: if (ref.read(feeSheetSessionCacheProvider).average[amount] != null) { - return ref.read(pAmountFormatter(coin)).format( + return ref + .read(pAmountFormatter(coin)) + .format( ref.read(feeSheetSessionCacheProvider).average[amount]!, indicatePrecisionLoss: false, withUnitName: false, @@ -820,7 +820,9 @@ class _TransactionFeeSelectionSheetState case FeeRateType.slow: if (ref.read(feeSheetSessionCacheProvider).slow[amount] != null) { - return ref.read(pAmountFormatter(coin)).format( + return ref + .read(pAmountFormatter(coin)) + .format( ref.read(feeSheetSessionCacheProvider).slow[amount]!, indicatePrecisionLoss: false, withUnitName: false, @@ -831,7 +833,7 @@ class _TransactionFeeSelectionSheetState return null; } } catch (e, s) { - Logging.instance.w("$e $s", error: e, stackTrace: s,); + Logging.instance.w("$e $s", error: e, stackTrace: s); return null; } } diff --git a/lib/pages/send_view/token_send_view.dart b/lib/pages/send_view/token_send_view.dart index 01ae07406..2cb873951 100644 --- a/lib/pages/send_view/token_send_view.dart +++ b/lib/pages/send_view/token_send_view.dart @@ -33,6 +33,7 @@ import '../../utilities/barcode_scanner_interface.dart'; import '../../utilities/clipboard_interface.dart'; import '../../utilities/constants.dart'; import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../utilities/eth_commons.dart'; import '../../utilities/logger.dart'; import '../../utilities/prefs.dart'; import '../../utilities/text_styles.dart'; @@ -45,6 +46,7 @@ import '../../wallets/models/tx_data.dart'; import '../../widgets/animated_text.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/eth_fee_form.dart'; import '../../widgets/icon_widgets/addressbook_icon.dart'; import '../../widgets/icon_widgets/clipboard_icon.dart'; import '../../widgets/icon_widgets/eth_token_icon.dart'; @@ -119,6 +121,10 @@ class _TokenSendViewState extends ConsumerState { late Future _calculateFeesFuture; String cachedFees = ""; + final isCustomFee = ValueNotifier(false); + + EthEIP1559Fee? ethFee; + void _onTokenSendViewPasteAddressFieldButtonPressed() async { final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); if (data?.text != null && data!.text!.isNotEmpty) { @@ -183,10 +189,12 @@ class _TokenSendViewState extends ConsumerState { // autofill amount field if (paymentData.amount != null) { - final Amount amount = Decimal.parse(paymentData.amount!).toAmount( - fractionDigits: tokenContract.decimals, - ); - cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( + final Amount amount = Decimal.parse( + paymentData.amount!, + ).toAmount(fractionDigits: tokenContract.decimals); + cryptoAmountController.text = ref + .read(pAmountFormatter(coin)) + .format( amount, withUnitName: false, indicatePrecisionLoss: false, @@ -231,22 +239,24 @@ class _TokenSendViewState extends ConsumerState { locale: ref.read(localeServiceChangeNotifierProvider).locale, ); if (baseAmount != null) { - final _price = ref - .read(priceAnd24hChangeNotifierProvider) - .getTokenPrice(tokenContract.address) - .item1; + final _price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice(tokenContract.address) + ?.value; - if (_price == Decimal.zero) { + if (_price == null || _price == Decimal.zero) { _amountToSend = Amount.zero; } else { - _amountToSend = baseAmount <= Amount.zero - ? Amount.zero - : Amount.fromDecimal( - (baseAmount.decimal / _price).toDecimal( - scaleOnInfinitePrecision: tokenContract.decimals, - ), - fractionDigits: tokenContract.decimals, - ); + _amountToSend = + baseAmount <= Amount.zero + ? Amount.zero + : Amount.fromDecimal( + (baseAmount.decimal / _price).toDecimal( + scaleOnInfinitePrecision: tokenContract.decimals, + ), + fractionDigits: tokenContract.decimals, + ); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -254,10 +264,9 @@ class _TokenSendViewState extends ConsumerState { _cachedAmountToSend = _amountToSend; _cryptoAmountChangeLock = true; - cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( - _amountToSend!, - withUnitName: false, - ); + cryptoAmountController.text = ref + .read(pAmountFormatter(coin)) + .format(_amountToSend!, withUnitName: false); _cryptoAmountChangeLock = false; } else { _amountToSend = Amount.zero; @@ -275,10 +284,9 @@ class _TokenSendViewState extends ConsumerState { void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { - final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( - cryptoAmountController.text, - ethContract: tokenContract, - ); + final cryptoAmount = ref + .read(pAmountFormatter(coin)) + .tryParse(cryptoAmountController.text, ethContract: tokenContract); if (cryptoAmount != null) { _amountToSend = cryptoAmount; if (_cachedAmountToSend != null && @@ -287,16 +295,15 @@ class _TokenSendViewState extends ConsumerState { } _cachedAmountToSend = _amountToSend; - final price = ref - .read(priceAnd24hChangeNotifierProvider) - .getTokenPrice(tokenContract.address) - .item1; + final price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice(tokenContract.address) + ?.value; - if (price > Decimal.zero) { + if (price != null && price > Decimal.zero) { baseAmountController.text = (_amountToSend!.decimal * price) - .toAmount( - fractionDigits: 2, - ) + .toAmount(fractionDigits: 2) .fiatString( locale: ref.read(localeServiceChangeNotifierProvider).locale, ); @@ -331,7 +338,7 @@ class _TokenSendViewState extends ConsumerState { } String? _updateInvalidAddressText(String address) { - if (_data != null && _data!.contactLabel == address) { + if (_data != null && _data.contactLabel == address) { return null; } if (address.isNotEmpty && @@ -359,9 +366,9 @@ class _TokenSendViewState extends ConsumerState { final wallet = ref.read(pCurrentTokenWallet)!; final feeObject = await wallet.fees; - late final int feeRate; + late final BigInt feeRate; - switch (ref.read(feeRateTypeStateProvider.state).state) { + switch (ref.read(feeRateTypeMobileStateProvider.state).state) { case FeeRateType.fast: feeRate = feeObject.fast; break; @@ -372,15 +379,13 @@ class _TokenSendViewState extends ConsumerState { feeRate = feeObject.slow; break; default: - feeRate = -1; + feeRate = BigInt.from(-1); } final Amount fee = await wallet.estimateFeeFor(Amount.zero, feeRate); - cachedFees = ref.read(pAmountFormatter(coin)).format( - fee, - withUnitName: true, - indicatePrecisionLoss: false, - ); + cachedFees = ref + .read(pAmountFormatter(coin)) + .format(fee, withUnitName: true, indicatePrecisionLoss: false); return cachedFees; } @@ -388,9 +393,7 @@ class _TokenSendViewState extends ConsumerState { Future _previewTransaction() async { // wait for keyboard to disappear FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); + await Future.delayed(const Duration(milliseconds: 100)); final wallet = ref.read(pWallets).getWallet(walletId); final tokenWallet = ref.read(pCurrentTokenWallet)!; @@ -471,33 +474,21 @@ class _TokenSendViewState extends ConsumerState { ); } - final time = Future.delayed( - const Duration( - milliseconds: 2500, - ), - ); + final time = Future.delayed(const Duration(milliseconds: 2500)); TxData txData; Future txDataFuture; txDataFuture = tokenWallet.prepareSend( txData: TxData( - recipients: [ - ( - address: _address!, - amount: amount, - isChange: false, - ), - ], - feeRateType: ref.read(feeRateTypeStateProvider), + recipients: [(address: _address!, amount: amount, isChange: false)], + feeRateType: ref.read(feeRateTypeMobileStateProvider), note: noteController.text, + ethEIP1559Fee: ethFee, ), ); - final results = await Future.wait([ - txDataFuture, - time, - ]); + final results = await Future.wait([txDataFuture, time]); txData = results.first as TxData; @@ -509,13 +500,14 @@ class _TokenSendViewState extends ConsumerState { Navigator.of(context).push( RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => ConfirmTransactionView( - txData: txData, - walletId: walletId, - isTokenTx: true, - onSuccess: clearSendForm, - routeOnSuccessName: TokenView.routeName, - ), + builder: + (_) => ConfirmTransactionView( + txData: txData, + walletId: walletId, + isTokenTx: true, + onSuccess: clearSendForm, + routeOnSuccessName: TokenView.routeName, + ), settings: const RouteSettings( name: ConfirmTransactionView.routeName, ), @@ -545,9 +537,10 @@ class _TokenSendViewState extends ConsumerState { child: Text( "Ok", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), onPressed: () { @@ -578,6 +571,9 @@ class _TokenSendViewState extends ConsumerState { @override void initState() { ref.refresh(feeSheetSessionCacheProvider); + isCustomFee.addListener(() { + if (!isCustomFee.value) ethFee = null; + }); _calculateFeesFuture = calculateFees(); _data = widget.autoFillData; @@ -598,11 +594,11 @@ class _TokenSendViewState extends ConsumerState { baseAmountController.addListener(_baseAmountChanged); if (_data != null) { - if (_data!.amount != null) { - cryptoAmountController.text = _data!.amount!.toString(); + if (_data.amount != null) { + cryptoAmountController.text = _data.amount!.toString(); } - sendToController.text = _data!.contactLabel; - _address = _data!.address.trim(); + sendToController.text = _data.contactLabel; + _address = _data.address.trim(); _addressToggleFlag = true; } @@ -627,6 +623,7 @@ class _TokenSendViewState extends ConsumerState { _addressFocusNode.dispose(); _cryptoFocus.dispose(); _baseFocus.dispose(); + isCustomFee.dispose(); super.dispose(); } @@ -637,6 +634,15 @@ class _TokenSendViewState extends ConsumerState { localeServiceChangeNotifierProvider.select((value) => value.locale), ); + Decimal? price; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getTokenPrice(tokenContract.address)?.value, + ), + ); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -660,11 +666,7 @@ class _TokenSendViewState extends ConsumerState { body: LayoutBuilder( builder: (builderContext, constraints) { return Padding( - padding: const EdgeInsets.only( - left: 12, - top: 12, - right: 12, - ), + padding: const EdgeInsets.only(left: 12, top: 12, right: 12), child: SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints( @@ -679,9 +681,10 @@ class _TokenSendViewState extends ConsumerState { children: [ Container( decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, + color: + Theme.of( + context, + ).extension()!.popupBG, borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), @@ -693,24 +696,24 @@ class _TokenSendViewState extends ConsumerState { EthTokenIcon( contractAddress: tokenContract.address, ), - const SizedBox( - width: 6, - ), + const SizedBox(width: 6), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ref.watch(pWalletName(walletId)), - style: STextStyles.titleBold12(context) - .copyWith(fontSize: 14), + style: STextStyles.titleBold12( + context, + ).copyWith(fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 1, ), Text( "Available balance", - style: STextStyles.label(context) - .copyWith(fontSize: 10), + style: STextStyles.label( + context, + ).copyWith(fontSize: 10), ), ], ), @@ -722,13 +725,11 @@ class _TokenSendViewState extends ConsumerState { .format( ref .read( - pTokenBalance( - ( - walletId: widget.walletId, - contractAddress: - tokenContract.address, - ), - ), + pTokenBalance(( + walletId: widget.walletId, + contractAddress: + tokenContract.address, + )), ) .spendable, ethContract: tokenContract, @@ -748,48 +749,30 @@ class _TokenSendViewState extends ConsumerState { .format( ref .watch( - pTokenBalance( - ( - walletId: - widget.walletId, - contractAddress: - tokenContract - .address, - ), - ), - ) - .spendable, - ethContract: tokenContract, - ), - style: - STextStyles.titleBold12(context) - .copyWith( - fontSize: 10, - ), - textAlign: TextAlign.right, - ), - Text( - "${(ref.watch( - pTokenBalance( - ( + pTokenBalance(( walletId: widget.walletId, contractAddress: tokenContract .address, - ), - ), - ).spendable.decimal * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getTokenPrice(tokenContract.address).item1))).toAmount( - fractionDigits: 2, - ).fiatString( - locale: locale, - )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles.subtitle(context) - .copyWith( - fontSize: 8, - ), + )), + ) + .spendable, + ethContract: tokenContract, + ), + style: STextStyles.titleBold12( + context, + ).copyWith(fontSize: 10), textAlign: TextAlign.right, ), + if (price != null) + Text( + "${(ref.watch(pTokenBalance((walletId: widget.walletId, contractAddress: tokenContract.address))).spendable.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: locale)} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle( + context, + ).copyWith(fontSize: 8), + textAlign: TextAlign.right, + ), ], ), ), @@ -798,17 +781,13 @@ class _TokenSendViewState extends ConsumerState { ), ), ), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), Text( "Send to", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -850,9 +829,10 @@ class _TokenSendViewState extends ConsumerState { right: 5, ), suffixIcon: Padding( - padding: sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), + padding: + sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: @@ -860,33 +840,33 @@ class _TokenSendViewState extends ConsumerState { children: [ _addressToggleFlag ? TextFieldIconButton( - key: const Key( - "tokenSendViewClearAddressFieldButtonKey", - ), - onTap: () { - sendToController.text = ""; - _address = ""; - _updatePreviewButtonState( - _address, - _amountToSend, - ); - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) + key: const Key( + "tokenSendViewClearAddressFieldButtonKey", + ), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, + _amountToSend, + ); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) : TextFieldIconButton( - key: const Key( - "tokenSendViewPasteAddressFieldButtonKey", - ), - onTap: - _onTokenSendViewPasteAddressFieldButtonPressed, - child: sendToController - .text.isEmpty - ? const ClipboardIcon() - : const XIcon(), + key: const Key( + "tokenSendViewPasteAddressFieldButtonKey", ), + onTap: + _onTokenSendViewPasteAddressFieldButtonPressed, + child: + sendToController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), if (sendToController.text.isEmpty) TextFieldIconButton( key: const Key( @@ -935,11 +915,13 @@ class _TokenSendViewState extends ConsumerState { child: Text( error, textAlign: TextAlign.left, - style: - STextStyles.label(context).copyWith( - color: Theme.of(context) - .extension()! - .textError, + style: STextStyles.label( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textError, ), ), ), @@ -947,9 +929,7 @@ class _TokenSendViewState extends ConsumerState { } }, ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -972,27 +952,28 @@ class _TokenSendViewState extends ConsumerState { // ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, + color: + Theme.of( + context, + ).extension()!.textDark, + ), + key: const Key( + "amountInputFieldCryptoTextFieldKey", ), - key: - const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: Util.isDesktop - ? null - : const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: + Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ AmountInputFormatter( @@ -1014,10 +995,9 @@ class _TokenSendViewState extends ConsumerState { right: 12, ), hintText: "0", - hintStyle: - STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), + hintStyle: STextStyles.fieldLabel( + context, + ).copyWith(fontSize: 14), prefixIcon: FittedBox( fit: BoxFit.scaleDown, child: Padding( @@ -1026,11 +1006,13 @@ class _TokenSendViewState extends ConsumerState { ref .watch(pAmountUnit(coin)) .unitForContract(tokenContract), - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + style: STextStyles.smallMed14( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorDark, ), ), ), @@ -1038,28 +1020,29 @@ class _TokenSendViewState extends ConsumerState { ), ), if (Prefs.instance.externalCalls) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (Prefs.instance.externalCalls) TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark, + color: + Theme.of( + context, + ).extension()!.textDark, + ), + key: const Key( + "amountInputFieldFiatTextFieldKey", ), - key: - const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: Util.isDesktop - ? null - : const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: + Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ AmountInputFormatter( @@ -1081,41 +1064,39 @@ class _TokenSendViewState extends ConsumerState { right: 12, ), hintText: "0", - hintStyle: - STextStyles.fieldLabel(context).copyWith( - fontSize: 14, - ), + hintStyle: STextStyles.fieldLabel( + context, + ).copyWith(fontSize: 14), prefixIcon: FittedBox( fit: BoxFit.scaleDown, child: Padding( padding: const EdgeInsets.all(12), child: Text( ref.watch( - prefsChangeNotifierProvider - .select((value) => value.currency), + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), ), - style: STextStyles.smallMed14(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + style: STextStyles.smallMed14( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .accentColorDark, ), ), ), ), ), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Text( "Note (optional)", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1132,41 +1113,38 @@ class _TokenSendViewState extends ConsumerState { _noteFocusNode, context, ).copyWith( - suffixIcon: noteController.text.isNotEmpty - ? Padding( - padding: - const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - noteController.text = ""; - }); - }, - ), - ], + suffixIcon: + noteController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only( + right: 0, ), - ), - ) - : null, + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + noteController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, ), ), ), - const SizedBox( - height: 12, - ), - if (coin is! Epiccash) - Text( - "Transaction fee (estimated)", - style: STextStyles.smallMed12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 8, + const SizedBox(height: 12), + Text( + "Transaction fee ${isCustomFee.value ? "" : "(max)"}", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, ), + const SizedBox(height: 8), Stack( children: [ TextField( @@ -1182,9 +1160,10 @@ class _TokenSendViewState extends ConsumerState { horizontal: 12, ), child: RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, + splashColor: + Theme.of( + context, + ).extension()!.highlight, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1199,25 +1178,39 @@ class _TokenSendViewState extends ConsumerState { top: Radius.circular(20), ), ), - builder: (_) => - TransactionFeeSelectionSheet( - walletId: walletId, - isToken: true, - amount: (Decimal.tryParse( - cryptoAmountController.text, - ) ?? - Decimal.zero) - .toAmount( - fractionDigits: - tokenContract.decimals, - ), - updateChosen: (String fee) { - setState(() { - _calculateFeesFuture = - Future(() => fee); - }); - }, - ), + builder: + (_) => TransactionFeeSelectionSheet( + walletId: walletId, + isToken: true, + amount: (Decimal.tryParse( + cryptoAmountController + .text, + ) ?? + Decimal.zero) + .toAmount( + fractionDigits: + tokenContract.decimals, + ), + updateChosen: (String fee) { + if (fee == "custom") { + if (!isCustomFee.value) { + setState(() { + isCustomFee.value = true; + }); + } + return; + } + + setState(() { + _calculateFeesFuture = Future( + () => fee, + ); + if (isCustomFee.value) { + isCustomFee.value = false; + } + }); + }, + ), ); }, child: Row( @@ -1229,7 +1222,7 @@ class _TokenSendViewState extends ConsumerState { Text( ref .watch( - feeRateTypeStateProvider + feeRateTypeMobileStateProvider .state, ) .state @@ -1238,9 +1231,7 @@ class _TokenSendViewState extends ConsumerState { context, ), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), FutureBuilder( future: _calculateFeesFuture, builder: (context, snapshot) { @@ -1248,11 +1239,13 @@ class _TokenSendViewState extends ConsumerState { ConnectionState.done && snapshot.hasData) { return Text( - "~${snapshot.data!}", + isCustomFee.value + ? "" + : "~${snapshot.data!}", style: STextStyles.itemSubtitle( - context, - ), + context, + ), ); } else { return AnimatedText( @@ -1264,8 +1257,8 @@ class _TokenSendViewState extends ConsumerState { ], style: STextStyles.itemSubtitle( - context, - ), + context, + ), ); } }, @@ -1276,9 +1269,12 @@ class _TokenSendViewState extends ConsumerState { Assets.svg.chevronDown, width: 8, height: 4, - color: Theme.of(context) - .extension()! - .textSubtitle2, + colorFilter: ColorFilter.mode( + Theme.of(context) + .extension()! + .textSubtitle2, + BlendMode.srcIn, + ), ), ], ), @@ -1286,37 +1282,43 @@ class _TokenSendViewState extends ConsumerState { ), ], ), + if (isCustomFee.value) const SizedBox(height: 12), + if (isCustomFee.value) + EthFeeForm( + minGasLimit: kEthereumTokenMinGasLimit, + stateChanged: (value) => ethFee = value, + ), const Spacer(), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), TextButton( - onPressed: ref - .watch( - previewTokenTxButtonStateProvider.state, - ) - .state - ? _previewTransaction - : null, - style: ref - .watch( - previewTokenTxButtonStateProvider.state, - ) - .state - ? Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context) - : Theme.of(context) - .extension()! - .getPrimaryDisabledButtonStyle(context), + onPressed: + ref + .watch( + previewTokenTxButtonStateProvider + .state, + ) + .state + ? _previewTransaction + : null, + style: + ref + .watch( + previewTokenTxButtonStateProvider + .state, + ) + .state + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonStyle(context), child: Text( "Preview", style: STextStyles.button(context), ), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 16), ], ), ), diff --git a/lib/pages/spark_names/confirm_spark_name_transaction_view.dart b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart index 9afc5694f..011532708 100644 --- a/lib/pages/spark_names/confirm_spark_name_transaction_view.dart +++ b/lib/pages/spark_names/confirm_spark_name_transaction_view.dart @@ -693,8 +693,8 @@ class _ConfirmSparkNameTransactionViewState ref .read(priceAnd24hChangeNotifierProvider) .getPrice(coin) - .item1; - if (price > Decimal.zero) { + ?.value; + if (price != null && price > Decimal.zero) { fiatAmount = (amountWithoutChange.decimal * price) .toAmount(fractionDigits: 2) .fiatString( diff --git a/lib/pages/token_view/sub_widgets/my_token_select_item.dart b/lib/pages/token_view/sub_widgets/my_token_select_item.dart index 4f1eec6d1..745f2a0fd 100644 --- a/lib/pages/token_view/sub_widgets/my_token_select_item.dart +++ b/lib/pages/token_view/sub_widgets/my_token_select_item.dart @@ -56,10 +56,7 @@ class _MyTokenSelectItemState extends ConsumerState { late final CachedEthTokenBalance cachedBalance; - Future _loadTokenWallet( - BuildContext context, - WidgetRef ref, - ) async { + Future _loadTokenWallet(BuildContext context, WidgetRef ref) async { try { await ref.read(pCurrentTokenWallet)!.init(); return true; @@ -67,20 +64,21 @@ class _MyTokenSelectItemState extends ConsumerState { await showDialog( barrierDismissible: false, context: context, - builder: (context) => BasicDialog( - title: "Failed to load token data", - desktopHeight: double.infinity, - desktopWidth: 450, - rightButton: PrimaryButton( - label: "OK", - onPressed: () { - Navigator.of(context).pop(); - if (!isDesktop) { - Navigator.of(context).pop(); - } - }, - ), - ), + builder: + (context) => BasicDialog( + title: "Failed to load token data", + desktopHeight: double.infinity, + desktopWidth: 450, + rightButton: PrimaryButton( + label: "OK", + onPressed: () { + Navigator.of(context).pop(); + if (!isDesktop) { + Navigator.of(context).pop(); + } + }, + ), + ), ); return false; } @@ -90,11 +88,14 @@ class _MyTokenSelectItemState extends ConsumerState { final old = ref.read(tokenServiceStateProvider); // exit previous if there is one unawaited(old?.exit()); - ref.read(tokenServiceStateProvider.state).state = Wallet.loadTokenWallet( - ethWallet: - ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet, - contract: widget.token, - ) as EthTokenWallet; + ref.read(tokenServiceStateProvider.state).state = + Wallet.loadTokenWallet( + ethWallet: + ref.read(pWallets).getWallet(widget.walletId) + as EthereumWallet, + contract: widget.token, + ) + as EthTokenWallet; final success = await showLoading( whileFuture: _loadTokenWallet(context, ref), @@ -138,17 +139,29 @@ class _MyTokenSelectItemState extends ConsumerState { @override Widget build(BuildContext context) { + String? priceString; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + priceString = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (s) => + s.getTokenPrice(widget.token.address)?.value.toStringAsFixed(2), + ), + ); + } + return RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: MaterialButton( key: Key("walletListItemButtonKey_${widget.token.symbol}"), - padding: isDesktop - ? const EdgeInsets.symmetric(horizontal: 28, vertical: 24) - : const EdgeInsets.symmetric(horizontal: 12, vertical: 13), + padding: + isDesktop + ? const EdgeInsets.symmetric(horizontal: 28, vertical: 24) + : const EdgeInsets.symmetric(horizontal: 12, vertical: 13), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), onPressed: _onPressed, child: Row( @@ -157,9 +170,7 @@ class _MyTokenSelectItemState extends ConsumerState { contractAddress: widget.token.address, size: isDesktop ? 32 : 28, ), - SizedBox( - width: isDesktop ? 12 : 10, - ), + SizedBox(width: isDesktop ? 12 : 10), Expanded( child: Consumer( builder: (_, ref, __) { @@ -170,14 +181,17 @@ class _MyTokenSelectItemState extends ConsumerState { children: [ Text( widget.token.name, - style: isDesktop - ? STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.titleBold12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.titleBold12(context), ), const Spacer(), Text( @@ -190,62 +204,52 @@ class _MyTokenSelectItemState extends ConsumerState { .format( ref .watch( - pTokenBalance( - ( - walletId: widget.walletId, - contractAddress: - widget.token.address - ), - ), + pTokenBalance(( + walletId: widget.walletId, + contractAddress: widget.token.address, + )), ) .total, ethContract: widget.token, ), - style: isDesktop - ? STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.itemSubtitle(context), ), ], ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), Row( children: [ Text( widget.token.symbol, - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle(context), ), const Spacer(), - Text( - "${ref.watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value - .getTokenPrice(widget.token.address) - .item1 - .toStringAsFixed(2), - ), - )} " - "${ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, - ), - )}", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), - ), + if (priceString != null) + Text( + "$priceString " + "${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle(context), + ), ], ), ], diff --git a/lib/pages/token_view/sub_widgets/token_summary.dart b/lib/pages/token_view/sub_widgets/token_summary.dart index f5b051ef7..9bebfda49 100644 --- a/lib/pages/token_view/sub_widgets/token_summary.dart +++ b/lib/pages/token_view/sub_widgets/token_summary.dart @@ -11,6 +11,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -53,12 +54,22 @@ class TokenSummary extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final token = - ref.watch(pCurrentTokenWallet.select((value) => value!.tokenContract)); + final token = ref.watch( + pCurrentTokenWallet.select((value) => value!.tokenContract), + ); final balance = ref.watch( pTokenBalance((walletId: walletId, contractAddress: token.address)), ); + Decimal? price; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getTokenPrice(token.address)?.value, + ), + ); + } + return Stack( children: [ RoundedContainer( @@ -71,30 +82,26 @@ class TokenSummary extends ConsumerWidget { children: [ SvgPicture.asset( Assets.svg.walletDesktop, - color: Theme.of(context) - .extension()! - .tokenSummaryTextSecondary, + color: + Theme.of( + context, + ).extension()!.tokenSummaryTextSecondary, width: 12, height: 12, ), - const SizedBox( - width: 6, - ), + const SizedBox(width: 6), Text( - ref.watch( - pWalletName(walletId), - ), + ref.watch(pWalletName(walletId)), style: STextStyles.w500_12(context).copyWith( - color: Theme.of(context) - .extension()! - .tokenSummaryTextSecondary, + color: + Theme.of( + context, + ).extension()!.tokenSummaryTextSecondary, ), ), ], ), - const SizedBox( - height: 6, - ), + const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -105,58 +112,31 @@ class TokenSummary extends ConsumerWidget { Ethereum(CryptoCurrencyNetwork.main), ), ) - .format( - balance.total, - ethContract: token, - ), + .format(balance.total, ethContract: token), style: STextStyles.pageTitleH1(context).copyWith( - color: Theme.of(context) - .extension()! - .tokenSummaryTextPrimary, + color: + Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, ), ), - const SizedBox( - width: 10, - ), - CoinTickerTag( - walletId: walletId, - ), + const SizedBox(width: 10), + CoinTickerTag(walletId: walletId), ], ), - const SizedBox( - height: 6, - ), - Text( - "${(balance.total.decimal * ref.watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getTokenPrice(token.address).item1, - ), - )).toAmount( - fractionDigits: 2, - ).fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider.select( - (value) => value.locale, - ), - ), - )} ${ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, + if (price != null) const SizedBox(height: 6), + if (price != null) + Text( + "${(balance.total.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle500(context).copyWith( + color: + Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, ), - )}", - style: STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .tokenSummaryTextPrimary, ), - ), - const SizedBox( - height: 20, - ), - TokenWalletOptions( - walletId: walletId, - tokenContract: token, - ), + const SizedBox(height: 20), + TokenWalletOptions(walletId: walletId, tokenContract: token), ], ), ), @@ -195,11 +175,7 @@ class TokenWalletOptions extends ConsumerWidget { unawaited( Navigator.of(context).pushNamed( WalletInitiatedExchangeView.routeName, - arguments: Tuple3( - walletId, - ethereum, - tokenContract, - ), + arguments: Tuple3(walletId, ethereum, tokenContract), ), ); } @@ -208,10 +184,7 @@ class TokenWalletOptions extends ConsumerWidget { unawaited( Navigator.of(context).pushNamed( BuyInWalletView.routeName, - arguments: Tuple2( - ethereum, - tokenContract, - ), + arguments: Tuple2(ethereum, tokenContract), ), ); } @@ -228,50 +201,35 @@ class TokenWalletOptions extends ConsumerWidget { onPressed: () { Navigator.of(context).pushNamed( ReceiveView.routeName, - arguments: Tuple2( - walletId, - tokenContract, - ), + arguments: Tuple2(walletId, tokenContract), ); }, subLabel: "Receive", iconAssetPathSVG: Assets.svg.arrowDownLeft, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), TokenOptionsButton( onPressed: () { Navigator.of(context).pushNamed( TokenSendView.routeName, - arguments: Tuple3( - walletId, - ethereum, - tokenContract, - ), + arguments: Tuple3(walletId, ethereum, tokenContract), ); }, subLabel: "Send", iconAssetPathSVG: Assets.svg.arrowUpRight, ), if (AppConfig.hasFeature(AppFeature.swap) && showExchange) - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), if (AppConfig.hasFeature(AppFeature.swap) && showExchange) TokenOptionsButton( onPressed: () => _onExchangePressed(context), subLabel: "Swap", iconAssetPathSVG: ref.watch( - themeProvider.select( - (value) => value.assets.exchange, - ), + themeProvider.select((value) => value.assets.exchange), ), ), if (AppConfig.hasFeature(AppFeature.buy) && showExchange) - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), if (AppConfig.hasFeature(AppFeature.buy) && showExchange) TokenOptionsButton( onPressed: () => _onBuyPressed(context), @@ -320,46 +278,47 @@ class TokenOptionsButton extends StatelessWidget { padding: const EdgeInsets.all(10), child: ConditionalParent( condition: iconSize < 24, - builder: (child) => RoundedContainer( - padding: const EdgeInsets.all(6), - color: Theme.of(context) - .extension()! - .tokenSummaryIcon - .withOpacity(0.4), - radiusMultiplier: 10, - child: Center( - child: child, - ), - ), - child: iconAssetPathSVG.startsWith("assets/") - ? SvgPicture.asset( - iconAssetPathSVG, - color: Theme.of(context) - .extension()! - .tokenSummaryIcon, - width: iconSize, - height: iconSize, - ) - : SvgPicture.file( - File(iconAssetPathSVG), - color: Theme.of(context) - .extension()! - .tokenSummaryIcon, - width: iconSize, - height: iconSize, - ), + builder: + (child) => RoundedContainer( + padding: const EdgeInsets.all(6), + color: Theme.of(context) + .extension()! + .tokenSummaryIcon + .withOpacity(0.4), + radiusMultiplier: 10, + child: Center(child: child), + ), + child: + iconAssetPathSVG.startsWith("assets/") + ? SvgPicture.asset( + iconAssetPathSVG, + color: + Theme.of( + context, + ).extension()!.tokenSummaryIcon, + width: iconSize, + height: iconSize, + ) + : SvgPicture.file( + File(iconAssetPathSVG), + color: + Theme.of( + context, + ).extension()!.tokenSummaryIcon, + width: iconSize, + height: iconSize, + ), ), ), ), - const SizedBox( - height: 6, - ), + const SizedBox(height: 6), Text( subLabel, style: STextStyles.w500_12(context).copyWith( - color: Theme.of(context) - .extension()! - .tokenSummaryTextPrimary, + color: + Theme.of( + context, + ).extension()!.tokenSummaryTextPrimary, ), ), ], @@ -368,10 +327,7 @@ class TokenOptionsButton extends StatelessWidget { } class CoinTickerTag extends ConsumerWidget { - const CoinTickerTag({ - super.key, - required this.walletId, - }); + const CoinTickerTag({super.key, required this.walletId}); final String walletId; @@ -382,11 +338,7 @@ class CoinTickerTag extends ConsumerWidget { radiusMultiplier: 0.25, color: Theme.of(context).extension()!.ethTagBG, child: Text( - ref - .watch( - pWalletCoin(walletId), - ) - .ticker, + ref.watch(pWalletCoin(walletId)).ticker, style: STextStyles.w600_12(context).copyWith( color: Theme.of(context).extension()!.ethTagText, ), diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart index 08d9152b8..1929a1ca4 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'dart:typed_data'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -52,9 +53,7 @@ class WalletSummaryInfo extends ConsumerWidget { useSafeArea: true, isScrollControlled: true, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => WalletBalanceToggleSheet(walletId: walletId), ); @@ -64,9 +63,6 @@ class WalletSummaryInfo extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { debugPrint("BUILD: $runtimeType"); - final externalCalls = ref.watch( - prefsChangeNotifierProvider.select((value) => value.externalCalls), - ); final coin = ref.watch(pWalletCoin(walletId)); final balance = ref.watch(pWalletBalance(walletId)); @@ -74,14 +70,27 @@ class WalletSummaryInfo extends ConsumerWidget { localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); + + ({double change24h, Decimal value})? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ); + } final priceTuple = ref.watch( priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin)), ); - final _showAvailable = ref.watch(walletBalanceToggleStateProvider) == + final _showAvailable = + ref.watch(walletBalanceToggleStateProvider) == WalletBalanceToggleState.available; final Amount balanceToShow; @@ -119,23 +128,23 @@ class WalletSummaryInfo extends ConsumerWidget { List? imageBytes; if (coin is Banano) { - imageBytes = (ref.watch(pWallets).getWallet(walletId) as BananoWallet) - .getMonkeyImageBytes(); + imageBytes = + (ref.watch(pWallets).getWallet(walletId) as BananoWallet) + .getMonkeyImageBytes(); } return ConditionalParent( condition: imageBytes != null, - builder: (child) => Stack( - children: [ - Positioned.fill( - left: 150.0, - child: SvgPicture.memory( - Uint8List.fromList(imageBytes!), - ), + builder: + (child) => Stack( + children: [ + Positioned.fill( + left: 150.0, + child: SvgPicture.memory(Uint8List.fromList(imageBytes!)), + ), + child, + ], ), - child, - ], - ), child: Row( children: [ Expanded( @@ -164,20 +173,20 @@ class WalletSummaryInfo extends ConsumerWidget { Text( title, style: STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), if (!toggleBalance) ...[ - const SizedBox( - width: 4, - ), + const SizedBox(width: 4), SvgPicture.asset( Assets.svg.chevronDown, - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, width: 8, height: 4, ), @@ -207,23 +216,21 @@ class WalletSummaryInfo extends ConsumerWidget { ref.watch(pAmountFormatter(coin)).format(balanceToShow), style: STextStyles.pageTitleH1(context).copyWith( fontSize: 24, - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ), - if (externalCalls) + if (price != null) Text( - "${(priceTuple.item1 * balanceToShow.decimal).toAmount( - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "${(price.value * balanceToShow.decimal).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), ), ], @@ -232,9 +239,7 @@ class WalletSummaryInfo extends ConsumerWidget { Column( children: [ SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), - ), + File(ref.watch(coinIconProvider(coin))), width: 24, height: 24, ), diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 5afd82597..9fcbfc3f4 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -51,17 +52,11 @@ import '../sub_widgets/tx_icon.dart'; import 'transaction_details_view.dart'; import 'transaction_search_filter_view.dart'; -typedef _GroupedTransactions = ({ - String label, - DateTime startDate, - List transactions -}); +typedef _GroupedTransactions = + ({String label, DateTime startDate, List transactions}); class AllTransactionsView extends ConsumerStatefulWidget { - const AllTransactionsView({ - super.key, - required this.walletId, - }); + const AllTransactionsView({super.key, required this.walletId}); static const String routeName = "/allTransactions"; @@ -106,13 +101,14 @@ class _TransactionDetailsViewState extends ConsumerState { // debugPrint("FILTER: $filter"); final contacts = ref.read(addressBookServiceProvider).contacts; - final notes = ref - .read(mainDBProvider) - .isar - .transactionNotes - .where() - .walletIdEqualTo(walletId) - .findAllSync(); + final notes = + ref + .read(mainDBProvider) + .isar + .transactionNotes + .where() + .walletIdEqualTo(walletId) + .findAllSync(); return transactions.where((tx) { if (!filter.sent && !filter.received) { @@ -162,15 +158,16 @@ class _TransactionDetailsViewState extends ConsumerState { bool contains = false; // check if address book name contains - contains |= contacts - .where( - (e) => - e.addresses - .where((a) => a.address == tx.address.value?.value) - .isNotEmpty && - e.name.toLowerCase().contains(keyword), - ) - .isNotEmpty; + contains |= + contacts + .where( + (e) => + e.addresses + .where((a) => a.address == tx.address.value?.value) + .isNotEmpty && + e.name.toLowerCase().contains(keyword), + ) + .isNotEmpty; // check if address contains contains |= @@ -195,8 +192,9 @@ class _TransactionDetailsViewState extends ConsumerState { contains |= tx.type.name.toLowerCase().contains(keyword); // check if date contains - contains |= - Format.extractDateFrom(tx.timestamp).toLowerCase().contains(keyword); + contains |= Format.extractDateFrom( + tx.timestamp, + ).toLowerCase().contains(keyword); return contains; } @@ -210,13 +208,14 @@ class _TransactionDetailsViewState extends ConsumerState { } text = text.toLowerCase(); final contacts = ref.read(addressBookServiceProvider).contacts; - final notes = ref - .read(mainDBProvider) - .isar - .transactionNotes - .where() - .walletIdEqualTo(walletId) - .findAllSync(); + final notes = + ref + .read(mainDBProvider) + .isar + .transactionNotes + .where() + .walletIdEqualTo(walletId) + .findAllSync(); return transactions .where((tx) => _isKeywordMatch(tx, text, contacts, notes)) @@ -232,8 +231,11 @@ class _TransactionDetailsViewState extends ConsumerState { final date = DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000); final monthYear = "${Constants.monthMap[date.month]} ${date.year}"; if (map[monthYear] == null) { - map[monthYear] = - (label: monthYear, startDate: date, transactions: [tx]); + map[monthYear] = ( + label: monthYear, + startDate: date, + transactions: [tx], + ); } else { map[monthYear]!.transactions.add(tx); } @@ -250,97 +252,99 @@ class _TransactionDetailsViewState extends ConsumerState { return MasterScaffold( background: Theme.of(context).extension()!.background, isDesktop: isDesktop, - appBar: isDesktop - ? DesktopAppBar( - isCompactHeight: true, - background: Theme.of(context).extension()!.popupBG, - leading: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Transactions", - style: STextStyles.desktopH3(context), - ), - ], - ), - ) - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75), - ); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Transactions", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 20, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("transactionSearchFilterViewButton"), - size: 36, + appBar: + isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension()!.popupBG, + leading: Row( + children: [ + const SizedBox(width: 32), + AppBarIconButton( + size: 32, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, shadows: const [], - color: Theme.of(context) - .extension()! - .background, icon: SvgPicture.asset( - Assets.svg.filter, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, ), - onPressed: () { - Navigator.of(context).pushNamed( - TransactionSearchFilterView.routeName, - arguments: - ref.read(pWallets).getWallet(walletId).info.coin, - ); - }, + onPressed: Navigator.of(context).pop, ), - ), + const SizedBox(width: 12), + Text("Transactions", style: STextStyles.desktopH3(context)), + ], ), - ], - ), + ) + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75), + ); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Transactions", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 20, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("transactionSearchFilterViewButton"), + size: 36, + shadows: const [], + color: + Theme.of( + context, + ).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.filter, + color: + Theme.of( + context, + ).extension()!.accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + TransactionSearchFilterView.routeName, + arguments: + ref + .read(pWallets) + .getWallet(walletId) + .info + .coin, + ); + }, + ), + ), + ), + ], + ), body: Padding( padding: EdgeInsets.only( left: isDesktop ? 20 : 12, @@ -355,15 +359,10 @@ class _TransactionDetailsViewState extends ConsumerState { children: [ ConditionalParent( condition: isDesktop, - builder: (child) => SizedBox( - width: 570, - child: child, - ), + builder: (child) => SizedBox(width: 570, child: child), child: ConditionalParent( condition: !isDesktop, - builder: (child) => Expanded( - child: child, - ), + builder: (child) => Expanded(child: child), child: ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -378,15 +377,18 @@ class _TransactionDetailsViewState extends ConsumerState { _searchString = value; }); }, - style: isDesktop - ? STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - height: 1.8, - ) - : STextStyles.field(context), + style: + isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), decoration: standardInputDecoration( "Search...", searchFieldFocusNode, @@ -404,35 +406,33 @@ class _TransactionDetailsViewState extends ConsumerState { height: isDesktop ? 20 : 16, ), ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchString = ""; - }); - }, - ), - ], + suffixIcon: + _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, ), ), ), ), ), - if (isDesktop) - const SizedBox( - width: 20, - ), + if (isDesktop) const SizedBox(width: 20), if (isDesktop) SecondaryButton( buttonHeight: ButtonHeight.l, @@ -440,9 +440,10 @@ class _TransactionDetailsViewState extends ConsumerState { label: "Filter", icon: SvgPicture.asset( Assets.svg.filter, - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, width: 20, height: 20, ), @@ -453,9 +454,7 @@ class _TransactionDetailsViewState extends ConsumerState { showDialog( context: context, builder: (context) { - return TransactionSearchFilterView( - coin: coin, - ); + return TransactionSearchFilterView(coin: coin); }, ); } else { @@ -469,25 +468,14 @@ class _TransactionDetailsViewState extends ConsumerState { ], ), ), - if (isDesktop) - const SizedBox( - height: 8, - ), + if (isDesktop) const SizedBox(height: 8), if (isDesktop && ref.watch(transactionFilterProvider.state).state != null) const Padding( - padding: EdgeInsets.symmetric( - vertical: 8, - ), - child: Row( - children: [ - TransactionFilterOptionBar(), - ], - ), + padding: EdgeInsets.symmetric(vertical: 8), + child: Row(children: [TransactionFilterOptionBar()]), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Expanded( child: Consumer( builder: (_, ref, __) { @@ -495,38 +483,40 @@ class _TransactionDetailsViewState extends ConsumerState { ref.watch(transactionFilterProvider.state).state; return FutureBuilder( - future: ref - .watch(mainDBProvider) - .isar - .transactions - .buildQuery( - whereClauses: [ - IndexWhereClause.equalTo( - indexName: 'walletId', - value: [widget.walletId], - ), - ], - // TODO: [prio=med] add filters to wallet or cryptocurrency class - // eth tokens should all be on v2 txn now so this should not be needed here - // filter: widget.contractAddress != null - // ? FilterGroup.and([ - // FilterCondition.equalTo( - // property: r"contractAddress", - // value: widget.contractAddress!, - // ), - // const FilterCondition.equalTo( - // property: r"subType", - // value: TransactionSubType.ethToken, - // ), - // ]) - // : null, - sortBy: [ - const SortProperty( - property: "timestamp", - sort: Sort.desc, - ), - ], - ).findAll(), + future: + ref + .watch(mainDBProvider) + .isar + .transactions + .buildQuery( + whereClauses: [ + IndexWhereClause.equalTo( + indexName: 'walletId', + value: [widget.walletId], + ), + ], + // TODO: [prio=med] add filters to wallet or cryptocurrency class + // eth tokens should all be on v2 txn now so this should not be needed here + // filter: widget.contractAddress != null + // ? FilterGroup.and([ + // FilterCondition.equalTo( + // property: r"contractAddress", + // value: widget.contractAddress!, + // ), + // const FilterCondition.equalTo( + // property: r"subType", + // value: TransactionSubType.ethToken, + // ), + // ]) + // : null, + sortBy: [ + const SortProperty( + property: "timestamp", + sort: Sort.desc, + ), + ], + ) + .findAll(), builder: (_, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { @@ -555,43 +545,39 @@ class _TransactionDetailsViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (index != 0) - const SizedBox( - height: 12, - ), + if (index != 0) const SizedBox(height: 12), Text( month.label, style: STextStyles.smallMed12(context), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), if (isDesktop) RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: ListView.separated( shrinkWrap: true, primary: false, - separatorBuilder: (context, _) => - Container( - height: 1, - color: Theme.of(context) - .extension()! - .background, - ), + separatorBuilder: + (context, _) => Container( + height: 1, + color: + Theme.of(context) + .extension()! + .background, + ), itemCount: month.transactions.length, - itemBuilder: (context, index) => - Padding( - padding: const EdgeInsets.all(4), - child: DesktopTransactionCardRow( - key: Key( - "transactionCard_key_${month.transactions[index].txid}", + itemBuilder: + (context, index) => Padding( + padding: const EdgeInsets.all(4), + child: DesktopTransactionCardRow( + key: Key( + "transactionCard_key_${month.transactions[index].txid}", + ), + transaction: + month.transactions[index], + walletId: walletId, + ), ), - transaction: - month.transactions[index], - walletId: walletId, - ), - ), ), ), if (!isDesktop) @@ -659,10 +645,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - sent: false, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(sent: false); setState(() {}); } }, @@ -678,10 +664,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - received: false, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(received: false); setState(() {}); } }, @@ -698,10 +684,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - to: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(to: null); setState(() {}); } }, @@ -717,10 +703,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - from: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(from: null); setState(() {}); } }, @@ -737,10 +723,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - amount: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(amount: null); setState(() {}); } }, @@ -756,10 +742,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - keyword: "", - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(keyword: ""); setState(() {}); } }, @@ -780,9 +766,7 @@ class _TransactionFilterOptionBarState scrollDirection: Axis.horizontal, shrinkWrap: true, itemCount: items.length, - separatorBuilder: (_, __) => const SizedBox( - width: 16, - ), + separatorBuilder: (_, __) => const SizedBox(width: 16), itemBuilder: (context, index) => items[index], ), ); @@ -811,9 +795,7 @@ class TransactionFilterOptionBarItem extends StatelessWidget { borderRadius: BorderRadius.circular(1000), ), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - ), + padding: const EdgeInsets.symmetric(horizontal: 14), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -831,9 +813,7 @@ class TransactionFilterOptionBarItem extends StatelessWidget { ), ), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), XIcon( width: 16, height: 16, @@ -915,17 +895,23 @@ class _DesktopTransactionCardRowState localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); final coin = ref.watch(pWalletCoin(walletId)); - final price = ref - .watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin)), - ) - .item1; + Decimal? price; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + price = + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ) + ?.value; + } late final String prefix; if (Util.isDesktop) { @@ -946,8 +932,9 @@ class _DesktopTransactionCardRowState color: Theme.of(context).extension()!.popupBG, elevation: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: RawMaterialButton( shape: RoundedRectangleBorder( @@ -970,34 +957,28 @@ class _DesktopTransactionCardRowState if (Util.isDesktop) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: TransactionDetailsView( - transaction: _transaction, - coin: coin, - walletId: walletId, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TransactionDetailsView( + transaction: _transaction, + coin: coin, + walletId: walletId, + ), + ), ); } else { unawaited( Navigator.of(context).pushNamed( TransactionDetailsView.routeName, - arguments: Tuple3( - _transaction, - coin, - walletId, - ), + arguments: Tuple3(_transaction, coin, walletId), ), ); } }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 16, - ), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), child: Row( children: [ TxIcon( @@ -1005,21 +986,16 @@ class _DesktopTransactionCardRowState currentHeight: currentHeight, coin: coin, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Expanded( flex: 3, child: Text( _transaction.isCancelled ? "Cancelled" - : whatIsIt( - _transaction.type, - coin, - currentHeight, - ), - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( + : whatIsIt(_transaction.type, coin, currentHeight), + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( color: Theme.of(context).extension()!.textDark, ), ), @@ -1038,20 +1014,19 @@ class _DesktopTransactionCardRowState final amount = _transaction.realAmount; return Text( "$prefix${ref.watch(pAmountFormatter(coin)).format(amount)}", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, ), ); }, ), ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) + if (price != null) Expanded( flex: 4, child: Builder( @@ -1059,11 +1034,7 @@ class _DesktopTransactionCardRowState final amount = _transaction.realAmount; return Text( - "$prefix${(amount.decimal * price).toAmount( - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "$prefix${(amount.decimal * price!).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.desktopTextExtraExtraSmall(context), ); }, diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index 11ebd1b82..edd0bff14 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -99,11 +100,12 @@ class _TransactionDetailsViewState isTokenTx = _transaction.subType == TransactionSubType.ethToken; walletId = widget.walletId; - minConfirms = ref - .read(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minConfirms; + minConfirms = + ref + .read(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minConfirms; coin = widget.coin; amount = _transaction.realAmount; fee = _transaction.fee.toAmountAsRaw(fractionDigits: coin.fractionDigits); @@ -114,9 +116,12 @@ class _TransactionDetailsViewState amountPrefix = _transaction.type == TransactionType.outgoing ? "-" : "+"; } - ethContract = isTokenTx - ? ref.read(mainDBProvider).getEthContractSync(_transaction.otherData!) - : null; + ethContract = + isTokenTx + ? ref + .read(mainDBProvider) + .getEthContractSync(_transaction.otherData!) + : null; unit = isTokenTx ? ethContract!.symbol : coin.ticker; @@ -208,10 +213,14 @@ class _TransactionDetailsViewState return address; } try { - final contacts = ref.read(addressBookServiceProvider).contacts.where( - (element) => element.addresses - .where((element) => element.address == address) - .isNotEmpty, + final contacts = ref + .read(addressBookServiceProvider) + .contacts + .where( + (element) => + element.addresses + .where((element) => element.address == address) + .isNotEmpty, ); if (contacts.isNotEmpty) { return contacts.first.name; @@ -219,7 +228,7 @@ class _TransactionDetailsViewState return address; } } catch (e, s) { - Logging.instance.w("$e\n$s", error: e, stackTrace: s,); + Logging.instance.w("$e\n$s", error: e, stackTrace: s); return address; } } @@ -240,8 +249,9 @@ class _TransactionDetailsViewState builder: (_, ref, __) { return Checkbox( value: ref.watch( - prefsChangeNotifierProvider - .select((value) => value.hideBlockExplorerWarning), + prefsChangeNotifierProvider.select( + (value) => value.hideBlockExplorerWarning, + ), ), onChanged: (value) { if (value is bool) { @@ -267,23 +277,21 @@ class _TransactionDetailsViewState child: Text( "Cancel", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), + style: Theme.of( + context, + ).extension()!.getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pop(true); }, - child: Text( - "Continue", - style: STextStyles.button(context), - ), + child: Text("Continue", style: STextStyles.button(context)), ), ); } else { @@ -297,10 +305,7 @@ class _TransactionDetailsViewState Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - "Attention", - style: STextStyles.desktopH2(context), - ), + Text("Attention", style: STextStyles.desktopH2(context)), Row( children: [ Consumer( @@ -344,10 +349,7 @@ class _TransactionDetailsViewState buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(false); + Navigator.of(context, rootNavigator: true).pop(false); }, ), const SizedBox(width: 20), @@ -356,10 +358,7 @@ class _TransactionDetailsViewState buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(true); + Navigator.of(context, rootNavigator: true).pop(true); }, ), ], @@ -378,38 +377,53 @@ class _TransactionDetailsViewState Widget build(BuildContext context) { final currentHeight = ref.watch(pWalletChainHeight(walletId)); + Decimal? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.otherData!)?.value + : value.getPrice(coin)?.value, + ), + ); + } + return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: child, - ), + builder: (child) => Background(child: child), child: Scaffold( - backgroundColor: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, - appBar: isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Transaction details", - style: STextStyles.navBarTitle(context), + backgroundColor: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: + isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Transaction details", + style: STextStyles.navBarTitle(context), + ), ), - ), body: Padding( - padding: isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(12), child: Column( children: [ if (isDesktop) @@ -425,21 +439,20 @@ class _TransactionDetailsViewState ), Expanded( child: Padding( - padding: isDesktop - ? const EdgeInsets.only( - right: 32, - bottom: 32, - ) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.only(right: 32, bottom: 32) + : const EdgeInsets.all(0), child: ConditionalParent( condition: isDesktop, builder: (child) { return RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context) - .extension()! - .backgroundAppBar - : null, + borderColor: + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, padding: const EdgeInsets.all(0), child: child, ); @@ -447,33 +460,40 @@ class _TransactionDetailsViewState child: SingleChildScrollView( primary: isDesktop ? false : null, child: Padding( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(4), + padding: + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(4), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), child: Container( - decoration: isDesktop - ? BoxDecoration( - color: Theme.of(context) - .extension()! - .backgroundAppBar, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, + decoration: + isDesktop + ? BoxDecoration( + color: + Theme.of(context) + .extension()! + .backgroundAppBar, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants + .size + .circularBorderRadius, + ), ), - ), - ) - : null, + ) + : null, child: Padding( - padding: isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -486,92 +506,59 @@ class _TransactionDetailsViewState currentHeight: currentHeight, coin: coin, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), SelectableText( _transaction.isCancelled ? coin is Ethereum ? "Failed" : "Cancelled" : whatIsIt( - _transaction, - currentHeight, - ), + _transaction, + currentHeight, + ), style: STextStyles.desktopTextMedium( - context, - ), + context, + ), ), ], ), Column( - crossAxisAlignment: isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + crossAxisAlignment: + isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "$amountPrefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.titleBold12( - context, - ), - ), - const SizedBox( - height: 2, - ), - if (ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) - SelectableText( - "$amountPrefix${(amount.decimal * ref.watch( - priceAnd24hChangeNotifierProvider - .select( - (value) => isTokenTx - ? value - .getTokenPrice( - _transaction - .otherData!, - ) - .item1 - : value - .getPrice( - coin, - ) - .item1, - ), - )).toAmount(fractionDigits: 2).fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale, - ), - ), - )} ${ref.watch( - prefsChangeNotifierProvider - .select( - (value) => value.currency, - ), - )}", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, ) - : STextStyles.itemSubtitle( + : STextStyles.titleBold12( context, ), + ), + const SizedBox(height: 2), + if (price != null) + SelectableText( + "$amountPrefix${(amount.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ], ), @@ -589,23 +576,24 @@ class _TransactionDetailsViewState isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Status", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle(context), ), // Flexible( // child: FittedBox( @@ -616,25 +604,30 @@ class _TransactionDetailsViewState ? coin is Ethereum ? "Failed" : "Cancelled" - : whatIsIt( - _transaction, - currentHeight, - ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: _transaction.type == - TransactionType.outgoing - ? Theme.of(context) - .extension()! - .accentColorOrange - : Theme.of(context) - .extension()! - .accentColorGreen, - ) - : STextStyles.itemSubtitle12(context), + : whatIsIt(_transaction, currentHeight), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + _transaction.type == + TransactionType + .outgoing + ? Theme.of(context) + .extension< + StackColors + >()! + .accentColorOrange + : Theme.of(context) + .extension< + StackColors + >()! + .accentColorGreen, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -649,9 +642,7 @@ class _TransactionDetailsViewState TransactionSubType.mint)) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (!((coin is Monero || coin is Wownero) && _transaction.type == TransactionType.outgoing) && @@ -659,9 +650,10 @@ class _TransactionDetailsViewState _transaction.subType == TransactionSubType.mint)) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -687,30 +679,36 @@ class _TransactionDetailsViewState if (isDesktop) { showDialog( context: context, - builder: (_) => - DesktopDialog( - maxHeight: - double.infinity, - child: - AddressDetailsView( - addressId: - _transaction - .address - .value! - .id, - walletId: widget - .walletId, - ), - ), + builder: + ( + _, + ) => DesktopDialog( + maxHeight: + double + .infinity, + child: AddressDetailsView( + addressId: + _transaction + .address + .value! + .id, + walletId: + widget + .walletId, + ), + ), ); } else { - Navigator.of(context) - .pushNamed( + Navigator.of( + context, + ).pushNamed( AddressDetailsView .routeName, arguments: Tuple2( - _transaction.address - .value!.id, + _transaction + .address + .value! + .id, widget.walletId, ), ); @@ -725,83 +723,86 @@ class _TransactionDetailsViewState TransactionType.outgoing ? "Sent to" : "Receiving address", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), _transaction.type == TransactionType.incoming ? FutureBuilder( - future: fetchContactNameFor( - _transaction - .address.value!.value, - ), - builder: ( - builderContext, - AsyncSnapshot - snapshot, - ) { - String - addressOrContactName = - _transaction.address - .value!.value; - if (snapshot.connectionState == - ConnectionState - .done && - snapshot.hasData) { - addressOrContactName = - snapshot.data!; - } - return SelectableText( - addressOrContactName, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( + future: fetchContactNameFor( + _transaction + .address + .value! + .value, + ), + builder: ( + builderContext, + AsyncSnapshot + snapshot, + ) { + String addressOrContactName = + _transaction + .address + .value! + .value; + if (snapshot.connectionState == + ConnectionState + .done && + snapshot.hasData) { + addressOrContactName = + snapshot.data!; + } + return SelectableText( + addressOrContactName, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( - color: Theme.of( - context, - ) - .extension< - StackColors>()! - .textDark, + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, ) - : STextStyles - .itemSubtitle12( + : STextStyles.itemSubtitle12( context, ), - ); - }, - ) + ); + }, + ) : SelectableText( - _transaction - .address.value!.value, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( + _transaction + .address + .value! + .value, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( context, ).copyWith( - color: Theme.of( - context, - ) - .extension< - StackColors>()! - .textDark, + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, ) - : STextStyles - .itemSubtitle12( + : STextStyles.itemSubtitle12( context, ), - ), + ), ], ), ), @@ -815,14 +816,13 @@ class _TransactionDetailsViewState if (coin is Epiccash) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is Epiccash) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -835,33 +835,33 @@ class _TransactionDetailsViewState children: [ Text( "On chain note", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), - ), - const SizedBox( - height: 8, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), + const SizedBox(height: 8), SelectableText( _transaction.otherData ?? "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -875,13 +875,12 @@ class _TransactionDetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -893,104 +892,104 @@ class _TransactionDetailsViewState (coin is Epiccash) ? "Local Note" : "Note ", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), isDesktop ? IconPencilButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: _transaction.txid, - walletId: walletId, - ), - ); - }, - ); - }, - ) - : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple2( - _transaction.txid, - walletId, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension< - StackColors>()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Edit", - style: STextStyles.link2( - context, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: _transaction.txid, + walletId: walletId, ), + ); + }, + ); + }, + ) + : GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple2( + _transaction.txid, + walletId, + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: + Theme.of(context) + .extension< + StackColors + >()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Edit", + style: STextStyles.link2( + context, ), - ], - ), + ), + ], ), + ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), SelectableText( ref .watch( - pTransactionNote( - ( - txid: _transaction.txid, - walletId: walletId - ), - ), + pTransactionNote(( + txid: _transaction.txid, + walletId: walletId, + )), ) ?.value ?? "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1002,34 +1001,36 @@ class _TransactionDetailsViewState children: [ Text( "Date", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), - if (isDesktop) - const SizedBox( - height: 2, - ), + if (isDesktop) const SizedBox(height: 2), if (isDesktop) SelectableText( Format.extractDateFrom( _transaction.timestamp, ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1038,16 +1039,21 @@ class _TransactionDetailsViewState Format.extractDateFrom( _transaction.timestamp, ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton( @@ -1061,34 +1067,36 @@ class _TransactionDetailsViewState if (coin is! NanoCurrency) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is! NanoCurrency) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Builder( builder: (context) { - final String feeString = showFeePending - ? _transaction.isConfirmed( - currentHeight, - minConfirms, - ) - ? ref + final String feeString = + showFeePending + ? _transaction.isConfirmed( + currentHeight, + minConfirms, + ) + ? ref + .watch( + pAmountFormatter(coin), + ) + .format( + fee, + withUnitName: isTokenTx, + ) + : "Pending" + : ref .watch(pAmountFormatter(coin)) .format( fee, withUnitName: isTokenTx, - ) - : "Pending" - : ref - .watch(pAmountFormatter(coin)) - .format( - fee, - withUnitName: isTokenTx, - ); + ); return Row( mainAxisAlignment: @@ -1102,55 +1110,56 @@ class _TransactionDetailsViewState children: [ Text( "Transaction fee", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), if (!isDesktop) SelectableText( feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: feeString), @@ -1161,9 +1170,7 @@ class _TransactionDetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), Builder( builder: (context) { final String height; @@ -1174,10 +1181,11 @@ class _TransactionDetailsViewState if (widget.coin is Bitcoincash || widget.coin is Ecash) { - height = _transaction.height != null && - _transaction.height! > 0 - ? "${_transaction.height!}" - : "Pending"; + height = + _transaction.height != null && + _transaction.height! > 0 + ? "${_transaction.height!}" + : "Pending"; confirmations = confirms.toString(); } else if (widget.coin is Epiccash && _transaction.slateId == null) { @@ -1192,9 +1200,10 @@ class _TransactionDetailsViewState height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { - height = confirms > 0 - ? "${_transaction.height}" - : "Pending"; + height = + confirms > 0 + ? "${_transaction.height}" + : "Pending"; } confirmations = confirms.toString(); @@ -1203,9 +1212,10 @@ class _TransactionDetailsViewState return Column( children: [ RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1218,56 +1228,58 @@ class _TransactionDetailsViewState children: [ Text( "Block height", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), if (!isDesktop) SelectableText( height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1276,13 +1288,12 @@ class _TransactionDetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1295,56 +1306,58 @@ class _TransactionDetailsViewState children: [ Text( "Confirmations", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( confirmations, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), if (!isDesktop) SelectableText( confirmations, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1358,14 +1371,13 @@ class _TransactionDetailsViewState if (coin is Ethereum) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is Ethereum) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1373,25 +1385,32 @@ class _TransactionDetailsViewState children: [ Text( "Nonce", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.nonce.toString(), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1399,14 +1418,13 @@ class _TransactionDetailsViewState if (kDebugMode) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (kDebugMode) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1414,38 +1432,44 @@ class _TransactionDetailsViewState children: [ Text( "Tx sub type", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.subType.toString(), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1458,50 +1482,49 @@ class _TransactionDetailsViewState children: [ Text( "Transaction ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), - ), - const SizedBox( - height: 8, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), + const SizedBox(height: 8), // Flexible( // child: FittedBox( // fit: BoxFit.scaleDown, // child: SelectableText( _transaction.txid, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (coin is! Epiccash) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (coin is! Epiccash) CustomTextButton( text: "Open in block explorer", onTap: () async { final uri = getBlockExplorerTransactionUrlFor( - coin: coin, - txid: _transaction.txid, - ); + coin: coin, + txid: _transaction.txid, + ); if (ref .read( @@ -1511,8 +1534,8 @@ class _TransactionDetailsViewState false) { final shouldContinue = await showExplorerWarning( - "${uri.scheme}://${uri.host}", - ); + "${uri.scheme}://${uri.host}", + ); if (!shouldContinue) { return; @@ -1527,21 +1550,22 @@ class _TransactionDetailsViewState try { await launchUrl( uri, - mode: LaunchMode - .externalApplication, + mode: + LaunchMode + .externalApplication, ); } catch (_) { - if (mounted) { + if (context.mounted) { unawaited( showDialog( context: context, - builder: (_) => - StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), + builder: + (_) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), ), ); } @@ -1562,14 +1586,9 @@ class _TransactionDetailsViewState ], ), ), + if (isDesktop) const SizedBox(width: 12), if (isDesktop) - const SizedBox( - width: 12, - ), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, - ), + IconCopyButton(data: _transaction.txid), ], ), ), @@ -1653,14 +1672,13 @@ class _TransactionDetailsViewState if (coin is Epiccash) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is Epiccash) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1672,14 +1690,14 @@ class _TransactionDetailsViewState children: [ Text( "Slate ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -1687,27 +1705,27 @@ class _TransactionDetailsViewState // child: SelectableText( _transaction.slateId ?? "Unknown", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), ], ), - if (isDesktop) - const SizedBox( - width: 12, - ), + if (isDesktop) const SizedBox(width: 12), if (isDesktop) IconCopyButton( data: _transaction.slateId ?? "Unknown", @@ -1715,10 +1733,7 @@ class _TransactionDetailsViewState ], ), ), - if (!isDesktop) - const SizedBox( - height: 12, - ), + if (!isDesktop) const SizedBox(height: 12), ], ), ), @@ -1730,101 +1745,106 @@ class _TransactionDetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) - ? ConditionalParent( - condition: isDesktop, - builder: (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, - ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, + floatingActionButton: + (coin is Epiccash && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) + ? ConditionalParent( + condition: isDesktop, + builder: + (child) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, ), - ), - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); + child: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, + ), + ), + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + + if (wallet is EpiccashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, + ), + ); + return; + } - if (wallet is EpiccashWallet) { - final String? id = _transaction.slateId; - if (id == null) { unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Could not find Epic transaction ID", + showDialog( + barrierDismissible: false, context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), ), ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - ), - ); - final result = - await wallet.cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (context.mounted) { + // pop progress dialog + Navigator.of(context).pop(); - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, ), - ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "ERROR: Wallet type is not Epic Cash", + context: context, + ), + ); + return; } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - ), - ); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), ), ), - ), - ) - : null, + ) + : null, ), ); } @@ -1843,10 +1863,7 @@ class _Divider extends StatelessWidget { } class IconCopyButton extends StatelessWidget { - const IconCopyButton({ - super.key, - required this.data, - }); + const IconCopyButton({super.key, required this.data}); final String data; @@ -1860,9 +1877,7 @@ class IconCopyButton extends StatelessWidget { Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), onPressed: () async { await Clipboard.setData(ClipboardData(text: data)); if (context.mounted) { @@ -1889,10 +1904,7 @@ class IconCopyButton extends StatelessWidget { } class IconPencilButton extends StatelessWidget { - const IconPencilButton({ - super.key, - this.onPressed, - }); + const IconPencilButton({super.key, this.onPressed}); final VoidCallback? onPressed; @@ -1906,9 +1918,7 @@ class IconPencilButton extends StatelessWidget { Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), onPressed: () => onPressed?.call(), child: Padding( padding: const EdgeInsets.all(5), diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart index ed6e007d2..353264600 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -32,6 +33,7 @@ import '../../../../utilities/constants.dart'; import '../../../../utilities/format.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; +import '../../../../wallets/crypto_currency/coins/ethereum.dart'; import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; @@ -51,11 +53,8 @@ import '../transaction_search_filter_view.dart'; import 'transaction_v2_card.dart'; import 'transaction_v2_details_view.dart'; -typedef _GroupedTransactions = ({ - String label, - DateTime startDate, - List transactions -}); +typedef _GroupedTransactions = + ({String label, DateTime startDate, List transactions}); class AllTransactionsV2View extends ConsumerStatefulWidget { const AllTransactionsV2View({ @@ -108,13 +107,14 @@ class _AllTransactionsV2ViewState extends ConsumerState { // debugPrint("FILTER: $filter"); final contacts = ref.read(addressBookServiceProvider).contacts; - final notes = ref - .read(mainDBProvider) - .isar - .transactionNotes - .where() - .walletIdEqualTo(walletId) - .findAllSync(); + final notes = + ref + .read(mainDBProvider) + .isar + .transactionNotes + .where() + .walletIdEqualTo(walletId) + .findAllSync(); return transactions.where((tx) { if (!filter.sent && !filter.received) { @@ -160,23 +160,25 @@ class _AllTransactionsV2ViewState extends ConsumerState { bool contains = false; // check if address book name contains - contains |= contacts - .where( - (e) => - e.addresses - .map((e) => e.address) - .toSet() - .intersection(tx.associatedAddresses()) - .isNotEmpty && - e.name.toLowerCase().contains(keyword), - ) - .isNotEmpty; + contains |= + contacts + .where( + (e) => + e.addresses + .map((e) => e.address) + .toSet() + .intersection(tx.associatedAddresses()) + .isNotEmpty && + e.name.toLowerCase().contains(keyword), + ) + .isNotEmpty; // check if address contains - contains |= tx - .associatedAddresses() - .where((e) => e.toLowerCase().contains(keyword)) - .isNotEmpty; + contains |= + tx + .associatedAddresses() + .where((e) => e.toLowerCase().contains(keyword)) + .isNotEmpty; TransactionNote? note; final matchingNotes = notes.where((e) => e.txid == tx.txid); @@ -197,8 +199,9 @@ class _AllTransactionsV2ViewState extends ConsumerState { contains |= tx.type.name.toLowerCase().contains(keyword); // check if date contains - contains |= - Format.extractDateFrom(tx.timestamp).toLowerCase().contains(keyword); + contains |= Format.extractDateFrom( + tx.timestamp, + ).toLowerCase().contains(keyword); return contains; } @@ -212,13 +215,14 @@ class _AllTransactionsV2ViewState extends ConsumerState { } text = text.toLowerCase(); final contacts = ref.read(addressBookServiceProvider).contacts; - final notes = ref - .read(mainDBProvider) - .isar - .transactionNotes - .where() - .walletIdEqualTo(walletId) - .findAllSync(); + final notes = + ref + .read(mainDBProvider) + .isar + .transactionNotes + .where() + .walletIdEqualTo(walletId) + .findAllSync(); return transactions .where((tx) => _isKeywordMatch(tx, text, contacts, notes)) @@ -234,8 +238,11 @@ class _AllTransactionsV2ViewState extends ConsumerState { final date = DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000); final monthYear = "${Constants.monthMap[date.month]} ${date.year}"; if (map[monthYear] == null) { - map[monthYear] = - (label: monthYear, startDate: date, transactions: [tx]); + map[monthYear] = ( + label: monthYear, + startDate: date, + transactions: [tx], + ); } else { map[monthYear]!.transactions.add(tx); } @@ -252,96 +259,94 @@ class _AllTransactionsV2ViewState extends ConsumerState { return MasterScaffold( background: Theme.of(context).extension()!.background, isDesktop: isDesktop, - appBar: isDesktop - ? DesktopAppBar( - isCompactHeight: true, - background: Theme.of(context).extension()!.popupBG, - leading: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: Navigator.of(context).pop, - ), - const SizedBox( - width: 12, - ), - Text( - "Transactions", - style: STextStyles.desktopH3(context), - ), - ], - ), - ) - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75), - ); - } - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Transactions", - style: STextStyles.navBarTitle(context), - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 20, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("transactionSearchFilterViewButton"), - size: 36, + appBar: + isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension()!.popupBG, + leading: Row( + children: [ + const SizedBox(width: 32), + AppBarIconButton( + size: 32, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultBG, shadows: const [], - color: Theme.of(context) - .extension()! - .background, icon: SvgPicture.asset( - Assets.svg.filter, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: + Theme.of( + context, + ).extension()!.topNavIconPrimary, ), - onPressed: () { - Navigator.of(context).pushNamed( - TransactionSearchFilterView.routeName, - arguments: ref.read(pWalletCoin(walletId)), - ); - }, + onPressed: Navigator.of(context).pop, ), - ), + const SizedBox(width: 12), + Text("Transactions", style: STextStyles.desktopH3(context)), + ], ), - ], - ), + ) + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75), + ); + } + if (context.mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Transactions", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 20, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("transactionSearchFilterViewButton"), + size: 36, + shadows: const [], + color: + Theme.of( + context, + ).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.filter, + color: + Theme.of( + context, + ).extension()!.accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + Navigator.of(context).pushNamed( + TransactionSearchFilterView.routeName, + arguments: ref.read(pWalletCoin(walletId)), + ); + }, + ), + ), + ), + ], + ), body: Padding( padding: EdgeInsets.only( left: isDesktop ? 20 : 12, @@ -356,15 +361,10 @@ class _AllTransactionsV2ViewState extends ConsumerState { children: [ ConditionalParent( condition: isDesktop, - builder: (child) => SizedBox( - width: 570, - child: child, - ), + builder: (child) => SizedBox(width: 570, child: child), child: ConditionalParent( condition: !isDesktop, - builder: (child) => Expanded( - child: child, - ), + builder: (child) => Expanded(child: child), child: ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -379,15 +379,18 @@ class _AllTransactionsV2ViewState extends ConsumerState { _searchString = value; }); }, - style: isDesktop - ? STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - height: 1.8, - ) - : STextStyles.field(context), + style: + isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), decoration: standardInputDecoration( "Search...", searchFieldFocusNode, @@ -405,35 +408,33 @@ class _AllTransactionsV2ViewState extends ConsumerState { height: isDesktop ? 20 : 16, ), ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - _searchString = ""; - }); - }, - ), - ], + suffixIcon: + _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), ), - ), - ) - : null, + ) + : null, ), ), ), ), ), - if (isDesktop) - const SizedBox( - width: 20, - ), + if (isDesktop) const SizedBox(width: 20), if (isDesktop) SecondaryButton( buttonHeight: ButtonHeight.l, @@ -441,9 +442,10 @@ class _AllTransactionsV2ViewState extends ConsumerState { label: "Filter", icon: SvgPicture.asset( Assets.svg.filter, - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, width: 20, height: 20, ), @@ -468,25 +470,14 @@ class _AllTransactionsV2ViewState extends ConsumerState { ], ), ), - if (isDesktop) - const SizedBox( - height: 8, - ), + if (isDesktop) const SizedBox(height: 8), if (isDesktop && ref.watch(transactionFilterProvider.state).state != null) const Padding( - padding: EdgeInsets.symmetric( - vertical: 8, - ), - child: Row( - children: [ - TransactionFilterOptionBar(), - ], - ), + padding: EdgeInsets.symmetric(vertical: 8), + child: Row(children: [TransactionFilterOptionBar()]), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Expanded( child: Consumer( builder: (_, ref, __) { @@ -494,33 +485,35 @@ class _AllTransactionsV2ViewState extends ConsumerState { ref.watch(transactionFilterProvider.state).state; return FutureBuilder( - future: ref - .watch(mainDBProvider) - .isar - .transactionV2s - .buildQuery( - whereClauses: [ - IndexWhereClause.equalTo( - indexName: 'walletId', - value: [widget.walletId], - ), - ], - filter: widget.contractAddress == null - ? ref - .watch(pWallets) - .getWallet(widget.walletId) - .transactionFilterOperation - : ref - .read(pCurrentTokenWallet)! - .transactionFilterOperation, - sortBy: [ - const SortProperty( - property: "timestamp", - sort: Sort.desc, - ), - ], - ) - .findAll(), + future: + ref + .watch(mainDBProvider) + .isar + .transactionV2s + .buildQuery( + whereClauses: [ + IndexWhereClause.equalTo( + indexName: 'walletId', + value: [widget.walletId], + ), + ], + filter: + widget.contractAddress == null + ? ref + .watch(pWallets) + .getWallet(widget.walletId) + .transactionFilterOperation + : ref + .read(pCurrentTokenWallet)! + .transactionFilterOperation, + sortBy: [ + const SortProperty( + property: "timestamp", + sort: Sort.desc, + ), + ], + ) + .findAll(), builder: (_, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { @@ -549,43 +542,39 @@ class _AllTransactionsV2ViewState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (index != 0) - const SizedBox( - height: 12, - ), + if (index != 0) const SizedBox(height: 12), Text( month.label, style: STextStyles.smallMed12(context), ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), if (isDesktop) RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: ListView.separated( shrinkWrap: true, primary: false, - separatorBuilder: (context, _) => - Container( - height: 1, - color: Theme.of(context) - .extension()! - .background, - ), + separatorBuilder: + (context, _) => Container( + height: 1, + color: + Theme.of(context) + .extension()! + .background, + ), itemCount: month.transactions.length, - itemBuilder: (context, index) => - Padding( - padding: const EdgeInsets.all(4), - child: DesktopTransactionCardRow( - key: Key( - "transactionCard_key_${month.transactions[index].txid}", + itemBuilder: + (context, index) => Padding( + padding: const EdgeInsets.all(4), + child: DesktopTransactionCardRow( + key: Key( + "transactionCard_key_${month.transactions[index].txid}", + ), + transaction: + month.transactions[index], + walletId: walletId, + ), ), - transaction: - month.transactions[index], - walletId: walletId, - ), - ), ), ), if (!isDesktop) @@ -652,10 +641,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - sent: false, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(sent: false); setState(() {}); } }, @@ -671,10 +660,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - received: false, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(received: false); setState(() {}); } }, @@ -691,10 +680,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - to: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(to: null); setState(() {}); } }, @@ -710,10 +699,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - from: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(from: null); setState(() {}); } }, @@ -730,10 +719,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - amount: null, - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(amount: null); setState(() {}); } }, @@ -749,10 +738,10 @@ class _TransactionFilterOptionBarState if (items.isEmpty) { ref.read(transactionFilterProvider.state).state = null; } else { - ref.read(transactionFilterProvider.state).state = - ref.read(transactionFilterProvider.state).state?.copyWith( - keyword: "", - ); + ref.read(transactionFilterProvider.state).state = ref + .read(transactionFilterProvider.state) + .state + ?.copyWith(keyword: ""); setState(() {}); } }, @@ -773,9 +762,7 @@ class _TransactionFilterOptionBarState scrollDirection: Axis.horizontal, shrinkWrap: true, itemCount: items.length, - separatorBuilder: (_, __) => const SizedBox( - width: 16, - ), + separatorBuilder: (_, __) => const SizedBox(width: 16), itemBuilder: (context, index) => items[index], ), ); @@ -804,9 +791,7 @@ class TransactionFilterOptionBarItem extends StatelessWidget { borderRadius: BorderRadius.circular(1000), ), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 14, - ), + padding: const EdgeInsets.symmetric(horizontal: 14), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, @@ -824,9 +809,7 @@ class TransactionFilterOptionBarItem extends StatelessWidget { ), ), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), XIcon( width: 16, height: 16, @@ -865,23 +848,25 @@ class _DesktopTransactionCardRowState bool get isTokenTx => ethContract != null; String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( - currentChainHeight: height, - minConfirms: minConfirms, - minCoinbaseConfirms: ref + currentChainHeight: height, + minConfirms: minConfirms, + minCoinbaseConfirms: + ref .read(pWallets) .getWallet(widget.walletId) .cryptoCurrency .minCoinbaseConfirms, - ); + ); @override void initState() { walletId = widget.walletId; - minConfirms = ref - .read(pWallets) - .getWallet(widget.walletId) - .cryptoCurrency - .minConfirms; + minConfirms = + ref + .read(pWallets) + .getWallet(widget.walletId) + .cryptoCurrency + .minConfirms; _transaction = widget.transaction; if (_transaction.subType == TransactionSubType.ethToken) { @@ -901,17 +886,25 @@ class _DesktopTransactionCardRowState localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); final coin = ref.watch(pWalletCoin(walletId)); - final price = ref - .watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin)), - ) - .item1; + Decimal? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.contractAddress!)?.value + : value.getPrice(coin)?.value, + ), + ); + } late final String prefix; if (Util.isDesktop) { @@ -939,6 +932,7 @@ class _DesktopTransactionCardRowState case TransactionType.outgoing: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); break; @@ -970,6 +964,7 @@ class _DesktopTransactionCardRowState case TransactionType.unknown: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); break; } @@ -979,8 +974,9 @@ class _DesktopTransactionCardRowState color: Theme.of(context).extension()!.popupBG, elevation: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: RawMaterialButton( shape: RoundedRectangleBorder( @@ -992,34 +988,28 @@ class _DesktopTransactionCardRowState if (Util.isDesktop) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: TransactionV2DetailsView( - transaction: _transaction, - coin: coin, - walletId: walletId, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TransactionV2DetailsView( + transaction: _transaction, + coin: coin, + walletId: walletId, + ), + ), ); } else { unawaited( Navigator.of(context).pushNamed( TransactionV2DetailsView.routeName, - arguments: ( - tx: _transaction, - coin: coin, - walletId: walletId, - ), + arguments: (tx: _transaction, coin: coin, walletId: walletId), ), ); } }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 10, - horizontal: 16, - ), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), child: Row( children: [ TxIcon( @@ -1027,18 +1017,14 @@ class _DesktopTransactionCardRowState currentHeight: currentHeight, coin: coin, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Expanded( flex: 3, child: Text( - whatIsIt( - _transaction, - currentHeight, - ), - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( + whatIsIt(_transaction, currentHeight), + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( color: Theme.of(context).extension()!.textDark, ), ), @@ -1062,24 +1048,18 @@ class _DesktopTransactionCardRowState flex: 6, child: Text( "$prefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}", - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( color: Theme.of(context).extension()!.textDark, ), ), ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) + if (price != null) Expanded( flex: 4, child: Text( - "$prefix${(amount.decimal * price).toAmount( - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "$prefix${(amount.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.desktopTextExtraExtraSmall(context), ), ), diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart index 34abbb43e..5219b3f26 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/boost_transaction_view.dart @@ -23,6 +23,7 @@ import '../../../../utilities/amount/amount_formatter.dart'; import '../../../../utilities/show_loading.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; +import '../../../../wallets/crypto_currency/coins/ethereum.dart'; import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart'; import '../../../../widgets/background.dart'; @@ -37,10 +38,7 @@ import '../../../../widgets/stack_dialog.dart'; import '../../../send_view/confirm_transaction_view.dart'; class BoostTransactionView extends ConsumerStatefulWidget { - const BoostTransactionView({ - super.key, - required this.transaction, - }); + const BoostTransactionView({super.key, required this.transaction}); static const String routeName = "/boostTransaction"; @@ -73,10 +71,11 @@ class _BoostTransactionViewState extends ConsumerState { if (_newRate <= rate) { await showDialog( context: context, - builder: (_) => const StackOkDialog( - title: "Error", - message: "New fee rate must be greater than the current rate.", - ), + builder: + (_) => const StackOkDialog( + title: "Error", + message: "New fee rate must be greater than the current rate.", + ), ); return; } @@ -99,11 +98,12 @@ class _BoostTransactionViewState extends ConsumerState { if (txData == null && mounted) { await showDialog( context: context, - builder: (_) => StackOkDialog( - title: "RBF send error", - message: ex?.toString() ?? "Unknown error found", - maxWidth: 600, - ), + builder: + (_) => StackOkDialog( + title: "RBF send error", + message: ex?.toString() ?? "Unknown error found", + maxWidth: 600, + ), ); return; } else { @@ -112,17 +112,18 @@ class _BoostTransactionViewState extends ConsumerState { unawaited( showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: ConfirmTransactionView( - txData: txData!, - walletId: walletId, - onSuccess: () {}, - // isPaynymTransaction: isPaynymSend, TODO ? - routeOnSuccessName: DesktopHomeView.routeName, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: ConfirmTransactionView( + txData: txData!, + walletId: walletId, + onSuccess: () {}, + // isPaynymTransaction: isPaynymSend, TODO ? + routeOnSuccessName: DesktopHomeView.routeName, + ), + ), ), ); } else if (mounted) { @@ -130,12 +131,13 @@ class _BoostTransactionViewState extends ConsumerState { Navigator.of(context).push( RouteGenerator.getRoute( shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, - builder: (_) => ConfirmTransactionView( - txData: txData!, - walletId: walletId, - // isPaynymTransaction: isPaynymSend, TODO ? - onSuccess: () {}, - ), + builder: + (_) => ConfirmTransactionView( + txData: txData!, + walletId: walletId, + // isPaynymTransaction: isPaynymSend, TODO ? + onSuccess: () {}, + ), settings: const RouteSettings( name: ConfirmTransactionView.routeName, ), @@ -159,6 +161,7 @@ class _BoostTransactionViewState extends ConsumerState { ); amount = _transaction.getAmountSentFromThisWallet( fractionDigits: ref.read(pWalletCoin(walletId)).fractionDigits, + subtractFee: ref.read(pWalletCoin(walletId)) is! Ethereum, ); rate = (fee.raw ~/ BigInt.from(_transaction.vSize!)).toInt(); _newRate = rate + 1; @@ -169,61 +172,56 @@ class _BoostTransactionViewState extends ConsumerState { @override Widget build(BuildContext context) { final coin = ref.watch(pWalletCoin(walletId)); - final String feeString = ref.watch(pAmountFormatter(coin)).format( - fee, - ); - final String amountString = ref.watch(pAmountFormatter(coin)).format( - amount, - ); + final String feeString = ref.watch(pAmountFormatter(coin)).format(fee); + final String amountString = ref + .watch(pAmountFormatter(coin)) + .format(amount); final String feeRateString = "$rate sats/vByte"; return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Boost transaction", - style: STextStyles.navBarTitle(context), + builder: + (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Boost transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: child, ), ), - body: child, - ), - ), child: Padding( - padding: isDesktop - ? const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.only(left: 32, right: 32, bottom: 32) + : const EdgeInsets.all(12), child: ConditionalParent( condition: isDesktop, builder: (child) { return Column( children: [ RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context) - .extension()! - .backgroundAppBar - : null, + borderColor: + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, padding: const EdgeInsets.all(0), child: child, ), - const SizedBox( - height: 32, - ), + const SizedBox(height: 32), PrimaryButton( buttonHeight: ButtonHeight.l, label: "Preview send", @@ -238,10 +236,11 @@ class _BoostTransactionViewState extends ConsumerState { children: [ ConditionalParent( condition: isDesktop, - builder: (child) => RoundedWhiteContainer( - padding: EdgeInsets.zero, - child: child, - ), + builder: + (child) => RoundedWhiteContainer( + padding: EdgeInsets.zero, + child: child, + ), child: Column( children: [ DetailItem( @@ -277,15 +276,9 @@ class _BoostTransactionViewState extends ConsumerState { ), ), if (!isDesktop) const Spacer(), + if (!isDesktop) const SizedBox(height: 16), if (!isDesktop) - const SizedBox( - height: 16, - ), - if (!isDesktop) - PrimaryButton( - label: "Preview send", - onPressed: _previewTxn, - ), + PrimaryButton(label: "Preview send", onPressed: _previewTxn), ], ), ), @@ -305,9 +298,7 @@ class _Divider extends StatelessWidget { color: Theme.of(context).extension()!.backgroundAppBar, ); } else { - return const SizedBox( - height: 12, - ); + return const SizedBox(height: 12); } } } diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart index 34e5c662b..9af007aef 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -25,10 +26,7 @@ import '../../sub_widgets/tx_icon.dart'; import 'transaction_v2_details_view.dart'; class TransactionCardV2 extends ConsumerStatefulWidget { - const TransactionCardV2({ - super.key, - required this.transaction, - }); + const TransactionCardV2({super.key, required this.transaction}); final TransactionV2 transaction; @@ -47,25 +45,24 @@ class _TransactionCardStateV2 extends ConsumerState { bool get isTokenTx => tokenContract != null; - String whatIsIt( - CryptoCurrency coin, - int currentHeight, - ) => + String whatIsIt(CryptoCurrency coin, int currentHeight) => _transaction.isCancelled && coin is Ethereum ? "Failed" : _transaction.statusLabel( - currentChainHeight: currentHeight, - minConfirms: ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .minConfirms, - minCoinbaseConfirms: ref - .read(pWallets) - .getWallet(walletId) - .cryptoCurrency - .minCoinbaseConfirms, - ); + currentChainHeight: currentHeight, + minConfirms: + ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minConfirms, + minCoinbaseConfirms: + ref + .read(pWallets) + .getWallet(walletId) + .cryptoCurrency + .minCoinbaseConfirms, + ); @override void initState() { @@ -106,18 +103,23 @@ class _TransactionCardStateV2 extends ConsumerState { localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); - final price = ref - .watch( - priceAnd24hChangeNotifierProvider.select( - (value) => isTokenTx - ? value.getTokenPrice(tokenContract!.address) - : value.getPrice(coin), - ), - ) - .item1; + Decimal? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => + isTokenTx + ? value.getTokenPrice(tokenContract!.address)?.value + : value.getPrice(coin)?.value, + ), + ); + } final currentHeight = ref.watch(pWalletChainHeight(walletId)); @@ -134,6 +136,7 @@ class _TransactionCardStateV2 extends ConsumerState { case TransactionType.outgoing: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); break; @@ -165,6 +168,7 @@ class _TransactionCardStateV2 extends ConsumerState { case TransactionType.unknown: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); break; } @@ -174,8 +178,9 @@ class _TransactionCardStateV2 extends ConsumerState { color: Theme.of(context).extension()!.popupBG, elevation: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: Padding( padding: const EdgeInsets.all(6), @@ -189,25 +194,22 @@ class _TransactionCardStateV2 extends ConsumerState { if (Util.isDesktop) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: TransactionV2DetailsView( - transaction: _transaction, - coin: coin, - walletId: walletId, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TransactionV2DetailsView( + transaction: _transaction, + coin: coin, + walletId: walletId, + ), + ), ); } else { unawaited( Navigator.of(context).pushNamed( TransactionV2DetailsView.routeName, - arguments: ( - tx: _transaction, - coin: coin, - walletId: walletId, - ), + arguments: (tx: _transaction, coin: coin, walletId: walletId), ), ); } @@ -221,9 +223,7 @@ class _TransactionCardStateV2 extends ConsumerState { coin: coin, currentHeight: currentHeight, ), - const SizedBox( - width: 14, - ), + const SizedBox(width: 14), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -236,17 +236,12 @@ class _TransactionCardStateV2 extends ConsumerState { child: FittedBox( fit: BoxFit.scaleDown, child: Text( - whatIsIt( - coin, - currentHeight, - ), + whatIsIt(coin, currentHeight), style: STextStyles.itemSubtitle12(context), ), ), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), Flexible( child: FittedBox( fit: BoxFit.scaleDown, @@ -262,9 +257,7 @@ class _TransactionCardStateV2 extends ConsumerState { ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // crossAxisAlignment: CrossAxisAlignment.end, @@ -278,29 +271,15 @@ class _TransactionCardStateV2 extends ConsumerState { ), ), ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) - const SizedBox( - width: 10, - ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) + if (price != null) const SizedBox(width: 10), + if (price != null) Flexible( child: FittedBox( fit: BoxFit.scaleDown, child: Builder( builder: (_) { return Text( - "$prefix${Amount.fromDecimal( - amount.decimal * price, - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "$prefix${Amount.fromDecimal(amount.decimal * price!, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.label(context), ); }, diff --git a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart index 8f0852941..06469c92b 100644 --- a/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart @@ -10,6 +10,7 @@ import 'dart:async'; +import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -133,43 +134,43 @@ class _TransactionV2DetailsViewState if (mounted) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: null, - maxWidth: 580, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + builder: + (context) => DesktopDialog( + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "Boost transaction", - style: STextStyles.desktopH3(context), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Boost transaction", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Flexible( + child: SingleChildScrollView( + child: BoostTransactionView( + transaction: _transaction, + ), ), ), - const DesktopDialogCloseButton(), ], ), - Flexible( - child: SingleChildScrollView( - child: BoostTransactionView( - transaction: _transaction, - ), - ), - ), - ], - ), - ), + ), ); } } else { unawaited( - Navigator.of(context).pushNamed( - BoostTransactionView.routeName, - arguments: _transaction, - ), + Navigator.of( + context, + ).pushNamed(BoostTransactionView.routeName, arguments: _transaction), ); } } finally { @@ -185,13 +186,15 @@ class _TransactionV2DetailsViewState final wallet = ref.read(pWallets).getWallet(walletId); - hasTxKeyProbably = wallet is LibMoneroWallet && + hasTxKeyProbably = + wallet is LibMoneroWallet && (_transaction.type == TransactionType.outgoing || _transaction.type == TransactionType.sentToSelf); if (_transaction.type case TransactionType.sentToSelf || TransactionType.outgoing) { - supportsRbf = _transaction.subType == TransactionSubType.none && + supportsRbf = + _transaction.subType == TransactionSubType.none && wallet is RbfInterface; } else { supportsRbf = false; @@ -230,6 +233,7 @@ class _TransactionV2DetailsViewState case TransactionType.unknown: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); break; @@ -240,81 +244,86 @@ class _TransactionV2DetailsViewState ); break; } - data = _transaction.outputs - .map( - (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, + data = + _transaction.outputs + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), ) - ), - ) - .toList(); + .toList(); } else if (_transaction.subType == TransactionSubType.cashFusion) { amount = _transaction.getAmountReceivedInThisWallet( fractionDigits: fractionDigits, ); - data = _transaction.outputs - .where((e) => e.walletOwns) - .map( - (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, - ) - ), - ) - .toList(); - } else { - switch (_transaction.type) { - case TransactionType.outgoing: - amount = _transaction.getAmountSentFromThisWallet( - fractionDigits: fractionDigits, - ); - data = _transaction.outputs - .where((e) => !e.walletOwns) + data = + _transaction.outputs + .where((e) => e.walletOwns) .map( (e) => ( addresses: e.addresses, amount: Amount( rawValue: e.value, fractionDigits: coin.fractionDigits, - ) + ), ), ) .toList(); + } else { + switch (_transaction.type) { + case TransactionType.outgoing: + amount = _transaction.getAmountSentFromThisWallet( + fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, + ); + data = + _transaction.outputs + .where((e) => !e.walletOwns) + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), + ) + .toList(); break; case TransactionType.incoming: case TransactionType.sentToSelf: if (_transaction.subType == TransactionSubType.sparkMint || _transaction.subType == TransactionSubType.sparkSpend) { - _sparkMemo = ref - .read(mainDBProvider) - .isar - .sparkCoins - .where() - .walletIdEqualToAnyLTagHash(walletId) - .filter() - .memoIsNotEmpty() - .and() - .heightEqualTo(_transaction.height) - .anyOf( - _transaction.outputs - .where( - (e) => - e.walletOwns && - e.addresses.isEmpty && - e.scriptPubKeyHex.length >= 488, - ) - .map((e) => e.scriptPubKeyHex.substring(2, 488)) - .toList(), - (q, element) => q.serializedCoinB64StartsWith(element), - ) - .memoProperty() - .findFirstSync(); + _sparkMemo = + ref + .read(mainDBProvider) + .isar + .sparkCoins + .where() + .walletIdEqualToAnyLTagHash(walletId) + .filter() + .memoIsNotEmpty() + .and() + .heightEqualTo(_transaction.height) + .anyOf( + _transaction.outputs + .where( + (e) => + e.walletOwns && + e.addresses.isEmpty && + e.scriptPubKeyHex.length >= 488, + ) + .map((e) => e.scriptPubKeyHex.substring(2, 488)) + .toList(), + (q, element) => q.serializedCoinB64StartsWith(element), + ) + .memoProperty() + .findFirstSync(); } if (_transaction.subType == TransactionSubType.sparkMint) { @@ -338,36 +347,39 @@ class _TransactionV2DetailsViewState fractionDigits: fractionDigits, ); } - data = _transaction.outputs - .where((e) => e.walletOwns) - .map( - (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, + data = + _transaction.outputs + .where((e) => e.walletOwns) + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), ) - ), - ) - .toList(); + .toList(); break; case TransactionType.unknown: amount = _transaction.getAmountSentFromThisWallet( fractionDigits: fractionDigits, + subtractFee: coin is! Ethereum, ); - data = _transaction.inputs - .where((e) => e.walletOwns) - .map( - (e) => ( - addresses: e.addresses, - amount: Amount( - rawValue: e.value, - fractionDigits: coin.fractionDigits, + data = + _transaction.inputs + .where((e) => e.walletOwns) + .map( + (e) => ( + addresses: e.addresses, + amount: Amount( + rawValue: e.value, + fractionDigits: coin.fractionDigits, + ), + ), ) - ), - ) - .toList(); + .toList(); break; } } @@ -381,24 +393,29 @@ class _TransactionV2DetailsViewState } String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel( - currentChainHeight: height, - minConfirms: minConfirms, - minCoinbaseConfirms: ref + currentChainHeight: height, + minConfirms: minConfirms, + minCoinbaseConfirms: + ref .read(pWallets) .getWallet(walletId) .cryptoCurrency .minCoinbaseConfirms, - ); + ); Future fetchContactNameFor(String address) async { if (address.isEmpty) { return address; } try { - final contacts = ref.read(addressBookServiceProvider).contacts.where( - (element) => element.addresses - .where((element) => element.address == address) - .isNotEmpty, + final contacts = ref + .read(addressBookServiceProvider) + .contacts + .where( + (element) => + element.addresses + .where((element) => element.address == address) + .isNotEmpty, ); if (contacts.isNotEmpty) { return contacts.first.name; @@ -406,7 +423,7 @@ class _TransactionV2DetailsViewState return address; } } catch (e, s) { - Logging.instance.w("$e\n$s", error: e, stackTrace: s,); + Logging.instance.w("$e\n$s", error: e, stackTrace: s); return address; } } @@ -427,8 +444,9 @@ class _TransactionV2DetailsViewState builder: (_, ref, __) { return Checkbox( value: ref.watch( - prefsChangeNotifierProvider - .select((value) => value.hideBlockExplorerWarning), + prefsChangeNotifierProvider.select( + (value) => value.hideBlockExplorerWarning, + ), ), onChanged: (value) { if (value is bool) { @@ -454,23 +472,21 @@ class _TransactionV2DetailsViewState child: Text( "Cancel", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), + style: Theme.of( + context, + ).extension()!.getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pop(true); }, - child: Text( - "Continue", - style: STextStyles.button(context), - ), + child: Text("Continue", style: STextStyles.button(context)), ), ); } else { @@ -484,10 +500,7 @@ class _TransactionV2DetailsViewState Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - "Attention", - style: STextStyles.desktopH2(context), - ), + Text("Attention", style: STextStyles.desktopH2(context)), Row( children: [ Consumer( @@ -531,10 +544,7 @@ class _TransactionV2DetailsViewState buttonHeight: ButtonHeight.l, label: "Cancel", onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(false); + Navigator.of(context, rootNavigator: true).pop(false); }, ), const SizedBox(width: 20), @@ -543,10 +553,7 @@ class _TransactionV2DetailsViewState buttonHeight: ButtonHeight.l, label: "Continue", onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(true); + Navigator.of(context, rootNavigator: true).pop(true); }, ), ], @@ -585,38 +592,53 @@ class _TransactionV2DetailsViewState coin.minCoinbaseConfirms, ); + Decimal? price; + if (ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls), + )) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.contractAddress!)?.value + : value.getPrice(coin)?.value, + ), + ); + } + return ConditionalParent( condition: !isDesktop, - builder: (child) => Background( - child: child, - ), + builder: (child) => Background(child: child), child: Scaffold( - backgroundColor: isDesktop - ? Colors.transparent - : Theme.of(context).extension()!.background, - appBar: isDesktop - ? null - : AppBar( - backgroundColor: - Theme.of(context).extension()!.background, - leading: AppBarBackButton( - onPressed: () async { - // if (FocusScope.of(context).hasFocus) { - // FocusScope.of(context).unfocus(); - // await Future.delayed(Duration(milliseconds: 50)); - // } - Navigator.of(context).pop(); - }, - ), - title: Text( - "Transaction details", - style: STextStyles.navBarTitle(context), + backgroundColor: + isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: + isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + // if (FocusScope.of(context).hasFocus) { + // FocusScope.of(context).unfocus(); + // await Future.delayed(Duration(milliseconds: 50)); + // } + Navigator.of(context).pop(); + }, + ), + title: Text( + "Transaction details", + style: STextStyles.navBarTitle(context), + ), ), - ), body: Padding( - padding: isDesktop - ? const EdgeInsets.only(left: 32) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.only(left: 32) + : const EdgeInsets.all(12), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -633,21 +655,20 @@ class _TransactionV2DetailsViewState ), Flexible( child: Padding( - padding: isDesktop - ? const EdgeInsets.only( - right: 32, - bottom: 32, - ) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.only(right: 32, bottom: 32) + : const EdgeInsets.all(0), child: ConditionalParent( condition: isDesktop, builder: (child) { return RoundedWhiteContainer( - borderColor: isDesktop - ? Theme.of(context) - .extension()! - .backgroundAppBar - : null, + borderColor: + isDesktop + ? Theme.of( + context, + ).extension()!.backgroundAppBar + : null, padding: const EdgeInsets.all(0), child: child, ); @@ -655,34 +676,41 @@ class _TransactionV2DetailsViewState child: SingleChildScrollView( primary: isDesktop ? false : null, child: Padding( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(4), + padding: + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(4), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(0) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(0) + : const EdgeInsets.all(12), child: Container( - decoration: isDesktop - ? BoxDecoration( - color: Theme.of(context) - .extension()! - .backgroundAppBar, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, + decoration: + isDesktop + ? BoxDecoration( + color: + Theme.of(context) + .extension()! + .backgroundAppBar, + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants + .size + .circularBorderRadius, + ), ), - ), - ) - : null, + ) + : null, child: Padding( - padding: isDesktop - ? const EdgeInsets.all(12) - : const EdgeInsets.all(0), + padding: + isDesktop + ? const EdgeInsets.all(12) + : const EdgeInsets.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -695,9 +723,7 @@ class _TransactionV2DetailsViewState currentHeight: currentHeight, coin: coin, ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), SelectableText( whatIsIt( _transaction, @@ -705,71 +731,72 @@ class _TransactionV2DetailsViewState ), style: STextStyles.desktopTextMedium( - context, - ), + context, + ), ), ], ), Column( - crossAxisAlignment: isDesktop - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + crossAxisAlignment: + isDesktop + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, children: [ SelectableText( "$amountPrefix${ref.watch(pAmountFormatter(coin)).format(amount, ethContract: ethContract)}", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.titleBold12( - context, - ), - ), - const SizedBox( - height: 2, - ), - if (ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.externalCalls, - ), - )) - SelectableText( - "$amountPrefix${(amount.decimal * ref.watch( - priceAnd24hChangeNotifierProvider - .select( - (value) => value - .getPrice( - coin, - ) - .item1, - ), - )).toAmount(fractionDigits: 2).fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale, - ), - ), - )} ${ref.watch( - prefsChangeNotifierProvider - .select( - (value) => value.currency, - ), - )}", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, ) - : STextStyles.itemSubtitle( + : STextStyles.titleBold12( context, ), + ), + const SizedBox(height: 2), + if (price != null) + Builder( + builder: (context) { + final total = (amount.decimal * + price!) + .toAmount( + fractionDigits: 2, + ); + final formatted = total.fiatString( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => + value.locale, + ), + ), + ); + final ticker = ref.watch( + prefsChangeNotifierProvider + .select( + (value) => + value.currency, + ), + ); + return SelectableText( + "$amountPrefix$formatted $ticker", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), + ); + }, ), ], ), @@ -787,52 +814,58 @@ class _TransactionV2DetailsViewState isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Status", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle(context), ), // Flexible( // child: FittedBox( // fit: BoxFit.scaleDown, // child: SelectableText( - whatIsIt( - _transaction, - currentHeight, - ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: _transaction.type == - TransactionType - .outgoing && - _transaction.subType != - TransactionSubType - .cashFusion - ? Theme.of(context) - .extension()! - .accentColorOrange - : Theme.of(context) - .extension()! - .accentColorGreen, - ) - : STextStyles.itemSubtitle12(context), + whatIsIt(_transaction, currentHeight), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + _transaction.type == + TransactionType + .outgoing && + _transaction + .subType != + TransactionSubType + .cashFusion + ? Theme.of(context) + .extension< + StackColors + >()! + .accentColorOrange + : Theme.of(context) + .extension< + StackColors + >()! + .accentColorGreen, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), @@ -847,9 +880,7 @@ class _TransactionV2DetailsViewState TransactionSubType.mint)) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (!((coin is Monero || coin is Wownero) && _transaction.type == TransactionType.outgoing) && @@ -857,9 +888,10 @@ class _TransactionV2DetailsViewState _transaction.subType == TransactionSubType.mint)) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -925,19 +957,17 @@ class _TransactionV2DetailsViewState }, child: Text( outputLabel, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -952,11 +982,13 @@ class _TransactionV2DetailsViewState builder: ( builderContext, AsyncSnapshot - snapshot, + snapshot, ) { String - addressOrContactName = - data.first.addresses + addressOrContactName = + data + .first + .addresses .first; if (snapshot.connectionState == ConnectionState @@ -967,92 +999,95 @@ class _TransactionV2DetailsViewState } return SelectableText( addressOrContactName, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of( + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, ) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + : STextStyles.itemSubtitle12( + context, + ), ); }, ) else - for (int i = 0; - i < data.length; - i++) + for ( + int i = 0; + i < data.length; + i++ + ) ConditionalParent( condition: i > 0, - builder: (child) => Column( - crossAxisAlignment: - CrossAxisAlignment - .stretch, - children: [ - const _Divider(), - child, - ], - ), + builder: + (child) => Column( + crossAxisAlignment: + CrossAxisAlignment + .stretch, + children: [ + const _Divider(), + child, + ], + ), child: Padding( padding: const EdgeInsets.all( - 8.0, - ), + 8.0, + ), child: Column( crossAxisAlignment: CrossAxisAlignment .start, children: [ - ...data[i] - .addresses - .map( - (e) { - return FutureBuilder( - future: - fetchContactNameFor( - e, - ), - builder: ( - builderContext, - AsyncSnapshot< - String> - snapshot, - ) { - final String - addressOrContactName; - if (snapshot.connectionState == - ConnectionState - .done && + ...data[i].addresses.map(( + e, + ) { + return FutureBuilder( + future: + fetchContactNameFor( + e, + ), + builder: ( + builderContext, + AsyncSnapshot< + String + > + snapshot, + ) { + final String + addressOrContactName; + if (snapshot.connectionState == + ConnectionState + .done && + snapshot + .hasData) { + addressOrContactName = snapshot - .hasData) { - addressOrContactName = - snapshot - .data!; - } else { - addressOrContactName = - e; - } - - return OutputCard( - address: - addressOrContactName, - amount: data[ - i] - .amount, - coin: coin, - ); - }, - ); - }, - ), + .data!; + } else { + addressOrContactName = + e; + } + + return OutputCard( + address: + addressOrContactName, + amount: + data[i] + .amount, + coin: coin, + ); + }, + ); + }), ], ), ), @@ -1072,14 +1107,13 @@ class _TransactionV2DetailsViewState if (coin is Epiccash) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is Epiccash) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1092,33 +1126,33 @@ class _TransactionV2DetailsViewState children: [ Text( "On chain note", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), - ), - const SizedBox( - height: 8, + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), + const SizedBox(height: 8), SelectableText( _transaction.onChainNote ?? "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1132,13 +1166,12 @@ class _TransactionV2DetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1150,94 +1183,96 @@ class _TransactionV2DetailsViewState (coin is Epiccash) ? "Local Note" : "Note ", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), isDesktop ? IconPencilButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxWidth: 580, - maxHeight: 360, - child: EditNoteView( - txid: _transaction.txid, - walletId: walletId, - ), - ); - }, - ); - }, - ) - : GestureDetector( - onTap: () { - Navigator.of(context).pushNamed( - EditNoteView.routeName, - arguments: Tuple2( - _transaction.txid, - walletId, - ), - ); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.pencil, - width: 10, - height: 10, - color: Theme.of(context) - .extension< - StackColors>()! - .infoItemIcons, - ), - const SizedBox( - width: 4, - ), - Text( - "Edit", - style: STextStyles.link2( - context, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 360, + child: EditNoteView( + txid: _transaction.txid, + walletId: walletId, ), + ); + }, + ); + }, + ) + : GestureDetector( + onTap: () { + Navigator.of(context).pushNamed( + EditNoteView.routeName, + arguments: Tuple2( + _transaction.txid, + walletId, + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: + Theme.of(context) + .extension< + StackColors + >()! + .infoItemIcons, + ), + const SizedBox(width: 4), + Text( + "Edit", + style: STextStyles.link2( + context, ), - ], - ), + ), + ], ), + ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), SelectableText( ref .watch( - pTransactionNote( - ( - txid: (coin is Epiccash) - ? _transaction.slateId - .toString() - : _transaction.txid, - walletId: walletId - ), - ), + pTransactionNote(( + txid: + (coin is Epiccash) + ? _transaction.slateId + .toString() + : _transaction.txid, + walletId: walletId, + )), ) ?.value ?? "", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1245,14 +1280,13 @@ class _TransactionV2DetailsViewState if (_sparkMemo != null) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (_sparkMemo != null) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -1260,45 +1294,47 @@ class _TransactionV2DetailsViewState children: [ Text( "Memo", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ], ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), SelectableText( _sparkMemo!, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1310,34 +1346,36 @@ class _TransactionV2DetailsViewState children: [ Text( "Date", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), - if (isDesktop) - const SizedBox( - height: 2, - ), + if (isDesktop) const SizedBox(height: 2), if (isDesktop) SelectableText( Format.extractDateFrom( _transaction.timestamp, ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1346,16 +1384,21 @@ class _TransactionV2DetailsViewState Format.extractDateFrom( _transaction.timestamp, ), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton( @@ -1367,39 +1410,39 @@ class _TransactionV2DetailsViewState ), ), if (coin is! NanoCurrency && - !(coin is Xelis && _transaction.type == TransactionType.incoming) - ) + !(coin is Xelis && + _transaction.type == + TransactionType.incoming)) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is! NanoCurrency && - !(coin is Xelis && _transaction.type == TransactionType.incoming) - ) + !(coin is Xelis && + _transaction.type == + TransactionType.incoming)) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Builder( builder: (context) { - final String feeString = showFeePending - ? _transaction.isConfirmed( - currentHeight, - minConfirms, - coin.minCoinbaseConfirms, - ) - ? ref - .watch(pAmountFormatter(coin)) - .format( - fee, + final String feeString = + showFeePending + ? _transaction.isConfirmed( + currentHeight, + minConfirms, + coin.minCoinbaseConfirms, ) - : "Pending" - : ref - .watch(pAmountFormatter(coin)) - .format( - fee, - ); + ? ref + .watch( + pAmountFormatter(coin), + ) + .format(fee) + : "Pending" + : ref + .watch(pAmountFormatter(coin)) + .format(fee); return Row( mainAxisAlignment: @@ -1413,41 +1456,38 @@ class _TransactionV2DetailsViewState children: [ Text( "Transaction fee", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (supportsRbf && !confirmedTxn) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (supportsRbf && !confirmedTxn) CustomTextButton( text: "Boost transaction", @@ -1458,19 +1498,21 @@ class _TransactionV2DetailsViewState if (!isDesktop) SelectableText( feeString, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: feeString), @@ -1481,9 +1523,7 @@ class _TransactionV2DetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), Builder( builder: (context) { final String height; @@ -1494,10 +1534,11 @@ class _TransactionV2DetailsViewState if (widget.coin is Bitcoincash || widget.coin is Ecash) { - height = _transaction.height != null && - _transaction.height! > 0 - ? "${_transaction.height!}" - : "Pending"; + height = + _transaction.height != null && + _transaction.height! > 0 + ? "${_transaction.height!}" + : "Pending"; confirmations = confirms.toString(); } else if (widget.coin is Epiccash && _transaction.slateId == null) { @@ -1505,16 +1546,18 @@ class _TransactionV2DetailsViewState height = "Unknown"; } else { final confirmed = _transaction.isConfirmed( - currentHeight, - minConfirms, - coin.minCoinbaseConfirms); + currentHeight, + minConfirms, + coin.minCoinbaseConfirms, + ); if (widget.coin is! Epiccash && confirmed) { height = "${_transaction.height == 0 ? "Unknown" : _transaction.height}"; } else { - height = confirms > 0 - ? "${_transaction.height}" - : "Pending"; + height = + confirms > 0 + ? "${_transaction.height}" + : "Pending"; } confirmations = confirms.toString(); @@ -1523,9 +1566,10 @@ class _TransactionV2DetailsViewState return Column( children: [ RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1538,56 +1582,58 @@ class _TransactionV2DetailsViewState children: [ Text( "Block height", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), if (!isDesktop) SelectableText( height, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1596,13 +1642,12 @@ class _TransactionV2DetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1615,56 +1660,58 @@ class _TransactionV2DetailsViewState children: [ Text( "Confirmations", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), if (isDesktop) - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), if (isDesktop) SelectableText( confirmations, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles - .itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), if (!isDesktop) SelectableText( confirmations, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (isDesktop) IconCopyButton(data: height), @@ -1675,18 +1722,65 @@ class _TransactionV2DetailsViewState ); }, ), - - if (kDebugMode) + if (coin is Ethereum && + _transaction.type != TransactionType.incoming) isDesktop ? const _Divider() - : const SizedBox( - height: 12, + : const SizedBox(height: 12), + if (coin is Ethereum && + _transaction.type != TransactionType.incoming) + RoundedWhiteContainer( + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Nonce", + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), + SelectableText( + _transaction.nonce.toString(), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), + ), + ], + ), + ), + if (kDebugMode) + isDesktop + ? const _Divider() + : const SizedBox(height: 12), if (kDebugMode) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1694,25 +1788,32 @@ class _TransactionV2DetailsViewState children: [ Text( "Tx sub type", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), SelectableText( _transaction.subType.toString(), - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), ], ), @@ -1720,9 +1821,7 @@ class _TransactionV2DetailsViewState if (hasTxKeyProbably) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (hasTxKeyProbably) TxKeyWidget( walletId: walletId, @@ -1730,13 +1829,12 @@ class _TransactionV2DetailsViewState ), isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1749,61 +1847,61 @@ class _TransactionV2DetailsViewState children: [ ConditionalParent( condition: !isDesktop, - builder: (child) => Row( - children: [ - Expanded(child: child), - SimpleCopyButton( - data: _transaction.txid, + builder: + (child) => Row( + children: [ + Expanded(child: child), + SimpleCopyButton( + data: _transaction.txid, + ), + ], ), - ], - ), child: Text( "Transaction ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), ), - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), // Flexible( // child: FittedBox( // fit: BoxFit.scaleDown, // child: SelectableText( _transaction.txid, - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), if (coin is! Epiccash) - const SizedBox( - height: 8, - ), + const SizedBox(height: 8), if (coin is! Epiccash) CustomTextButton( text: "Open in block explorer", onTap: () async { final uri = getBlockExplorerTransactionUrlFor( - coin: coin, - txid: _transaction.txid, - ); + coin: coin, + txid: _transaction.txid, + ); if (ref .read( @@ -1813,8 +1911,8 @@ class _TransactionV2DetailsViewState false) { final shouldContinue = await showExplorerWarning( - "${uri.scheme}://${uri.host}", - ); + "${uri.scheme}://${uri.host}", + ); if (!shouldContinue) { return; @@ -1829,21 +1927,22 @@ class _TransactionV2DetailsViewState try { await launchUrl( uri, - mode: LaunchMode - .externalApplication, + mode: + LaunchMode + .externalApplication, ); } catch (_) { - if (mounted) { + if (context.mounted) { unawaited( showDialog( context: context, - builder: (_) => - StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", - ), + builder: + (_) => StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), ), ); } @@ -1864,14 +1963,9 @@ class _TransactionV2DetailsViewState ], ), ), + if (isDesktop) const SizedBox(width: 12), if (isDesktop) - const SizedBox( - width: 12, - ), - if (isDesktop) - IconCopyButton( - data: _transaction.txid, - ), + IconCopyButton(data: _transaction.txid), ], ), ), @@ -1955,14 +2049,13 @@ class _TransactionV2DetailsViewState if (coin is Epiccash) isDesktop ? const _Divider() - : const SizedBox( - height: 12, - ), + : const SizedBox(height: 12), if (coin is Epiccash) RoundedWhiteContainer( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(12), + padding: + isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: @@ -1974,14 +2067,14 @@ class _TransactionV2DetailsViewState children: [ Text( "Slate ID", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ) - : STextStyles.itemSubtitle( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle( + context, + ), ), // Flexible( // child: FittedBox( @@ -1989,27 +2082,27 @@ class _TransactionV2DetailsViewState // child: SelectableText( _transaction.slateId ?? "Unknown", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context, - ).copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ) - : STextStyles.itemSubtitle12( - context, - ), + style: + isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension< + StackColors + >()! + .textDark, + ) + : STextStyles.itemSubtitle12( + context, + ), ), // ), // ), ], ), - if (isDesktop) - const SizedBox( - width: 12, - ), + if (isDesktop) const SizedBox(width: 12), if (isDesktop) IconCopyButton( data: _transaction.slateId ?? "Unknown", @@ -2017,10 +2110,7 @@ class _TransactionV2DetailsViewState ], ), ), - if (!isDesktop) - const SizedBox( - height: 12, - ), + if (!isDesktop) const SizedBox(height: 12), // if (whatIsIt( // _transaction, // currentHeight, @@ -2042,101 +2132,106 @@ class _TransactionV2DetailsViewState ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: (coin is Epiccash && - _transaction.getConfirmations(currentHeight) < 1 && - _transaction.isCancelled == false) - ? ConditionalParent( - condition: isDesktop, - builder: (child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - child: child, - ), - child: SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, + floatingActionButton: + (coin is Epiccash && + _transaction.getConfirmations(currentHeight) < 1 && + _transaction.isCancelled == false) + ? ConditionalParent( + condition: isDesktop, + builder: + (child) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, ), - ), - onPressed: () async { - final wallet = ref.read(pWallets).getWallet(walletId); + child: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, + ), + ), + onPressed: () async { + final wallet = ref.read(pWallets).getWallet(walletId); + + if (wallet is EpiccashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, + ), + ); + return; + } - if (wallet is EpiccashWallet) { - final String? id = _transaction.slateId; - if (id == null) { unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "Could not find Epic transaction ID", + showDialog( + barrierDismissible: false, context: context, + builder: + (_) => + const CancellingTransactionProgressDialog(), ), ); - return; - } - - unawaited( - showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - ), - ); - final result = - await wallet.cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - wallet.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName( - WalletView.routeName, + final result = await wallet + .cancelPendingTransactionAndPost(id); + if (context.mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + wallet.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + }, ), - ); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); + ); + } else { + await showDialog( + context: context, + builder: + (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } } + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "ERROR: Wallet type is not Epic Cash", + context: context, + ), + ); + return; } - } else { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - ), - ); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), ), ), - ), - ) - : null, + ) + : null, ), ); } @@ -2161,38 +2256,44 @@ class OutputCard extends ConsumerWidget { children: [ Text( "Address", - style: Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + style: + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( address, - style: Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), - ), - const SizedBox( - height: 10, + style: + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), + const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Amount", - style: Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - : STextStyles.itemSubtitle(context), + style: + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), ), SelectableText( ref.watch(pAmountFormatter(coin)).format(amount), - style: Util.isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textDark, - ) - : STextStyles.itemSubtitle12(context), + style: + Util.isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), ), ], ), @@ -2214,10 +2315,7 @@ class _Divider extends StatelessWidget { } class IconCopyButton extends StatelessWidget { - const IconCopyButton({ - super.key, - required this.data, - }); + const IconCopyButton({super.key, required this.data}); final String data; @@ -2231,9 +2329,7 @@ class IconCopyButton extends StatelessWidget { Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), onPressed: () async { await Clipboard.setData(ClipboardData(text: data)); if (context.mounted) { @@ -2260,10 +2356,7 @@ class IconCopyButton extends StatelessWidget { } class IconPencilButton extends StatelessWidget { - const IconPencilButton({ - super.key, - this.onPressed, - }); + const IconPencilButton({super.key, this.onPressed}); final VoidCallback? onPressed; @@ -2277,9 +2370,7 @@ class IconPencilButton extends StatelessWidget { Theme.of(context).extension()!.buttonBackSecondary, elevation: 0, hoverElevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), onPressed: () => onPressed?.call(), child: Padding( padding: const EdgeInsets.all(5), diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index d9f730804..1133731dc 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -10,6 +10,7 @@ import 'dart:io'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -67,53 +68,63 @@ class _FavoriteCardState extends ConsumerState { prefsChangeNotifierProvider.select((value) => value.externalCalls), ); + Decimal? price; + if (externalCalls) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin)?.value, + ), + ); + } return ConditionalParent( condition: Util.isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _hovering = true; - }); - }, - onExit: (_) { - setState(() { - _hovering = false; - }); - }, - child: AnimatedScale( - duration: const Duration(milliseconds: 200), - scale: _hovering ? 1.05 : 1, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: _hovering - ? BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - boxShadow: [ - Theme.of(context) - .extension()! - .standardBoxShadow, - Theme.of(context) - .extension()! - .standardBoxShadow, - Theme.of(context) - .extension()! - .standardBoxShadow, - ], - ) - : BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: child, + builder: + (child) => MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _hovering = true; + }); + }, + onExit: (_) { + setState(() { + _hovering = false; + }); + }, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + scale: _hovering ? 1.05 : 1, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: + _hovering + ? BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + boxShadow: [ + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + Theme.of( + context, + ).extension()!.standardBoxShadow, + ], + ) + : BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: child, + ), + ), ), - ), - ), child: GestureDetector( onTap: () async { final wallet = ref.read(pWallets).getWallet(walletId); @@ -133,8 +144,9 @@ class _FavoriteCardState extends ConsumerState { final Future loadFuture; if (wallet is ExternalWallet) { - loadFuture = - wallet.init().then((value) async => await (wallet).open()); + loadFuture = wallet.init().then( + (value) async => await (wallet).open(), + ); } else { loadFuture = wallet.init(); } @@ -147,15 +159,13 @@ class _FavoriteCardState extends ConsumerState { if (mounted) { if (Util.isDesktop) { - await Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: walletId, - ); + await Navigator.of( + context, + ).pushNamed(DesktopWalletView.routeName, arguments: walletId); } else { - await Navigator.of(context).pushNamed( - WalletView.routeName, - arguments: walletId, - ); + await Navigator.of( + context, + ).pushNamed(WalletView.routeName, arguments: walletId); } } }, @@ -183,17 +193,16 @@ class _FavoriteCardState extends ConsumerState { child: Text( ref.watch(pWalletName(walletId)), style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of( + context, + ).extension()!.textFavoriteCard, ), overflow: TextOverflow.fade, ), ), SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), - ), + File(ref.watch(coinIconProvider(coin))), width: 24, height: 24, ), @@ -202,36 +211,24 @@ class _FavoriteCardState extends ConsumerState { ), Builder( builder: (context) { - final balance = ref.watch( - pWalletBalance(walletId), - ); + final balance = ref.watch(pWalletBalance(walletId)); Amount total = balance.total; if (coin is Firo) { - total += ref - .watch( - pWalletBalanceSecondary(walletId), - ) - .total; - total += ref - .watch( - pWalletBalanceTertiary(walletId), - ) - .total; + total += + ref.watch(pWalletBalanceSecondary(walletId)).total; + total += + ref.watch(pWalletBalanceTertiary(walletId)).total; } Amount fiatTotal = Amount.zero; - if (externalCalls && total > Amount.zero) { - fiatTotal = (total.decimal * - ref - .watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin), - ), - ) - .item1) - .toAmount(fractionDigits: 2); + if (externalCalls && + total > Amount.zero && + price != null) { + fiatTotal = (total.decimal * price).toAmount( + fractionDigits: 2, + ); } return Column( @@ -243,35 +240,26 @@ class _FavoriteCardState extends ConsumerState { ref.watch(pAmountFormatter(coin)).format(total), style: STextStyles.titleBold12(context).copyWith( fontSize: 16, - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of(context) + .extension()! + .textFavoriteCard, ), ), ), - if (externalCalls) - const SizedBox( - height: 4, - ), - if (externalCalls) + if (externalCalls && price != null) + const SizedBox(height: 4), + if (externalCalls && price != null) Text( - "${fiatTotal.fiatString( - locale: ref.watch( - localeServiceChangeNotifierProvider.select( - (value) => value.locale, - ), - ), - )} ${ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, - ), - )}", - style: - STextStyles.itemSubtitle12(context).copyWith( + "${fiatTotal.fiatString(locale: ref.watch(localeServiceChangeNotifierProvider.select((value) => value.locale)))} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.itemSubtitle12( + context, + ).copyWith( fontSize: 10, - color: Theme.of(context) - .extension()! - .textFavoriteCard, + color: + Theme.of(context) + .extension()! + .textFavoriteCard, ), ), ], @@ -300,11 +288,6 @@ class CardOverlayStack extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - children: [ - background, - child, - ], - ); + return Stack(children: [background, child]); } } diff --git a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart index 938baac71..64d9ecbc9 100644 --- a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart +++ b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart @@ -46,8 +46,9 @@ class WalletListItem extends ConsumerWidget { // debugPrint("BUILD: $runtimeType"); final walletCountString = walletCount == 1 ? "$walletCount wallet" : "$walletCount wallets"; - final currency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final currency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); return RoundedWhiteContainer( padding: const EdgeInsets.all(0), @@ -57,8 +58,9 @@ class WalletListItem extends ConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 13), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), onPressed: () async { // Check if Tor is enabled... @@ -66,11 +68,10 @@ class WalletListItem extends ConsumerWidget { // ... and if the coin supports Tor. if (!coin.torSupport) { // If not, show a Tor warning dialog. - final shouldContinue = await showDialog( + final shouldContinue = + await showDialog( context: context, - builder: (_) => TorWarningDialog( - coin: coin, - ), + builder: (_) => TorWarningDialog(coin: coin), ) ?? false; if (!shouldContinue) { @@ -100,8 +101,9 @@ class WalletListItem extends ConsumerWidget { final Future loadFuture; if (wallet is ExternalWallet) { - loadFuture = - wallet.init().then((value) async => await (wallet).open()); + loadFuture = wallet.init().then( + (value) async => await (wallet).open(), + ); } else { loadFuture = wallet.init(); } @@ -113,63 +115,67 @@ class WalletListItem extends ConsumerWidget { ); if (context.mounted) { unawaited( - Navigator.of(context).pushNamed( - WalletView.routeName, - arguments: wallet.walletId, - ), + Navigator.of( + context, + ).pushNamed(WalletView.routeName, arguments: wallet.walletId), ); } } else { unawaited( - Navigator.of(context).pushNamed( - WalletsOverview.routeName, - arguments: coin, - ), + Navigator.of( + context, + ).pushNamed(WalletsOverview.routeName, arguments: coin), ); } }, child: Row( children: [ SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), - ), + File(ref.watch(coinIconProvider(coin))), width: 28, height: 28, ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), Expanded( child: Consumer( builder: (_, ref, __) { - final tuple = ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin)), - ); - final calls = - ref.watch(prefsChangeNotifierProvider).externalCalls; + Color percentChangedColor = + Theme.of(context).extension()!.textDark; + String? priceString; + double? percentChange; + if (ref.watch( + prefsChangeNotifierProvider.select((s) => s.externalCalls), + )) { + final price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ); - final priceString = - tuple.item1.toAmount(fractionDigits: 2).fiatString( + if (price != null) { + priceString = price.value + .toAmount(fractionDigits: 2) + .fiatString( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), ); + percentChange = price.change24h; - final double percentChange = tuple.item2; - - var percentChangedColor = - Theme.of(context).extension()!.textDark; - if (percentChange > 0) { - percentChangedColor = Theme.of(context) - .extension()! - .accentColorGreen; - } else if (percentChange < 0) { - percentChangedColor = Theme.of(context) - .extension()! - .accentColorRed; + if (percentChange > 0) { + percentChangedColor = + Theme.of( + context, + ).extension()!.accentColorGreen; + } else if (percentChange < 0) { + percentChangedColor = + Theme.of( + context, + ).extension()!.accentColorRed; + } + } } return Column( @@ -182,16 +188,14 @@ class WalletListItem extends ConsumerWidget { style: STextStyles.titleBold12(context), ), const Spacer(), - if (calls) + if (priceString != null) Text( "$priceString $currency/${coin.ticker}", style: STextStyles.itemSubtitle(context), ), ], ), - const SizedBox( - height: 1, - ), + const SizedBox(height: 1), Row( children: [ Text( @@ -199,12 +203,12 @@ class WalletListItem extends ConsumerWidget { style: STextStyles.itemSubtitle(context), ), const Spacer(), - if (calls) + if (percentChange != null) Text( "${percentChange.toStringAsFixed(2)}%", - style: STextStyles.itemSubtitle(context).copyWith( - color: percentChangedColor, - ), + style: STextStyles.itemSubtitle( + context, + ).copyWith(color: percentChangedColor), ), ], ), diff --git a/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart b/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart index 023b9fb79..476a3eb24 100644 --- a/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart @@ -10,6 +10,7 @@ import 'dart:io'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -63,6 +64,15 @@ class _DesktopPaynymSendDialogState final coin = ref.watch(pWalletCoin(widget.walletId)); + Decimal? price; + if (ref.watch(prefsChangeNotifierProvider.select((s) => s.externalCalls))) { + price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin)?.value, + ), + ); + } + return DesktopDialog( maxHeight: double.infinity, maxWidth: 580, @@ -90,15 +100,11 @@ class _DesktopPaynymSendDialogState child: Row( children: [ SvgPicture.file( - File( - ref.watch(coinIconProvider(coin)), - ), + File(ref.watch(coinIconProvider(coin))), width: 36, height: 36, ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -108,15 +114,14 @@ class _DesktopPaynymSendDialogState overflow: TextOverflow.ellipsis, maxLines: 1, ), - const SizedBox( - height: 2, - ), + const SizedBox(height: 2), Text( "Available balance", style: STextStyles.baseXS(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), ), ], @@ -128,7 +133,9 @@ class _DesktopPaynymSendDialogState crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - ref.watch(pAmountFormatter(coin)).format( + ref + .watch(pAmountFormatter(coin)) + .format( ref .watch(pWalletBalance(widget.walletId)) .spendable, @@ -136,28 +143,18 @@ class _DesktopPaynymSendDialogState style: STextStyles.titleBold12(context), textAlign: TextAlign.right, ), - const SizedBox( - height: 2, - ), - Text( - "${(ref.watch(pWalletBalance(widget.walletId)).spendable.decimal * ref.watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin).item1, - ), - )).toAmount(fractionDigits: 2).fiatString( - locale: locale, - )} ${ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, + if (price != null) const SizedBox(height: 2), + if (price != null) + Text( + "${(ref.watch(pWalletBalance(widget.walletId)).spendable.decimal * price).toAmount(fractionDigits: 2).fiatString(locale: locale)} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.baseXS(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textSubtitle1, ), - )}", - style: STextStyles.baseXS(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, + textAlign: TextAlign.right, ), - textAlign: TextAlign.right, - ), ], ), ), @@ -165,15 +162,9 @@ class _DesktopPaynymSendDialogState ), ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), child: DesktopSend( walletId: widget.walletId, accountLite: widget.accountLite, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart index 066ae9073..45b5d710e 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart @@ -10,6 +10,7 @@ import 'dart:io'; +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -53,12 +54,11 @@ class _WalletTableState extends ConsumerState { return ConditionalParent( condition: index + 1 == walletsByCoin.length, - builder: (child) => Padding( - padding: const EdgeInsets.only( - bottom: 16, - ), - child: child, - ), + builder: + (child) => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: child, + ), child: DesktopWalletSummaryRow( key: Key("DesktopWalletSummaryRow_key_${coin.identifier}"), coin: coin, @@ -66,9 +66,7 @@ class _WalletTableState extends ConsumerState { ), ); }, - separatorBuilder: (_, __) => const SizedBox( - height: 10, - ), + separatorBuilder: (_, __) => const SizedBox(height: 10), itemCount: walletsByCoin.length, ); } @@ -96,11 +94,10 @@ class _DesktopWalletSummaryRowState // ... and if the coin supports Tor. if (!widget.coin.torSupport) { // If not, show a Tor warning dialog. - final shouldContinue = await showDialog( + final shouldContinue = + await showDialog( context: context, - builder: (_) => TorWarningDialog( - coin: widget.coin, - ), + builder: (_) => TorWarningDialog(coin: widget.coin), ) ?? false; if (!shouldContinue) { @@ -121,8 +118,12 @@ class _DesktopWalletSummaryRowState await _checkTor(); if (mounted) { - final wallet = ref.read(pWallets).wallets.firstWhere( - (e) => e.cryptoCurrency.identifier == widget.coin.identifier); + final wallet = ref + .read(pWallets) + .wallets + .firstWhere( + (e) => e.cryptoCurrency.identifier == widget.coin.identifier, + ); final canContinue = await checkShowNodeTorSettingsMismatch( context: context, @@ -139,8 +140,9 @@ class _DesktopWalletSummaryRowState final Future loadFuture; if (wallet is ExternalWallet) { - loadFuture = - wallet.init().then((value) async => await (wallet).open()); + loadFuture = wallet.init().then( + (value) async => await (wallet).open(), + ); } else { loadFuture = wallet.init(); } @@ -152,10 +154,9 @@ class _DesktopWalletSummaryRowState ); if (mounted) { - await Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: wallet.walletId, - ); + await Navigator.of( + context, + ).pushNamed(DesktopWalletView.routeName, arguments: wallet.walletId); } } } finally { @@ -173,40 +174,41 @@ class _DesktopWalletSummaryRowState if (mounted) { await showDialog( context: context, - builder: (_) => DesktopDialog( - maxHeight: 600, - maxWidth: 700, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + builder: + (_) => DesktopDialog( + maxHeight: 600, + maxWidth: 700, + child: Column( children: [ - Padding( - padding: const EdgeInsets.only(left: 32), - child: Text( - "${widget.coin.prettyName} (${widget.coin.ticker}) wallets", - style: STextStyles.desktopH3(context), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "${widget.coin.prettyName} (${widget.coin.ticker}) wallets", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: WalletsOverview( + coin: widget.coin, + navigatorState: Navigator.of(context), + ), ), ), - const DesktopDialogCloseButton(), ], ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: WalletsOverview( - coin: widget.coin, - navigatorState: Navigator.of(context), - ), - ), - ), - ], - ), - ), + ), ); } } finally { @@ -216,6 +218,12 @@ class _DesktopWalletSummaryRowState @override Widget build(BuildContext context) { + final price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(widget.coin), + ), + ); + return Breathing( child: RoundedWhiteContainer( padding: const EdgeInsets.all(20), @@ -229,15 +237,11 @@ class _DesktopWalletSummaryRowState child: Row( children: [ SvgPicture.file( - File( - ref.watch(coinIconProvider(widget.coin)), - ), + File(ref.watch(coinIconProvider(widget.coin))), width: 28, height: 28, ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), Text( widget.coin.prettyName, style: STextStyles.desktopTextExtraSmall(context).copyWith( @@ -260,12 +264,11 @@ class _DesktopWalletSummaryRowState ), ), ), - Expanded( - flex: 6, - child: TablePriceInfo( - coin: widget.coin, + if (price != null) + Expanded( + flex: 6, + child: TablePriceInfo(coin: widget.coin, price: price), ), - ), ], ), ), @@ -274,43 +277,30 @@ class _DesktopWalletSummaryRowState } class TablePriceInfo extends ConsumerWidget { - const TablePriceInfo({super.key, required this.coin}); + const TablePriceInfo({super.key, required this.coin, required this.price}); final CryptoCurrency coin; + final ({Decimal value, double change24h}) price; @override Widget build(BuildContext context, WidgetRef ref) { - final tuple = ref.watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin), - ), - ); - final currency = ref.watch( - prefsChangeNotifierProvider.select( - (value) => value.currency, - ), + prefsChangeNotifierProvider.select((value) => value.currency), ); final priceString = Amount.fromDecimal( - tuple.item1, + price.value, fractionDigits: 2, ).fiatString( - locale: ref - .watch( - localeServiceChangeNotifierProvider.notifier, - ) - .locale, + locale: ref.watch(localeServiceChangeNotifierProvider.notifier).locale, ); - final double percentChange = tuple.item2; - var percentChangedColor = Theme.of(context).extension()!.textDark; - if (percentChange > 0) { + if (price.change24h > 0) { percentChangedColor = Theme.of(context).extension()!.accentColorGreen; - } else if (percentChange < 0) { + } else if (price.change24h < 0) { percentChangedColor = Theme.of(context).extension()!.accentColorRed; } @@ -325,10 +315,10 @@ class TablePriceInfo extends ConsumerWidget { ), ), Text( - "${percentChange.toStringAsFixed(2)}%", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: percentChangedColor, - ), + "${price.change24h.toStringAsFixed(2)}%", + style: STextStyles.desktopTextExtraSmall( + context, + ).copyWith(color: percentChangedColor), ), ], ); 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 deleted file mode 100644 index 6326a6474..000000000 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart +++ /dev/null @@ -1,447 +0,0 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'package:dropdown_button2/dropdown_button2.dart'; -import 'package:flutter/material.dart'; -import 'package:cs_monero/cs_monero.dart' as lib_monero; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; - -import '../../../../models/models.dart'; -import '../../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; -import '../../../../providers/global/wallets_provider.dart'; -import '../../../../providers/ui/fee_rate_type_state_provider.dart'; -import '../../../../providers/wallet/public_private_balance_state_provider.dart'; -import '../../../../themes/stack_colors.dart'; -import '../../../../utilities/amount/amount.dart'; -import '../../../../utilities/amount/amount_formatter.dart'; -import '../../../../utilities/assets.dart'; -import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/fee_rate_type_enum.dart'; -import '../../../../utilities/text_styles.dart'; -import '../../../../wallets/crypto_currency/crypto_currency.dart'; -import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; -import '../../../../wallets/isar/providers/wallet_info_provider.dart'; -import '../../../../wallets/wallet/impl/firo_wallet.dart'; -import '../../../../widgets/animated_text.dart'; - -final tokenFeeSessionCacheProvider = - ChangeNotifierProvider((ref) { - return FeeSheetSessionCache(); -}); - -class DesktopFeeDropDown extends ConsumerStatefulWidget { - const DesktopFeeDropDown({ - super.key, - required this.walletId, - this.isToken = false, - }); - - final String walletId; - final bool isToken; - - @override - ConsumerState createState() => _DesktopFeeDropDownState(); -} - -class _DesktopFeeDropDownState extends ConsumerState { - late final String walletId; - - FeeObject? feeObject; - FeeRateType feeRateType = FeeRateType.average; - - final stringsToLoopThrough = [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ]; - - Future feeFor({ - required Amount amount, - required FeeRateType feeRateType, - required int feeRate, - required CryptoCurrency coin, - }) async { - switch (feeRateType) { - case FeeRateType.fast: - if (ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .fast[amount] == - null) { - if (widget.isToken == false) { - final wallet = ref.read(pWallets).getWallet(walletId); - - if (coin is Monero || coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - lib_monero.TransactionPriority.high.value, - ); - ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; - } else if (coin is Firo) { - final Amount fee; - switch (ref.read(publicPrivateBalanceStateProvider.state).state) { - case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); - case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); - case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); - } - ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; - } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - await wallet.estimateFeeFor(amount, feeRate); - } - } else { - final tokenWallet = ref.read(pCurrentTokenWallet)!; - final fee = await tokenWallet.estimateFeeFor(amount, feeRate); - ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee; - } - } - return ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .fast[amount]!; - - case FeeRateType.average: - if (ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .average[amount] == - null) { - if (widget.isToken == false) { - final wallet = ref.read(pWallets).getWallet(walletId); - - if (coin is Monero || coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - lib_monero.TransactionPriority.medium.value, - ); - ref.read(feeSheetSessionCacheProvider).average[amount] = fee; - } else if (coin is Firo) { - final Amount fee; - switch (ref.read(publicPrivateBalanceStateProvider.state).state) { - case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); - case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); - case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); - } - ref.read(feeSheetSessionCacheProvider).average[amount] = fee; - } else { - ref.read(feeSheetSessionCacheProvider).average[amount] = - await wallet.estimateFeeFor(amount, feeRate); - } - } else { - final tokenWallet = ref.read(pCurrentTokenWallet)!; - final fee = await tokenWallet.estimateFeeFor(amount, feeRate); - ref.read(tokenFeeSessionCacheProvider).average[amount] = fee; - } - } - return ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .average[amount]!; - - case FeeRateType.slow: - if (ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .slow[amount] == - null) { - if (widget.isToken == false) { - final wallet = ref.read(pWallets).getWallet(walletId); - - if (coin is Monero || coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - lib_monero.TransactionPriority.normal.value, - ); - ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; - } else if (coin is Firo) { - final Amount fee; - switch (ref.read(publicPrivateBalanceStateProvider.state).state) { - case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); - case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); - case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); - } - ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; - } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - await wallet.estimateFeeFor(amount, feeRate); - } - } else { - final tokenWallet = ref.read(pCurrentTokenWallet)!; - final fee = await tokenWallet.estimateFeeFor(amount, feeRate); - ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee; - } - } - return ref - .read( - widget.isToken - ? tokenFeeSessionCacheProvider - : feeSheetSessionCacheProvider, - ) - .slow[amount]!; - default: - return Amount.zero; - } - } - - @override - void initState() { - walletId = widget.walletId; - super.initState(); - } - - String? labelSlow; - String? labelAverage; - String? labelFast; - - @override - Widget build(BuildContext context) { - debugPrint("BUILD: $runtimeType"); - - final wallet = - ref.watch(pWallets.select((value) => value.getWallet(walletId))); - - return FutureBuilder( - future: wallet.fees, - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - feeObject = snapshot.data!; - } - return DropdownButtonHideUnderline( - child: DropdownButton2( - isExpanded: true, - value: ref.watch(feeRateTypeStateProvider.state).state, - items: [ - ...FeeRateType.values.map( - (e) => DropdownMenuItem( - value: e, - child: FeeDropDownChild( - feeObject: feeObject, - feeRateType: e, - walletId: walletId, - feeFor: feeFor, - isSelected: false, - ), - ), - ), - ], - onChanged: (newRateType) { - if (newRateType is FeeRateType) { - ref.read(feeRateTypeStateProvider.state).state = newRateType; - } - }, - iconStyleData: IconStyleData( - icon: SvgPicture.asset( - Assets.svg.chevronDown, - width: 12, - height: 6, - color: Theme.of(context).extension()!.textDark3, - ), - ), - dropdownStyleData: DropdownStyleData( - offset: const Offset(0, -10), - elevation: 0, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - ), - menuItemStyleData: const MenuItemStyleData( - padding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - ), - ), - ); - }, - ); - } -} - -final sendAmountProvider = - StateProvider.autoDispose((_) => Amount.zero); - -class FeeDropDownChild extends ConsumerWidget { - const FeeDropDownChild({ - super.key, - required this.feeObject, - required this.feeRateType, - required this.walletId, - required this.feeFor, - required this.isSelected, - }); - - final FeeObject? feeObject; - final FeeRateType feeRateType; - final String walletId; - final Future Function({ - required Amount amount, - required FeeRateType feeRateType, - required int feeRate, - required CryptoCurrency coin, - }) feeFor; - final bool isSelected; - - static const stringsToLoopThrough = [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ]; - - String estimatedTimeToBeIncludedInNextBlock( - int targetBlockTime, - int estimatedNumberOfBlocks, - ) { - final int time = targetBlockTime * estimatedNumberOfBlocks; - - final int hours = (time / 3600).floor(); - if (hours > 1) { - return "~$hours hours"; - } else if (hours == 1) { - return "~$hours hour"; - } - - // less than an hour - - final string = (time / 60).toStringAsFixed(1); - - if (string == "1.0") { - return "~1 minute"; - } else { - if (string.endsWith(".0")) { - return "~${(time / 60).floor()} minutes"; - } - return "~$string minutes"; - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - debugPrint("BUILD: $runtimeType : $feeRateType"); - - final coin = ref.watch(pWalletCoin(walletId)); - - if (feeObject == null) { - return AnimatedText( - stringsToLoopThrough: stringsToLoopThrough, - style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: - Theme.of(context).extension()!.textFieldActiveText, - ), - ); - } else { - return FutureBuilder( - future: feeFor( - coin: coin, - feeRateType: feeRateType, - feeRate: feeRateType == FeeRateType.fast - ? feeObject!.fast - : feeRateType == FeeRateType.slow - ? feeObject!.slow - : feeObject!.medium, - amount: ref.watch(sendAmountProvider.state).state, - ), - builder: (_, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "${feeRateType.prettyName} " - "(~${ref.watch(pAmountFormatter(coin)).format( - snapshot.data!, - indicatePrecisionLoss: false, - )})", - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - ), - textAlign: TextAlign.left, - ), - if (feeObject != null) - Text( - coin is Ethereum - ? "" - : estimatedTimeToBeIncludedInNextBlock( - coin.targetBlockTimeSeconds, - feeRateType == FeeRateType.fast - ? feeObject!.numberOfBlocksFast - : feeRateType == FeeRateType.slow - ? feeObject!.numberOfBlocksSlow - : feeObject!.numberOfBlocksAverage, - ), - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - ), - ], - ); - } else { - return AnimatedText( - stringsToLoopThrough: stringsToLoopThrough, - style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, - ), - ); - } - }, - ); - } - } -} 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 fe65a90f0..300bc4d99 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 @@ -10,7 +10,6 @@ import 'dart:async'; -import 'package:cs_monero/cs_monero.dart' as lib_monero; import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; @@ -28,6 +27,7 @@ import '../../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet. import '../../../../providers/providers.dart'; import '../../../../providers/ui/fee_rate_type_state_provider.dart'; import '../../../../providers/ui/preview_tx_button_state_provider.dart'; +import '../../../../providers/wallet/desktop_fee_providers.dart'; import '../../../../providers/wallet/public_private_balance_state_provider.dart'; import '../../../../services/spark_names_service.dart'; import '../../../../themes/stack_colors.dart'; @@ -40,7 +40,6 @@ import '../../../../utilities/assets.dart'; import '../../../../utilities/barcode_scanner_interface.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; -import '../../../../utilities/enums/fee_rate_type_enum.dart'; import '../../../../utilities/logger.dart'; import '../../../../utilities/prefs.dart'; import '../../../../utilities/text_styles.dart'; @@ -51,20 +50,16 @@ import '../../../../wallets/isar/providers/wallet_info_provider.dart'; import '../../../../wallets/models/tx_data.dart'; import '../../../../wallets/wallet/impl/firo_wallet.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart'; -import '../../../../wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart'; import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; -import '../../../../widgets/animated_text.dart'; -import '../../../../widgets/conditional_parent.dart'; import '../../../../widgets/custom_buttons/blue_text_button.dart'; import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; -import '../../../../widgets/desktop/desktop_fee_dialog.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/qr_code_scanner_dialog.dart'; import '../../../../widgets/desktop/secondary_button.dart'; import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart'; -import '../../../../widgets/fee_slider.dart'; +import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/qrcode_icon.dart'; @@ -75,7 +70,7 @@ import '../../../../widgets/textfield_icon_button.dart'; import '../../../coin_control/desktop_coin_control_use_dialog.dart'; import '../../../desktop_home_view.dart'; import 'address_book_address_chooser/address_book_address_chooser.dart'; -import 'desktop_fee_dropdown.dart'; +import 'desktop_send_fee_form.dart'; class DesktopSend extends ConsumerStatefulWidget { const DesktopSend({ @@ -106,8 +101,8 @@ class _DesktopSendState extends ConsumerState { late TextEditingController sendToController; late TextEditingController cryptoAmountController; late TextEditingController baseAmountController; - // late TextEditingController feeController; late TextEditingController memoController; + late TextEditingController nonceController; late final SendViewAutoFillData? _data; @@ -115,6 +110,7 @@ class _DesktopSendState extends ConsumerState { final _cryptoFocus = FocusNode(); final _baseFocus = FocusNode(); final _memoFocus = FocusNode(); + final _nonceFocusNode = FocusNode(); late final bool isStellar; @@ -135,14 +131,7 @@ class _DesktopSendState extends ConsumerState { bool isCustomFee = false; int customFeeRate = 1; - (FeeRateType, String?, String?)? feeSelectionResult; - - final stringsToLoopThrough = [ - "Calculating", - "Calculating.", - "Calculating..", - "Calculating...", - ]; + EthEIP1559Fee? ethFee; Future scanWebcam() async { try { @@ -197,9 +186,7 @@ class _DesktopSendState extends ConsumerState { ref.read(prefsChangeNotifierProvider).enableCoinControl; if (!(wallet is CoinControlInterface && coinControlEnabled) || - (wallet is CoinControlInterface && - coinControlEnabled && - ref.read(desktopUseUTXOs).isEmpty)) { + (coinControlEnabled && ref.read(desktopUseUTXOs).isEmpty)) { // confirm send all if (amount == availableBalance) { final bool? shouldSendAll = await showDialog( @@ -321,7 +308,7 @@ class _DesktopSendState extends ConsumerState { if (isPaynymSend) { final paynymWallet = wallet as PaynymInterface; - final feeRate = ref.read(feeRateTypeStateProvider); + final feeRate = ref.read(feeRateTypeDesktopStateProvider); txDataFuture = paynymWallet.preparePaymentCodeSend( txData: TxData( paynymAccountLite: widget.accountLite!, @@ -356,11 +343,10 @@ class _DesktopSendState extends ConsumerState { isChange: false, ), ], - feeRateType: ref.read(feeRateTypeStateProvider), + feeRateType: ref.read(feeRateTypeDesktopStateProvider), satsPerVByte: isCustomFee ? customFeeRate : null, utxos: - (wallet is CoinControlInterface && - coinControlEnabled && + (coinControlEnabled && ref.read(desktopUseUTXOs).isNotEmpty) ? ref.read(desktopUseUTXOs) : null, @@ -372,11 +358,10 @@ class _DesktopSendState extends ConsumerState { recipients: [ (address: _address!, amount: amount, isChange: false), ], - feeRateType: ref.read(feeRateTypeStateProvider), + feeRateType: ref.read(feeRateTypeDesktopStateProvider), satsPerVByte: isCustomFee ? customFeeRate : null, utxos: - (wallet is CoinControlInterface && - coinControlEnabled && + (coinControlEnabled && ref.read(desktopUseUTXOs).isNotEmpty) ? ref.read(desktopUseUTXOs) : null, @@ -425,14 +410,19 @@ class _DesktopSendState extends ConsumerState { txData: TxData( recipients: [(address: _address!, amount: amount, isChange: false)], memo: memo, - feeRateType: ref.read(feeRateTypeStateProvider), + feeRateType: ref.read(feeRateTypeDesktopStateProvider), satsPerVByte: isCustomFee ? customFeeRate : null, + nonce: + wallet.cryptoCurrency is Ethereum + ? int.tryParse(nonceController.text) + : null, utxos: (wallet is CoinControlInterface && coinControlEnabled && ref.read(desktopUseUTXOs).isNotEmpty) ? ref.read(desktopUseUTXOs) : null, + ethEIP1559Fee: ethFee, ), ); } @@ -547,6 +537,7 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.text = ""; baseAmountController.text = ""; memoController.text = ""; + nonceController.text = ""; _address = ""; _addressToggleFlag = false; if (mounted) { @@ -569,9 +560,9 @@ class _DesktopSendState extends ConsumerState { _cachedAmountToSend = amount; final price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin)?.value; - if (price > Decimal.zero) { + if (price != null && price > Decimal.zero) { final String fiatAmountString = (amount.decimal * price) .toAmount(fractionDigits: 2) .fiatString( @@ -800,9 +791,9 @@ class _DesktopSendState extends ConsumerState { final Amount? amount; if (baseAmount != null) { final _price = - ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; + ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin)?.value; - if (_price == Decimal.zero) { + if (_price == null || _price == Decimal.zero) { amount = Decimal.zero.toAmount(fractionDigits: coin.fractionDigits); } else { amount = @@ -910,7 +901,7 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController = TextEditingController(); baseAmountController = TextEditingController(); memoController = TextEditingController(); - // feeController = TextEditingController(); + nonceController = TextEditingController(); onCryptoAmountChanged = _cryptoAmountChanged; cryptoAmountController.addListener(onCryptoAmountChanged); @@ -964,12 +955,13 @@ class _DesktopSendState extends ConsumerState { cryptoAmountController.dispose(); baseAmountController.dispose(); memoController.dispose(); - // feeController.dispose(); + nonceController.dispose(); _addressFocusNode.dispose(); _cryptoFocus.dispose(); _baseFocus.dispose(); _memoFocus.dispose(); + _nonceFocusNode.dispose(); super.dispose(); } @@ -1671,228 +1663,64 @@ class _DesktopSendState extends ConsumerState { ), if (!isPaynymSend) const SizedBox(height: 20), if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) - ConditionalParent( - condition: - ref.watch(pWallets).getWallet(walletId) is ElectrumXInterface && - !(((coin is Firo) && - (ref.watch(publicPrivateBalanceStateProvider.state).state == - FiroType.lelantus || - ref - .watch(publicPrivateBalanceStateProvider.state) - .state == - FiroType.spark))), - builder: - (child) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - child, - CustomTextButton( - text: "Edit", - onTap: () async { - feeSelectionResult = - await showDialog<(FeeRateType, String?, String?)?>( - context: context, - builder: - (_) => DesktopFeeDialog(walletId: walletId), - ); - - if (feeSelectionResult != null) { - if (isCustomFee && - feeSelectionResult!.$1 != FeeRateType.custom) { - isCustomFee = false; - } else if (!isCustomFee && - feeSelectionResult!.$1 == FeeRateType.custom) { - isCustomFee = true; - } - } - - setState(() {}); - }, - ), - ], - ), - child: Text( - "Transaction fee" - "${isCustomFee ? "" : " (${coin is Ethereum ? "max" : "estimated"})"}", + DesktopSendFeeForm( + walletId: walletId, + isToken: false, + onCustomFeeSliderChanged: (value) => customFeeRate = value, + onCustomFeeOptionChanged: (value) { + isCustomFee = value; + ethFee = null; + }, + onCustomEip1559FeeOptionChanged: (value) => ethFee = value, + ), + if (coin is Ethereum) const SizedBox(height: 20), + if (coin is Ethereum) + Text( + "Nonce", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + if (coin is Ethereum) const SizedBox(height: 10), + if (coin is Ethereum) + 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()!.textFieldActiveSearchIconRight, + ).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, + ), ), - textAlign: TextAlign.left, - ), - ), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) - const SizedBox(height: 10), - if (coin is! NanoCurrency && coin is! Epiccash && coin is! Tezos) - if (!isCustomFee) - Padding( - padding: const EdgeInsets.all(10), - child: - (feeSelectionResult?.$2 == null) - ? FutureBuilder( - future: ref.watch( - pWallets.select( - (value) => value.getWallet(walletId).fees, - ), - ), - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return DesktopFeeItem( - feeObject: snapshot.data, - feeRateType: FeeRateType.average, - walletId: walletId, - isButton: false, - feeFor: ({ - required Amount amount, - required FeeRateType feeRateType, - required int feeRate, - required CryptoCurrency coin, - }) async { - if (ref - .read(feeSheetSessionCacheProvider) - .average[amount] == - null) { - final wallet = ref - .read(pWallets) - .getWallet(walletId); - - if (coin is Monero || coin is Wownero) { - final fee = await wallet.estimateFeeFor( - amount, - lib_monero - .TransactionPriority - .medium - .value, - ); - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = - fee; - } else if ((coin is Firo) && - ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state != - FiroType.public) { - final firoWallet = wallet as FiroWallet; - - if (ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state == - FiroType.lelantus) { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = await firoWallet - .estimateFeeForLelantus(amount); - } else if (ref - .read( - publicPrivateBalanceStateProvider - .state, - ) - .state == - FiroType.spark) { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = await firoWallet - .estimateFeeForSpark(amount); - } - } else { - ref - .read(feeSheetSessionCacheProvider) - .average[amount] = await wallet - .estimateFeeFor(amount, feeRate); - } - } - return ref - .read(feeSheetSessionCacheProvider) - .average[amount]!; - }, - isSelected: true, - ); - } else { - return Row( - children: [ - AnimatedText( - stringsToLoopThrough: stringsToLoopThrough, - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textFieldActiveText, - ), - ), - ], - ); - } - }, - ) - : (coin is Firo) && - ref - .watch( - publicPrivateBalanceStateProvider.state, - ) - .state == - FiroType.lelantus - ? Text( - "~${ref.watch(pAmountFormatter(coin)).format(Amount(rawValue: BigInt.parse("3794"), fractionDigits: coin.fractionDigits), indicatePrecisionLoss: false)}", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of( - context, - ).extension()!.textFieldActiveText, - ), - textAlign: TextAlign.left, - ) - : Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - feeSelectionResult?.$2 ?? "", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textFieldActiveText, - ), - textAlign: TextAlign.left, - ), - Text( - feeSelectionResult?.$3 ?? "", - style: STextStyles.desktopTextExtraExtraSmall( - context, - ).copyWith( - color: - Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - ), - ], - ), - ), - if (isCustomFee) - Padding( - padding: const EdgeInsets.only(bottom: 12, top: 16), - child: FeeSlider( - coin: coin, - onSatVByteChanged: (rate) { - customFeeRate = rate; - }, ), ), const SizedBox(height: 36), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart new file mode 100644 index 000000000..154955148 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send_fee_form.dart @@ -0,0 +1,342 @@ +import 'package:cs_monero/cs_monero.dart' as lib_monero; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import '../../../../providers/providers.dart'; +import '../../../../providers/wallet/desktop_fee_providers.dart'; +import '../../../../providers/wallet/public_private_balance_state_provider.dart'; +import '../../../../themes/stack_colors.dart'; +import '../../../../utilities/amount/amount.dart'; +import '../../../../utilities/amount/amount_formatter.dart'; +import '../../../../utilities/enums/fee_rate_type_enum.dart'; +import '../../../../utilities/eth_commons.dart'; +import '../../../../utilities/text_styles.dart'; +import '../../../../wallets/crypto_currency/crypto_currency.dart'; +import '../../../../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart'; +import '../../../../wallets/isar/providers/eth/current_token_wallet_provider.dart'; +import '../../../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../../../wallets/wallet/impl/firo_wallet.dart'; +import '../../../../widgets/animated_text.dart'; +import '../../../../widgets/conditional_parent.dart'; +import '../../../../widgets/custom_buttons/blue_text_button.dart'; +import '../../../../widgets/desktop/desktop_fee_dialog.dart'; +import '../../../../widgets/eth_fee_form.dart'; +import '../../../../widgets/fee_slider.dart'; + +class DesktopSendFeeForm extends ConsumerStatefulWidget { + const DesktopSendFeeForm({ + super.key, + required this.walletId, + required this.isToken, + required this.onCustomFeeSliderChanged, + required this.onCustomFeeOptionChanged, + this.onCustomEip1559FeeOptionChanged, + }); + + final String walletId; + final bool isToken; + final void Function(int) onCustomFeeSliderChanged; + final void Function(bool) onCustomFeeOptionChanged; + final void Function(EthEIP1559Fee)? onCustomEip1559FeeOptionChanged; + + @override + ConsumerState createState() => _DesktopSendFeeFormState(); +} + +class _DesktopSendFeeFormState extends ConsumerState { + final stringsToLoopThrough = [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ]; + + late final CryptoCurrency cryptoCurrency; + + bool get isEth => cryptoCurrency is Ethereum; + + bool isCustomFee = false; + (FeeRateType, String?, String?)? feeSelectionResult; + + @override + void initState() { + super.initState(); + cryptoCurrency = ref.read(pWalletCoin(widget.walletId)); + } + + @override + Widget build(BuildContext context) { + final canEditFees = + isEth || + (cryptoCurrency is ElectrumXCurrencyInterface && + !(((cryptoCurrency is Firo) && + (ref.watch(publicPrivateBalanceStateProvider.state).state == + FiroType.lelantus || + ref.watch(publicPrivateBalanceStateProvider.state).state == + FiroType.spark)))); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConditionalParent( + condition: canEditFees, + builder: + (child) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + child, + CustomTextButton( + text: "Edit", + onTap: () async { + feeSelectionResult = + await showDialog<(FeeRateType, String?, String?)?>( + context: context, + builder: + (_) => DesktopFeeDialog( + walletId: widget.walletId, + isToken: widget.isToken, + ), + ); + + if (feeSelectionResult != null) { + if (isCustomFee && + feeSelectionResult!.$1 != FeeRateType.custom) { + isCustomFee = false; + } else if (!isCustomFee && + feeSelectionResult!.$1 == FeeRateType.custom) { + isCustomFee = true; + } + } + + setState(() {}); + }, + ), + ], + ), + child: Text( + "Transaction fee" + "${isCustomFee ? "" : " (${isEth ? "max" : "estimated"})"}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + ), + const SizedBox(height: 10), + if (!isCustomFee) + Padding( + padding: const EdgeInsets.all(10), + child: + (feeSelectionResult?.$2 == null) + ? FutureBuilder( + future: ref.watch( + pWallets.select( + (value) => value.getWallet(widget.walletId).fees, + ), + ), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + return DesktopFeeItem( + feeObject: snapshot.data, + feeRateType: FeeRateType.average, + walletId: widget.walletId, + isButton: false, + feeFor: ({ + required Amount amount, + required FeeRateType feeRateType, + required BigInt feeRate, + required CryptoCurrency coin, + }) async { + if (ref + .read( + widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider, + ) + .average[amount] == + null) { + if (widget.isToken == false) { + final wallet = ref + .read(pWallets) + .getWallet(widget.walletId); + + if (coin is Monero || coin is Wownero) { + final fee = await wallet.estimateFeeFor( + amount, + BigInt.from( + lib_monero + .TransactionPriority + .medium + .value, + ), + ); + ref + .read(feeSheetSessionCacheProvider) + .average[amount] = + fee; + } else if ((coin is Firo) && + ref + .read( + publicPrivateBalanceStateProvider + .state, + ) + .state != + FiroType.public) { + final firoWallet = wallet as FiroWallet; + + if (ref + .read( + publicPrivateBalanceStateProvider + .state, + ) + .state == + FiroType.lelantus) { + ref + .read(feeSheetSessionCacheProvider) + .average[amount] = await firoWallet + .estimateFeeForLelantus(amount); + } else if (ref + .read( + publicPrivateBalanceStateProvider + .state, + ) + .state == + FiroType.spark) { + ref + .read(feeSheetSessionCacheProvider) + .average[amount] = await firoWallet + .estimateFeeForSpark(amount); + } + } else { + ref + .read(feeSheetSessionCacheProvider) + .average[amount] = await wallet + .estimateFeeFor(amount, feeRate); + } + } else { + final tokenWallet = + ref.read(pCurrentTokenWallet)!; + final fee = await tokenWallet.estimateFeeFor( + amount, + feeRate, + ); + ref + .read(tokenFeeSessionCacheProvider) + .average[amount] = + fee; + } + } + return ref + .read( + widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider, + ) + .average[amount]!; + }, + isSelected: true, + ); + } else { + return Row( + children: [ + AnimatedText( + stringsToLoopThrough: stringsToLoopThrough, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveText, + ), + ), + ], + ); + } + }, + ) + : (cryptoCurrency is Firo) && + ref + .watch(publicPrivateBalanceStateProvider.state) + .state == + FiroType.lelantus + ? Builder( + builder: (context) { + final lelantusFee = ref + .watch(pAmountFormatter(cryptoCurrency)) + .format( + Amount( + rawValue: BigInt.parse("3794"), + fractionDigits: cryptoCurrency.fractionDigits, + ), + indicatePrecisionLoss: false, + ); + return Text( + "~$lelantusFee", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + ), + textAlign: TextAlign.left, + ); + }, + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + feeSelectionResult?.$2 ?? "", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + ), + textAlign: TextAlign.left, + ), + Text( + feeSelectionResult?.$3 ?? "", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ), + ], + ), + ), + if (isCustomFee && isEth) + EthFeeForm( + minGasLimit: + widget.isToken + ? kEthereumTokenMinGasLimit + : kEthereumMinGasLimit, + stateChanged: + (value) => widget.onCustomEip1559FeeOptionChanged?.call(value), + ), + if (isCustomFee && !isEth) + Padding( + padding: const EdgeInsets.only(bottom: 12, top: 16), + child: FeeSlider( + coin: cryptoCurrency, + onSatVByteChanged: widget.onCustomFeeSliderChanged, + ), + ), + ], + ); + } +} 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 647b1e1cc..48e8cb230 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 @@ -23,6 +23,7 @@ import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.dart import '../../../../providers/providers.dart'; import '../../../../providers/ui/fee_rate_type_state_provider.dart'; import '../../../../providers/ui/preview_tx_button_state_provider.dart'; +import '../../../../providers/wallet/desktop_fee_providers.dart'; import '../../../../themes/stack_colors.dart'; import '../../../../utilities/address_utils.dart'; import '../../../../utilities/amount/amount.dart'; @@ -33,7 +34,6 @@ import '../../../../utilities/barcode_scanner_interface.dart'; import '../../../../utilities/clipboard_interface.dart'; import '../../../../utilities/constants.dart'; import '../../../../utilities/logger.dart'; -import '../../../../utilities/prefs.dart'; import '../../../../utilities/text_styles.dart'; import '../../../../utilities/util.dart'; import '../../../../wallets/crypto_currency/crypto_currency.dart'; @@ -45,6 +45,7 @@ import '../../../../widgets/desktop/desktop_dialog.dart'; import '../../../../widgets/desktop/desktop_dialog_close_button.dart'; import '../../../../widgets/desktop/primary_button.dart'; import '../../../../widgets/desktop/secondary_button.dart'; +import '../../../../widgets/eth_fee_form.dart'; import '../../../../widgets/icon_widgets/addressbook_icon.dart'; import '../../../../widgets/icon_widgets/clipboard_icon.dart'; import '../../../../widgets/icon_widgets/x_icon.dart'; @@ -52,9 +53,7 @@ import '../../../../widgets/stack_text_field.dart'; import '../../../../widgets/textfield_icon_button.dart'; import '../../../desktop_home_view.dart'; import 'address_book_address_chooser/address_book_address_chooser.dart'; -import 'desktop_fee_dropdown.dart'; - -// const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$'; +import 'desktop_send_fee_form.dart'; class DesktopTokenSend extends ConsumerStatefulWidget { const DesktopTokenSend({ @@ -105,20 +104,21 @@ class _DesktopTokenSendState extends ConsumerState { bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; + EthEIP1559Fee? ethFee; + Future previewSend() async { final tokenWallet = ref.read(pCurrentTokenWallet)!; final Amount amount = _amountToSend!; - final Amount availableBalance = ref - .read( - pTokenBalance( - ( - walletId: walletId, - contractAddress: tokenWallet.tokenContract.address - ), - ), - ) - .spendable; + final Amount availableBalance = + ref + .read( + pTokenBalance(( + walletId: walletId, + contractAddress: tokenWallet.tokenContract.address, + )), + ) + .spendable; // confirm send all if (amount == availableBalance) { @@ -131,10 +131,7 @@ class _DesktopTokenSendState extends ConsumerState { maxWidth: 450, maxHeight: double.infinity, child: Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 32, - ), + padding: const EdgeInsets.only(left: 32, bottom: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -148,29 +145,20 @@ class _DesktopTokenSendState extends ConsumerState { const DesktopDialogCloseButton(), ], ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( - padding: const EdgeInsets.only( - right: 32, - ), + padding: const EdgeInsets.only(right: 32), child: Text( "You are about to send your entire balance. Would you like to continue?", textAlign: TextAlign.left, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - fontSize: 18, - ), + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith(fontSize: 18), ), ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 40), Padding( - padding: const EdgeInsets.only( - right: 32, - ), + padding: const EdgeInsets.only(right: 32), child: Row( children: [ Expanded( @@ -182,9 +170,7 @@ class _DesktopTokenSendState extends ConsumerState { }, ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: PrimaryButton( buttonHeight: ButtonHeight.l, @@ -241,71 +227,52 @@ class _DesktopTokenSendState extends ConsumerState { ); } - final time = Future.delayed( - const Duration( - milliseconds: 2500, - ), - ); + final time = Future.delayed(const Duration(milliseconds: 2500)); TxData txData; Future txDataFuture; txDataFuture = tokenWallet.prepareSend( txData: TxData( - recipients: [ - ( - address: _address!, - amount: amount, - isChange: false, - ), - ], - feeRateType: ref.read(feeRateTypeStateProvider), + recipients: [(address: _address!, amount: amount, isChange: false)], + feeRateType: ref.read(feeRateTypeDesktopStateProvider), nonce: int.tryParse(nonceController.text), + ethEIP1559Fee: ethFee, ), ); - final results = await Future.wait([ - txDataFuture, - time, - ]); + final results = await Future.wait([txDataFuture, time]); txData = results.first as TxData; if (!wasCancelled && mounted) { - txData = txData.copyWith( - note: _note ?? "", - ); + txData = txData.copyWith(note: _note ?? ""); // pop building dialog - Navigator.of( - context, - rootNavigator: true, - ).pop(); + Navigator.of(context, rootNavigator: true).pop(); unawaited( showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: ConfirmTransactionView( - txData: txData, - walletId: walletId, - onSuccess: clearSendForm, - isTokenTx: true, - routeOnSuccessName: DesktopHomeView.routeName, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: ConfirmTransactionView( + txData: txData, + walletId: walletId, + onSuccess: clearSendForm, + isTokenTx: true, + routeOnSuccessName: DesktopHomeView.routeName, + ), + ), ), ); } } catch (e) { if (mounted) { // pop building dialog - Navigator.of( - context, - rootNavigator: true, - ).pop(); + Navigator.of(context, rootNavigator: true).pop(); unawaited( showDialog( @@ -315,10 +282,7 @@ class _DesktopTokenSendState extends ConsumerState { maxWidth: 450, maxHeight: double.infinity, child: Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 32, - ), + padding: const EdgeInsets.only(left: 32, bottom: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -332,25 +296,18 @@ class _DesktopTokenSendState extends ConsumerState { const DesktopDialogCloseButton(), ], ), - const SizedBox( - height: 12, - ), + const SizedBox(height: 12), Padding( - padding: const EdgeInsets.only( - right: 32, - ), + padding: const EdgeInsets.only(right: 32), child: SelectableText( e.toString(), textAlign: TextAlign.left, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - fontSize: 18, - ), + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith(fontSize: 18), ), ), - const SizedBox( - height: 40, - ), + const SizedBox(height: 40), Row( children: [ Expanded( @@ -365,9 +322,7 @@ class _DesktopTokenSendState extends ConsumerState { }, ), ), - const SizedBox( - width: 32, - ), + const SizedBox(width: 32), ], ), ], @@ -395,7 +350,9 @@ class _DesktopTokenSendState extends ConsumerState { void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { - final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( + final cryptoAmount = ref + .read(pAmountFormatter(coin)) + .tryParse( cryptoAmountController.text, ethContract: ref.read(pCurrentTokenWallet)!.tokenContract, ); @@ -408,14 +365,15 @@ class _DesktopTokenSendState extends ConsumerState { } _cachedAmountToSend = _amountToSend; - final price = ref - .read(priceAnd24hChangeNotifierProvider) - .getTokenPrice( - ref.read(pCurrentTokenWallet)!.tokenContract.address, - ) - .item1; + final price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(pCurrentTokenWallet)!.tokenContract.address, + ) + ?.value; - if (price > Decimal.zero) { + if (price != null && price > Decimal.zero) { final String fiatAmountString = Amount.fromDecimal( _amountToSend!.decimal * price, fractionDigits: 2, @@ -495,10 +453,9 @@ class _DesktopTokenSendState extends ConsumerState { fractionDigits: ref.read(pCurrentTokenWallet)!.tokenContract.decimals, ); - cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format( - amount, - withUnitName: false, - ); + cryptoAmountController.text = ref + .read(pAmountFormatter(coin)) + .format(amount, withUnitName: false); _amountToSend = amount; } @@ -554,33 +511,39 @@ class _DesktopTokenSendState extends ConsumerState { if (baseAmountString.isNotEmpty && baseAmountString != "." && baseAmountString != ",") { - final baseAmount = baseAmountString.contains(",") - ? Decimal.parse(baseAmountString.replaceFirst(",", ".")) - .toAmount(fractionDigits: 2) - : Decimal.parse(baseAmountString).toAmount(fractionDigits: 2); - - final Decimal _price = ref - .read(priceAnd24hChangeNotifierProvider) - .getTokenPrice( - ref.read(pCurrentTokenWallet)!.tokenContract.address, - ) - .item1; - - if (_price == Decimal.zero) { + final baseAmount = + baseAmountString.contains(",") + ? Decimal.parse( + baseAmountString.replaceFirst(",", "."), + ).toAmount(fractionDigits: 2) + : Decimal.parse(baseAmountString).toAmount(fractionDigits: 2); + + final Decimal? _price = + ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(pCurrentTokenWallet)!.tokenContract.address, + ) + ?.value; + + if (_price == null || _price == Decimal.zero) { _amountToSend = Decimal.zero.toAmount(fractionDigits: tokenDecimals); } else { - _amountToSend = baseAmount <= Amount.zero - ? Decimal.zero.toAmount(fractionDigits: tokenDecimals) - : (baseAmount.decimal / _price) - .toDecimal(scaleOnInfinitePrecision: tokenDecimals) - .toAmount(fractionDigits: tokenDecimals); + _amountToSend = + baseAmount <= Amount.zero + ? Decimal.zero.toAmount(fractionDigits: tokenDecimals) + : (baseAmount.decimal / _price) + .toDecimal(scaleOnInfinitePrecision: tokenDecimals) + .toAmount(fractionDigits: tokenDecimals); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; } _cachedAmountToSend = _amountToSend; - final amountString = ref.read(pAmountFormatter(coin)).format( + final amountString = ref + .read(pAmountFormatter(coin)) + .format( _amountToSend!, withUnitName: false, ethContract: ref.read(pCurrentTokenWallet)!.tokenContract, @@ -602,19 +565,15 @@ class _DesktopTokenSendState extends ConsumerState { Future sendAllTapped() async { cryptoAmountController.text = ref .read( - pTokenBalance( - ( - walletId: walletId, - contractAddress: - ref.read(pCurrentTokenWallet)!.tokenContract.address - ), - ), + pTokenBalance(( + walletId: walletId, + contractAddress: + ref.read(pCurrentTokenWallet)!.tokenContract.address, + )), ) .spendable .decimal - .toStringAsFixed( - ref.read(pCurrentTokenWallet)!.tokenContract.decimals, - ); + .toStringAsFixed(ref.read(pCurrentTokenWallet)!.tokenContract.decimals); } @override @@ -702,16 +661,15 @@ class _DesktopTokenSendState extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), if (coin is Firo) Text( "Send from", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), @@ -721,9 +679,10 @@ class _DesktopTokenSendState extends ConsumerState { Text( "Amount", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), @@ -733,9 +692,7 @@ class _DesktopTokenSendState extends ConsumerState { ), ], ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, @@ -745,20 +702,22 @@ class _DesktopTokenSendState extends ConsumerState { key: const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: Util.isDesktop - ? null - : const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: + Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ AmountInputFormatter( decimals: tokenContract.decimals, unit: ref.watch(pAmountUnit(coin)), locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), ), // regex to validate a crypto amount with 8 decimal places @@ -780,9 +739,10 @@ class _DesktopTokenSendState extends ConsumerState { ), hintText: "0", hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldDefaultText, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultText, ), prefixIcon: FittedBox( fit: BoxFit.scaleDown, @@ -791,20 +751,23 @@ class _DesktopTokenSendState extends ConsumerState { child: Text( ref.watch(pAmountUnit(coin)).unitForContract(tokenContract), style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), ), ), ), - if (Prefs.instance.externalCalls) - const SizedBox( - height: 10, - ), - if (Prefs.instance.externalCalls) + if (ref.watch( + prefsChangeNotifierProvider.select((s) => s.externalCalls), + )) + const SizedBox(height: 10), + if (ref.watch( + prefsChangeNotifierProvider.select((s) => s.externalCalls), + )) TextField( autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, @@ -814,19 +777,21 @@ class _DesktopTokenSendState extends ConsumerState { key: const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: Util.isDesktop - ? null - : const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: + Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ AmountInputFormatter( decimals: 2, locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), ), // // regex to validate a fiat amount with 2 decimal places @@ -845,9 +810,10 @@ class _DesktopTokenSendState extends ConsumerState { ), hintText: "0", hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldDefaultText, + color: + Theme.of( + context, + ).extension()!.textFieldDefaultText, ), prefixIcon: FittedBox( fit: BoxFit.scaleDown, @@ -855,34 +821,33 @@ class _DesktopTokenSendState extends ConsumerState { padding: const EdgeInsets.all(12), child: Text( ref.watch( - prefsChangeNotifierProvider - .select((value) => value.currency), + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), ), style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: + Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), ), ), ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Text( "Send to", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -915,9 +880,10 @@ class _DesktopTokenSendState extends ConsumerState { }, focusNode: _addressFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, height: 1.8, ), decoration: standardInputDecoration( @@ -933,78 +899,83 @@ class _DesktopTokenSendState extends ConsumerState { right: 5, ), suffixIcon: Padding( - padding: sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), + padding: + sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), child: UnconstrainedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _addressToggleFlag ? TextFieldIconButton( - key: const Key( - "sendTokenViewClearAddressFieldButtonKey", - ), - onTap: () { - sendToController.text = ""; - _address = ""; - _updatePreviewButtonState( - _address, - _amountToSend, - ); - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) + key: const Key( + "sendTokenViewClearAddressFieldButtonKey", + ), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, + _amountToSend, + ); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) : TextFieldIconButton( - key: const Key( - "sendTokenViewPasteAddressFieldButtonKey", - ), - onTap: pasteAddress, - child: sendToController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), + key: const Key( + "sendTokenViewPasteAddressFieldButtonKey", ), + onTap: pasteAddress, + child: + sendToController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), if (sendToController.text.isEmpty) TextFieldIconButton( key: const Key("sendTokenViewAddressBookButtonKey"), onTap: () async { - final entry = - await showDialog( + final entry = await showDialog< + ContactAddressEntry? + >( context: context, - builder: (context) => DesktopDialog( - maxWidth: 696, - maxHeight: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, + builder: + (context) => DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Address book", - style: - STextStyles.desktopH3(context), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3( + context, + ), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, ), ), - const DesktopDialogCloseButton(), ], ), - Expanded( - child: AddressBookAddressChooser( - coin: coin, - ), - ), - ], - ), - ), + ), ); if (entry != null) { @@ -1034,9 +1005,7 @@ class _DesktopTokenSendState extends ConsumerState { ), Builder( builder: (_) { - final error = _updateInvalidAddressText( - _address ?? "", - ); + final error = _updateInvalidAddressText(_address ?? ""); if (error == null || error.isEmpty) { return Container(); @@ -1044,10 +1013,7 @@ class _DesktopTokenSendState extends ConsumerState { return Align( alignment: Alignment.topLeft, child: Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - ), + padding: const EdgeInsets.only(left: 12.0, top: 4.0), child: Text( error, textAlign: TextAlign.left, @@ -1061,40 +1027,28 @@ class _DesktopTokenSendState extends ConsumerState { } }, ), - const SizedBox( - height: 20, - ), - Text( - "Transaction fee (max)", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 10, - ), - DesktopFeeDropDown( + const SizedBox(height: 20), + DesktopSendFeeForm( walletId: walletId, isToken: true, + onCustomFeeSliderChanged: (value) => {}, + onCustomFeeOptionChanged: (value) { + ethFee = null; + }, + onCustomEip1559FeeOptionChanged: (value) => ethFee = value, ), - const SizedBox( - height: 20, - ), + const SizedBox(height: 20), Text( "Nonce", style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + color: + Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, ), textAlign: TextAlign.left, ), - const SizedBox( - height: 10, - ), + const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -1110,9 +1064,10 @@ class _DesktopTokenSendState extends ConsumerState { keyboardType: const TextInputType.numberWithOptions(), focusNode: _nonceFocusNode, style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, height: 1.8, ), decoration: standardInputDecoration( @@ -1130,16 +1085,15 @@ class _DesktopTokenSendState extends ConsumerState { ), ), ), - const SizedBox( - height: 36, - ), + const SizedBox(height: 36), PrimaryButton( buttonHeight: ButtonHeight.l, label: "Preview send", enabled: ref.watch(previewTokenTxButtonStateProvider.state).state, - onPressed: ref.watch(previewTokenTxButtonStateProvider.state).state - ? previewSend - : null, + onPressed: + ref.watch(previewTokenTxButtonStateProvider.state).state + ? previewSend + : null, ), ], ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index fdc0f99b7..dc09c757d 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -84,7 +84,7 @@ class _WDesktopWalletSummaryState extends ConsumerState { ) : null; - final priceTuple = + final price = widget.isToken ? ref.watch( priceAnd24hChangeNotifierProvider.select( @@ -150,9 +150,9 @@ class _WDesktopWalletSummaryState extends ConsumerState { style: STextStyles.desktopH3(context), ), ), - if (externalCalls) + if (externalCalls && price != null) SelectableText( - "${Amount.fromDecimal(priceTuple.item1 * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", + "${Amount.fromDecimal(price.value * balanceToShow.decimal, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of( diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart index 73f384f82..ecc80aa28 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/firo_desktop_wallet_summary.dart @@ -66,12 +66,14 @@ class _WFiroDesktopWalletSummaryState if (ref.watch( prefsChangeNotifierProvider.select((value) => value.externalCalls), )) { - final priceTuple = ref.watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin), - ), - ); - price = priceTuple.item1; + price = + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ) + ?.value; } final _showAvailable = diff --git a/lib/providers/ui/fee_rate_type_state_provider.dart b/lib/providers/ui/fee_rate_type_state_provider.dart index 3e1421c14..8b309a2e5 100644 --- a/lib/providers/ui/fee_rate_type_state_provider.dart +++ b/lib/providers/ui/fee_rate_type_state_provider.dart @@ -9,7 +9,13 @@ */ import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../../utilities/enums/fee_rate_type_enum.dart'; -final feeRateTypeStateProvider = - StateProvider.autoDispose((_) => FeeRateType.average); +final feeRateTypeMobileStateProvider = StateProvider.autoDispose( + (_) => FeeRateType.average, +); + +final feeRateTypeDesktopStateProvider = StateProvider( + (_) => FeeRateType.average, +); diff --git a/lib/providers/wallet/desktop_fee_providers.dart b/lib/providers/wallet/desktop_fee_providers.dart new file mode 100644 index 000000000..d1723c78c --- /dev/null +++ b/lib/providers/wallet/desktop_fee_providers.dart @@ -0,0 +1,23 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import '../../utilities/amount/amount.dart'; + +final tokenFeeSessionCacheProvider = + ChangeNotifierProvider((ref) { + return FeeSheetSessionCache(); + }); + +final sendAmountProvider = StateProvider.autoDispose( + (_) => Amount.zero, +); diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart index fccce0ceb..091165b30 100644 --- a/lib/services/ethereum/ethereum_api.dart +++ b/lib/services/ethereum/ethereum_api.dart @@ -10,18 +10,13 @@ import 'dart:convert'; -import 'package:tuple/tuple.dart'; - import '../../dto/ethereum/eth_token_tx_dto.dart'; -import '../../dto/ethereum/eth_token_tx_extra_dto.dart'; import '../../dto/ethereum/eth_tx_dto.dart'; -import '../../dto/ethereum/pending_eth_tx_dto.dart'; import '../../models/isar/models/ethereum/eth_contract.dart'; import '../../models/paymint/fee_object_model.dart'; import '../../networking/http.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/eth_commons.dart'; -import '../../utilities/extensions/extensions.dart'; import '../../utilities/logger.dart'; import '../../utilities/prefs.dart'; import '../../wallets/crypto_currency/crypto_currency.dart'; @@ -60,11 +55,12 @@ abstract class EthereumAPI { try { final response = await client.get( url: Uri.parse( - "$stackBaseServer/export?addrs=$address&firstBlock=$firstBlock", + "$stackBaseServer/export?addrs=$address&firstBlock=$firstBlock&unripe=true", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { @@ -80,17 +76,11 @@ abstract class EthereumAPI { txns.add(txn); } } - return EthereumResponse( - txns, - null, - ); + return EthereumResponse(txns, null); } else { // nice that the api returns an empty body instead of being // consistent and returning a json object with no transactions - return EthereumResponse( - [], - null, - ); + return EthereumResponse([], null); } } else { throw EthApiException( @@ -99,10 +89,7 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { Logging.instance.e("getEthTransactions()", error: e); Logging.instance.d( @@ -110,212 +97,7 @@ abstract class EthereumAPI { error: e, stackTrace: s, ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); - } - } - - static Future> getEthTransactionByHash( - String txid, - ) async { - try { - final response = await client.post( - url: Uri.parse( - "$stackBaseServer/v1/mainnet", - ), - headers: {'Content-Type': 'application/json'}, - body: json.encode({ - "jsonrpc": "2.0", - "method": "eth_getTransactionByHash", - "params": [ - txid, - ], - "id": DateTime.now().millisecondsSinceEpoch, - }), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - - if (response.code == 200) { - if (response.body.isNotEmpty) { - try { - final json = jsonDecode(response.body) as Map; - final result = json["result"] as Map; - return EthereumResponse( - PendingEthTxDto.fromMap(Map.from(result)), - null, - ); - } catch (_) { - throw EthApiException( - "getEthTransactionByHash($txid) failed with response: " - "${response.body}", - ); - } - } else { - throw EthApiException( - "getEthTransactionByHash($txid) response is empty but status code is " - "${response.code}", - ); - } - } else { - throw EthApiException( - "getEthTransactionByHash($txid) failed with status code: " - "${response.code}", - ); - } - } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); - } catch (e, s) { - Logging.instance.e( - "getEthTransactionByHash()", - error: e, - ); - Logging.instance.d( - "getEthTransactionByHash($txid)", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); - } - } - - static Future>>> - getEthTransactionNonces( - List txns, - ) async { - try { - final response = await client.get( - url: Uri.parse( - "$stackBaseServer/transactions?transactions=${txns.map((e) => e.hash).join(" ")}&raw=true", - ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - - if (response.code == 200) { - if (response.body.isNotEmpty) { - final json = jsonDecode(response.body) as Map; - final list = List>.from(json["data"] as List); - - final List> result = []; - - for (final dto in txns) { - final data = - list.firstWhere((e) => e["hash"] == dto.hash, orElse: () => {}); - - final nonce = (data["nonce"] as String?)?.toBigIntFromHex.toInt(); - result.add(Tuple2(dto, nonce)); - } - return EthereumResponse( - result, - null, - ); - } else { - // nice that the api returns an empty body instead of being - // consistent and returning a json object with no transactions - return EthereumResponse( - [], - null, - ); - } - } else { - throw EthApiException( - "getEthTransactionNonces($txns) failed with status code: " - "${response.code}", - ); - } - } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); - } catch (e, s) { - Logging.instance.e( - "getEthTransactionNonces()", - error: e, - ); - Logging.instance.d( - "getEthTransactionNonces($txns)", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); - } - } - - static Future>> - getEthTokenTransactionsByTxids(List txids) async { - try { - final response = await client.get( - url: Uri.parse( - "$stackBaseServer/transactions?transactions=${txids.join(" ")}", - ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - - if (response.code == 200) { - if (response.body.isNotEmpty) { - final json = jsonDecode(response.body) as Map; - final list = json["data"] as List?; - - final List txns = []; - for (final map in list!) { - final txn = EthTokenTxExtraDTO.fromMap( - Map.from(map as Map), - ); - - txns.add(txn); - } - return EthereumResponse( - txns, - null, - ); - } else { - throw EthApiException( - "getEthTokenTransactionsByTxids($txids) response is empty but status code is " - "${response.code}", - ); - } - } else { - throw EthApiException( - "getEthTokenTransactionsByTxids($txids) failed with status code: " - "${response.code}", - ); - } - } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); - } catch (e, s) { - Logging.instance.e( - "getEthTokenTransactionsByTxids()", - error: e, - ); - Logging.instance.d( - "getEthTokenTransactionsByTxids($txids)", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + return EthereumResponse(null, EthApiException(e.toString())); } } @@ -328,9 +110,10 @@ abstract class EthereumAPI { url: Uri.parse( "$stackBaseServer/export?addrs=$address&emitter=$tokenContractAddress&logs=true", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { @@ -340,22 +123,17 @@ abstract class EthereumAPI { final List txns = []; for (final map in list!) { - final txn = - EthTokenTxDto.fromMap(Map.from(map as Map)); + final txn = EthTokenTxDto.fromMap( + Map.from(map as Map), + ); txns.add(txn); } - return EthereumResponse( - txns, - null, - ); + return EthereumResponse(txns, null); } else { // nice that the api returns an empty body instead of being // consistent and returning a json object with no transactions - return EthereumResponse( - [], - null, - ); + return EthereumResponse([], null); } } else { throw EthApiException( @@ -364,100 +142,18 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { - Logging.instance.e( - "getTokenTransactions()", - error: e, - ); + Logging.instance.e("getTokenTransactions()", error: e); Logging.instance.d( "getTokenTransactions($address, $tokenContractAddress)", error: e, stackTrace: s, ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + return EthereumResponse(null, EthApiException(e.toString())); } } -// ONLY FETCHES WALLET TOKENS WITH A NON ZERO BALANCE - // static Future>> getWalletTokens({ - // required String address, - // }) async { - // try { - // final uri = Uri.parse( - // "$blockExplorer?module=account&action=tokenlist&address=$address", - // ); - // final response = await get(uri); - // - // if (response.statusCode == 200) { - // final json = jsonDecode(response.body); - // if (json["message"] == "OK") { - // final result = - // List>.from(json["result"] as List); - // final List tokens = []; - // for (final map in result) { - // if (map["type"] == "ERC-20") { - // tokens.add( - // Erc20Token( - // balance: int.parse(map["balance"] as String), - // contractAddress: map["contractAddress"] as String, - // decimals: int.parse(map["decimals"] as String), - // name: map["name"] as String, - // symbol: map["symbol"] as String, - // ), - // ); - // } else if (map["type"] == "ERC-721") { - // tokens.add( - // Erc721Token( - // balance: int.parse(map["balance"] as String), - // contractAddress: map["contractAddress"] as String, - // decimals: int.parse(map["decimals"] as String), - // name: map["name"] as String, - // symbol: map["symbol"] as String, - // ), - // ); - // } else { - // throw EthApiException( - // "Unsupported token type found: ${map["type"]}"); - // } - // } - // - // return EthereumResponse( - // tokens, - // null, - // ); - // } else { - // throw EthApiException(json["message"] as String); - // } - // } else { - // throw EthApiException( - // "getWalletTokens($address) failed with status code: " - // "${response.statusCode}", - // ); - // } - // } on EthApiException catch (e) { - // return EthereumResponse( - // null, - // e, - // ); - // } catch (e, s) { - // Logging.instance.log( - // "getWalletTokens(): $e\n$s", - // level: LogLevel.Error, - // ); - // return EthereumResponse( - // null, - // EthApiException(e.toString()), - // ); - // } - // } - static Future> getWalletTokenBalance({ required String address, required String contractAddress, @@ -468,9 +164,10 @@ abstract class EthereumAPI { ); final response = await client.get( url: uri, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { @@ -479,7 +176,7 @@ abstract class EthereumAPI { final map = json["data"].first as Map; final balance = - BigInt.tryParse(map["units"].toString()) ?? BigInt.zero; + BigInt.tryParse(map["balance"].toString()) ?? BigInt.zero; return EthereumResponse( Amount(rawValue: balance, fractionDigits: map["decimals"] as int), @@ -495,84 +192,21 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); - } catch (e, s) { - Logging.instance.e( - "getWalletTokenBalance()", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); - } - } - - static Future> getAddressNonce({ - required String address, - }) async { - try { - final uri = Uri.parse( - "$stackBaseServer/state?addrs=$address&parts=all", - ); - final response = await client.get( - url: uri, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, - ); - - if (response.code == 200) { - final json = jsonDecode(response.body); - if (json["data"] is List) { - final map = json["data"].first as Map; - - final nonce = map["nonce"] as int; - - return EthereumResponse( - nonce, - null, - ); - } else { - throw EthApiException(json["message"] as String); - } - } else { - throw EthApiException( - "getAddressNonce($address) failed with status code: " - "${response.code}", - ); - } - } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { - Logging.instance.e( - "getAddressNonce()", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + Logging.instance.e("getWalletTokenBalance()", error: e, stackTrace: s); + return EthereumResponse(null, EthApiException(e.toString())); } } static Future> getGasOracle() async { try { final response = await client.get( - url: Uri.parse( - "$stackBaseServer/gas-prices", - ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + url: Uri.parse("$stackBaseServer/gas-prices"), + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { @@ -604,36 +238,27 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { - Logging.instance.e( - "getGasOracle()", - error: e, - stackTrace: s, - ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + Logging.instance.e("getGasOracle()", error: e, stackTrace: s); + return EthereumResponse(null, EthApiException(e.toString())); } } - static Future getFees() async { - final fees = (await getGasOracle()).value!; - final feesFast = fees.fast.shift(9).toBigInt(); - final feesStandard = fees.average.shift(9).toBigInt(); - final feesSlow = fees.slow.shift(9).toBigInt(); - - return FeeObject( - numberOfBlocksFast: fees.numberOfBlocksFast, - numberOfBlocksAverage: fees.numberOfBlocksAverage, - numberOfBlocksSlow: fees.numberOfBlocksSlow, - fast: feesFast.toInt(), - medium: feesStandard.toInt(), - slow: feesSlow.toInt(), + static Future getFees() async { + final response = await getGasOracle(); + if (response.exception != null) { + throw response.exception!; + } + + return EthFeeObject( + suggestBaseFee: response.value!.suggestBaseFee.shift(9).toBigInt(), + numberOfBlocksFast: response.value!.numberOfBlocksFast, + numberOfBlocksAverage: response.value!.numberOfBlocksAverage, + numberOfBlocksSlow: response.value!.numberOfBlocksSlow, + fast: response.value!.high.shift(9).toBigInt(), + medium: response.value!.average.shift(9).toBigInt(), + slow: response.value!.low.shift(9).toBigInt(), ); } @@ -642,9 +267,10 @@ abstract class EthereumAPI { url: Uri.parse( "$stackBaseServer/names?terms=$contractAddress&autoname=$contractAddress&all", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); } @@ -658,9 +284,10 @@ abstract class EthereumAPI { // "$stackBaseServer/tokens?addrs=$contractAddress&parts=all", "$stackBaseServer/names?terms=$contractAddress&all", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { @@ -713,10 +340,7 @@ abstract class EthereumAPI { ); } - return EthereumResponse( - token, - null, - ); + return EthereumResponse(token, null); } else { throw EthApiException(response.body); } @@ -727,20 +351,14 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { Logging.instance.e( "getTokenByContractAddress()", error: e, stackTrace: s, ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + return EthereumResponse(null, EthApiException(e.toString())); } } @@ -753,18 +371,16 @@ abstract class EthereumAPI { url: Uri.parse( "$stackBaseServer/abis?addrs=$contractAddress&verbose=true", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { final json = jsonDecode(response.body)["data"] as List; - return EthereumResponse( - jsonEncode(json), - null, - ); + return EthereumResponse(jsonEncode(json), null); } else { throw EthApiException( "getTokenAbi($name, $contractAddress) failed with status code: " @@ -772,20 +388,14 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { Logging.instance.e( "getTokenAbi($name, $contractAddress)", error: e, stackTrace: s, ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + return EthereumResponse(null, EthApiException(e.toString())); } } @@ -798,19 +408,17 @@ abstract class EthereumAPI { url: Uri.parse( "$stackBaseServer/state?addrs=$contractAddress&parts=proxy", ), - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); if (response.code == 200) { final json = jsonDecode(response.body); final list = json["data"] as List; final map = Map.from(list.first as Map); - return EthereumResponse( - map["proxy"] as String, - null, - ); + return EthereumResponse(map["proxy"] as String, null); } else { throw EthApiException( "getProxyTokenImplementationAddress($contractAddress) failed with" @@ -818,20 +426,14 @@ abstract class EthereumAPI { ); } } on EthApiException catch (e) { - return EthereumResponse( - null, - e, - ); + return EthereumResponse(null, e); } catch (e, s) { Logging.instance.e( "getProxyTokenImplementationAddress($contractAddress)", error: e, stackTrace: s, ); - return EthereumResponse( - null, - EthApiException(e.toString()), - ); + return EthereumResponse(null, EthApiException(e.toString())); } } } diff --git a/lib/services/price.dart b/lib/services/price.dart index 801e720e2..75a4ad664 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -13,7 +13,6 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; -import 'package:tuple/tuple.dart'; import '../app_config.dart'; import '../db/hive/db.dart'; @@ -54,13 +53,15 @@ class PriceAPI { static const refreshInterval = 60; // initialize to older than current time minus at least refreshInterval - static DateTime _lastCalled = - DateTime.now().subtract(const Duration(seconds: refreshInterval + 10)); + static DateTime _lastCalled = DateTime.now().subtract( + const Duration(seconds: refreshInterval + 10), + ); static String _lastUsedBaseCurrency = ""; - static const Duration refreshIntervalDuration = - Duration(seconds: refreshInterval); + static const Duration refreshIntervalDuration = Duration( + seconds: refreshInterval, + ); final HTTP client; @@ -72,7 +73,7 @@ class PriceAPI { } Future _updateCachedPrices( - Map> data, + Map data, ) async { final Map map = {}; @@ -81,30 +82,29 @@ class PriceAPI { if (entry == null) { map[coin.prettyName] = ["0", 0.0]; } else { - map[coin.prettyName] = [entry.item1.toString(), entry.item2]; + map[coin.prettyName] = [entry.value.toString(), entry.change24h]; } } - await DB.instance - .put(boxName: DB.boxNamePriceCache, key: 'cache', value: map); + await DB.instance.put( + boxName: DB.boxNamePriceCache, + key: 'cache', + value: map, + ); } - Map> get _cachedPrices { + Map get _cachedPrices { final map = DB.instance.get(boxName: DB.boxNamePriceCache, key: 'cache') - as Map? ?? - {}; + as Map? ?? + {}; // init with 0 - final result = { - for (final coin in AppConfig.coins) coin: Tuple2(Decimal.zero, 0.0), - }; + final Map result = {}; for (final entry in map.entries) { - result[AppConfig.getCryptoCurrencyByPrettyName( - entry.key as String, - )] = Tuple2( - Decimal.parse(entry.value[0] as String), - entry.value[1] as double, + result[AppConfig.getCryptoCurrencyByPrettyName(entry.key as String)] = ( + value: Decimal.parse(entry.value[0] as String), + change24h: entry.value[1] as double, ); } @@ -117,9 +117,8 @@ class PriceAPI { .where((e) => e != null) .join(","); - Future>> getPricesAnd24hChange({ - required String baseCurrency, - }) async { + Future> + getPricesAnd24hChange({required String baseCurrency}) async { final now = DateTime.now(); if (_lastUsedBaseCurrency != baseCurrency || now.difference(_lastCalled) > refreshIntervalDuration) { @@ -132,12 +131,10 @@ class PriceAPI { final externalCalls = Prefs.instance.externalCalls; if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.i( - "User does not want to use external calls", - ); + Logging.instance.i("User does not want to use external calls"); return _cachedPrices; } - final Map> result = {}; + final Map result = {}; try { final uri = Uri.parse( "https://api.coingecko.com/api/v3/coins/markets?vs_currency" @@ -148,9 +145,10 @@ class PriceAPI { final coinGeckoResponse = await client.get( url: uri, headers: {'Content-Type': 'application/json'}, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); final coinGeckoData = jsonDecode(coinGeckoResponse.body) as List; @@ -159,12 +157,17 @@ class PriceAPI { final String coinName = map["name"] as String; final coin = AppConfig.getCryptoCurrencyByPrettyName(coinName); - final price = Decimal.parse(map["current_price"].toString()); - final change24h = map["price_change_percentage_24h"] != null - ? double.parse(map["price_change_percentage_24h"].toString()) - : 0.0; - - result[coin] = Tuple2(price, change24h); + try { + final price = Decimal.parse(map["current_price"].toString()); + final change24h = + map["price_change_percentage_24h"] != null + ? double.parse(map["price_change_percentage_24h"].toString()) + : 0.0; + + result[coin] = (value: price, change24h: change24h); + } catch (_) { + result.remove(coin); + } } // update cache @@ -172,8 +175,11 @@ class PriceAPI { return _cachedPrices; } catch (e, s) { - Logging.instance - .e("getPricesAnd24hChange($baseCurrency): ", error: e, stackTrace: s); + Logging.instance.e( + "getPricesAnd24hChange($baseCurrency): ", + error: e, + stackTrace: s, + ); // return previous cached values return _cachedPrices; } @@ -185,9 +191,7 @@ class PriceAPI { if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.i( - "User does not want to use external calls", - ); + Logging.instance.i("User does not want to use external calls"); return null; } const uriString = @@ -197,9 +201,10 @@ class PriceAPI { final response = await client.get( url: uri, headers: {'Content-Type': 'application/json'}, - proxyInfo: Prefs.instance.useTor - ? TorService.sharedInstance.getProxyInfo() - : null, + proxyInfo: + Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, ); final json = jsonDecode(response.body) as List; @@ -214,22 +219,22 @@ class PriceAPI { } } - Future>> - getPricesAnd24hChangeForEthTokens({ + Future> + getPricesAnd24hChangeForEthTokens({ required Set contractAddresses, required String baseCurrency, }) async { - final Map> tokenPrices = {}; + final Map tokenPrices = {}; if (AppConfig.coins.whereType().isEmpty || - contractAddresses.isEmpty) return tokenPrices; + contractAddresses.isEmpty) { + return tokenPrices; + } final externalCalls = Prefs.instance.externalCalls; if ((!Util.isTestEnv && !externalCalls) || !(await Prefs.instance.isExternalCallsSet())) { - Logging.instance.i( - "User does not want to use external calls", - ); + Logging.instance.i("User does not want to use external calls"); return tokenPrices; } diff --git a/lib/services/price_service.dart b/lib/services/price_service.dart index fb30c8477..43eb68e32 100644 --- a/lib/services/price_service.dart +++ b/lib/services/price_service.dart @@ -13,13 +13,12 @@ import 'dart:async'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:isar/isar.dart'; + import '../db/isar/main_db.dart'; import '../models/isar/models/isar_models.dart'; import '../networking/http.dart'; -import 'price.dart'; -import '../app_config.dart'; import '../wallets/crypto_currency/crypto_currency.dart'; -import 'package:tuple/tuple.dart'; +import 'price.dart'; class PriceService extends ChangeNotifier { late final String baseTicker; @@ -29,25 +28,26 @@ class PriceService extends ChangeNotifier { final Duration updateInterval = const Duration(seconds: 60); Timer? _timer; - final Map> _cachedPrices = { - for (final coin in AppConfig.coins) coin: Tuple2(Decimal.zero, 0.0), - }; + final Map _cachedPrices = + {}; - final Map> _cachedTokenPrices = {}; + final Map _cachedTokenPrices = + {}; final _priceAPI = PriceAPI(HTTP()); - Tuple2 getPrice(CryptoCurrency coin) => _cachedPrices[coin]!; + ({Decimal value, double change24h})? getPrice(CryptoCurrency coin) => + _cachedPrices[coin]; - Tuple2 getTokenPrice(String contractAddress) => - _cachedTokenPrices[contractAddress.toLowerCase()] ?? - Tuple2(Decimal.zero, 0); + ({Decimal value, double change24h})? getTokenPrice(String contractAddress) => + _cachedTokenPrices[contractAddress.toLowerCase()]; PriceService(this.baseTicker); Future updatePrice() async { - final priceMap = - await _priceAPI.getPricesAnd24hChange(baseCurrency: baseTicker); + final priceMap = await _priceAPI.getPricesAnd24hChange( + baseCurrency: baseTicker, + ); bool shouldNotify = false; for (final map in priceMap.entries) { diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 171dc09b2..d14e4e809 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -33,8 +33,9 @@ class AddressUtils { /// Return only recognized parameters. static Map _filterParams(Map params) { return Map.fromEntries( - params.entries - .where((entry) => recognizedParams.contains(entry.key.toLowerCase())), + params.entries.where( + (entry) => recognizedParams.contains(entry.key.toLowerCase()), + ), ); } @@ -52,8 +53,10 @@ class AddressUtils { result["address"] = u.path; } else if (result["scheme"] == "monero") { // Monero addresses can contain '?' which Uri.parse interprets as query start. - final addressEnd = - uri.indexOf('?', 7); // 7 is the length of "monero:". + final addressEnd = uri.indexOf( + '?', + 7, + ); // 7 is the length of "monero:". if (addressEnd != -1) { result["address"] = uri.substring(7, addressEnd); } else { @@ -130,10 +133,7 @@ class AddressUtils { /// Centralized method to handle various cryptocurrency URIs and return a common object. /// /// Returns null on failure to parse - static PaymentUriData? parsePaymentUri( - String uri, { - Logging? logging, - }) { + static PaymentUriData? parsePaymentUri(String uri, {Logging? logging}) { try { final Map parsedData = _parseUri(uri); @@ -155,7 +155,7 @@ class AddressUtils { additionalParams: filteredParams, ); } catch (e, s) { - logging?.e("", error: e, stackTrace: s); + logging?.i("Invalid payment URI: $uri", error: e, stackTrace: s); return null; } } @@ -259,8 +259,8 @@ class PaymentUriData { final Map additionalParams; CryptoCurrency? get coin => AddressUtils._getCryptoCurrencyByScheme( - scheme ?? "", // empty will just return null - ); + scheme ?? "", // empty will just return null + ); PaymentUriData({ required this.address, @@ -273,7 +273,8 @@ class PaymentUriData { }); @override - String toString() => "PaymentUriData { " + String toString() => + "PaymentUriData { " "coin: $coin, " "address: $address, " "amount: $amount, " diff --git a/lib/utilities/eth_commons.dart b/lib/utilities/eth_commons.dart index c105412db..3bb8557de 100644 --- a/lib/utilities/eth_commons.dart +++ b/lib/utilities/eth_commons.dart @@ -12,36 +12,45 @@ import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; import 'package:decimal/decimal.dart'; import "package:hex/hex.dart"; + import '../wallets/crypto_currency/crypto_currency.dart'; class GasTracker { + final Decimal low; final Decimal average; - final Decimal fast; - final Decimal slow; + final Decimal high; final int numberOfBlocksFast; final int numberOfBlocksAverage; final int numberOfBlocksSlow; + final Decimal suggestBaseFee; + final String lastBlock; const GasTracker({ + required this.low, required this.average, - required this.fast, - required this.slow, + required this.high, required this.numberOfBlocksFast, required this.numberOfBlocksAverage, required this.numberOfBlocksSlow, + required this.suggestBaseFee, required this.lastBlock, }); + Decimal get lowPriority => (low - suggestBaseFee).zeroIfNegative; + Decimal get averagePriority => (average - suggestBaseFee).zeroIfNegative; + Decimal get highPriority => (high - suggestBaseFee).zeroIfNegative; + factory GasTracker.fromJson(Map json) { final targetTime = Ethereum(CryptoCurrencyNetwork.main).targetBlockTimeSeconds; return GasTracker( - fast: Decimal.parse(json["FastGasPrice"].toString()), + high: Decimal.parse(json["FastGasPrice"].toString()), average: Decimal.parse(json["ProposeGasPrice"].toString()), - slow: Decimal.parse(json["SafeGasPrice"].toString()), + low: Decimal.parse(json["SafeGasPrice"].toString()), + suggestBaseFee: Decimal.parse(json["suggestBaseFee"].toString()), // TODO fix hardcoded numberOfBlocksFast: 30 ~/ targetTime, numberOfBlocksAverage: 180 ~/ targetTime, @@ -49,10 +58,34 @@ class GasTracker { lastBlock: json["LastBlock"] as String, ); } + + @override + String toString() { + return 'GasTracker(' + 'slow: $low, ' + 'average: $average, ' + 'fast: $high, ' + 'suggestBaseFee: $suggestBaseFee, ' + 'numberOfBlocksFast: $numberOfBlocksFast, ' + 'numberOfBlocksAverage: $numberOfBlocksAverage, ' + 'numberOfBlocksSlow: $numberOfBlocksSlow, ' + 'lastBlock: $lastBlock' + ')'; + } +} + +extension DecimalExt on Decimal { + Decimal get zeroIfNegative { + if (this < Decimal.zero) return Decimal.zero; + return this; + } } const hdPathEthereum = "m/44'/60'/0'/0"; +const kEthereumMinGasLimit = 21000; +const kEthereumTokenMinGasLimit = 65000; + // equal to "0x${keccak256("Transfer(address,address,uint256)".toUint8ListFromUtf8).toHex}"; const kTransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart index f62b1ea4e..d4cc03dcf 100644 --- a/lib/utilities/logger.dart +++ b/lib/utilities/logger.dart @@ -53,87 +53,79 @@ class Logging { SendPort get _sendPort { final port = IsolateNameServer.lookupPortByName(_kLoggerPortName); if (port == null) { - throw Exception( - "Did you forget to call Logging.initialize()?", - ); + throw Exception("Did you forget to call Logging.initialize()?"); } return port; } - Future initialize(String logsPath, {required Level level}) async { + Future initialize( + String logsPath, { + required Level level, + Level? debugConsoleLevel, + }) async { if (Isolate.current.debugName != "main") { throw Exception( "Logging.initialize() must be called on the main isolate.", ); } if (IsolateNameServer.lookupPortByName(_kLoggerPortName) != null) { - throw Exception( - "Logging was already initialized", - ); + throw Exception("Logging was already initialized"); } logsDirPath = logsPath; final receivePort = ReceivePort(); - await Isolate.spawn( - (sendPort) { - final ReceivePort receivePort = ReceivePort(); - sendPort.send(receivePort.sendPort); + await Isolate.spawn((sendPort) { + final ReceivePort receivePort = ReceivePort(); + sendPort.send(receivePort.sendPort); + + PrettyPrinter prettyPrinter(bool toFile) => PrettyPrinter( + printEmojis: false, + methodCount: 0, + dateTimeFormat: + toFile ? DateTimeFormat.none : DateTimeFormat.dateAndTime, + colors: !toFile, + noBoxingByDefault: toFile, + ); - PrettyPrinter prettyPrinter(bool toFile) => PrettyPrinter( - printEmojis: false, - methodCount: 0, - dateTimeFormat: - toFile ? DateTimeFormat.none : DateTimeFormat.dateAndTime, - colors: !toFile, - noBoxingByDefault: toFile, - ); + final consoleLogger = Logger( + printer: PrefixPrinter(prettyPrinter(false)), + filter: ProductionFilter(), + level: debugConsoleLevel ?? level, + ); - final consoleLogger = Logger( - printer: PrefixPrinter(prettyPrinter(false)), - filter: ProductionFilter(), - level: level, - ); + final fileLogger = Logger( + printer: PrefixPrinter(prettyPrinter(true)), + filter: ProductionFilter(), + level: level, + output: AdvancedFileOutput( + path: logsDirPath, + overrideExisting: false, + latestFileName: "latest.txt", + writeImmediately: [Level.error, Level.fatal, Level.warning], + ), + ); - final fileLogger = Logger( - printer: PrefixPrinter(prettyPrinter(true)), - filter: ProductionFilter(), - level: level, - output: AdvancedFileOutput( - path: logsDirPath, - overrideExisting: false, - latestFileName: "latest.txt", - writeImmediately: [ - Level.error, - Level.fatal, - Level.warning, - Level.trace, // mainly for spark debugging. TODO: Remove later - ], - ), + receivePort.listen((message) { + final event = (message as (LogEvent, bool)).$1; + consoleLogger.log( + event.level, + event.message, + stackTrace: event.stackTrace, + error: event.error, + time: event.time.toUtc(), ); - - receivePort.listen((message) { - final event = (message as (LogEvent, bool)).$1; - consoleLogger.log( + if (message.$2) { + fileLogger.log( event.level, - event.message, + "${event.time.toUtc().toIso8601String()} ${event.message}", stackTrace: event.stackTrace, error: event.error, - time: event.time.toUtc(), + time: event.time, ); - if (message.$2) { - fileLogger.log( - event.level, - "${event.time.toUtc().toIso8601String()} ${event.message}", - stackTrace: event.stackTrace, - error: event.error, - time: event.time, - ); - } - }); - }, - receivePort.sendPort, - ); + } + }); + }, receivePort.sendPort); final loggerPort = await receivePort.first as SendPort; IsolateNameServer.registerPortWithName(loggerPort, _kLoggerPortName); } @@ -155,18 +147,16 @@ class Logging { toFile = false; } try { - _sendPort.send( - ( - LogEvent( - level, - _stringifyMessage(message), - time: time, - error: error, - stackTrace: stackTrace, - ), - toFile + _sendPort.send(( + LogEvent( + level, + _stringifyMessage(message), + time: time, + error: error, + stackTrace: stackTrace, ), - ); + toFile, + )); } catch (e, s) { t("Isolates suck", error: e, stackTrace: s); } @@ -177,82 +167,76 @@ class Logging { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.trace, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.trace, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); void d( dynamic message, { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.debug, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.debug, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); void i( dynamic message, { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.info, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.info, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); void w( dynamic message, { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.warning, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.warning, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); void e( dynamic message, { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.error, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.error, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); void f( dynamic message, { DateTime? time, Object? error, StackTrace? stackTrace, - }) => - log( - Level.fatal, - message, - time: time, - error: error, - stackTrace: stackTrace, - ); + }) => log( + Level.fatal, + message, + time: time, + error: error, + stackTrace: stackTrace, + ); } diff --git a/lib/wallets/crypto_currency/coins/ethereum.dart b/lib/wallets/crypto_currency/coins/ethereum.dart index 1d7bb53ed..cd908b9e6 100644 --- a/lib/wallets/crypto_currency/coins/ethereum.dart +++ b/lib/wallets/crypto_currency/coins/ethereum.dart @@ -4,6 +4,7 @@ import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/node_model.dart'; import '../../../utilities/default_nodes.dart'; import '../../../utilities/enums/derive_path_type_enum.dart'; +import '../../../utilities/eth_commons.dart'; import '../crypto_currency.dart'; import '../intermediate/bip39_currency.dart'; @@ -41,25 +42,25 @@ class Ethereum extends Bip39Currency { @override String get ticker => _ticker; - int get gasLimit => 21000; + int get gasLimit => kEthereumMinGasLimit; @override bool get hasTokenSupport => true; @override NodeModel get defaultNode => NodeModel( - host: "https://eth.stackwallet.com", - port: 443, - name: DefaultNodes.defaultName, - id: DefaultNodes.buildId(this), - useSSL: true, - enabled: true, - coinName: identifier, - isFailover: true, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - ); + host: "https://eth2.stackwallet.com", + port: 443, + name: DefaultNodes.defaultName, + id: DefaultNodes.buildId(this), + useSSL: true, + enabled: true, + coinName: identifier, + isFailover: true, + isDown: false, + torEnabled: true, + clearnetEnabled: true, + ); @override // Not used for eth diff --git a/lib/wallets/models/tx_data.dart b/lib/wallets/models/tx_data.dart index 13430294b..c95fad163 100644 --- a/lib/wallets/models/tx_data.dart +++ b/lib/wallets/models/tx_data.dart @@ -7,6 +7,7 @@ import '../../models/isar/models/isar_models.dart'; import '../../models/paynym/paynym_account_lite.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../widgets/eth_fee_form.dart'; import '../isar/models/spark_coin.dart'; import 'name_op_state.dart'; @@ -14,7 +15,7 @@ typedef TxRecipient = ({String address, Amount amount, bool isChange}); class TxData { final FeeRateType? feeRateType; - final int? feeRateAmount; + final BigInt? feeRateAmount; final int? satsPerVByte; final Amount? fee; @@ -43,12 +44,11 @@ class TxData { // paynym specific final PaynymAccountLite? paynymAccountLite; - // eth token specific + // eth & token specific + final EthEIP1559Fee? ethEIP1559Fee; final web3dart.Transaction? web3dartTransaction; final int? nonce; final BigInt? chainId; - final BigInt? feeInWei; - // wownero and monero specific final lib_monero.PendingTransaction? pendingTransaction; @@ -105,10 +105,10 @@ class TxData { this.frostMSConfig, this.frostSigners, this.paynymAccountLite, + this.ethEIP1559Fee, this.web3dartTransaction, this.nonce, this.chainId, - this.feeInWei, this.pendingTransaction, this.jMintValue, this.spendCoinIndexes, @@ -211,7 +211,7 @@ class TxData { TxData copyWith({ FeeRateType? feeRateType, - int? feeRateAmount, + BigInt? feeRateAmount, int? satsPerVByte, Amount? fee, int? vSize, @@ -229,10 +229,10 @@ class TxData { List? frostSigners, String? changeAddress, PaynymAccountLite? paynymAccountLite, + EthEIP1559Fee? ethEIP1559Fee, web3dart.Transaction? web3dartTransaction, int? nonce, BigInt? chainId, - BigInt? feeInWei, lib_monero.PendingTransaction? pendingTransaction, int? jMintValue, List? spendCoinIndexes, @@ -276,10 +276,10 @@ class TxData { frostSigners: frostSigners ?? this.frostSigners, changeAddress: changeAddress ?? this.changeAddress, paynymAccountLite: paynymAccountLite ?? this.paynymAccountLite, + ethEIP1559Fee: ethEIP1559Fee ?? this.ethEIP1559Fee, web3dartTransaction: web3dartTransaction ?? this.web3dartTransaction, nonce: nonce ?? this.nonce, chainId: chainId ?? this.chainId, - feeInWei: feeInWei ?? this.feeInWei, pendingTransaction: pendingTransaction ?? this.pendingTransaction, jMintValue: jMintValue ?? this.jMintValue, spendCoinIndexes: spendCoinIndexes ?? this.spendCoinIndexes, @@ -319,10 +319,10 @@ class TxData { 'frostSigners: $frostSigners, ' 'changeAddress: $changeAddress, ' 'paynymAccountLite: $paynymAccountLite, ' + 'ethEIP1559Fee: $ethEIP1559Fee, ' 'web3dartTransaction: $web3dartTransaction, ' 'nonce: $nonce, ' 'chainId: $chainId, ' - 'feeInWei: $feeInWei, ' 'pendingTransaction: $pendingTransaction, ' 'jMintValue: $jMintValue, ' 'spendCoinIndexes: $spendCoinIndexes, ' diff --git a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart index 3beaa0e8d..2b5173b6b 100644 --- a/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_frost_wallet.dart @@ -337,7 +337,7 @@ class BitcoinFrostWallet extends Wallet } } - Future sweepAllEstimate(int feeRate) async { + Future sweepAllEstimate(BigInt feeRate) async { int available = 0; int inputCount = 0; final height = await chainHeight; @@ -367,11 +367,11 @@ class BitcoinFrostWallet extends Wallet // return vSize * (feeRatePerKB / 1000).ceil(); // } - Amount _roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount _roughFeeEstimate(int inputCount, int outputCount, BigInt feeRatePerKB) { return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); @@ -724,7 +724,7 @@ class BitcoinFrostWallet extends Wallet } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { final available = info.cachedBalance.spendable; if (available == amount) { @@ -790,15 +790,15 @@ class BitcoinFrostWallet extends Wallet fast: Amount.fromDecimal( fast, fractionDigits: cryptoCurrency.fractionDigits, - ).raw.toInt(), + ).raw, medium: Amount.fromDecimal( medium, fractionDigits: cryptoCurrency.fractionDigits, - ).raw.toInt(), + ).raw, slow: Amount.fromDecimal( slow, fractionDigits: cryptoCurrency.fractionDigits, - ).raw.toInt(), + ).raw, ); Logging.instance.i("fetched fees: $feeObject"); diff --git a/lib/wallets/wallet/impl/bitcoin_wallet.dart b/lib/wallets/wallet/impl/bitcoin_wallet.dart index 5c7f3ed75..2ce942673 100644 --- a/lib/wallets/wallet/impl/bitcoin_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_wallet.dart @@ -54,19 +54,19 @@ class BitcoinWallet extends Bip39HDWallet // =========================================================================== @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate(int inputCount, int outputCount, BigInt feeRatePerKB) { return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } // // @override diff --git a/lib/wallets/wallet/impl/bitcoincash_wallet.dart b/lib/wallets/wallet/impl/bitcoincash_wallet.dart index 666d5fd6b..cee690219 100644 --- a/lib/wallets/wallet/impl/bitcoincash_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoincash_wallet.dart @@ -33,57 +33,54 @@ class BitcoincashWallet int get isarTransactionVersion => 2; BitcoincashWallet(CryptoCurrencyNetwork network) - : super(Bitcoincash(network) as T); + : super(Bitcoincash(network) as T); @override - FilterOperation? get changeAddressFilterOperation => FilterGroup.and( - [ - ...standardChangeAddressFilters, - FilterGroup.not( - const ObjectFilter( - property: "derivationPath", - filter: FilterCondition.startsWith( - property: "value", - value: "m/44'/0'", - ), - ), - ), - ], - ); + FilterOperation? get changeAddressFilterOperation => FilterGroup.and([ + ...standardChangeAddressFilters, + FilterGroup.not( + const ObjectFilter( + property: "derivationPath", + filter: FilterCondition.startsWith( + property: "value", + value: "m/44'/0'", + ), + ), + ), + ]); @override - FilterOperation? get receivingAddressFilterOperation => FilterGroup.and( - [ - ...standardReceivingAddressFilters, - FilterGroup.not( - const ObjectFilter( - property: "derivationPath", - filter: FilterCondition.startsWith( - property: "value", - value: "m/44'/0'", - ), - ), - ), - ], - ); + FilterOperation? get receivingAddressFilterOperation => FilterGroup.and([ + ...standardReceivingAddressFilters, + FilterGroup.not( + const ObjectFilter( + property: "derivationPath", + filter: FilterCondition.startsWith( + property: "value", + value: "m/44'/0'", + ), + ), + ), + ]); // =========================================================================== @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .typeEqualTo(AddressType.nonWallet) - .and() - .group( - (q) => q - .subTypeEqualTo(AddressSubType.receiving) - .or() - .subTypeEqualTo(AddressSubType.change), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(AddressType.nonWallet) + .and() + .group( + (q) => q + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.change), + ) + .findAll(); return allAddresses; } @@ -106,20 +103,23 @@ class BitcoincashWallet final List
allAddressesOld = await fetchAddressesForElectrumXScan(); - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => convertAddressString(e.value)) + .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => convertAddressString(e.value)) + .toSet(); final allAddressesSet = {...receivingAddresses, ...changeAddresses}; - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); final List> allTransactions = []; @@ -139,8 +139,9 @@ class BitcoincashWallet ); // check for duplicates before adding to list - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -294,12 +295,8 @@ class BitcoincashWallet // only found outputs owned by this wallet type = TransactionType.incoming; } else { - Logging.instance.e( - "Unexpected tx found (ignoring it)", - ); - Logging.instance.d( - "Unexpected tx found (ignoring it): $txData", - ); + Logging.instance.e("Unexpected tx found (ignoring it)"); + Logging.instance.d("Unexpected tx found (ignoring it): $txData"); continue; } @@ -310,7 +307,8 @@ class BitcoincashWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -327,7 +325,7 @@ class BitcoincashWallet @override Future<({String? blockedReason, bool blocked, String? utxoLabel})> - checkBlockUTXO( + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -339,8 +337,9 @@ class BitcoincashWallet if (scriptPubKeyHex != null) { // check for cash tokens try { - final ctOutput = - cash_tokens.unwrap_spk(scriptPubKeyHex.toUint8ListFromHex); + final ctOutput = cash_tokens.unwrap_spk( + scriptPubKeyHex.toUint8ListFromHex, + ); if (ctOutput.token_data != null) { // found a token! blocked = true; @@ -374,19 +373,23 @@ class BitcoincashWallet // TODO: correct formula for bch? @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: info.coin.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } @override diff --git a/lib/wallets/wallet/impl/cardano_wallet.dart b/lib/wallets/wallet/impl/cardano_wallet.dart index 3beafe5f5..2cad7c514 100644 --- a/lib/wallets/wallet/impl/cardano_wallet.dart +++ b/lib/wallets/wallet/impl/cardano_wallet.dart @@ -45,15 +45,16 @@ class CardanoWallet extends Bip39Wallet { final seed = CardanoIcarusSeedGenerator(mnemonic).generate(); final cip1852 = Cip1852.fromSeed(seed, Cip1852Coins.cardanoIcarus); final derivationAccount = cip1852.purpose.coin.account(0); - final shelley = CardanoShelley.fromCip1852Object(derivationAccount) - .change(Bip44Changes.chainExt) - .addressIndex(0); + final shelley = CardanoShelley.fromCip1852Object( + derivationAccount, + ).change(Bip44Changes.chainExt).addressIndex(0); final paymentPublicKey = shelley.bip44.publicKey.compressed; final stakePublicKey = shelley.bip44Sk.publicKey.compressed; - final addressStr = ADABaseAddress.fromPublicKey( - basePubkeyBytes: paymentPublicKey, - stakePubkeyBytes: stakePublicKey, - ).address; + final addressStr = + ADABaseAddress.fromPublicKey( + basePubkeyBytes: paymentPublicKey, + stakePubkeyBytes: stakePublicKey, + ).address; return Address( walletId: walletId, value: addressStr, @@ -76,7 +77,11 @@ class CardanoWallet extends Bip39Wallet { await mainDB.updateOrPutAddresses([address]); } } catch (e, s) { - Logging.instance.e("$runtimeType checkSaveInitialReceivingAddress() failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s, + ); } } @@ -91,13 +96,17 @@ class CardanoWallet extends Bip39Wallet { return Future.value(health); } catch (e, s) { - Logging.instance.e("Error ping checking in cardano_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error ping checking in cardano_wallet.dart: ", + error: e, + stackTrace: s, + ); return Future.value(false); } } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { await updateProvider(); if (info.cachedBalance.spendable.raw == BigInt.zero) { @@ -113,10 +122,7 @@ class CardanoWallet extends Bip39Wallet { final fee = params.calculateFee(284); - return Amount( - rawValue: fee, - fractionDigits: cryptoCurrency.fractionDigits, - ); + return Amount(rawValue: fee, fractionDigits: cryptoCurrency.fractionDigits); } @override @@ -129,7 +135,7 @@ class CardanoWallet extends Bip39Wallet { ); // 284 is the size of a basic transaction with one input and two outputs (change and recipient) - final fee = params.calculateFee(284).toInt(); + final fee = params.calculateFee(284); return FeeObject( numberOfBlocksFast: 2, @@ -140,7 +146,11 @@ class CardanoWallet extends Bip39Wallet { slow: fee, ); } catch (e, s) { - Logging.instance.e("Error getting fees in cardano_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error getting fees in cardano_wallet.dart: ", + error: e, + stackTrace: s, + ); rethrow; } } @@ -181,39 +191,45 @@ class CardanoWallet extends Bip39Wallet { } final bip32 = CardanoIcarusBip32.fromSeed( - CardanoIcarusSeedGenerator(await getMnemonic()).generate()); + CardanoIcarusSeedGenerator(await getMnemonic()).generate(), + ); final spend = bip32.derivePath("1852'/1815'/0'/0/0"); final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw); // Calculate fees with example tx final exampleFee = ADAHelper.toLovelaces("0.10"); final change = TransactionOutput( - address: ADABaseAddress((await getCurrentReceivingAddress())!.value), - amount: Value(coin: totalBalance - (txData.amount!.raw))); + address: ADABaseAddress((await getCurrentReceivingAddress())!.value), + amount: Value(coin: totalBalance - (txData.amount!.raw)), + ); final body = TransactionBody( - inputs: listOfUtxosToBeUsed - .map((e) => TransactionInput( - transactionId: TransactionHash.fromHex(e.txHash), - index: e.outputIndex)) - .toList(), + inputs: + listOfUtxosToBeUsed + .map( + (e) => TransactionInput( + transactionId: TransactionHash.fromHex(e.txHash), + index: e.outputIndex, + ), + ) + .toList(), outputs: [ change, TransactionOutput( - address: ADABaseAddress(txData.recipients!.first.address), - amount: Value(coin: txData.amount!.raw - exampleFee)) + address: ADABaseAddress(txData.recipients!.first.address), + amount: Value(coin: txData.amount!.raw - exampleFee), + ), ], fee: exampleFee, ); final exampleTx = ADATransaction( body: body, witnessSet: TransactionWitnessSet( - vKeys: [ - privateKey.createSignatureWitness(body.toHash().data), - ], + vKeys: [privateKey.createSignatureWitness(body.toHash().data)], ), ); - final params = await blockfrostProvider! - .request(BlockfrostRequestLatestEpochProtocolParameters()); + final params = await blockfrostProvider!.request( + BlockfrostRequestLatestEpochProtocolParameters(), + ); final fee = params.calculateFee(exampleTx.size); // Check if we are sending all balance, which means no change and only one output for recipient. @@ -244,7 +260,8 @@ class CardanoWallet extends Bip39Wallet { if (totalBalance - (txData.amount!.raw + fee) < ADAHelper.toLovelaces("1")) { throw Exception( - "Not enough balance for change. By network rules, please either send all balance or leave at least 1 ADA change."); + "Not enough balance for change. By network rules, please either send all balance or leave at least 1 ADA change.", + ); } return txData.copyWith( @@ -255,7 +272,11 @@ class CardanoWallet extends Bip39Wallet { ); } } catch (e, s) { - Logging.instance.e("$runtimeType Cardano prepareSend failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType Cardano prepareSend failed: ", + error: e, + stackTrace: s, + ); rethrow; } } @@ -293,44 +314,51 @@ class CardanoWallet extends Bip39Wallet { } final bip32 = CardanoIcarusBip32.fromSeed( - CardanoIcarusSeedGenerator(await getMnemonic()).generate()); + CardanoIcarusSeedGenerator(await getMnemonic()).generate(), + ); final spend = bip32.derivePath("1852'/1815'/0'/0/0"); final privateKey = AdaPrivateKey.fromBytes(spend.privateKey.raw); final change = TransactionOutput( - address: ADABaseAddress((await getCurrentReceivingAddress())!.value), - amount: Value( - coin: totalUtxoAmount - (txData.amount!.raw + txData.fee!.raw))); + address: ADABaseAddress((await getCurrentReceivingAddress())!.value), + amount: Value( + coin: totalUtxoAmount - (txData.amount!.raw + txData.fee!.raw), + ), + ); List outputs = []; if (totalBalance == (txData.amount!.raw + txData.fee!.raw)) { outputs = [ TransactionOutput( - address: ADABaseAddress(txData.recipients!.first.address), - amount: Value(coin: txData.amount!.raw)) + address: ADABaseAddress(txData.recipients!.first.address), + amount: Value(coin: txData.amount!.raw), + ), ]; } else { outputs = [ change, TransactionOutput( - address: ADABaseAddress(txData.recipients!.first.address), - amount: Value(coin: txData.amount!.raw)) + address: ADABaseAddress(txData.recipients!.first.address), + amount: Value(coin: txData.amount!.raw), + ), ]; } final body = TransactionBody( - inputs: listOfUtxosToBeUsed - .map((e) => TransactionInput( - transactionId: TransactionHash.fromHex(e.txHash), - index: e.outputIndex)) - .toList(), + inputs: + listOfUtxosToBeUsed + .map( + (e) => TransactionInput( + transactionId: TransactionHash.fromHex(e.txHash), + index: e.outputIndex, + ), + ) + .toList(), outputs: outputs, fee: txData.fee!.raw, ); final tx = ADATransaction( body: body, witnessSet: TransactionWitnessSet( - vKeys: [ - privateKey.createSignatureWitness(body.toHash().data), - ], + vKeys: [privateKey.createSignatureWitness(body.toHash().data)], ), ); @@ -339,11 +367,13 @@ class CardanoWallet extends Bip39Wallet { transactionCborBytes: tx.serialize(), ), ); - return txData.copyWith( - txid: sentTx, - ); + return txData.copyWith(txid: sentTx); } catch (e, s) { - Logging.instance.e("$runtimeType Cardano confirmSend failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType Cardano confirmSend failed: ", + error: e, + stackTrace: s, + ); rethrow; } } @@ -410,7 +440,11 @@ class CardanoWallet extends Bip39Wallet { await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.e("Error getting balance in cardano_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error getting balance in cardano_wallet.dart: ", + error: e, + stackTrace: s, + ); } } @@ -428,7 +462,11 @@ class CardanoWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.e("Error updating transactions in cardano_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error updating transactions in cardano_wallet.dart: ", + error: e, + stackTrace: s, + ); } } @@ -446,14 +484,13 @@ class CardanoWallet extends Bip39Wallet { final txsList = await blockfrostProvider!.request( BlockfrostRequestAddressTransactions( - ADAAddress.fromAddress( - currentAddr, - ), + ADAAddress.fromAddress(currentAddr), ), ); - final parsedTxsList = - List>.empty(growable: true); + final parsedTxsList = List>.empty( + growable: true, + ); for (final tx in txsList) { final txInfo = await blockfrostProvider!.request( @@ -525,10 +562,11 @@ class CardanoWallet extends Bip39Wallet { type: txType, subType: isar.TransactionSubType.none, amount: amount, - amountString: Amount( - rawValue: BigInt.from(amount), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + amountString: + Amount( + rawValue: BigInt.from(amount), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), fee: int.parse(txInfo.fees), height: txInfo.blockHeight, isCancelled: false, @@ -548,9 +586,10 @@ class CardanoWallet extends Bip39Wallet { derivationIndex: 0, derivationPath: DerivationPath()..value = _addressDerivationPath, type: AddressType.cardanoShelley, - subType: txType == isar.TransactionType.outgoing - ? AddressSubType.unknown - : AddressSubType.receiving, + subType: + txType == isar.TransactionType.outgoing + ? AddressSubType.unknown + : AddressSubType.receiving, ); parsedTxsList.add(Tuple2(transaction, txAddress)); @@ -560,7 +599,11 @@ class CardanoWallet extends Bip39Wallet { } on NodeTorMismatchConfigException { rethrow; } catch (e, s) { - Logging.instance.e("Error updating transactions in cardano_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error updating transactions in cardano_wallet.dart: ", + error: e, + stackTrace: s, + ); } } @@ -576,10 +619,7 @@ class CardanoWallet extends Bip39Wallet { final client = HttpClient(); if (prefs.useTor) { final proxyInfo = TorService.sharedInstance.getProxyInfo(); - final proxySettings = ProxySettings( - proxyInfo.host, - proxyInfo.port, - ); + final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port); SocksTCPClient.assignToHttpClient(client, [proxySettings]); } blockfrostProvider = CustomBlockForestProvider( diff --git a/lib/wallets/wallet/impl/dash_wallet.dart b/lib/wallets/wallet/impl/dash_wallet.dart index 59fdaa037..f9a90d5b8 100644 --- a/lib/wallets/wallet/impl/dash_wallet.dart +++ b/lib/wallets/wallet/impl/dash_wallet.dart @@ -36,17 +36,18 @@ class DashWallet extends Bip39HDWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } @@ -59,30 +60,34 @@ class DashWallet extends Bip39HDWallet await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { // Check for duplicates by searching for tx by tx_hash in db. - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -95,8 +100,9 @@ class DashWallet extends Bip39HDWallet ); // Only tx to list once. - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -246,7 +252,8 @@ class DashWallet extends Bip39HDWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -263,7 +270,7 @@ class DashWallet extends Bip39HDWallet @override Future<({String? blockedReason, bool blocked, String? utxoLabel})> - checkBlockUTXO( + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -296,18 +303,22 @@ class DashWallet extends Bip39HDWallet } @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } } diff --git a/lib/wallets/wallet/impl/dogecoin_wallet.dart b/lib/wallets/wallet/impl/dogecoin_wallet.dart index e9046f959..da24c5a9d 100644 --- a/lib/wallets/wallet/impl/dogecoin_wallet.dart +++ b/lib/wallets/wallet/impl/dogecoin_wallet.dart @@ -38,17 +38,18 @@ class DogecoinWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } @@ -61,30 +62,34 @@ class DogecoinWallet await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { // Check for duplicates by searching for tx by tx_hash in db. - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -97,8 +102,9 @@ class DogecoinWallet ); // Only tx to list once. - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -249,7 +255,8 @@ class DogecoinWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -266,7 +273,7 @@ class DogecoinWallet @override Future<({String? blockedReason, bool blocked, String? utxoLabel})> - checkBlockUTXO( + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -287,7 +294,8 @@ class DogecoinWallet // https://en.bitcoin.it/wiki/BIP_0047#Sending if (bytes.length == 80 && bytes.first == 1) { blocked = true; - blockedReason = "Paynym notification output. Incautious " + blockedReason = + "Paynym notification output. Incautious " "handling of outputs from notification transactions " "may cause unintended loss of privacy."; break; @@ -299,18 +307,22 @@ class DogecoinWallet } @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } } diff --git a/lib/wallets/wallet/impl/ecash_wallet.dart b/lib/wallets/wallet/impl/ecash_wallet.dart index a8741ba21..aacdc4f31 100644 --- a/lib/wallets/wallet/impl/ecash_wallet.dart +++ b/lib/wallets/wallet/impl/ecash_wallet.dart @@ -34,46 +34,37 @@ class EcashWallet extends Bip39HDWallet EcashWallet(CryptoCurrencyNetwork network) : super(Ecash(network) as T); @override - FilterOperation? get changeAddressFilterOperation => FilterGroup.and( - [ - ...standardChangeAddressFilters, - const ObjectFilter( - property: "derivationPath", - filter: FilterCondition.startsWith( - property: "value", - value: "m/44'/899", - ), - ), - ], - ); + FilterOperation? get changeAddressFilterOperation => FilterGroup.and([ + ...standardChangeAddressFilters, + const ObjectFilter( + property: "derivationPath", + filter: FilterCondition.startsWith(property: "value", value: "m/44'/899"), + ), + ]); @override - FilterOperation? get receivingAddressFilterOperation => FilterGroup.and( - [ - ...standardReceivingAddressFilters, - const ObjectFilter( - property: "derivationPath", - filter: FilterCondition.startsWith( - property: "value", - value: "m/44'/899", - ), - ), - ], - ); + FilterOperation? get receivingAddressFilterOperation => FilterGroup.and([ + ...standardReceivingAddressFilters, + const ObjectFilter( + property: "derivationPath", + filter: FilterCondition.startsWith(property: "value", value: "m/44'/899"), + ), + ]); // =========================================================================== @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .typeEqualTo(AddressType.nonWallet) - .and() - .not() - .subTypeEqualTo(AddressSubType.nonWallet) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(AddressType.nonWallet) + .and() + .not() + .subTypeEqualTo(AddressSubType.nonWallet) + .findAll(); return allAddresses; } @@ -96,28 +87,32 @@ class EcashWallet extends Bip39HDWallet final List
allAddressesOld = await fetchAddressesForElectrumXScan(); - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => convertAddressString(e.value)) + .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => convertAddressString(e.value)) + .toSet(); final allAddressesSet = {...receivingAddresses, ...changeAddresses}; - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); final List> allTransactions = []; for (final txHash in allTxHashes) { - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -129,8 +124,9 @@ class EcashWallet extends Bip39HDWallet ); // check for duplicates before adding to list - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -288,7 +284,8 @@ class EcashWallet extends Bip39HDWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -304,12 +301,8 @@ class EcashWallet extends Bip39HDWallet } @override - Future< - ({ - String? blockedReason, - bool blocked, - String? utxoLabel, - })> checkBlockUTXO( + Future<({String? blockedReason, bool blocked, String? utxoLabel})> + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -321,8 +314,9 @@ class EcashWallet extends Bip39HDWallet if (scriptPubKeyHex != null) { // check for cash tokens try { - final ctOutput = - cash_tokens.unwrap_spk(scriptPubKeyHex.toUint8ListFromHex); + final ctOutput = cash_tokens.unwrap_spk( + scriptPubKeyHex.toUint8ListFromHex, + ); if (ctOutput.token_data != null) { // found a token! blocked = true; @@ -350,19 +344,23 @@ class EcashWallet extends Bip39HDWallet // TODO: correct formula for ecash? @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: info.coin.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } @override diff --git a/lib/wallets/wallet/impl/epiccash_wallet.dart b/lib/wallets/wallet/impl/epiccash_wallet.dart index 10238cef6..215f41917 100644 --- a/lib/wallets/wallet/impl/epiccash_wallet.dart +++ b/lib/wallets/wallet/impl/epiccash_wallet.dart @@ -53,15 +53,17 @@ class EpiccashWallet extends Bip39Wallet { final int lastScannedBlock = info.epicData?.lastScannedBlock ?? 0; final _chainHeight = await chainHeight; final double restorePercent = lastScannedBlock / _chainHeight; - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercent, walletId)); + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent(highestPercent, walletId), + ); if (restorePercent > highestPercent) { highestPercent = restorePercent; } final int blocksRemaining = _chainHeight - lastScannedBlock; - GlobalEventBus.instance - .fire(BlocksRemainingEvent(blocksRemaining, walletId)); + GlobalEventBus.instance.fire( + BlocksRemainingEvent(blocksRemaining, walletId), + ); return restorePercent < 0 ? 0.0 : restorePercent; } @@ -84,17 +86,14 @@ class EpiccashWallet extends Bip39Wallet { Future cancelPendingTransactionAndPost(String txSlateId) async { try { _hackedCheckTorNodePrefs(); - final String wallet = (await secureStorageInterface.read( - key: '${walletId}_wallet', - ))!; + final String wallet = + (await secureStorageInterface.read(key: '${walletId}_wallet'))!; final result = await epiccash.LibEpiccash.cancelTransaction( wallet: wallet, transactionId: txSlateId, ); - Logging.instance.d( - "cancel $txSlateId result: $result", - ); + Logging.instance.d("cancel $txSlateId result: $result"); return result; } catch (e, s) { Logging.instance.e("", error: e, stackTrace: s); @@ -154,8 +153,10 @@ class EpiccashWallet extends Bip39Wallet { config["chain"] = "mainnet"; config["account"] = "default"; config["api_listen_port"] = port; - config["api_listen_interface"] = - nodeApiAddress.replaceFirst(uri.scheme, ""); + config["api_listen_interface"] = nodeApiAddress.replaceFirst( + uri.scheme, + "", + ); final String stringConfig = jsonEncode(config); return stringConfig; } @@ -219,12 +220,14 @@ class EpiccashWallet extends Bip39Wallet { } Future< - ({ - double awaitingFinalization, - double pending, - double spendable, - double total - })> _allWalletBalances() async { + ({ + double awaitingFinalization, + double pending, + double spendable, + double total, + }) + > + _allWalletBalances() async { _hackedCheckTorNodePrefs(); final wallet = await secureStorageInterface.read(key: '${walletId}_wallet'); const refreshFromNode = 0; @@ -243,9 +246,7 @@ class EpiccashWallet extends Bip39Wallet { try { final uri = Uri.parse('wss://$host:$port'); - channel = WebSocketChannel.connect( - uri, - ); + channel = WebSocketChannel.connect(uri); await channel.ready; @@ -280,9 +281,7 @@ class EpiccashWallet extends Bip39Wallet { "to": to, }; await info.updateExtraEpiccashWalletInfo( - epicData: info.epicData!.copyWith( - slatesToCommits: slatesToCommits, - ), + epicData: info.epicData!.copyWith(slatesToCommits: slatesToCommits), isar: mainDB.isar, ); return true; @@ -305,9 +304,7 @@ class EpiccashWallet extends Bip39Wallet { } /// Only index 0 is currently used in stack wallet. - Future
_generateAndStoreReceivingAddressForIndex( - int index, - ) async { + Future
_generateAndStoreReceivingAddressForIndex(int index) async { // Since only 0 is a valid index in stack wallet at this time, lets just // throw is not zero if (index != 0) { @@ -338,9 +335,7 @@ class EpiccashWallet extends Bip39Wallet { epicboxConfig: epicboxConfig.toString(), ); - Logging.instance.d( - "WALLET_ADDRESS_IS $walletAddress", - ); + Logging.instance.d("WALLET_ADDRESS_IS $walletAddress"); final address = Address( walletId: walletId, @@ -359,8 +354,9 @@ class EpiccashWallet extends Bip39Wallet { try { //First stop the current listener epiccash.LibEpiccash.stopEpicboxListener(); - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); // max number of blocks to scan per loop iteration const scanChunkSize = 10000; @@ -387,9 +383,7 @@ class EpiccashWallet extends Bip39Wallet { // update local cache await info.updateExtraEpiccashWalletInfo( - epicData: info.epicData!.copyWith( - lastScannedBlock: nextScannedBlock, - ), + epicData: info.epicData!.copyWith(lastScannedBlock: nextScannedBlock), isar: mainDB.isar, ); @@ -470,8 +464,9 @@ class EpiccashWallet extends Bip39Wallet { @override Future init({bool? isRestore}) async { if (isRestore != true) { - String? encodedWallet = - await secureStorageInterface.read(key: "${walletId}_wallet"); + String? encodedWallet = await secureStorageInterface.read( + key: "${walletId}_wallet", + ); // check if should create a new wallet if (encodedWallet == null) { @@ -543,8 +538,9 @@ class EpiccashWallet extends Bip39Wallet { ); final config = await _getRealConfig(); - final password = - await secureStorageInterface.read(key: '${walletId}_password'); + final password = await secureStorageInterface.read( + key: '${walletId}_password', + ); final walletOpen = await epiccash.LibEpiccash.openWallet( config: config, @@ -558,8 +554,11 @@ class EpiccashWallet extends Bip39Wallet { await updateNode(); } catch (e, s) { // do nothing, still allow user into wallet - Logging.instance - .w("$runtimeType init() failed: ", error: e, stackTrace: s); + Logging.instance.w( + "$runtimeType init() failed: ", + error: e, + stackTrace: s, + ); } } } @@ -571,8 +570,9 @@ class EpiccashWallet extends Bip39Wallet { Future confirmSend({required TxData txData}) async { try { _hackedCheckTorNodePrefs(); - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); final EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // TODO determine whether it is worth sending change to a change address. @@ -581,9 +581,7 @@ class EpiccashWallet extends Bip39Wallet { if (!receiverAddress.startsWith("http://") || !receiverAddress.startsWith("https://")) { - final bool isEpicboxConnected = await _testEpicboxServer( - epicboxConfig, - ); + final bool isEpicboxConnected = await _testEpicboxServer(epicboxConfig); if (!isEpicboxConnected) { throw Exception("Failed to send TX : Unable to reach epicbox server"); } @@ -618,9 +616,7 @@ class EpiccashWallet extends Bip39Wallet { txAddressInfo['to'] = txData.recipients!.first.address; await _putSendToAddresses(transaction, txAddressInfo); - return txData.copyWith( - txid: transaction.slateId, - ); + return txData.copyWith(txid: transaction.slateId); } catch (e, s) { Logging.instance.e("Epic cash confirmSend: ", error: e, stackTrace: s); rethrow; @@ -658,10 +654,7 @@ class EpiccashWallet extends Bip39Wallet { ); } - return txData.copyWith( - recipients: [recipient], - fee: feeAmount, - ); + return txData.copyWith(recipients: [recipient], fee: feeAmount); } catch (e, s) { Logging.instance.e("Epic cash prepareSend", error: e, stackTrace: s); rethrow; @@ -898,10 +891,7 @@ class EpiccashWallet extends Bip39Wallet { ), ); - await info.updateBalance( - newBalance: balance, - isar: mainDB.isar, - ); + await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { Logging.instance.w( "Epic cash wallet failed to update balance: ", @@ -915,20 +905,22 @@ class EpiccashWallet extends Bip39Wallet { Future updateTransactions() async { try { _hackedCheckTorNodePrefs(); - final wallet = - await secureStorageInterface.read(key: '${walletId}_wallet'); + final wallet = await secureStorageInterface.read( + key: '${walletId}_wallet', + ); const refreshFromNode = 1; - final myAddresses = await mainDB - .getAddresses(walletId) - .filter() - .typeEqualTo(AddressType.mimbleWimble) - .and() - .subTypeEqualTo(AddressSubType.receiving) - .and() - .valueIsNotEmpty() - .valueProperty() - .findAll(); + final myAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .typeEqualTo(AddressType.mimbleWimble) + .and() + .subTypeEqualTo(AddressSubType.receiving) + .and() + .valueIsNotEmpty() + .valueProperty() + .findAll(); final myAddressesSet = myAddresses.toSet(); final transactions = await epiccash.LibEpiccash.getTransactions( @@ -943,7 +935,7 @@ class EpiccashWallet extends Bip39Wallet { for (final tx in transactions) { final isIncoming = tx.txType == epic_models.TransactionType.TxReceived || - tx.txType == epic_models.TransactionType.TxReceivedCancelled; + tx.txType == epic_models.TransactionType.TxReceivedCancelled; final slateId = tx.txSlateId; final commitId = slatesToCommits[slateId]?['commitId'] as String?; final numberOfMessages = tx.messages?.messages.length; @@ -964,9 +956,7 @@ class EpiccashWallet extends Bip39Wallet { OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: credit.toString(), - addresses: [ - if (addressFrom != null) addressFrom, - ], + addresses: [if (addressFrom != null) addressFrom], walletOwns: true, ); final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( @@ -1010,11 +1000,12 @@ class EpiccashWallet extends Bip39Wallet { "onChainNote": onChainNote, "isCancelled": tx.txType == epic_models.TransactionType.TxSentCancelled || - tx.txType == epic_models.TransactionType.TxReceivedCancelled, - "overrideFee": Amount( - rawValue: BigInt.from(fee), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + tx.txType == epic_models.TransactionType.TxReceivedCancelled, + "overrideFee": + Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), }; final txn = TransactionV2( @@ -1092,11 +1083,7 @@ class EpiccashWallet extends Bip39Wallet { ) != null; } catch (e, s) { - Logging.instance.e( - "", - error: e, - stackTrace: s, - ); + Logging.instance.e("", error: e, stackTrace: s); return false; } } @@ -1105,8 +1092,9 @@ class EpiccashWallet extends Bip39Wallet { Future updateChainHeight() async { _hackedCheckTorNodePrefs(); final config = await _getRealConfig(); - final latestHeight = - await epiccash.LibEpiccash.getChainHeight(config: config); + final latestHeight = await epiccash.LibEpiccash.getChainHeight( + config: config, + ); await info.updateCachedChainHeight( newHeight: latestHeight, isar: mainDB.isar, @@ -1114,7 +1102,7 @@ class EpiccashWallet extends Bip39Wallet { } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { _hackedCheckTorNodePrefs(); // setting ifErrorEstimateFee doesn't do anything as its not used in the nativeFee function????? final int currentFee = await _nativeFee( @@ -1135,9 +1123,9 @@ class EpiccashWallet extends Bip39Wallet { numberOfBlocksFast: 10, numberOfBlocksAverage: 10, numberOfBlocksSlow: 10, - fast: 1, - medium: 1, - slow: 1, + fast: BigInt.one, + medium: BigInt.one, + slow: BigInt.one, ); } @@ -1202,10 +1190,7 @@ Future deleteEpicWallet({ return "Tried to delete non existent epic wallet file with walletId=$walletId"; } else { try { - return epiccash.LibEpiccash.deleteWallet( - wallet: wallet, - config: config!, - ); + return epiccash.LibEpiccash.deleteWallet(wallet: wallet, config: config!); } catch (e, s) { Logging.instance.e("$e\n$s", error: e, stackTrace: s); return "deleteEpicWallet($walletId) failed..."; diff --git a/lib/wallets/wallet/impl/ethereum_wallet.dart b/lib/wallets/wallet/impl/ethereum_wallet.dart index e61da1df6..1bb5ec7c7 100644 --- a/lib/wallets/wallet/impl/ethereum_wallet.dart +++ b/lib/wallets/wallet/impl/ethereum_wallet.dart @@ -7,6 +7,7 @@ import 'package:http/http.dart'; import 'package:isar/isar.dart'; import 'package:web3dart/web3dart.dart' as web3; +import '../../../dto/ethereum/eth_tx_dto.dart'; import '../../../models/balance.dart'; import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/transaction.dart'; @@ -57,9 +58,10 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { return web3.Web3Client(node.host, client); } - Amount estimateEthFee(int feeRate, int gasLimit, int decimals) { + Amount estimateEthFee(BigInt feeRate, int gasLimit, int decimals) { final gweiAmount = feeRate.toDecimal() / (Decimal.ten.pow(9).toDecimal()); - final fee = gasLimit.toDecimal() * + final fee = + gasLimit.toDecimal() * gweiAmount.toDecimal( scaleOnInfinitePrecision: cryptoCurrency.fractionDigits, ); @@ -95,9 +97,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { final OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: amount.raw.toString(), - addresses: [ - addressTo, - ], + addresses: [addressTo], walletOwns: addressTo == myAddress, ); final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( @@ -132,16 +132,15 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), version: -1, - type: addressTo == myAddress - ? TransactionType.sentToSelf - : TransactionType.outgoing, + type: + addressTo == myAddress + ? TransactionType.sentToSelf + : TransactionType.outgoing, subType: TransactionSubType.none, otherData: jsonEncode(otherData), ); - return txData.copyWith( - tempTx: txn, - ); + return txData.copyWith(tempTx: txn); } // ==================== Overrides ============================================ @@ -151,11 +150,11 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { @override FilterOperation? get transactionFilterOperation => FilterGroup.not( - const FilterCondition.equalTo( - property: r"subType", - value: TransactionSubType.ethToken, - ), - ); + const FilterCondition.equalTo( + property: r"subType", + value: TransactionSubType.ethToken, + ), + ); @override FilterOperation? get changeAddressFilterOperation => @@ -189,7 +188,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { return estimateEthFee( feeRate, (cryptoCurrency as Ethereum).gasLimit, @@ -198,7 +197,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { } @override - Future get fees => EthereumAPI.getFees(); + Future get fees => EthereumAPI.getFees(); @override Future pingCheck() async { @@ -235,10 +234,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { fractionDigits: cryptoCurrency.fractionDigits, ), ); - await info.updateBalance( - newBalance: balance, - isar: mainDB.isar, - ); + await info.updateBalance(newBalance: balance, isar: mainDB.isar); } catch (e, s) { Logging.instance.w( "$runtimeType wallet failed to update balance: ", @@ -254,10 +250,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { final client = getEthClient(); final height = await client.getBlockNumber(); - await info.updateCachedChainHeight( - newHeight: height, - isar: mainDB.isar, - ); + await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } catch (e, s) { Logging.instance.w( "$runtimeType Exception caught in chainHeight: ", @@ -279,7 +272,8 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { int firstBlock = 0; if (!isRescan) { - firstBlock = await mainDB.isar.transactionV2s + firstBlock = + await mainDB.isar.transactionV2s .where() .walletIdEqualTo(walletId) .heightProperty() @@ -300,8 +294,8 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { if (response.value == null) { Logging.instance.w( - "Failed to refresh transactions for ${cryptoCurrency.prettyName} ${info.name} " - "$walletId: ${response.exception}", + "Failed to refresh transactions for ${cryptoCurrency.prettyName}" + " ${info.name} $walletId: ${response.exception}", ); return; } @@ -311,108 +305,114 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { return; } - final txsResponse = - await EthereumAPI.getEthTransactionNonces(response.value!); + web3.Web3Client? client; + final List allTxs = []; + for (final dto in response.value!) { + if (dto.nonce == null) { + client ??= getEthClient(); + final txInfo = await client.getTransactionByHash(dto.hash); + if (txInfo == null) { + // Something strange is happening + Logging.instance.w( + "Could not find transaction via RPC that was found use TrueBlocks " + "API.\nOffending tx: $dto", + ); + } else { + final updated = dto.copyWith(nonce: txInfo.nonce); + allTxs.add(updated); + } + } else { + allTxs.add(dto); + } + } - if (txsResponse.value != null) { - final allTxs = txsResponse.value!; - final List txns = []; - for (final tuple in allTxs) { - final element = tuple.item1; + final List txns = []; + for (final element in allTxs) { + if (element.hasToken && !element.isError) { + continue; + } - if (element.hasToken && !element.isError) { - continue; + //Calculate fees (GasLimit * gasPrice) + // int txFee = element.gasPrice * element.gasUsed; + final Amount txFee = element.gasCost; + final transactionAmount = element.value; + final addressFrom = checksumEthereumAddress(element.from); + final addressTo = checksumEthereumAddress(element.to); + + bool isIncoming; + bool txFailed = false; + if (addressFrom == thisAddress) { + if (element.isError) { + txFailed = true; } + isIncoming = false; + } else if (addressTo == thisAddress) { + isIncoming = true; + } else { + continue; + } - //Calculate fees (GasLimit * gasPrice) - // int txFee = element.gasPrice * element.gasUsed; - final Amount txFee = element.gasCost; - final transactionAmount = element.value; - final addressFrom = checksumEthereumAddress(element.from); - final addressTo = checksumEthereumAddress(element.to); - - bool isIncoming; - bool txFailed = false; - if (addressFrom == thisAddress) { - if (element.isError) { - txFailed = true; - } - isIncoming = false; - } else if (addressTo == thisAddress) { - isIncoming = true; - } else { - continue; - } + // hack eth tx data into inputs and outputs + final List outputs = []; + final List inputs = []; - // hack eth tx data into inputs and outputs - final List outputs = []; - final List inputs = []; - - final OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( - scriptPubKeyHex: "00", - valueStringSats: transactionAmount.raw.toString(), - addresses: [ - addressTo, - ], - walletOwns: addressTo == thisAddress, - ); - final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( - scriptSigHex: null, - scriptSigAsm: null, - sequence: null, - outpoint: null, - addresses: [addressFrom], - valueStringSats: transactionAmount.raw.toString(), - witness: null, - innerRedeemScriptAsm: null, - coinbase: null, - walletOwns: addressFrom == thisAddress, - ); + final OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: transactionAmount.raw.toString(), + addresses: [addressTo], + walletOwns: addressTo == thisAddress, + ); + final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + scriptSigAsm: null, + sequence: null, + outpoint: null, + addresses: [addressFrom], + valueStringSats: transactionAmount.raw.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: addressFrom == thisAddress, + ); - final TransactionType txType; - if (isIncoming) { - if (addressFrom == addressTo) { - txType = TransactionType.sentToSelf; - } else { - txType = TransactionType.incoming; - } + final TransactionType txType; + if (isIncoming) { + if (addressFrom == addressTo) { + txType = TransactionType.sentToSelf; } else { - txType = TransactionType.outgoing; + txType = TransactionType.incoming; } + } else { + txType = TransactionType.outgoing; + } - outputs.add(output); - inputs.add(input); - - final otherData = { - "nonce": tuple.item2, - "isCancelled": txFailed, - "overrideFee": txFee.toJsonString(), - }; - - final txn = TransactionV2( - walletId: walletId, - blockHash: element.blockHash, - hash: element.hash, - txid: element.hash, - timestamp: element.timestamp, - height: element.blockNumber, - inputs: List.unmodifiable(inputs), - outputs: List.unmodifiable(outputs), - version: -1, - type: txType, - subType: TransactionSubType.none, - otherData: jsonEncode(otherData), - ); + outputs.add(output); + inputs.add(input); - txns.add(txn); - } - await mainDB.updateOrPutTransactionV2s(txns); - } else { - Logging.instance.w( - "Failed to refresh transactions with nonces for ${cryptoCurrency.prettyName} " - "${info.name} $walletId: ${txsResponse.exception}", + final otherData = { + "nonce": element.nonce, + "isCancelled": txFailed, + "overrideFee": txFee.toJsonString(), + }; + + final txn = TransactionV2( + walletId: walletId, + blockHash: element.blockHash, + hash: element.hash, + txid: element.hash, + timestamp: element.timestamp, + height: element.blockNumber, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: -1, + type: txType, + subType: TransactionSubType.none, + otherData: jsonEncode(otherData), ); + + txns.add(txn); } + await mainDB.updateOrPutTransactionV2s(txns); } @override @@ -421,88 +421,128 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { return false; } - @override - Future prepareSend({required TxData txData}) async { - final int - rate; // TODO: use BigInt for feeObject whenever FeeObject gets redone + Future getMyWeb3Address() async { + final myAddress = (await getCurrentReceivingAddress())!.value; + final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); + return myWeb3Address; + } + + Future< + ({ + int nonce, + BigInt chainId, + BigInt baseFee, + BigInt maxBaseFee, + BigInt priorityFee, + }) + > + internalSharedPrepareSend({ + required TxData txData, + required web3.EthereumAddress myWeb3Address, + }) async { + if (txData.feeRateType == null) throw Exception("Missing fee rate type."); + if (txData.feeRateType == FeeRateType.custom && + txData.ethEIP1559Fee == null) { + throw Exception("Missing custom EIP-1559 values."); + } + + await updateBalance(); + + final client = getEthClient(); + final chainId = await client.getChainId(); + final nonce = + txData.nonce ?? + await client.getTransactionCount( + myWeb3Address, + atBlock: const web3.BlockNum.pending(), + ); + final feeObject = await fees; + final baseFee = feeObject.suggestBaseFee; + BigInt maxBaseFee = baseFee; + BigInt priorityFee; + switch (txData.feeRateType!) { case FeeRateType.fast: - rate = feeObject.fast; + priorityFee = feeObject.fast - baseFee; + if (priorityFee.isNegative) priorityFee = BigInt.zero; break; + case FeeRateType.average: - rate = feeObject.medium; + priorityFee = feeObject.medium - baseFee; + if (priorityFee.isNegative) priorityFee = BigInt.zero; break; + case FeeRateType.slow: - rate = feeObject.slow; + priorityFee = feeObject.slow - baseFee; + if (priorityFee.isNegative) priorityFee = BigInt.zero; break; + case FeeRateType.custom: - throw UnimplementedError("custom eth fees"); + priorityFee = txData.ethEIP1559Fee!.priorityFeeWei; + maxBaseFee = txData.ethEIP1559Fee!.maxBaseFeeWei; + break; } - final feeEstimate = await estimateFeeFor(Amount.zero, rate); - - // bool isSendAll = false; - // final availableBalance = balance.spendable; - // if (satoshiAmount == availableBalance) { - // isSendAll = true; - // } - // - // if (isSendAll) { - // //Subtract fee amount from send amount - // satoshiAmount -= feeEstimate; - // } - - final client = getEthClient(); + if (baseFee > maxBaseFee) { + throw Exception("Base cannot be greater than max base fee"); + } + if (priorityFee > maxBaseFee) { + throw Exception("Priority fee cannot be greater than max base fee"); + } - final myAddress = (await getCurrentReceivingAddress())!.value; - final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); + return ( + nonce: nonce, + chainId: chainId, + baseFee: baseFee, + maxBaseFee: maxBaseFee, + priorityFee: priorityFee, + ); + } + @override + Future prepareSend({required TxData txData}) async { final amount = txData.recipients!.first.amount; final address = txData.recipients!.first.address; - // final est = await client.estimateGas( - // sender: myWeb3Address, - // to: web3.EthereumAddress.fromHex(address), - // gasPrice: web3.EtherAmount.fromUnitAndValue( - // web3.EtherUnit.wei, - // rate, - // ), - // amountOfGas: BigInt.from((cryptoCurrency as Ethereum).gasLimit), - // value: web3.EtherAmount.inWei(amount.raw), - // ); - - final nonce = txData.nonce ?? - await client.getTransactionCount( - myWeb3Address, - atBlock: const web3.BlockNum.pending(), - ); + final myWeb3Address = await getMyWeb3Address(); - // final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); - // print("=============================================================="); - // print("ETH client.estimateGas: $est"); - // print("ETH estimateFeeFor : $feeEstimate"); - // print("ETH nonce custom response: $nResponse"); - // print("ETH actual nonce : $nonce"); - // print("=============================================================="); + final prep = await internalSharedPrepareSend( + txData: txData, + myWeb3Address: myWeb3Address, + ); + + // double check balance after internalSharedPrepareSend call to ensure + // balance is up to date + if (amount > info.cachedBalance.spendable) { + throw Exception("Insufficient balance"); + } final tx = web3.Transaction( to: web3.EthereumAddress.fromHex(address), - gasPrice: web3.EtherAmount.fromUnitAndValue( + maxGas: txData.ethEIP1559Fee?.gasLimit ?? kEthereumMinGasLimit, + value: web3.EtherAmount.inWei(amount.raw), + nonce: prep.nonce, + maxFeePerGas: web3.EtherAmount.fromBigInt( web3.EtherUnit.wei, - rate, + prep.maxBaseFee, ), - maxGas: (cryptoCurrency as Ethereum).gasLimit, - value: web3.EtherAmount.inWei(amount.raw), - nonce: nonce, + maxPriorityFeePerGas: web3.EtherAmount.fromBigInt( + web3.EtherUnit.wei, + prep.priorityFee, + ), + ); + + final feeEstimate = await estimateFeeFor( + Amount.zero, + prep.maxBaseFee + prep.priorityFee, ); return txData.copyWith( nonce: tx.nonce, web3dartTransaction: tx, fee: feeEstimate, - feeInWei: BigInt.from(rate), - chainId: (await client.getChainId()), + chainId: prep.chainId, ); } @@ -523,10 +563,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { ); final data = (prepareTempTx ?? _prepareTempTx)( - txData.copyWith( - txid: txid, - txHash: txid, - ), + txData.copyWith(txid: txid, txHash: txid), (await getCurrentReceivingAddress())!.value, ); diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index aed72a33a..75d7290a9 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -60,9 +60,7 @@ class FiroWallet extends Bip39HDWallet if (txData.tempTx != null) { await mainDB.updateOrPutTransactionV2s([txData.tempTx!]); _unconfirmedTxids.add(txData.tempTx!.txid); - Logging.instance.d( - "Added firo unconfirmed: ${txData.tempTx!.txid}", - ); + Logging.instance.d("Added firo unconfirmed: ${txData.tempTx!.txid}"); } return txData; } @@ -72,36 +70,41 @@ class FiroWallet extends Bip39HDWallet final List
allAddressesOld = await fetchAddressesForElectrumXScan(); - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => convertAddressString(e.value)) + .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => convertAddressString(e.value)) - .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => convertAddressString(e.value)) + .toSet(); final allAddressesSet = {...receivingAddresses, ...changeAddresses}; - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); - final sparkCoins = await mainDB.isar.sparkCoins - .where() - .walletIdEqualToAnyLTagHash(walletId) - .findAll(); + final sparkCoins = + await mainDB.isar.sparkCoins + .where() + .walletIdEqualToAnyLTagHash(walletId) + .findAll(); final List> allTransactions = []; // some lelantus transactions aren't fetched via wallet addresses so they // will never show as confirmed in the gui. - final unconfirmedTransactions = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .heightIsNull() - .findAll(); + final unconfirmedTransactions = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .heightIsNull() + .findAll(); for (final tx in unconfirmedTransactions) { final txn = await electrumXCachedClient.getTransaction( txHash: tx.txid, @@ -113,10 +116,7 @@ class FiroWallet extends Bip39HDWallet if (height != null) { // tx was mined // add to allTxHashes - final info = { - "tx_hash": tx.txid, - "height": height, - }; + final info = {"tx_hash": tx.txid, "height": height}; allTxHashes.add(info); } } @@ -126,10 +126,7 @@ class FiroWallet extends Bip39HDWallet sparkTxids.add(coin.txHash); // check for duplicates before adding to list if (allTxHashes.indexWhere((e) => e["tx_hash"] == coin.txHash) == -1) { - final info = { - "tx_hash": coin.txHash, - "height": coin.height, - }; + final info = {"tx_hash": coin.txHash, "height": coin.height}; allTxHashes.add(info); } } @@ -138,9 +135,7 @@ class FiroWallet extends Bip39HDWallet for (final txid in missing.map((e) => e.txid).toSet()) { // check for duplicates before adding to list if (allTxHashes.indexWhere((e) => e["tx_hash"] == txid) == -1) { - final info = { - "tx_hash": txid, - }; + final info = {"tx_hash": txid}; allTxHashes.add(info); } } @@ -148,12 +143,13 @@ class FiroWallet extends Bip39HDWallet final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final storedTx = await mainDB.isar.transactionV2s - .where() - .walletIdEqualTo(walletId) - .filter() - .txidEqualTo(txHash["tx_hash"] as String) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); if (storedTx?.isConfirmed( currentHeight, @@ -180,8 +176,9 @@ class FiroWallet extends Bip39HDWallet } // check for duplicates before adding to list - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] ??= txHash["height"]; allTransactions.add(tx); @@ -290,17 +287,19 @@ class FiroWallet extends Bip39HDWallet if (output.addresses.isEmpty && output.scriptPubKeyHex.length >= 488) { // likely spark related - final opByte = output.scriptPubKeyHex - .substring(0, 2) - .toUint8ListFromHex - .first; + final opByte = + output.scriptPubKeyHex + .substring(0, 2) + .toUint8ListFromHex + .first; if (opByte == OP_SPARKMINT || opByte == OP_SPARKSMINT) { final serCoin = base64Encode( output.scriptPubKeyHex.substring(2, 488).toUint8ListFromHex, ); - final coin = sparkCoinsInvolvedReceived - .where((e) => e.serializedCoinB64!.startsWith(serCoin)) - .firstOrNull; + final coin = + sparkCoinsInvolvedReceived + .where((e) => e.serializedCoinB64!.startsWith(serCoin)) + .firstOrNull; if (coin == null) { // not ours @@ -308,9 +307,7 @@ class FiroWallet extends Bip39HDWallet output = output.copyWith( walletOwns: true, valueStringSats: coin.value.toString(), - addresses: [ - coin.address, - ], + addresses: [coin.address], ); } } @@ -395,11 +392,10 @@ class FiroWallet extends Bip39HDWallet txid: txData["txid"] as String, network: cryptoCurrency.network, ); - spentSparkCoins = sparkCoinsInvolvedSpent - .where( - (e) => tags.contains(e.lTagHash), - ) - .toList(); + spentSparkCoins = + sparkCoinsInvolvedSpent + .where((e) => tags.contains(e.lTagHash)) + .toList(); } else if (isSparkSpend) { parseAnonFees(); } else if (isSparkMint) { @@ -483,10 +479,11 @@ class FiroWallet extends Bip39HDWallet if (usedCoins.isNotEmpty) { input = input.copyWith( addresses: usedCoins.map((e) => e.address).toList(), - valueStringSats: usedCoins - .map((e) => e.value) - .reduce((value, element) => value += element) - .toString(), + valueStringSats: + usedCoins + .map((e) => e.value) + .reduce((value, element) => value += element) + .toString(), walletOwns: true, ); wasSentFromThisWallet = true; @@ -497,10 +494,11 @@ class FiroWallet extends Bip39HDWallet spentSparkCoins.isNotEmpty) { input = input.copyWith( addresses: spentSparkCoins.map((e) => e.address).toList(), - valueStringSats: spentSparkCoins - .map((e) => e.value) - .fold(BigInt.zero, (p, e) => p + e) - .toString(), + valueStringSats: + spentSparkCoins + .map((e) => e.value) + .fold(BigInt.zero, (p, e) => p + e) + .toString(), walletOwns: true, ); wasSentFromThisWallet = true; @@ -570,11 +568,7 @@ class FiroWallet extends Bip39HDWallet String? otherData; if (anonFees != null) { - otherData = jsonEncode( - { - "overrideFee": anonFees!.toJsonString(), - }, - ); + otherData = jsonEncode({"overrideFee": anonFees!.toJsonString()}); } final tx = TransactionV2( @@ -584,7 +578,8 @@ class FiroWallet extends Bip39HDWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -613,12 +608,8 @@ class FiroWallet extends Bip39HDWallet } @override - Future< - ({ - String? blockedReason, - bool blocked, - String? utxoLabel, - })> checkBlockUTXO( + Future<({String? blockedReason, bool blocked, String? utxoLabel})> + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map? jsonTX, @@ -631,7 +622,8 @@ class FiroWallet extends Bip39HDWallet if (jsonUTXO["value"] is int) { // TODO: [prio=high] use special electrumx call to verify the 1000 Firo output is masternode // electrumx call should exist now. Unsure if it works though - blocked = Amount.fromDecimal( + blocked = + Amount.fromDecimal( Decimal.fromInt( 1000, // 1000 firo output is a possible master node ), @@ -654,7 +646,8 @@ class FiroWallet extends Bip39HDWallet } if (blocked) { - blockedReason = "Possible masternode collateral. " + blockedReason = + "Possible masternode collateral. " "Unlock and spend at your own risk."; label = "Possible masternode collateral"; } @@ -707,13 +700,11 @@ class FiroWallet extends Bip39HDWallet final List> lelantusFutures = []; final enableLelantusScanning = info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ?? - false; + false; if (enableLelantusScanning) { latestSetId = await electrumXClient.getLelantusLatestCoinId(); lelantusFutures.add( - electrumXCachedClient.getUsedCoinSerials( - cryptoCurrency: info.coin, - ), + electrumXCachedClient.getUsedCoinSerials(cryptoCurrency: info.coin), ); lelantusFutures.add(getSetDataMap(latestSetId)); } @@ -733,9 +724,9 @@ class FiroWallet extends Bip39HDWallet } final sparkUsedCoinTagsFuture = FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags( - electrumXClient, - cryptoCurrency.network, - ); + electrumXClient, + cryptoCurrency.network, + ); // receiving addresses Logging.instance.i("checking receiving addresses..."); @@ -745,17 +736,8 @@ class FiroWallet extends Bip39HDWallet for (final type in cryptoCurrency.supportedDerivationPathTypes) { receiveFutures.add( canBatch - ? checkGapsBatched( - txCountBatchSize, - root, - type, - receiveChain, - ) - : checkGapsLinearly( - root, - type, - receiveChain, - ), + ? checkGapsBatched(txCountBatchSize, root, type, receiveChain) + : checkGapsLinearly(root, type, receiveChain), ); } @@ -764,17 +746,8 @@ class FiroWallet extends Bip39HDWallet for (final type in cryptoCurrency.supportedDerivationPathTypes) { changeFutures.add( canBatch - ? checkGapsBatched( - txCountBatchSize, - root, - type, - changeChain, - ) - : checkGapsLinearly( - root, - type, - changeChain, - ), + ? checkGapsBatched(txCountBatchSize, root, type, changeChain) + : checkGapsLinearly(root, type, changeChain), ); } @@ -834,10 +807,7 @@ class FiroWallet extends Bip39HDWallet await mainDB.updateOrPutAddresses(addressesToStore); - await Future.wait([ - updateTransactions(), - updateUTXOs(), - ]); + await Future.wait([updateTransactions(), updateUTXOs()]); final List> futures = []; if (enableLelantusScanning) { @@ -865,9 +835,7 @@ class FiroWallet extends Bip39HDWallet usedSerialNumbers: usedSerialsSet!, setDataMap: setDataMap!, ), - recoverSparkWallet( - latestSparkCoinId: latestSparkCoinId, - ), + recoverSparkWallet(latestSparkCoinId: latestSparkCoinId), ]); } else { if (enableLelantusScanning) { @@ -877,9 +845,7 @@ class FiroWallet extends Bip39HDWallet setDataMap: setDataMap!, ); } - await recoverSparkWallet( - latestSparkCoinId: latestSparkCoinId, - ); + await recoverSparkWallet(latestSparkCoinId: latestSparkCoinId); } }); @@ -900,19 +866,23 @@ class FiroWallet extends Bip39HDWallet } @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } // =========================================================================== diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart index 034056817..e72b8e633 100644 --- a/lib/wallets/wallet/impl/litecoin_wallet.dart +++ b/lib/wallets/wallet/impl/litecoin_wallet.dart @@ -44,17 +44,18 @@ class LitecoinWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } @@ -67,14 +68,16 @@ class LitecoinWallet await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; @@ -84,17 +87,19 @@ class LitecoinWallet ); // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { // Check for duplicates by searching for tx by tx_hash in db. - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -107,8 +112,9 @@ class LitecoinWallet ); // Only tx to list once. - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -248,12 +254,13 @@ class LitecoinWallet // Check for special Litecoin outputs like ordinals. if (outputs.isNotEmpty) { // may not catch every case but it is much quicker - final hasOrdinal = await mainDB.isar.ordinals - .where() - .filter() - .walletIdEqualTo(walletId) - .utxoTXIDEqualTo(txData["txid"] as String) - .isNotEmpty(); + final hasOrdinal = + await mainDB.isar.ordinals + .where() + .filter() + .walletIdEqualTo(walletId) + .utxoTXIDEqualTo(txData["txid"] as String) + .isNotEmpty(); if (hasOrdinal) { subType = TransactionSubType.ordinal; } @@ -307,7 +314,8 @@ class LitecoinWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -323,79 +331,84 @@ class LitecoinWallet } @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } -// -// @override -// Future coinSelection({required TxData txData}) async { -// final isCoinControl = txData.utxos != null; -// final isSendAll = txData.amount == info.cachedBalance.spendable; -// -// final utxos = -// txData.utxos?.toList() ?? await mainDB.getUTXOs(walletId).findAll(); -// -// final currentChainHeight = await chainHeight; -// final List spendableOutputs = []; -// int spendableSatoshiValue = 0; -// -// // Build list of spendable outputs and totaling their satoshi amount -// for (final utxo in utxos) { -// if (utxo.isBlocked == false && -// utxo.isConfirmed(currentChainHeight, cryptoCurrency.minConfirms) && -// utxo.used != true) { -// spendableOutputs.add(utxo); -// spendableSatoshiValue += utxo.value; -// } -// } -// -// if (isCoinControl && spendableOutputs.length < utxos.length) { -// throw ArgumentError("Attempted to use an unavailable utxo"); -// } -// -// if (spendableSatoshiValue < txData.amount!.raw.toInt()) { -// throw Exception("Insufficient balance"); -// } else if (spendableSatoshiValue == txData.amount!.raw.toInt() && -// !isSendAll) { -// throw Exception("Insufficient balance to pay transaction fee"); -// } -// -// if (isCoinControl) { -// } else { -// final selection = cs.coinSelection( -// spendableOutputs -// .map((e) => cs.InputModel( -// i: e.vout, -// txid: e.txid, -// value: e.value, -// address: e.address, -// )) -// .toList(), -// txData.recipients! -// .map((e) => cs.OutputModel( -// address: e.address, -// value: e.amount.raw.toInt(), -// )) -// .toList(), -// txData.feeRateAmount!, -// 10, // TODO: ??????????????????????????????? -// ); -// -// // .inputs and .outputs will be null if no solution was found -// if (selection.inputs!.isEmpty || selection.outputs!.isEmpty) { -// throw Exception("coin selection failed"); -// } -// } -// } + + // + // @override + // Future coinSelection({required TxData txData}) async { + // final isCoinControl = txData.utxos != null; + // final isSendAll = txData.amount == info.cachedBalance.spendable; + // + // final utxos = + // txData.utxos?.toList() ?? await mainDB.getUTXOs(walletId).findAll(); + // + // final currentChainHeight = await chainHeight; + // final List spendableOutputs = []; + // int spendableSatoshiValue = 0; + // + // // Build list of spendable outputs and totaling their satoshi amount + // for (final utxo in utxos) { + // if (utxo.isBlocked == false && + // utxo.isConfirmed(currentChainHeight, cryptoCurrency.minConfirms) && + // utxo.used != true) { + // spendableOutputs.add(utxo); + // spendableSatoshiValue += utxo.value; + // } + // } + // + // if (isCoinControl && spendableOutputs.length < utxos.length) { + // throw ArgumentError("Attempted to use an unavailable utxo"); + // } + // + // if (spendableSatoshiValue < txData.amount!.raw.toInt()) { + // throw Exception("Insufficient balance"); + // } else if (spendableSatoshiValue == txData.amount!.raw.toInt() && + // !isSendAll) { + // throw Exception("Insufficient balance to pay transaction fee"); + // } + // + // if (isCoinControl) { + // } else { + // final selection = cs.coinSelection( + // spendableOutputs + // .map((e) => cs.InputModel( + // i: e.vout, + // txid: e.txid, + // value: e.value, + // address: e.address, + // )) + // .toList(), + // txData.recipients! + // .map((e) => cs.OutputModel( + // address: e.address, + // value: e.amount.raw.toInt(), + // )) + // .toList(), + // txData.feeRateAmount!, + // 10, // TODO: ??????????????????????????????? + // ); + // + // // .inputs and .outputs will be null if no solution was found + // if (selection.inputs!.isEmpty || selection.outputs!.isEmpty) { + // throw Exception("coin selection failed"); + // } + // } + // } } diff --git a/lib/wallets/wallet/impl/monero_wallet.dart b/lib/wallets/wallet/impl/monero_wallet.dart index a99cf30a3..03bbfd5c3 100644 --- a/lib/wallets/wallet/impl/monero_wallet.dart +++ b/lib/wallets/wallet/impl/monero_wallet.dart @@ -12,14 +12,14 @@ class MoneroWallet extends LibMoneroWallet { : super(Monero(network), lib_monero_compat.WalletType.monero); @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { if (libMoneroWallet == null || syncStatus is! lib_monero_compat.SyncedSyncStatus) { return Amount.zeroWith(fractionDigits: cryptoCurrency.fractionDigits); } lib_monero.TransactionPriority priority; - switch (feeRate) { + switch (feeRate.toInt()) { case 1: priority = lib_monero.TransactionPriority.low; break; diff --git a/lib/wallets/wallet/impl/namecoin_wallet.dart b/lib/wallets/wallet/impl/namecoin_wallet.dart index bf6f43e41..bb3f020f3 100644 --- a/lib/wallets/wallet/impl/namecoin_wallet.dart +++ b/lib/wallets/wallet/impl/namecoin_wallet.dart @@ -200,17 +200,21 @@ class NamecoinWallet } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } // TODO: Check if this is the correct formula for namecoin. @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); @@ -897,7 +901,7 @@ class NamecoinWallet if (customSatsPerVByte != null) { final result = await coinSelectionName( - txData: txData.copyWith(feeRateAmount: -1), + txData: txData.copyWith(feeRateAmount: BigInt.from(-1)), utxos: utxos?.toList(), coinControl: coinControl, ); @@ -911,10 +915,10 @@ class NamecoinWallet } return result; - } else if (feeRateType is FeeRateType || feeRateAmount is int) { - late final int rate; + } else if (feeRateType is FeeRateType || feeRateAmount is BigInt) { + late final BigInt rate; if (feeRateType is FeeRateType) { - int fee = 0; + BigInt fee = BigInt.zero; final feeObject = await fees; switch (feeRateType) { case FeeRateType.fast: @@ -931,7 +935,7 @@ class NamecoinWallet } rate = fee; } else { - rate = feeRateAmount as int; + rate = feeRateAmount!; } final result = await coinSelectionName( diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index 80db6e2ef..1188625cb 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -45,29 +45,26 @@ class ParticlWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = + await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } -// =========================================================================== + // =========================================================================== @override - Future< - ({ - bool blocked, - String? blockedReason, - String? utxoLabel, - })> checkBlockUTXO( + Future<({bool blocked, String? blockedReason, String? utxoLabel})> + checkBlockUTXO( Map jsonUTXO, String? scriptPubKeyHex, Map jsonTX, @@ -98,8 +95,9 @@ class ParticlWallet utxoLabel = "Unsupported output type."; } else if (output['scriptPubKey'] != null) { if (output['scriptPubKey']?['asm'] is String && - (output['scriptPubKey']['asm'] as String) - .contains("OP_ISCOINSTAKE")) { + (output['scriptPubKey']['asm'] as String).contains( + "OP_ISCOINSTAKE", + )) { blocked = true; blockedReason = "Spending staking"; utxoLabel = "Unsupported output type."; @@ -111,21 +109,25 @@ class ParticlWallet return ( blocked: blocked, blockedReason: blockedReason, - utxoLabel: utxoLabel + utxoLabel: utxoLabel, ); } @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate( + int inputCount, + int outputCount, + BigInt feeRatePerKB, + ) { return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); @@ -138,30 +140,34 @@ class ParticlWallet await fetchAddressesForElectrumXScan(); // Separate receiving and change addresses. - final Set receivingAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.receiving) - .map((e) => e.value) - .toSet(); - final Set changeAddresses = allAddressesOld - .where((e) => e.subType == AddressSubType.change) - .map((e) => e.value) - .toSet(); + final Set receivingAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.receiving) + .map((e) => e.value) + .toSet(); + final Set changeAddresses = + allAddressesOld + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; // Fetch history from ElectrumX. - final List> allTxHashes = - await fetchHistory(allAddressesSet); + final List> allTxHashes = await fetchHistory( + allAddressesSet, + ); // Only parse new txs (not in db yet). final List> allTransactions = []; for (final txHash in allTxHashes) { // Check for duplicates by searching for tx by tx_hash in db. - final storedTx = await mainDB.isar.transactionV2s - .where() - .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) - .findFirst(); + final storedTx = + await mainDB.isar.transactionV2s + .where() + .txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId) + .findFirst(); if (storedTx == null || storedTx.height == null || @@ -174,8 +180,9 @@ class ParticlWallet ); // Only tx to list once. - if (allTransactions - .indexWhere((e) => e["txid"] == tx["txid"] as String) == + if (allTransactions.indexWhere( + (e) => e["txid"] == tx["txid"] as String, + ) == -1) { tx["height"] = txHash["height"]; allTransactions.add(tx); @@ -325,7 +332,8 @@ class ParticlWallet txid: txData["txid"] as String, height: txData["height"] as int?, version: txData["version"] as int, - timestamp: txData["blocktime"] as int? ?? + timestamp: + txData["blocktime"] as int? ?? DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), @@ -372,32 +380,31 @@ class ParticlWallet switch (sd.derivePathType) { case DerivePathType.bip44: - data = bitcoindart - .P2PKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; + data = + bitcoindart + .P2PKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip49: - final p2wpkh = bitcoindart - .P2WPKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; + final p2wpkh = + bitcoindart + .P2WPKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; redeem = p2wpkh.output; - data = bitcoindart - .P2SH( - data: bitcoindart.PaymentData(redeem: p2wpkh), - network: convertedNetwork, - ) - .data; + data = + bitcoindart + .P2SH( + data: bitcoindart.PaymentData(redeem: p2wpkh), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip84: @@ -405,14 +412,13 @@ class ParticlWallet // prevOut: coinlib.OutPoint.fromHex(sd.utxo.txid, sd.utxo.vout), // publicKey: keys.publicKey, // ); - data = bitcoindart - .P2WPKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; + data = + bitcoindart + .P2WPKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip86: @@ -432,9 +438,7 @@ class ParticlWallet extraData.add((output: output, redeem: redeem)); } - final txb = bitcoindart.TransactionBuilder( - network: convertedNetwork, - ); + final txb = bitcoindart.TransactionBuilder(network: convertedNetwork); const version = 160; // buildTransaction overridden for Particl to set this. // TODO: [prio=low] refactor overridden buildTransaction to use eg. cryptocurrency.networkParams.txVersion. txb.setVersion(version); @@ -463,9 +467,10 @@ class ParticlWallet txid: utxoSigningData[i].utxo.txid, vout: utxoSigningData[i].utxo.vout, ), - addresses: utxoSigningData[i].utxo.address == null - ? [] - : [utxoSigningData[i].utxo.address!], + addresses: + utxoSigningData[i].utxo.address == null + ? [] + : [utxoSigningData[i].utxo.address!], valueStringSats: utxoSigningData[i].utxo.value.toString(), witness: null, innerRedeemScriptAsm: null, @@ -487,10 +492,9 @@ class ParticlWallet OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "000000", valueStringSats: txData.recipients![i].amount.raw.toString(), - addresses: [ - txData.recipients![i].address.toString(), - ], - walletOwns: (await mainDB.isar.addresses + addresses: [txData.recipients![i].address.toString()], + walletOwns: + (await mainDB.isar.addresses .where() .walletIdEqualTo(walletId) .filter() @@ -518,8 +522,11 @@ class ParticlWallet ); } } catch (e, s) { - Logging.instance.e("Caught exception while signing transaction: ", - error: e, stackTrace: s); + Logging.instance.e( + "Caught exception while signing transaction: ", + error: e, + stackTrace: s, + ); rethrow; } diff --git a/lib/wallets/wallet/impl/peercoin_wallet.dart b/lib/wallets/wallet/impl/peercoin_wallet.dart index 153131415..09c69e2cc 100644 --- a/lib/wallets/wallet/impl/peercoin_wallet.dart +++ b/lib/wallets/wallet/impl/peercoin_wallet.dart @@ -54,13 +54,13 @@ class PeercoinWallet // =========================================================================== @override - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + Amount roughFeeEstimate(int inputCount, int outputCount, BigInt feeRatePerKB) { // TODO: actually do this properly for peercoin // this is probably wrong for peercoin return Amount( rawValue: BigInt.from( ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(), + (feeRatePerKB.toInt() / 1000).ceil(), ), fractionDigits: cryptoCurrency.fractionDigits, ); @@ -68,8 +68,8 @@ class PeercoinWallet /// we can just pretend vSize is size for peercoin @override - int estimateTxFee({required int vSize, required int feeRatePerKB}) { - return vSize * (feeRatePerKB / 1000).ceil(); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { + return vSize * (feeRatePerKB.toInt() / 1000).ceil(); } // =========================================================================== diff --git a/lib/wallets/wallet/impl/solana_wallet.dart b/lib/wallets/wallet/impl/solana_wallet.dart index 8ae0db348..0b7c2911d 100644 --- a/lib/wallets/wallet/impl/solana_wallet.dart +++ b/lib/wallets/wallet/impl/solana_wallet.dart @@ -55,13 +55,13 @@ class SolanaWallet extends Bip39Wallet { return addressStruct; } - Future _getCurrentBalanceInLamports() async { + Future _getCurrentBalanceInLamports() async { _checkClient(); final balance = await _rpcClient?.getBalance((await _getKeyPair()).address); - return balance!.value; + return BigInt.from(balance!.value); } - Future _getEstimatedNetworkFee(Amount transferAmount) async { + Future _getEstimatedNetworkFee(Amount transferAmount) async { _checkClient(); final latestBlockhash = await _rpcClient?.getLatestBlockhash(); final pubKey = (await _getKeyPair()).publicKey; @@ -79,9 +79,13 @@ class SolanaWallet extends Bip39Wallet { feePayer: pubKey, ); - return await _rpcClient?.getFeeForMessage( + final estimate = await _rpcClient?.getFeeForMessage( base64Encode(compiledMessage.toByteArray().toList()), ); + + if (estimate == null) return null; + + return BigInt.from(estimate); } @override @@ -99,7 +103,11 @@ class SolanaWallet extends Bip39Wallet { await mainDB.updateOrPutAddresses([address]); } } catch (e, s) { - Logging.instance.e("$runtimeType checkSaveInitialReceivingAddress() failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s, + ); } } @@ -133,28 +141,33 @@ class SolanaWallet extends Bip39Wallet { throw Exception("Account does not appear to exist"); } - final int minimumRent = - await _rpcClient!.getMinimumBalanceForRentExemption( - accInfo.value!.data.toString().length, + final BigInt minimumRent = BigInt.from( + await _rpcClient!.getMinimumBalanceForRentExemption( + accInfo.value!.data.toString().length, + ), ); if (minimumRent > ((await _getCurrentBalanceInLamports()) - - txData.amount!.raw.toInt() - + txData.amount!.raw - feeAmount)) { throw Exception( "Insufficient remaining balance for rent exemption, minimum rent: " - "${minimumRent / pow(10, cryptoCurrency.fractionDigits)}", + "${minimumRent.toInt() / pow(10, cryptoCurrency.fractionDigits)}", ); } return txData.copyWith( fee: Amount( - rawValue: BigInt.from(feeAmount), + rawValue: feeAmount, fractionDigits: cryptoCurrency.fractionDigits, ), ); } catch (e, s) { - Logging.instance.e("$runtimeType Solana prepareSend failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType Solana prepareSend failed: ", + error: e, + stackTrace: s, + ); rethrow; } } @@ -166,8 +179,9 @@ class SolanaWallet extends Bip39Wallet { final keyPair = await _getKeyPair(); final recipientAccount = txData.recipients!.first; - final recipientPubKey = - Ed25519HDPublicKey.fromBase58(recipientAccount.address); + final recipientPubKey = Ed25519HDPublicKey.fromBase58( + recipientAccount.address, + ); final message = Message( instructions: [ SystemInstruction.transfer( @@ -187,17 +201,19 @@ class SolanaWallet extends Bip39Wallet { ); final txid = await _rpcClient?.signAndSendTransaction(message, [keyPair]); - return txData.copyWith( - txid: txid, - ); + return txData.copyWith(txid: txid); } catch (e, s) { - Logging.instance.e("$runtimeType Solana confirmSend failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType Solana confirmSend failed: ", + error: e, + stackTrace: s, + ); rethrow; } } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { _checkClient(); if (info.cachedBalance.spendable.raw == BigInt.zero) { @@ -212,10 +228,7 @@ class SolanaWallet extends Bip39Wallet { throw Exception("Failed to get fees, please check your node connection."); } - return Amount( - rawValue: BigInt.from(fee), - fractionDigits: cryptoCurrency.fractionDigits, - ); + return Amount(rawValue: fee, fractionDigits: cryptoCurrency.fractionDigits); } @override @@ -250,7 +263,9 @@ class SolanaWallet extends Bip39Wallet { health = await _rpcClient?.getHealth(); return health != null; } catch (e, s) { - Logging.instance.e("$runtimeType Solana pingCheck failed \"health response=$health\": $e\n$s"); + Logging.instance.e( + "$runtimeType Solana pingCheck failed \"health response=$health\": $e\n$s", + ); return Future.value(false); } } @@ -295,10 +310,10 @@ class SolanaWallet extends Bip39Wallet { throw Exception("Account does not appear to exist"); } - final int minimumRent = - await _rpcClient!.getMinimumBalanceForRentExemption( - accInfo.value!.data.toString().length, - ); + final int minimumRent = await _rpcClient! + .getMinimumBalanceForRentExemption( + accInfo.value!.data.toString().length, + ); final spendableBalance = balance!.value - minimumRent; final newBalance = Balance( @@ -322,7 +337,11 @@ class SolanaWallet extends Bip39Wallet { await info.updateBalance(newBalance: newBalance, isar: mainDB.isar); } catch (e, s) { - Logging.instance.e("Error getting balance in solana_wallet.dart: ", error: e, stackTrace: s); + Logging.instance.e( + "Error getting balance in solana_wallet.dart: ", + error: e, + stackTrace: s, + ); } } @@ -339,23 +358,29 @@ class SolanaWallet extends Bip39Wallet { isar: mainDB.isar, ); } catch (e, s) { - Logging.instance.e("Error occurred in solana_wallet.dart while getting" - " chain height for solana: $e\n$s"); + Logging.instance.e( + "Error occurred in solana_wallet.dart while getting" + " chain height for solana: $e\n$s", + ); } } @override Future updateNode() async { - _solNode = NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? + _solNode = + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? info.coin.defaultNode; await refresh(); } @override NodeModel getCurrentNode() { - _solNode ??= NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? + _solNode ??= + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? info.coin.defaultNode; return _solNode!; @@ -370,8 +395,9 @@ class SolanaWallet extends Bip39Wallet { (await _getKeyPair()).publicKey, encoding: Encoding.jsonParsed, ); - final txsList = - List>.empty(growable: true); + final txsList = List>.empty( + growable: true, + ); final myAddress = (await getCurrentReceivingAddress())!; @@ -384,8 +410,9 @@ class SolanaWallet extends Bip39Wallet { (tx.transaction as ParsedTransaction).message.accountKeys[1].pubkey; var txType = isar.TransactionType.unknown; final txAmount = Amount( - rawValue: - BigInt.from(tx.meta!.postBalances[1] - tx.meta!.preBalances[1]), + rawValue: BigInt.from( + tx.meta!.postBalances[1] - tx.meta!.preBalances[1], + ), fractionDigits: cryptoCurrency.fractionDigits, ); @@ -429,9 +456,10 @@ class SolanaWallet extends Bip39Wallet { derivationIndex: 0, derivationPath: DerivationPath()..value = _addressDerivationPath, type: AddressType.solana, - subType: txType == isar.TransactionType.outgoing - ? AddressSubType.unknown - : AddressSubType.receiving, + subType: + txType == isar.TransactionType.outgoing + ? AddressSubType.unknown + : AddressSubType.receiving, ); txsList.add(Tuple2(transaction, txAddress)); @@ -440,8 +468,10 @@ class SolanaWallet extends Bip39Wallet { } on NodeTorMismatchConfigException { rethrow; } catch (e, s) { - Logging.instance.e("Error occurred in solana_wallet.dart while getting" - " transactions for solana: $e\n$s"); + Logging.instance.e( + "Error occurred in solana_wallet.dart while getting" + " transactions for solana: $e\n$s", + ); } } diff --git a/lib/wallets/wallet/impl/stellar_wallet.dart b/lib/wallets/wallet/impl/stellar_wallet.dart index 811858546..91af9758f 100644 --- a/lib/wallets/wallet/impl/stellar_wallet.dart +++ b/lib/wallets/wallet/impl/stellar_wallet.dart @@ -33,34 +33,34 @@ class StellarWallet extends Bip39Wallet { final bus = GlobalEventBus.instance; // Listen for tor status changes. - _torStatusListener = bus.on().listen( - (event) async { - switch (event.newStatus) { - case TorConnectionStatus.connecting: - if (!_torConnectingLock.isLocked) { - await _torConnectingLock.acquire(); - } - _requireMutex = true; - break; + _torStatusListener = bus.on().listen(( + event, + ) async { + switch (event.newStatus) { + case TorConnectionStatus.connecting: + if (!_torConnectingLock.isLocked) { + await _torConnectingLock.acquire(); + } + _requireMutex = true; + break; - case TorConnectionStatus.connected: - case TorConnectionStatus.disconnected: - if (_torConnectingLock.isLocked) { - _torConnectingLock.release(); - } - _requireMutex = false; - break; - } - }, - ); + case TorConnectionStatus.connected: + case TorConnectionStatus.disconnected: + if (_torConnectingLock.isLocked) { + _torConnectingLock.release(); + } + _requireMutex = false; + break; + } + }); // Listen for tor preference changes. - _torPreferenceListener = bus.on().listen( - (event) async { - _stellarSdk?.httpClient.close(); - _stellarSdk = null; - }, - ); + _torPreferenceListener = bus.on().listen(( + event, + ) async { + _stellarSdk?.httpClient.close(); + _stellarSdk = null; + }); } void _hackedCheck() { @@ -116,12 +116,10 @@ class StellarWallet extends Bip39Wallet { // ============== Private ==================================================== // add finalizer to cancel stream subscription when all references to an // instance of this becomes inaccessible - final _ = Finalizer( - (p0) { - p0._torPreferenceListener?.cancel(); - p0._torStatusListener?.cancel(); - }, - ); + final _ = Finalizer((p0) { + p0._torPreferenceListener?.cancel(); + p0._torStatusListener?.cancel(); + }); StreamSubscription? _torStatusListener; StreamSubscription? _torPreferenceListener; @@ -131,9 +129,9 @@ class StellarWallet extends Bip39Wallet { stellar.StellarSDK? _stellarSdk; - Future _getBaseFee() async { + Future _getBaseFee() async { final fees = await (await stellarSdk).feeStats.execute(); - return int.parse(fees.lastLedgerBaseFee); + return BigInt.parse(fees.lastLedgerBaseFee); } stellar.StellarSDK _getFreshSdk() { @@ -145,15 +143,9 @@ class StellarWallet extends Bip39Wallet { TorService.sharedInstance.getProxyInfo(); _httpClient = HttpClient(); - SocksTCPClient.assignToHttpClient( - _httpClient, - [ - ProxySettings( - proxyInfo.host, - proxyInfo.port, - ), - ], - ); + SocksTCPClient.assignToHttpClient(_httpClient, [ + ProxySettings(proxyInfo.host, proxyInfo.port), + ]); } return stellar.StellarSDK( @@ -166,16 +158,18 @@ class StellarWallet extends Bip39Wallet { bool exists = false; try { - final receiverAccount = - await (await stellarSdk).accounts.account(accountId); + final receiverAccount = await (await stellarSdk).accounts.account( + accountId, + ); if (receiverAccount.accountId != "") { exists = true; } } catch (e, s) { Logging.instance.e( - "Error getting account ${e.toString()} - ${s.toString()}", - error: e, - stackTrace: s); + "Error getting account ${e.toString()} - ${s.toString()}", + error: e, + stackTrace: s, + ); } return exists; } @@ -225,15 +219,17 @@ class StellarWallet extends Bip39Wallet { try { final address = await getCurrentReceivingAddress(); if (address == null) { - await mainDB - .updateOrPutAddresses([await _fetchStellarAddress(index: 0)]); + await mainDB.updateOrPutAddresses([ + await _fetchStellarAddress(index: 0), + ]); } } catch (e, s) { // do nothing, still allow user into wallet Logging.instance.e( - "$runtimeType checkSaveInitialReceivingAddress() failed: ", - error: e, - stackTrace: s); + "$runtimeType checkSaveInitialReceivingAddress() failed: ", + error: e, + stackTrace: s, + ); } } @@ -245,7 +241,7 @@ class StellarWallet extends Bip39Wallet { } final feeRate = txData.feeRateType; - var fee = 1000; + BigInt fee = BigInt.from(1000); if (feeRate is FeeRateType) { final theFees = await fees; switch (feeRate) { @@ -261,13 +257,16 @@ class StellarWallet extends Bip39Wallet { return txData.copyWith( fee: Amount( - rawValue: BigInt.from(fee), + rawValue: fee, fractionDigits: cryptoCurrency.fractionDigits, ), ); } catch (e, s) { - Logging.instance - .e("$runtimeType prepareSend() failed: ", error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType prepareSend() failed: ", + error: e, + stackTrace: s, + ); rethrow; } } @@ -275,8 +274,9 @@ class StellarWallet extends Bip39Wallet { @override Future confirmSend({required TxData txData}) async { final senderKeyPair = await _getSenderKeyPair(index: 0); - final sender = - await (await stellarSdk).accounts.account(senderKeyPair.accountId); + final sender = await (await stellarSdk).accounts.account( + senderKeyPair.accountId, + ); final address = txData.recipients!.first.address; final amountToSend = txData.recipients!.first.amount; @@ -293,9 +293,9 @@ class StellarWallet extends Bip39Wallet { address, amountToSend.decimal.toString(), ); - transactionBuilder = stellar.TransactionBuilder(sender).addOperation( - createAccBuilder.build(), - ); + transactionBuilder = stellar.TransactionBuilder( + sender, + ).addOperation(createAccBuilder.build()); } else { transactionBuilder = stellar.TransactionBuilder(sender).addOperation( stellar.PaymentOperationBuilder( @@ -316,14 +316,13 @@ class StellarWallet extends Bip39Wallet { try { final response = await (await stellarSdk).submitTransaction(transaction); if (!response.success) { - throw Exception("${response.extras?.resultCodes?.transactionResultCode}" - " ::: ${response.extras?.resultCodes?.operationsResultCodes}"); + throw Exception( + "${response.extras?.resultCodes?.transactionResultCode}" + " ::: ${response.extras?.resultCodes?.operationsResultCodes}", + ); } - return txData.copyWith( - txHash: response.hash!, - txid: response.hash!, - ); + return txData.copyWith(txHash: response.hash!, txid: response.hash!); } catch (e, s) { Logging.instance.e("Error sending TX $e - $s", error: e, stackTrace: s); rethrow; @@ -331,17 +330,17 @@ class StellarWallet extends Bip39Wallet { } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { final baseFee = await _getBaseFee(); return Amount( - rawValue: BigInt.from(baseFee), + rawValue: baseFee, fractionDigits: cryptoCurrency.fractionDigits, ); } @override Future get fees async { - final int fee = await _getBaseFee(); + final fee = await _getBaseFee(); return FeeObject( numberOfBlocksFast: 1, numberOfBlocksAverage: 1, @@ -379,16 +378,17 @@ class StellarWallet extends Bip39Wallet { stellar.AccountResponse accountResponse; try { - accountResponse = await (await stellarSdk) - .accounts + accountResponse = await (await stellarSdk).accounts .account((await getCurrentReceivingAddress())!.value) .onError((error, stackTrace) => throw error!); } catch (e) { if (e is stellar.ErrorResponse && - e.body.contains("The resource at the url requested was not found. " - "This usually occurs for one of two reasons: " - "The url requested is not valid, or no data in our database " - "could be found with the parameters provided.")) { + e.body.contains( + "The resource at the url requested was not found. " + "This usually occurs for one of two reasons: " + "The url requested is not valid, or no data in our database " + "could be found with the parameters provided.", + )) { // probably just doesn't have any history yet or whatever stellar needs return; } else { @@ -438,16 +438,18 @@ class StellarWallet extends Bip39Wallet { @override Future updateChainHeight() async { try { - final height = await (await stellarSdk) - .ledgers + final height = await (await stellarSdk).ledgers .order(stellar.RequestBuilderOrder.DESC) .limit(1) .execute() .then((value) => value.records!.first.sequence); await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } catch (e, s) { - Logging.instance.e("$runtimeType updateChainHeight() failed: ", - error: e, stackTrace: s); + Logging.instance.e( + "$runtimeType updateChainHeight() failed: ", + error: e, + stackTrace: s, + ); rethrow; } @@ -467,17 +469,19 @@ class StellarWallet extends Bip39Wallet { final List transactionList = []; stellar.Page payments; try { - payments = await (await stellarSdk) - .payments - .forAccount(myAddress.value) - .order(stellar.RequestBuilderOrder.DESC) - .execute(); + payments = + await (await stellarSdk).payments + .forAccount(myAddress.value) + .order(stellar.RequestBuilderOrder.DESC) + .execute(); } catch (e) { if (e is stellar.ErrorResponse && - e.body.contains("The resource at the url requested was not found. " - "This usually occurs for one of two reasons: " - "The url requested is not valid, or no data in our database " - "could be found with the parameters provided.")) { + e.body.contains( + "The resource at the url requested was not found. " + "This usually occurs for one of two reasons: " + "The url requested is not valid, or no data in our database " + "could be found with the parameters provided.", + )) { // probably just doesn't have any history yet or whatever stellar needs return; } else { @@ -521,13 +525,11 @@ class StellarWallet extends Bip39Wallet { final OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( - scriptPubKeyHex: "00", - valueStringSats: amount.raw.toString(), - addresses: [ - addressTo, - ], - walletOwns: addressTo == myAddress.value, - ); + scriptPubKeyHex: "00", + valueStringSats: amount.raw.toString(), + addresses: [addressTo], + walletOwns: addressTo == myAddress.value, + ); final InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( scriptSigHex: null, scriptSigAsm: null, @@ -558,10 +560,11 @@ class StellarWallet extends Bip39Wallet { } final otherData = { - "overrideFee": Amount( - rawValue: BigInt.from(fee), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + "overrideFee": + Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), }; final theTransaction = TransactionV2( @@ -603,8 +606,8 @@ class StellarWallet extends Bip39Wallet { final List outputs = []; final List inputs = []; - final OutputV2 output = - OutputV2.isarCantDoRequiredInDefaultConstructor( + final OutputV2 + output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: amount.raw.toString(), addresses: [ @@ -634,19 +637,20 @@ class StellarWallet extends Bip39Wallet { int fee = 0; int height = 0; - final tx = await (await stellarSdk) - .transactions - .transaction(caor.transactionHash!); + final tx = await (await stellarSdk).transactions.transaction( + caor.transactionHash!, + ); if (tx.hash.isNotEmpty) { fee = tx.feeCharged!; height = tx.ledger; } final otherData = { - "overrideFee": Amount( - rawValue: BigInt.from(fee), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + "overrideFee": + Amount( + rawValue: BigInt.from(fee), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), }; final theTransaction = TransactionV2( @@ -671,8 +675,11 @@ class StellarWallet extends Bip39Wallet { await mainDB.updateOrPutTransactionV2s(transactionList); } catch (e, s) { - Logging.instance.e("Exception rethrown from updateTransactions(): ", - error: e, stackTrace: s); + Logging.instance.e( + "Exception rethrown from updateTransactions(): ", + error: e, + stackTrace: s, + ); rethrow; } } diff --git a/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart b/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart index 845b0282b..d32ae3db8 100644 --- a/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart +++ b/lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart @@ -5,7 +5,6 @@ import 'package:isar/isar.dart'; import 'package:web3dart/web3dart.dart' as web3dart; import '../../../../dto/ethereum/eth_token_tx_dto.dart'; -import '../../../../dto/ethereum/eth_token_tx_extra_dto.dart'; import '../../../../models/balance.dart'; import '../../../../models/isar/models/blockchain_data/transaction.dart'; import '../../../../models/isar/models/blockchain_data/v2/input_v2.dart'; @@ -15,7 +14,6 @@ import '../../../../models/isar/models/ethereum/eth_contract.dart'; import '../../../../models/paymint/fee_object_model.dart'; import '../../../../services/ethereum/ethereum_api.dart'; import '../../../../utilities/amount/amount.dart'; -import '../../../../utilities/enums/fee_rate_type_enum.dart'; import '../../../../utilities/eth_commons.dart'; import '../../../../utilities/extensions/extensions.dart'; import '../../../../utilities/logger.dart'; @@ -29,7 +27,7 @@ class EthTokenWallet extends Wallet { int get isarTransactionVersion => 2; EthTokenWallet(this.ethWallet, this._tokenContract) - : super(ethWallet.cryptoCurrency); + : super(ethWallet.cryptoCurrency); final EthereumWallet ethWallet; @@ -39,8 +37,6 @@ class EthTokenWallet extends Wallet { late web3dart.DeployedContract _deployedContract; late web3dart.ContractFunction _sendFunction; - static const _gasLimit = 65000; - // =========================================================================== // =========================================================================== @@ -85,9 +81,7 @@ class EthTokenWallet extends Wallet { final output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: amount.raw.toString(), - addresses: [ - addressTo, - ], + addresses: [addressTo], walletOwns: addressTo == myAddress, ); final input = InputV2.isarCantDoRequiredInDefaultConstructor( @@ -116,16 +110,15 @@ class EthTokenWallet extends Wallet { inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), version: -1, - type: addressTo == myAddress - ? TransactionType.sentToSelf - : TransactionType.outgoing, + type: + addressTo == myAddress + ? TransactionType.sentToSelf + : TransactionType.outgoing, subType: TransactionSubType.ethToken, otherData: jsonEncode(otherData), ); - return txData.copyWith( - tempTx: tempTx, - ); + return txData.copyWith(tempTx: tempTx); } // =========================================================================== @@ -143,8 +136,9 @@ class EthTokenWallet extends Wallet { try { await super.init(); - final contractAddress = - web3dart.EthereumAddress.fromHex(tokenContract.address); + final contractAddress = web3dart.EthereumAddress.fromHex( + tokenContract.address, + ); // first try to update the abi regardless just in case something has changed try { @@ -153,8 +147,11 @@ class EthTokenWallet extends Wallet { usingContractAddress: contractAddress.hex, ); } catch (e, s) { - Logging.instance - .w("$runtimeType _updateTokenABI(): ", error: e, stackTrace: s); + Logging.instance.w( + "$runtimeType _updateTokenABI(): ", + error: e, + stackTrace: s, + ); } try { @@ -176,8 +173,8 @@ class EthTokenWallet extends Wallet { // Some failure, try for proxy contract final contractAddressResponse = await EthereumAPI.getProxyTokenImplementationAddress( - contractAddress.hex, - ); + contractAddress.hex, + ); if (contractAddressResponse.value != null) { _tokenContract = await _updateTokenABI( @@ -198,55 +195,36 @@ class EthTokenWallet extends Wallet { _sendFunction = _deployedContract.function('transfer'); } catch (e, s) { - Logging.instance - .w("$runtimeType wallet failed init(): ", error: e, stackTrace: s); + Logging.instance.w( + "$runtimeType wallet failed init(): ", + error: e, + stackTrace: s, + ); } } @override Future prepareSend({required TxData txData}) async { - final feeRateType = txData.feeRateType!; - int fee = 0; - final feeObject = await fees; - switch (feeRateType) { - case FeeRateType.fast: - fee = feeObject.fast; - break; - case FeeRateType.average: - fee = feeObject.medium; - break; - case FeeRateType.slow: - fee = feeObject.slow; - break; - case FeeRateType.custom: - throw UnimplementedError("custom eth token fees"); - } - - final feeEstimate = await estimateFeeFor(Amount.zero, fee); - - final client = ethWallet.getEthClient(); - - final myAddress = (await getCurrentReceivingAddress())!.value; - final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress); - - final nonce = txData.nonce ?? - await client.getTransactionCount( - myWeb3Address, - atBlock: const web3dart.BlockNum.pending(), - ); - final amount = txData.recipients!.first.amount; final address = txData.recipients!.first.address; - await updateBalance(); - final info = await mainDB.isar.tokenWalletInfo - .where() - .walletIdTokenAddressEqualTo(walletId, tokenContract.address) - .findFirst(); - final availableBalance = info?.getCachedBalance().spendable ?? - Amount.zeroWith( - fractionDigits: tokenContract.decimals, - ); + final myWeb3Address = await ethWallet.getMyWeb3Address(); + + final prep = await ethWallet.internalSharedPrepareSend( + txData: txData, + myWeb3Address: myWeb3Address, + ); + + // double check balance after internalSharedPrepareSend call to ensure + // balance is up to date + final info = + await mainDB.isar.tokenWalletInfo + .where() + .walletIdTokenAddressEqualTo(walletId, tokenContract.address) + .findFirst(); + final availableBalance = + info?.getCachedBalance().spendable ?? + Amount.zeroWith(fractionDigits: tokenContract.decimals); if (amount > availableBalance) { throw Exception("Insufficient balance"); } @@ -255,19 +233,26 @@ class EthTokenWallet extends Wallet { contract: _deployedContract, function: _sendFunction, parameters: [web3dart.EthereumAddress.fromHex(address), amount.raw], - maxGas: _gasLimit, - gasPrice: web3dart.EtherAmount.fromUnitAndValue( + maxGas: txData.ethEIP1559Fee?.gasLimit ?? kEthereumTokenMinGasLimit, + nonce: prep.nonce, + maxFeePerGas: web3dart.EtherAmount.fromBigInt( web3dart.EtherUnit.wei, - fee, + prep.maxBaseFee, + ), + maxPriorityFeePerGas: web3dart.EtherAmount.fromBigInt( + web3dart.EtherUnit.wei, + prep.priorityFee, ), - nonce: nonce, ); + final feeEstimate = await estimateFeeFor( + Amount.zero, + prep.maxBaseFee + prep.priorityFee, + ); return txData.copyWith( fee: feeEstimate, - feeInWei: BigInt.from(fee), web3dartTransaction: tx, - chainId: await client.getChainId(), + chainId: prep.chainId, nonce: tx.nonce, ); } @@ -286,16 +271,16 @@ class EthTokenWallet extends Wallet { } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { return ethWallet.estimateEthFee( feeRate, - _gasLimit, + kEthereumTokenMinGasLimit, cryptoCurrency.fractionDigits, ); } @override - Future get fees => EthereumAPI.getFees(); + Future get fees => EthereumAPI.getFees(); @override Future pingCheck() async { @@ -317,10 +302,11 @@ class EthTokenWallet extends Wallet { @override Future updateBalance() async { try { - final info = await mainDB.isar.tokenWalletInfo - .where() - .walletIdTokenAddressEqualTo(walletId, tokenContract.address) - .findFirst(); + final info = + await mainDB.isar.tokenWalletInfo + .where() + .walletIdTokenAddressEqualTo(walletId, tokenContract.address) + .findFirst(); final response = await EthereumAPI.getWalletTokenBalance( address: (await getCurrentReceivingAddress())!.value, contractAddress: tokenContract.address, @@ -364,8 +350,9 @@ class EthTokenWallet extends Wallet { @override Future updateTransactions() async { try { - final String addressString = - checksumEthereumAddress((await getCurrentReceivingAddress())!.value); + final String addressString = checksumEthereumAddress( + (await getCurrentReceivingAddress())!.value, + ); final response = await EthereumAPI.getTokenTransactions( address: addressString, @@ -374,8 +361,9 @@ class EthTokenWallet extends Wallet { if (response.value == null) { if (response.exception != null && - response.exception!.message - .contains("response is empty but status code is 200")) { + response.exception!.message.contains( + "response is empty but status code is 200", + )) { Logging.instance.d( "No ${tokenContract.name} transfers found for $addressString", ); @@ -390,50 +378,38 @@ class EthTokenWallet extends Wallet { return; } - final response2 = await EthereumAPI.getEthTokenTransactionsByTxids( - response.value!.map((e) => e.transactionHash).toSet().toList(), - ); - - if (response2.value == null) { - throw response2.exception ?? - Exception("Failed to fetch token transactions"); - } - final List<({EthTokenTxDto tx, EthTokenTxExtraDTO extra})> data = []; - for (final tokenDto in response.value!) { - try { - final txExtra = response2.value!.firstWhere( - (e) => e.hash == tokenDto.transactionHash, - ); - data.add( - ( - tx: tokenDto, - extra: txExtra, - ), - ); - } catch (e, s) { - // Server indexing failed for some reason. Instead of hard crashing or - // showing no transactions we just skip it here. Not ideal but better - // than nothing showing up - Logging.instance.e( - "Server error: Transaction hash not found.", - error: e, - stackTrace: s, - ); - Logging.instance.d( - "Server error: Transaction ${tokenDto.transactionHash} not found.", - error: e, - stackTrace: s, - ); + web3dart.Web3Client? client; + final List allTxs = []; + for (final dto in response.value!) { + if (dto.nonce == null) { + client ??= ethWallet.getEthClient(); + final txInfo = await client.getTransactionByHash(dto.transactionHash); + if (txInfo == null) { + // Something strange is happening + Logging.instance.w( + "Could not find token transaction via RPC that was found use " + "TrueBlocks API.\nOffending tx: $dto", + ); + } else { + final updated = dto.copyWith( + nonce: txInfo.nonce, + gasPrice: txInfo.gasPrice.getInWei, + gasUsed: txInfo.gas, + ); + allTxs.add(updated); + } + } else { + allTxs.add(dto); } } final List txns = []; - for (final tuple in data) { + for (final tx in allTxs) { // ignore all non Transfer events (for now) - if (tuple.tx.topics[0] == kTransferEventSignature) { + if (tx.topics[0] == kTransferEventSignature) { final amount = Amount( - rawValue: tuple.tx.data.toBigIntFromHex, + rawValue: tx.data.toBigIntFromHex, fractionDigits: tokenContract.decimals, ); @@ -442,13 +418,12 @@ class EthTokenWallet extends Wallet { continue; } - final Amount txFee = tuple.extra.gasUsed * tuple.extra.gasPrice; - final addressFrom = _addressFromTopic( - tuple.tx.topics[1], - ); - final addressTo = _addressFromTopic( - tuple.tx.topics[2], + final txFee = Amount( + rawValue: BigInt.from(tx.gasUsed!) * tx.gasPrice!, + fractionDigits: cryptoCurrency.fractionDigits, ); + final addressFrom = _addressFromTopic(tx.topics[1]); + final addressTo = _addressFromTopic(tx.topics[2]); final TransactionType txType; if (addressTo == addressString) { @@ -470,10 +445,10 @@ class EthTokenWallet extends Wallet { } final otherData = { - "nonce": tuple.extra.nonce, + "nonce": tx.nonce, "isCancelled": false, "overrideFee": txFee.toJsonString(), - "contractAddress": tuple.tx.address, + "contractAddress": tx.address, }; // hack eth tx data into inputs and outputs @@ -483,9 +458,7 @@ class EthTokenWallet extends Wallet { final output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "00", valueStringSats: amount.raw.toString(), - addresses: [ - addressTo, - ], + addresses: [addressTo], walletOwns: addressTo == addressString, ); final input = InputV2.isarCantDoRequiredInDefaultConstructor( @@ -506,11 +479,11 @@ class EthTokenWallet extends Wallet { final txn = TransactionV2( walletId: walletId, - blockHash: tuple.extra.blockHash, - hash: tuple.tx.transactionHash, - txid: tuple.tx.transactionHash, - timestamp: tuple.extra.timestamp, - height: tuple.tx.blockNumber, + blockHash: tx.blockHash, + hash: tx.transactionHash, + txid: tx.transactionHash, + timestamp: tx.timestamp, + height: tx.blockNumber, inputs: List.unmodifiable(inputs), outputs: List.unmodifiable(outputs), version: -1, @@ -544,15 +517,15 @@ class EthTokenWallet extends Wallet { @override FilterOperation? get transactionFilterOperation => FilterGroup.and([ - FilterCondition.equalTo( - property: r"contractAddress", - value: tokenContract.address, - ), - const FilterCondition.equalTo( - property: r"subType", - value: TransactionSubType.ethToken, - ), - ]); + FilterCondition.equalTo( + property: r"contractAddress", + value: tokenContract.address, + ), + const FilterCondition.equalTo( + property: r"subType", + value: TransactionSubType.ethToken, + ), + ]); @override Future checkSaveInitialReceivingAddress() async { diff --git a/lib/wallets/wallet/impl/tezos_wallet.dart b/lib/wallets/wallet/impl/tezos_wallet.dart index 992a900ae..ae2183be0 100644 --- a/lib/wallets/wallet/impl/tezos_wallet.dart +++ b/lib/wallets/wallet/impl/tezos_wallet.dart @@ -115,9 +115,10 @@ class TezosWallet extends Bip39Wallet { prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null; final tezartClient = tezart.TezartClient( server, - proxy: proxyInfo != null - ? "socks5://${proxyInfo.host}:${proxyInfo.port};" - : null, + proxy: + proxyInfo != null + ? "socks5://${proxyInfo.host}:${proxyInfo.port};" + : null, ); final opList = await tezartClient.transferOperation( @@ -197,9 +198,7 @@ class TezosWallet extends Bip39Wallet { } final myAddress = (await getCurrentReceivingAddress())!; - final account = await TezosAPI.getAccount( - myAddress.value, - ); + final account = await TezosAPI.getAccount(myAddress.value); // final bool isSendAll = sendAmount == info.cachedBalance.spendable; // @@ -262,13 +261,8 @@ class TezosWallet extends Bip39Wallet { // fee: fee, fee: Amount( rawValue: opList.operations - .map( - (e) => BigInt.from(e.fee), - ) - .fold( - BigInt.zero, - (p, e) => p + e, - ), + .map((e) => BigInt.from(e.fee)) + .fold(BigInt.zero, (p, e) => p + e), fractionDigits: cryptoCurrency.fractionDigits, ), tezosOperationsList: opList, @@ -280,14 +274,14 @@ class TezosWallet extends Bip39Wallet { stackTrace: s, ); - if (e - .toString() - .contains("(_operationResult['errors']): Must not be null")) { + if (e.toString().contains( + "(_operationResult['errors']): Must not be null", + )) { throw Exception("Probably insufficient balance"); } else if (e.toString().contains( - "The simulation of the operation: \"transaction\" failed with error(s) :" - " contract.balance_too_low, tez.subtraction_underflow.", - )) { + "The simulation of the operation: \"transaction\" failed with error(s) :" + " contract.balance_too_low, tez.subtraction_underflow.", + )) { throw Exception("Insufficient balance to pay fees"); } @@ -300,9 +294,7 @@ class TezosWallet extends Bip39Wallet { _hackedCheckTorNodePrefs(); await txData.tezosOperationsList!.inject(); await txData.tezosOperationsList!.monitor(); - return txData.copyWith( - txid: txData.tezosOperationsList!.result.id, - ); + return txData.copyWith(txid: txData.tezosOperationsList!.result.id); } int _estCount = 0; @@ -350,13 +342,8 @@ class TezosWallet extends Bip39Wallet { rethrow; } else { _estCount++; - Logging.instance.e( - "_estimate() retry _estCount=$_estCount", - ); - return await _estimate( - account, - recipientAddress, - ); + Logging.instance.e("_estimate() retry _estCount=$_estCount"); + return await _estimate(account, recipientAddress); } } } @@ -364,7 +351,7 @@ class TezosWallet extends Bip39Wallet { @override Future estimateFeeFor( Amount amount, - int feeRate, { + BigInt feeRate, { String recipientAddress = "tz1MXvDCyXSqBqXPNDcsdmVZKfoxL9FTHmp2", }) async { _hackedCheckTorNodePrefs(); @@ -376,9 +363,7 @@ class TezosWallet extends Bip39Wallet { } final myAddress = (await getCurrentReceivingAddress())!; - final account = await TezosAPI.getAccount( - myAddress.value, - ); + final account = await TezosAPI.getAccount(myAddress.value); try { final fees = await _estimate(account, recipientAddress); @@ -402,7 +387,7 @@ class TezosWallet extends Bip39Wallet { /// Not really used (yet) @override Future get fees async { - const feePerTx = 1; + final feePerTx = BigInt.one; return FeeObject( numberOfBlocksFast: 10, numberOfBlocksAverage: 10, @@ -418,10 +403,7 @@ class TezosWallet extends Bip39Wallet { _hackedCheckTorNodePrefs(); final currentNode = getCurrentNode(); return await TezosRpcAPI.testNetworkConnection( - nodeInfo: ( - host: currentNode.host, - port: currentNode.port, - ), + nodeInfo: (host: currentNode.host, port: currentNode.port), ); } @@ -518,16 +500,10 @@ class TezosWallet extends Bip39Wallet { _hackedCheckTorNodePrefs(); final currentNode = _xtzNode ?? getCurrentNode(); final height = await TezosRpcAPI.getChainHeight( - nodeInfo: ( - host: currentNode.host, - port: currentNode.port, - ), + nodeInfo: (host: currentNode.host, port: currentNode.port), ); - await info.updateCachedChainHeight( - newHeight: height!, - isar: mainDB.isar, - ); + await info.updateCachedChainHeight(newHeight: height!, isar: mainDB.isar); } catch (e, s) { Logging.instance.e( "Error occurred in tezos_wallet.dart while getting" @@ -540,8 +516,10 @@ class TezosWallet extends Bip39Wallet { @override Future updateNode() async { - _xtzNode = NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? + _xtzNode = + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? info.coin.defaultNode; await refresh(); @@ -550,9 +528,10 @@ class TezosWallet extends Bip39Wallet { @override NodeModel getCurrentNode() { return _xtzNode ??= - NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? - info.coin.defaultNode; + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? + info.coin.defaultNode; } @override @@ -590,10 +569,11 @@ class TezosWallet extends Bip39Wallet { type: txType, subType: TransactionSubType.none, amount: theTx.amountInMicroTez, - amountString: Amount( - rawValue: BigInt.from(theTx.amountInMicroTez), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + amountString: + Amount( + rawValue: BigInt.from(theTx.amountInMicroTez), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), fee: theTx.feeInMicroTez, height: theTx.height, isCancelled: false, diff --git a/lib/wallets/wallet/impl/wownero_wallet.dart b/lib/wallets/wallet/impl/wownero_wallet.dart index 73b1c4f7b..e1c09b693 100644 --- a/lib/wallets/wallet/impl/wownero_wallet.dart +++ b/lib/wallets/wallet/impl/wownero_wallet.dart @@ -14,7 +14,7 @@ class WowneroWallet extends LibMoneroWallet { : super(Wownero(network), lib_monero_compat.WalletType.wownero); @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { if (libMoneroWallet == null || syncStatus is! lib_monero_compat.SyncedSyncStatus) { return Amount.zeroWith(fractionDigits: cryptoCurrency.fractionDigits); @@ -22,7 +22,7 @@ class WowneroWallet extends LibMoneroWallet { lib_monero.TransactionPriority priority; FeeRateType feeRateType = FeeRateType.slow; - switch (feeRate) { + switch (feeRate.toInt()) { case 1: priority = lib_monero.TransactionPriority.low; feeRateType = FeeRateType.average; diff --git a/lib/wallets/wallet/impl/xelis_wallet.dart b/lib/wallets/wallet/impl/xelis_wallet.dart index 352b076f7..2605d40ca 100644 --- a/lib/wallets/wallet/impl/xelis_wallet.dart +++ b/lib/wallets/wallet/impl/xelis_wallet.dart @@ -622,9 +622,9 @@ class XelisWallet extends LibXelisWallet { numberOfBlocksFast: 10, numberOfBlocksAverage: 10, numberOfBlocksSlow: 10, - fast: 1, - medium: 1, - slow: 1, + fast: BigInt.one, + medium: BigInt.one, + slow: BigInt.one, ); } @@ -661,7 +661,7 @@ class XelisWallet extends LibXelisWallet { // Estimate fee using the shared method final boostedFee = await estimateFeeFor( totalSendAmount, - 1, + BigInt.one, feeMultiplier: 1.0, recipients: recipients, assetId: asset, @@ -701,7 +701,7 @@ class XelisWallet extends LibXelisWallet { @override Future estimateFeeFor( Amount amount, - int feeRate, { + BigInt feeRate, { double? feeMultiplier, List recipients = const [], String? assetId, diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index b21f5884f..b81a10d97 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -520,7 +520,11 @@ abstract class LibMoneroWallet trusted: node.trusted ?? false, useSSL: node.useSSL, socksProxyAddress: - node.forceNoTor ? null : proxy == null ? null : "${proxy.host.address}:${proxy.port}", + node.forceNoTor + ? null + : proxy == null + ? null + : "${proxy.host.address}:${proxy.port}", ); }); } else { @@ -531,7 +535,11 @@ abstract class LibMoneroWallet trusted: node.trusted ?? false, useSSL: node.useSSL, socksProxyAddress: - node.forceNoTor ? null : proxy == null ? null : "${proxy.host.address}:${proxy.port}", + node.forceNoTor + ? null + : proxy == null + ? null + : "${proxy.host.address}:${proxy.port}", ); } libMoneroWallet?.startSyncing(); @@ -1230,9 +1238,9 @@ abstract class LibMoneroWallet numberOfBlocksFast: 10, numberOfBlocksAverage: 15, numberOfBlocksSlow: 20, - fast: lib_monero.TransactionPriority.high.value, - medium: lib_monero.TransactionPriority.medium.value, - slow: lib_monero.TransactionPriority.normal.value, + fast: BigInt.from(lib_monero.TransactionPriority.high.value), + medium: BigInt.from(lib_monero.TransactionPriority.medium.value), + slow: BigInt.from(lib_monero.TransactionPriority.normal.value), ); @override diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index be21c5282..9ed0c9c6e 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -127,11 +127,7 @@ abstract class Wallet { await updateChainHeight(); } catch (e, s) { // do nothing on failure (besides logging) - Logging.instance.w( - "$e\n$s", - error: e, - stackTrace: s, - ); + Logging.instance.w("$e\n$s", error: e, stackTrace: s); } // return regardless of whether it was updated or not as we want a @@ -173,7 +169,8 @@ abstract class Wallet { value: viewOnlyData!.toJsonEncodedString(), ); } else if (wallet is MnemonicInterface) { - if (wallet is CryptonoteWallet || wallet is XelisWallet) { // + if (wallet is CryptonoteWallet || wallet is XelisWallet) { + // // currently a special case due to the xmr/wow/xelis libraries handling their // own mnemonic generation on new wallet creation // if its a restore we must set them @@ -238,10 +235,11 @@ abstract class Wallet { required NodeService nodeService, required Prefs prefs, }) async { - final walletInfo = await mainDB.isar.walletInfo - .where() - .walletIdEqualTo(walletId) - .findFirst(); + final walletInfo = + await mainDB.isar.walletInfo + .where() + .walletIdEqualTo(walletId) + .findFirst(); Logging.instance.i( "Wallet.load loading" @@ -270,10 +268,7 @@ abstract class Wallet { required EthereumWallet ethWallet, required EthContract contract, }) { - final Wallet wallet = EthTokenWallet( - ethWallet, - contract, - ); + final Wallet wallet = EthTokenWallet(ethWallet, contract); wallet.prefs = ethWallet.prefs; wallet.nodeService = ethWallet.nodeService; @@ -287,27 +282,19 @@ abstract class Wallet { // ========== Static Util ==================================================== // secure storage key - static String mnemonicKey({ - required String walletId, - }) => + static String mnemonicKey({required String walletId}) => "${walletId}_mnemonic"; // secure storage key - static String mnemonicPassphraseKey({ - required String walletId, - }) => + static String mnemonicPassphraseKey({required String walletId}) => "${walletId}_mnemonicPassphrase"; // secure storage key - static String privateKeyKey({ - required String walletId, - }) => + static String privateKeyKey({required String walletId}) => "${walletId}_privateKey"; // secure storage key - static String getViewOnlyWalletDataSecStoreKey({ - required String walletId, - }) => + static String getViewOnlyWalletDataSecStoreKey({required String walletId}) => "${walletId}_viewOnlyWalletData"; //============================================================================ @@ -321,9 +308,7 @@ abstract class Wallet { required NodeService nodeService, required Prefs prefs, }) async { - final Wallet wallet = _loadWallet( - walletInfo: walletInfo, - ); + final Wallet wallet = _loadWallet(walletInfo: walletInfo); wallet.prefs = prefs; wallet.nodeService = nodeService; @@ -339,9 +324,7 @@ abstract class Wallet { .._walletId = walletInfo.walletId; } - static Wallet _loadWallet({ - required WalletInfo walletInfo, - }) { + static Wallet _loadWallet({required WalletInfo walletInfo}) { final net = walletInfo.coin.network; switch (walletInfo.coin.runtimeType) { case const (Banano): @@ -421,12 +404,11 @@ abstract class Wallet { _periodicPingCheck(); // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); + _networkAliveTimer = Timer.periodic(Constants.networkAliveTimerDuration, ( + _, + ) async { + _periodicPingCheck(); + }); } void _periodicPingCheck() async { @@ -438,15 +420,12 @@ abstract class Wallet { final bool hasNetwork = await pingCheck(); if (_isConnected != hasNetwork) { - final NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; + final NodeConnectionStatus status = + hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - status, - walletId, - cryptoCurrency, - ), + NodeConnectionStatusChangedEvent(status, walletId, cryptoCurrency), ); _isConnected = hasNetwork; @@ -499,7 +478,7 @@ abstract class Wallet { /// updates the wallet info's cachedChainHeight Future updateChainHeight(); - Future estimateFeeFor(Amount amount, int feeRate); + Future estimateFeeFor(Amount amount, BigInt feeRate); Future get fees; @@ -518,7 +497,8 @@ abstract class Wallet { } NodeModel getCurrentNode() { - final node = nodeService.getPrimaryNodeFor(currency: cryptoCurrency) ?? + final node = + nodeService.getPrimaryNodeFor(currency: cryptoCurrency) ?? cryptoCurrency.defaultNode; return node; @@ -538,8 +518,9 @@ abstract class Wallet { ); if (shouldAutoSync) { - _periodicRefreshTimer ??= - Timer.periodic(const Duration(seconds: 150), (timer) async { + _periodicRefreshTimer ??= Timer.periodic(const Duration(seconds: 150), ( + timer, + ) async { // chain height check currently broken // if ((await chainHeight) != (await storedChainHeight)) { @@ -596,7 +577,8 @@ abstract class Wallet { } final start = DateTime.now(); - final viewOnly = this is ViewOnlyOptionInterface && + final viewOnly = + this is ViewOnlyOptionInterface && (this as ViewOnlyOptionInterface).isViewOnly; try { @@ -621,8 +603,9 @@ abstract class Wallet { final Set codesToCheck = {}; if (this is PaynymInterface && !viewOnly) { // isSegwit does not matter here at all - final myCode = - await (this as PaynymInterface).getPaymentCode(isSegwit: false); + final myCode = await (this as PaynymInterface).getPaymentCode( + isSegwit: false, + ); final nym = await PaynymIsApi().nym(myCode.toString()); if (nym.value != null) { @@ -685,8 +668,9 @@ abstract class Wallet { // TODO: [prio=low] handle this differently. Extra modification of this file for coin specific functionality should be avoided. if (!viewOnly && this is PaynymInterface && codesToCheck.isNotEmpty) { - await (this as PaynymInterface) - .checkForNotificationTransactionsTo(codesToCheck); + await (this as PaynymInterface).checkForNotificationTransactionsTo( + codesToCheck, + ); // check utxos again for notification outputs await updateUTXOs(); } @@ -746,10 +730,11 @@ abstract class Wallet { // Check if there's another wallet of this coin on the sync list. final List walletIds = []; for (final id in prefs.walletIdsSyncOnStartup) { - final wallet = mainDB.isar.walletInfo - .where() - .walletIdEqualTo(id) - .findFirstSync()!; + final wallet = + mainDB.isar.walletInfo + .where() + .walletIdEqualTo(id) + .findFirstSync()!; if (wallet.coin == cryptoCurrency) { walletIds.add(id); @@ -802,17 +787,11 @@ abstract class Wallet { return await mainDB.isar.addresses .buildQuery
( whereClauses: [ - IndexWhereClause.equalTo( - indexName: r"walletId", - value: [walletId], - ), + IndexWhereClause.equalTo(indexName: r"walletId", value: [walletId]), ], filter: filterOperation, sortBy: [ - const SortProperty( - property: r"derivationIndex", - sort: Sort.desc, - ), + const SortProperty(property: r"derivationIndex", sort: Sort.desc), ], ) .findFirst(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index e393ceb9d..8fe157a61 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -439,7 +439,7 @@ mixin ElectrumXInterface required BigInt satoshisBeingUsed, required List utxoSigningData, required int? satsPerVByte, - required int feeRatePerKB, + required BigInt feeRatePerKB, }) async { Logging.instance.d("Attempting to send all $cryptoCurrency"); if (txData.recipients!.length != 1) { @@ -1225,17 +1225,17 @@ mixin ElectrumXInterface Amount.fromDecimal( fast, fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), + ).raw, medium: Amount.fromDecimal( medium, fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), + ).raw, slow: Amount.fromDecimal( slow, fractionDigits: info.coin.fractionDigits, - ).raw.toInt(), + ).raw, ); Logging.instance.d("fetched fees: $feeObject"); @@ -1256,7 +1256,7 @@ mixin ElectrumXInterface } @override - Future estimateFeeFor(Amount amount, int feeRate) async { + Future estimateFeeFor(Amount amount, BigInt feeRate) async { final available = info.cachedBalance.spendable; final utxos = _spendableUTXOs(await mainDB.getUTXOs(walletId).findAll()); @@ -1713,7 +1713,7 @@ mixin ElectrumXInterface } final result = await coinSelection( - txData: txData.copyWith(feeRateAmount: -1), + txData: txData.copyWith(feeRateAmount: BigInt.from(-1)), isSendAll: isSendAll, utxos: utxos?.toList(), coinControl: coinControl, @@ -1729,10 +1729,10 @@ mixin ElectrumXInterface } return result; - } else if (feeRateType is FeeRateType || feeRateAmount is int) { - late final int rate; + } else if (feeRateType is FeeRateType || feeRateAmount is BigInt) { + late final BigInt rate; if (feeRateType is FeeRateType) { - int fee = 0; + BigInt fee = BigInt.zero; final feeObject = await fees; switch (feeRateType) { case FeeRateType.fast: @@ -1749,7 +1749,7 @@ mixin ElectrumXInterface } rate = fee; } else { - rate = feeRateAmount as int; + rate = feeRateAmount!; } // check for send all @@ -1832,8 +1832,8 @@ mixin ElectrumXInterface // =========================================================================== // ========== Interface functions ============================================ - int estimateTxFee({required int vSize, required int feeRatePerKB}); - Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB); + int estimateTxFee({required int vSize, required BigInt feeRatePerKB}); + Amount roughFeeEstimate(int inputCount, int outputCount, BigInt feeRatePerKB); Future> fetchAddressesForElectrumXScan(); @@ -1864,7 +1864,10 @@ mixin ElectrumXInterface .toList(); } - Future _sweepAllEstimate(int feeRate, List usableUTXOs) async { + Future _sweepAllEstimate( + BigInt feeRate, + List usableUTXOs, + ) async { final available = usableUTXOs .map((e) => BigInt.from(e.value)) .fold(BigInt.zero, (p, e) => p + e); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart index 148bd61a2..1a7937113 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart @@ -48,46 +48,50 @@ mixin LelantusInterface } Future> _getLelantusEntry() async { - final List lelantusCoins = await mainDB.isar.lelantusCoins - .where() - .walletIdEqualTo(walletId) - .filter() - .isUsedEqualTo(false) - .not() - .group( - (q) => q - .valueEqualTo("0") - .or() - .anonymitySetIdEqualTo(LelantusFfiWrapper.ANONYMITY_SET_EMPTY_ID), - ) - .findAll(); + final List lelantusCoins = + await mainDB.isar.lelantusCoins + .where() + .walletIdEqualTo(walletId) + .filter() + .isUsedEqualTo(false) + .not() + .group( + (q) => q + .valueEqualTo("0") + .or() + .anonymitySetIdEqualTo( + LelantusFfiWrapper.ANONYMITY_SET_EMPTY_ID, + ), + ) + .findAll(); final root = await getRootHDNode(); - final waitLelantusEntries = lelantusCoins.map((coin) async { - final derivePath = cryptoCurrency.constructDerivePath( - derivePathType: DerivePathType.bip44, - chain: LelantusFfiWrapper.MINT_INDEX, - index: coin.mintIndex, - ); + final waitLelantusEntries = + lelantusCoins.map((coin) async { + final derivePath = cryptoCurrency.constructDerivePath( + derivePathType: DerivePathType.bip44, + chain: LelantusFfiWrapper.MINT_INDEX, + index: coin.mintIndex, + ); - try { - final keyPair = root.derivePath(derivePath); - final String privateKey = keyPair.privateKey.data.toHex; - return lelantus.DartLelantusEntry( - coin.isUsed ? 1 : 0, - 0, - coin.anonymitySetId, - int.parse(coin.value), - coin.mintIndex, - privateKey, - ); - } catch (e, s) { - Logging.instance.e("error bad key"); - Logging.instance.t("error bad key", error: e, stackTrace: s); - return lelantus.DartLelantusEntry(1, 0, 0, 0, 0, ''); - } - }).toList(); + try { + final keyPair = root.derivePath(derivePath); + final String privateKey = keyPair.privateKey.data.toHex; + return lelantus.DartLelantusEntry( + coin.isUsed ? 1 : 0, + 0, + coin.anonymitySetId, + int.parse(coin.value), + coin.mintIndex, + privateKey, + ); + } catch (e, s) { + Logging.instance.e("error bad key"); + Logging.instance.t("error bad key", error: e, stackTrace: s); + return lelantus.DartLelantusEntry(1, 0, 0, 0, 0, ''); + } + }).toList(); final lelantusEntries = await Future.wait(waitLelantusEntries); @@ -100,13 +104,9 @@ mixin LelantusInterface return lelantusEntries; } - Future prepareSendLelantus({ - required TxData txData, - }) async { + Future prepareSendLelantus({required TxData txData}) async { if (txData.recipients!.length != 1) { - throw Exception( - "Lelantus send requires a single recipient", - ); + throw Exception("Lelantus send requires a single recipient"); } if (txData.recipients!.first.amount.raw > @@ -125,8 +125,9 @@ mixin LelantusInterface isSendAll = true; } - final lastUsedIndex = - await mainDB.getHighestUsedMintIndex(walletId: walletId); + final lastUsedIndex = await mainDB.getHighestUsedMintIndex( + walletId: walletId, + ); final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; final root = await getRootHDNode(); @@ -174,18 +175,15 @@ mixin LelantusInterface } } - Future confirmSendLelantus({ - required TxData txData, - }) async { + Future confirmSendLelantus({required TxData txData}) async { final latestSetId = await electrumXClient.getLelantusLatestCoinId(); - final txid = await electrumXClient.broadcastTransaction( - rawTx: txData.raw!, - ); + final txid = await electrumXClient.broadcastTransaction(rawTx: txData.raw!); assert(txid == txData.txid!); - final lastUsedIndex = - await mainDB.getHighestUsedMintIndex(walletId: walletId); + final lastUsedIndex = await mainDB.getHighestUsedMintIndex( + walletId: walletId, + ); final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; if (txData.spendCoinIndexes != null) { @@ -197,10 +195,11 @@ mixin LelantusInterface // Update all of the coins that have been spent. for (final index in spentCoinIndexes) { - final possibleCoin = await mainDB.isar.lelantusCoins - .where() - .mintIndexWalletIdEqualTo(index, walletId) - .findFirst(); + final possibleCoin = + await mainDB.isar.lelantusCoins + .where() + .mintIndexWalletIdEqualTo(index, walletId) + .findFirst(); if (possibleCoin != null) { updatedCoins.add(possibleCoin.copyWith(isUsed: true)); @@ -232,11 +231,7 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.put(jmint); }); } catch (e, s) { - Logging.instance.e( - "", - error: e, - stackTrace: s, - ); + Logging.instance.e("", error: e, stackTrace: s); rethrow; } @@ -264,7 +259,8 @@ mixin LelantusInterface numberOfMessages: null, ); - final transactionAddress = await mainDB + final transactionAddress = + await mainDB .getAddresses(walletId) .filter() .valueEqualTo(txData.recipients!.first.address) @@ -311,18 +307,12 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(updatedCoins); }); } catch (e, s) { - Logging.instance.e( - "", - error: e, - stackTrace: s, - ); + Logging.instance.e("", error: e, stackTrace: s); rethrow; } } - return txData.copyWith( - txid: txid, - ); + return txData.copyWith(txid: txid); } Future>> fastFetch(List allTxHashes) async { @@ -334,10 +324,10 @@ mixin LelantusInterface for (final txHash in allTxHashes) { final Future> transactionFuture = electrumXCachedClient.getTransaction( - txHash: txHash, - verbose: true, - cryptoCurrency: cryptoCurrency, - ); + txHash: txHash, + verbose: true, + cryptoCurrency: cryptoCurrency, + ); transactionFutures.add(transactionFuture); currentFutureCount++; if (currentFutureCount > futureLimit) { @@ -373,8 +363,9 @@ mixin LelantusInterface ) async { try { final Map txs = {}; - final List> allTransactions = - await fastFetch(transactions); + final List> allTransactions = await fastFetch( + transactions, + ); for (int i = 0; i < allTransactions.length; i++) { try { @@ -397,16 +388,18 @@ mixin LelantusInterface final txn = Transaction( walletId: walletId, txid: tx["txid"] as String, - timestamp: tx["time"] as int? ?? + timestamp: + tx["time"] as int? ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000), type: TransactionType.outgoing, subType: TransactionSubType.join, amount: amount.raw.toInt(), amountString: amount.toJsonString(), - fee: Amount.fromDecimal( - Decimal.parse(tx["fees"].toString()), - fractionDigits: cryptoCurrency.fractionDigits, - ).raw.toInt(), + fee: + Amount.fromDecimal( + Decimal.parse(tx["fees"].toString()), + fractionDigits: cryptoCurrency.fractionDigits, + ).raw.toInt(), height: tx["height"] as int?, isCancelled: false, isLelantus: true, @@ -418,7 +411,8 @@ mixin LelantusInterface numberOfMessages: null, ); - final address = await mainDB + final address = + await mainDB .getAddresses(walletId) .filter() .valueEqualTo(tx["address"] as String) @@ -488,8 +482,10 @@ mixin LelantusInterface final Map setDataMap = {}; final anonymitySets = await fetchAnonymitySets(); for (int setId = 1; setId <= latestSetId; setId++) { - final setData = anonymitySets - .firstWhere((element) => element["setId"] == setId, orElse: () => {}); + final setData = anonymitySets.firstWhere( + (element) => element["setId"] == setId, + orElse: () => {}, + ); if (setData.isNotEmpty) { setDataMap[setId] = setData; @@ -500,22 +496,22 @@ mixin LelantusInterface // TODO: verify this function does what we think it does Future refreshLelantusData() async { - final lelantusCoins = await mainDB.isar.lelantusCoins - .where() - .walletIdEqualTo(walletId) - .filter() - .isUsedEqualTo(false) - .not() - .valueEqualTo(0.toString()) - .findAll(); + final lelantusCoins = + await mainDB.isar.lelantusCoins + .where() + .walletIdEqualTo(walletId) + .filter() + .isUsedEqualTo(false) + .not() + .valueEqualTo(0.toString()) + .findAll(); final List updatedCoins = []; final usedSerialNumbersSet = (await electrumXCachedClient.getUsedCoinSerials( - cryptoCurrency: info.coin, - )) - .toSet(); + cryptoCurrency: info.coin, + )).toSet(); final root = await getRootHDNode(); @@ -563,11 +559,7 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(updatedCoins); }); } catch (e, s) { - Logging.instance.f( - " ", - error: e, - stackTrace: s, - ); + Logging.instance.f(" ", error: e, stackTrace: s); rethrow; } } @@ -607,13 +599,14 @@ mixin LelantusInterface final currentHeight = await chainHeight; - final txns = await mainDB - .getTransactions(walletId) - .filter() - .isLelantusIsNull() - .or() - .isLelantusEqualTo(false) - .findAll(); + final txns = + await mainDB + .getTransactions(walletId) + .filter() + .isLelantusIsNull() + .or() + .isLelantusEqualTo(false) + .findAll(); // TODO: [prio=high] shouldn't these be v2? If it doesn't matter than we can get rid of this logic // Edit the receive transactions with the mint fees. @@ -663,10 +656,11 @@ mixin LelantusInterface type: inputTx.type, subType: TransactionSubType.mint, amount: inputTx.amount, - amountString: Amount( - rawValue: BigInt.from(inputTx.amount), - fractionDigits: cryptoCurrency.fractionDigits, - ).toJsonString(), + amountString: + Amount( + rawValue: BigInt.from(inputTx.amount), + fractionDigits: cryptoCurrency.fractionDigits, + ).toJsonString(), fee: sharedFee, height: inputTx.height, isCancelled: false, @@ -706,11 +700,7 @@ mixin LelantusInterface await mainDB.isar.lelantusCoins.putAll(result.lelantusCoins); }); } catch (e, s) { - Logging.instance.e( - "", - error: e, - stackTrace: s, - ); + Logging.instance.e("", error: e, stackTrace: s); // don't just rethrow since isar likes to strip stack traces for some reason throw Exception("e=$e & s=$s"); } @@ -722,13 +712,12 @@ mixin LelantusInterface } // Create the joinsplit transactions. - final spendTxs = await getJMintTransactions( - result.spendTxIds, - ); + final spendTxs = await getJMintTransactions(result.spendTxIds); Logging.instance.d("lelantus spendTxs: $spendTxs"); for (final element in spendTxs.entries) { - final address = element.value.address.value ?? + final address = + element.value.address.value ?? data[element.value.txid]?.item1 ?? element.key; // Address( @@ -747,8 +736,9 @@ mixin LelantusInterface for (final value in data.values) { final transactionAddress = value.item1!; - final outs = - value.item2.outputs.where((_) => true).toList(growable: false); + final outs = value.item2.outputs + .where((_) => true) + .toList(growable: false); final ins = value.item2.inputs.where((_) => true).toList(growable: false); txnsData.add( @@ -778,9 +768,7 @@ mixin LelantusInterface wif: cryptoCurrency.networkParams.wifPrefix, ); - final txb = bitcoindart.TransactionBuilder( - network: convertedNetwork, - ); + final txb = bitcoindart.TransactionBuilder(network: convertedNetwork); txb.setVersion(2); final int height = await chainHeight; @@ -794,42 +782,40 @@ mixin LelantusInterface switch (signingData[i].derivePathType) { case DerivePathType.bip44: - data = bitcoindart - .P2PKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; + data = + bitcoindart + .P2PKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip49: - final p2wpkh = bitcoindart - .P2WPKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; - data = bitcoindart - .P2SH( - data: bitcoindart.PaymentData(redeem: p2wpkh), - network: convertedNetwork, - ) - .data; + final p2wpkh = + bitcoindart + .P2WPKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; + data = + bitcoindart + .P2SH( + data: bitcoindart.PaymentData(redeem: p2wpkh), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip84: - data = bitcoindart - .P2WPKH( - data: bitcoindart.PaymentData( - pubkey: pubKey, - ), - network: convertedNetwork, - ) - .data; + data = + bitcoindart + .P2WPKH( + data: bitcoindart.PaymentData(pubkey: pubKey), + network: convertedNetwork, + ) + .data; break; case DerivePathType.bip86: @@ -851,8 +837,9 @@ mixin LelantusInterface for (final mintsElement in txData.mintsMapLelantus!) { Logging.instance.d("using $mintsElement"); - final Uint8List mintu8 = - Format.stringToUint8List(mintsElement['script'] as String); + final Uint8List mintu8 = Format.stringToUint8List( + mintsElement['script'] as String, + ); txb.addOutput(mintu8, mintsElement['value'] as int); } @@ -922,11 +909,12 @@ mixin LelantusInterface /// Returns the mint transaction hex to mint all of the available funds. Future _mintSelection() async { final currentChainHeight = await chainHeight; - final List availableOutputs = await mainDB - .getUTXOs(walletId) - .filter() - .isBlockedEqualTo(false) - .findAll(); + final List availableOutputs = + await mainDB + .getUTXOs(walletId) + .filter() + .isBlockedEqualTo(false) + .findAll(); final List spendableOutputs = []; // Build list of spendable outputs and totaling their satoshi amount @@ -944,21 +932,23 @@ mixin LelantusInterface } } - final lelantusCoins = await mainDB.isar.lelantusCoins - .where() - .walletIdEqualTo(walletId) - .filter() - .not() - .valueEqualTo(0.toString()) - .findAll(); - - final data = await mainDB - .getTransactions(walletId) - .filter() - .isLelantusIsNull() - .or() - .isLelantusEqualTo(false) - .findAll(); + final lelantusCoins = + await mainDB.isar.lelantusCoins + .where() + .walletIdEqualTo(walletId) + .filter() + .not() + .valueEqualTo(0.toString()) + .findAll(); + + final data = + await mainDB + .getTransactions(walletId) + .filter() + .isLelantusIsNull() + .or() + .isLelantusEqualTo(false) + .findAll(); for (final value in data) { if (value.inputs.isNotEmpty) { @@ -969,8 +959,9 @@ mixin LelantusInterface orElse: () => null, ) != null) { - spendableOutputs - .removeWhere((output) => output!.txid == element.txid); + spendableOutputs.removeWhere( + (output) => output!.txid == element.txid, + ); } } } @@ -1005,10 +996,11 @@ mixin LelantusInterface final feesObject = await fees; - final Decimal fastFee = Amount( - rawValue: BigInt.from(feesObject.fast), - fractionDigits: cryptoCurrency.fractionDigits, - ).decimal; + final Decimal fastFee = + Amount( + rawValue: feesObject.fast, + fractionDigits: cryptoCurrency.fractionDigits, + ).decimal; int firoFee = (dvSize * fastFee * Decimal.fromInt(100000)).toDouble().ceil(); // int firoFee = (vSize * feesObject.fast * (1 / 1000.0) * 100000000).ceil(); @@ -1022,9 +1014,7 @@ mixin LelantusInterface final mintsWithFee = await _createMintsFromAmount(satoshiAmountToSend); txData = await buildMintTransaction( - txData: txData.copyWith( - mintsMapLelantus: mintsWithFee, - ), + txData: txData.copyWith(mintsMapLelantus: mintsWithFee), ); return txData; @@ -1039,8 +1029,9 @@ mixin LelantusInterface int tmpTotal = total; int counter = 0; - final lastUsedIndex = - await mainDB.getHighestUsedMintIndex(walletId: walletId); + final lastUsedIndex = await mainDB.getHighestUsedMintIndex( + walletId: walletId, + ); final nextFreeMintIndex = (lastUsedIndex ?? 0) + 1; final isTestnet = cryptoCurrency.network.isTestNet; @@ -1118,12 +1109,9 @@ mixin LelantusInterface isTestNet: isTestnet, ); - mints.add({ - "value": mintValue, - "script": mint, - "index": index, - }); - tmpTotal = tmpTotal - + mints.add({"value": mintValue, "script": mint, "index": index}); + tmpTotal = + tmpTotal - (isTestnet ? LelantusFfiWrapper.MINT_LIMIT_TESTNET : LelantusFfiWrapper.MINT_LIMIT); @@ -1156,32 +1144,29 @@ mixin LelantusInterface // call to super to update transparent balance final normalBalanceFuture = super.updateBalance(); - final lelantusCoins = await mainDB.isar.lelantusCoins - .where() - .walletIdEqualTo(walletId) - .filter() - .isUsedEqualTo(false) - .not() - .valueEqualTo(0.toString()) - .findAll(); + final lelantusCoins = + await mainDB.isar.lelantusCoins + .where() + .walletIdEqualTo(walletId) + .filter() + .isUsedEqualTo(false) + .not() + .valueEqualTo(0.toString()) + .findAll(); final currentChainHeight = await chainHeight; int intLelantusBalance = 0; int unconfirmedLelantusBalance = 0; for (final lelantusCoin in lelantusCoins) { - final Transaction? txn = mainDB.isar.transactions - .where() - .txidWalletIdEqualTo( - lelantusCoin.txid, - walletId, - ) - .findFirstSync(); + final Transaction? txn = + mainDB.isar.transactions + .where() + .txidWalletIdEqualTo(lelantusCoin.txid, walletId) + .findFirstSync(); if (txn == null) { - Logging.instance.e( - "Transaction not found in DB for lelantus coin", - ); + Logging.instance.e("Transaction not found in DB for lelantus coin"); Logging.instance.d( "Transaction not found in DB for lelantus coin: $lelantusCoin", ); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart index 1d725afb4..4c3233de7 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/nano_interface.dart @@ -28,9 +28,7 @@ import '../intermediate/bip39_wallet.dart'; const _kWorkServer = "https://nodes.nanswap.com/XNO"; Map _buildHeaders(String url) { - final result = { - 'Content-type': 'application/json', - }; + final result = {'Content-type': 'application/json'}; if (url case "https://nodes.nanswap.com/XNO" || "https://nodes.nanswap.com/BAN") { result["nodes-api-key"] = kNanoSwapRpcApiKey; @@ -52,28 +50,24 @@ mixin NanoInterface on Bip39Wallet { _hackedCheckTorNodePrefs(); return _httpClient .post( - url: Uri.parse(_kWorkServer), // this should be a - headers: _buildHeaders(_kWorkServer), - body: json.encode( - { - "action": "work_generate", - "hash": hash, - }, - ), - proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null, - ) + url: Uri.parse(_kWorkServer), // this should be a + headers: _buildHeaders(_kWorkServer), + body: json.encode({"action": "work_generate", "hash": hash}), + proxyInfo: + prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null, + ) .then((_httpClient) { - if (_httpClient.code == 200) { - final Map decoded = - json.decode(_httpClient.body) as Map; - if (decoded.containsKey("error")) { - throw Exception("Received error ${decoded["error"]}"); - } - return decoded["work"] as String?; - } else { - throw Exception("Received error ${_httpClient.code}"); - } - }); + if (_httpClient.code == 200) { + final Map decoded = + json.decode(_httpClient.body) as Map; + if (decoded.containsKey("error")) { + throw Exception("Received error ${decoded["error"]}"); + } + return decoded["work"] as String?; + } else { + throw Exception("Received error ${_httpClient.code}"); + } + }); } Future _getPrivateKeyFromMnemonic() async { @@ -87,8 +81,10 @@ mixin NanoInterface on Bip39Wallet { await _getPrivateKeyFromMnemonic(), ); - final addressString = - NanoAccounts.createAccount(cryptoCurrency.nanoAccountType, publicKey); + final addressString = NanoAccounts.createAccount( + cryptoCurrency.nanoAccountType, + publicKey, + ); return Address( walletId: walletId, @@ -146,8 +142,9 @@ mixin NanoInterface on Bip39Wallet { ); final balanceData = jsonDecode(balanceResponse.body); - final BigInt currentBalance = - BigInt.parse(balanceData["balance"].toString()); + final BigInt currentBalance = BigInt.parse( + balanceData["balance"].toString(), + ); final BigInt txAmount = BigInt.parse(amountRaw); final BigInt balanceAfterTx = currentBalance + txAmount; @@ -162,16 +159,19 @@ mixin NanoInterface on Bip39Wallet { // link = send block hash: final String link = blockHash; // this "linkAsAccount" is meaningless: - final String linkAsAccount = - NanoAccounts.createAccount(NanoAccountType.BANANO, blockHash); + final String linkAsAccount = NanoAccounts.createAccount( + NanoAccountType.BANANO, + blockHash, + ); // construct the receive block: final Map receiveBlock = { "type": "state", "account": publicAddress, - "previous": openBlock - ? "0000000000000000000000000000000000000000000000000000000000000000" - : frontier, + "previous": + openBlock + ? "0000000000000000000000000000000000000000000000000000000000000000" + : frontier, "representative": representative, "balance": balanceAfterTx.toString(), "link": link, @@ -320,8 +320,10 @@ mixin NanoInterface on Bip39Wallet { @override Future updateNode() async { - _cachedNode = NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? + _cachedNode = + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? info.coin.defaultNode; unawaited(refresh()); @@ -330,8 +332,9 @@ mixin NanoInterface on Bip39Wallet { @override NodeModel getCurrentNode() { return _cachedNode ?? - NodeService(secureStorageInterface: secureStorageInterface) - .getPrimaryNodeFor(currency: info.coin) ?? + NodeService( + secureStorageInterface: secureStorageInterface, + ).getPrimaryNodeFor(currency: info.coin) ?? info.coin.defaultNode; } @@ -365,11 +368,7 @@ mixin NanoInterface on Bip39Wallet { final response = await _httpClient.post( url: uri, headers: _buildHeaders(node.host), - body: jsonEncode( - { - "action": "version", - }, - ), + body: jsonEncode({"action": "version"}), proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null, ); @@ -485,15 +484,9 @@ mixin NanoInterface on Bip39Wallet { } // return the hash of the transaction: - return txData.copyWith( - txid: decoded["hash"].toString(), - ); + return txData.copyWith(txid: decoded["hash"].toString()); } catch (e, s) { - Logging.instance.e( - "Error sending transaction", - error: e, - stackTrace: s, - ); + Logging.instance.e("Error sending transaction", error: e, stackTrace: s); rethrow; } } @@ -544,8 +537,9 @@ mixin NanoInterface on Bip39Wallet { ); // this should really have proper type checking and error propagation but I'm out of time - final newData = - Map.from((await jsonDecode(response.body)) as Map); + final newData = Map.from( + (await jsonDecode(response.body)) as Map, + ); if (newData["previous"] is String) { if (data?["history"] is List) { @@ -572,9 +566,10 @@ mixin NanoInterface on Bip39Wallet { final data = await _fetchAll(publicAddress, null, null); - final transactions = data["history"] is List - ? data["history"] as List - : []; + final transactions = + data["history"] is List + ? data["history"] as List + : []; if (transactions.isEmpty) { return; } else { @@ -612,17 +607,18 @@ mixin NanoInterface on Bip39Wallet { numberOfMessages: null, ); - final Address address = transactionType == TransactionType.incoming - ? receivingAddress - : Address( - walletId: walletId, - publicKey: [], - value: tx["account"].toString(), - derivationIndex: 0, - derivationPath: null, - type: info.mainAddressType, - subType: AddressSubType.nonWallet, - ); + final Address address = + transactionType == TransactionType.incoming + ? receivingAddress + : Address( + walletId: walletId, + publicKey: [], + value: tx["account"].toString(), + derivationIndex: 0, + derivationPath: null, + type: info.mainAddressType, + subType: AddressSubType.nonWallet, + ); final Tuple2 tuple = Tuple2(transaction, address); transactionList.add(tuple); } @@ -653,8 +649,9 @@ mixin NanoInterface on Bip39Wallet { final data = jsonDecode(response.body); final balance = Balance( total: Amount( - rawValue: (BigInt.parse(data["balance"].toString()) + - BigInt.parse(data["receivable"].toString())), + rawValue: + (BigInt.parse(data["balance"].toString()) + + BigInt.parse(data["receivable"].toString())), fractionDigits: cryptoCurrency.fractionDigits, ), spendable: Amount( @@ -703,10 +700,8 @@ mixin NanoInterface on Bip39Wallet { ); final infoData = jsonDecode(infoResponse.body); - final height = int.tryParse( - infoData["confirmation_height"].toString(), - ) ?? - 0; + final height = + int.tryParse(infoData["confirmation_height"].toString()) ?? 0; await info.updateCachedChainHeight(newHeight: height, isar: mainDB.isar); } catch (e, s) { @@ -734,21 +729,21 @@ mixin NanoInterface on Bip39Wallet { @override // nano has no fees - Future estimateFeeFor(Amount amount, int feeRate) async => Amount( - rawValue: BigInt.from(0), - fractionDigits: cryptoCurrency.fractionDigits, - ); + Future estimateFeeFor(Amount amount, BigInt feeRate) async => Amount( + rawValue: BigInt.from(0), + fractionDigits: cryptoCurrency.fractionDigits, + ); @override // nano has no fees Future get fees async => FeeObject( - numberOfBlocksFast: 1, - numberOfBlocksAverage: 1, - numberOfBlocksSlow: 1, - fast: 0, - medium: 0, - slow: 0, - ); + numberOfBlocksFast: 1, + numberOfBlocksAverage: 1, + numberOfBlocksSlow: 1, + fast: BigInt.zero, + medium: BigInt.zero, + slow: BigInt.zero, + ); void _hackedCheckTorNodePrefs() { final node = getCurrentNode(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart index 5c0ace879..1bfb3a2a3 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart @@ -457,7 +457,7 @@ mixin PaynymInterface } Future prepareNotificationTx({ - required int selectedTxFeeRate, + required BigInt selectedTxFeeRate, required String targetPaymentCodeString, int additionalOutputs = 0, List? utxos, diff --git a/lib/widgets/desktop/desktop_fee_dialog.dart b/lib/widgets/desktop/desktop_fee_dialog.dart index 713aaa109..aa3175cac 100644 --- a/lib/widgets/desktop/desktop_fee_dialog.dart +++ b/lib/widgets/desktop/desktop_fee_dialog.dart @@ -4,8 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/models.dart'; import '../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; -import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart'; import '../../providers/global/wallets_provider.dart'; +import '../../providers/ui/fee_rate_type_state_provider.dart'; +import '../../providers/wallet/desktop_fee_providers.dart'; import '../../providers/wallet/public_private_balance_state_provider.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/amount/amount.dart'; @@ -43,7 +44,7 @@ class _DesktopFeeDialogState extends ConsumerState { Future feeFor({ required Amount amount, required FeeRateType feeRateType, - required int feeRate, + required BigInt feeRate, required CryptoCurrency coin, }) async { switch (feeRateType) { @@ -62,26 +63,30 @@ class _DesktopFeeDialogState extends ConsumerState { if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.high.value, + BigInt.from(lib_monero.TransactionPriority.high.value), ); ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - await wallet.estimateFeeFor(amount, feeRate); + ref.read(feeSheetSessionCacheProvider).fast[amount] = await wallet + .estimateFeeFor(amount, feeRate); } } else { final tokenWallet = ref.read(pCurrentTokenWallet)!; @@ -112,21 +117,25 @@ class _DesktopFeeDialogState extends ConsumerState { if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.medium.value, + BigInt.from(lib_monero.TransactionPriority.medium.value), ); ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } else { @@ -162,26 +171,30 @@ class _DesktopFeeDialogState extends ConsumerState { if (coin is Monero || coin is Wownero) { final fee = await wallet.estimateFeeFor( amount, - lib_monero.TransactionPriority.normal.value, + BigInt.from(lib_monero.TransactionPriority.normal.value), ); ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else if (coin is Firo) { final Amount fee; switch (ref.read(publicPrivateBalanceStateProvider.state).state) { case FiroType.spark: - fee = - await (wallet as FiroWallet).estimateFeeForSpark(amount); + fee = await (wallet as FiroWallet).estimateFeeForSpark( + amount, + ); case FiroType.lelantus: - fee = await (wallet as FiroWallet) - .estimateFeeForLelantus(amount); + fee = await (wallet as FiroWallet).estimateFeeForLelantus( + amount, + ); case FiroType.public: - fee = await (wallet as FiroWallet) - .estimateFeeFor(amount, feeRate); + fee = await (wallet as FiroWallet).estimateFeeFor( + amount, + feeRate, + ); } ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - await wallet.estimateFeeFor(amount, feeRate); + ref.read(feeSheetSessionCacheProvider).slow[amount] = await wallet + .estimateFeeFor(amount, feeRate); } } else { final tokenWallet = ref.read(pCurrentTokenWallet)!; @@ -214,9 +227,7 @@ class _DesktopFeeDialogState extends ConsumerState { maxHeight: double.infinity, child: FutureBuilder( future: ref.watch( - pWallets.select( - (value) => value.getWallet(walletId).fees, - ), + pWallets.select((value) => value.getWallet(walletId).fees), ), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done && @@ -255,9 +266,7 @@ class _DesktopFeeDialogState extends ConsumerState { ), ), ), - const SizedBox( - height: 16, - ), + const SizedBox(height: 16), ], ); }, @@ -283,9 +292,10 @@ class DesktopFeeItem extends ConsumerStatefulWidget { final Future Function({ required Amount amount, required FeeRateType feeRateType, - required int feeRate, + required BigInt feeRate, required CryptoCurrency coin, - }) feeFor; + }) + feeFor; final bool isSelected; final bool isButton; @@ -337,19 +347,18 @@ class _DesktopFeeItemState extends ConsumerState { return ConditionalParent( condition: widget.isButton, - builder: (child) => MaterialButton( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onPressed: () { - Navigator.of(context).pop( - ( - widget.feeRateType, - feeString, - timeString, - ), - ); - }, - child: child, - ), + builder: + (child) => MaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + onPressed: () { + ref.read(feeRateTypeDesktopStateProvider.state).state = + widget.feeRateType; + Navigator.of( + context, + ).pop((widget.feeRateType, feeString, timeString)); + }, + child: child, + ), child: Builder( builder: (_) { if (!widget.isButton) { @@ -360,19 +369,14 @@ class _DesktopFeeItemState extends ConsumerState { ); if ((coin is Firo) && ref.watch(publicPrivateBalanceStateProvider.state).state == - "Private") { + FiroType.lelantus) { return Text( - "~${ref.watch(pAmountFormatter(coin)).format( - Amount( - rawValue: BigInt.parse("3794"), - fractionDigits: coin.fractionDigits, - ), - indicatePrecisionLoss: false, - )}", + "~${ref.watch(pAmountFormatter(coin)).format(Amount(rawValue: BigInt.parse("3794"), fractionDigits: coin.fractionDigits), indicatePrecisionLoss: false)}", style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, ), textAlign: TextAlign.left, ); @@ -385,11 +389,13 @@ class _DesktopFeeItemState extends ConsumerState { children: [ Text( widget.feeRateType.prettyName, - style: - STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, ), textAlign: TextAlign.left, ), @@ -405,9 +411,10 @@ class _DesktopFeeItemState extends ConsumerState { return AnimatedText( stringsToLoopThrough: stringsToLoopThrough, style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, ), ); } else { @@ -415,9 +422,10 @@ class _DesktopFeeItemState extends ConsumerState { future: widget.feeFor( coin: wallet.info.coin, feeRateType: widget.feeRateType, - feeRate: widget.feeRateType == FeeRateType.fast - ? widget.feeObject!.fast - : widget.feeRateType == FeeRateType.slow + feeRate: + widget.feeRateType == FeeRateType.fast + ? widget.feeObject!.fast + : widget.feeRateType == FeeRateType.slow ? widget.feeObject!.slow : widget.feeObject!.medium, amount: ref.watch(sendAmountProvider.state).state, @@ -425,44 +433,47 @@ class _DesktopFeeItemState extends ConsumerState { builder: (_, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { - feeString = "${widget.feeRateType.prettyName} " - "(~${ref.watch(pAmountFormatter(wallet.info.coin)).format( - snapshot.data!, - indicatePrecisionLoss: false, - )})"; - - timeString = wallet.info.coin is Ethereum - ? "" - : estimatedTimeToBeIncludedInNextBlock( - wallet.info.coin.targetBlockTimeSeconds, - widget.feeRateType == FeeRateType.fast - ? widget.feeObject!.numberOfBlocksFast - : widget.feeRateType == FeeRateType.slow - ? widget.feeObject!.numberOfBlocksSlow - : widget.feeObject!.numberOfBlocksAverage, - ); + feeString = + "${widget.feeRateType.prettyName} " + "(~${ref.watch(pAmountFormatter(wallet.info.coin)).format(snapshot.data!, indicatePrecisionLoss: false)})"; + + timeString = + wallet.info.coin is Ethereum + ? "" + : estimatedTimeToBeIncludedInNextBlock( + wallet.info.coin.targetBlockTimeSeconds, + widget.feeRateType == FeeRateType.fast + ? widget.feeObject!.numberOfBlocksFast + : widget.feeRateType == FeeRateType.slow + ? widget.feeObject!.numberOfBlocksSlow + : widget.feeObject!.numberOfBlocksAverage, + ); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( feeString!, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, ), textAlign: TextAlign.left, ), if (widget.feeObject != null) Text( timeString!, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, ), ), ], @@ -470,11 +481,13 @@ class _DesktopFeeItemState extends ConsumerState { } else { return AnimatedText( stringsToLoopThrough: stringsToLoopThrough, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveText, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, ), ); } diff --git a/lib/widgets/eth_fee_form.dart b/lib/widgets/eth_fee_form.dart new file mode 100644 index 000000000..2f4e2889e --- /dev/null +++ b/lib/widgets/eth_fee_form.dart @@ -0,0 +1,310 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; + +import '../services/ethereum/ethereum_api.dart'; +import '../themes/stack_colors.dart'; +import '../utilities/constants.dart'; +import '../utilities/text_styles.dart'; +import '../utilities/util.dart'; +import 'stack_text_field.dart'; + +@immutable +class EthEIP1559Fee { + final Decimal maxBaseFeeGwei; + final Decimal priorityFeeGwei; + final int gasLimit; + + const EthEIP1559Fee({ + required this.maxBaseFeeGwei, + required this.priorityFeeGwei, + required this.gasLimit, + }); + + BigInt get maxBaseFeeWei => maxBaseFeeGwei.shift(9).toBigInt(); + BigInt get priorityFeeWei => priorityFeeGwei.shift(9).toBigInt(); + + @override + String toString() => + "EthEIP1559Fee(" + "maxBaseFeeGwei: $maxBaseFeeGwei, " + "priorityFeeGwei: $priorityFeeGwei, " + "maxBaseFeeWei: $maxBaseFeeWei, " + "priorityFeeWei: $priorityFeeWei, " + "gasLimit: $gasLimit)"; +} + +class EthFeeForm extends StatefulWidget { + EthFeeForm({ + super.key, + this.minGasLimit = 21000, + this.maxGasLimit = 30000000, + this.initialState, + required this.stateChanged, + }) : assert( + initialState == null || + (initialState.gasLimit >= minGasLimit && + initialState.gasLimit <= maxGasLimit), + ); + + final int minGasLimit; + final int maxGasLimit; + + final EthEIP1559Fee? initialState; + + final void Function(EthEIP1559Fee) stateChanged; + + @override + State createState() => _EthFeeFormState(); +} + +class _EthFeeFormState extends State { + static const _textFadeDuration = Duration(milliseconds: 300); + + final maxBaseController = TextEditingController(); + final priorityFeeController = TextEditingController(); + final gasLimitController = TextEditingController(); + final maxBaseFocus = FocusNode(); + final priorityFeeFocus = FocusNode(); + final gasLimitFocus = FocusNode(); + + late int _gasLimitCache; + + EthEIP1559Fee get _current => EthEIP1559Fee( + maxBaseFeeGwei: Decimal.tryParse(maxBaseController.text) ?? Decimal.zero, + priorityFeeGwei: + Decimal.tryParse(priorityFeeController.text) ?? Decimal.zero, + gasLimit: int.parse(gasLimitController.text), + ); + + String _currentBase = "Current: "; + String _currentPriority = "Current: "; + + void _checkNetworkGas() async { + final gas = await EthereumAPI.getGasOracle(); + + if (mounted) { + setState(() { + _currentBase = + "Current: ${gas.value!.suggestBaseFee.toStringAsFixed(3)} GWEI"; + _currentPriority = + "Current: ${gas.value!.lowPriority.toStringAsFixed(3)} - ${gas.value!.highPriority.toStringAsFixed(3)} GWEI"; + }); + } + } + + Timer? _gasTimer; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkNetworkGas(); + _gasTimer = Timer.periodic( + const Duration(seconds: 5), + (_) => _checkNetworkGas(), + ); + }); + + maxBaseController.text = + widget.initialState?.maxBaseFeeGwei.toString() ?? ""; + priorityFeeController.text = + widget.initialState?.priorityFeeGwei.toString() ?? ""; + + _gasLimitCache = widget.initialState?.gasLimit ?? widget.minGasLimit; + gasLimitController.text = _gasLimitCache.toString(); + } + + @override + void dispose() { + _gasTimer?.cancel(); + _gasTimer = null; + maxBaseController.dispose(); + priorityFeeController.dispose(); + gasLimitController.dispose(); + maxBaseFocus.dispose(); + priorityFeeFocus.dispose(); + gasLimitFocus.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Max base fee (GWEI)", style: STextStyles.smallMed12(context)), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 1, + controller: maxBaseController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + focusNode: maxBaseFocus, + onChanged: (value) { + widget.stateChanged(_current); + }, + style: + Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + null, + maxBaseFocus, + context, + desktopMed: Util.isDesktop, + ).copyWith( + contentPadding: EdgeInsets.only( + left: 16, + top: Util.isDesktop ? 11 : 6, + bottom: Util.isDesktop ? 12 : 8, + right: 5, + ), + ), + ), + ), + const SizedBox(height: 6), + AnimatedSwitcher( + duration: _textFadeDuration, + transitionBuilder: + (child, animation) => + FadeTransition(opacity: animation, child: child), + child: Text( + _currentBase, + key: ValueKey( + _currentBase, + ), // Important: ensures AnimatedSwitcher sees the text change + style: STextStyles.smallMed12(context), + ), + ), + const SizedBox(height: 20), + Text("Priority fee (GWEI)", style: STextStyles.smallMed12(context)), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 1, + controller: priorityFeeController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + focusNode: priorityFeeFocus, + onChanged: (value) { + widget.stateChanged(_current); + }, + style: + Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + null, + priorityFeeFocus, + context, + desktopMed: Util.isDesktop, + ).copyWith( + contentPadding: EdgeInsets.only( + left: 16, + top: Util.isDesktop ? 11 : 6, + bottom: Util.isDesktop ? 12 : 8, + right: 5, + ), + ), + ), + ), + const SizedBox(height: 6), + AnimatedSwitcher( + duration: _textFadeDuration, + transitionBuilder: + (child, animation) => + FadeTransition(opacity: animation, child: child), + child: Text( + _currentPriority, + key: ValueKey( + _currentPriority, + ), // Important: ensures AnimatedSwitcher sees the text change + style: STextStyles.smallMed12(context), + ), + ), + const SizedBox(height: 20), + Text("Gas limit", style: STextStyles.smallMed12(context)), + const SizedBox(height: 10), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 1, + controller: gasLimitController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + focusNode: gasLimitFocus, + onChanged: (value) { + final intValue = int.tryParse(value); + if (intValue == null || + intValue < widget.minGasLimit || + intValue > widget.maxGasLimit) { + gasLimitController.text = _gasLimitCache.toString(); + return; + } + + _gasLimitCache = intValue; + + widget.stateChanged(_current); + }, + style: + Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + null, + gasLimitFocus, + context, + desktopMed: Util.isDesktop, + ).copyWith( + contentPadding: EdgeInsets.only( + left: 16, + top: Util.isDesktop ? 11 : 6, + bottom: Util.isDesktop ? 12 : 8, + right: 5, + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index cab5043d6..f4227005e 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -143,30 +143,35 @@ class _TransactionCardState extends ConsumerState { localeServiceChangeNotifierProvider.select((value) => value.locale), ); - final baseCurrency = ref - .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + final baseCurrency = ref.watch( + prefsChangeNotifierProvider.select((value) => value.currency), + ); - final price = ref - .watch( - priceAnd24hChangeNotifierProvider.select( - (value) => isTokenTx - ? value.getTokenPrice(_transaction.otherData!) - : value.getPrice(coin), - ), - ) - .item1; + final price = + ref + .watch( + priceAnd24hChangeNotifierProvider.select( + (value) => + isTokenTx + ? value.getTokenPrice(_transaction.otherData!) + : value.getPrice(coin), + ), + ) + ?.value; final currentHeight = ref.watch( - pWallets - .select((value) => value.getWallet(walletId).info.cachedChainHeight), + pWallets.select( + (value) => value.getWallet(walletId).info.cachedChainHeight, + ), ); return Material( color: Theme.of(context).extension()!.popupBG, elevation: 0, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.size.circularBorderRadius), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), child: Padding( padding: const EdgeInsets.all(6), @@ -191,25 +196,22 @@ class _TransactionCardState extends ConsumerState { if (Util.isDesktop) { await showDialog( context: context, - builder: (context) => DesktopDialog( - maxHeight: MediaQuery.of(context).size.height - 64, - maxWidth: 580, - child: TransactionDetailsView( - transaction: _transaction, - coin: coin, - walletId: walletId, - ), - ), + builder: + (context) => DesktopDialog( + maxHeight: MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: TransactionDetailsView( + transaction: _transaction, + coin: coin, + walletId: walletId, + ), + ), ); } else { unawaited( Navigator.of(context).pushNamed( TransactionDetailsView.routeName, - arguments: Tuple3( - _transaction, - coin, - walletId, - ), + arguments: Tuple3(_transaction, coin, walletId), ), ); } @@ -223,9 +225,7 @@ class _TransactionCardState extends ConsumerState { coin: coin, currentHeight: currentHeight, ), - const SizedBox( - width: 14, - ), + const SizedBox(width: 14), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -243,17 +243,15 @@ class _TransactionCardState extends ConsumerState { ? "Failed" : "Cancelled" : whatIsIt( - _transaction.type, - coin, - currentHeight, - ), + _transaction.type, + coin, + currentHeight, + ), style: STextStyles.itemSubtitle12(context), ), ), ), - const SizedBox( - width: 10, - ), + const SizedBox(width: 10), Flexible( child: FittedBox( fit: BoxFit.scaleDown, @@ -271,9 +269,7 @@ class _TransactionCardState extends ConsumerState { ), ], ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // crossAxisAlignment: CrossAxisAlignment.end, @@ -287,17 +283,19 @@ class _TransactionCardState extends ConsumerState { ), ), ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) - const SizedBox( - width: 10, - ), - if (ref.watch( - prefsChangeNotifierProvider - .select((value) => value.externalCalls), - )) + if (price != null && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) + const SizedBox(width: 10), + if (price != null && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + )) Flexible( child: FittedBox( fit: BoxFit.scaleDown, @@ -306,12 +304,7 @@ class _TransactionCardState extends ConsumerState { final amount = _transaction.realAmount; return Text( - "$prefix${Amount.fromDecimal( - amount.decimal * price, - fractionDigits: 2, - ).fiatString( - locale: locale, - )} $baseCurrency", + "$prefix${Amount.fromDecimal(amount.decimal * price, fractionDigits: 2).fiatString(locale: locale)} $baseCurrency", style: STextStyles.label(context), ); }, diff --git a/test/models/fee_object_model_test.dart b/test/models/fee_object_model_test.dart index 52045d662..1f108d531 100644 --- a/test/models/fee_object_model_test.dart +++ b/test/models/fee_object_model_test.dart @@ -4,27 +4,16 @@ import 'package:stackwallet/models/models.dart'; void main() { test("FeeObject constructor", () { final feeObject = FeeObject( - fast: 3, - medium: 2, - slow: 1, + fast: BigInt.from(3), + medium: BigInt.from(2), + slow: BigInt.from(1), numberOfBlocksFast: 4, numberOfBlocksSlow: 5, numberOfBlocksAverage: 10, ); - expect(feeObject.toString(), - "{fast: 3, medium: 2, slow: 1, numberOfBlocksFast: 4, numberOfBlocksAverage: 10, numberOfBlocksSlow: 5}"); - }); - - test("FeeObject.fromJson factory", () { - final feeObject = FeeObject.fromJson({ - "fast": 3, - "average": 2, - "slow": 1, - "numberOfBlocksFast": 4, - "numberOfBlocksSlow": 5, - "numberOfBlocksAverage": 6, - }); - expect(feeObject.toString(), - "{fast: 3, medium: 2, slow: 1, numberOfBlocksFast: 4, numberOfBlocksAverage: 6, numberOfBlocksSlow: 5}"); + expect( + feeObject.toString(), + "{fast: 3, medium: 2, slow: 1, numberOfBlocksFast: 4, numberOfBlocksAverage: 10, numberOfBlocksSlow: 5}", + ); }); } diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index a146a9abf..ac6790693 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -3,41 +3,41 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i11; -import 'dart:typed_data' as _i26; -import 'dart:ui' as _i17; +import 'dart:async' as _i10; +import 'dart:typed_data' as _i25; +import 'dart:ui' as _i16; -import 'package:decimal/decimal.dart' as _i23; -import 'package:isar/isar.dart' as _i9; -import 'package:logger/logger.dart' as _i20; +import 'package:decimal/decimal.dart' as _i22; +import 'package:isar/isar.dart' as _i8; +import 'package:logger/logger.dart' as _i19; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i16; +import 'package:mockito/src/dummies.dart' as _i15; import 'package:stackwallet/db/isar/main_db.dart' as _i3; -import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i28; +import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i27; import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart' as _i30; -import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i27; -import 'package:stackwallet/models/isar/models/isar_models.dart' as _i29; -import 'package:stackwallet/models/isar/stack_theme.dart' as _i25; -import 'package:stackwallet/networking/http.dart' as _i8; -import 'package:stackwallet/services/locale_service.dart' as _i15; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i26; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i28; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i24; +import 'package:stackwallet/networking/http.dart' as _i7; +import 'package:stackwallet/services/locale_service.dart' as _i14; import 'package:stackwallet/services/node_service.dart' as _i2; -import 'package:stackwallet/services/price_service.dart' as _i22; -import 'package:stackwallet/services/wallets.dart' as _i10; -import 'package:stackwallet/themes/theme_service.dart' as _i24; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i21; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i19; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i18; +import 'package:stackwallet/services/price_service.dart' as _i21; +import 'package:stackwallet/services/wallets.dart' as _i9; +import 'package:stackwallet/themes/theme_service.dart' as _i23; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i20; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i18; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i17; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i13; -import 'package:stackwallet/utilities/prefs.dart' as _i14; + as _i12; +import 'package:stackwallet/utilities/prefs.dart' as _i13; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' as _i4; -import 'package:stackwallet/wallets/isar/models/wallet_info.dart' as _i12; +import 'package:stackwallet/wallets/isar/models/wallet_info.dart' as _i11; import 'package:stackwallet/wallets/wallet/wallet.dart' as _i5; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart' as _i6; -import 'package:tuple/tuple.dart' as _i7; +import 'package:tuple/tuple.dart' as _i29; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -103,9 +103,8 @@ class _FakeDuration_4 extends _i1.SmartFake implements Duration { ); } -class _FakeTuple2_5 extends _i1.SmartFake - implements _i7.Tuple2 { - _FakeTuple2_5( +class _FakeHTTP_5 extends _i1.SmartFake implements _i7.HTTP { + _FakeHTTP_5( Object parent, Invocation parentInvocation, ) : super( @@ -114,8 +113,8 @@ class _FakeTuple2_5 extends _i1.SmartFake ); } -class _FakeHTTP_6 extends _i1.SmartFake implements _i8.HTTP { - _FakeHTTP_6( +class _FakeIsar_6 extends _i1.SmartFake implements _i8.Isar { + _FakeIsar_6( Object parent, Invocation parentInvocation, ) : super( @@ -124,19 +123,9 @@ class _FakeHTTP_6 extends _i1.SmartFake implements _i8.HTTP { ); } -class _FakeIsar_7 extends _i1.SmartFake implements _i9.Isar { - _FakeIsar_7( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeQueryBuilder_8 extends _i1.SmartFake - implements _i9.QueryBuilder { - _FakeQueryBuilder_8( +class _FakeQueryBuilder_7 extends _i1.SmartFake + implements _i8.QueryBuilder { + _FakeQueryBuilder_7( Object parent, Invocation parentInvocation, ) : super( @@ -148,7 +137,7 @@ class _FakeQueryBuilder_8 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i10.Wallets { +class MockWallets extends _i1.Mock implements _i9.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -221,9 +210,9 @@ class MockWallets extends _i1.Mock implements _i10.Wallets { ); @override - _i11.Future deleteWallet( - _i12.WalletInfo? info, - _i13.SecureStorageInterface? secureStorage, + _i10.Future deleteWallet( + _i11.WalletInfo? info, + _i12.SecureStorageInterface? secureStorage, ) => (super.noSuchMethod( Invocation.method( @@ -233,13 +222,13 @@ class MockWallets extends _i1.Mock implements _i10.Wallets { secureStorage, ], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future load( - _i14.Prefs? prefs, + _i10.Future load( + _i13.Prefs? prefs, _i3.MainDB? mainDB, ) => (super.noSuchMethod( @@ -250,13 +239,13 @@ class MockWallets extends _i1.Mock implements _i10.Wallets { mainDB, ], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future loadAfterStackRestore( - _i14.Prefs? prefs, + _i10.Future loadAfterStackRestore( + _i13.Prefs? prefs, List<_i5.Wallet<_i4.CryptoCurrency>>? wallets, bool? isDesktop, ) => @@ -269,15 +258,15 @@ class MockWallets extends _i1.Mock implements _i10.Wallets { isDesktop, ], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); } /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i15.LocaleService { +class MockLocaleService extends _i1.Mock implements _i14.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -285,7 +274,7 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#locale), ), @@ -298,18 +287,18 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { ) as bool); @override - _i11.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i10.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -318,7 +307,7 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -348,7 +337,7 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i14.Prefs { +class MockPrefs extends _i1.Mock implements _i13.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -412,13 +401,13 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override - _i18.SyncingType get syncType => (super.noSuchMethod( + _i17.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i18.SyncingType.currentWalletOnly, - ) as _i18.SyncingType); + returnValue: _i17.SyncingType.currentWalletOnly, + ) as _i17.SyncingType); @override - set syncType(_i18.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i17.SyncingType? syncType) => super.noSuchMethod( Invocation.setter( #syncType, syncType, @@ -459,7 +448,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { @override String get language => (super.noSuchMethod( Invocation.getter(#language), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#language), ), @@ -477,7 +466,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { @override String get currency => (super.noSuchMethod( Invocation.getter(#currency), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#currency), ), @@ -607,13 +596,13 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override - _i19.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i18.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i19.BackupFrequencyType.everyTenMinutes, - ) as _i19.BackupFrequencyType); + returnValue: _i18.BackupFrequencyType.everyTenMinutes, + ) as _i18.BackupFrequencyType); @override - set backupFrequencyType(_i19.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i18.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -720,7 +709,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { @override String get themeId => (super.noSuchMethod( Invocation.getter(#themeId), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#themeId), ), @@ -738,7 +727,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { @override String get systemBrightnessLightThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessLightThemeId), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#systemBrightnessLightThemeId), ), @@ -757,7 +746,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { @override String get systemBrightnessDarkThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessDarkThemeId), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#systemBrightnessDarkThemeId), ), @@ -843,13 +832,13 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override - _i20.Level get logLevel => (super.noSuchMethod( + _i19.Level get logLevel => (super.noSuchMethod( Invocation.getter(#logLevel), - returnValue: _i20.Level.all, - ) as _i20.Level); + returnValue: _i19.Level.all, + ) as _i19.Level); @override - set logLevel(_i20.Level? logLevel) => super.noSuchMethod( + set logLevel(_i19.Level? logLevel) => super.noSuchMethod( Invocation.setter( #logLevel, logLevel, @@ -864,67 +853,67 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ) as bool); @override - _i11.Future init() => (super.noSuchMethod( + _i10.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i10.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future isExternalCallsSet() => (super.noSuchMethod( + _i10.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i11.Future saveUserID(String? userId) => (super.noSuchMethod( + _i10.Future saveUserID(String? userId) => (super.noSuchMethod( Invocation.method( #saveUserID, [userId], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + _i10.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( Invocation.method( #saveSignupEpoch, [signupEpoch], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i21.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( + _i20.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method( #amountUnit, [coin], ), - returnValue: _i21.AmountUnit.normal, - ) as _i21.AmountUnit); + returnValue: _i20.AmountUnit.normal, + ) as _i20.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i21.AmountUnit? amountUnit, + required _i20.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method( @@ -997,7 +986,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1006,7 +995,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1036,7 +1025,7 @@ class MockPrefs extends _i1.Mock implements _i14.Prefs { /// A class which mocks [PriceService]. /// /// See the documentation for Mockito's code generation for more information. -class MockPriceService extends _i1.Mock implements _i22.PriceService { +class MockPriceService extends _i1.Mock implements _i21.PriceService { MockPriceService() { _i1.throwOnMissingStub(this); } @@ -1044,7 +1033,7 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { @override String get baseTicker => (super.noSuchMethod( Invocation.getter(#baseTicker), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#baseTicker), ), @@ -1069,11 +1058,11 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { ) as Duration); @override - _i11.Future> get tokenContractAddressesToCheck => + _i10.Future> get tokenContractAddressesToCheck => (super.noSuchMethod( Invocation.getter(#tokenContractAddressesToCheck), - returnValue: _i11.Future>.value({}), - ) as _i11.Future>); + returnValue: _i10.Future>.value({}), + ) as _i10.Future>); @override bool get hasListeners => (super.noSuchMethod( @@ -1082,46 +1071,30 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { ) as bool); @override - _i7.Tuple2<_i23.Decimal, double> getPrice(_i4.CryptoCurrency? coin) => - (super.noSuchMethod( - Invocation.method( - #getPrice, - [coin], - ), - returnValue: _FakeTuple2_5<_i23.Decimal, double>( - this, - Invocation.method( - #getPrice, - [coin], - ), - ), - ) as _i7.Tuple2<_i23.Decimal, double>); + ({double change24h, _i22.Decimal value})? getPrice( + _i4.CryptoCurrency? coin) => + (super.noSuchMethod(Invocation.method( + #getPrice, + [coin], + )) as ({double change24h, _i22.Decimal value})?); @override - _i7.Tuple2<_i23.Decimal, double> getTokenPrice(String? contractAddress) => - (super.noSuchMethod( - Invocation.method( - #getTokenPrice, - [contractAddress], - ), - returnValue: _FakeTuple2_5<_i23.Decimal, double>( - this, - Invocation.method( - #getTokenPrice, - [contractAddress], - ), - ), - ) as _i7.Tuple2<_i23.Decimal, double>); + ({double change24h, _i22.Decimal value})? getTokenPrice( + String? contractAddress) => + (super.noSuchMethod(Invocation.method( + #getTokenPrice, + [contractAddress], + )) as ({double change24h, _i22.Decimal value})?); @override - _i11.Future updatePrice() => (super.noSuchMethod( + _i10.Future updatePrice() => (super.noSuchMethod( Invocation.method( #updatePrice, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override void cancel() => super.noSuchMethod( @@ -1151,7 +1124,7 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { ); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1160,7 +1133,7 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1181,22 +1154,22 @@ class MockPriceService extends _i1.Mock implements _i22.PriceService { /// A class which mocks [ThemeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockThemeService extends _i1.Mock implements _i24.ThemeService { +class MockThemeService extends _i1.Mock implements _i23.ThemeService { MockThemeService() { _i1.throwOnMissingStub(this); } @override - _i8.HTTP get client => (super.noSuchMethod( + _i7.HTTP get client => (super.noSuchMethod( Invocation.getter(#client), - returnValue: _FakeHTTP_6( + returnValue: _FakeHTTP_5( this, Invocation.getter(#client), ), - ) as _i8.HTTP); + ) as _i7.HTTP); @override - set client(_i8.HTTP? _client) => super.noSuchMethod( + set client(_i7.HTTP? _client) => super.noSuchMethod( Invocation.setter( #client, _client, @@ -1214,10 +1187,10 @@ class MockThemeService extends _i1.Mock implements _i24.ThemeService { ) as _i3.MainDB); @override - List<_i25.StackTheme> get installedThemes => (super.noSuchMethod( + List<_i24.StackTheme> get installedThemes => (super.noSuchMethod( Invocation.getter(#installedThemes), - returnValue: <_i25.StackTheme>[], - ) as List<_i25.StackTheme>); + returnValue: <_i24.StackTheme>[], + ) as List<_i24.StackTheme>); @override void init(_i3.MainDB? db) => super.noSuchMethod( @@ -1229,79 +1202,79 @@ class MockThemeService extends _i1.Mock implements _i24.ThemeService { ); @override - _i11.Future install({required _i26.Uint8List? themeArchiveData}) => + _i10.Future install({required _i25.Uint8List? themeArchiveData}) => (super.noSuchMethod( Invocation.method( #install, [], {#themeArchiveData: themeArchiveData}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future remove({required String? themeId}) => (super.noSuchMethod( + _i10.Future remove({required String? themeId}) => (super.noSuchMethod( Invocation.method( #remove, [], {#themeId: themeId}, ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future checkDefaultThemesOnStartup() => (super.noSuchMethod( + _i10.Future checkDefaultThemesOnStartup() => (super.noSuchMethod( Invocation.method( #checkDefaultThemesOnStartup, [], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future verifyInstalled({required String? themeId}) => + _i10.Future verifyInstalled({required String? themeId}) => (super.noSuchMethod( Invocation.method( #verifyInstalled, [], {#themeId: themeId}, ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i11.Future> fetchThemes() => + _i10.Future> fetchThemes() => (super.noSuchMethod( Invocation.method( #fetchThemes, [], ), - returnValue: _i11.Future>.value( - <_i24.StackThemeMetaData>[]), - ) as _i11.Future>); + returnValue: _i10.Future>.value( + <_i23.StackThemeMetaData>[]), + ) as _i10.Future>); @override - _i11.Future<_i26.Uint8List> fetchTheme( - {required _i24.StackThemeMetaData? themeMetaData}) => + _i10.Future<_i25.Uint8List> fetchTheme( + {required _i23.StackThemeMetaData? themeMetaData}) => (super.noSuchMethod( Invocation.method( #fetchTheme, [], {#themeMetaData: themeMetaData}, ), - returnValue: _i11.Future<_i26.Uint8List>.value(_i26.Uint8List(0)), - ) as _i11.Future<_i26.Uint8List>); + returnValue: _i10.Future<_i25.Uint8List>.value(_i25.Uint8List(0)), + ) as _i10.Future<_i25.Uint8List>); @override - _i25.StackTheme? getTheme({required String? themeId}) => + _i24.StackTheme? getTheme({required String? themeId}) => (super.noSuchMethod(Invocation.method( #getTheme, [], {#themeId: themeId}, - )) as _i25.StackTheme?); + )) as _i24.StackTheme?); } /// A class which mocks [MainDB]. @@ -1313,166 +1286,166 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { } @override - _i9.Isar get isar => (super.noSuchMethod( + _i8.Isar get isar => (super.noSuchMethod( Invocation.getter(#isar), - returnValue: _FakeIsar_7( + returnValue: _FakeIsar_6( this, Invocation.getter(#isar), ), - ) as _i9.Isar); + ) as _i8.Isar); @override - _i11.Future initMainDB({_i9.Isar? mock}) => (super.noSuchMethod( + _i10.Future initMainDB({_i8.Isar? mock}) => (super.noSuchMethod( Invocation.method( #initMainDB, [], {#mock: mock}, ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i11.Future putWalletInfo(_i12.WalletInfo? walletInfo) => + _i10.Future putWalletInfo(_i11.WalletInfo? walletInfo) => (super.noSuchMethod( Invocation.method( #putWalletInfo, [walletInfo], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future updateWalletInfo(_i12.WalletInfo? walletInfo) => + _i10.Future updateWalletInfo(_i11.WalletInfo? walletInfo) => (super.noSuchMethod( Invocation.method( #updateWalletInfo, [walletInfo], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - List<_i27.ContactEntry> getContactEntries() => (super.noSuchMethod( + List<_i26.ContactEntry> getContactEntries() => (super.noSuchMethod( Invocation.method( #getContactEntries, [], ), - returnValue: <_i27.ContactEntry>[], - ) as List<_i27.ContactEntry>); + returnValue: <_i26.ContactEntry>[], + ) as List<_i26.ContactEntry>); @override - _i11.Future deleteContactEntry({required String? id}) => + _i10.Future deleteContactEntry({required String? id}) => (super.noSuchMethod( Invocation.method( #deleteContactEntry, [], {#id: id}, ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i11.Future isContactEntryExists({required String? id}) => + _i10.Future isContactEntryExists({required String? id}) => (super.noSuchMethod( Invocation.method( #isContactEntryExists, [], {#id: id}, ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i27.ContactEntry? getContactEntry({required String? id}) => + _i26.ContactEntry? getContactEntry({required String? id}) => (super.noSuchMethod(Invocation.method( #getContactEntry, [], {#id: id}, - )) as _i27.ContactEntry?); + )) as _i26.ContactEntry?); @override - _i11.Future putContactEntry( - {required _i27.ContactEntry? contactEntry}) => + _i10.Future putContactEntry( + {required _i26.ContactEntry? contactEntry}) => (super.noSuchMethod( Invocation.method( #putContactEntry, [], {#contactEntry: contactEntry}, ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i28.TransactionBlockExplorer? getTransactionBlockExplorer( + _i27.TransactionBlockExplorer? getTransactionBlockExplorer( {required _i4.CryptoCurrency? cryptoCurrency}) => (super.noSuchMethod(Invocation.method( #getTransactionBlockExplorer, [], {#cryptoCurrency: cryptoCurrency}, - )) as _i28.TransactionBlockExplorer?); + )) as _i27.TransactionBlockExplorer?); @override - _i11.Future putTransactionBlockExplorer( - _i28.TransactionBlockExplorer? explorer) => + _i10.Future putTransactionBlockExplorer( + _i27.TransactionBlockExplorer? explorer) => (super.noSuchMethod( Invocation.method( #putTransactionBlockExplorer, [explorer], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i9.QueryBuilder<_i29.Address, _i29.Address, _i9.QAfterWhereClause> + _i8.QueryBuilder<_i28.Address, _i28.Address, _i8.QAfterWhereClause> getAddresses(String? walletId) => (super.noSuchMethod( Invocation.method( #getAddresses, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i29.Address, _i29.Address, - _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_7<_i28.Address, _i28.Address, + _i8.QAfterWhereClause>( this, Invocation.method( #getAddresses, [walletId], ), ), - ) as _i9 - .QueryBuilder<_i29.Address, _i29.Address, _i9.QAfterWhereClause>); + ) as _i8 + .QueryBuilder<_i28.Address, _i28.Address, _i8.QAfterWhereClause>); @override - _i11.Future putAddress(_i29.Address? address) => (super.noSuchMethod( + _i10.Future putAddress(_i28.Address? address) => (super.noSuchMethod( Invocation.method( #putAddress, [address], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i11.Future> putAddresses(List<_i29.Address>? addresses) => + _i10.Future> putAddresses(List<_i28.Address>? addresses) => (super.noSuchMethod( Invocation.method( #putAddresses, [addresses], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i10.Future>.value([]), + ) as _i10.Future>); @override - _i11.Future> updateOrPutAddresses(List<_i29.Address>? addresses) => + _i10.Future> updateOrPutAddresses(List<_i28.Address>? addresses) => (super.noSuchMethod( Invocation.method( #updateOrPutAddresses, [addresses], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i10.Future>.value([]), + ) as _i10.Future>); @override - _i11.Future<_i29.Address?> getAddress( + _i10.Future<_i28.Address?> getAddress( String? walletId, String? address, ) => @@ -1484,13 +1457,13 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { address, ], ), - returnValue: _i11.Future<_i29.Address?>.value(), - ) as _i11.Future<_i29.Address?>); + returnValue: _i10.Future<_i28.Address?>.value(), + ) as _i10.Future<_i28.Address?>); @override - _i11.Future updateAddress( - _i29.Address? oldAddress, - _i29.Address? newAddress, + _i10.Future updateAddress( + _i28.Address? oldAddress, + _i28.Address? newAddress, ) => (super.noSuchMethod( Invocation.method( @@ -1500,50 +1473,50 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { newAddress, ], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i9.QueryBuilder<_i29.Transaction, _i29.Transaction, _i9.QAfterWhereClause> + _i8.QueryBuilder<_i28.Transaction, _i28.Transaction, _i8.QAfterWhereClause> getTransactions(String? walletId) => (super.noSuchMethod( Invocation.method( #getTransactions, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i29.Transaction, _i29.Transaction, - _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_7<_i28.Transaction, _i28.Transaction, + _i8.QAfterWhereClause>( this, Invocation.method( #getTransactions, [walletId], ), ), - ) as _i9.QueryBuilder<_i29.Transaction, _i29.Transaction, - _i9.QAfterWhereClause>); + ) as _i8.QueryBuilder<_i28.Transaction, _i28.Transaction, + _i8.QAfterWhereClause>); @override - _i11.Future putTransaction(_i29.Transaction? transaction) => + _i10.Future putTransaction(_i28.Transaction? transaction) => (super.noSuchMethod( Invocation.method( #putTransaction, [transaction], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i11.Future> putTransactions( - List<_i29.Transaction>? transactions) => + _i10.Future> putTransactions( + List<_i28.Transaction>? transactions) => (super.noSuchMethod( Invocation.method( #putTransactions, [transactions], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i10.Future>.value([]), + ) as _i10.Future>); @override - _i11.Future<_i29.Transaction?> getTransaction( + _i10.Future<_i28.Transaction?> getTransaction( String? walletId, String? txid, ) => @@ -1555,11 +1528,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { txid, ], ), - returnValue: _i11.Future<_i29.Transaction?>.value(), - ) as _i11.Future<_i29.Transaction?>); + returnValue: _i10.Future<_i28.Transaction?>.value(), + ) as _i10.Future<_i28.Transaction?>); @override - _i11.Stream<_i29.Transaction?> watchTransaction({ + _i10.Stream<_i28.Transaction?> watchTransaction({ required int? id, bool? fireImmediately = false, }) => @@ -1572,11 +1545,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i29.Transaction?>.empty(), - ) as _i11.Stream<_i29.Transaction?>); + returnValue: _i10.Stream<_i28.Transaction?>.empty(), + ) as _i10.Stream<_i28.Transaction?>); @override - _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause> getUTXOs( + _i8.QueryBuilder<_i28.UTXO, _i28.UTXO, _i8.QAfterWhereClause> getUTXOs( String? walletId) => (super.noSuchMethod( Invocation.method( @@ -1584,17 +1557,17 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { [walletId], ), returnValue: - _FakeQueryBuilder_8<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause>( + _FakeQueryBuilder_7<_i28.UTXO, _i28.UTXO, _i8.QAfterWhereClause>( this, Invocation.method( #getUTXOs, [walletId], ), ), - ) as _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterWhereClause>); + ) as _i8.QueryBuilder<_i28.UTXO, _i28.UTXO, _i8.QAfterWhereClause>); @override - _i9.QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterFilterCondition> + _i8.QueryBuilder<_i28.UTXO, _i28.UTXO, _i8.QAfterFilterCondition> getUTXOsByAddress( String? walletId, String? address, @@ -1607,8 +1580,8 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { address, ], ), - returnValue: _FakeQueryBuilder_8<_i29.UTXO, _i29.UTXO, - _i9.QAfterFilterCondition>( + returnValue: _FakeQueryBuilder_7<_i28.UTXO, _i28.UTXO, + _i8.QAfterFilterCondition>( this, Invocation.method( #getUTXOsByAddress, @@ -1618,33 +1591,33 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ], ), ), - ) as _i9 - .QueryBuilder<_i29.UTXO, _i29.UTXO, _i9.QAfterFilterCondition>); + ) as _i8 + .QueryBuilder<_i28.UTXO, _i28.UTXO, _i8.QAfterFilterCondition>); @override - _i11.Future putUTXO(_i29.UTXO? utxo) => (super.noSuchMethod( + _i10.Future putUTXO(_i28.UTXO? utxo) => (super.noSuchMethod( Invocation.method( #putUTXO, [utxo], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future putUTXOs(List<_i29.UTXO>? utxos) => (super.noSuchMethod( + _i10.Future putUTXOs(List<_i28.UTXO>? utxos) => (super.noSuchMethod( Invocation.method( #putUTXOs, [utxos], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future updateUTXOs( + _i10.Future updateUTXOs( String? walletId, - List<_i29.UTXO>? utxos, + List<_i28.UTXO>? utxos, ) => (super.noSuchMethod( Invocation.method( @@ -1654,11 +1627,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { utxos, ], ), - returnValue: _i11.Future.value(false), - ) as _i11.Future); + returnValue: _i10.Future.value(false), + ) as _i10.Future); @override - _i11.Stream<_i29.UTXO?> watchUTXO({ + _i10.Stream<_i28.UTXO?> watchUTXO({ required int? id, bool? fireImmediately = false, }) => @@ -1671,54 +1644,54 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i29.UTXO?>.empty(), - ) as _i11.Stream<_i29.UTXO?>); + returnValue: _i10.Stream<_i28.UTXO?>.empty(), + ) as _i10.Stream<_i28.UTXO?>); @override - _i9.QueryBuilder<_i29.TransactionNote, _i29.TransactionNote, - _i9.QAfterWhereClause> getTransactionNotes( + _i8.QueryBuilder<_i28.TransactionNote, _i28.TransactionNote, + _i8.QAfterWhereClause> getTransactionNotes( String? walletId) => (super.noSuchMethod( Invocation.method( #getTransactionNotes, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i29.TransactionNote, - _i29.TransactionNote, _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_7<_i28.TransactionNote, + _i28.TransactionNote, _i8.QAfterWhereClause>( this, Invocation.method( #getTransactionNotes, [walletId], ), ), - ) as _i9.QueryBuilder<_i29.TransactionNote, _i29.TransactionNote, - _i9.QAfterWhereClause>); + ) as _i8.QueryBuilder<_i28.TransactionNote, _i28.TransactionNote, + _i8.QAfterWhereClause>); @override - _i11.Future putTransactionNote(_i29.TransactionNote? transactionNote) => + _i10.Future putTransactionNote(_i28.TransactionNote? transactionNote) => (super.noSuchMethod( Invocation.method( #putTransactionNote, [transactionNote], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future putTransactionNotes( - List<_i29.TransactionNote>? transactionNotes) => + _i10.Future putTransactionNotes( + List<_i28.TransactionNote>? transactionNotes) => (super.noSuchMethod( Invocation.method( #putTransactionNotes, [transactionNotes], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future<_i29.TransactionNote?> getTransactionNote( + _i10.Future<_i28.TransactionNote?> getTransactionNote( String? walletId, String? txid, ) => @@ -1730,11 +1703,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { txid, ], ), - returnValue: _i11.Future<_i29.TransactionNote?>.value(), - ) as _i11.Future<_i29.TransactionNote?>); + returnValue: _i10.Future<_i28.TransactionNote?>.value(), + ) as _i10.Future<_i28.TransactionNote?>); @override - _i11.Stream<_i29.TransactionNote?> watchTransactionNote({ + _i10.Stream<_i28.TransactionNote?> watchTransactionNote({ required int? id, bool? fireImmediately = false, }) => @@ -1747,39 +1720,39 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i29.TransactionNote?>.empty(), - ) as _i11.Stream<_i29.TransactionNote?>); + returnValue: _i10.Stream<_i28.TransactionNote?>.empty(), + ) as _i10.Stream<_i28.TransactionNote?>); @override - _i9.QueryBuilder<_i29.AddressLabel, _i29.AddressLabel, _i9.QAfterWhereClause> + _i8.QueryBuilder<_i28.AddressLabel, _i28.AddressLabel, _i8.QAfterWhereClause> getAddressLabels(String? walletId) => (super.noSuchMethod( Invocation.method( #getAddressLabels, [walletId], ), - returnValue: _FakeQueryBuilder_8<_i29.AddressLabel, - _i29.AddressLabel, _i9.QAfterWhereClause>( + returnValue: _FakeQueryBuilder_7<_i28.AddressLabel, + _i28.AddressLabel, _i8.QAfterWhereClause>( this, Invocation.method( #getAddressLabels, [walletId], ), ), - ) as _i9.QueryBuilder<_i29.AddressLabel, _i29.AddressLabel, - _i9.QAfterWhereClause>); + ) as _i8.QueryBuilder<_i28.AddressLabel, _i28.AddressLabel, + _i8.QAfterWhereClause>); @override - _i11.Future putAddressLabel(_i29.AddressLabel? addressLabel) => + _i10.Future putAddressLabel(_i28.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #putAddressLabel, [addressLabel], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - int putAddressLabelSync(_i29.AddressLabel? addressLabel) => + int putAddressLabelSync(_i28.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #putAddressLabelSync, @@ -1789,18 +1762,18 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { ) as int); @override - _i11.Future putAddressLabels(List<_i29.AddressLabel>? addressLabels) => + _i10.Future putAddressLabels(List<_i28.AddressLabel>? addressLabels) => (super.noSuchMethod( Invocation.method( #putAddressLabels, [addressLabels], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future<_i29.AddressLabel?> getAddressLabel( + _i10.Future<_i28.AddressLabel?> getAddressLabel( String? walletId, String? addressString, ) => @@ -1812,11 +1785,11 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { addressString, ], ), - returnValue: _i11.Future<_i29.AddressLabel?>.value(), - ) as _i11.Future<_i29.AddressLabel?>); + returnValue: _i10.Future<_i28.AddressLabel?>.value(), + ) as _i10.Future<_i28.AddressLabel?>); @override - _i29.AddressLabel? getAddressLabelSync( + _i28.AddressLabel? getAddressLabelSync( String? walletId, String? addressString, ) => @@ -1826,10 +1799,10 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { walletId, addressString, ], - )) as _i29.AddressLabel?); + )) as _i28.AddressLabel?); @override - _i11.Stream<_i29.AddressLabel?> watchAddressLabel({ + _i10.Stream<_i28.AddressLabel?> watchAddressLabel({ required int? id, bool? fireImmediately = false, }) => @@ -1842,55 +1815,55 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { #fireImmediately: fireImmediately, }, ), - returnValue: _i11.Stream<_i29.AddressLabel?>.empty(), - ) as _i11.Stream<_i29.AddressLabel?>); + returnValue: _i10.Stream<_i28.AddressLabel?>.empty(), + ) as _i10.Stream<_i28.AddressLabel?>); @override - _i11.Future updateAddressLabel(_i29.AddressLabel? addressLabel) => + _i10.Future updateAddressLabel(_i28.AddressLabel? addressLabel) => (super.noSuchMethod( Invocation.method( #updateAddressLabel, [addressLabel], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i11.Future deleteWalletBlockchainData(String? walletId) => + _i10.Future deleteWalletBlockchainData(String? walletId) => (super.noSuchMethod( Invocation.method( #deleteWalletBlockchainData, [walletId], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future deleteAddressLabels(String? walletId) => + _i10.Future deleteAddressLabels(String? walletId) => (super.noSuchMethod( Invocation.method( #deleteAddressLabels, [walletId], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future deleteTransactionNotes(String? walletId) => + _i10.Future deleteTransactionNotes(String? walletId) => (super.noSuchMethod( Invocation.method( #deleteTransactionNotes, [walletId], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future addNewTransactionData( - List<_i7.Tuple2<_i29.Transaction, _i29.Address?>>? transactionsData, + _i10.Future addNewTransactionData( + List<_i29.Tuple2<_i28.Transaction, _i28.Address?>>? transactionsData, String? walletId, ) => (super.noSuchMethod( @@ -1901,93 +1874,93 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { walletId, ], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future> updateOrPutTransactionV2s( + _i10.Future> updateOrPutTransactionV2s( List<_i30.TransactionV2>? transactions) => (super.noSuchMethod( Invocation.method( #updateOrPutTransactionV2s, [transactions], ), - returnValue: _i11.Future>.value([]), - ) as _i11.Future>); + returnValue: _i10.Future>.value([]), + ) as _i10.Future>); @override - _i9.QueryBuilder<_i29.EthContract, _i29.EthContract, _i9.QWhere> + _i8.QueryBuilder<_i28.EthContract, _i28.EthContract, _i8.QWhere> getEthContracts() => (super.noSuchMethod( Invocation.method( #getEthContracts, [], ), - returnValue: _FakeQueryBuilder_8<_i29.EthContract, _i29.EthContract, - _i9.QWhere>( + returnValue: _FakeQueryBuilder_7<_i28.EthContract, _i28.EthContract, + _i8.QWhere>( this, Invocation.method( #getEthContracts, [], ), ), - ) as _i9 - .QueryBuilder<_i29.EthContract, _i29.EthContract, _i9.QWhere>); + ) as _i8 + .QueryBuilder<_i28.EthContract, _i28.EthContract, _i8.QWhere>); @override - _i11.Future<_i29.EthContract?> getEthContract(String? contractAddress) => + _i10.Future<_i28.EthContract?> getEthContract(String? contractAddress) => (super.noSuchMethod( Invocation.method( #getEthContract, [contractAddress], ), - returnValue: _i11.Future<_i29.EthContract?>.value(), - ) as _i11.Future<_i29.EthContract?>); + returnValue: _i10.Future<_i28.EthContract?>.value(), + ) as _i10.Future<_i28.EthContract?>); @override - _i29.EthContract? getEthContractSync(String? contractAddress) => + _i28.EthContract? getEthContractSync(String? contractAddress) => (super.noSuchMethod(Invocation.method( #getEthContractSync, [contractAddress], - )) as _i29.EthContract?); + )) as _i28.EthContract?); @override - _i11.Future putEthContract(_i29.EthContract? contract) => + _i10.Future putEthContract(_i28.EthContract? contract) => (super.noSuchMethod( Invocation.method( #putEthContract, [contract], ), - returnValue: _i11.Future.value(0), - ) as _i11.Future); + returnValue: _i10.Future.value(0), + ) as _i10.Future); @override - _i11.Future putEthContracts(List<_i29.EthContract>? contracts) => + _i10.Future putEthContracts(List<_i28.EthContract>? contracts) => (super.noSuchMethod( Invocation.method( #putEthContracts, [contracts], ), - returnValue: _i11.Future.value(), - returnValueForMissingStub: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); @override - _i11.Future getHighestUsedMintIndex({required String? walletId}) => + _i10.Future getHighestUsedMintIndex({required String? walletId}) => (super.noSuchMethod( Invocation.method( #getHighestUsedMintIndex, [], {#walletId: walletId}, ), - returnValue: _i11.Future.value(), - ) as _i11.Future); + returnValue: _i10.Future.value(), + ) as _i10.Future); } /// A class which mocks [IThemeAssets]. /// /// See the documentation for Mockito's code generation for more information. -class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { +class MockIThemeAssets extends _i1.Mock implements _i24.IThemeAssets { MockIThemeAssets() { _i1.throwOnMissingStub(this); } @@ -1995,7 +1968,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get bellNew => (super.noSuchMethod( Invocation.getter(#bellNew), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#bellNew), ), @@ -2004,7 +1977,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get buy => (super.noSuchMethod( Invocation.getter(#buy), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#buy), ), @@ -2013,7 +1986,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get exchange => (super.noSuchMethod( Invocation.getter(#exchange), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#exchange), ), @@ -2022,7 +1995,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get personaIncognito => (super.noSuchMethod( Invocation.getter(#personaIncognito), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#personaIncognito), ), @@ -2031,7 +2004,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get personaEasy => (super.noSuchMethod( Invocation.getter(#personaEasy), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#personaEasy), ), @@ -2040,7 +2013,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get stack => (super.noSuchMethod( Invocation.getter(#stack), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#stack), ), @@ -2049,7 +2022,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get stackIcon => (super.noSuchMethod( Invocation.getter(#stackIcon), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#stackIcon), ), @@ -2058,7 +2031,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get receive => (super.noSuchMethod( Invocation.getter(#receive), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#receive), ), @@ -2067,7 +2040,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get receivePending => (super.noSuchMethod( Invocation.getter(#receivePending), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#receivePending), ), @@ -2076,7 +2049,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get receiveCancelled => (super.noSuchMethod( Invocation.getter(#receiveCancelled), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#receiveCancelled), ), @@ -2085,7 +2058,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get send => (super.noSuchMethod( Invocation.getter(#send), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#send), ), @@ -2094,7 +2067,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get sendPending => (super.noSuchMethod( Invocation.getter(#sendPending), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#sendPending), ), @@ -2103,7 +2076,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get sendCancelled => (super.noSuchMethod( Invocation.getter(#sendCancelled), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#sendCancelled), ), @@ -2112,7 +2085,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get themeSelector => (super.noSuchMethod( Invocation.getter(#themeSelector), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#themeSelector), ), @@ -2121,7 +2094,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get themePreview => (super.noSuchMethod( Invocation.getter(#themePreview), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#themePreview), ), @@ -2130,7 +2103,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get txExchange => (super.noSuchMethod( Invocation.getter(#txExchange), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#txExchange), ), @@ -2139,7 +2112,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get txExchangePending => (super.noSuchMethod( Invocation.getter(#txExchangePending), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#txExchangePending), ), @@ -2148,7 +2121,7 @@ class MockIThemeAssets extends _i1.Mock implements _i25.IThemeAssets { @override String get txExchangeFailed => (super.noSuchMethod( Invocation.getter(#txExchangeFailed), - returnValue: _i16.dummyValue( + returnValue: _i15.dummyValue( this, Invocation.getter(#txExchangeFailed), ),