import 'dart:typed_data'; import 'package:grpc/grpc.dart'; import 'package:path_provider/path_provider.dart'; import 'cw_mweb_platform_interface.dart'; import 'mwebd.pbgrpc.dart'; class CwMweb { static RpcClient? _rpcClient; static ClientChannel? _clientChannel; static int? _port; static const TIMEOUT_DURATION = Duration(seconds: 5); static Future _initializeClient() async { await stop(); // wait a few seconds to make sure the server is stopped await Future.delayed(const Duration(seconds: 5)); final appDir = await getApplicationSupportDirectory(); const ltcNodeUri = "45.79.13.180:9333"; _port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri); if (_port == null || _port == 0) { throw Exception("Failed to start server"); } print("Attempting to connect to server on port: $_port"); // wait for the server to finish starting up before we try to connect to it: await Future.delayed(const Duration(seconds: 5)); _clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () { print("Channel is shutting down!"); }, options: const ChannelOptions( credentials: ChannelCredentials.insecure(), keepAlive: ClientKeepAliveOptions(permitWithoutCalls: true), )); _rpcClient = RpcClient(_clientChannel!); } static Future stub({int maxRetries = 3}) async { for (int i = 0; i < maxRetries; i++) { try { if (_rpcClient == null) { await _initializeClient(); } final status = await _rpcClient! .status(StatusRequest(), options: CallOptions(timeout: TIMEOUT_DURATION)); if (status.blockTime == 0) { throw Exception("blockTime shouldn't be 0! (this connection is likely broken)"); } return _rpcClient!; } catch (e) { print("Attempt $i failed: $e"); _rpcClient = null; } } throw Exception("Failed to connect after $maxRetries attempts"); } static Future stop() async { try { await CwMwebPlatform.instance.stop(); await cleanup(); } catch (e) { print("Error stopping server: $e"); } } static Future address(Uint8List scanSecret, Uint8List spendPub, int index) async { try { return CwMwebPlatform.instance.address(scanSecret, spendPub, index); } catch (e) { print("Error getting address: $e"); return null; } } static Future cleanup() async { await _clientChannel?.terminate(); _rpcClient = null; _clientChannel = null; _port = null; } // wrappers that handle the connection issues: static Future spent(SpentRequest request) async { try { if (_rpcClient == null) { await _initializeClient(); } return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION)); } catch (e) { print("Error getting spent: $e"); return SpentResponse(); } } static Future status(StatusRequest request) async { try { if (_rpcClient == null) { await _initializeClient(); } return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION)); } catch (e) { print("Error getting status: $e"); return StatusResponse(); } } static Future create(CreateRequest request) async { try { if (_rpcClient == null) { await _initializeClient(); } return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION)); } catch (e) { print("Error getting create: $e"); return CreateResponse(); } } static Future?> utxos(UtxosRequest request) async { try { if (_rpcClient == null) { await _initializeClient(); } // this is a stream, so we should have an effectively infinite timeout: return _rpcClient!.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365))); } catch (e) { print("Error getting utxos: $e"); return null; } } }