7
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
8
import 'package:cw_core/cake_hive.dart';
9
import 'package:cw_core/mweb_utxo.dart';
10
+import 'package:cw_core/unspent_coin_type.dart';
11
import 'package:cw_mweb/mwebd.pbgrpc.dart';
12
import 'package:fixnum/fixnum.dart';
13
import 'package:bip39/bip39.dart' as bip39;
96
autorun((_) {
97
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
98
});
99
+ reaction((_) => mwebSyncStatus, (status) async {
100
+ if (mwebSyncStatus is FailedSyncStatus) {
101
+ // we failed to connect to mweb, check if we are connected to the litecoin node:
102
+ late int nodeHeight;
103
+ try {
104
+ nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
105
+ } catch (_) {
106
+ nodeHeight = 0;
107
+ }
108
+
109
+ if (nodeHeight == 0) {
110
+ // we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
111
+ } else {
112
+ // we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
113
+ await CwMweb.stop();
114
+ await Future.delayed(const Duration(seconds: 5));
115
+ startSync();
116
+ }
117
+ } else if (mwebSyncStatus is SyncingSyncStatus) {
118
+ syncStatus = mwebSyncStatus;
119
+ } else if (mwebSyncStatus is SyncronizingSyncStatus) {
120
+ if (syncStatus is! SyncronizingSyncStatus) {
121
+ syncStatus = mwebSyncStatus;
122
+ }
123
+ } else if (mwebSyncStatus is SyncedSyncStatus) {
124
+ if (syncStatus is! SyncedSyncStatus) {
125
+ syncStatus = mwebSyncStatus;
126
+ }
127
+ }
128
+ });
129
}
130
late final Bip32Slip10Secp256k1 mwebHd;
131
late final Box<MwebUtxo> mwebUtxosBox;
136
late bool mwebEnabled;
137
bool processingUtxos = false;
138
139
+ @observable
140
+ SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
141
+
142
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
143
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
144
278
@override
279
Future<void> startSync() async {
280
print("startSync() called!");
247
- if (syncStatus is SyncronizingSyncStatus) {
281
+ print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
282
+ if (!mwebEnabled) {
283
+ try {
284
+ // in case we're switching from a litecoin wallet that had mweb enabled
285
+ CwMweb.stop();
286
+ } catch (_) {}
287
+ super.startSync();
288
+ return;
289
+ }
290
+
291
+ if (mwebSyncStatus is SyncronizingSyncStatus) {
292
return;
293
}
294
+
295
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
296
_syncTimer?.cancel();
297
try {
253
- syncStatus = SyncronizingSyncStatus();
298
+ mwebSyncStatus = SyncronizingSyncStatus();
299
try {
300
await subscribeForUpdates();
301
} catch (e) {
306
_feeRatesTimer =
307
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
308
264
- if (!mwebEnabled) {
265
- try {
266
- // in case we're switching from a litecoin wallet that had mweb enabled
267
- CwMweb.stop();
268
- } catch (_) {}
269
- try {
270
- await updateAllUnspents();
271
- await updateTransactions();
272
- await updateBalance();
273
- syncStatus = SyncedSyncStatus();
274
- } catch (e, s) {
275
- print(e);
276
- print(s);
277
- syncStatus = FailedSyncStatus();
278
- }
279
- return;
280
- }
281
-
309
+ print("START SYNC FUNCS");
310
await waitForMwebAddresses();
311
await processMwebUtxos();
312
await updateTransactions();
313
await updateUnspent();
314
await updateBalance();
287
- } catch (e) {
288
- print("failed to start mweb sync: $e");
289
- syncStatus = FailedSyncStatus(error: "failed to start");
315
+ print("DONE SYNC FUNCS");
316
+ } catch (e, s) {
317
+ print("mweb sync failed: $e $s");
318
+ mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
319
return;
320
}
321
322
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
294
- if (syncStatus is FailedSyncStatus) return;
323
+ if (mwebSyncStatus is FailedSyncStatus) {
324
+ _syncTimer?.cancel();
325
+ return;
326
+ }
327
328
final nodeHeight =
329
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
330
331
if (nodeHeight == 0) {
332
// we aren't connected to the ltc node yet
301
- if (syncStatus is! NotConnectedSyncStatus) {
302
- syncStatus = FailedSyncStatus(error: "Failed to connect to Litecoin node");
333
+ if (mwebSyncStatus is! NotConnectedSyncStatus) {
334
+ mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
335
}
336
return;
337
}
341
try {
342
if (resp.blockHeaderHeight < nodeHeight) {
343
int h = resp.blockHeaderHeight;
312
- syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
344
+ mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
345
} else if (resp.mwebHeaderHeight < nodeHeight) {
346
int h = resp.mwebHeaderHeight;
315
- syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
347
+ mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
348
} else if (resp.mwebUtxosHeight < nodeHeight) {
317
- syncStatus = SyncingSyncStatus(1, 0.999);
349
+ mwebSyncStatus = SyncingSyncStatus(1, 0.999);
350
} else {
351
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
352
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
357
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
358
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
359
if (transaction.confirmations == confirmations) continue;
360
+ if (transaction.confirmations == 0) {
361
+ updateBalance();
362
+ }
363
transaction.confirmations = confirmations;
364
transactionHistory.addOne(transaction);
365
}
367
}
368
369
// prevent unnecessary reaction triggers:
335
- if (syncStatus is! SyncedSyncStatus) {
370
+ if (mwebSyncStatus is! SyncedSyncStatus) {
371
// mwebd is synced, but we could still be processing incoming utxos:
372
if (!processingUtxos) {
338
- syncStatus = SyncedSyncStatus();
373
+ mwebSyncStatus = SyncedSyncStatus();
374
}
375
}
376
return;
377
}
378
} catch (e) {
379
print("error syncing: $e");
345
- syncStatus = FailedSyncStatus(error: e.toString());
380
+ mwebSyncStatus = FailedSyncStatus(error: e.toString());
381
}
382
});
383
}
547
}
548
_utxoStream = responseStream.listen((Utxo sUtxo) async {
549
// we're processing utxos, so our balance could still be innacurate:
515
- if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
516
- syncStatus = SyncronizingSyncStatus();
550
+ if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
551
+ mwebSyncStatus = SyncronizingSyncStatus();
552
processingUtxos = true;
553
_processingTimer?.cancel();
554
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
565
value: sUtxo.value.toInt(),
566
);
567
533
- // if (mwebUtxosBox.containsKey(utxo.outputId)) {
534
- // // we've already stored this utxo, skip it:
535
- // return;
536
- // }
568
+ if (mwebUtxosBox.containsKey(utxo.outputId)) {
569
+ // we've already stored this utxo, skip it:
570
+ // but do update the utxo height if it's somehow different:
571
+ final existingUtxo = mwebUtxosBox.get(utxo.outputId);
572
+ if (existingUtxo!.height != utxo.height) {
573
+ print(
574
+ "updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
575
+ existingUtxo.height = utxo.height;
576
+ await mwebUtxosBox.put(utxo.outputId, existingUtxo);
577
+ }
578
+ return;
579
+ }
580
581
await updateUnspent();
582
await updateBalance();
622
final height = await electrumClient.getCurrentBlockChainTip();
623
if (height == null || status.blockHeaderHeight != height) return;
624
if (status.mwebUtxosHeight != height) return; // we aren't synced
582
-
625
int amount = 0;
626
Set<String> inputAddresses = {};
627
var output = convert.AccumulatorSink<Digest>();
715
@override
716
@action
717
Future<void> updateAllUnspents() async {
676
- // get ltc unspents:
677
- await super.updateAllUnspents();
678
-
718
if (!mwebEnabled) {
719
+ await super.updateAllUnspents();
720
return;
721
}
722
752
}
753
mwebUnspentCoins.add(unspent);
754
});
755
+
756
+ // copy coin control attributes to mwebCoins:
757
+ await updateCoins(mwebUnspentCoins);
758
+ // get regular ltc unspents (this resets unspentCoins):
759
+ await super.updateAllUnspents();
760
+ // add the mwebCoins:
761
unspentCoins.addAll(mwebUnspentCoins);
762
}
763
936
tx.isMweb = mwebEnabled;
937
938
if (!mwebEnabled) {
939
+ tx.changeAddressOverride =
940
+ await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false);
941
return tx;
942
}
943
await waitForMwebAddresses();
961
hasMwebOutput = true;
962
break;
963
}
964
+ if (output.address.toLowerCase().contains("mweb")) {
965
+ hasMwebOutput = true;
966
+ break;
967
+ }
968
}
969
918
- if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
919
- hasMwebInput = true;
970
+ // check if mweb inputs are used:
971
+ for (final utxo in tx.utxos) {
972
+ if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
973
+ hasMwebInput = true;
974
+ }
975
}
976
977
+ bool isPegIn = !hasMwebInput && hasMwebOutput;
978
+ bool isRegular = !hasMwebInput && !hasMwebOutput;
979
+ tx.changeAddressOverride = await (walletAddresses as LitecoinWalletAddresses)
980
+ .getChangeAddress(isPegIn: isPegIn || isRegular);
981
if (!hasMwebInput && !hasMwebOutput) {
982
tx.isMweb = false;
983
return tx;
1030
final addresses = <String>{};
1031
transaction.inputAddresses?.forEach((id) async {
1032
final utxo = mwebUtxosBox.get(id);
974
- // await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
1033
+ await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
1034
if (utxo == null) return;
1035
final addressRecord = walletAddresses.allAddresses
1036
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
1049
print(e);
1050
print(s);
1051
if (e.toString().contains("commit failed")) {
1052
+ print(e);
1053
throw Exception("Transaction commit failed (no peers responded), please try again.");
1054
}
1055
rethrow;