mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 03:59:23 +00:00
feat: restore & scan imp
This commit is contained in:
parent
9bb3d9f35b
commit
c35dec0d09
13 changed files with 448 additions and 172 deletions
|
@ -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();
|
||||
|
|
|
@ -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'] =
|
||||
|
|
|
@ -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;
|
||||
syncStatus = SyncedSyncStatus();
|
||||
if (_syncedTimes == 3) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
|
||||
await save();
|
||||
} catch (e, stacktrace) {
|
||||
print(stacktrace);
|
||||
print("startSync $e");
|
||||
print(stacktrace);
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
@ -389,8 +394,10 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@action
|
||||
Future<void> onFeesResponse(TransactionPriorities result) async {
|
||||
feeRates = result;
|
||||
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(
|
||||
scripthashes: walletAddresses.allScriptHashes.toList(),
|
||||
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
|
||||
|
|
|
@ -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 ElectrumRequestMethods.tweaksSubscribeMethod:
|
||||
await _handleScanSilentPayments(
|
||||
ElectrumWorkerTweaksSubscribeRequest.fromJson(messageJson),
|
||||
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;
|
||||
|
||||
_electrumClient = await ElectrumApiProvider.connect(
|
||||
request.useSSL
|
||||
? ElectrumSSLService.connect(
|
||||
request.uri,
|
||||
onConnectionStatusChange: (status) {
|
||||
_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));
|
||||
},
|
||||
defaultRequestTimeOut: const Duration(seconds: 5),
|
||||
connectionTimeOut: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
try {
|
||||
_electrumClient = await ElectrumApiProvider.connect(
|
||||
request.useSSL
|
||||
? ElectrumSSLService.connect(
|
||||
request.uri,
|
||||
onConnectionStatusChange: (status) {
|
||||
_sendResponse(
|
||||
ElectrumWorkerConnectionResponse(status: status, id: request.id),
|
||||
);
|
||||
},
|
||||
)
|
||||
: ElectrumTCPService.connect(
|
||||
request.uri,
|
||||
onConnectionStatusChange: (status) {
|
||||
_sendResponse(
|
||||
ElectrumWorkerConnectionResponse(status: status, id: request.id),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} 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,52 +408,54 @@ class ElectrumWorker {
|
|||
ElectrumGetTransactionHex(transactionHash: hash),
|
||||
);
|
||||
|
||||
if (mempoolAPIEnabled) {
|
||||
try {
|
||||
final txVerbose = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
|
||||
),
|
||||
);
|
||||
|
||||
if (txVerbose.statusCode == 200 &&
|
||||
txVerbose.body.isNotEmpty &&
|
||||
jsonDecode(txVerbose.body) != null) {
|
||||
height = jsonDecode(txVerbose.body)['block_height'] as int;
|
||||
|
||||
final blockHash = await http.get(
|
||||
if (getTime) {
|
||||
if (mempoolAPIEnabled) {
|
||||
try {
|
||||
final txVerbose = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
|
||||
),
|
||||
);
|
||||
|
||||
if (blockHash.statusCode == 200 && blockHash.body.isNotEmpty) {
|
||||
final blockResponse = await http.get(
|
||||
if (txVerbose.statusCode == 200 &&
|
||||
txVerbose.body.isNotEmpty &&
|
||||
jsonDecode(txVerbose.body) != null) {
|
||||
height = jsonDecode(txVerbose.body)['block_height'] as int;
|
||||
|
||||
final blockHash = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||
),
|
||||
);
|
||||
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||
if (blockHash.statusCode == 200 && blockHash.body.isNotEmpty) {
|
||||
final blockResponse = await http.get(
|
||||
Uri.parse(
|
||||
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||
),
|
||||
);
|
||||
|
||||
if (date != null) {
|
||||
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||
isDateValidated = newDate == date;
|
||||
if (blockResponse.statusCode == 200 &&
|
||||
blockResponse.body.isNotEmpty &&
|
||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||
|
||||
if (date != null) {
|
||||
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
|
||||
isDateValidated = newDate == date;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (confirmations == null && height != null) {
|
||||
final tip = currentChainTip;
|
||||
if (tip > 0 && height > 0) {
|
||||
// Add one because the block itself is the first confirmation
|
||||
confirmations = tip - height + 1;
|
||||
if (confirmations == null && height != null) {
|
||||
final tip = currentChainTip;
|
||||
if (tip > 0 && height > 0) {
|
||||
// Add one because the block itself is the first confirmation
|
||||
confirmations = tip - height + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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?,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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?,
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
49
cw_bitcoin/lib/electrum_worker/methods/stop_scanning.dart
Normal file
49
cw_bitcoin/lib/electrum_worker/methods/stop_scanning.dart
Normal 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?,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -161,16 +161,21 @@ class AddressCell extends StatelessWidget {
|
|||
if (derivationPath.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Flexible(
|
||||
child: AutoSizeText(
|
||||
derivationPath,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: isChange ? 10 : 14,
|
||||
color: textColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: AutoSizeText(
|
||||
derivationPath,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: isChange ? 10 : 14,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (hasBalance || hasReceived)
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue