mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
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:
parent
7faca38cfa
commit
50825a62c1
13 changed files with 266 additions and 140 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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)) {
|
||||||
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 {
|
_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:
|
||||||
syncStatus = NotConnectedSyncStatus();
|
if (syncStatus is! NotConnectedSyncStatus) {
|
||||||
|
syncStatus = NotConnectedSyncStatus();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.failed:
|
case ConnectionStatus.failed:
|
||||||
syncStatus = LostConnectionSyncStatus();
|
if (syncStatus is! LostConnectionSyncStatus) {
|
||||||
|
syncStatus = LostConnectionSyncStatus();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionStatus.connecting:
|
case ConnectionStatus.connecting:
|
||||||
syncStatus = ConnectingSyncStatus();
|
if (syncStatus is! 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
hasMwebInput = true;
|
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) {
|
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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
|
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
return bitcoinWallet.unspentCoins.where((element) {
|
return bitcoinWallet.unspentCoins.where((element) {
|
||||||
switch(coinTypeToSpendFrom) {
|
switch (coinTypeToSpendFrom) {
|
||||||
case UnspentCoinType.mweb:
|
case UnspentCoinType.mweb:
|
||||||
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
|
||||||
case UnspentCoinType.nonMweb:
|
case UnspentCoinType.nonMweb:
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
await nodeSource.add(newCakeWalletBitcoinNode);
|
if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
|
||||||
|
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);
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,30 +197,28 @@ abstract class WalletCreationVMBase with Store {
|
||||||
var appStore = getIt.get<AppStore>();
|
var appStore = getIt.get<AppStore>();
|
||||||
var node = appStore.settingsStore.getCurrentNode(walletType);
|
var node = appStore.settingsStore.getCurrentNode(walletType);
|
||||||
|
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
|
||||||
|
mnemonic: restoreWallet.mnemonicSeed!,
|
||||||
|
node: node,
|
||||||
|
passphrase: restoreWallet.passphrase,
|
||||||
|
);
|
||||||
|
|
||||||
final derivationList = await bitcoin!.getDerivationsFromMnemonic(
|
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
|
||||||
mnemonic: restoreWallet.mnemonicSeed!,
|
return [];
|
||||||
node: node,
|
return derivationList;
|
||||||
passphrase: restoreWallet.passphrase,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
case WalletType.nano:
|
||||||
if (derivationList.first.transactionsCount == 0 && derivationList.length > 1) return [];
|
return nanoUtil!.getDerivationsFromMnemonic(
|
||||||
|
mnemonic: restoreWallet.mnemonicSeed!,
|
||||||
return derivationList;
|
node: node,
|
||||||
|
);
|
||||||
case WalletType.nano:
|
default:
|
||||||
return nanoUtil!.getDerivationsFromMnemonic(
|
break;
|
||||||
mnemonic: restoreWallet.mnemonicSeed!,
|
}
|
||||||
node: node,
|
return list;
|
||||||
);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
|
WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue