diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart index 8b56ae2130..08d6d88d4b 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -66,18 +66,16 @@ class _ExchangeOptionState extends ConsumerState { efCurrencyPairProvider.select((value) => value.receive), ); final reversed = ref.watch(efReversedProvider); - final amount = - reversed - ? ref.watch(efReceiveAmountProvider) - : ref.watch(efSendAmountProvider); + final amount = reversed + ? ref.watch(efReceiveAmountProvider) + : ref.watch(efSendAmountProvider); final data = ref.watch(efEstimatesListProvider(widget.exchange.name)); final estimates = data?.item1.value; - final pair = - sendCurrency != null && receivingCurrency != null - ? (from: sendCurrency, to: receivingCurrency) - : null; + final pair = sendCurrency != null && receivingCurrency != null + ? (from: sendCurrency, to: receivingCurrency) + : null; return AnimatedSize( duration: const Duration(milliseconds: 500), @@ -86,7 +84,7 @@ class _ExchangeOptionState extends ConsumerState { builder: (_) { if (ref.watch(efRefreshingProvider)) { // show loading - return _ProviderOption( + return ExchProviderOption( exchange: widget.exchange, estimate: null, pair: pair, @@ -108,10 +106,9 @@ class _ExchangeOptionState extends ConsumerState { int decimals; try { - decimals = - AppConfig.getCryptoCurrencyForTicker( - receivingCurrency.ticker, - )!.fractionDigits; + decimals = AppConfig.getCryptoCurrencyForTicker( + receivingCurrency.ticker, + )!.fractionDigits; } catch (_) { decimals = 8; // some reasonable alternative } @@ -161,23 +158,21 @@ class _ExchangeOptionState extends ConsumerState { return ConditionalParent( condition: i > 0, - builder: - (child) => Column( - mainAxisSize: MainAxisSize.min, - children: [ - isDesktop - ? Container( - height: 1, - color: - Theme.of(context) - .extension()! - .background, - ) - : const SizedBox(height: 16), - child, - ], - ), - child: _ProviderOption( + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + isDesktop + ? Container( + height: 1, + color: Theme.of( + context, + ).extension()!.background, + ) + : const SizedBox(height: 16), + child, + ], + ), + child: ExchProviderOption( key: Key(widget.exchange.name + e.exchangeProvider), exchange: widget.exchange, pair: pair, @@ -209,26 +204,27 @@ class _ExchangeOptionState extends ConsumerState { } else if (data?.item1.value == null) { final rateType = ref.watch(efRateTypeProvider) == - ExchangeRateType.estimated - ? "estimated" - : "fixed"; + ExchangeRateType.estimated + ? "estimated" + : "fixed"; message ??= "Pair unavailable on $rateType rate flow"; } - return _ProviderOption( + return ExchProviderOption( exchange: widget.exchange, estimate: null, pair: pair, rateString: message ?? "Failed to fetch rate", - rateColor: - Theme.of(context).extension()!.textError, + rateColor: Theme.of( + context, + ).extension()!.textError, ); }, ); } } else { // show n/a - return _ProviderOption( + return ExchProviderOption( exchange: widget.exchange, estimate: null, pair: pair, @@ -241,8 +237,8 @@ class _ExchangeOptionState extends ConsumerState { } } -class _ProviderOption extends ConsumerStatefulWidget { - const _ProviderOption({ +class ExchProviderOption extends ConsumerStatefulWidget { + const ExchProviderOption({ super.key, required this.exchange, required this.estimate, @@ -262,10 +258,10 @@ class _ProviderOption extends ConsumerStatefulWidget { final Color? rateColor; @override - ConsumerState<_ProviderOption> createState() => _ProviderOptionState(); + ConsumerState createState() => _ProviderOptionState(); } -class _ProviderOptionState extends ConsumerState<_ProviderOption> { +class _ProviderOptionState extends ConsumerState { final isDesktop = Util.isDesktop; late final String _id; @@ -335,9 +331,8 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { return ConditionalParent( condition: isDesktop, - builder: - (child) => - MouseRegion(cursor: SystemMouseCursors.click, child: child), + builder: (child) => + MouseRegion(cursor: SystemMouseCursors.click, child: child), child: GestureDetector( onTap: () { ref.read(efExchangeProvider.notifier).state = widget.exchange; @@ -347,8 +342,9 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { child: Container( color: Colors.transparent, child: Padding( - padding: - isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0), + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -358,18 +354,18 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { child: Padding( padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), child: Radio( - activeColor: - Theme.of( - context, - ).extension()!.radioButtonIconEnabled, + activeColor: Theme.of( + context, + ).extension()!.radioButtonIconEnabled, value: _id, groupValue: groupValue, onChanged: (_) { ref.read(efExchangeProvider.notifier).state = widget.exchange; ref - .read(efExchangeProviderNameProvider.notifier) - .state = widget.estimate?.exchangeProvider ?? + .read(efExchangeProviderNameProvider.notifier) + .state = + widget.estimate?.exchangeProvider ?? widget.exchange.name; }, ), @@ -383,47 +379,41 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { height: isDesktop ? 32 : 24, child: widget.estimate?.exchangeProviderLogo != null && - widget - .estimate! - .exchangeProviderLogo! - .isNotEmpty - ? ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.network( - widget.estimate!.exchangeProviderLogo!, - loadingBuilder: ( - context, - child, - loadingProgress, - ) { - if (loadingProgress == null) { - return child; - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - errorBuilder: (context, error, stackTrace) { - return SvgPicture.asset( - Assets.exchange.getIconFor( - exchangeName: widget.exchange.name, - ), - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ); - }, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - ) - : SvgPicture.asset( - Assets.exchange.getIconFor( - exchangeName: widget.exchange.name, - ), + widget.estimate!.exchangeProviderLogo!.isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.network( + widget.estimate!.exchangeProviderLogo!, + loadingBuilder: + (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + errorBuilder: (context, error, stackTrace) { + return SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ); + }, width: isDesktop ? 32 : 24, height: isDesktop ? 32 : 24, ), + ) + : SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), ), ), const SizedBox(width: 10), @@ -435,55 +425,54 @@ class _ProviderOptionState extends ConsumerState<_ProviderOption> { children: [ ConditionalParent( condition: _warnings.isNotEmpty, - builder: - (child) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - child, - CustomTextButton( - text: _warnings.first.value, - onTap: () { - _showNoSparkWarning(); - }, - ), - ], + builder: (child) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + child, + CustomTextButton( + text: _warnings.first.value, + onTap: () { + _showNoSparkWarning(); + }, ), + ], + ), child: Text( widget.estimate?.exchangeProvider ?? widget.exchange.name, style: STextStyles.titleBold12(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textDark2, + color: Theme.of( + context, + ).extension()!.textDark2, ), ), ), widget.loadingString ? AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: STextStyles.itemSubtitle12(context).copyWith( - color: - Theme.of( - context, - ).extension()!.textSubtitle1, - ), - ) + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ) : Text( - widget.rateString, - style: STextStyles.itemSubtitle12(context).copyWith( - color: - widget.rateColor ?? - Theme.of( - context, - ).extension()!.textSubtitle1, + widget.rateString, + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: + widget.rateColor ?? + Theme.of(context) + .extension()! + .textSubtitle1, + ), ), - ), ], ), ), diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index a2ef393053..e4c264e04b 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -21,7 +21,7 @@ import '../../../themes/stack_colors.dart'; import '../../../utilities/prefs.dart'; import '../../../utilities/util.dart'; import '../../../widgets/rounded_white_container.dart'; -import 'exchange_provider_option.dart'; +import 'sorted_exchange_providers.dart'; class ExchangeProviderOptions extends ConsumerStatefulWidget { const ExchangeProviderOptions({ @@ -94,46 +94,55 @@ class _ExchangeProviderOptionsState return RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), - borderColor: - isDesktop - ? Theme.of(context).extension()!.background - : null, - child: Column( - children: [ - if (showChangeNow) - ExchangeOption( - exchange: ChangeNowExchange.instance, - fixedRate: widget.fixedRate, - reversed: widget.reversed, - ), - if (showChangeNow && showTrocador) - isDesktop - ? Container( - height: 1, - color: Theme.of(context).extension()!.background, - ) - : const SizedBox(height: 16), - if (showTrocador) - ExchangeOption( - fixedRate: widget.fixedRate, - reversed: widget.reversed, - exchange: TrocadorExchange.instance, - ), - if ((showChangeNow || showTrocador) && showNanswap) - isDesktop - ? Container( - height: 1, - color: Theme.of(context).extension()!.background, - ) - : const SizedBox(height: 16), - if (showNanswap) - ExchangeOption( - fixedRate: widget.fixedRate, - reversed: widget.reversed, - exchange: NanswapExchange.instance, - ), + borderColor: isDesktop + ? Theme.of(context).extension()!.background + : null, + child: SortedExchangeProviders( + exchangees: [ + if (showChangeNow) ChangeNowExchange.instance, + if (showTrocador) TrocadorExchange.instance, + if (showNanswap) NanswapExchange.instance, ], + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), + + // Column( + // children: [ + // if (showChangeNow) + // ExchangeOption( + // exchange: ChangeNowExchange.instance, + // fixedRate: widget.fixedRate, + // reversed: widget.reversed, + // ), + // if (showChangeNow && showTrocador) + // isDesktop + // ? Container( + // height: 1, + // color: Theme.of(context).extension()!.background, + // ) + // : const SizedBox(height: 16), + // if (showTrocador) + // ExchangeOption( + // fixedRate: widget.fixedRate, + // reversed: widget.reversed, + // exchange: TrocadorExchange.instance, + // ), + // if ((showChangeNow || showTrocador) && showNanswap) + // isDesktop + // ? Container( + // height: 1, + // color: Theme.of(context).extension()!.background, + // ) + // : const SizedBox(height: 16), + // if (showNanswap) + // ExchangeOption( + // fixedRate: widget.fixedRate, + // reversed: widget.reversed, + // exchange: NanswapExchange.instance, + // ), + // ], + // ), ); } } diff --git a/lib/pages/exchange_view/sub_widgets/sorted_exchange_providers.dart b/lib/pages/exchange_view/sub_widgets/sorted_exchange_providers.dart new file mode 100644 index 0000000000..c478f9ead0 --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/sorted_exchange_providers.dart @@ -0,0 +1,265 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:tuple/tuple.dart'; + +import '../../../app_config.dart'; +import '../../../models/exchange/response_objects/estimate.dart'; +import '../../../models/exchange/response_objects/range.dart'; +import '../../../providers/exchange/exchange_form_state_provider.dart'; +import '../../../providers/global/locale_provider.dart'; +import '../../../services/exchange/exchange.dart'; +import '../../../services/exchange/exchange_response.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/amount/amount_formatter.dart'; +import '../../../utilities/amount/amount_unit.dart'; +import '../../../utilities/enums/exchange_rate_type_enum.dart'; +import '../../../utilities/util.dart'; +import '../../../wallets/crypto_currency/crypto_currency.dart'; +import '../../../widgets/conditional_parent.dart'; +import '../../../widgets/loading_indicator.dart'; +import 'exchange_provider_option.dart'; + +class SortedExchangeProviders extends ConsumerStatefulWidget { + const SortedExchangeProviders({ + super.key, + required this.exchangees, + required this.fixedRate, + required this.reversed, + }); + + final List exchangees; + final bool fixedRate; + final bool reversed; + + @override + ConsumerState createState() => + _SortedExchangeProvidersState(); +} + +class _SortedExchangeProvidersState + extends ConsumerState { + final List<(Exchange, Tuple2>, Range?>?)> + dataList = []; + final List<(Exchange, List?)> estimates = []; + + List<(Exchange, Estimate?)> transform(Decimal amount, String rcvTicker) { + final List<(Exchange, Estimate?)> flattened = []; + + for (final s in estimates) { + if (s.$2 != null && s.$2!.isNotEmpty) { + for (final e in s.$2!) { + flattened.add((s.$1, e)); + } + } else { + flattened.add((s.$1, null)); + } + } + + flattened.sort((a, b) { + if (a.$2 == null && b.$2 == null) return 1; + if (a.$2 != null && b.$2 == null) return 0; + if (a.$2 == null && b.$2 != null) return 0; + + // or we get problems!!! + assert(a.$2!.reversed == b.$2!.reversed); + + return _getRate(a.$2!, amount, rcvTicker) > + _getRate(b.$2!, amount, rcvTicker) + ? 0 + : 1; + }); + + return flattened; + } + + Amount _getRate(Estimate e, Decimal amount, String rcvTicker) { + int decimals; + try { + decimals = AppConfig.getCryptoCurrencyForTicker( + rcvTicker, + )!.fractionDigits; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (e.reversed) { + rate = (amount / e.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (e.estimatedAmount / amount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } + return rate; + } + + @override + Widget build(BuildContext context) { + final sendCurrency = ref.watch( + efCurrencyPairProvider.select((value) => value.send), + ); + final receivingCurrency = ref.watch( + efCurrencyPairProvider.select((value) => value.receive), + ); + final reversed = ref.watch(efReversedProvider); + final amount = reversed + ? ref.watch(efReceiveAmountProvider) + : ref.watch(efSendAmountProvider); + + dataList.clear(); + estimates.clear(); + for (final exchange in widget.exchangees) { + final data = ref.watch(efEstimatesListProvider(exchange.name)); + dataList.add((exchange, data)); + estimates.add((exchange, data?.item1.value)); + } + + // final data = ref.watch(efEstimatesListProvider(widget.exchange.name)); + // final estimates = data?.item1.value; + + final pair = sendCurrency != null && receivingCurrency != null + ? (from: sendCurrency, to: receivingCurrency) + : null; + + if (ref.watch(efRefreshingProvider)) { + return const LoadingIndicator(width: 48, height: 48); + } + + if (sendCurrency != null && + receivingCurrency != null && + amount != null && + amount > Decimal.zero) { + final estimates = transform(amount, receivingCurrency.ticker); + + if (estimates.isNotEmpty) { + return Column( + mainAxisSize: .min, + children: [ + for (int i = 0; i < estimates.length; i++) + Builder( + builder: (context) { + final e = estimates[i].$2; + + if (e == null) { + return Consumer( + builder: (_, ref, __) { + String? message; + + final data = dataList + .firstWhere((e) => identical(e.$1, estimates[i].$1)) + .$2; + + final range = data?.item2; + if (range != null) { + if (range.min != null && amount < range.min!) { + message ??= "Amount too small"; + } else if (range.max != null && amount > range.max!) { + message ??= "Amount too large"; + } + } else if (data?.item1.value == null) { + final rateType = + ref.watch(efRateTypeProvider) == + ExchangeRateType.estimated + ? "estimated" + : "fixed"; + message ??= "Pair unavailable on $rateType rate flow"; + } + + return ExchProviderOption( + exchange: estimates[i].$1, + estimate: null, + pair: pair, + rateString: message ?? "Failed to fetch rate", + rateColor: Theme.of( + context, + ).extension()!.textError, + ); + }, + ); + } + + final rate = _getRate(e, amount, receivingCurrency.ticker); + + CryptoCurrency? coin; + try { + coin = AppConfig.getCryptoCurrencyForTicker( + receivingCurrency.ticker, + ); + } catch (_) { + coin = null; + } + + final String rateString; + if (coin != null) { + rateString = + "1 ${sendCurrency.ticker.toUpperCase()} " + "~ ${ref.watch(pAmountFormatter(coin)).format(rate)}"; + } else { + final formatter = AmountFormatter( + unit: AmountUnit.normal, + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + coin: Bitcoin( + CryptoCurrencyNetwork.main, + ), // some sane default + maxDecimals: 8, // some sane default + ); + rateString = + "1 ${sendCurrency.ticker.toUpperCase()} " + "~ ${formatter.format(rate, withUnitName: false)}" + " ${receivingCurrency.ticker.toUpperCase()}"; + } + + return ConditionalParent( + condition: i > 0, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Util.isDesktop + ? Container( + height: 1, + color: Theme.of( + context, + ).extension()!.background, + ) + : const SizedBox(height: 16), + child, + ], + ), + child: ExchProviderOption( + key: Key(estimates[i].$1.name + e.exchangeProvider), + exchange: estimates[i].$1, + pair: pair, + estimate: e, + rateString: rateString, + kycRating: e.kycRating, + ), + ); + }, + ), + ], + ); + } + } + + return Column( + mainAxisSize: .min, + children: [ + ...widget.exchangees.map( + (e) => ExchProviderOption( + exchange: e, + estimate: null, + pair: pair, + rateString: "n/a", + ), + ), + ], + ); + } +} diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 57808de9fd..58ffaad712 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -352,6 +352,11 @@ class _WalletSettingsViewState extends ConsumerState { canBackup = false; } + final shouldShowClearSparkCache = + wallet is SparkInterface && + (!wallet.isViewOnly || + (wallet.isViewOnly && wallet.viewOnlyType == .spark)); + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -363,258 +368,251 @@ class _WalletSettingsViewState extends ConsumerState { ), title: Text("Settings", style: STextStyles.navBarTitle(context)), ), - body: SafeArea( - child: LayoutBuilder( - builder: (builderContext, constraints) { - return Padding( - padding: const EdgeInsets.only(left: 12, top: 12, right: 12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(4), - child: Column( - children: [ - SettingsListButton( - iconAssetName: Assets.svg.addressBook, - iconSize: 16, - title: "Address book", - onPressed: () { - Navigator.of(context).pushNamed( - AddressBookView.routeName, - arguments: coin, - ); - }, - ), - if (coin is FrostCurrency) - const SizedBox(height: 8), - if (coin is FrostCurrency) - SettingsListButton( - iconAssetName: Assets.svg.addressBook2, - iconSize: 16, - title: "FROST Multisig settings", - onPressed: () { - Navigator.of(context).pushNamed( - FrostMSWalletOptionsView.routeName, - arguments: walletId, - ); - }, - ), - const SizedBox(height: 8), - SettingsListButton( - iconAssetName: Assets.svg.node, - iconSize: 16, - title: "Network", - onPressed: () { - Navigator.of(context).pushNamed( - WalletNetworkSettingsView.routeName, - arguments: Tuple3( - walletId, - _currentSyncStatus, - widget.initialNodeStatus, - ), - ); - }, - ), - if (canBackup) const SizedBox(height: 8), - if (canBackup) - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.lock, - iconSize: 16, - title: "Wallet backup", - onPressed: _walletBackupPressed, - ); - }, - ), - const SizedBox(height: 8), - SettingsListButton( - iconAssetName: Assets.svg.downloadFolder, - title: "Wallet settings", - iconSize: 16, - onPressed: () { - Navigator.of(context).pushNamed( - WalletSettingsWalletSettingsView - .routeName, - arguments: walletId, - ); - }, - ), - const SizedBox(height: 8), - SettingsListButton( - iconAssetName: Assets.svg.arrowRotate, - title: "Syncing preferences", - onPressed: () { - Navigator.of(context).pushNamed( - SyncingPreferencesView.routeName, - ); - }, - ), - if (xPubEnabled) const SizedBox(height: 8), - if (xPubEnabled) - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.eye, - title: "Wallet xPub", - onPressed: _walletXPubPressed, - ); - }, - ), - if (sparkViewKeyEnabled) - const SizedBox(height: 8), - if (sparkViewKeyEnabled) - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.eye, - title: "Spark view key", - onPressed: _walletSparkViewKeyPressed, - ); - }, - ), - if (coin is Firo) const SizedBox(height: 8), - if (coin is Firo) - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.eye, - title: "Clear electrumx cache", - onPressed: () async { - String? result; - await showDialog( - useSafeArea: false, - barrierDismissible: true, - context: context, - builder: (_) => StackOkDialog( - title: - "Are you sure you want to clear " - "${coin.prettyName} electrumx cache?", - onOkPressed: (value) { - result = value; - }, - leftButton: SecondaryButton( - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - ); - - if (result == "OK" && - context.mounted) { - await showLoading( - whileFuture: Future.wait([ - Future.delayed( - const Duration( - milliseconds: 1500, - ), - ), - DB.instance - .clearSharedTransactionCache( - currency: coin, - ), - if (coin is Firo) - FiroCacheCoordinator.clearSharedCache( - coin.network, - ), - ]), - context: context, - message: "Clearing cache...", - ); - } - }, - ); - }, - ), - if (coin is NanoCurrency) - const SizedBox(height: 8), - if (coin is NanoCurrency) - Consumer( - builder: (_, ref, __) { - return SettingsListButton( - iconAssetName: Assets.svg.eye, - title: "Change representative", - onPressed: () { - Navigator.of(context).pushNamed( - ChangeRepresentativeView - .routeName, - arguments: widget.walletId, - ); - }, - ); - }, - ), - // const SizedBox( - // height: 8, - // ), - // SettingsListButton( - // iconAssetName: Assets.svg.ellipsis, - // title: "Debug Info", - // onPressed: () { - // Navigator.of(context) - // .pushNamed(DebugView.routeName); - // }, - // ), - ], - ), + body: _WalletSettingsViewBody( + children: [ + SettingsListButton( + iconAssetName: Assets.svg.addressBook, + iconSize: 16, + title: "Address book", + onPressed: () { + Navigator.of( + context, + ).pushNamed(AddressBookView.routeName, arguments: coin); + }, + ), + if (coin is FrostCurrency) const SizedBox(height: 8), + if (coin is FrostCurrency) + SettingsListButton( + iconAssetName: Assets.svg.addressBook2, + iconSize: 16, + title: "FROST Multisig settings", + onPressed: () { + Navigator.of(context).pushNamed( + FrostMSWalletOptionsView.routeName, + arguments: walletId, + ); + }, + ), + const SizedBox(height: 8), + SettingsListButton( + iconAssetName: Assets.svg.node, + iconSize: 16, + title: "Network", + onPressed: () { + Navigator.of(context).pushNamed( + WalletNetworkSettingsView.routeName, + arguments: Tuple3( + walletId, + _currentSyncStatus, + widget.initialNodeStatus, + ), + ); + }, + ), + if (canBackup) const SizedBox(height: 8), + if (canBackup) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.lock, + iconSize: 16, + title: "Wallet backup", + onPressed: _walletBackupPressed, + ); + }, + ), + const SizedBox(height: 8), + SettingsListButton( + iconAssetName: Assets.svg.downloadFolder, + title: "Wallet settings", + iconSize: 16, + onPressed: () { + Navigator.of(context).pushNamed( + WalletSettingsWalletSettingsView.routeName, + arguments: walletId, + ); + }, + ), + const SizedBox(height: 8), + SettingsListButton( + iconAssetName: Assets.svg.arrowRotate, + title: "Syncing preferences", + onPressed: () { + Navigator.of( + context, + ).pushNamed(SyncingPreferencesView.routeName); + }, + ), + if (xPubEnabled) const SizedBox(height: 8), + if (xPubEnabled) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.eye, + title: "Wallet xPub", + onPressed: _walletXPubPressed, + ); + }, + ), + if (sparkViewKeyEnabled) const SizedBox(height: 8), + if (sparkViewKeyEnabled) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.eye, + title: "Spark view key", + onPressed: _walletSparkViewKeyPressed, + ); + }, + ), + if (shouldShowClearSparkCache) const SizedBox(height: 8), + if (shouldShowClearSparkCache) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.eye, + title: "Clear electrumx cache", + onPressed: () async { + String? result; + await showDialog( + useSafeArea: false, + barrierDismissible: true, + context: context, + builder: (_) => StackOkDialog( + title: + "Are you sure you want to clear " + "${coin.prettyName} electrumx cache?", + onOkPressed: (value) { + result = value; + }, + leftButton: SecondaryButton( + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ); + + if (result == "OK" && context.mounted) { + await showLoading( + whileFuture: Future.wait([ + Future.delayed(const Duration(milliseconds: 1500)), + DB.instance.clearSharedTransactionCache( + currency: coin, ), - const SizedBox(height: 12), - const Spacer(), - Consumer( - builder: (_, ref, __) { - return TextButton( - onPressed: () { - // TODO: [prio=med] needs more thought if this is still required - // ref - // .read(pWallets) - // .getWallet(walletId) - // .isActiveWallet = false; - ref - .read( - transactionFilterProvider.state, - ) - .state = - null; - - Navigator.of(context).popUntil( - ModalRoute.withName(HomeView.routeName), - ); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonStyle(context), - child: Text( - "Log out", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), + if (coin is Firo) + FiroCacheCoordinator.clearSharedCache( + coin.network, + ), + ]), + context: context, + message: "Clearing cache...", + ); + } + }, + ); + }, + ), + if (coin is NanoCurrency) const SizedBox(height: 8), + if (coin is NanoCurrency) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.eye, + title: "Change representative", + onPressed: () { + Navigator.of(context).pushNamed( + ChangeRepresentativeView.routeName, + arguments: widget.walletId, + ); + }, + ); + }, + ), + // const SizedBox( + // height: 8, + // ), + // SettingsListButton( + // iconAssetName: Assets.svg.ellipsis, + // title: "Debug Info", + // onPressed: () { + // Navigator.of(context) + // .pushNamed(DebugView.routeName); + // }, + // ), + ], + ), + ), + ); + } +} + +class _WalletSettingsViewBody extends StatelessWidget { + const _WalletSettingsViewBody({super.key, required this.children}); + + final List children; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only(left: 12, top: 12, right: 12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(4), + child: Column(children: children), + ), + + const SizedBox(height: 12), + const Spacer(), + Consumer( + builder: (_, ref, __) { + return TextButton( + onPressed: () { + ref + .read(transactionFilterProvider.state) + .state = + null; + + Navigator.of(context).popUntil( + ModalRoute.withName(HomeView.routeName), ); }, - ), - ], + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Log out", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ); + }, ), - ), + ], ), ), ), - ); - }, - ), - ), + ), + ), + ); + }, ), ); } @@ -683,7 +681,7 @@ class _EpiBoxInfoFormState extends ConsumerState { hostController.text, int.parse(portController.text), ); - if (mounted) { + if (context.mounted) { await showFloatingFlushBar( context: context, message: "Epicbox info saved!", @@ -692,11 +690,13 @@ class _EpiBoxInfoFormState extends ConsumerState { } unawaited(wallet.refresh()); } catch (e) { - await showFloatingFlushBar( - context: context, - message: "Failed to save epicbox info: $e", - type: FlushBarType.warning, - ); + if (context.mounted) { + await showFloatingFlushBar( + context: context, + message: "Failed to save epicbox info: $e", + type: FlushBarType.warning, + ); + } } }, child: Text( @@ -778,7 +778,7 @@ class _MwcmqsInfoFormState extends ConsumerState { hostController.text, int.parse(portController.text), ); - if (mounted) { + if (context.mounted) { await showFloatingFlushBar( context: context, message: "Mwcmqs info saved!", @@ -787,11 +787,13 @@ class _MwcmqsInfoFormState extends ConsumerState { } unawaited(wallet.refresh()); } catch (e) { - await showFloatingFlushBar( - context: context, - message: "Failed to save mwcmqs info: $e", - type: FlushBarType.warning, - ); + if (context.mounted) { + await showFloatingFlushBar( + context: context, + message: "Failed to save mwcmqs info: $e", + type: FlushBarType.warning, + ); + } } }, child: Text( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index 04907952e5..a5464cdb53 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -134,7 +134,9 @@ class _WalletSettingsWalletSettingsViewState return StackDialog( title: "Warning!", message: - "Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?", + "Reusing addresses reduces your privacy and " + "security. Are you sure you want to reuse " + "addresses by default?", leftButton: TextButton( style: Theme.of(context) .extension()! @@ -187,8 +189,9 @@ class _WalletSettingsWalletSettingsViewState return StackDialog( title: "Notice", message: - "Activating MWEB requires synchronizing on-chain MWEB related data. " - "This currently requires about 800 MB of storage.", + "Activating MWEB requires synchronizing on-chain MWEB " + "related data. This currently requires about " + "800 MB of storage.", leftButton: SecondaryButton( onPressed: () { Navigator.of(context).pop(false); @@ -292,7 +295,6 @@ class _WalletSettingsWalletSettingsViewState RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -362,7 +364,6 @@ class _WalletSettingsWalletSettingsViewState RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( @@ -412,7 +413,6 @@ class _WalletSettingsWalletSettingsViewState RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( @@ -628,7 +628,6 @@ class _WalletSettingsWalletSettingsViewState RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -642,7 +641,8 @@ class _WalletSettingsWalletSettingsViewState context: context, builder: (_) => StackDialog( title: - "Do you want to delete ${ref.read(pWalletName(widget.walletId))}?", + "Do you want to delete " + "${ref.read(pWalletName(widget.walletId))}?", leftButton: TextButton( style: Theme.of(context) .extension()! diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart deleted file mode 100644 index a022be5888..0000000000 --- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart +++ /dev/null @@ -1,9 +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 - * - */ diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart index 63366f22fa..e052df5513 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -580,7 +580,8 @@ class _DesktopWalletFeaturesState extends ConsumerState { final showMwebOption = wallet is MwebInterface && !wallet.isViewOnly; final extraOptions = [ - if (wallet is SparkInterface && !isViewOnly) + if (wallet is SparkInterface && + (!isViewOnly || (isViewOnly && wallet.viewOnlyType == .spark))) (WalletFeature.clearSparkCache, Assets.svg.key, () => ()), if (wallet is RbfInterface) (WalletFeature.rbf, Assets.svg.key, () => ()), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart index 74be0581c4..2a635e5c0a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -134,7 +134,9 @@ class _MoreFeaturesDialogState extends ConsumerState { mainAxisSize: MainAxisSize.min, children: [ Text( - "Reusing addresses reduces your privacy and security. Are you sure you want to reuse addresses by default?", + "Reusing addresses reduces your privacy and " + "security. Are you sure you want to reuse " + "addresses by default?", style: STextStyles.desktopTextSmall(context), ), const SizedBox(height: 43), @@ -238,8 +240,9 @@ class _MoreFeaturesDialogState extends ConsumerState { mainAxisSize: MainAxisSize.min, children: [ Text( - "Activating MWEB requires synchronizing on-chain MWEB related data. " - "This currently requires about 800 MB of storage.", + "Activating MWEB requires synchronizing on-chain " + "MWEB related data. This currently requires about " + "800 MB of storage.", style: STextStyles.desktopTextSmall(context), ), const SizedBox(height: 43), diff --git a/lib/wallets/wallet/impl/bitcoin_wallet.dart b/lib/wallets/wallet/impl/bitcoin_wallet.dart index 6361ec135d..dc1cf5df40 100644 --- a/lib/wallets/wallet/impl/bitcoin_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoin_wallet.dart @@ -37,18 +37,17 @@ class BitcoinWallet 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; } @@ -71,7 +70,7 @@ class BitcoinWallet extends Bip39HDWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } // diff --git a/lib/wallets/wallet/impl/bitcoincash_wallet.dart b/lib/wallets/wallet/impl/bitcoincash_wallet.dart index 5edcbf9f75..4191052bc2 100644 --- a/lib/wallets/wallet/impl/bitcoincash_wallet.dart +++ b/lib/wallets/wallet/impl/bitcoincash_wallet.dart @@ -67,20 +67,19 @@ class BitcoincashWallet @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; } @@ -103,17 +102,15 @@ 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}; @@ -389,7 +386,7 @@ class BitcoincashWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } @override diff --git a/lib/wallets/wallet/impl/dash_wallet.dart b/lib/wallets/wallet/impl/dash_wallet.dart index 9d39bd26f7..a00faf77c5 100644 --- a/lib/wallets/wallet/impl/dash_wallet.dart +++ b/lib/wallets/wallet/impl/dash_wallet.dart @@ -36,18 +36,17 @@ 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; } @@ -60,16 +59,14 @@ 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}; @@ -83,11 +80,10 @@ class DashWallet extends Bip39HDWallet 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 || @@ -319,6 +315,6 @@ class DashWallet extends Bip39HDWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } } diff --git a/lib/wallets/wallet/impl/dogecoin_wallet.dart b/lib/wallets/wallet/impl/dogecoin_wallet.dart index 01a1eed402..444b0bafaf 100644 --- a/lib/wallets/wallet/impl/dogecoin_wallet.dart +++ b/lib/wallets/wallet/impl/dogecoin_wallet.dart @@ -38,18 +38,17 @@ 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; } @@ -62,16 +61,14 @@ 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}; @@ -85,11 +82,10 @@ class DogecoinWallet 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 || @@ -323,6 +319,6 @@ class DogecoinWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } } diff --git a/lib/wallets/wallet/impl/ecash_wallet.dart b/lib/wallets/wallet/impl/ecash_wallet.dart index 4a72b2b945..9e83afb70f 100644 --- a/lib/wallets/wallet/impl/ecash_wallet.dart +++ b/lib/wallets/wallet/impl/ecash_wallet.dart @@ -55,16 +55,15 @@ class EcashWallet extends Bip39HDWallet @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; } @@ -87,17 +86,15 @@ 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}; @@ -108,11 +105,10 @@ class EcashWallet extends Bip39HDWallet 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 || @@ -360,7 +356,7 @@ class EcashWallet extends Bip39HDWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } @override diff --git a/lib/wallets/wallet/impl/fact0rn_wallet.dart b/lib/wallets/wallet/impl/fact0rn_wallet.dart index 0f6a93d0d9..3ddd053db2 100644 --- a/lib/wallets/wallet/impl/fact0rn_wallet.dart +++ b/lib/wallets/wallet/impl/fact0rn_wallet.dart @@ -35,18 +35,17 @@ class Fact0rnWallet @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,16 +58,14 @@ class Fact0rnWallet 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}; @@ -82,11 +79,10 @@ class Fact0rnWallet 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 || @@ -321,6 +317,6 @@ class Fact0rnWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } } diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart index 8c69f79705..bd2b3f70f7 100644 --- a/lib/wallets/wallet/impl/firo_wallet.dart +++ b/lib/wallets/wallet/impl/firo_wallet.dart @@ -813,6 +813,6 @@ class FiroWallet extends Bip39HDWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } } diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart index db497a9040..c9fa52a330 100644 --- a/lib/wallets/wallet/impl/litecoin_wallet.dart +++ b/lib/wallets/wallet/impl/litecoin_wallet.dart @@ -49,20 +49,19 @@ class LitecoinWallet @override Future> fetchAddressesForElectrumXScan() async { - final allAddresses = - await mainDB - .getAddresses(walletId) - .filter() - .not() - .group( - (q) => q - .typeEqualTo(AddressType.mweb) - .or() - .typeEqualTo(AddressType.nonWallet) - .or() - .subTypeEqualTo(AddressSubType.nonWallet), - ) - .findAll(); + final allAddresses = await mainDB + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(AddressType.mweb) + .or() + .typeEqualTo(AddressType.nonWallet) + .or() + .subTypeEqualTo(AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } @@ -75,16 +74,14 @@ 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}; @@ -102,11 +99,10 @@ class LitecoinWallet 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 || @@ -239,10 +235,9 @@ class LitecoinWallet final db = Drift.get(walletId); - final mwebUtxo = - await (db.select( - db.mwebUtxos, - )..where((e) => e.outputId.equals(outputId))).getSingleOrNull(); + final mwebUtxo = await (db.select( + db.mwebUtxos, + )..where((e) => e.outputId.equals(outputId))).getSingleOrNull(); final output = OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "mweb", @@ -283,13 +278,12 @@ 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; } else { @@ -384,7 +378,7 @@ class LitecoinWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } // diff --git a/lib/wallets/wallet/impl/namecoin_wallet.dart b/lib/wallets/wallet/impl/namecoin_wallet.dart index a6dd6f74b7..10ea40c5a7 100644 --- a/lib/wallets/wallet/impl/namecoin_wallet.dart +++ b/lib/wallets/wallet/impl/namecoin_wallet.dart @@ -72,18 +72,17 @@ class NamecoinWallet @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; } @@ -140,9 +139,8 @@ class NamecoinWallet blockReason = "Contains name"; try { - final rawNameOP = - (output["scriptPubKey"]["nameOp"] as Map) - .cast(); + final rawNameOP = (output["scriptPubKey"]["nameOp"] as Map) + .cast(); otherDataString = jsonEncode({ UTXOOtherDataKeys.nameOpData: jsonEncode(rawNameOP), @@ -201,7 +199,7 @@ class NamecoinWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } // TODO: Check if this is the correct formula for namecoin. @@ -227,16 +225,14 @@ class NamecoinWallet 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}; @@ -250,11 +246,10 @@ class NamecoinWallet 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 || @@ -442,8 +437,11 @@ class NamecoinWallet ) async { // first check own utxos. Should only need to check NAME NEW here. // NAME UPDATE and NAME FIRST UPDATE will appear readable from electrumx - final utxos = - await mainDB.getUTXOs(walletId).filter().otherDataIsNotNull().findAll(); + final utxos = await mainDB + .getUTXOs(walletId) + .filter() + .otherDataIsNotNull() + .findAll(); for (final utxo in utxos) { final nameOp = getOpNameDataFrom(utxo); if (nameOp?.op == OpName.nameNew) { @@ -509,18 +507,17 @@ class NamecoinWallet try { final currentHeight = await chainHeight; // not ideal filtering - final utxos = - await mainDB - .getUTXOs(walletId) - .filter() - .otherDataIsNotNull() - .and() - .blockHeightIsNotNull() - .and() - .blockHeightGreaterThan(0) - .and() - .blockHeightLessThan(currentHeight - kNameWaitBlocks) - .findAll(); + final utxos = await mainDB + .getUTXOs(walletId) + .filter() + .otherDataIsNotNull() + .and() + .blockHeightIsNotNull() + .and() + .blockHeightGreaterThan(0) + .and() + .blockHeightLessThan(currentHeight - kNameWaitBlocks) + .findAll(); Logging.instance.t( "_unknownNameNewOutputs(count=${_unknownNameNewOutputs.length})" @@ -572,8 +569,9 @@ class NamecoinWallet data.salt, ); - String noteName = - data.name.startsWith("d/") ? data.name.substring(2) : data.name; + String noteName = data.name.startsWith("d/") + ? data.name.substring(2) + : data.name; if (!noteName.endsWith(".bit")) { noteName += ".bit"; } @@ -638,8 +636,10 @@ class NamecoinWallet assert(txData.recipients!.where((e) => !e.isChange).length == 1); if (!isForFeeCalcPurposesOnly) { - final nameAmount = - txData.recipients!.where((e) => !e.isChange).first.amount; + final nameAmount = txData.recipients! + .where((e) => !e.isChange) + .first + .amount; switch (txData.opNameState!.type) { case OpName.nameNew: @@ -664,10 +664,9 @@ class NamecoinWallet ); // TODO: [prio=high]: check this opt in rbf - final sequence = - this is RbfInterface && (this as RbfInterface).flagOptInRBF - ? 0xffffffff - 10 - : 0xffffffff - 1; + final sequence = this is RbfInterface && (this as RbfInterface).flagOptInRBF + ? 0xffffffff - 10 + : 0xffffffff - 1; // Add transaction inputs for (int i = 0; i < inputsWithKeys.length; i++) { @@ -737,10 +736,9 @@ class NamecoinWallet txid: inputsWithKeys[i].utxo.txid, vout: inputsWithKeys[i].utxo.vout, ), - addresses: - inputsWithKeys[i].utxo.address == null - ? [] - : [inputsWithKeys[i].utxo.address!], + addresses: inputsWithKeys[i].utxo.address == null + ? [] + : [inputsWithKeys[i].utxo.address!], valueStringSats: inputsWithKeys[i].utxo.value.toString(), witness: null, innerRedeemScriptAsm: null, @@ -872,9 +870,9 @@ class NamecoinWallet version: clTx.version, type: tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) && - txData.paynymAccountLite == null - ? TransactionType.sentToSelf - : TransactionType.outgoing, + txData.paynymAccountLite == null + ? TransactionType.sentToSelf + : TransactionType.outgoing, subType: TransactionSubType.none, otherData: null, ), @@ -1023,20 +1021,19 @@ class NamecoinWallet final canCPFP = this is CpfpInterface && coinControl; - final spendableOutputs = - availableOutputs - .where( - (e) => - !e.isBlocked && - (e.used != true) && - (canCPFP || - e.isConfirmed( - currentChainHeight, - cryptoCurrency.minConfirms, - cryptoCurrency.minCoinbaseConfirms, - )), - ) - .toList(); + final spendableOutputs = availableOutputs + .where( + (e) => + !e.isBlocked && + (e.used != true) && + (canCPFP || + e.isConfirmed( + currentChainHeight, + cryptoCurrency.minConfirms, + cryptoCurrency.minCoinbaseConfirms, + )), + ) + .toList(); if (coinControl) { if (spendableOutputs.length < availableOutputs.length) { @@ -1118,24 +1115,22 @@ class NamecoinWallet final List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data - final inputsWithKeys = - (await addSigningKeys( - utxoObjectsToUse.map((e) => StandardInput(e)).toList(), - )).whereType().toList(); + final inputsWithKeys = (await addSigningKeys( + utxoObjectsToUse.map((e) => StandardInput(e)).toList(), + )).whereType().toList(); final int vSizeForOneOutput; try { - vSizeForOneOutput = - (await _createNameTx( - inputsWithKeys: inputsWithKeys, - isForFeeCalcPurposesOnly: true, - txData: txData.copyWith( - recipients: await helperRecipientsConvert( - [recipientAddress], - [satoshisBeingUsed], - ), - ), - )).vSize!; + vSizeForOneOutput = (await _createNameTx( + inputsWithKeys: inputsWithKeys, + isForFeeCalcPurposesOnly: true, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress], + [satoshisBeingUsed], + ), + ), + )).vSize!; } catch (e, s) { Logging.instance.e("vSizeForOneOutput: $e", error: e, stackTrace: s); rethrow; @@ -1146,20 +1141,19 @@ class NamecoinWallet BigInt maxBI(BigInt a, BigInt b) => a > b ? a : b; try { - vSizeForTwoOutPuts = - (await _createNameTx( - inputsWithKeys: inputsWithKeys, - isForFeeCalcPurposesOnly: true, - txData: txData.copyWith( - recipients: await helperRecipientsConvert( - [recipientAddress, (await getCurrentChangeAddress())!.value], - [ - satoshiAmountToSend, - maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend), - ], - ), - ), - )).vSize!; + vSizeForTwoOutPuts = (await _createNameTx( + inputsWithKeys: inputsWithKeys, + isForFeeCalcPurposesOnly: true, + txData: txData.copyWith( + recipients: await helperRecipientsConvert( + [recipientAddress, (await getCurrentChangeAddress())!.value], + [ + satoshiAmountToSend, + maxBI(BigInt.zero, satoshisBeingUsed - satoshiAmountToSend), + ], + ), + ), + )).vSize!; } catch (e, s) { Logging.instance.e("vSizeForTwoOutPuts: $e", error: e, stackTrace: s); rethrow; @@ -1170,18 +1164,18 @@ class NamecoinWallet satsPerVByte != null ? (satsPerVByte * vSizeForOneOutput) : estimateTxFee( - vSize: vSizeForOneOutput, - feeRatePerKB: selectedTxFeeRate, - ), + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ), ); // Assume 2 outputs, one for recipient and one for change final feeForTwoOutputs = BigInt.from( satsPerVByte != null ? (satsPerVByte * vSizeForTwoOutPuts) : estimateTxFee( - vSize: vSizeForTwoOutPuts, - feeRatePerKB: selectedTxFeeRate, - ), + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ), ); Logging.instance.d( diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index eb9fb60437..6f2b9764ea 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -45,18 +45,17 @@ 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; } @@ -115,7 +114,7 @@ class ParticlWallet @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } @override @@ -140,16 +139,14 @@ 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}; @@ -163,11 +160,10 @@ class ParticlWallet 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 || @@ -382,31 +378,28 @@ 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: @@ -414,13 +407,12 @@ 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: @@ -469,10 +461,9 @@ class ParticlWallet txid: insAndKeys[i].utxo.txid, vout: insAndKeys[i].utxo.vout, ), - addresses: - insAndKeys[i].utxo.address == null - ? [] - : [insAndKeys[i].utxo.address!], + addresses: insAndKeys[i].utxo.address == null + ? [] + : [insAndKeys[i].utxo.address!], valueStringSats: insAndKeys[i].utxo.value.toString(), witness: null, innerRedeemScriptAsm: null, diff --git a/lib/wallets/wallet/impl/peercoin_wallet.dart b/lib/wallets/wallet/impl/peercoin_wallet.dart index 8046f0d23c..bcdb36c3ed 100644 --- a/lib/wallets/wallet/impl/peercoin_wallet.dart +++ b/lib/wallets/wallet/impl/peercoin_wallet.dart @@ -37,18 +37,17 @@ class PeercoinWallet @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; } @@ -74,7 +73,7 @@ class PeercoinWallet /// we can just pretend vSize is size for peercoin @override int estimateTxFee({required int vSize, required BigInt feeRatePerKB}) { - return vSize * (feeRatePerKB.toInt() / 1000).ceil(); + return (feeRatePerKB * BigInt.from(vSize) ~/ BigInt.from(1000)).toInt(); } // =========================================================================== @@ -98,16 +97,14 @@ class PeercoinWallet 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}; @@ -121,11 +118,10 @@ class PeercoinWallet 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 || diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart index c5dcd279b8..98fd505ffd 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart @@ -1942,6 +1942,7 @@ mixin ElectrumXInterface final data = await (this as MwebInterface).processMwebTransaction( mwebData, ); + Logging.instance.d("prepare MWEB send: $data"); return data.copyWith(fee: fee); } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart index 35a00595a0..e183be63b6 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart @@ -185,8 +185,45 @@ mixin MwebInterface Logging.instance.i("info.restoreHeight: ${info.restoreHeight}"); Logging.instance.i( - "info.otherData[WalletInfoKeys.mwebScanHeight]: ${info.otherData[WalletInfoKeys.mwebScanHeight]}", + "info.otherData[WalletInfoKeys.mwebScanHeight]:" + " ${info.otherData[WalletInfoKeys.mwebScanHeight]}", ); + + // ========================================================================= + final List utxos = []; + final stream = await client.utxos( + UtxosRequest( + fromHeight: info.restoreHeight, + scanSecret: await _scanSecret, + ), + ); + try { + await for (final utxo in stream.timeout(const Duration(seconds: 2))) { + final newUtxo = MwebUtxosCompanion( + outputId: Value(utxo.outputId), + address: Value(utxo.address), + value: Value(utxo.value.toInt()), + height: Value(utxo.height), + blockTime: Value(utxo.blockTime), + blocked: const Value(false), + used: const Value(false), + ); + utxos.add(newUtxo); + } + } catch (_) {} + + try { + await stream.cancel(); + } catch (_) {} + final db = Drift.get(walletId); + await db.transaction(() async { + await db.delete(db.mwebUtxos).go(); + for (final utxo in utxos) { + await db.into(db.mwebUtxos).insert(utxo); + } + }); + // ========================================================================= + final fromHeight = info.otherData[WalletInfoKeys.mwebScanHeight] as int? ?? info.restoreHeight; @@ -196,7 +233,6 @@ mixin MwebInterface scanSecret: await _scanSecret, ); - final db = Drift.get(walletId); _mwebUtxoSubscription = (await client.utxos(request)).listen((utxo) async { Logging.instance.t( "Found UTXO in stream: Utxo(" @@ -322,7 +358,8 @@ mixin MwebInterface Future
generateNextMwebAddress({bool isChange = false}) async { if (!info.isMwebEnabled) { throw Exception( - "Tried calling generateNextMwebAddress with mweb disabled for $walletId ${info.name}", + "Tried calling generateNextMwebAddress with mweb " + "disabled for $walletId ${info.name}", ); } @@ -382,7 +419,8 @@ mixin MwebInterface SpentRequest(outputId: [input.outpoint!.txid]), ); if (response.outputId.contains(input.outpoint!.txid)) { - // dummy to show tx as confirmed. Need a better way to handle this as its kind of stupid, resulting in terrible UX + // dummy to show tx as confirmed. Need a better way to handle + // this as its kind of stupid, resulting in terrible UX final dummyHeight = await chainHeight; TransactionV2? transaction = await mainDB.isar.transactionV2s @@ -480,7 +518,8 @@ mixin MwebInterface Future _confirmSendMweb({required TxData txData}) async { if (!info.isMwebEnabled) { throw Exception( - "Tried calling _confirmSendMweb with mweb disabled for $walletId ${info.name}", + "Tried calling _confirmSendMweb with mweb disabled for" + " $walletId ${info.name}", ); } @@ -533,7 +572,11 @@ mixin MwebInterface final db = Drift.get(walletId); await db.transaction(() async { for (final used in usedMwebUtxos) { - await db.update(db.mwebUtxos).replace(used); + await db + .update(db.mwebUtxos) + .replace( + used.copyWith(used: true), + ); // used should already be set to true here but... } }); } @@ -578,7 +621,8 @@ mixin MwebInterface Future anonymizeAllMweb() async { if (!info.isMwebEnabled) { Logging.instance.e( - "Tried calling anonymizeAllMweb with mweb disabled for $walletId ${info.name}", + "Tried calling anonymizeAllMweb with mweb disabled for" + " $walletId ${info.name}", ); return; } @@ -909,6 +953,9 @@ mixin MwebInterface final preOutputSum = outputs.fold(BigInt.zero, (p, e) => p + e.amount.raw); final fee = sumOfUtxosValue - preOutputSum; + final feeRate = + txData.satsPerVByte ?? (txData.feeRateAmount!.toInt() / 1000).ceil(); + final client = await _client; final resp = await client.create( @@ -916,7 +963,7 @@ mixin MwebInterface rawTx: txData.raw!.toUint8ListFromHex, scanSecret: await _scanSecret, spendSecret: await _spendSecret, - feeRatePerKb: Int64(txData.feeRateAmount!.toInt()), + feeRatePerKb: Int64(feeRate * 1000), dryRun: true, ), ); @@ -948,14 +995,11 @@ mixin MwebInterface BigInt feeIncrease = posOutputSum - expectedPegin; if (expectedPegin > BigInt.zero) { - feeIncrease += - BigInt.from((txData.feeRateAmount! / BigInt.from(1000)).ceil()) * - BigInt.from(41); + feeIncrease += BigInt.from(feeRate * 41); } - // bandaid: add one to account for a rounding error that happens sometimes return Amount( - rawValue: fee + feeIncrease + BigInt.one, + rawValue: fee + feeIncrease, fractionDigits: cryptoCurrency.fractionDigits, ); } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart index 5e1560f0aa..2fa46378dc 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart @@ -39,7 +39,8 @@ import 'electrumx_interface.dart'; const kDefaultSparkIndex = 1; // TODO dart style constants. Maybe move to spark lib? -const MAX_STANDARD_TX_WEIGHT = 400000; +// https://github.com/firoorg/firo/pull/1457/files#diff-1fc0f6b5081e8ed5dfa8bf230744ad08cc6f4c1147e98552f1f424b0492fe9bdR28 +const MAX_NEW_TX_WEIGHT = 1000000; //https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/include/spark.h#L16 const SPARK_OUT_LIMIT_PER_TX = 16; @@ -498,13 +499,15 @@ mixin SparkInterface // See SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION at https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/include/spark.h#L17 // and COIN https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/bitcoin/amount.h#L17 // Note that as MAX_MONEY is greater than this limit, we can ignore it. See https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/bitcoin/amount.h#L31 + // NOTE: This was updated to 5x what is was before (previously 10k) if (transparentSumOut > Amount.fromDecimal( - Decimal.parse("10000"), + Decimal.parse("50000"), fractionDigits: cryptoCurrency.fractionDigits, )) { throw Exception( - "Spend to transparent address limit exceeded (10,000 Firo per transaction).", + "Spend to transparent address limit exceeded " + "(50,000 Firo per transaction).", ); } @@ -1734,7 +1737,7 @@ mixin SparkInterface final dummyTx = dummyTxb.build(); final nBytes = dummyTx.virtualSize(); - if (dummyTx.weight() > MAX_STANDARD_TX_WEIGHT) { + if (dummyTx.weight() > MAX_NEW_TX_WEIGHT) { throw Exception("Transaction too large"); } @@ -1991,6 +1994,9 @@ mixin SparkInterface ); if (nFeeRet.toInt() < data.vSize!) { + Logging.instance.w( + "Spark mint transaction failed: $nFeeRet is less than ${data.vSize}", + ); throw Exception("fee is less than vSize"); } diff --git a/pubspec.lock b/pubspec.lock index b98546188b..64550758b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1061,10 +1061,10 @@ packages: dependency: "direct main" description: name: flutter_mwebd - sha256: faaec843d9749c5d3cd02e9c7afbd7754449f93a4316fec43b2c26f372e0eb55 + sha256: "14f2a331b2621b78ddf62081ca8a466f6a2b4352a66950fffd68615c14e63edf" url: "https://pub.dev" source: hosted - version: "0.0.1-pre.10" + version: "0.0.1-pre.11" flutter_native_splash: dependency: "direct main" description: diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml index 0723e2d5b1..030c902c54 100644 --- a/scripts/app_config/templates/pubspec.template.yaml +++ b/scripts/app_config/templates/pubspec.template.yaml @@ -76,7 +76,7 @@ dependencies: # %%END_ENABLE_SAL%% # %%ENABLE_MWEBD%% -# flutter_mwebd: ^0.0.1-pre.10 +# flutter_mwebd: 0.0.1-pre.11 # %%END_ENABLE_MWEBD%% monero_rpc: ^2.0.0