Mweb enhancements 3 (#1744)

* version 4.20.0

* update build numbers

* UI updates and script fix for ios bundle identifier

* disable mweb for desktop

* change hardcoded ltc server ip address
electrum connection enhancement

* MWEB enhancements 2.0 (#1735)

* additional logging and minor fixes

* additional logging and minor fixes

* addresses pt.1

* Allow Wallet Group Names to be the same as Wallet Names (#1730)

* fix: Issues with imaging

* fix: Allow group names to be the same as wallet names

* fix: Bug with wallet grouping when a wallet is minimized

* fix: Bug with wallet grouping when a wallet is minimized

* logs of fixes and experimental changes, close wallet before opening next

* save

* fix icon

* fixes

* [skip ci] updates

* [skip ci] updates

* updates

* minor optimizations

* fix for when switching between wallets

* [skip ci] updates

* [skip ci] updates

* Update cw_bitcoin/lib/litecoin_wallet.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update cw_bitcoin/lib/litecoin_wallet.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* mobx

* mostly logging

* stream fix pt.1 [skip ci]

* updates

* some fixes and enhancements

* [skip ci] minor

* potential partial fix for streamsink closed

* fix stream sink closed errors

* fix mweb logo colors

* save

* minor enhancements [skip ci]

* save

* experimental

* minor

* minor [skip ci]

---------

Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* fix menu list removing from original list

* detach sync status from mwebsyncstatus

* minor

* keep sync status in sync where necessary

* minor

* wip

* appears to work?

* updates

* prevent mwebd from submitting non mweb transactions

* fix unspent coins info not persisting for mweb coins + other minor fixes

* [skip ci] minor

* Polish MWEB card UI

* make sure current chain tip is updated correctly [skip ci]

* [skip ci] review fixes

* [skip ci] detect mweb outputs more thoroughly (fix peg-in commit error)

* fix change address on send ui

* fix qr code scan issue

* get segwit address for pegout even if mweb is selected on the receive screen [skip ci]

* - Fix adding nodes twice
- Fix mempool API parsing error

* (potentially) fix duplicate tx history bug

* [skip ci] fix bc1 address

* don't show contacts prompt on pegin/out + potential unconfirmed balance fixes

* [skip ci] minor cleanup

* fix mweb input detection

* fix showing mweb address for non-mweb transactions

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
Co-authored-by: David Adegoke <64401859+Blazebrain@users.noreply.github.com>
Co-authored-by: tuxpizza <tuxsudo@tux.pizza>
This commit is contained in:
Matthew Fosse 2024-10-18 17:05:48 -07:00 committed by GitHub
parent 7faca38cfa
commit 50825a62c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 266 additions and 140 deletions

View file

@ -68,8 +68,8 @@ class ElectrumClient {
try {
await socket?.close();
socket = null;
} catch (_) {}
socket = null;
try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
@ -102,7 +102,8 @@ class ElectrumClient {
return;
}
_setConnectionStatus(ConnectionStatus.connected);
// use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);
socket!.listen(
(Uint8List event) {
@ -128,7 +129,7 @@ class ElectrumClient {
print("SOCKET CLOSED!!!!!");
unterminatedString = '';
try {
if (host == socket?.address.host) {
if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy();
}
@ -178,7 +179,7 @@ class ElectrumClient {
unterminatedString = '';
}
} catch (e) {
print(e.toString());
print("parse $e");
}
}
@ -191,7 +192,7 @@ class ElectrumClient {
try {
await callWithTimeout(method: 'server.ping');
_setConnectionStatus(ConnectionStatus.connected);
} on RequestFailedTimeoutException catch (_) {
} catch (_) {
_setConnectionStatus(ConnectionStatus.disconnected);
}
}
@ -431,7 +432,7 @@ class ElectrumClient {
return subscription;
} catch (e) {
print(e.toString());
print("subscribe $e");
return null;
}
}
@ -470,7 +471,8 @@ class ElectrumClient {
return completer.future;
} catch (e) {
print(e.toString());
print("callWithTimeout $e");
rethrow;
}
}
@ -537,6 +539,12 @@ class ElectrumClient {
onConnectionStatusChange?.call(status);
_connectionStatus = status;
_isConnected = status == ConnectionStatus.connected;
if (!_isConnected) {
try {
socket?.destroy();
} catch (_) {}
socket = null;
}
}
void _handleResponse(Map<String, dynamic> response) {

View file

@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
@ -249,7 +250,7 @@ abstract class ElectrumWalletBase
int? _currentChainTip;
Future<int> getCurrentChainTip() async {
if (_currentChainTip != null) {
if ((_currentChainTip ?? 0) > 0) {
return _currentChainTip!;
}
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
@ -301,6 +302,7 @@ abstract class ElectrumWalletBase
@action
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
if (this is! BitcoinWallet) return;
final chainTip = chainTipParam ?? await getUpdatedChainTip();
if (chainTip == height) {
@ -467,7 +469,7 @@ abstract class ElectrumWalletBase
}
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
print("startSync $e");
syncStatus = FailedSyncStatus();
}
}
@ -479,10 +481,10 @@ abstract class ElectrumWalletBase
final response =
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended"));
final result = json.decode(response.body) as Map<String, num>;
final slowFee = result['economyFee']?.toInt() ?? 0;
int mediumFee = result['hourFee']?.toInt() ?? 0;
int fastFee = result['fastestFee']?.toInt() ?? 0;
final result = json.decode(response.body) as Map<String, dynamic>;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
if (slowFee == mediumFee) {
mediumFee++;
}
@ -491,7 +493,9 @@ abstract class ElectrumWalletBase
}
_feeRates = [slowFee, mediumFee, fastFee];
return;
} catch (_) {}
} catch (e) {
print(e);
}
}
final feeRates = await electrumClient.feeRates(network: network);
@ -571,7 +575,7 @@ abstract class ElectrumWalletBase
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
print("connectToNode $e");
syncStatus = FailedSyncStatus();
}
}
@ -826,8 +830,8 @@ abstract class ElectrumWalletBase
}
final changeAddress = await walletAddresses.getChangeAddress(
inputs: utxoDetails.availableInputs,
outputs: updatedOutputs,
utxoDetails: utxoDetails,
);
final address = RegexUtils.addressTypeFromStr(changeAddress, network);
updatedOutputs.add(BitcoinOutput(
@ -1181,6 +1185,7 @@ abstract class ElectrumWalletBase
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) {
@ -1370,7 +1375,7 @@ abstract class ElectrumWalletBase
});
}
// Set the balance of all non-silent payment addresses to 0 before updating
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
@ -1487,7 +1492,7 @@ abstract class ElectrumWalletBase
await unspentCoinsInfo.deleteAll(keys);
}
} catch (e) {
print(e.toString());
print("refreshUnspentCoinsInfo $e");
}
}
@ -1831,7 +1836,7 @@ abstract class ElectrumWalletBase
return historiesWithDetails;
} catch (e) {
print(e.toString());
print("fetchTransactions $e");
return {};
}
}
@ -1905,7 +1910,9 @@ abstract class ElectrumWalletBase
if (height > 0) {
storedTx.height = height;
// the tx's block itself is the first confirmation so add 1
if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1;
if ((currentHeight ?? 0) > 0) {
storedTx.confirmations = currentHeight! - height + 1;
}
storedTx.isPending = storedTx.confirmations == 0;
}
@ -1946,9 +1953,13 @@ abstract class ElectrumWalletBase
}
await getCurrentChainTip();
transactionHistory.transactions.values.forEach((tx) async {
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) {
tx.confirmations = await getCurrentChainTip() - tx.height! + 1;
transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null &&
tx.unspents!.isNotEmpty &&
tx.height != null &&
tx.height! > 0 &&
(_currentChainTip ?? 0) > 0) {
tx.confirmations = _currentChainTip! - tx.height! + 1;
}
});
@ -1973,9 +1984,17 @@ abstract class ElectrumWalletBase
await Future.wait(unsubscribedScriptHashes.map((address) async {
final sh = address.getScriptHash(network);
if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) {
await _scripthashesUpdateSubject[sh]?.close();
try {
await _scripthashesUpdateSubject[sh]?.close();
} catch (e) {
print("failed to close: $e");
}
}
try {
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
} catch (e) {
print("failed scripthashUpdate: $e");
}
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async {
try {
await updateUnspentsForAddress(address);
@ -2171,6 +2190,7 @@ abstract class ElectrumWalletBase
@action
void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) {
case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus ||
@ -2182,19 +2202,26 @@ abstract class ElectrumWalletBase
break;
case ConnectionStatus.disconnected:
syncStatus = NotConnectedSyncStatus();
if (syncStatus is! NotConnectedSyncStatus) {
syncStatus = NotConnectedSyncStatus();
}
break;
case ConnectionStatus.failed:
syncStatus = LostConnectionSyncStatus();
if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus();
}
break;
case ConnectionStatus.connecting:
syncStatus = ConnectingSyncStatus();
if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus();
}
break;
default:
}
}
void _syncStatusReaction(SyncStatus syncStatus) async {
print("SYNC_STATUS_CHANGE: ${syncStatus}");
if (syncStatus is SyncingSyncStatus) {
return;
}

View file

@ -3,7 +3,7 @@ import 'dart:io' show Platform;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -267,7 +267,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@action
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
Future<String> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
updateChangeAddresses();
if (changeAddresses.isEmpty) {
@ -478,7 +478,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await saveAddressesInBox();
} catch (e) {
print(e.toString());
print("updateAddresses $e");
}
}

View file

@ -7,6 +7,7 @@ import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_mweb/mwebd.pbgrpc.dart';
import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39;
@ -95,6 +96,36 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
});
reaction((_) => mwebSyncStatus, (status) async {
if (mwebSyncStatus is FailedSyncStatus) {
// we failed to connect to mweb, check if we are connected to the litecoin node:
late int nodeHeight;
try {
nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
} catch (_) {
nodeHeight = 0;
}
if (nodeHeight == 0) {
// we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
} else {
// we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
await CwMweb.stop();
await Future.delayed(const Duration(seconds: 5));
startSync();
}
} else if (mwebSyncStatus is SyncingSyncStatus) {
syncStatus = mwebSyncStatus;
} else if (mwebSyncStatus is SyncronizingSyncStatus) {
if (syncStatus is! SyncronizingSyncStatus) {
syncStatus = mwebSyncStatus;
}
} else if (mwebSyncStatus is SyncedSyncStatus) {
if (syncStatus is! SyncedSyncStatus) {
syncStatus = mwebSyncStatus;
}
}
});
}
late final Bip32Slip10Secp256k1 mwebHd;
late final Box<MwebUtxo> mwebUtxosBox;
@ -105,6 +136,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
late bool mwebEnabled;
bool processingUtxos = false;
@observable
SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
@ -244,13 +278,24 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
Future<void> startSync() async {
print("startSync() called!");
if (syncStatus is SyncronizingSyncStatus) {
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
if (!mwebEnabled) {
try {
// in case we're switching from a litecoin wallet that had mweb enabled
CwMweb.stop();
} catch (_) {}
super.startSync();
return;
}
if (mwebSyncStatus is SyncronizingSyncStatus) {
return;
}
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
_syncTimer?.cancel();
try {
syncStatus = SyncronizingSyncStatus();
mwebSyncStatus = SyncronizingSyncStatus();
try {
await subscribeForUpdates();
} catch (e) {
@ -261,45 +306,32 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
_feeRatesTimer =
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
if (!mwebEnabled) {
try {
// in case we're switching from a litecoin wallet that had mweb enabled
CwMweb.stop();
} catch (_) {}
try {
await updateAllUnspents();
await updateTransactions();
await updateBalance();
syncStatus = SyncedSyncStatus();
} catch (e, s) {
print(e);
print(s);
syncStatus = FailedSyncStatus();
}
return;
}
print("START SYNC FUNCS");
await waitForMwebAddresses();
await processMwebUtxos();
await updateTransactions();
await updateUnspent();
await updateBalance();
} catch (e) {
print("failed to start mweb sync: $e");
syncStatus = FailedSyncStatus(error: "failed to start");
print("DONE SYNC FUNCS");
} catch (e, s) {
print("mweb sync failed: $e $s");
mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
return;
}
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
if (syncStatus is FailedSyncStatus) return;
if (mwebSyncStatus is FailedSyncStatus) {
_syncTimer?.cancel();
return;
}
final nodeHeight =
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
if (nodeHeight == 0) {
// we aren't connected to the ltc node yet
if (syncStatus is! NotConnectedSyncStatus) {
syncStatus = FailedSyncStatus(error: "Failed to connect to Litecoin node");
if (mwebSyncStatus is! NotConnectedSyncStatus) {
mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
}
return;
}
@ -309,12 +341,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
try {
if (resp.blockHeaderHeight < nodeHeight) {
int h = resp.blockHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebHeaderHeight < nodeHeight) {
int h = resp.mwebHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebUtxosHeight < nodeHeight) {
syncStatus = SyncingSyncStatus(1, 0.999);
mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else {
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
@ -325,6 +357,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
if (transaction.confirmations == confirmations) continue;
if (transaction.confirmations == 0) {
updateBalance();
}
transaction.confirmations = confirmations;
transactionHistory.addOne(transaction);
}
@ -332,17 +367,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
// prevent unnecessary reaction triggers:
if (syncStatus is! SyncedSyncStatus) {
if (mwebSyncStatus is! SyncedSyncStatus) {
// mwebd is synced, but we could still be processing incoming utxos:
if (!processingUtxos) {
syncStatus = SyncedSyncStatus();
mwebSyncStatus = SyncedSyncStatus();
}
}
return;
}
} catch (e) {
print("error syncing: $e");
syncStatus = FailedSyncStatus(error: e.toString());
mwebSyncStatus = FailedSyncStatus(error: e.toString());
}
});
}
@ -512,8 +547,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
_utxoStream = responseStream.listen((Utxo sUtxo) async {
// we're processing utxos, so our balance could still be innacurate:
if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
syncStatus = SyncronizingSyncStatus();
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
mwebSyncStatus = SyncronizingSyncStatus();
processingUtxos = true;
_processingTimer?.cancel();
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
@ -530,10 +565,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
value: sUtxo.value.toInt(),
);
// if (mwebUtxosBox.containsKey(utxo.outputId)) {
// // we've already stored this utxo, skip it:
// return;
// }
if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
}
return;
}
await updateUnspent();
await updateBalance();
@ -579,7 +622,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final height = await electrumClient.getCurrentBlockChainTip();
if (height == null || status.blockHeaderHeight != height) return;
if (status.mwebUtxosHeight != height) return; // we aren't synced
int amount = 0;
Set<String> inputAddresses = {};
var output = convert.AccumulatorSink<Digest>();
@ -673,10 +715,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
@action
Future<void> updateAllUnspents() async {
// get ltc unspents:
await super.updateAllUnspents();
if (!mwebEnabled) {
await super.updateAllUnspents();
return;
}
@ -712,6 +752,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
mwebUnspentCoins.add(unspent);
});
// copy coin control attributes to mwebCoins:
await updateCoins(mwebUnspentCoins);
// get regular ltc unspents (this resets unspentCoins):
await super.updateAllUnspents();
// add the mwebCoins:
unspentCoins.addAll(mwebUnspentCoins);
}
@ -890,6 +936,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled;
if (!mwebEnabled) {
tx.changeAddressOverride =
await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false);
return tx;
}
await waitForMwebAddresses();
@ -913,12 +961,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
hasMwebOutput = true;
break;
}
if (output.address.toLowerCase().contains("mweb")) {
hasMwebOutput = true;
break;
}
}
if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
hasMwebInput = true;
// check if mweb inputs are used:
for (final utxo in tx.utxos) {
if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
hasMwebInput = true;
}
}
bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride = await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular);
if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx;
@ -971,7 +1030,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = <String>{};
transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id);
// await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return;
final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
@ -990,6 +1049,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
print(e);
print(s);
if (e.toString().contains("commit failed")) {
print(e);
throw Exception("Transaction commit failed (no peers responded), please try again.");
}
rethrow;

View file

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -142,14 +143,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@action
@override
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
Future<String> getChangeAddress(
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
// use regular change address on peg in, otherwise use mweb for change address:
if (!mwebEnabled) {
if (!mwebEnabled || isPegIn) {
return super.getChangeAddress();
}
if (outputs != null && utxoDetails != null) {
if (inputs != null && outputs != null) {
// check if this is a PEGIN:
bool outputsToMweb = false;
bool comesFromMweb = false;
@ -161,14 +163,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
outputsToMweb = true;
}
}
// TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
utxoDetails.availableInputs.forEach((element) {
inputs.forEach((element) {
if (!element.isSending || element.isFrozen) {
return;
}
if (element.address.contains("mweb")) {
comesFromMweb = true;
}
});
bool isPegIn = !comesFromMweb && outputsToMweb;
if (isPegIn && mwebEnabled) {
return super.getChangeAddress();
}

View file

@ -24,6 +24,7 @@ class PendingBitcoinTransaction with PendingTransaction {
this.isSendAll = false,
this.hasTaprootInputs = false,
this.isMweb = false,
this.utxos = const [],
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
@ -36,7 +37,9 @@ class PendingBitcoinTransaction with PendingTransaction {
final bool isSendAll;
final bool hasChange;
final bool hasTaprootInputs;
List<UtxoWithAddress> utxos;
bool isMweb;
String? changeAddressOverride;
String? idOverride;
String? hexOverride;
List<String>? outputAddresses;
@ -63,6 +66,9 @@ class PendingBitcoinTransaction with PendingTransaction {
PendingChange? get change {
try {
final change = _tx.outputs.firstWhere((out) => out.isChange);
if (changeAddressOverride != null) {
return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
}
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
} catch (_) {
return null;

View file

@ -40,7 +40,7 @@ class CwMweb {
}
static Future<void> _initializeClient() async {
print("initialize client called!");
print("_initializeClient() called!");
final appDir = await getApplicationSupportDirectory();
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
@ -54,7 +54,7 @@ class CwMweb {
log("Attempting to connect to server on port: $_port");
// wait for the server to finish starting up before we try to connect to it:
await Future.delayed(const Duration(seconds: 5));
await Future.delayed(const Duration(seconds: 8));
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
_rpcClient = null;
@ -83,10 +83,13 @@ class CwMweb {
log("Attempt $i failed: $e");
log('Caught grpc error: ${e.message}');
_rpcClient = null;
// necessary if the database isn't open:
await stop();
await Future.delayed(const Duration(seconds: 3));
} catch (e) {
log("Attempt $i failed: $e");
_rpcClient = null;
await stop();
await Future.delayed(const Duration(seconds: 3));
}
}

View file

@ -208,7 +208,7 @@ class CWBitcoin extends Bitcoin {
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins.where((element) {
switch(coinTypeToSpendFrom) {
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
@ -216,7 +216,6 @@ class CWBitcoin extends Bitcoin {
case UnspentCoinType.any:
return true;
}
}).toList();
}
@ -399,19 +398,21 @@ class CWBitcoin extends Bitcoin {
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.first.value.toString();
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e) {
print(e);
} catch (e, s) {
print("derivationInfoError: $e");
print("derivationInfoStack: $s");
}
}
}
// sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list;
}
@ -682,4 +683,15 @@ class CWBitcoin extends Bitcoin {
return null;
}
}
String? getUnusedSegwitAddress(Object wallet) {
try {
final electrumWallet = wallet as ElectrumWallet;
final segwitAddress = electrumWallet.walletAddresses.allAddresses
.firstWhere((element) => !element.isUsed && element.type == SegwitAddresType.p2wpkh);
return segwitAddress.address;
} catch (_) {
return null;
}
}
}

View file

@ -899,7 +899,9 @@ Future<void> changeDefaultBitcoinNode(
final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newCakeWalletBitcoinNode);
if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
await nodeSource.add(newCakeWalletBitcoinNode);
}
if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt(
@ -931,6 +933,10 @@ Future<void> _addBitcoinNode({
bool replaceExisting = false,
bool useSSL = false,
}) async {
bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
if (isNodeExists) {
return;
}
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);

View file

@ -17,7 +17,6 @@ import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/payment_request.dart';
@ -843,7 +842,7 @@ class BalanceRowWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (currency == CryptoCurrency.ltc)
@ -851,17 +850,15 @@ class BalanceRowWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.only(right: 16, top: 16),
padding: EdgeInsets.only(right: 16, top: 0),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129),
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40,
),
),
@ -889,7 +886,6 @@ class BalanceRowWidget extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
@ -907,9 +903,9 @@ class BalanceRowWidget extends StatelessWidget {
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 20,
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
@ -918,15 +914,15 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
@ -1019,7 +1015,6 @@ class BalanceRowWidget extends StatelessWidget {
paymentRequest =
PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
@ -1030,11 +1025,10 @@ class BalanceRowWidget extends StatelessWidget {
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400, width: 0),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@ -1058,7 +1052,7 @@ class BalanceRowWidget extends StatelessWidget {
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
.textColor,
),
),
],
@ -1074,13 +1068,12 @@ class BalanceRowWidget extends StatelessWidget {
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getAddress(dashboardViewModel.wallet);
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if (litecoinAddress.isNotEmpty) {
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
@ -1091,11 +1084,10 @@ class BalanceRowWidget extends StatelessWidget {
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldButtonIconColor
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400, width: 0),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@ -1119,7 +1111,7 @@ class BalanceRowWidget extends StatelessWidget {
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
.textColor,
),
),
],

View file

@ -28,6 +28,7 @@ import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
@ -509,6 +510,10 @@ class SendPage extends BasePage {
newContactAddress =
newContactAddress ?? sendViewModel.newContactAddress();
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
newContactAddress = null;
}
final successMessage = S.of(_dialogContext).send_success(
sendViewModel.selectedCryptoCurrency.toString());

View file

@ -115,7 +115,9 @@ abstract class WalletCreationVMBase with Store {
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState();
} catch (e, _) {
} catch (e, s) {
print("error: $e");
print("stack: $s");
state = FailureState(e.toString());
}
}
@ -195,30 +197,28 @@ abstract class WalletCreationVMBase with Store {
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
return [];
return derivationList;
if (derivationList.first.transactionsCount == 0 && derivationList.length > 1) return [];
return derivationList;
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
);
default:
break;
}
return list;
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
);
default:
break;
}
return list;
}
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();

View file

@ -231,6 +231,7 @@ abstract class Bitcoin {
Future<void> setMwebEnabled(Object wallet, bool enabled);
bool getMwebEnabled(Object wallet);
String? getUnusedMwebAddress(Object wallet);
String? getUnusedSegwitAddress(Object wallet);
}
""";