feat: restore & scan imp

This commit is contained in:
Rafael Saes 2024-11-22 21:29:29 -03:00
parent 9bb3d9f35b
commit c35dec0d09
13 changed files with 448 additions and 172 deletions

View file

@ -108,6 +108,53 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
int initialSilentAddressIndex = 0,
required bool mempoolAPIEnabled,
}) async {
List<int>? seedBytes = null;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
if (walletInfo.isRecovery) {
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.description?.contains("SP") ?? false) {
continue;
}
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
break;
} else {
try {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.electrum] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v2 seed error: $e");
try {
seedBytes = ElectrumV1SeedGenerator(mnemonic).generate();
hdWallets[CWBitcoinDerivationType.electrum] =
Bip32Slip10Secp256k1.fromSeed(seedBytes);
} catch (e) {
print("electrum_v1 seed error: $e");
}
}
break;
}
}
if (hdWallets[CWBitcoinDerivationType.bip39] != null) {
hdWallets[CWBitcoinDerivationType.old_bip39] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
if (hdWallets[CWBitcoinDerivationType.electrum] != null) {
hdWallets[CWBitcoinDerivationType.old_electrum] =
hdWallets[CWBitcoinDerivationType.electrum]!;
}
} else {
seedBytes = walletInfo.derivationInfo?.derivationType == DerivationType.electrum
? ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase)
: Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
}
return BitcoinWallet(
mnemonic: mnemonic,
passphrase: passphrase ?? "",
@ -119,9 +166,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialSilentAddressIndex: initialSilentAddressIndex,
initialBalance: initialBalance,
encryptionFileUtils: encryptionFileUtils,
seedBytes: walletInfo.derivationInfo?.derivationType == DerivationType.electrum
? ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase)
: Bip39SeedGenerator.generateFromString(mnemonic, passphrase),
seedBytes: seedBytes,
hdWallets: hdWallets,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: addressPageType,
@ -253,9 +299,13 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
Future<bool> getNodeIsElectrs() async {
final version = await sendWorker(ElectrumWorkerGetVersionRequest()) as List<String>;
if (node?.uri.host.contains("electrs") ?? false) {
return true;
}
if (version.isNotEmpty) {
final version = await sendWorker(ElectrumWorkerGetVersionRequest());
if (version is List<String> && version.isNotEmpty) {
final server = version[0];
if (server.toLowerCase().contains('electrs')) {
@ -263,6 +313,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
node!.save();
return node!.isElectrs!;
}
} else if (version is String && version.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
node!.save();
return node!.isElectrs!;
}
node!.isElectrs = false;
@ -271,33 +325,39 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
Future<bool> getNodeSupportsSilentPayments() async {
return true;
// TODO: handle disconnection on check
// TODO: use cached values
if (node == null) {
return false;
}
final isFulcrum = node!.uri.host.contains("fulcrum");
if (isFulcrum) {
return false;
}
// As of today (august 2024), only ElectrumRS supports silent payments
// if (!(await getNodeIsElectrs())) {
// return false;
// }
if (!(await getNodeIsElectrs())) {
return false;
}
// if (node == null) {
// return false;
// }
try {
final workerResponse = (await sendWorker(ElectrumWorkerCheckTweaksRequest())) as String;
final tweaksResponse = ElectrumWorkerCheckTweaksResponse.fromJson(
json.decode(workerResponse) as Map<String, dynamic>,
);
final supportsScanning = tweaksResponse.result == true;
// try {
// final tweaksResponse = await electrumClient.getTweaks(height: 0);
if (supportsScanning) {
node!.supportsSilentPayments = true;
node!.save();
return node!.supportsSilentPayments!;
}
} catch (_) {}
// if (tweaksResponse != null) {
// node!.supportsSilentPayments = true;
// node!.save();
// return node!.supportsSilentPayments!;
// }
// } on RequestFailedTimeoutException catch (_) {
// node!.supportsSilentPayments = false;
// node!.save();
// return node!.supportsSilentPayments!;
// } catch (_) {}
// node!.supportsSilentPayments = false;
// node!.save();
// return node!.supportsSilentPayments!;
node!.supportsSilentPayments = false;
node!.save();
return node!.supportsSilentPayments!;
}
LedgerConnection? _ledgerConnection;
@ -383,16 +443,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
if (tip > walletInfo.restoreHeight) {
_setListeners(walletInfo.restoreHeight);
}
} else {
alwaysScan = false;
// _isolate?.then((value) => value.kill(priority: Isolate.immediate));
// if (rpc!.isConnected) {
// syncStatus = SyncedSyncStatus();
// } else {
// syncStatus = NotConnectedSyncStatus();
// }
} else if (syncStatus is! SyncedSyncStatus) {
await sendWorker(ElectrumWorkerStopScanningRequest());
await startSync();
}
}
@ -565,9 +618,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
messageJson = message as Map<String, dynamic>;
}
final workerMethod = messageJson['method'] as String;
final workerError = messageJson['error'] as String?;
switch (workerMethod) {
case ElectrumRequestMethods.tweaksSubscribeMethod:
if (workerError != null) {
print(messageJson);
// _onConnectionStatusChange(ConnectionStatus.failed);
break;
}
final response = ElectrumWorkerTweaksSubscribeResponse.fromJson(messageJson);
onTweaksSyncResponse(response.result);
break;
@ -651,9 +711,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
syncStatus = SyncingSyncStatus(newSyncStatus.blocksLeft, newSyncStatus.ptc);
} else {
syncStatus = newSyncStatus;
if (newSyncStatus is SyncedSyncStatus) {
silentPaymentsScanningActive = false;
}
}
await walletInfo.updateRestoreHeight(result.height!);
final height = result.height;
if (height != null) {
await walletInfo.updateRestoreHeight(height);
}
}
await save();
@ -801,6 +868,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
case SyncingSyncStatus:
return;
case SyncedTipSyncStatus:
silentPaymentsScanningActive = false;
// Message is shown on the UI for 3 seconds, then reverted to synced
Timer(Duration(seconds: 3), () {
if (this.syncStatus is SyncedTipSyncStatus) this.syncStatus = SyncedSyncStatus();

View file

@ -361,6 +361,15 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
return labels;
}
@override
@action
void updateHiddenAddresses() {
super.updateHiddenAddresses();
this.hiddenAddresses.addAll(silentPaymentAddresses
.where((addressRecord) => addressRecord.isHidden)
.map((addressRecord) => addressRecord.address));
}
Map<String, dynamic> toJson() {
final json = super.toJson();
json['silentPaymentAddresses'] =

View file

@ -125,7 +125,7 @@ abstract class ElectrumWalletBase
workerSendPort!.send(json);
try {
return completer.future.timeout(Duration(seconds: 5));
return completer.future.timeout(Duration(seconds: 30));
} catch (e) {
_errorCompleters.addAll({messageId: e});
_responseCompleters.remove(messageId);
@ -146,13 +146,8 @@ abstract class ElectrumWalletBase
final workerMethod = messageJson['method'] as String;
final workerError = messageJson['error'] as String?;
if (workerError != null) {
print('Worker error: $workerError');
return;
}
final responseId = messageJson['id'] as int?;
if (responseId != null && _responseCompleters.containsKey(responseId)) {
_responseCompleters[responseId]!.complete(message);
_responseCompleters.remove(responseId);
@ -160,6 +155,11 @@ abstract class ElectrumWalletBase
switch (workerMethod) {
case ElectrumWorkerMethods.connectionMethod:
if (workerError != null) {
_onConnectionStatusChange(ConnectionStatus.failed);
break;
}
final response = ElectrumWorkerConnectionResponse.fromJson(messageJson);
_onConnectionStatusChange(response.result);
break;
@ -214,6 +214,7 @@ abstract class ElectrumWalletBase
bool? alwaysScan;
bool mempoolAPIEnabled;
bool _updatingHistories = false;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
Bip32Slip10Secp256k1 get bip32 => walletAddresses.hdWallet;
@ -323,7 +324,8 @@ abstract class ElectrumWalletBase
List<String> scripthashesListening;
bool _chainTipListenerOn = false;
bool _isInitialSync = true;
// TODO: improve this
int _syncedTimes = 0;
void Function(FlutterErrorDetails)? _onError;
Timer? _autoSaveTimer;
@ -348,9 +350,11 @@ abstract class ElectrumWalletBase
syncStatus = SynchronizingSyncStatus();
// INFO: FIRST: Call subscribe for headers, wait for completion to update currentChainTip (needed for other methods)
// INFO: FIRST (always): Call subscribe for headers, wait for completion to update currentChainTip (needed for other methods)
await sendWorker(ElectrumWorkerHeadersSubscribeRequest());
_syncedTimes = 0;
// INFO: SECOND: Start loading transaction histories for every address, this will help discover addresses until the unused gap limit has been reached, which will help finding the full balance and unspents next
await updateTransactions();
@ -365,13 +369,14 @@ abstract class ElectrumWalletBase
_updateFeeRateTimer ??=
Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
_isInitialSync = false;
if (_syncedTimes == 3) {
syncStatus = SyncedSyncStatus();
}
await save();
} catch (e, stacktrace) {
print(stacktrace);
print("startSync $e");
print(stacktrace);
syncStatus = FailedSyncStatus();
}
}
@ -389,9 +394,11 @@ abstract class ElectrumWalletBase
}
@action
Future<void> onFeesResponse(TransactionPriorities result) async {
Future<void> onFeesResponse(TransactionPriorities? result) async {
if (result != null) {
feeRates = result;
}
}
Node? node;
@ -400,8 +407,6 @@ abstract class ElectrumWalletBase
Future<void> connectToNode({required Node node}) async {
this.node = node;
if (syncStatus is ConnectingSyncStatus) return;
try {
syncStatus = ConnectingSyncStatus();
@ -416,6 +421,7 @@ abstract class ElectrumWalletBase
_workerIsolate = await Isolate.spawn<SendPort>(ElectrumWorker.run, receivePort!.sendPort);
_workerSubscription = receivePort!.listen((message) {
print('Main: received message: $message');
if (message is SendPort) {
workerSendPort = message;
workerSendPort!.send(
@ -1159,15 +1165,11 @@ abstract class ElectrumWalletBase
@action
Future<void> updateAllUnspents() async {
final req = ElectrumWorkerListUnspentRequest(
workerSendPort!.send(
ElectrumWorkerListUnspentRequest(
scripthashes: walletAddresses.allScriptHashes.toList(),
).toJson(),
);
if (_isInitialSync) {
await sendWorker(req);
} else {
workerSendPort!.send(req.toJson());
}
}
@action
@ -1222,6 +1224,11 @@ abstract class ElectrumWalletBase
unspentCoins.forEach(updateCoin);
await refreshUnspentCoinsInfo();
_syncedTimes++;
if (_syncedTimes == 3) {
syncStatus = SyncedSyncStatus();
}
}
@action
@ -1299,10 +1306,13 @@ abstract class ElectrumWalletBase
@action
Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async {
if (histories.isEmpty) {
if (histories.isEmpty || _updatingHistories) {
_updatingHistories = false;
return;
}
_updatingHistories = true;
final addressesWithHistory = <BitcoinAddressRecord>[];
BitcoinAddressType? lastDiscoveredType;
@ -1340,7 +1350,13 @@ abstract class ElectrumWalletBase
isChange: isChange,
derivationType: addressRecord.derivationType,
addressType: addressRecord.addressType,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressRecord.addressType),
derivationInfo: BitcoinAddressUtils.getDerivationFromType(
addressRecord.addressType,
isElectrum: [
CWBitcoinDerivationType.electrum,
CWBitcoinDerivationType.old_electrum,
].contains(addressRecord.derivationType),
),
);
final newAddressList =
@ -1364,6 +1380,12 @@ abstract class ElectrumWalletBase
}
walletAddresses.updateHiddenAddresses();
_updatingHistories = false;
_syncedTimes++;
if (_syncedTimes == 3) {
syncStatus = SyncedSyncStatus();
}
}
Future<String?> canReplaceByFee(ElectrumTransactionInfo tx) async {
@ -1606,10 +1628,8 @@ abstract class ElectrumWalletBase
@action
Future<void> updateTransactions([List<BitcoinAddressRecord>? addresses]) async {
addresses ??= walletAddresses.allAddresses.toList();
final req = ElectrumWorkerGetHistoryRequest(
addresses: addresses,
workerSendPort!.send(ElectrumWorkerGetHistoryRequest(
addresses: walletAddresses.allAddresses.toList(),
storedTxs: transactionHistory.transactions.values.toList(),
walletType: type,
// If we still don't have currentChainTip, txs will still be fetched but shown
@ -1617,13 +1637,7 @@ abstract class ElectrumWalletBase
chainTip: currentChainTip ?? getBitcoinHeightByDate(date: DateTime.now()),
network: network,
mempoolAPIEnabled: mempoolAPIEnabled,
);
if (_isInitialSync) {
await sendWorker(req);
} else {
workerSendPort!.send(req.toJson());
}
).toJson());
}
@action
@ -1663,17 +1677,18 @@ abstract class ElectrumWalletBase
unconfirmed: totalUnconfirmed,
frozen: totalFrozen,
);
_syncedTimes++;
if (_syncedTimes == 3) {
syncStatus = SyncedSyncStatus();
}
}
@action
Future<void> updateBalance() async {
final req = ElectrumWorkerGetBalanceRequest(scripthashes: walletAddresses.allScriptHashes);
if (_isInitialSync) {
await sendWorker(req);
} else {
workerSendPort!.send(req.toJson());
}
workerSendPort!.send(ElectrumWorkerGetBalanceRequest(
scripthashes: walletAddresses.allScriptHashes,
).toJson());
}
@override

View file

@ -23,6 +23,8 @@ class ElectrumWorker {
final SendPort sendPort;
ElectrumApiProvider? _electrumClient;
BasedUtxoNetwork? _network;
bool _isScanning = false;
bool _stopScanRequested = false;
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
: _electrumClient = electrumClient;
@ -45,8 +47,6 @@ class ElectrumWorker {
}
void handleMessage(dynamic message) async {
print("Worker: received message: $message");
try {
Map<String, dynamic> messageJson;
if (message is String) {
@ -97,10 +97,35 @@ class ElectrumWorker {
ElectrumWorkerBroadcastRequest.fromJson(messageJson),
);
break;
case ElectrumWorkerMethods.checkTweaksMethod:
await _handleCheckTweaks(
ElectrumWorkerCheckTweaksRequest.fromJson(messageJson),
);
break;
case ElectrumWorkerMethods.stopScanningMethod:
await _handleStopScanning(
ElectrumWorkerStopScanningRequest.fromJson(messageJson),
);
break;
case ElectrumRequestMethods.estimateFeeMethod:
case ElectrumRequestMethods.tweaksSubscribeMethod:
if (_isScanning) {
_stopScanRequested = false;
}
if (!_stopScanRequested) {
await _handleScanSilentPayments(
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
);
} else {
_stopScanRequested = false;
_sendResponse(
ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(syncStatus: SyncedSyncStatus()),
),
);
}
break;
case ElectrumRequestMethods.estimateFeeMethod:
await _handleGetFeeRates(
@ -113,8 +138,7 @@ class ElectrumWorker {
);
break;
}
} catch (e, s) {
print(s);
} catch (e) {
_sendError(ElectrumWorkerErrorResponse(error: e.toString()));
}
}
@ -122,25 +146,29 @@ class ElectrumWorker {
Future<void> _handleConnect(ElectrumWorkerConnectionRequest request) async {
_network = request.network;
try {
_electrumClient = await ElectrumApiProvider.connect(
request.useSSL
? ElectrumSSLService.connect(
request.uri,
onConnectionStatusChange: (status) {
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
_sendResponse(
ElectrumWorkerConnectionResponse(status: status, id: request.id),
);
},
defaultRequestTimeOut: const Duration(seconds: 5),
connectionTimeOut: const Duration(seconds: 5),
)
: ElectrumTCPService.connect(
request.uri,
onConnectionStatusChange: (status) {
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
_sendResponse(
ElectrumWorkerConnectionResponse(status: status, id: request.id),
);
},
defaultRequestTimeOut: const Duration(seconds: 5),
connectionTimeOut: const Duration(seconds: 5),
),
);
} catch (e) {
_sendError(ElectrumWorkerConnectionError(error: e.toString()));
}
}
Future<void> _handleHeadersSubscribe(ElectrumWorkerHeadersSubscribeRequest request) async {
@ -230,6 +258,7 @@ class ElectrumWorker {
hash: txid,
currentChainTip: result.chainTip,
mempoolAPIEnabled: result.mempoolAPIEnabled,
getTime: true,
confirmations: tx?.confirmations,
date: tx?.date,
),
@ -367,6 +396,7 @@ class ElectrumWorker {
required String hash,
required int currentChainTip,
required bool mempoolAPIEnabled,
bool getTime = false,
int? confirmations,
DateTime? date,
}) async {
@ -378,6 +408,7 @@ class ElectrumWorker {
ElectrumGetTransactionHex(transactionHash: hash),
);
if (getTime) {
if (mempoolAPIEnabled) {
try {
final txVerbose = await http.get(
@ -426,6 +457,7 @@ class ElectrumWorker {
confirmations = tip - height + 1;
}
}
}
final original = BtcTransaction.fromRaw(transactionHex);
final ins = <BtcTransaction>[];
@ -498,20 +530,47 @@ class ElectrumWorker {
}
}
Future<void> _handleCheckTweaks(ElectrumWorkerCheckTweaksRequest request) async {
final response = await _electrumClient!.request(
ElectrumTweaksSubscribe(
height: 0,
count: 1,
historicalMode: false,
),
);
final supportsScanning = response != null;
_sendResponse(
ElectrumWorkerCheckTweaksResponse(result: supportsScanning, id: request.id),
);
}
Future<void> _handleStopScanning(ElectrumWorkerStopScanningRequest request) async {
_stopScanRequested = true;
_sendResponse(
ElectrumWorkerStopScanningResponse(result: true, id: request.id),
);
}
Future<void> _handleScanSilentPayments(ElectrumWorkerTweaksSubscribeRequest request) async {
_isScanning = true;
final scanData = request.scanData;
// TODO: confirmedSwitch use new connection
// final _electrumClient = await ElectrumApiProvider.connect(
// ElectrumTCPService.connect(
// Uri.parse("tcp://electrs.cakewallet.com:50001"),
// onConnectionStatusChange: (status) {
// _sendResponse(
// ElectrumWorkerConnectionResponse(status: status, id: request.id),
// );
// },
// ),
// );
int syncHeight = scanData.height;
int initialSyncHeight = syncHeight;
int getCountPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
final amountLeft = scanData.chainTip - syncHeight + 1;
return amountLeft;
}
final receivers = scanData.silentPaymentsWallets.map(
(wallet) {
return Receiver(
@ -525,7 +584,6 @@ class ElectrumWorker {
);
// Initial status UI update, send how many blocks in total to scan
final initialCount = getCountPerRequest(syncHeight);
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(
height: syncHeight,
@ -535,14 +593,19 @@ class ElectrumWorker {
final req = ElectrumTweaksSubscribe(
height: syncHeight,
count: initialCount,
count: 1,
historicalMode: false,
);
final stream = await _electrumClient!.subscribe(req);
Future<void> listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) async {
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) {
final response = req.onResponse(event);
if (_stopScanRequested || response == null) {
_stopScanRequested = false;
_isScanning = false;
return;
}
// success or error msg
final noData = response.message != null;
@ -554,13 +617,12 @@ class ElectrumWorker {
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
final nextCount = getCountPerRequest(nextHeight);
if (nextCount > 0) {
final nextStream = await _electrumClient!.subscribe(
if (nextHeight <= scanData.chainTip) {
final nextStream = _electrumClient!.subscribe(
ElectrumTweaksSubscribe(
height: syncHeight,
count: initialCount,
height: nextHeight,
count: 1,
historicalMode: false,
),
);
@ -693,6 +755,7 @@ class ElectrumWorker {
}
stream?.listen((event) => listenFn(event, req));
_isScanning = false;
}
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {

View file

@ -5,10 +5,14 @@ class ElectrumWorkerMethods {
static const String connectionMethod = "connection";
static const String unknownMethod = "unknown";
static const String txHashMethod = "txHash";
static const String checkTweaksMethod = "checkTweaks";
static const String stopScanningMethod = "stopScanning";
static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod);
static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod);
static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod);
static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod);
static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod);
@override
String toString() {

View file

@ -0,0 +1,49 @@
part of 'methods.dart';
class ElectrumWorkerCheckTweaksRequest implements ElectrumWorkerRequest {
ElectrumWorkerCheckTweaksRequest({this.id});
final int? id;
@override
final String method = ElectrumWorkerMethods.checkTweaks.method;
@override
factory ElectrumWorkerCheckTweaksRequest.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerCheckTweaksRequest(id: json['id'] as int?);
}
@override
Map<String, dynamic> toJson() {
return {'method': method, 'id': id};
}
}
class ElectrumWorkerCheckTweaksError extends ElectrumWorkerErrorResponse {
ElectrumWorkerCheckTweaksError({required super.error, super.id}) : super();
@override
final String method = ElectrumWorkerMethods.checkTweaks.method;
}
class ElectrumWorkerCheckTweaksResponse extends ElectrumWorkerResponse<bool, String> {
ElectrumWorkerCheckTweaksResponse({
required super.result,
super.error,
super.id,
}) : super(method: ElectrumWorkerMethods.checkTweaks.method);
@override
String resultJson(result) {
return result.toString();
}
@override
factory ElectrumWorkerCheckTweaksResponse.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerCheckTweaksResponse(
result: json['result'] == "true",
error: json['error'] as String?,
id: json['id'] as int?,
);
}
}

View file

@ -37,7 +37,7 @@ class ElectrumWorkerGetFeesError extends ElectrumWorkerErrorResponse {
}
class ElectrumWorkerGetFeesResponse
extends ElectrumWorkerResponse<TransactionPriorities, Map<String, int>> {
extends ElectrumWorkerResponse<TransactionPriorities?, Map<String, int>> {
ElectrumWorkerGetFeesResponse({
required super.result,
super.error,
@ -46,13 +46,15 @@ class ElectrumWorkerGetFeesResponse
@override
Map<String, int> resultJson(result) {
return result.toJson();
return result?.toJson() ?? {};
}
@override
factory ElectrumWorkerGetFeesResponse.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerGetFeesResponse(
result: deserializeTransactionPriorities(json['result'] as Map<String, dynamic>),
result: json['result'] == null
? null
: deserializeTransactionPriorities(json['result'] as Map<String, dynamic>),
error: json['error'] as String?,
id: json['id'] as int?,
);

View file

@ -20,3 +20,5 @@ part 'list_unspent.dart';
part 'tweaks_subscribe.dart';
part 'get_fees.dart';
part 'version.dart';
part 'check_tweaks_method.dart';
part 'stop_scanning.dart';

View file

@ -0,0 +1,49 @@
part of 'methods.dart';
class ElectrumWorkerStopScanningRequest implements ElectrumWorkerRequest {
ElectrumWorkerStopScanningRequest({this.id});
final int? id;
@override
final String method = ElectrumWorkerMethods.stopScanning.method;
@override
factory ElectrumWorkerStopScanningRequest.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerStopScanningRequest(id: json['id'] as int?);
}
@override
Map<String, dynamic> toJson() {
return {'method': method, 'id': id};
}
}
class ElectrumWorkerStopScanningError extends ElectrumWorkerErrorResponse {
ElectrumWorkerStopScanningError({required super.error, super.id}) : super();
@override
final String method = ElectrumWorkerMethods.stopScanning.method;
}
class ElectrumWorkerStopScanningResponse extends ElectrumWorkerResponse<bool, String> {
ElectrumWorkerStopScanningResponse({
required super.result,
super.error,
super.id,
}) : super(method: ElectrumWorkerMethods.stopScanning.method);
@override
String resultJson(result) {
return result.toString();
}
@override
factory ElectrumWorkerStopScanningResponse.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerStopScanningResponse(
result: json['result'] as bool,
error: json['error'] as String?,
id: json['id'] as int?,
);
}
}

View file

@ -245,6 +245,9 @@ Future<int> getHavenCurrentHeight() async {
// Data taken from https://timechaincalendar.com/
const bitcoinDates = {
"2024-11": 868345,
"2024-10": 863584,
"2024-09": 859317,
"2024-08": 854889,
"2024-07": 850182,
"2024-06": 846005,

View file

@ -422,7 +422,9 @@ class CWBitcoin extends Bitcoin {
var bip39SeedBytes;
try {
bip39SeedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
} catch (_) {}
} catch (e) {
print("bip39 seed error: $e");
}
if (bip39SeedBytes != null) {
for (final addressType in BITCOIN_ADDRESS_TYPES) {

View file

@ -161,7 +161,10 @@ class AddressCell extends StatelessWidget {
if (derivationPath.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: AutoSizeText(
derivationPath,
maxLines: 1,
@ -172,6 +175,8 @@ class AddressCell extends StatelessWidget {
),
),
),
],
),
),
if (hasBalance || hasReceived)
Padding(

View file

@ -167,6 +167,10 @@ class _AddressListState extends State<AddressList> {
: backgroundColor,
textColor: textColor,
onTap: (_) {
if (item.isChange || item.isHidden) {
return;
}
if (widget.onSelect != null) {
widget.onSelect!(item.address);
return;