This commit is contained in:
Matthew Fosse 2024-07-18 23:37:29 -07:00
parent a2121ef73e
commit 964f66c74a
9 changed files with 229 additions and 61 deletions

View file

@ -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<void> _subscribeForUpdates() async {
Future<void> subscribeForUpdates() async {
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
);

View file

@ -89,6 +89,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
StreamSubscription<Utxo>? _utxoStream;
int mwebUtxosHeight = 0;
late RpcClient _stub;
late bool mwebEnabled = true;
static Future<LitecoinWallet> create(
{required String mnemonic,
@ -154,17 +155,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@action
@override
Future<void> 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<MwebUtxo>(boxName);
}
@override
Future<void> 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<MwebUtxo>(oldBoxName);
mwebUtxosBox = await CakeHive.openBox<MwebUtxo>(newBoxName);
for (final key in oldBox.keys) {
await mwebUtxosBox.put(key, oldBox.get(key)!);
}
await super.renameWalletFiles(newWalletName);
}
@action
@override
Future<void> 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<PendingTransaction> 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;
}

View file

@ -23,6 +23,7 @@ class PendingBitcoinTransaction with PendingTransaction {
required this.hasChange,
this.isSendAll = false,
this.hasTaprootInputs = false,
this.isMweb = false,
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
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<String>? outputs;
@ -103,7 +105,7 @@ class PendingBitcoinTransaction with PendingTransaction {
@override
Future<void> commit() async {
if (network is LitecoinNetwork) {
if (isMweb) {
await _ltcCommit();
} else {
await _commit();

View file

@ -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;
}
}

View file

@ -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';

View file

@ -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<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.mwebEnabled,
onTaped: () => _toggleMweb(context),
),
)
],
),
],
),
onTap: () => _toggleMweb(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
],
],
);
}),
@ -355,6 +421,13 @@ class CryptoBalanceWidget extends StatelessWidget {
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
Future<void> _toggleMweb(BuildContext context) async {
final isMwebEnabled = dashboardViewModel.mwebEnabled;
final newValue = !isMwebEnabled;
return dashboardViewModel.setMwebEnabled(newValue);
}
}
class BalanceRowWidget extends StatelessWidget {

View file

@ -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);

View file

@ -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;

View file

@ -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<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5});
void setMwebEnabled(Object wallet, bool enabled);
bool getMwebEnabled(Object wallet);
}
""";