From 4ea84da8951c91bee2b77401a2db7118c3b750d9 Mon Sep 17 00:00:00 2001 From: ruben beck Date: Fri, 20 Oct 2023 09:17:01 +0200 Subject: [PATCH] get mempool instance from NetworkSettingsBloc --- lib/bloc/network/network_settings_bloc.dart | 34 +++++--- .../closed_channel_payment_details.dart | 87 ++++++++++--------- .../subswap/swap/widgets/inprogress_swap.dart | 11 +-- .../in_progress/reverse_swap_in_progress.dart | 10 +-- lib/utils/mempool_helper.dart | 17 ---- .../network/network_settings_bloc_test.dart | 32 +++++-- 6 files changed, 106 insertions(+), 85 deletions(-) delete mode 100644 lib/utils/mempool_helper.dart diff --git a/lib/bloc/network/network_settings_bloc.dart b/lib/bloc/network/network_settings_bloc.dart index 599992dd6..16935e215 100644 --- a/lib/bloc/network/network_settings_bloc.dart +++ b/lib/bloc/network/network_settings_bloc.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; import 'package:c_breez/bloc/network/network_settings_state.dart'; import 'package:c_breez/config.dart' as lib; +import 'package:c_breez/config.dart'; +import 'package:c_breez/services/injector.dart'; import 'package:c_breez/utils/preferences.dart'; import 'package:logging/logging.dart'; import 'package:http/http.dart' as http; @@ -47,7 +50,7 @@ class NetworkSettingsBloc extends Cubit with HydratedMixin _log.warning("Invalid mempool url: $mempoolUrl"); return false; } - if (!await _testUri(uri) && !await _testUriSupportsMempoolApi(uri)) { + if (!await _testUriSupportsMempoolApi(uri)) { _log.warning("Mempool url is not reachable: $mempoolUrl"); return false; } @@ -83,24 +86,35 @@ class NetworkSettingsBloc extends Cubit with HydratedMixin )); } - Future _testUri(Uri uri) async { - try { - final response = await _httpClient.get(uri); - return response.statusCode < 400; - } catch (e) { - _log.warning("Failed to test mempool url: $uri", e); - return false; + String formatTransactionUrl({required String txid, required String mempoolInstance}) { + return "$mempoolInstance/tx/$txid"; + } + + String formatRecomendedFeesUrl({required String mempoolInstance}) { + return "$mempoolInstance/api/v1/fees/recommended"; + } + + Future get mempoolInstance async { + String? mempoolInstance = await ServiceInjector().preferences.getMempoolSpaceUrl(); + if (mempoolInstance == null) { + final config = await Config.instance(); + mempoolInstance = config.defaultMempoolUrl; } + return mempoolInstance; } Future _testUriSupportsMempoolApi(Uri uri) async { // We need to make sure that the mempool rest api is supported // as the sdk depends on it. - uri = Uri.parse("${uri.toString()}/api/v1/fees/recommended"); + uri = Uri.parse(formatRecomendedFeesUrl(mempoolInstance: uri.toString())); try { final response = await _httpClient.get(uri); - return response.statusCode == 200; + if (response.statusCode != 200) { + return false; + } + final Map body = jsonDecode(response.body); + return body.containsKey("fastestFee"); } catch (e) { _log.warning("Failed to test mempool url: $uri", e); return false; diff --git a/lib/routes/home/widgets/payments_list/dialog/closed_channel_payment_details.dart b/lib/routes/home/widgets/payments_list/dialog/closed_channel_payment_details.dart index 93fdaf074..abd3f419b 100644 --- a/lib/routes/home/widgets/payments_list/dialog/closed_channel_payment_details.dart +++ b/lib/routes/home/widgets/payments_list/dialog/closed_channel_payment_details.dart @@ -1,16 +1,16 @@ import 'package:breez_sdk/bridge_generated.dart' as sdk; import 'package:breez_translations/breez_translations_locales.dart'; +import 'package:c_breez/bloc/network/network_settings_bloc.dart'; import 'package:c_breez/models/payment_minutiae.dart'; import 'package:c_breez/routes/home/widgets/payments_list/dialog/tx_widget.dart'; -import 'package:c_breez/utils/mempool_helper.dart'; import 'package:c_breez/widgets/loader.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class ClosedChannelPaymentDetailsWidget extends StatelessWidget { final PaymentMinutiae paymentMinutiae; - final mempoolHelper = MempoolHelper(); - ClosedChannelPaymentDetailsWidget({ + const ClosedChannelPaymentDetailsWidget({ Key? key, required this.paymentMinutiae, }) : super(key: key); @@ -19,65 +19,66 @@ class ClosedChannelPaymentDetailsWidget extends StatelessWidget { Widget build(BuildContext context) { final themeData = Theme.of(context); final texts = context.texts(); + final networkSettingsBloc = context.read(); return FutureBuilder( - future: mempoolHelper.mempoolInstance, + future: networkSettingsBloc.mempoolInstance, builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - final mempoolInstance = snapshot.data!; - if (paymentMinutiae.status == sdk.PaymentStatus.Complete) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - RichText( - text: TextSpan( - style: themeData.dialogTheme.contentTextStyle, - text: texts.payment_details_dialog_closed_channel_local_wallet, - ), - ), - if (paymentMinutiae.paymentType == sdk.PaymentType.ClosedChannel && - paymentMinutiae.closingTxid != null) ...[ - TxWidget( - txURL: mempoolHelper.formatTransactionUrl( - txid: paymentMinutiae.closingTxid!, mempoolInstance: mempoolInstance), - txID: paymentMinutiae.closingTxid!, - ), - ], - ], - ); - } - // TODO pendingExpirationHeight - // TODO hoursToExpire - String estimation = texts.payment_details_dialog_closed_channel_transfer_no_estimation; + if (snapshot.connectionState != ConnectionState.done) { + return const Loader(); + } + final mempoolInstance = snapshot.data!; + if (paymentMinutiae.status == sdk.PaymentStatus.Complete) { return Column( mainAxisSize: MainAxisSize.min, children: [ RichText( text: TextSpan( style: themeData.dialogTheme.contentTextStyle, - text: estimation, + text: texts.payment_details_dialog_closed_channel_local_wallet, ), ), - if (paymentMinutiae.fundingTxid != null) ...[ - TxWidget( - txURL: mempoolHelper.formatTransactionUrl( - txid: paymentMinutiae.fundingTxid!, mempoolInstance: mempoolInstance), - txID: paymentMinutiae.fundingTxid!, - ), - ], - if (paymentMinutiae.closingTxid != null) ...[ + if (paymentMinutiae.paymentType == sdk.PaymentType.ClosedChannel && + paymentMinutiae.closingTxid != null) ...[ TxWidget( - txURL: mempoolHelper.formatTransactionUrl( + txURL: networkSettingsBloc.formatTransactionUrl( txid: paymentMinutiae.closingTxid!, mempoolInstance: mempoolInstance), txID: paymentMinutiae.closingTxid!, ), - ] + ], ], ); - } else { - return const Loader(); } + // TODO pendingExpirationHeight + // TODO hoursToExpire + String estimation = texts.payment_details_dialog_closed_channel_transfer_no_estimation; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + RichText( + text: TextSpan( + style: themeData.dialogTheme.contentTextStyle, + text: estimation, + ), + ), + if (paymentMinutiae.fundingTxid != null) ...[ + TxWidget( + txURL: networkSettingsBloc.formatTransactionUrl( + txid: paymentMinutiae.fundingTxid!, mempoolInstance: mempoolInstance), + txID: paymentMinutiae.fundingTxid!, + ), + ], + if (paymentMinutiae.closingTxid != null) ...[ + TxWidget( + txURL: networkSettingsBloc.formatTransactionUrl( + txid: paymentMinutiae.closingTxid!, mempoolInstance: mempoolInstance), + txID: paymentMinutiae.closingTxid!, + ), + ] + ], + ); }, ); } diff --git a/lib/routes/subswap/swap/widgets/inprogress_swap.dart b/lib/routes/subswap/swap/widgets/inprogress_swap.dart index 52c9e20ff..dfff65e62 100644 --- a/lib/routes/subswap/swap/widgets/inprogress_swap.dart +++ b/lib/routes/subswap/swap/widgets/inprogress_swap.dart @@ -1,11 +1,12 @@ import 'package:breez_sdk/bridge_generated.dart'; import 'package:breez_translations/breez_translations_locales.dart'; +import 'package:c_breez/bloc/network/network_settings_bloc.dart'; import 'package:c_breez/services/injector.dart'; -import 'package:c_breez/utils/mempool_helper.dart'; import 'package:c_breez/widgets/flushbar.dart'; import 'package:c_breez/widgets/link_launcher.dart'; import 'package:c_breez/widgets/loader.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class SwapInprogress extends StatelessWidget { final SwapInfo swap; @@ -44,16 +45,16 @@ class SwapInprogress extends StatelessWidget { class _TxLink extends StatelessWidget { final String txid; - final mempoolExplorer = MempoolHelper(); - _TxLink({required this.txid}); + const _TxLink({required this.txid}); @override Widget build(BuildContext context) { final text = context.texts(); + final networkSettingsBloc = context.read(); return FutureBuilder( - future: mempoolExplorer.mempoolInstance, + future: networkSettingsBloc.mempoolInstance, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Loader(); @@ -62,7 +63,7 @@ class _TxLink extends StatelessWidget { return LinkLauncher( linkName: txid, - linkAddress: mempoolExplorer.formatTransactionUrl(txid: txid, mempoolInstance: mempoolInstance), + linkAddress: networkSettingsBloc.formatTransactionUrl(txid: txid, mempoolInstance: mempoolInstance), onCopy: () { ServiceInjector().device.setClipboardText(txid); showFlushbar( diff --git a/lib/routes/withdraw/reverse_swap/in_progress/reverse_swap_in_progress.dart b/lib/routes/withdraw/reverse_swap/in_progress/reverse_swap_in_progress.dart index d2bb17893..4e4f7fa3b 100644 --- a/lib/routes/withdraw/reverse_swap/in_progress/reverse_swap_in_progress.dart +++ b/lib/routes/withdraw/reverse_swap/in_progress/reverse_swap_in_progress.dart @@ -1,7 +1,7 @@ import 'package:breez_sdk/bridge_generated.dart' as sdk; import 'package:breez_translations/breez_translations_locales.dart'; +import 'package:c_breez/bloc/network/network_settings_bloc.dart'; import 'package:c_breez/services/injector.dart'; -import 'package:c_breez/utils/mempool_helper.dart'; import 'package:c_breez/widgets/flushbar.dart'; import 'package:c_breez/widgets/link_launcher.dart'; import 'package:c_breez/widgets/loader.dart'; @@ -40,23 +40,23 @@ class ReverseSwapInprogress extends StatelessWidget { class _TxLink extends StatelessWidget { final String txid; - final mempoolHelper = MempoolHelper(); - _TxLink({required this.txid}); + const _TxLink({required this.txid}); @override Widget build(BuildContext context) { final text = context.texts(); + final networkSettingsBloc = context.read(); return FutureBuilder( - future: mempoolHelper.mempoolInstance, + future: networkSettingsBloc.mempoolInstance, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Loader(); } final transactionUrl = - mempoolHelper.formatTransactionUrl(mempoolInstance: snapshot.data!, txid: txid); + networkSettingsBloc.formatTransactionUrl(mempoolInstance: snapshot.data!, txid: txid); return LinkLauncher( linkName: txid, diff --git a/lib/utils/mempool_helper.dart b/lib/utils/mempool_helper.dart deleted file mode 100644 index 1dcb1f375..000000000 --- a/lib/utils/mempool_helper.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:c_breez/config.dart'; -import 'package:c_breez/services/injector.dart'; - -class MempoolHelper { - String formatTransactionUrl({required String txid, required String mempoolInstance}) { - return "$mempoolInstance/tx/$txid"; - } - - Future get mempoolInstance async { - String? mempoolInstance = await ServiceInjector().preferences.getMempoolSpaceUrl(); - if (mempoolInstance == null) { - final config = await Config.instance(); - mempoolInstance = config.defaultMempoolUrl; - } - return mempoolInstance; - } -} diff --git a/test/bloc/network/network_settings_bloc_test.dart b/test/bloc/network/network_settings_bloc_test.dart index 022744ab9..1a1a94f56 100644 --- a/test/bloc/network/network_settings_bloc_test.dart +++ b/test/bloc/network/network_settings_bloc_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:c_breez/bloc/network/network_settings_bloc.dart'; import 'package:c_breez/bloc/network/network_settings_state.dart'; import 'package:c_breez/services/injector.dart'; @@ -12,6 +14,17 @@ import '../../unit_logger.dart'; import '../../utils/fake_path_provider_platform.dart'; import '../../utils/hydrated_bloc_storage.dart'; +String get _recomendedMockFeesResponse { + final Map recomendedFees = { + "fastestFee": 1, + "halfHourFee": 1, + "hourFee": 1, + "economyFee": 1, + "minimumFee": 1 + }; + return jsonEncode(recomendedFees); +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final platform = FakePathProviderPlatform(); @@ -75,8 +88,12 @@ void main() { test('set mempool space url with a valid url should set on the preferences', () async { const url = "https://mempool.space"; - httpClient.getAnswer["$url/api/v1/fees/recommended"] = http.Response("{}", 200); + + httpClient.getAnswer[url] = http.Response("{}", 200); final bloc = make(); + + httpClient.getAnswer[bloc.formatRecomendedFeesUrl(mempoolInstance: url)] = + http.Response(_recomendedMockFeesResponse, 200); final result = await bloc.setMempoolUrl(url); expect(result, true); expect(injector.preferencesMock.setMempoolSpaceUrlUrl, url); @@ -84,8 +101,10 @@ void main() { test('set mempool space url with a valid url missing scheme should set on the preferences', () async { const url = "mempool.space"; - httpClient.getAnswer["https://$url/api/v1/fees/recommended"] = http.Response("{}", 200); + final bloc = make(); + httpClient.getAnswer["https://${bloc.formatRecomendedFeesUrl(mempoolInstance: url)}"] = + http.Response(_recomendedMockFeesResponse, 200); final result = await bloc.setMempoolUrl(url); expect(result, true); expect(injector.preferencesMock.setMempoolSpaceUrlUrl, "https://$url"); @@ -101,8 +120,9 @@ void main() { test('set mempool space url with an ip should should set on the preferences', () async { const url = "https://192.168.15.2"; - httpClient.getAnswer["$url/api/v1/fees/recommended"] = http.Response("{}", 200); final bloc = make(); + httpClient.getAnswer[bloc.formatRecomendedFeesUrl(mempoolInstance: url)] = + http.Response(_recomendedMockFeesResponse, 200); final result = await bloc.setMempoolUrl(url); expect(result, true); expect(injector.preferencesMock.setMempoolSpaceUrlUrl, url); @@ -110,8 +130,9 @@ void main() { test('set mempool space url with an ip and port should should set on the preferences', () async { const url = "https://192.168.15.2:3006"; - httpClient.getAnswer["$url/api/v1/fees/recommended"] = http.Response("{}", 200); final bloc = make(); + httpClient.getAnswer[bloc.formatRecomendedFeesUrl(mempoolInstance: url)] = + http.Response(_recomendedMockFeesResponse, 200); final result = await bloc.setMempoolUrl(url); expect(result, true); expect(injector.preferencesMock.setMempoolSpaceUrlUrl, url); @@ -119,8 +140,9 @@ void main() { test('set mempool space url with an ip missing scheme should should set on the preferences', () async { const url = "192.168.15.2"; - httpClient.getAnswer["https://$url/api/v1/fees/recommended"] = http.Response("{}", 200); final bloc = make(); + httpClient.getAnswer["https://${bloc.formatRecomendedFeesUrl(mempoolInstance: url)}"] = + http.Response(_recomendedMockFeesResponse, 200); final result = await bloc.setMempoolUrl(url); expect(result, true); expect(injector.preferencesMock.setMempoolSpaceUrlUrl, "https://$url");