diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index c1bbd43f4..855b33c8f 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -4,6 +4,7 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; abstract class BaseBitcoinAddressRecord { BaseBitcoinAddressRecord( @@ -20,16 +21,15 @@ abstract class BaseBitcoinAddressRecord { _balance = balance, _name = name, _isUsed = isUsed, - _isHidden = isHidden ?? isChange, + isHidden = isHidden ?? isChange, _isChange = isChange; @override bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; final String address; - bool _isHidden; + bool isHidden; - bool get isHidden => _isHidden; final bool _isChange; bool get isChange => _isChange; @@ -53,7 +53,7 @@ abstract class BaseBitcoinAddressRecord { void setAsUsed() { _isUsed = true; - _isHidden = true; + isHidden = true; } void setNewName(String label) => _name = label; @@ -75,11 +75,15 @@ abstract class BaseBitcoinAddressRecord { 'runtimeType': runtimeType.toString(), }); - static BaseBitcoinAddressRecord fromJSON(String jsonSource) { + static BaseBitcoinAddressRecord fromJSON( + String jsonSource, [ + DerivationInfo? derivationInfo, + BasedUtxoNetwork? network, + ]) { final decoded = json.decode(jsonSource) as Map; if (decoded['runtimeType'] == 'BitcoinAddressRecord') { - return BitcoinAddressRecord.fromJSON(jsonSource); + return BitcoinAddressRecord.fromJSON(jsonSource, derivationInfo, network); } else if (decoded['runtimeType'] == 'BitcoinSilentPaymentAddressRecord') { return BitcoinSilentPaymentAddressRecord.fromJSON(jsonSource); } else if (decoded['runtimeType'] == 'BitcoinReceivedSPAddressRecord') { @@ -87,7 +91,7 @@ abstract class BaseBitcoinAddressRecord { } else if (decoded['runtimeType'] == 'LitecoinMWEBAddressRecord') { return LitecoinMWEBAddressRecord.fromJSON(jsonSource); } else { - throw ArgumentError('Unknown runtimeType'); + return BitcoinAddressRecord.fromJSON(jsonSource, derivationInfo, network); } } } @@ -120,17 +124,34 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { } } - factory BitcoinAddressRecord.fromJSON(String jsonSource) { + factory BitcoinAddressRecord.fromJSON( + String jsonSource, [ + DerivationInfo? derivationInfo, + BasedUtxoNetwork? network, + ]) { final decoded = json.decode(jsonSource) as Map; + final derivationInfoSnp = decoded['derivationInfo'] as Map<String, dynamic>?; + final derivationTypeSnp = decoded['derivationType'] as int?; + final cwDerivationType = derivationTypeSnp != null + ? CWBitcoinDerivationType.values[derivationTypeSnp] + : derivationInfo!.derivationType == DerivationType.bip39 + ? CWBitcoinDerivationType.old_bip39 + : CWBitcoinDerivationType.old_electrum; return BitcoinAddressRecord( decoded['address'] as String, index: decoded['index'] as int, - derivationInfo: BitcoinDerivationInfo.fromJSON( - decoded['derivationInfo'] as Map<String, dynamic>, - ), - // TODO: make nullable maybe? - cwDerivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int], + derivationInfo: derivationInfoSnp == null + ? [CWBitcoinDerivationType.bip39, CWBitcoinDerivationType.old_bip39] + .contains(cwDerivationType) + ? BitcoinDerivationInfo.fromDerivationAndAddress( + BitcoinDerivationType.bip39, + decoded['address'] as String, + network!, + ) + : BitcoinDerivationInfos.ELECTRUM + : BitcoinDerivationInfo.fromJSON(derivationInfoSnp), + cwDerivationType: cwDerivationType, isHidden: decoded['isHidden'] as bool? ?? false, isChange: decoded['isChange'] as bool? ?? false, isUsed: decoded['isUsed'] as bool? ?? false, @@ -218,7 +239,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { return BitcoinSilentPaymentAddressRecord( decoded['address'] as String, derivationPath: - decoded['derivationPath'] as String? ?? BitcoinWalletAddressesBase.OLD_SP_SPEND_PATH, + (decoded['derivationPath'] as String?) ?? BitcoinWalletAddressesBase.OLD_SP_PATH, labelIndex: decoded['index'] as int, isUsed: decoded['isUsed'] as bool? ?? false, txCount: decoded['txCount'] as int? ?? 0, diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart index 0135e1d74..3efe02265 100644 --- a/cw_bitcoin/lib/bitcoin_unspent.dart +++ b/cw_bitcoin/lib/bitcoin_unspent.dart @@ -1,6 +1,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_core/unspent_transaction_output.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_core/wallet_info.dart'; class BitcoinUnspent extends Unspent { BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout) @@ -10,9 +11,19 @@ class BitcoinUnspent extends Unspent { factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) => BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout); - factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) { + factory BitcoinUnspent.fromJSON( + BaseBitcoinAddressRecord? address, + Map<String, dynamic> json, [ + DerivationInfo? derivationInfo, + BasedUtxoNetwork? network, + ]) { return BitcoinUnspent( - address ?? BaseBitcoinAddressRecord.fromJSON(json['address_record'] as String), + address ?? + BaseBitcoinAddressRecord.fromJSON( + json['address_record'] as String, + derivationInfo, + network, + ), json['tx_hash'] as String, int.parse(json['value'].toString()), int.parse(json['tx_pos'].toString()), diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 14eb5f6b3..254ce9db2 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -144,7 +144,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { snp = await BitcoinWalletSnapshot.load( encryptionFileUtils, name, - walletInfo.type, + walletInfo, password, network, ); @@ -209,27 +209,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final isNamedElectrs = node?.uri.host.contains("electrs") ?? false; if (isNamedElectrs) { node!.isElectrs = true; + node!.save(); + return true; } final isNamedFulcrum = node!.uri.host.contains("fulcrum"); if (isNamedFulcrum) { node!.isElectrs = false; + node!.save(); + return false; } - if (node!.isElectrs == null) { - final version = await waitSendWorker(ElectrumWorkerGetVersionRequest()); + final version = await waitSendWorker(ElectrumWorkerGetVersionRequest()); - if (version is List<String> && version.isNotEmpty) { - final server = version[0]; + if (version is List<String> && version.isNotEmpty) { + final server = version[0]; - if (server.toLowerCase().contains('electrs')) { - node!.isElectrs = true; - } - } else if (version is String && version.toLowerCase().contains('electrs')) { + if (server.toLowerCase().contains('electrs')) { node!.isElectrs = true; - } else { - node!.isElectrs = false; } + } else if (version is String && version.toLowerCase().contains('electrs')) { + node!.isElectrs = true; + } else { + node!.isElectrs = false; } node!.save(); @@ -293,8 +295,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[]; for (final utxo in utxos) { - final rawTx = - (await getTransactionExpanded(hash: utxo.utxo.txHash)).originalTransaction.toHex(); + final rawTx = await getTransactionHex(hash: utxo.utxo.txHash); final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; psbtReadyInputs.add(PSBTReadyUtxoWithAddress( @@ -373,7 +374,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } @action - Future<void> setSilentPaymentsScanning(bool active) async { + Future<void> setSilentPaymentsScanning(bool active, [int? height, bool? doSingleScan]) async { silentPaymentsScanningActive = active; final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments(); final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning; @@ -382,14 +383,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { syncStatus = AttemptingScanSyncStatus(); final tip = currentChainTip!; + final beginHeight = height ?? walletInfo.restoreHeight; - if (tip == walletInfo.restoreHeight) { + if (tip == beginHeight) { syncStatus = SyncedTipSyncStatus(tip); return; } - if (tip > walletInfo.restoreHeight) { - _setListeners(walletInfo.restoreHeight); + if (tip > beginHeight) { + _requestTweakScanning(beginHeight, doSingleScan: doSingleScan); } } else if (syncStatus is! SyncedSyncStatus) { await waitSendWorker(ElectrumWorkerStopScanningRequest()); @@ -501,23 +503,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } } - @action - @override - Future<void> startSync() async { - await _setInitialScanHeight(); - - await super.startSync(); - - if (alwaysScan == true) { - _setListeners(walletInfo.restoreHeight); - } - } - @action @override Future<void> rescan({required int height, bool? doSingleScan}) async { - silentPaymentsScanningActive = true; - _setListeners(height, doSingleScan: doSingleScan); + setSilentPaymentsScanning(true, height, doSingleScan); } @action @@ -645,14 +634,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } final height = result.height; - if (height != null) { + if (height != null && result.wasSingleBlock == false) { await walletInfo.updateRestoreHeight(height); } } } @action - Future<void> _setListeners(int height, {bool? doSingleScan}) async { + Future<void> _requestTweakScanning(int height, {bool? doSingleScan}) async { if (currentChainTip == null) { throw Exception("currentChainTip is null"); } @@ -763,7 +752,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { // New headers received, start scanning if (alwaysScan == true && syncStatus is SyncedSyncStatus) { - _setListeners(walletInfo.restoreHeight); + _requestTweakScanning(walletInfo.restoreHeight); } } diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index 6f3063f83..52696a14a 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -31,7 +31,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S silentPaymentWallets = [silentPaymentWallet!]; } - static const OLD_SP_SPEND_PATH = "m/352'/1'/0'/0'/0"; + static const OLD_SP_PATH = "m/352'/1'/0'/#'/0"; static const BITCOIN_ADDRESS_TYPES = [ SegwitAddressType.p2wpkh, P2pkhAddressType.p2pkh, @@ -74,8 +74,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S if (silentPaymentAddresses.isEmpty) { if (walletInfo.isRecovery) { - final oldScanPath = Bip32PathParser.parse("m/352'/1'/0'/1'/0"); - final oldSpendPath = Bip32PathParser.parse("m/352'/1'/0'/0'/0"); + final oldScanPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "1")); + final oldSpendPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "0")); final oldSilentPaymentWallet = SilentPaymentOwner.fromPrivateKeys( b_scan: ECPrivate(hdWallet.derive(oldScanPath).privateKey), @@ -315,7 +315,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S final usableSilentPaymentAddresses = silentPaymentAddresses .where((a) => a.type != SegwitAddressType.p2tr && - a.derivationPath != OLD_SP_SPEND_PATH && + a.derivationPath != OLD_SP_PATH && a.isChange == false) .toList(); final nextSPLabelIndex = usableSilentPaymentAddresses.length; @@ -330,7 +330,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S ); silentPaymentAddresses.add(address); - updateAddressesOnReceiveScreen(); + updateAddressesByType(); return address; } @@ -382,14 +382,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @override @action - void updateAddressesOnReceiveScreen() { + void updateAddressesByType() { if (addressPageType == SilentPaymentsAddresType.p2sp) { - addressesOnReceiveScreen.clear(); - addressesOnReceiveScreen.addAll(silentPaymentAddresses); + receiveAddressesByType.clear(); + receiveAddressesByType[SilentPaymentsAddresType.p2sp] = silentPaymentAddresses + .where((addressRecord) => + addressRecord.type == SilentPaymentsAddresType.p2sp && !addressRecord.isChange) + .toList(); return; } - super.updateAddressesOnReceiveScreen(); + super.updateAddressesByType(); } @action @@ -398,7 +401,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S addressesSet.addAll(addresses); this.silentPaymentAddresses.clear(); this.silentPaymentAddresses.addAll(addressesSet); - updateAddressesOnReceiveScreen(); + updateAddressesByType(); } @action @@ -407,7 +410,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S addressesSet.addAll(addresses); this.receivedSPAddresses.clear(); this.receivedSPAddresses.addAll(addressesSet); - updateAddressesOnReceiveScreen(); + updateAddressesByType(); } @action @@ -416,7 +419,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); silentPaymentAddresses.remove(addressRecord); - updateAddressesOnReceiveScreen(); + updateAddressesByType(); } Map<String, String> get labels { @@ -492,6 +495,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets, required BasedUtxoNetwork network, required bool isHardwareWallet, + // TODO: make it used List<BitcoinAddressRecord>? initialAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses, diff --git a/cw_bitcoin/lib/bitcoin_wallet_snapshot.dart b/cw_bitcoin/lib/bitcoin_wallet_snapshot.dart index 68dab90e7..b722ac74c 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_snapshot.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_snapshot.dart @@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/wallet_info.dart'; class BitcoinWalletSnapshot extends ElectrumWalletSnapshot { BitcoinWalletSnapshot({ @@ -27,10 +27,11 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot { static Future<BitcoinWalletSnapshot> load( EncryptionFileUtils encryptionFileUtils, String name, - WalletType type, + WalletInfo walletInfo, String password, BasedUtxoNetwork network, ) async { + final type = walletInfo.type; final path = await pathForWallet(name: name, type: type); final jsonSource = await encryptionFileUtils.read(path: path, password: password); final data = json.decode(jsonSource) as Map; @@ -38,7 +39,7 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot { final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load( encryptionFileUtils, name, - type, + walletInfo, password, network, ); diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 2f01c605f..d336967ee 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -160,6 +160,7 @@ class ElectrumTransactionInfo extends TransactionInfo { List<String> inputAddresses = []; List<String> outputAddresses = []; + final sentAmounts = <int>[]; if (bundle.ins.length > 0) { for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { final input = bundle.originalTransaction.inputs[i]; @@ -167,11 +168,13 @@ class ElectrumTransactionInfo extends TransactionInfo { final outTransaction = inputTransaction.outputs[input.txIndex]; inputAmount += outTransaction.amount.toInt(); if (addresses.contains( - BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network))) { + BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network), + )) { direction = TransactionDirection.outgoing; inputAddresses.add( BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network), ); + sentAmounts.add(outTransaction.amount.toInt()); } } } @@ -214,6 +217,8 @@ class ElectrumTransactionInfo extends TransactionInfo { // Self-send direction = TransactionDirection.incoming; amount = receivedAmounts.reduce((a, b) => a + b); + } else if (sentAmounts.length > 0) { + amount = sentAmounts.reduce((a, b) => a + b); } final fee = inputAmount - totalOutAmount; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d395e6671..10668e8ae 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -186,8 +186,7 @@ abstract class ElectrumWalletBase onBalanceResponse(response.result); break; case ElectrumRequestMethods.getHistoryMethod: - final response = ElectrumWorkerGetHistoryResponse.fromJson(messageJson); - onHistoriesResponse(response.result); + onHistoriesResponse(ElectrumWorkerGetHistoryResponse.fromJson(messageJson)); break; case ElectrumRequestMethods.listunspentMethod: final response = ElectrumWorkerListUnspentResponse.fromJson(messageJson); @@ -1270,12 +1269,10 @@ abstract class ElectrumWalletBase await Future.forEach(walletAddresses.allAddresses, (BitcoinAddressRecord addressRecord) async { final isChange = addressRecord.isChange; - final matchingAddressList = - (isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where( - (element) => - element.type == addressRecord.type && - element.cwDerivationType == addressRecord.cwDerivationType, - ); + final matchingAddressList = walletAddresses + .getAddressesByType(addressRecord.type, isChange) + .where((element) => + (element as BitcoinAddressRecord).cwDerivationType == addressRecord.cwDerivationType); final totalMatchingAddresses = matchingAddressList.length; final matchingGapLimit = (isChange @@ -1305,11 +1302,11 @@ abstract class ElectrumWalletBase walletAddresses.updateAdresses(newAddresses); final newMatchingAddressList = - (isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where( - (element) => - element.type == addressRecord.type && - element.cwDerivationType == addressRecord.cwDerivationType, - ); + walletAddresses.getAddressesByType(addressRecord.type, isChange).where( + (element) => + element.type == addressRecord.type && + (element as BitcoinAddressRecord) == addressRecord.cwDerivationType, + ); printV( "discovered ${newAddresses.length} new ${isChange ? "change" : "receive"} addresses"); printV( @@ -1329,7 +1326,8 @@ abstract class ElectrumWalletBase } @action - Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async { + Future<void> onHistoriesResponse(ElectrumWorkerGetHistoryResponse response) async { + final histories = response.result; if (histories.isNotEmpty) { final addressesWithHistory = <BitcoinAddressRecord>[]; @@ -1350,8 +1348,8 @@ abstract class ElectrumWalletBase } await save(); - } else { - // checkAddressesGap(); + } else if (response.completed) { + checkAddressesGap(); } } @@ -1582,7 +1580,7 @@ abstract class ElectrumWalletBase } // Identify all change outputs - final changeAddresses = walletAddresses.changeAddresses; + final changeAddresses = walletAddresses.allChangeAddresses; final List<BitcoinOutput> changeOutputs = outputs .where((output) => changeAddresses .any((element) => element.address == output.address.toAddress(network))) @@ -1643,6 +1641,12 @@ abstract class ElectrumWalletBase } } + Future<String> getTransactionHex({required String hash}) async { + return await waitSendWorker( + ElectrumWorkerTxHexRequest(txHash: hash, currentChainTip: currentChainTip!), + ) as String; + } + Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async { return await waitSendWorker( ElectrumWorkerTxExpandedRequest(txHash: hash, currentChainTip: currentChainTip!), diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 842a74d88..d9a2fbe6f 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -28,12 +28,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { Map<String, int>? initialChangeAddressIndex, BitcoinAddressType? initialAddressPageType, }) : _allAddresses = ObservableList.of(initialAddresses ?? []), - addressesOnReceiveScreen = - ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), - receiveAddresses = ObservableList<BitcoinAddressRecord>.of( - (initialAddresses ?? []).where((addressRecord) => !addressRecord.isChange).toSet()), - changeAddresses = ObservableList<BitcoinAddressRecord>.of( - (initialAddresses ?? []).where((addressRecord) => addressRecord.isChange).toSet()), currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {}, currentChangeAddressIndexByType = initialChangeAddressIndex ?? {}, _addressPageType = initialAddressPageType ?? @@ -46,10 +40,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { static const defaultChangeAddressesCount = 17; static const gap = 20; + final walletAddressTypes = <BitcoinAddressType>[]; + final ObservableList<BitcoinAddressRecord> _allAddresses; - final ObservableList<BaseBitcoinAddressRecord> addressesOnReceiveScreen; - final ObservableList<BitcoinAddressRecord> receiveAddresses; - final ObservableList<BitcoinAddressRecord> changeAddresses; + @observable + Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> receiveAddressesByType = {}; + @observable + Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> changeAddressesByType = {}; + final BasedUtxoNetwork network; final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets; @@ -61,6 +59,24 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @observable late BitcoinAddressType _addressPageType; + @computed + List<BitcoinAddressRecord> get allChangeAddresses => + _allAddresses.where((addr) => addr.isChange).toList(); + + @computed + List<BaseBitcoinAddressRecord> get selectedReceiveAddresses => + receiveAddressesByType[_addressPageType]!; + + @computed + List<BaseBitcoinAddressRecord> get selectedChangeAddresses => + receiveAddressesByType[_addressPageType]!; + + List<BaseBitcoinAddressRecord> getAddressesByType( + BitcoinAddressType type, [ + bool isChange = false, + ]) => + isChange ? changeAddressesByType[type]! : receiveAddressesByType[type]!; + @computed BitcoinAddressType get addressPageType => _addressPageType; @@ -75,6 +91,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return _allAddresses.firstWhere((element) => element.address == address); } + // TODO: toggle to switch @observable BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh; @@ -83,9 +100,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { String get address { String receiveAddress = ""; - final typeMatchingReceiveAddresses = addressesOnReceiveScreen.where((addr) => !addr.isUsed); + final typeMatchingReceiveAddresses = selectedReceiveAddresses.where( + (addressRecord) => !addressRecord.isUsed, + ); - if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) || + if ((isEnabledAutoGenerateSubaddress && selectedReceiveAddresses.isEmpty) || typeMatchingReceiveAddresses.isEmpty) { receiveAddress = generateNewAddress().address; } else { @@ -94,7 +113,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (typeMatchingReceiveAddresses.isNotEmpty) { if (previousAddressMatchesType && - typeMatchingReceiveAddresses.first.address != addressesOnReceiveScreen.first.address) { + typeMatchingReceiveAddresses.first.address != selectedReceiveAddresses.first.address) { receiveAddress = previousAddressRecord!.address; } else { receiveAddress = typeMatchingReceiveAddresses.first.address; @@ -143,22 +162,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @observable BitcoinAddressRecord? previousAddressRecord; - @computed - int get totalCountOfReceiveAddresses => addressesOnReceiveScreen.fold(0, (acc, addressRecord) { - if (!addressRecord.isChange) { - return acc + 1; - } - return acc; - }); - - @computed - int get totalCountOfChangeAddresses => addressesOnReceiveScreen.fold(0, (acc, addressRecord) { - if (addressRecord.isChange) { - return acc + 1; - } - return acc; - }); - CWBitcoinDerivationType getHDWalletType() { if (hdWallets.containsKey(CWBitcoinDerivationType.bip39)) { return CWBitcoinDerivationType.bip39; @@ -171,25 +174,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @override Future<void> init() async { - updateAddressesOnReceiveScreen(); - updateReceiveAddresses(); - updateChangeAddresses(); + updateAddressesByType(); updateHiddenAddresses(); await updateAddressesInBox(); } @action Future<BaseBitcoinAddressRecord> getChangeAddress() async { - updateChangeAddresses(); - - final address = changeAddresses.firstWhere( - (addressRecord) => _isUnusedChangeAddressByType(addressRecord, changeAddressType), + final address = selectedChangeAddresses.firstWhere( + (addr) => _isUnusedChangeAddressByType(addr, changeAddressType), ); return address; } BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { - final newAddressIndex = addressesOnReceiveScreen.fold( + final newAddressIndex = selectedReceiveAddresses.fold( 0, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc, ); @@ -282,21 +281,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } @action - void updateAddressesOnReceiveScreen() { - addressesOnReceiveScreen.clear(); - addressesOnReceiveScreen.addAll(_allAddresses.where(_isAddressPageTypeMatch).toList()); - } - - @action - void updateReceiveAddresses() { - receiveAddresses.clear(); - receiveAddresses.addAll(_allAddresses.where((addressRecord) => !addressRecord.isChange)); - } - - @action - void updateChangeAddresses() { - changeAddresses.clear(); - changeAddresses.addAll(_allAddresses.where((addressRecord) => addressRecord.isChange)); + void updateAddressesByType() { + receiveAddressesByType.clear(); + walletAddressTypes.forEach((type) { + receiveAddressesByType[type] = + _allAddresses.where((addr) => _isAddressByType(addr, type)).toList(); + changeAddressesByType[type] = + _allAddresses.where((addr) => _isAddressByType(addr, type)).toList(); + }); } @action @@ -310,8 +302,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ? ElectrumWalletAddressesBase.defaultChangeAddressesCount : ElectrumWalletAddressesBase.defaultReceiveAddressesCount; - final startIndex = (isChange ? changeAddresses : receiveAddresses) - .where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType) + final startIndex = (isChange ? selectedChangeAddresses : selectedReceiveAddresses) + .where((addr) => + (addr as BitcoinAddressRecord).cwDerivationType == derivationType && + addr.type == addressType) .length; final newAddresses = <BitcoinAddressRecord>[]; @@ -420,9 +414,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if (replacedAddresses.isNotEmpty) { _allAddresses.addAll(replacedAddresses); } else { - updateAddressesOnReceiveScreen(); - updateReceiveAddresses(); - updateChangeAddresses(); + updateAddressesByType(); } } @@ -431,9 +423,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { this._allAddresses.addAll(addresses); updateHiddenAddresses(); - updateAddressesOnReceiveScreen(); - updateReceiveAddresses(); - updateChangeAddresses(); + updateAddressesByType(); } @action @@ -447,18 +437,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action Future<void> setAddressType(BitcoinAddressType type) async { _addressPageType = type; - updateAddressesOnReceiveScreen(); + updateAddressesByType(); walletInfo.addressPageType = addressPageType.toString(); await walletInfo.save(); } - bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) { - return _isAddressByType(addressRecord, addressPageType); - } - bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; - bool _isUnusedChangeAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) { + bool _isUnusedChangeAddressByType(BaseBitcoinAddressRecord addr, BitcoinAddressType type) { return addr.isChange && !addr.isUsed && addr.type == type; } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 98356277b..401772a47 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -48,10 +48,11 @@ class ElectrumWalletSnapshot { static Future<ElectrumWalletSnapshot> load( EncryptionFileUtils encryptionFileUtils, String name, - WalletType type, + WalletInfo walletInfo, String password, BasedUtxoNetwork network, ) async { + final type = walletInfo.type; final path = await pathForWallet(name: name, type: type); final jsonSource = await encryptionFileUtils.read(path: path, password: password); final data = json.decode(jsonSource) as Map; @@ -80,8 +81,14 @@ class ElectrumWalletSnapshot { derivationType: derivationType, derivationPath: derivationPath, unspentCoins: (data['unspent_coins'] as List?) - ?.map((e) => BitcoinUnspent.fromJSON(null, e as Map<String, dynamic>)) - .toList() ?? [], + ?.map((e) => BitcoinUnspent.fromJSON( + null, + e as Map<String, dynamic>, + walletInfo.derivationInfo!, + network, + )) + .toList() ?? + [], didInitialSync: data['didInitialSync'] as bool?, walletAddressesSnapshot: walletAddressesSnapshot, ); diff --git a/cw_bitcoin/lib/electrum_worker/electrum_worker.dart b/cw_bitcoin/lib/electrum_worker/electrum_worker.dart index b106c430c..d194366c1 100644 --- a/cw_bitcoin/lib/electrum_worker/electrum_worker.dart +++ b/cw_bitcoin/lib/electrum_worker/electrum_worker.dart @@ -24,6 +24,7 @@ class ElectrumWorker { final SendPort sendPort; ElectrumProvider? _electrumClient; ServerCapability? _serverCapability; + String? get version => _serverCapability?.version; BehaviorSubject<Map<String, dynamic>>? _scanningStream; BasedUtxoNetwork? _network; @@ -72,6 +73,11 @@ class ElectrumWorker { ElectrumWorkerTxExpandedRequest.fromJson(messageJson), ); break; + case ElectrumWorkerMethods.txHexMethod: + await _handleGetTxHex( + ElectrumWorkerTxHexRequest.fromJson(messageJson), + ); + break; case ElectrumRequestMethods.headersSubscribeMethod: await _handleHeadersSubscribe( ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson), @@ -257,6 +263,8 @@ class ElectrumWorker { completed: true, )); } + }, onError: () { + _serverCapability!.supportsBatching = false; }); })); } else { @@ -488,9 +496,7 @@ class ElectrumWorker { } if (txVerbose?.isEmpty ?? true) { - txHex = await _electrumClient!.request( - ElectrumRequestGetTransactionHex(transactionHash: hash), - ); + txHex = await _getTransactionHex(hash: hash); } else { txHex = txVerbose!['hex'] as String; } @@ -624,11 +630,11 @@ class ElectrumWorker { // date is validated when the API responds with the same date at least twice // since sometimes the mempool api returns the wrong date at first + final canValidateDate = request.mempoolAPIEnabled || _serverCapability!.supportsTxVerbose; if (tx == null || tx.original == null || - // TODO: use mempool api or tx verbose - // (tx.isDateValidated != true && request.mempoolAPIEnabled)) { - (tx.isDateValidated != true)) { + (tx.isDateValidated != true && canValidateDate) || + tx.time == null) { transactionsByIds[txid] = TxToFetch(height: height, tx: tx); } } @@ -832,9 +838,7 @@ class ElectrumWorker { } if (txVerbose?.isEmpty ?? true) { - txHex = await _electrumClient!.request( - ElectrumRequestGetTransactionHex(transactionHash: hash), - ); + txHex = await _getTransactionHex(hash: hash); } else { txHex = txVerbose!['hex'] as String; } @@ -869,10 +873,7 @@ class ElectrumWorker { final inputTransactionHexes = <String, String>{}; await Future.wait(original.inputs.map((e) => e.txId).toList().map((inHash) async { - final hex = await _electrumClient!.request( - ElectrumRequestGetTransactionHex(transactionHash: inHash), - ); - + final hex = await _getTransactionHex(hash: inHash); inputTransactionHexes[inHash] = hex; })); @@ -1058,6 +1059,11 @@ class ElectrumWorker { } } + Future<void> _handleGetTxHex(ElectrumWorkerTxHexRequest request) async { + final hex = await _getTransactionHex(hash: request.txHash); + _sendResponse(ElectrumWorkerTxHexResponse(hex: hex, id: request.id)); + } + Future<void> _handleGetTxExpanded(ElectrumWorkerTxExpandedRequest request) async { final tx = await _getTransactionExpanded( hash: request.txHash, @@ -1129,11 +1135,8 @@ class ElectrumWorker { } } else { await Future.wait(hashes.map((hash) async { - final history = await _electrumClient!.request( - ElectrumRequestGetTransactionHex(transactionHash: hash), - ); - - transactionHexes.add(history); + final hex = await _getTransactionHex(hash: hash); + transactionHexes.add(hex); })); } } @@ -1248,9 +1251,7 @@ class ElectrumWorker { time = transactionVerbose['time'] as int?; confirmations = transactionVerbose['confirmations'] as int?; } else { - transactionHex = await _electrumClient!.request( - ElectrumRequestGetTransactionHex(transactionHash: hash), - ); + transactionHex = await _getTransactionHex(hash: hash); } if (getTime && _walletType == WalletType.bitcoin) { @@ -1271,11 +1272,7 @@ class ElectrumWorker { final ins = <BtcTransaction>[]; for (final vin in original.inputs) { - final inputTransactionHex = await _electrumClient!.request( - // TODO: _getTXHex - ElectrumRequestGetTransactionHex(transactionHash: vin.txId), - ); - + final inputTransactionHex = await _getTransactionHex(hash: vin.txId); ins.add(BtcTransaction.fromRaw(inputTransactionHex)); } @@ -1311,12 +1308,7 @@ class ElectrumWorker { } } else { await Future.wait(hashes.map((hash) async { - final hex = await _electrumClient!.request( - ElectrumRequestGetTransactionHex( - transactionHash: hash, - ), - ); - + final hex = await _getTransactionHex(hash: hash); inputTransactionHexById[hash] = hex; })); } @@ -1324,6 +1316,14 @@ class ElectrumWorker { return inputTransactionHexById; } + Future<String> _getTransactionHex({required String hash}) async { + final hex = await _electrumClient!.request( + ElectrumRequestGetTransactionHex(transactionHash: hash), + ); + + return hex; + } + Future<void> _handleGetFeeRates(ElectrumWorkerGetFeesRequest request) async { if (request.mempoolAPIEnabled && _walletType == WalletType.bitcoin) { try { @@ -1350,7 +1350,7 @@ class ElectrumWorker { // this guarantees that, even if all fees are low and equal, // higher priority fee txs can be consumed when chain fees start surging - _sendResponse( + return _sendResponse( ElectrumWorkerGetFeesResponse( result: BitcoinAPITransactionPriorities( minimum: minimum, @@ -1362,24 +1362,17 @@ class ElectrumWorker { ), ), ); - } catch (e) { - _sendResponse( - ElectrumWorkerGetFeesResponse( - result: ElectrumTransactionPriorities.fromList( - await _electrumClient!.getFeeRates(), - ), - ), - ); - } - } else { - _sendResponse( - ElectrumWorkerGetFeesResponse( - result: ElectrumTransactionPriorities.fromList( - await _electrumClient!.getFeeRates(), - ), - ), - ); + } catch (_) {} } + + // If the above didn't run or failed, fallback to Electrum fees anyway + _sendResponse( + ElectrumWorkerGetFeesResponse( + result: ElectrumTransactionPriorities.fromList( + await _electrumClient!.getFeeRates(), + ), + ), + ); } Future<void> _handleCheckTweaks(ElectrumWorkerCheckTweaksRequest request) async { @@ -1440,11 +1433,11 @@ class ElectrumWorker { } // Initial status UI update, send how many blocks in total to scan - // TODO: isSingleScan : dont update restoreHeight _sendResponse(ElectrumWorkerTweaksSubscribeResponse( result: TweaksSyncResponse( height: syncHeight, syncStatus: StartingScanSyncStatus(syncHeight), + wasSingleBlock: scanData.isSingleScan, ), )); @@ -1493,7 +1486,11 @@ class ElectrumWorker { ? SyncingSyncStatus(1, 0) : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); _sendResponse(ElectrumWorkerTweaksSubscribeResponse( - result: TweaksSyncResponse(height: syncHeight, syncStatus: syncingStatus), + result: TweaksSyncResponse( + height: syncHeight, + syncStatus: syncingStatus, + wasSingleBlock: scanData.isSingleScan, + ), )); final tweakHeight = response.block; @@ -1509,20 +1506,21 @@ class ElectrumWorker { try { final addToWallet = {}; - receivers.forEach((receiver) { - // scanOutputs called from rust here - final scanResult = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver); + // receivers.forEach((receiver) { + // scanOutputs called from rust here + final receiver = receivers.first; + final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver); - if (scanResult.isEmpty) { - return; - } + if (scanResult.isEmpty) { + continue; + } - if (addToWallet[receiver.BSpend] == null) { - addToWallet[receiver.BSpend] = scanResult; - } else { - addToWallet[receiver.BSpend].addAll(scanResult); - } - }); + if (addToWallet[receiver.BSpend] == null) { + addToWallet[receiver.BSpend] = scanResult; + } else { + addToWallet[receiver.BSpend].addAll(scanResult); + } + // }); if (addToWallet.isEmpty) { // no results tx, continue to next tx @@ -1588,6 +1586,7 @@ class ElectrumWorker { _sendResponse(ElectrumWorkerTweaksSubscribeResponse( result: TweaksSyncResponse( transactions: {txInfo.id: TweakResponseData(txInfo: txInfo, unspents: unspents)}, + wasSingleBlock: scanData.isSingleScan, ), )); @@ -1604,7 +1603,7 @@ class ElectrumWorker { syncHeight = tweakHeight; - if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) { + if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) { _sendResponse( ElectrumWorkerTweaksSubscribeResponse( result: TweaksSyncResponse( @@ -1612,6 +1611,7 @@ class ElectrumWorker { syncStatus: scanData.isSingleScan ? SyncedSyncStatus() : SyncedTipSyncStatus(scanData.chainTip), + wasSingleBlock: scanData.isSingleScan, ), ), ); @@ -1627,15 +1627,7 @@ class ElectrumWorker { Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async { _sendResponse( - ElectrumWorkerGetVersionResponse( - result: await _electrumClient!.request( - ElectrumRequestVersion( - clientName: "", - protocolVersion: "1.4", - ), - ), - id: request.id, - ), + ElectrumWorkerGetVersionResponse(result: version!, id: request.id), ); } } diff --git a/cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart b/cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart index 4d9c85a47..47c66c46e 100644 --- a/cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart +++ b/cw_bitcoin/lib/electrum_worker/electrum_worker_methods.dart @@ -5,12 +5,14 @@ class ElectrumWorkerMethods { static const String connectionMethod = "connection"; static const String unknownMethod = "unknown"; static const String txHashMethod = "txHash"; + static const String txHexMethod = "txHex"; static const String checkTweaksMethod = "checkTweaks"; static const String stopScanningMethod = "stopScanning"; static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod); static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod); static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod); + static const ElectrumWorkerMethods txHex = ElectrumWorkerMethods._(txHexMethod); static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod); static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod); diff --git a/cw_bitcoin/lib/electrum_worker/methods/get_tx_hex.dart b/cw_bitcoin/lib/electrum_worker/methods/get_tx_hex.dart new file mode 100644 index 000000000..ef31a7b4f --- /dev/null +++ b/cw_bitcoin/lib/electrum_worker/methods/get_tx_hex.dart @@ -0,0 +1,77 @@ +part of 'methods.dart'; + +class ElectrumWorkerTxHexRequest implements ElectrumWorkerRequest { + ElectrumWorkerTxHexRequest({ + required this.txHash, + required this.currentChainTip, + this.mempoolAPIEnabled = false, + this.id, + this.completed = false, + }); + + final String txHash; + final int currentChainTip; + final bool mempoolAPIEnabled; + final int? id; + final bool completed; + + @override + final String method = ElectrumWorkerMethods.txHex.method; + + @override + factory ElectrumWorkerTxHexRequest.fromJson(Map<String, dynamic> json) { + return ElectrumWorkerTxHexRequest( + txHash: json['txHash'] as String, + currentChainTip: json['currentChainTip'] as int, + mempoolAPIEnabled: json['mempoolAPIEnabled'] as bool, + id: json['id'] as int?, + completed: json['completed'] as bool? ?? false, + ); + } + + @override + Map<String, dynamic> toJson() { + return { + 'method': method, + 'id': id, + 'completed': completed, + 'txHash': txHash, + 'currentChainTip': currentChainTip, + 'mempoolAPIEnabled': mempoolAPIEnabled, + }; + } +} + +class ElectrumWorkerTxHexError extends ElectrumWorkerErrorResponse { + ElectrumWorkerTxHexError({ + required String error, + super.id, + }) : super(error: error); + + @override + String get method => ElectrumWorkerMethods.txHex.method; +} + +class ElectrumWorkerTxHexResponse extends ElectrumWorkerResponse<String, String> { + ElectrumWorkerTxHexResponse({ + required String hex, + super.error, + super.id, + super.completed, + }) : super(result: hex, method: ElectrumWorkerMethods.txHex.method); + + @override + String resultJson(result) { + return result; + } + + @override + factory ElectrumWorkerTxHexResponse.fromJson(Map<String, dynamic> json) { + return ElectrumWorkerTxHexResponse( + hex: json['result'] as String, + error: json['error'] as String?, + id: json['id'] as int?, + completed: json['completed'] as bool? ?? false, + ); + } +} diff --git a/cw_bitcoin/lib/electrum_worker/methods/methods.dart b/cw_bitcoin/lib/electrum_worker/methods/methods.dart index c117c45d3..2e53556ef 100644 --- a/cw_bitcoin/lib/electrum_worker/methods/methods.dart +++ b/cw_bitcoin/lib/electrum_worker/methods/methods.dart @@ -16,6 +16,7 @@ part 'scripthashes_subscribe.dart'; part 'get_balance.dart'; part 'get_history.dart'; part 'get_tx_expanded.dart'; +part 'get_tx_hex.dart'; part 'broadcast.dart'; part 'list_unspent.dart'; part 'tweaks_subscribe.dart'; diff --git a/cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart b/cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart index 3eeb015e7..03294a281 100644 --- a/cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart +++ b/cw_bitcoin/lib/electrum_worker/methods/tweaks_subscribe.dart @@ -143,14 +143,21 @@ class TweaksSyncResponse { int? height; SyncStatus? syncStatus; Map<String, TweakResponseData>? transactions = {}; + final bool wasSingleBlock; - TweaksSyncResponse({this.height, this.syncStatus, this.transactions}); + TweaksSyncResponse({ + required this.wasSingleBlock, + this.height, + this.syncStatus, + this.transactions, + }); Map<String, dynamic> toJson() { return { 'height': height, 'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!), 'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())), + 'wasSingleBlock': wasSingleBlock, }; } @@ -168,6 +175,7 @@ class TweaksSyncResponse { TweakResponseData.fromJson(value as Map<String, dynamic>), ), ), + wasSingleBlock: json['wasSingleBlock'] as bool? ?? false, ); } } diff --git a/cw_bitcoin/lib/electrum_worker/methods/version.dart b/cw_bitcoin/lib/electrum_worker/methods/version.dart index 2c20aab36..b6c3cc9a8 100644 --- a/cw_bitcoin/lib/electrum_worker/methods/version.dart +++ b/cw_bitcoin/lib/electrum_worker/methods/version.dart @@ -40,7 +40,7 @@ class ElectrumWorkerGetVersionError extends ElectrumWorkerErrorResponse { String get method => ElectrumRequestMethods.version.method; } -class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<String>, List<String>> { +class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<String, String> { ElectrumWorkerGetVersionResponse({ required super.result, super.error, @@ -49,14 +49,14 @@ class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<Strin }) : super(method: ElectrumRequestMethods.version.method); @override - List<String> resultJson(result) { - return result; + String resultJson(result) { + return result.toString(); } @override factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) { return ElectrumWorkerGetVersionResponse( - result: json['result'] as List<String>, + result: json['result'] as String, error: json['error'] as String?, id: json['id'] as int?, completed: json['completed'] as bool? ?? false, diff --git a/cw_bitcoin/lib/electrum_worker/server_capability.dart b/cw_bitcoin/lib/electrum_worker/server_capability.dart index 038c85755..ac395a2bf 100644 --- a/cw_bitcoin/lib/electrum_worker/server_capability.dart +++ b/cw_bitcoin/lib/electrum_worker/server_capability.dart @@ -7,15 +7,24 @@ class ServerCapability { bool supportsBatching; bool supportsTxVerbose; + String version; - ServerCapability({required this.supportsBatching, required this.supportsTxVerbose}); + ServerCapability({ + required this.supportsBatching, + required this.supportsTxVerbose, + required this.version, + }); static ServerCapability fromVersion(List<String> serverVersion) { if (serverVersion.isNotEmpty) { final server = serverVersion.first.toLowerCase(); if (server.contains('electrumx')) { - return ServerCapability(supportsBatching: true, supportsTxVerbose: true); + return ServerCapability( + supportsBatching: true, + supportsTxVerbose: true, + version: server, + ); } if (server.startsWith('electrs/')) { @@ -28,13 +37,21 @@ class ServerCapability { try { final version = ElectrumVersion.fromStr(electrsVersion); if (version.compareTo(ELECTRS_MIN_BATCHING_VERSION) >= 0) { - return ServerCapability(supportsBatching: true, supportsTxVerbose: false); + return ServerCapability( + supportsBatching: true, + supportsTxVerbose: false, + version: server, + ); } } catch (e) { // ignore version parsing errors } - return ServerCapability(supportsBatching: false, supportsTxVerbose: false); + return ServerCapability( + supportsBatching: false, + supportsTxVerbose: false, + version: server, + ); } if (server.startsWith('fulcrum')) { @@ -43,7 +60,11 @@ class ServerCapability { try { final version = ElectrumVersion.fromStr(fulcrumVersion); if (version.compareTo(FULCRUM_MIN_BATCHING_VERSION) >= 0) { - return ServerCapability(supportsBatching: true, supportsTxVerbose: true); + return ServerCapability( + supportsBatching: true, + supportsTxVerbose: true, + version: server, + ); } } catch (e) {} } @@ -59,7 +80,11 @@ class ServerCapability { try { final version = ElectrumVersion.fromStr(mempoolElectrsVersion); if (version.compareTo(MEMPOOL_ELECTRS_MIN_BATCHING_VERSION) > 0) { - return ServerCapability(supportsBatching: true, supportsTxVerbose: false); + return ServerCapability( + supportsBatching: true, + supportsTxVerbose: false, + version: server, + ); } } catch (e) { // ignore version parsing errors @@ -67,6 +92,10 @@ class ServerCapability { } } - return ServerCapability(supportsBatching: false, supportsTxVerbose: false); + return ServerCapability( + supportsBatching: false, + supportsTxVerbose: false, + version: "unknown", + ); } } diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index d9714147a..8d1dfe926 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -72,13 +72,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { mwebEnabled = alwaysScan ?? false; if (walletAddressesSnapshot != null) { - // walletAddresses = LitecoinWalletAddresses.fromJson( - // walletAddressesSnapshot, - // walletInfo, - // network: network, - // isHardwareWallet: isHardwareWallet, - // hdWallets: hdWallets, - // ); + walletAddresses = LitecoinWalletAddressesBase.fromJson( + walletAddressesSnapshot, + walletInfo, + network: network, + isHardwareWallet: isHardwareWallet, + hdWallets: hdWallets, + ); } else { walletAddresses = LitecoinWalletAddresses( walletInfo, @@ -186,7 +186,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { snp = await LitecoinWalletSnapshot.load( encryptionFileUtils, name, - walletInfo.type, + walletInfo, password, LitecoinNetwork.mainnet, ); @@ -347,6 +347,19 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { // if the confirmations haven't changed, skip updating: if (tx.confirmations == confirmations) continue; + // if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin): + if (confirmations >= 2 && tx.direction == TransactionDirection.outgoing) { + for (var coin in unspentCoins) { + if (tx.inputAddresses?.contains(coin.address) ?? false) { + final utxo = mwebUtxosBox.get(coin.address); + if (utxo != null) { + printV("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + await mwebUtxosBox.delete(coin.address); + } + } + } + } + tx.confirmations = confirmations; tx.isPending = false; transactionHistory.addOne(tx); @@ -786,84 +799,85 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { } // @override - // Future<ElectrumBalance> fetchBalances() async { - // final balance = await super.fetchBalances(); + Future<void> updateBalance([Set<String>? scripthashes, bool? wait]) async { + await super.updateBalance(scripthashes, true); + final balance = this.balance[currency]!; - // if (!mwebEnabled) { - // return balance; - // } + if (!mwebEnabled) { + return; + } - // // update unspent balances: - // await updateUnspent(); + // update unspent balances: + await updateUnspent(); - // int confirmed = balance.confirmed; - // int unconfirmed = balance.unconfirmed; - // int confirmedMweb = 0; - // int unconfirmedMweb = 0; - // try { - // mwebUtxosBox.values.forEach((utxo) { - // if (utxo.height > 0) { - // confirmedMweb += utxo.value.toInt(); - // } else { - // unconfirmedMweb += utxo.value.toInt(); - // } - // }); - // if (unconfirmedMweb > 0) { - // unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb); - // } - // } catch (_) {} + int confirmed = balance.confirmed; + int unconfirmed = balance.unconfirmed; + int confirmedMweb = 0; + int unconfirmedMweb = 0; + try { + mwebUtxosBox.values.forEach((utxo) { + if (utxo.height > 0) { + confirmedMweb += utxo.value.toInt(); + } else { + unconfirmedMweb += utxo.value.toInt(); + } + }); + if (unconfirmedMweb > 0) { + unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb); + } + } catch (_) {} - // for (var addressRecord in walletAddresses.allAddresses) { - // addressRecord.balance = 0; - // addressRecord.txCount = 0; - // } + for (var addressRecord in walletAddresses.allAddresses) { + addressRecord.balance = 0; + addressRecord.txCount = 0; + } - // unspentCoins.forEach((coin) { - // final coinInfoList = unspentCoinsInfo.values.where( - // (element) => - // element.walletId.contains(id) && - // element.hash.contains(coin.hash) && - // element.vout == coin.vout, - // ); + unspentCoins.forEach((coin) { + final coinInfoList = unspentCoinsInfo.values.where( + (element) => + element.walletId.contains(id) && + element.hash.contains(coin.hash) && + element.vout == coin.vout, + ); - // if (coinInfoList.isNotEmpty) { - // final coinInfo = coinInfoList.first; + if (coinInfoList.isNotEmpty) { + final coinInfo = coinInfoList.first; - // coin.isFrozen = coinInfo.isFrozen; - // coin.isSending = coinInfo.isSending; - // coin.note = coinInfo.note; - // coin.bitcoinAddressRecord.balance += coinInfo.value; - // } else { - // super.addCoinInfo(coin); - // } - // }); + coin.isFrozen = coinInfo.isFrozen; + coin.isSending = coinInfo.isSending; + coin.note = coinInfo.note; + coin.bitcoinAddressRecord.balance += coinInfo.value; + } else { + super.addCoinInfo(coin); + } + }); - // // update the txCount for each address using the tx history, since we can't rely on mwebd - // // to have an accurate count, we should just keep it in sync with what we know from the tx history: - // for (final tx in transactionHistory.transactions.values) { - // // if (tx.isPending) continue; - // if (tx.inputAddresses == null || tx.outputAddresses == null) { - // continue; - // } - // final txAddresses = tx.inputAddresses! + tx.outputAddresses!; - // for (final address in txAddresses) { - // final addressRecord = walletAddresses.allAddresses - // .firstWhereOrNull((addressRecord) => addressRecord.address == address); - // if (addressRecord == null) { - // continue; - // } - // addressRecord.txCount++; - // } - // } + // update the txCount for each address using the tx history, since we can't rely on mwebd + // to have an accurate count, we should just keep it in sync with what we know from the tx history: + for (final tx in transactionHistory.transactions.values) { + // if (tx.isPending) continue; + if (tx.inputAddresses == null || tx.outputAddresses == null) { + continue; + } + final txAddresses = tx.inputAddresses! + tx.outputAddresses!; + for (final address in txAddresses) { + final addressRecord = walletAddresses.allAddresses + .firstWhereOrNull((addressRecord) => addressRecord.address == address); + if (addressRecord == null) { + continue; + } + addressRecord.txCount++; + } + } - // return ElectrumBalance( - // confirmed: confirmed, - // unconfirmed: unconfirmed, - // frozen: balance.frozen, - // secondConfirmed: confirmedMweb, - // secondUnconfirmed: unconfirmedMweb, - // ); - // } + this.balance[currency] = ElectrumBalance( + confirmed: confirmed, + unconfirmed: unconfirmed, + frozen: balance.frozen, + secondConfirmed: confirmedMweb, + secondUnconfirmed: unconfirmedMweb, + ); + } @override ElectrumTxCreateUtxoDetails createUTXOS({ diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart index 02dce4d5d..1faa8eeb5 100644 --- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart @@ -72,8 +72,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with ? ElectrumWalletAddressesBase.defaultChangeAddressesCount : ElectrumWalletAddressesBase.defaultReceiveAddressesCount; - final startIndex = (isChange ? changeAddresses : receiveAddresses) - .where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType) + final startIndex = getAddressesByType(addressType, isChange) + .where((addr) => (addr as BitcoinAddressRecord).cwDerivationType == derivationType) .length; final mwebAddresses = <LitecoinMWEBAddressRecord>[]; @@ -268,7 +268,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with @override String get addressForExchange { // don't use mweb addresses for exchange refund address: - final addresses = receiveAddresses + final addresses = selectedReceiveAddresses .where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed); return addresses.first.address; } @@ -329,6 +329,64 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with addressesSet.addAll(addresses); this.mwebAddresses.clear(); this.mwebAddresses.addAll(addressesSet); - updateAddressesOnReceiveScreen(); + updateAddressesByType(); + } + + Map<String, dynamic> toJson() { + final json = super.toJson(); + json['mwebAddresses'] = mwebAddresses.map((address) => address.toJSON()).toList(); + // json['mwebAddressIndex'] = + return json; + } + + static Map<String, dynamic> fromSnapshot(Map<dynamic, dynamic> data) { + final electrumSnapshot = ElectrumWalletAddressesBase.fromSnapshot(data); + + final mwebAddresses = data['mweb_addresses'] as List? ?? + <Object>[].map((e) => LitecoinMWEBAddressRecord.fromJSON(e as String)).toList(); + + // var mwebAddressIndex = 0; + + // try { + // mwebAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0'); + // } catch (_) {} + + return { + 'allAddresses': electrumSnapshot["addresses"], + 'addressPageType': data['address_page_type'] as String?, + 'receiveAddressIndexByType': electrumSnapshot["receiveAddressIndexByType"], + 'changeAddressIndexByType': electrumSnapshot["changeAddressIndexByType"], + 'mwebAddresses': mwebAddresses, + }; + } + + static LitecoinWalletAddressesBase fromJson( + Map<String, dynamic> json, + WalletInfo walletInfo, { + required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets, + required BasedUtxoNetwork network, + required bool isHardwareWallet, + List<BitcoinAddressRecord>? initialAddresses, + List<LitecoinMWEBAddressRecord>? initialMwebAddresses, + }) { + initialAddresses ??= (json['allAddresses'] as List) + .map((record) => BitcoinAddressRecord.fromJSON(record as String)) + .toList(); + + initialMwebAddresses ??= (json['mwebAddresses'] as List) + .map( + (address) => LitecoinMWEBAddressRecord.fromJSON(address as String), + ) + .toList(); + + return LitecoinWalletAddresses( + walletInfo, + hdWallets: hdWallets, + network: network, + isHardwareWallet: isHardwareWallet, + initialAddresses: initialAddresses, + initialMwebAddresses: initialMwebAddresses, + mwebEnabled: true, // TODO + ); } } diff --git a/cw_bitcoin/lib/litecoin_wallet_snapshot.dart b/cw_bitcoin/lib/litecoin_wallet_snapshot.dart index c6e31624d..b051e6b4b 100644 --- a/cw_bitcoin/lib/litecoin_wallet_snapshot.dart +++ b/cw_bitcoin/lib/litecoin_wallet_snapshot.dart @@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/pathForWallet.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/wallet_info.dart'; class LitecoinWalletSnapshot extends ElectrumWalletSnapshot { LitecoinWalletSnapshot({ @@ -29,10 +29,11 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot { static Future<LitecoinWalletSnapshot> load( EncryptionFileUtils encryptionFileUtils, String name, - WalletType type, + WalletInfo walletInfo, String password, BasedUtxoNetwork network, ) async { + final type = walletInfo.type; final path = await pathForWallet(name: name, type: type); final jsonSource = await encryptionFileUtils.read(path: path, password: password); final data = json.decode(jsonSource) as Map; @@ -40,7 +41,7 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot { final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load( encryptionFileUtils, name, - type, + walletInfo, password, network, ); diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 7402beac9..91ac7a90e 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -121,7 +121,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { snp = await ElectrumWalletSnapshot.load( encryptionFileUtils, name, - walletInfo.type, + walletInfo, password, BitcoinCashNetwork.mainnet, ); diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index d6bf07588..f534128bf 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -23,6 +23,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi @override Future<void> init() async { await generateInitialAddresses(type: P2pkhAddressType.p2pkh); + allAddresses.forEach((addr) { + print(addr.address); + }); await super.init(); } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 40dc828ef..46b8d0d6f 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -153,7 +153,10 @@ class CWBitcoin extends Bitcoin { @computed List<ElectrumSubAddress> getSubAddresses(Object wallet) { final electrumWallet = wallet as ElectrumWallet; - return electrumWallet.walletAddresses.addressesOnReceiveScreen + return [ + ...electrumWallet.walletAddresses.selectedReceiveAddresses, + ...electrumWallet.walletAddresses.selectedChangeAddresses + ] .map( (addr) => ElectrumSubAddress( id: addr.index, diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 4035dbfdd..671d8c699 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -9,7 +9,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; -List<TransactionPriority> priorityForWalletType(WalletBase wallet) { +List<TransactionPriority> priorityForWallet(WalletBase wallet) { switch (wallet.type) { case WalletType.monero: return monero!.getTransactionPriorities(); diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index bae9a972a..7e3c2b555 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -1,13 +1,27 @@ +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart'; import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart'; import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; +import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/share_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; @@ -102,13 +116,13 @@ class ReceivePage extends BasePage { Padding( padding: EdgeInsets.fromLTRB(24, 50, 24, 24), child: QRWidget( - addressListViewModel: addressListViewModel, - formKey: _formKey, - heroTag: _heroTag, - amountTextFieldFocusNode: _cryptoAmountFocus, - amountController: _amountController, - isLight: currentTheme.type == ThemeType.light, - ), + addressListViewModel: addressListViewModel, + formKey: _formKey, + heroTag: _heroTag, + amountTextFieldFocusNode: _cryptoAmountFocus, + amountController: _amountController, + isLight: currentTheme.type == ThemeType.light, + ), ), AddressList(addressListViewModel: addressListViewModel), Padding( diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index f6b189ac8..af13a25a4 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -529,7 +529,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S } Future<void> pickTransactionPriority(BuildContext context) async { - final items = priorityForWalletType(sendViewModel.wallet); + final items = priorityForWallet(sendViewModel.wallet); final selectedItem = items.indexOf(sendViewModel.transactionPriority); final customItemIndex = sendViewModel.getCustomPriorityIndex(items); final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin; diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index c9d32d19a..50d294d5a 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -37,7 +37,7 @@ class OtherSettingsPage extends BasePage { _otherSettingsViewModel.walletType == WalletType.bitcoin ? SettingsPriorityPickerCell( title: S.current.settings_fee_priority, - items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet), + items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet), displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, selectedItem: _otherSettingsViewModel.transactionPriority, customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, @@ -47,7 +47,7 @@ class OtherSettingsPage extends BasePage { ) : SettingsPickerCell( title: S.current.settings_fee_priority, - items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet), + items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet), displayItem: _otherSettingsViewModel.getDisplayPriority, selectedItem: _otherSettingsViewModel.transactionPriority, onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 653a9c615..59c995bad 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -92,7 +92,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor setTransactionPriority(bitcoinTransactionPriorityMedium); } final priority = _settingsStore.priority[wallet.type]; - final priorities = priorityForWalletType(wallet); + final priorities = priorityForWallet(wallet); if (priorities.isNotEmpty && !priorities.contains(priority)) { _settingsStore.priority[wallet.type] = priorities.first; } diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 7437ea71e..949abb826 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -24,7 +24,7 @@ abstract class OtherSettingsViewModelBase with Store { .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); final priority = _settingsStore.priority[_wallet.type]; - final priorities = priorityForWalletType(_wallet); + final priorities = priorityForWallet(_wallet); if (!priorities.contains(priority) && priorities.isNotEmpty) { _settingsStore.priority[_wallet.type] = priorities.first; @@ -103,7 +103,7 @@ abstract class OtherSettingsViewModelBase with Store { double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble(); int? get customPriorityItemIndex { - final priorities = priorityForWalletType(_wallet); + final priorities = priorityForWallet(_wallet); final customItem = priorities .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); return customItem != null ? priorities.indexOf(customItem) : null; diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 1680e4d7a..4fa5b4742 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -566,7 +566,7 @@ abstract class TransactionDetailsViewModelBase with Store { StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte')); } - final priorities = priorityForWalletType(wallet); + final priorities = priorityForWallet(wallet); final selectedItem = priorities.indexOf(sendViewModel.transactionPriority); final customItem = priorities.firstWhereOrNull( (element) => element.title == sendViewModel.bitcoinTransactionPriorityCustom.title);