V4.20.0 rc2 (#1727)

* version 4.20.0

* update build numbers

* UI updates and script fix for ios bundle identifier

* disable mweb for desktop

* change hardcoded ltc server ip address
electrum connection enhancement

* MWEB enhancements 2.0 (#1735)

* additional logging and minor fixes

* additional logging and minor fixes

* addresses pt.1

* Allow Wallet Group Names to be the same as Wallet Names (#1730)

* fix: Issues with imaging

* fix: Allow group names to be the same as wallet names

* fix: Bug with wallet grouping when a wallet is minimized

* fix: Bug with wallet grouping when a wallet is minimized

* logs of fixes and experimental changes, close wallet before opening next

* save

* fix icon

* fixes

* [skip ci] updates

* [skip ci] updates

* updates

* minor optimizations

* fix for when switching between wallets

* [skip ci] updates

* [skip ci] updates

* Update cw_bitcoin/lib/litecoin_wallet.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update cw_bitcoin/lib/litecoin_wallet.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* mobx

* mostly logging

* stream fix pt.1 [skip ci]

* updates

* some fixes and enhancements

* [skip ci] minor

* potential partial fix for streamsink closed

* fix stream sink closed errors

* fix mweb logo colors

* save

* minor enhancements [skip ci]

* save

* experimental

* minor

* minor [skip ci]

---------

Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* fix menu list removing from original list

---------

Co-authored-by: Matthew Fosse <matt@fosse.co>
Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com>
This commit is contained in:
Omar Hatem 2024-10-13 02:13:52 +03:00 committed by GitHub
parent 8acf8bdfb2
commit 380f7653b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 572 additions and 329 deletions

View file

@ -40,10 +40,17 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
port = null
result.success(null)
} else if (call.method == "address") {
// val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
// val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
// val index: Int = call.argument<Int>("index") ?: 0
// val res = Mwebd.address(scanSecret, spendPub, index)
// result.success(res)
} else if (call.method == "addresses") {
val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
val index: Int = call.argument<Int>("index") ?: 0
val res = Mwebd.address(scanSecret, spendPub, index)
val fromIndex: Int = call.argument<Int>("fromIndex") ?: 0
val toIndex: Int = call.argument<Int>("toIndex") ?: 0
val res = Mwebd.addresses(scanSecret, spendPub, fromIndex, toIndex)
result.success(res)
} else {
result.notImplemented()

View file

@ -32,15 +32,26 @@ public static func register(with registrar: FlutterPluginRegistrar) {
stopServer()
result(nil)
break
case "address":
// case "address":
// let args = call.arguments as! [String: Any]
// let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
// let spendPub = args["spendPub"] as! FlutterStandardTypedData
// let index = args["index"] as! Int32
// let scanSecretData = scanSecret.data
// let spendPubData = spendPub.data
// result(MwebdAddress(scanSecretData, spendPubData, index))
// break
case "addresses":
let args = call.arguments as! [String: Any]
let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
let spendPub = args["spendPub"] as! FlutterStandardTypedData
let index = args["index"] as! Int32
let fromIndex = args["fromIndex"] as! Int32
let toIndex = args["toIndex"] as! Int32
let scanSecretData = scanSecret.data
let spendPubData = spendPub.data
result(MwebdAddress(scanSecretData, spendPubData, index))
result(MwebdAddresses(scanSecretData, spendPubData, fromIndex, toIndex))
break
default:
result(FlutterMethodNotImplemented)

View file

@ -1,3 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:typed_data';
import 'package:grpc/grpc.dart';
@ -10,25 +14,51 @@ class CwMweb {
static ClientChannel? _clientChannel;
static int? _port;
static const TIMEOUT_DURATION = Duration(seconds: 5);
static Timer? logTimer;
static void readFileWithTimer(String filePath) {
final file = File(filePath);
int lastLength = 0;
logTimer?.cancel();
logTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
final currentLength = await file.length();
if (currentLength != lastLength) {
final fileStream = file.openRead(lastLength, currentLength);
final newLines = await fileStream.transform(utf8.decoder).join();
lastLength = currentLength;
log(newLines);
}
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
log('The mwebd debug log probably is not initialized yet.');
}
});
}
static Future<void> _initializeClient() async {
await stop();
// wait a few seconds to make sure the server is stopped
await Future.delayed(const Duration(seconds: 5));
print("initialize client called!");
final appDir = await getApplicationSupportDirectory();
const ltcNodeUri = "45.79.13.180:9333";
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
String debugLogPath = "${appDir.path}/logs/debug.log";
readFileWithTimer(debugLogPath);
_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");
log("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!");
_rpcClient = null;
log("Channel is shutting down!");
},
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
@ -49,9 +79,15 @@ class CwMweb {
throw Exception("blockTime shouldn't be 0! (this connection is likely broken)");
}
return _rpcClient!;
} catch (e) {
print("Attempt $i failed: $e");
} on GrpcError catch (e) {
log("Attempt $i failed: $e");
log('Caught grpc error: ${e.message}');
_rpcClient = null;
await Future.delayed(const Duration(seconds: 3));
} catch (e) {
log("Attempt $i failed: $e");
_rpcClient = null;
await Future.delayed(const Duration(seconds: 3));
}
}
throw Exception("Failed to connect after $maxRetries attempts");
@ -61,22 +97,43 @@ class CwMweb {
try {
await CwMwebPlatform.instance.stop();
await cleanup();
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error stopping server: $e");
log("Error stopping server: $e");
}
}
static Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
try {
return CwMwebPlatform.instance.address(scanSecret, spendPub, index);
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, index, index + 1))
?.split(',')
.first;
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting address: $e");
return null;
log("Error getting address: $e");
}
return null;
}
static Future<List<String>?> addresses(
Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
try {
return (await CwMwebPlatform.instance.addresses(scanSecret, spendPub, fromIndex, toIndex))
?.split(',');
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
log("Error getting addresses: $e");
}
return null;
}
static Future<void> cleanup() async {
await _clientChannel?.terminate();
try {
await _clientChannel?.terminate();
} catch (_) {}
_rpcClient = null;
_clientChannel = null;
_port = null;
@ -84,51 +141,57 @@ class CwMweb {
// wrappers that handle the connection issues:
static Future<SpentResponse> spent(SpentRequest request) async {
log("mweb.spent() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting spent: $e");
return SpentResponse();
log("Error getting spent: $e");
}
return SpentResponse();
}
static Future<StatusResponse> status(StatusRequest request) async {
log("mweb.status() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting status: $e");
return StatusResponse();
log("Error getting status: $e");
}
return StatusResponse();
}
static Future<CreateResponse> create(CreateRequest request) async {
log("mweb.create() called");
try {
if (_rpcClient == null) {
await _initializeClient();
}
_rpcClient = await stub();
return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting create: $e");
return CreateResponse();
log("Error getting create: $e");
}
return CreateResponse();
}
static Future<ResponseStream<Utxo>?> utxos(UtxosRequest request) async {
log("mweb.utxos() called");
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)));
_rpcClient = await stub();
final resp = _rpcClient!
.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
log("got utxo stream");
return resp;
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
} catch (e) {
print("Error getting utxos: $e");
return null;
log("Error getting utxos: $e");
}
return null;
}
}

View file

@ -1,3 +1,5 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@ -11,6 +13,9 @@ class MethodChannelCwMweb extends CwMwebPlatform {
@override
Future<int?> start(String dataDir, String nodeUri) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result =
await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir, 'nodeUri': nodeUri});
return result;
@ -18,11 +23,17 @@ class MethodChannelCwMweb extends CwMwebPlatform {
@override
Future<void> stop() async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return;
}
await methodChannel.invokeMethod<void>('stop');
}
@override
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result = await methodChannel.invokeMethod<String>('address', {
'scanSecret': scanSecret,
'spendPub': spendPub,
@ -30,4 +41,18 @@ class MethodChannelCwMweb extends CwMwebPlatform {
});
return result;
}
@override
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) async {
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return null;
}
final result = await methodChannel.invokeMethod<String>('addresses', {
'scanSecret': scanSecret,
'spendPub': spendPub,
'fromIndex': fromIndex,
'toIndex': toIndex,
});
return result;
}
}

View file

@ -36,4 +36,8 @@ abstract class CwMwebPlatform extends PlatformInterface {
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) {
throw UnimplementedError('address(int) has not been implemented.');
}
Future<String?> addresses(Uint8List scanSecret, Uint8List spendPub, int fromIndex, int toIndex) {
throw UnimplementedError('addresses has not been implemented.');
}
}