From 964f66c74ad3a0569acdbb85f6a3d206bdba5e62 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Thu, 18 Jul 2024 23:37:29 -0700 Subject: [PATCH] updates --- cw_bitcoin/lib/electrum_wallet.dart | 24 ++-- cw_bitcoin/lib/litecoin_wallet.dart | 129 ++++++++++++------ .../lib/pending_bitcoin_transaction.dart | 4 +- lib/bitcoin/cw_bitcoin.dart | 12 ++ lib/entities/preferences_key.dart | 2 + .../screens/dashboard/pages/balance_page.dart | 75 +++++++++- lib/store/settings_store.dart | 22 +++ .../dashboard/dashboard_view_model.dart | 18 +++ tool/configure.dart | 4 + 9 files changed, 229 insertions(+), 61 deletions(-) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d1b464f88..38fded49e 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -416,23 +416,19 @@ abstract class ElectrumWalletBase await _setInitialHeight(); } - if (this is! LitecoinWallet) { - await _subscribeForUpdates(); - await updateTransactions(); + await subscribeForUpdates(); + await updateTransactions(); - await updateAllUnspents(); - await updateBalance(); - } + await updateAllUnspents(); + await updateBalance(); await updateFeeRates(); Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); - if (this is! LitecoinWallet) { - if (alwaysScan == true) { - _setListeners(walletInfo.restoreHeight); - } else { - syncStatus = SyncedSyncStatus(); - } + if (alwaysScan == true) { + _setListeners(walletInfo.restoreHeight); + } else { + syncStatus = SyncedSyncStatus(); } } catch (e, stacktrace) { print(stacktrace); @@ -1595,7 +1591,7 @@ abstract class ElectrumWalletBase matchedAddresses.toList(), addressRecord.isHidden, (address) async { - await _subscribeForUpdates(); + await subscribeForUpdates(); return _fetchAddressHistory(address, await getCurrentChainTip()) .then((history) => history.isNotEmpty ? address.address : null); }, @@ -1684,7 +1680,7 @@ abstract class ElectrumWalletBase } } - Future _subscribeForUpdates() async { + Future subscribeForUpdates() async { final unsubscribedScriptHashes = walletAddresses.allAddresses.where( (address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)), ); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 505ff9d8e..22fb8ebc7 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -89,6 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { StreamSubscription? _utxoStream; int mwebUtxosHeight = 0; late RpcClient _stub; + late bool mwebEnabled = true; static Future create( {required String mnemonic, @@ -154,17 +155,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @action @override Future startSync() async { - await super.startSync(); + if (!mwebEnabled) { + syncStatus = SyncronizingSyncStatus(); + await subscribeForUpdates(); + await updateTransactions(); + syncStatus = SyncedSyncStatus(); + return; + } + + await subscribeForUpdates(); + await updateTransactions(); + await updateFeeRates(); + + Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); + _stub = await CwMweb.stub(); _syncTimer?.cancel(); _syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async { if (syncStatus is FailedSyncStatus) return; final height = await electrumClient.getCurrentBlockChainTip() ?? 0; final resp = await _stub.status(StatusRequest()); - // print("height: $height"); - // print("resp.blockHeaderHeight: ${resp.blockHeaderHeight}"); - // print("resp.mwebHeaderHeight: ${resp.mwebHeaderHeight}"); - // print("resp.mwebUtxosHeight: ${resp.mwebUtxosHeight}"); if (resp.blockHeaderHeight < height) { int h = resp.blockHeaderHeight; syncStatus = SyncingSyncStatus(height - h, h / height); @@ -211,6 +221,21 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mwebUtxosBox = await CakeHive.openBox(boxName); } + @override + Future renameWalletFiles(String newWalletName) async { + // rename the hive box: + final oldBoxName = "${walletInfo.name.replaceAll(" ", "_")}_${MwebUtxo.boxName}"; + final newBoxName = "${newWalletName.replaceAll(" ", "_")}_${MwebUtxo.boxName}"; + + final oldBox = await Hive.openBox(oldBoxName); + mwebUtxosBox = await CakeHive.openBox(newBoxName); + for (final key in oldBox.keys) { + await mwebUtxosBox.put(key, oldBox.get(key)!); + } + + await super.renameWalletFiles(newWalletName); + } + @action @override Future rescan({ @@ -261,11 +286,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { ); } - tx.height = utxo.height; - tx.isPending = utxo.height == 0; - tx.confirmations = confirmations; bool isNew = transactionHistory.transactions[tx.id] == null; + // don't update the confirmations if the tx is updated by electrum: + if (tx.confirmations == 0 || utxo.height != 0) { + tx.height = utxo.height; + tx.isPending = utxo.height == 0; + tx.confirmations = confirmations; + } + if (!(tx.outputAddresses?.contains(utxo.address) ?? false)) { tx.outputAddresses?.add(utxo.address); isNew = true; @@ -295,12 +324,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { int restoreHeight = walletInfo.restoreHeight; print("SCANNING FROM HEIGHT: $restoreHeight"); final req = UtxosRequest(scanSecret: hex.decode(scanSecret), fromHeight: restoreHeight); - bool initDone = false; // process old utxos: for (final utxo in mwebUtxosBox.values) { if (utxo.address.isEmpty) { - initDone = true; continue; } @@ -310,11 +337,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { await handleIncoming(utxo, _stub); - // if (initDone) { - // await updateUnspent(); - // await updateBalance(); - // } - if (utxo.height > walletInfo.restoreHeight) { walletInfo.updateRestoreHeight(utxo.height); } @@ -459,36 +481,38 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { updatedUnspentCoins.addAll(await fetchUnspent(address)); })); - // update mweb unspents: - final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; - mwebUtxosBox.keys.forEach((dynamic oId) { - final String outputId = oId as String; - final utxo = mwebUtxosBox.get(outputId); - if (utxo == null) { - return; - } - if (utxo.address.isEmpty) { - // not sure if a bug or a special case but we definitely ignore these - return; - } - final addressRecord = walletAddresses.allAddresses - .firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address); + if (mwebEnabled) { + // update mweb unspents: + final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; + mwebUtxosBox.keys.forEach((dynamic oId) { + final String outputId = oId as String; + final utxo = mwebUtxosBox.get(outputId); + if (utxo == null) { + return; + } + if (utxo.address.isEmpty) { + // not sure if a bug or a special case but we definitely ignore these + return; + } + final addressRecord = walletAddresses.allAddresses + .firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address); - if (addressRecord == null) { - print("addressRecord is null! TODO: handle this case2"); - return; - } - final unspent = BitcoinUnspent( - addressRecord, - outputId, - utxo.value.toInt(), - mwebAddrs.indexOf(utxo.address), - ); - if (unspent.vout == 0) { - unspent.isChange = true; - } - updatedUnspentCoins.add(unspent); - }); + if (addressRecord == null) { + print("utxo contains an address that is not in the wallet: ${utxo.address}"); + return; + } + final unspent = BitcoinUnspent( + addressRecord, + outputId, + utxo.value.toInt(), + mwebAddrs.indexOf(utxo.address), + ); + if (unspent.vout == 0) { + unspent.isChange = true; + } + updatedUnspentCoins.add(unspent); + }); + } unspentCoins = updatedUnspentCoins; } @@ -638,7 +662,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @override Future createTransaction(Object credentials) async { try { - final tx = await super.createTransaction(credentials) as PendingBitcoinTransaction; + var tx = await super.createTransaction(credentials) as PendingBitcoinTransaction; + tx.isMweb = mwebEnabled; + + if (!mwebEnabled) { + return tx; + } final resp = await _stub.create(CreateRequest( rawTx: hex.decode(tx.hex), @@ -708,4 +737,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { await mwebUtxosBox.close(); _syncTimer?.cancel(); } + + void setMwebEnabled(bool enabled) { + if (!mwebEnabled && enabled) { + mwebEnabled = enabled; + startSync(); + } + mwebEnabled = enabled; + } + + bool get isMwebEnabled => mwebEnabled; } diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 4217d35b4..df5826a3c 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -23,6 +23,7 @@ class PendingBitcoinTransaction with PendingTransaction { required this.hasChange, this.isSendAll = false, this.hasTaprootInputs = false, + this.isMweb = false, }) : _listeners = []; final WalletType type; @@ -35,6 +36,7 @@ class PendingBitcoinTransaction with PendingTransaction { final bool isSendAll; final bool hasChange; final bool hasTaprootInputs; + bool isMweb; String? idOverride; String? hexOverride; List? outputs; @@ -103,7 +105,7 @@ class PendingBitcoinTransaction with PendingTransaction { @override Future commit() async { - if (network is LitecoinNetwork) { + if (isMweb) { await _ltcCommit(); } else { await _commit(); diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index bc01b33ec..7e0264962 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -582,4 +582,16 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; await bitcoinWallet.updateFeeRates(); } + + @override + void setMwebEnabled(Object wallet, bool enabled) { + final litecoinWallet = wallet as LitecoinWallet; + litecoinWallet.setMwebEnabled(enabled); + } + + @override + bool getMwebEnabled(Object wallet) { + final litecoinWallet = wallet as LitecoinWallet; + return litecoinWallet.isMwebEnabled; + } } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index e1ee0ada3..2c669c3bd 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -48,6 +48,8 @@ class PreferencesKey { static const customBitcoinFeeRate = 'custom_electrum_fee_rate'; static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay'; static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan'; + static const mwebCardDisplay = 'mwebCardDisplay'; + static const mwebEnabled = 'mwebEnabled'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const shouldShowRepWarning = 'should_show_rep_warning'; diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 7ffcf918d..530038ee9 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -315,7 +315,73 @@ class CryptoBalanceWidget extends StatelessWidget { ), ), ), - ] + ], + if (dashboardViewModel.showMwebCard) ...[ + SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: DashBoardRoundedCardWidget( + customBorder: 30, + title: "T: MWEB", + subTitle: "T: Enable MWEB", + hint: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => launchUrl( + Uri.parse( + "https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"), + mode: LaunchMode.externalApplication, + ), + child: Row( + children: [ + Text( + "T: What is MWEB?", + style: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w400, + color: Theme.of(context) + .extension()! + .labelTextColor, + height: 1, + ), + softWrap: true, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Icon(Icons.help_outline, + size: 16, + color: Theme.of(context) + .extension()! + .labelTextColor), + ) + ], + ), + ), + Observer( + builder: (_) => StandardSwitch( + value: dashboardViewModel.mwebEnabled, + onTaped: () => _toggleMweb(context), + ), + ) + ], + ), + ], + ), + onTap: () => _toggleMweb(context), + icon: Icon( + Icons.lock, + color: + Theme.of(context).extension()!.pageTitleTextColor, + size: 50, + ), + ), + ), + ], ], ); }), @@ -355,6 +421,13 @@ class CryptoBalanceWidget extends StatelessWidget { return dashboardViewModel.setSilentPaymentsScanning(newValue); } + + + Future _toggleMweb(BuildContext context) async { + final isMwebEnabled = dashboardViewModel.mwebEnabled; + final newValue = !isMwebEnabled; + return dashboardViewModel.setMwebEnabled(newValue); + } } class BalanceRowWidget extends StatelessWidget { diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 8e16adbff..b6947b1c7 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -110,6 +110,8 @@ abstract class SettingsStoreBase with Store { required this.customBitcoinFeeRate, required this.silentPaymentsCardDisplay, required this.silentPaymentsAlwaysScan, + required this.mwebCardDisplay, + required this.mwebEnabled, TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialWowneroTransactionPriority, @@ -536,6 +538,14 @@ abstract class SettingsStoreBase with Store { (bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool( PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan)); + reaction( + (_) => mwebCardDisplay, + (bool mwebCardDisplay) => + _sharedPreferences.setBool(PreferencesKey.mwebCardDisplay, mwebCardDisplay)); + + reaction((_) => mwebEnabled, + (bool mwebEnabled) => _sharedPreferences.setBool(PreferencesKey.mwebEnabled, mwebEnabled)); + this.nodes.observe((change) { if (change.newValue != null && change.key != null) { _saveCurrentNode(change.newValue!, change.key!); @@ -737,6 +747,12 @@ abstract class SettingsStoreBase with Store { @observable bool silentPaymentsAlwaysScan; + @observable + bool mwebCardDisplay; + + @observable + bool mwebEnabled; + final SecureStorage _secureStorage; final SharedPreferences _sharedPreferences; final BackgroundTasks _backgroundTasks; @@ -893,6 +909,8 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; final silentPaymentsAlwaysScan = sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false; + final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true; + final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false; // If no value if (pinLength == null || pinLength == 0) { @@ -1145,6 +1163,8 @@ abstract class SettingsStoreBase with Store { customBitcoinFeeRate: customBitcoinFeeRate, silentPaymentsCardDisplay: silentPaymentsCardDisplay, silentPaymentsAlwaysScan: silentPaymentsAlwaysScan, + mwebCardDisplay: mwebCardDisplay, + mwebEnabled: mwebEnabled, initialMoneroTransactionPriority: moneroTransactionPriority, initialWowneroTransactionPriority: wowneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, @@ -1293,6 +1313,8 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; silentPaymentsAlwaysScan = sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false; + mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true; + mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false; final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final bitcoinElectrumServerId = sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 28ac98978..f9729e5f5 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -329,6 +329,24 @@ abstract class DashboardViewModelBase with Store { } } + @computed + bool get hasMweb => wallet.type == WalletType.litecoin; + + @computed + bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay; + + @observable + bool mwebEnabled = false; + + @action + void setMwebEnabled(bool active) { + mwebEnabled = active; + + if (hasMweb) { + bitcoin!.setMwebEnabled(wallet, active); + } + } + BalanceViewModel balanceViewModel; AppStore appStore; diff --git a/tool/configure.dart b/tool/configure.dart index 853d06448..f2907678f 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -115,6 +115,7 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/litecoin_wallet_service.dart'; +import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/bitcoin_hardware_wallet_service.dart'; @@ -219,6 +220,9 @@ abstract class Bitcoin { void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + + void setMwebEnabled(Object wallet, bool enabled); + bool getMwebEnabled(Object wallet); } """;