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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.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/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -142,14 +143,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@action @action
@override @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: // use regular change address on peg in, otherwise use mweb for change address:
if (!mwebEnabled) { if (!mwebEnabled || isPegIn) {
return super.getChangeAddress(); return super.getChangeAddress();
} }
if (outputs != null && utxoDetails != null) { if (inputs != null && outputs != null) {
// check if this is a PEGIN: // check if this is a PEGIN:
bool outputsToMweb = false; bool outputsToMweb = false;
bool comesFromMweb = false; bool comesFromMweb = false;
@ -161,14 +163,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
outputsToMweb = true; 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")) { if (element.address.contains("mweb")) {
comesFromMweb = true; comesFromMweb = true;
} }
}); });
bool isPegIn = !comesFromMweb && outputsToMweb; bool isPegIn = !comesFromMweb && outputsToMweb;
if (isPegIn && mwebEnabled) { if (isPegIn && mwebEnabled) {
return super.getChangeAddress(); return super.getChangeAddress();
} }

View file

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

View file

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

View file

@ -216,7 +216,6 @@ class CWBitcoin extends Bitcoin {
case UnspentCoinType.any: case UnspentCoinType.any:
return true; return true;
} }
}).toList(); }).toList();
} }
@ -399,19 +398,21 @@ class CWBitcoin extends Bitcoin {
final history = await electrumClient.getHistory(sh); final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(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.address = address;
dInfoCopy.transactionsCount = history.length; dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy); list.add(dInfoCopy);
} catch (e) { } catch (e, s) {
print(e); print("derivationInfoError: $e");
print("derivationInfoStack: $s");
} }
} }
} }
// sort the list such that derivations with the most transactions are first: // sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount)); list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list; return list;
} }
@ -682,4 +683,15 @@ class CWBitcoin extends Bitcoin {
return null; 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 = final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false); Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
await nodeSource.add(newCakeWalletBitcoinNode); await nodeSource.add(newCakeWalletBitcoinNode);
}
if (needToReplaceCurrentBitcoinNode) { if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt( await sharedPreferences.setInt(
@ -931,6 +933,10 @@ Future<void> _addBitcoinNode({
bool replaceExisting = false, bool replaceExisting = false,
bool useSSL = false, bool useSSL = false,
}) async { }) async {
bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
if (isNodeExists) {
return;
}
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com'; const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId = final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); 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/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.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/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/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/payment_request.dart';
@ -843,7 +842,7 @@ class BalanceRowWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( 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( child: Stack(
children: [ children: [
if (currency == CryptoCurrency.ltc) if (currency == CryptoCurrency.ltc)
@ -851,17 +850,15 @@ class BalanceRowWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Container( Container(
padding: EdgeInsets.only(right: 16, top: 16), padding: EdgeInsets.only(right: 16, top: 0),
child: Column( child: Column(
children: [ children: [
Container( Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ImageIcon( child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'), AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129), color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40, size: 40,
), ),
), ),
@ -889,7 +886,6 @@ class BalanceRowWidget extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 24),
Text( Text(
'${secondAvailableBalanceLabel}', '${secondAvailableBalanceLabel}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -907,9 +903,9 @@ class BalanceRowWidget extends StatelessWidget {
AutoSizeText( AutoSizeText(
secondAvailableBalance, secondAvailableBalance,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 24,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w400, fontWeight: FontWeight.w900,
color: Theme.of(context) color: Theme.of(context)
.extension<BalancePageTheme>()! .extension<BalancePageTheme>()!
.assetTitleColor, .assetTitleColor,
@ -918,15 +914,15 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1, maxLines: 1,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), SizedBox(height: 6),
if (!isTestnet) if (!isTestnet)
Text( Text(
'${secondAvailableFiatBalance}', '${secondAvailableFiatBalance}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 16,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w400, fontWeight: FontWeight.w500,
color: Theme.of(context) color: Theme.of(context)
.extension<BalancePageTheme>()! .extension<BalancePageTheme>()!
.textColor, .textColor,
@ -1019,7 +1015,6 @@ class BalanceRowWidget extends StatelessWidget {
paymentRequest = paymentRequest =
PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}")); PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}"));
} }
Navigator.pushNamed( Navigator.pushNamed(
context, context,
Routes.send, Routes.send,
@ -1030,11 +1025,10 @@ class BalanceRowWidget extends StatelessWidget {
); );
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context) backgroundColor: Colors.grey.shade400
.extension<SendPageTheme>()!
.textFieldButtonIconColor
.withAlpha(50), .withAlpha(50),
side: BorderSide(color: Colors.grey.shade400, width: 0), side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
@ -1058,7 +1052,7 @@ class BalanceRowWidget extends StatelessWidget {
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.extension<BalancePageTheme>()! .extension<BalancePageTheme>()!
.assetTitleColor, .textColor,
), ),
), ),
], ],
@ -1074,13 +1068,12 @@ class BalanceRowWidget extends StatelessWidget {
child: OutlinedButton( child: OutlinedButton(
onPressed: () { onPressed: () {
final litecoinAddress = final litecoinAddress =
bitcoin!.getAddress(dashboardViewModel.wallet); bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null; PaymentRequest? paymentRequest = null;
if (litecoinAddress.isNotEmpty) { if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri( paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}")); Uri.parse("litecoin:${litecoinAddress}"));
} }
Navigator.pushNamed( Navigator.pushNamed(
context, context,
Routes.send, Routes.send,
@ -1091,11 +1084,10 @@ class BalanceRowWidget extends StatelessWidget {
); );
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
backgroundColor: Theme.of(context) backgroundColor: Colors.grey.shade400
.extension<SendPageTheme>()!
.textFieldButtonIconColor
.withAlpha(50), .withAlpha(50),
side: BorderSide(color: Colors.grey.shade400, width: 0), side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
@ -1119,7 +1111,7 @@ class BalanceRowWidget extends StatelessWidget {
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.extension<BalancePageTheme>()! .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/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/output.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: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.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
@ -509,6 +510,10 @@ class SendPage extends BasePage {
newContactAddress = newContactAddress =
newContactAddress ?? sendViewModel.newContactAddress(); newContactAddress ?? sendViewModel.newContactAddress();
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
newContactAddress = null;
}
final successMessage = S.of(_dialogContext).send_success( final successMessage = S.of(_dialogContext).send_success(
sendViewModel.selectedCryptoCurrency.toString()); sendViewModel.selectedCryptoCurrency.toString());

View file

@ -115,7 +115,9 @@ abstract class WalletCreationVMBase with Store {
getIt.get<BackgroundTasks>().registerSyncTask(); getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed(); _appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
} catch (e, _) { } catch (e, s) {
print("error: $e");
print("stack: $s");
state = FailureState(e.toString()); state = FailureState(e.toString());
} }
} }
@ -198,16 +200,14 @@ abstract class WalletCreationVMBase with Store {
switch (walletType) { switch (walletType) {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
final derivationList = await bitcoin!.getDerivationsFromMnemonic( final derivationList = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!, mnemonic: restoreWallet.mnemonicSeed!,
node: node, node: node,
passphrase: restoreWallet.passphrase, passphrase: restoreWallet.passphrase,
); );
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
if (derivationList.first.transactionsCount == 0 && derivationList.length > 1) return []; return [];
return derivationList; return derivationList;
case WalletType.nano: case WalletType.nano:

View file

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