feat: misc reviews

This commit is contained in:
Rafael Saes 2025-01-24 13:04:36 -03:00
parent 7da30d85b7
commit 309dca9ac9
29 changed files with 568 additions and 338 deletions

View file

@ -4,6 +4,7 @@ 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_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
abstract class BaseBitcoinAddressRecord { abstract class BaseBitcoinAddressRecord {
BaseBitcoinAddressRecord( BaseBitcoinAddressRecord(
@ -20,16 +21,15 @@ abstract class BaseBitcoinAddressRecord {
_balance = balance, _balance = balance,
_name = name, _name = name,
_isUsed = isUsed, _isUsed = isUsed,
_isHidden = isHidden ?? isChange, isHidden = isHidden ?? isChange,
_isChange = isChange; _isChange = isChange;
@override @override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address; bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
final String address; final String address;
bool _isHidden; bool isHidden;
bool get isHidden => _isHidden;
final bool _isChange; final bool _isChange;
bool get isChange => _isChange; bool get isChange => _isChange;
@ -53,7 +53,7 @@ abstract class BaseBitcoinAddressRecord {
void setAsUsed() { void setAsUsed() {
_isUsed = true; _isUsed = true;
_isHidden = true; isHidden = true;
} }
void setNewName(String label) => _name = label; void setNewName(String label) => _name = label;
@ -75,11 +75,15 @@ abstract class BaseBitcoinAddressRecord {
'runtimeType': runtimeType.toString(), 'runtimeType': runtimeType.toString(),
}); });
static BaseBitcoinAddressRecord fromJSON(String jsonSource) { static BaseBitcoinAddressRecord fromJSON(
String jsonSource, [
DerivationInfo? derivationInfo,
BasedUtxoNetwork? network,
]) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
if (decoded['runtimeType'] == 'BitcoinAddressRecord') { if (decoded['runtimeType'] == 'BitcoinAddressRecord') {
return BitcoinAddressRecord.fromJSON(jsonSource); return BitcoinAddressRecord.fromJSON(jsonSource, derivationInfo, network);
} else if (decoded['runtimeType'] == 'BitcoinSilentPaymentAddressRecord') { } else if (decoded['runtimeType'] == 'BitcoinSilentPaymentAddressRecord') {
return BitcoinSilentPaymentAddressRecord.fromJSON(jsonSource); return BitcoinSilentPaymentAddressRecord.fromJSON(jsonSource);
} else if (decoded['runtimeType'] == 'BitcoinReceivedSPAddressRecord') { } else if (decoded['runtimeType'] == 'BitcoinReceivedSPAddressRecord') {
@ -87,7 +91,7 @@ abstract class BaseBitcoinAddressRecord {
} else if (decoded['runtimeType'] == 'LitecoinMWEBAddressRecord') { } else if (decoded['runtimeType'] == 'LitecoinMWEBAddressRecord') {
return LitecoinMWEBAddressRecord.fromJSON(jsonSource); return LitecoinMWEBAddressRecord.fromJSON(jsonSource);
} else { } 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 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( return BitcoinAddressRecord(
decoded['address'] as String, decoded['address'] as String,
index: decoded['index'] as int, index: decoded['index'] as int,
derivationInfo: BitcoinDerivationInfo.fromJSON( derivationInfo: derivationInfoSnp == null
decoded['derivationInfo'] as Map<String, dynamic>, ? [CWBitcoinDerivationType.bip39, CWBitcoinDerivationType.old_bip39]
), .contains(cwDerivationType)
// TODO: make nullable maybe? ? BitcoinDerivationInfo.fromDerivationAndAddress(
cwDerivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int], BitcoinDerivationType.bip39,
decoded['address'] as String,
network!,
)
: BitcoinDerivationInfos.ELECTRUM
: BitcoinDerivationInfo.fromJSON(derivationInfoSnp),
cwDerivationType: cwDerivationType,
isHidden: decoded['isHidden'] as bool? ?? false, isHidden: decoded['isHidden'] as bool? ?? false,
isChange: decoded['isChange'] as bool? ?? false, isChange: decoded['isChange'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false, isUsed: decoded['isUsed'] as bool? ?? false,
@ -218,7 +239,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
return BitcoinSilentPaymentAddressRecord( return BitcoinSilentPaymentAddressRecord(
decoded['address'] as String, decoded['address'] as String,
derivationPath: derivationPath:
decoded['derivationPath'] as String? ?? BitcoinWalletAddressesBase.OLD_SP_SPEND_PATH, (decoded['derivationPath'] as String?) ?? BitcoinWalletAddressesBase.OLD_SP_PATH,
labelIndex: decoded['index'] as int, labelIndex: decoded['index'] as int,
isUsed: decoded['isUsed'] as bool? ?? false, isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0, txCount: decoded['txCount'] as int? ?? 0,

View file

@ -1,6 +1,7 @@
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/wallet_info.dart';
class BitcoinUnspent extends Unspent { class BitcoinUnspent extends Unspent {
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout) BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
@ -10,9 +11,19 @@ class BitcoinUnspent extends Unspent {
factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) => factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) =>
BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout); 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( 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, json['tx_hash'] as String,
int.parse(json['value'].toString()), int.parse(json['value'].toString()),
int.parse(json['tx_pos'].toString()), int.parse(json['tx_pos'].toString()),

View file

@ -144,7 +144,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
snp = await BitcoinWalletSnapshot.load( snp = await BitcoinWalletSnapshot.load(
encryptionFileUtils, encryptionFileUtils,
name, name,
walletInfo.type, walletInfo,
password, password,
network, network,
); );
@ -209,27 +209,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final isNamedElectrs = node?.uri.host.contains("electrs") ?? false; final isNamedElectrs = node?.uri.host.contains("electrs") ?? false;
if (isNamedElectrs) { if (isNamedElectrs) {
node!.isElectrs = true; node!.isElectrs = true;
node!.save();
return true;
} }
final isNamedFulcrum = node!.uri.host.contains("fulcrum"); final isNamedFulcrum = node!.uri.host.contains("fulcrum");
if (isNamedFulcrum) { if (isNamedFulcrum) {
node!.isElectrs = false; 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) { if (version is List<String> && version.isNotEmpty) {
final server = version[0]; final server = version[0];
if (server.toLowerCase().contains('electrs')) { if (server.toLowerCase().contains('electrs')) {
node!.isElectrs = true;
}
} else if (version is String && version.toLowerCase().contains('electrs')) {
node!.isElectrs = true; 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(); node!.save();
@ -293,8 +295,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[]; final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) { for (final utxo in utxos) {
final rawTx = final rawTx = await getTransactionHex(hash: utxo.utxo.txHash);
(await getTransactionExpanded(hash: utxo.utxo.txHash)).originalTransaction.toHex();
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress( psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
@ -373,7 +374,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
} }
@action @action
Future<void> setSilentPaymentsScanning(bool active) async { Future<void> setSilentPaymentsScanning(bool active, [int? height, bool? doSingleScan]) async {
silentPaymentsScanningActive = active; silentPaymentsScanningActive = active;
final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments(); final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments();
final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning; final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning;
@ -382,14 +383,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
syncStatus = AttemptingScanSyncStatus(); syncStatus = AttemptingScanSyncStatus();
final tip = currentChainTip!; final tip = currentChainTip!;
final beginHeight = height ?? walletInfo.restoreHeight;
if (tip == walletInfo.restoreHeight) { if (tip == beginHeight) {
syncStatus = SyncedTipSyncStatus(tip); syncStatus = SyncedTipSyncStatus(tip);
return; return;
} }
if (tip > walletInfo.restoreHeight) { if (tip > beginHeight) {
_setListeners(walletInfo.restoreHeight); _requestTweakScanning(beginHeight, doSingleScan: doSingleScan);
} }
} else if (syncStatus is! SyncedSyncStatus) { } else if (syncStatus is! SyncedSyncStatus) {
await waitSendWorker(ElectrumWorkerStopScanningRequest()); 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 @action
@override @override
Future<void> rescan({required int height, bool? doSingleScan}) async { Future<void> rescan({required int height, bool? doSingleScan}) async {
silentPaymentsScanningActive = true; setSilentPaymentsScanning(true, height, doSingleScan);
_setListeners(height, doSingleScan: doSingleScan);
} }
@action @action
@ -645,14 +634,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
} }
final height = result.height; final height = result.height;
if (height != null) { if (height != null && result.wasSingleBlock == false) {
await walletInfo.updateRestoreHeight(height); await walletInfo.updateRestoreHeight(height);
} }
} }
} }
@action @action
Future<void> _setListeners(int height, {bool? doSingleScan}) async { Future<void> _requestTweakScanning(int height, {bool? doSingleScan}) async {
if (currentChainTip == null) { if (currentChainTip == null) {
throw Exception("currentChainTip is null"); throw Exception("currentChainTip is null");
} }
@ -763,7 +752,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
// New headers received, start scanning // New headers received, start scanning
if (alwaysScan == true && syncStatus is SyncedSyncStatus) { if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
_setListeners(walletInfo.restoreHeight); _requestTweakScanning(walletInfo.restoreHeight);
} }
} }

View file

@ -31,7 +31,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
silentPaymentWallets = [silentPaymentWallet!]; 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 = [ static const BITCOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh, SegwitAddressType.p2wpkh,
P2pkhAddressType.p2pkh, P2pkhAddressType.p2pkh,
@ -74,8 +74,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
if (silentPaymentAddresses.isEmpty) { if (silentPaymentAddresses.isEmpty) {
if (walletInfo.isRecovery) { if (walletInfo.isRecovery) {
final oldScanPath = Bip32PathParser.parse("m/352'/1'/0'/1'/0"); final oldScanPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "1"));
final oldSpendPath = Bip32PathParser.parse("m/352'/1'/0'/0'/0"); final oldSpendPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "0"));
final oldSilentPaymentWallet = SilentPaymentOwner.fromPrivateKeys( final oldSilentPaymentWallet = SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate(hdWallet.derive(oldScanPath).privateKey), b_scan: ECPrivate(hdWallet.derive(oldScanPath).privateKey),
@ -315,7 +315,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
final usableSilentPaymentAddresses = silentPaymentAddresses final usableSilentPaymentAddresses = silentPaymentAddresses
.where((a) => .where((a) =>
a.type != SegwitAddressType.p2tr && a.type != SegwitAddressType.p2tr &&
a.derivationPath != OLD_SP_SPEND_PATH && a.derivationPath != OLD_SP_PATH &&
a.isChange == false) a.isChange == false)
.toList(); .toList();
final nextSPLabelIndex = usableSilentPaymentAddresses.length; final nextSPLabelIndex = usableSilentPaymentAddresses.length;
@ -330,7 +330,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
); );
silentPaymentAddresses.add(address); silentPaymentAddresses.add(address);
updateAddressesOnReceiveScreen(); updateAddressesByType();
return address; return address;
} }
@ -382,14 +382,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
@override @override
@action @action
void updateAddressesOnReceiveScreen() { void updateAddressesByType() {
if (addressPageType == SilentPaymentsAddresType.p2sp) { if (addressPageType == SilentPaymentsAddresType.p2sp) {
addressesOnReceiveScreen.clear(); receiveAddressesByType.clear();
addressesOnReceiveScreen.addAll(silentPaymentAddresses); receiveAddressesByType[SilentPaymentsAddresType.p2sp] = silentPaymentAddresses
.where((addressRecord) =>
addressRecord.type == SilentPaymentsAddresType.p2sp && !addressRecord.isChange)
.toList();
return; return;
} }
super.updateAddressesOnReceiveScreen(); super.updateAddressesByType();
} }
@action @action
@ -398,7 +401,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
addressesSet.addAll(addresses); addressesSet.addAll(addresses);
this.silentPaymentAddresses.clear(); this.silentPaymentAddresses.clear();
this.silentPaymentAddresses.addAll(addressesSet); this.silentPaymentAddresses.addAll(addressesSet);
updateAddressesOnReceiveScreen(); updateAddressesByType();
} }
@action @action
@ -407,7 +410,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
addressesSet.addAll(addresses); addressesSet.addAll(addresses);
this.receivedSPAddresses.clear(); this.receivedSPAddresses.clear();
this.receivedSPAddresses.addAll(addressesSet); this.receivedSPAddresses.addAll(addressesSet);
updateAddressesOnReceiveScreen(); updateAddressesByType();
} }
@action @action
@ -416,7 +419,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address); addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
silentPaymentAddresses.remove(addressRecord); silentPaymentAddresses.remove(addressRecord);
updateAddressesOnReceiveScreen(); updateAddressesByType();
} }
Map<String, String> get labels { Map<String, String> get labels {
@ -492,6 +495,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets, required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets,
required BasedUtxoNetwork network, required BasedUtxoNetwork network,
required bool isHardwareWallet, required bool isHardwareWallet,
// TODO: make it used
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses, List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,

View file

@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.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 { class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
BitcoinWalletSnapshot({ BitcoinWalletSnapshot({
@ -27,10 +27,11 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
static Future<BitcoinWalletSnapshot> load( static Future<BitcoinWalletSnapshot> load(
EncryptionFileUtils encryptionFileUtils, EncryptionFileUtils encryptionFileUtils,
String name, String name,
WalletType type, WalletInfo walletInfo,
String password, String password,
BasedUtxoNetwork network, BasedUtxoNetwork network,
) async { ) async {
final type = walletInfo.type;
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await encryptionFileUtils.read(path: path, password: password); final jsonSource = await encryptionFileUtils.read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
@ -38,7 +39,7 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load( final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
encryptionFileUtils, encryptionFileUtils,
name, name,
type, walletInfo,
password, password,
network, network,
); );

View file

@ -160,6 +160,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
List<String> inputAddresses = []; List<String> inputAddresses = [];
List<String> outputAddresses = []; List<String> outputAddresses = [];
final sentAmounts = <int>[];
if (bundle.ins.length > 0) { if (bundle.ins.length > 0) {
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.inputs[i]; final input = bundle.originalTransaction.inputs[i];
@ -167,11 +168,13 @@ class ElectrumTransactionInfo extends TransactionInfo {
final outTransaction = inputTransaction.outputs[input.txIndex]; final outTransaction = inputTransaction.outputs[input.txIndex];
inputAmount += outTransaction.amount.toInt(); inputAmount += outTransaction.amount.toInt();
if (addresses.contains( if (addresses.contains(
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network))) { BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network),
)) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
inputAddresses.add( inputAddresses.add(
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network), BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network),
); );
sentAmounts.add(outTransaction.amount.toInt());
} }
} }
} }
@ -214,6 +217,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
// Self-send // Self-send
direction = TransactionDirection.incoming; direction = TransactionDirection.incoming;
amount = receivedAmounts.reduce((a, b) => a + b); amount = receivedAmounts.reduce((a, b) => a + b);
} else if (sentAmounts.length > 0) {
amount = sentAmounts.reduce((a, b) => a + b);
} }
final fee = inputAmount - totalOutAmount; final fee = inputAmount - totalOutAmount;

View file

@ -186,8 +186,7 @@ abstract class ElectrumWalletBase
onBalanceResponse(response.result); onBalanceResponse(response.result);
break; break;
case ElectrumRequestMethods.getHistoryMethod: case ElectrumRequestMethods.getHistoryMethod:
final response = ElectrumWorkerGetHistoryResponse.fromJson(messageJson); onHistoriesResponse(ElectrumWorkerGetHistoryResponse.fromJson(messageJson));
onHistoriesResponse(response.result);
break; break;
case ElectrumRequestMethods.listunspentMethod: case ElectrumRequestMethods.listunspentMethod:
final response = ElectrumWorkerListUnspentResponse.fromJson(messageJson); final response = ElectrumWorkerListUnspentResponse.fromJson(messageJson);
@ -1270,12 +1269,10 @@ abstract class ElectrumWalletBase
await Future.forEach(walletAddresses.allAddresses, (BitcoinAddressRecord addressRecord) async { await Future.forEach(walletAddresses.allAddresses, (BitcoinAddressRecord addressRecord) async {
final isChange = addressRecord.isChange; final isChange = addressRecord.isChange;
final matchingAddressList = final matchingAddressList = walletAddresses
(isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where( .getAddressesByType(addressRecord.type, isChange)
(element) => .where((element) =>
element.type == addressRecord.type && (element as BitcoinAddressRecord).cwDerivationType == addressRecord.cwDerivationType);
element.cwDerivationType == addressRecord.cwDerivationType,
);
final totalMatchingAddresses = matchingAddressList.length; final totalMatchingAddresses = matchingAddressList.length;
final matchingGapLimit = (isChange final matchingGapLimit = (isChange
@ -1305,11 +1302,11 @@ abstract class ElectrumWalletBase
walletAddresses.updateAdresses(newAddresses); walletAddresses.updateAdresses(newAddresses);
final newMatchingAddressList = final newMatchingAddressList =
(isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where( walletAddresses.getAddressesByType(addressRecord.type, isChange).where(
(element) => (element) =>
element.type == addressRecord.type && element.type == addressRecord.type &&
element.cwDerivationType == addressRecord.cwDerivationType, (element as BitcoinAddressRecord) == addressRecord.cwDerivationType,
); );
printV( printV(
"discovered ${newAddresses.length} new ${isChange ? "change" : "receive"} addresses"); "discovered ${newAddresses.length} new ${isChange ? "change" : "receive"} addresses");
printV( printV(
@ -1329,7 +1326,8 @@ abstract class ElectrumWalletBase
} }
@action @action
Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async { Future<void> onHistoriesResponse(ElectrumWorkerGetHistoryResponse response) async {
final histories = response.result;
if (histories.isNotEmpty) { if (histories.isNotEmpty) {
final addressesWithHistory = <BitcoinAddressRecord>[]; final addressesWithHistory = <BitcoinAddressRecord>[];
@ -1350,8 +1348,8 @@ abstract class ElectrumWalletBase
} }
await save(); await save();
} else { } else if (response.completed) {
// checkAddressesGap(); checkAddressesGap();
} }
} }
@ -1582,7 +1580,7 @@ abstract class ElectrumWalletBase
} }
// Identify all change outputs // Identify all change outputs
final changeAddresses = walletAddresses.changeAddresses; final changeAddresses = walletAddresses.allChangeAddresses;
final List<BitcoinOutput> changeOutputs = outputs final List<BitcoinOutput> changeOutputs = outputs
.where((output) => changeAddresses .where((output) => changeAddresses
.any((element) => element.address == output.address.toAddress(network))) .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 { Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
return await waitSendWorker( return await waitSendWorker(
ElectrumWorkerTxExpandedRequest(txHash: hash, currentChainTip: currentChainTip!), ElectrumWorkerTxExpandedRequest(txHash: hash, currentChainTip: currentChainTip!),

View file

@ -28,12 +28,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
}) : _allAddresses = ObservableList.of(initialAddresses ?? []), }) : _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 ?? {}, currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {}, currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
_addressPageType = initialAddressPageType ?? _addressPageType = initialAddressPageType ??
@ -46,10 +40,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const defaultChangeAddressesCount = 17; static const defaultChangeAddressesCount = 17;
static const gap = 20; static const gap = 20;
final walletAddressTypes = <BitcoinAddressType>[];
final ObservableList<BitcoinAddressRecord> _allAddresses; final ObservableList<BitcoinAddressRecord> _allAddresses;
final ObservableList<BaseBitcoinAddressRecord> addressesOnReceiveScreen; @observable
final ObservableList<BitcoinAddressRecord> receiveAddresses; Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> receiveAddressesByType = {};
final ObservableList<BitcoinAddressRecord> changeAddresses; @observable
Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> changeAddressesByType = {};
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets; final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
@ -61,6 +59,24 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
late BitcoinAddressType _addressPageType; 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 @computed
BitcoinAddressType get addressPageType => _addressPageType; BitcoinAddressType get addressPageType => _addressPageType;
@ -75,6 +91,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return _allAddresses.firstWhere((element) => element.address == address); return _allAddresses.firstWhere((element) => element.address == address);
} }
// TODO: toggle to switch
@observable @observable
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh; BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
@ -83,9 +100,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get address { String get address {
String receiveAddress = ""; 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) { typeMatchingReceiveAddresses.isEmpty) {
receiveAddress = generateNewAddress().address; receiveAddress = generateNewAddress().address;
} else { } else {
@ -94,7 +113,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (typeMatchingReceiveAddresses.isNotEmpty) { if (typeMatchingReceiveAddresses.isNotEmpty) {
if (previousAddressMatchesType && if (previousAddressMatchesType &&
typeMatchingReceiveAddresses.first.address != addressesOnReceiveScreen.first.address) { typeMatchingReceiveAddresses.first.address != selectedReceiveAddresses.first.address) {
receiveAddress = previousAddressRecord!.address; receiveAddress = previousAddressRecord!.address;
} else { } else {
receiveAddress = typeMatchingReceiveAddresses.first.address; receiveAddress = typeMatchingReceiveAddresses.first.address;
@ -143,22 +162,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
BitcoinAddressRecord? previousAddressRecord; 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() { CWBitcoinDerivationType getHDWalletType() {
if (hdWallets.containsKey(CWBitcoinDerivationType.bip39)) { if (hdWallets.containsKey(CWBitcoinDerivationType.bip39)) {
return CWBitcoinDerivationType.bip39; return CWBitcoinDerivationType.bip39;
@ -171,25 +174,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override @override
Future<void> init() async { Future<void> init() async {
updateAddressesOnReceiveScreen(); updateAddressesByType();
updateReceiveAddresses();
updateChangeAddresses();
updateHiddenAddresses(); updateHiddenAddresses();
await updateAddressesInBox(); await updateAddressesInBox();
} }
@action @action
Future<BaseBitcoinAddressRecord> getChangeAddress() async { Future<BaseBitcoinAddressRecord> getChangeAddress() async {
updateChangeAddresses(); final address = selectedChangeAddresses.firstWhere(
(addr) => _isUnusedChangeAddressByType(addr, changeAddressType),
final address = changeAddresses.firstWhere(
(addressRecord) => _isUnusedChangeAddressByType(addressRecord, changeAddressType),
); );
return address; return address;
} }
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
final newAddressIndex = addressesOnReceiveScreen.fold( final newAddressIndex = selectedReceiveAddresses.fold(
0, 0,
(int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc,
); );
@ -282,21 +281,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
@action @action
void updateAddressesOnReceiveScreen() { void updateAddressesByType() {
addressesOnReceiveScreen.clear(); receiveAddressesByType.clear();
addressesOnReceiveScreen.addAll(_allAddresses.where(_isAddressPageTypeMatch).toList()); walletAddressTypes.forEach((type) {
} receiveAddressesByType[type] =
_allAddresses.where((addr) => _isAddressByType(addr, type)).toList();
@action changeAddressesByType[type] =
void updateReceiveAddresses() { _allAddresses.where((addr) => _isAddressByType(addr, type)).toList();
receiveAddresses.clear(); });
receiveAddresses.addAll(_allAddresses.where((addressRecord) => !addressRecord.isChange));
}
@action
void updateChangeAddresses() {
changeAddresses.clear();
changeAddresses.addAll(_allAddresses.where((addressRecord) => addressRecord.isChange));
} }
@action @action
@ -310,8 +302,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
? ElectrumWalletAddressesBase.defaultChangeAddressesCount ? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount; : ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
final startIndex = (isChange ? changeAddresses : receiveAddresses) final startIndex = (isChange ? selectedChangeAddresses : selectedReceiveAddresses)
.where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType) .where((addr) =>
(addr as BitcoinAddressRecord).cwDerivationType == derivationType &&
addr.type == addressType)
.length; .length;
final newAddresses = <BitcoinAddressRecord>[]; final newAddresses = <BitcoinAddressRecord>[];
@ -420,9 +414,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (replacedAddresses.isNotEmpty) { if (replacedAddresses.isNotEmpty) {
_allAddresses.addAll(replacedAddresses); _allAddresses.addAll(replacedAddresses);
} else { } else {
updateAddressesOnReceiveScreen(); updateAddressesByType();
updateReceiveAddresses();
updateChangeAddresses();
} }
} }
@ -431,9 +423,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
this._allAddresses.addAll(addresses); this._allAddresses.addAll(addresses);
updateHiddenAddresses(); updateHiddenAddresses();
updateAddressesOnReceiveScreen(); updateAddressesByType();
updateReceiveAddresses();
updateChangeAddresses();
} }
@action @action
@ -447,18 +437,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
Future<void> setAddressType(BitcoinAddressType type) async { Future<void> setAddressType(BitcoinAddressType type) async {
_addressPageType = type; _addressPageType = type;
updateAddressesOnReceiveScreen(); updateAddressesByType();
walletInfo.addressPageType = addressPageType.toString(); walletInfo.addressPageType = addressPageType.toString();
await walletInfo.save(); await walletInfo.save();
} }
bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
return _isAddressByType(addressRecord, addressPageType);
}
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; 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; return addr.isChange && !addr.isUsed && addr.type == type;
} }

View file

@ -48,10 +48,11 @@ class ElectrumWalletSnapshot {
static Future<ElectrumWalletSnapshot> load( static Future<ElectrumWalletSnapshot> load(
EncryptionFileUtils encryptionFileUtils, EncryptionFileUtils encryptionFileUtils,
String name, String name,
WalletType type, WalletInfo walletInfo,
String password, String password,
BasedUtxoNetwork network, BasedUtxoNetwork network,
) async { ) async {
final type = walletInfo.type;
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await encryptionFileUtils.read(path: path, password: password); final jsonSource = await encryptionFileUtils.read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
@ -80,8 +81,14 @@ class ElectrumWalletSnapshot {
derivationType: derivationType, derivationType: derivationType,
derivationPath: derivationPath, derivationPath: derivationPath,
unspentCoins: (data['unspent_coins'] as List?) unspentCoins: (data['unspent_coins'] as List?)
?.map((e) => BitcoinUnspent.fromJSON(null, e as Map<String, dynamic>)) ?.map((e) => BitcoinUnspent.fromJSON(
.toList() ?? [], null,
e as Map<String, dynamic>,
walletInfo.derivationInfo!,
network,
))
.toList() ??
[],
didInitialSync: data['didInitialSync'] as bool?, didInitialSync: data['didInitialSync'] as bool?,
walletAddressesSnapshot: walletAddressesSnapshot, walletAddressesSnapshot: walletAddressesSnapshot,
); );

View file

@ -24,6 +24,7 @@ class ElectrumWorker {
final SendPort sendPort; final SendPort sendPort;
ElectrumProvider? _electrumClient; ElectrumProvider? _electrumClient;
ServerCapability? _serverCapability; ServerCapability? _serverCapability;
String? get version => _serverCapability?.version;
BehaviorSubject<Map<String, dynamic>>? _scanningStream; BehaviorSubject<Map<String, dynamic>>? _scanningStream;
BasedUtxoNetwork? _network; BasedUtxoNetwork? _network;
@ -72,6 +73,11 @@ class ElectrumWorker {
ElectrumWorkerTxExpandedRequest.fromJson(messageJson), ElectrumWorkerTxExpandedRequest.fromJson(messageJson),
); );
break; break;
case ElectrumWorkerMethods.txHexMethod:
await _handleGetTxHex(
ElectrumWorkerTxHexRequest.fromJson(messageJson),
);
break;
case ElectrumRequestMethods.headersSubscribeMethod: case ElectrumRequestMethods.headersSubscribeMethod:
await _handleHeadersSubscribe( await _handleHeadersSubscribe(
ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson), ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson),
@ -257,6 +263,8 @@ class ElectrumWorker {
completed: true, completed: true,
)); ));
} }
}, onError: () {
_serverCapability!.supportsBatching = false;
}); });
})); }));
} else { } else {
@ -488,9 +496,7 @@ class ElectrumWorker {
} }
if (txVerbose?.isEmpty ?? true) { if (txVerbose?.isEmpty ?? true) {
txHex = await _electrumClient!.request( txHex = await _getTransactionHex(hash: hash);
ElectrumRequestGetTransactionHex(transactionHash: hash),
);
} else { } else {
txHex = txVerbose!['hex'] as String; 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 // 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 // since sometimes the mempool api returns the wrong date at first
final canValidateDate = request.mempoolAPIEnabled || _serverCapability!.supportsTxVerbose;
if (tx == null || if (tx == null ||
tx.original == null || tx.original == null ||
// TODO: use mempool api or tx verbose (tx.isDateValidated != true && canValidateDate) ||
// (tx.isDateValidated != true && request.mempoolAPIEnabled)) { tx.time == null) {
(tx.isDateValidated != true)) {
transactionsByIds[txid] = TxToFetch(height: height, tx: tx); transactionsByIds[txid] = TxToFetch(height: height, tx: tx);
} }
} }
@ -832,9 +838,7 @@ class ElectrumWorker {
} }
if (txVerbose?.isEmpty ?? true) { if (txVerbose?.isEmpty ?? true) {
txHex = await _electrumClient!.request( txHex = await _getTransactionHex(hash: hash);
ElectrumRequestGetTransactionHex(transactionHash: hash),
);
} else { } else {
txHex = txVerbose!['hex'] as String; txHex = txVerbose!['hex'] as String;
} }
@ -869,10 +873,7 @@ class ElectrumWorker {
final inputTransactionHexes = <String, String>{}; final inputTransactionHexes = <String, String>{};
await Future.wait(original.inputs.map((e) => e.txId).toList().map((inHash) async { await Future.wait(original.inputs.map((e) => e.txId).toList().map((inHash) async {
final hex = await _electrumClient!.request( final hex = await _getTransactionHex(hash: inHash);
ElectrumRequestGetTransactionHex(transactionHash: inHash),
);
inputTransactionHexes[inHash] = hex; 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 { Future<void> _handleGetTxExpanded(ElectrumWorkerTxExpandedRequest request) async {
final tx = await _getTransactionExpanded( final tx = await _getTransactionExpanded(
hash: request.txHash, hash: request.txHash,
@ -1129,11 +1135,8 @@ class ElectrumWorker {
} }
} else { } else {
await Future.wait(hashes.map((hash) async { await Future.wait(hashes.map((hash) async {
final history = await _electrumClient!.request( final hex = await _getTransactionHex(hash: hash);
ElectrumRequestGetTransactionHex(transactionHash: hash), transactionHexes.add(hex);
);
transactionHexes.add(history);
})); }));
} }
} }
@ -1248,9 +1251,7 @@ class ElectrumWorker {
time = transactionVerbose['time'] as int?; time = transactionVerbose['time'] as int?;
confirmations = transactionVerbose['confirmations'] as int?; confirmations = transactionVerbose['confirmations'] as int?;
} else { } else {
transactionHex = await _electrumClient!.request( transactionHex = await _getTransactionHex(hash: hash);
ElectrumRequestGetTransactionHex(transactionHash: hash),
);
} }
if (getTime && _walletType == WalletType.bitcoin) { if (getTime && _walletType == WalletType.bitcoin) {
@ -1271,11 +1272,7 @@ class ElectrumWorker {
final ins = <BtcTransaction>[]; final ins = <BtcTransaction>[];
for (final vin in original.inputs) { for (final vin in original.inputs) {
final inputTransactionHex = await _electrumClient!.request( final inputTransactionHex = await _getTransactionHex(hash: vin.txId);
// TODO: _getTXHex
ElectrumRequestGetTransactionHex(transactionHash: vin.txId),
);
ins.add(BtcTransaction.fromRaw(inputTransactionHex)); ins.add(BtcTransaction.fromRaw(inputTransactionHex));
} }
@ -1311,12 +1308,7 @@ class ElectrumWorker {
} }
} else { } else {
await Future.wait(hashes.map((hash) async { await Future.wait(hashes.map((hash) async {
final hex = await _electrumClient!.request( final hex = await _getTransactionHex(hash: hash);
ElectrumRequestGetTransactionHex(
transactionHash: hash,
),
);
inputTransactionHexById[hash] = hex; inputTransactionHexById[hash] = hex;
})); }));
} }
@ -1324,6 +1316,14 @@ class ElectrumWorker {
return inputTransactionHexById; 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 { Future<void> _handleGetFeeRates(ElectrumWorkerGetFeesRequest request) async {
if (request.mempoolAPIEnabled && _walletType == WalletType.bitcoin) { if (request.mempoolAPIEnabled && _walletType == WalletType.bitcoin) {
try { try {
@ -1350,7 +1350,7 @@ class ElectrumWorker {
// this guarantees that, even if all fees are low and equal, // this guarantees that, even if all fees are low and equal,
// higher priority fee txs can be consumed when chain fees start surging // higher priority fee txs can be consumed when chain fees start surging
_sendResponse( return _sendResponse(
ElectrumWorkerGetFeesResponse( ElectrumWorkerGetFeesResponse(
result: BitcoinAPITransactionPriorities( result: BitcoinAPITransactionPriorities(
minimum: minimum, minimum: minimum,
@ -1362,24 +1362,17 @@ class ElectrumWorker {
), ),
), ),
); );
} catch (e) { } catch (_) {}
_sendResponse(
ElectrumWorkerGetFeesResponse(
result: ElectrumTransactionPriorities.fromList(
await _electrumClient!.getFeeRates(),
),
),
);
}
} else {
_sendResponse(
ElectrumWorkerGetFeesResponse(
result: ElectrumTransactionPriorities.fromList(
await _electrumClient!.getFeeRates(),
),
),
);
} }
// 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 { Future<void> _handleCheckTweaks(ElectrumWorkerCheckTweaksRequest request) async {
@ -1440,11 +1433,11 @@ class ElectrumWorker {
} }
// Initial status UI update, send how many blocks in total to scan // Initial status UI update, send how many blocks in total to scan
// TODO: isSingleScan : dont update restoreHeight
_sendResponse(ElectrumWorkerTweaksSubscribeResponse( _sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse( result: TweaksSyncResponse(
height: syncHeight, height: syncHeight,
syncStatus: StartingScanSyncStatus(syncHeight), syncStatus: StartingScanSyncStatus(syncHeight),
wasSingleBlock: scanData.isSingleScan,
), ),
)); ));
@ -1493,7 +1486,11 @@ class ElectrumWorker {
? SyncingSyncStatus(1, 0) ? SyncingSyncStatus(1, 0)
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight); : SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
_sendResponse(ElectrumWorkerTweaksSubscribeResponse( _sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(height: syncHeight, syncStatus: syncingStatus), result: TweaksSyncResponse(
height: syncHeight,
syncStatus: syncingStatus,
wasSingleBlock: scanData.isSingleScan,
),
)); ));
final tweakHeight = response.block; final tweakHeight = response.block;
@ -1509,20 +1506,21 @@ class ElectrumWorker {
try { try {
final addToWallet = {}; final addToWallet = {};
receivers.forEach((receiver) { // receivers.forEach((receiver) {
// scanOutputs called from rust here // scanOutputs called from rust here
final scanResult = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver); final receiver = receivers.first;
final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
if (scanResult.isEmpty) { if (scanResult.isEmpty) {
return; continue;
} }
if (addToWallet[receiver.BSpend] == null) { if (addToWallet[receiver.BSpend] == null) {
addToWallet[receiver.BSpend] = scanResult; addToWallet[receiver.BSpend] = scanResult;
} else { } else {
addToWallet[receiver.BSpend].addAll(scanResult); addToWallet[receiver.BSpend].addAll(scanResult);
} }
}); // });
if (addToWallet.isEmpty) { if (addToWallet.isEmpty) {
// no results tx, continue to next tx // no results tx, continue to next tx
@ -1588,6 +1586,7 @@ class ElectrumWorker {
_sendResponse(ElectrumWorkerTweaksSubscribeResponse( _sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse( result: TweaksSyncResponse(
transactions: {txInfo.id: TweakResponseData(txInfo: txInfo, unspents: unspents)}, transactions: {txInfo.id: TweakResponseData(txInfo: txInfo, unspents: unspents)},
wasSingleBlock: scanData.isSingleScan,
), ),
)); ));
@ -1604,7 +1603,7 @@ class ElectrumWorker {
syncHeight = tweakHeight; syncHeight = tweakHeight;
if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) { if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
_sendResponse( _sendResponse(
ElectrumWorkerTweaksSubscribeResponse( ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse( result: TweaksSyncResponse(
@ -1612,6 +1611,7 @@ class ElectrumWorker {
syncStatus: scanData.isSingleScan syncStatus: scanData.isSingleScan
? SyncedSyncStatus() ? SyncedSyncStatus()
: SyncedTipSyncStatus(scanData.chainTip), : SyncedTipSyncStatus(scanData.chainTip),
wasSingleBlock: scanData.isSingleScan,
), ),
), ),
); );
@ -1627,15 +1627,7 @@ class ElectrumWorker {
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async { Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
_sendResponse( _sendResponse(
ElectrumWorkerGetVersionResponse( ElectrumWorkerGetVersionResponse(result: version!, id: request.id),
result: await _electrumClient!.request(
ElectrumRequestVersion(
clientName: "",
protocolVersion: "1.4",
),
),
id: request.id,
),
); );
} }
} }

View file

@ -5,12 +5,14 @@ class ElectrumWorkerMethods {
static const String connectionMethod = "connection"; static const String connectionMethod = "connection";
static const String unknownMethod = "unknown"; static const String unknownMethod = "unknown";
static const String txHashMethod = "txHash"; static const String txHashMethod = "txHash";
static const String txHexMethod = "txHex";
static const String checkTweaksMethod = "checkTweaks"; static const String checkTweaksMethod = "checkTweaks";
static const String stopScanningMethod = "stopScanning"; static const String stopScanningMethod = "stopScanning";
static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod); static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod);
static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod); static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod);
static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod); static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod);
static const ElectrumWorkerMethods txHex = ElectrumWorkerMethods._(txHexMethod);
static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod); static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod);
static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod); static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod);

View file

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

View file

@ -16,6 +16,7 @@ part 'scripthashes_subscribe.dart';
part 'get_balance.dart'; part 'get_balance.dart';
part 'get_history.dart'; part 'get_history.dart';
part 'get_tx_expanded.dart'; part 'get_tx_expanded.dart';
part 'get_tx_hex.dart';
part 'broadcast.dart'; part 'broadcast.dart';
part 'list_unspent.dart'; part 'list_unspent.dart';
part 'tweaks_subscribe.dart'; part 'tweaks_subscribe.dart';

View file

@ -143,14 +143,21 @@ class TweaksSyncResponse {
int? height; int? height;
SyncStatus? syncStatus; SyncStatus? syncStatus;
Map<String, TweakResponseData>? transactions = {}; 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() { Map<String, dynamic> toJson() {
return { return {
'height': height, 'height': height,
'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!), 'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!),
'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())), 'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())),
'wasSingleBlock': wasSingleBlock,
}; };
} }
@ -168,6 +175,7 @@ class TweaksSyncResponse {
TweakResponseData.fromJson(value as Map<String, dynamic>), TweakResponseData.fromJson(value as Map<String, dynamic>),
), ),
), ),
wasSingleBlock: json['wasSingleBlock'] as bool? ?? false,
); );
} }
} }

View file

@ -40,7 +40,7 @@ class ElectrumWorkerGetVersionError extends ElectrumWorkerErrorResponse {
String get method => ElectrumRequestMethods.version.method; String get method => ElectrumRequestMethods.version.method;
} }
class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<String>, List<String>> { class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<String, String> {
ElectrumWorkerGetVersionResponse({ ElectrumWorkerGetVersionResponse({
required super.result, required super.result,
super.error, super.error,
@ -49,14 +49,14 @@ class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<Strin
}) : super(method: ElectrumRequestMethods.version.method); }) : super(method: ElectrumRequestMethods.version.method);
@override @override
List<String> resultJson(result) { String resultJson(result) {
return result; return result.toString();
} }
@override @override
factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) { factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerGetVersionResponse( return ElectrumWorkerGetVersionResponse(
result: json['result'] as List<String>, result: json['result'] as String,
error: json['error'] as String?, error: json['error'] as String?,
id: json['id'] as int?, id: json['id'] as int?,
completed: json['completed'] as bool? ?? false, completed: json['completed'] as bool? ?? false,

View file

@ -7,15 +7,24 @@ class ServerCapability {
bool supportsBatching; bool supportsBatching;
bool supportsTxVerbose; 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) { static ServerCapability fromVersion(List<String> serverVersion) {
if (serverVersion.isNotEmpty) { if (serverVersion.isNotEmpty) {
final server = serverVersion.first.toLowerCase(); final server = serverVersion.first.toLowerCase();
if (server.contains('electrumx')) { if (server.contains('electrumx')) {
return ServerCapability(supportsBatching: true, supportsTxVerbose: true); return ServerCapability(
supportsBatching: true,
supportsTxVerbose: true,
version: server,
);
} }
if (server.startsWith('electrs/')) { if (server.startsWith('electrs/')) {
@ -28,13 +37,21 @@ class ServerCapability {
try { try {
final version = ElectrumVersion.fromStr(electrsVersion); final version = ElectrumVersion.fromStr(electrsVersion);
if (version.compareTo(ELECTRS_MIN_BATCHING_VERSION) >= 0) { if (version.compareTo(ELECTRS_MIN_BATCHING_VERSION) >= 0) {
return ServerCapability(supportsBatching: true, supportsTxVerbose: false); return ServerCapability(
supportsBatching: true,
supportsTxVerbose: false,
version: server,
);
} }
} catch (e) { } catch (e) {
// ignore version parsing errors // ignore version parsing errors
} }
return ServerCapability(supportsBatching: false, supportsTxVerbose: false); return ServerCapability(
supportsBatching: false,
supportsTxVerbose: false,
version: server,
);
} }
if (server.startsWith('fulcrum')) { if (server.startsWith('fulcrum')) {
@ -43,7 +60,11 @@ class ServerCapability {
try { try {
final version = ElectrumVersion.fromStr(fulcrumVersion); final version = ElectrumVersion.fromStr(fulcrumVersion);
if (version.compareTo(FULCRUM_MIN_BATCHING_VERSION) >= 0) { if (version.compareTo(FULCRUM_MIN_BATCHING_VERSION) >= 0) {
return ServerCapability(supportsBatching: true, supportsTxVerbose: true); return ServerCapability(
supportsBatching: true,
supportsTxVerbose: true,
version: server,
);
} }
} catch (e) {} } catch (e) {}
} }
@ -59,7 +80,11 @@ class ServerCapability {
try { try {
final version = ElectrumVersion.fromStr(mempoolElectrsVersion); final version = ElectrumVersion.fromStr(mempoolElectrsVersion);
if (version.compareTo(MEMPOOL_ELECTRS_MIN_BATCHING_VERSION) > 0) { 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) { } catch (e) {
// ignore version parsing errors // ignore version parsing errors
@ -67,6 +92,10 @@ class ServerCapability {
} }
} }
return ServerCapability(supportsBatching: false, supportsTxVerbose: false); return ServerCapability(
supportsBatching: false,
supportsTxVerbose: false,
version: "unknown",
);
} }
} }

View file

@ -72,13 +72,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mwebEnabled = alwaysScan ?? false; mwebEnabled = alwaysScan ?? false;
if (walletAddressesSnapshot != null) { if (walletAddressesSnapshot != null) {
// walletAddresses = LitecoinWalletAddresses.fromJson( walletAddresses = LitecoinWalletAddressesBase.fromJson(
// walletAddressesSnapshot, walletAddressesSnapshot,
// walletInfo, walletInfo,
// network: network, network: network,
// isHardwareWallet: isHardwareWallet, isHardwareWallet: isHardwareWallet,
// hdWallets: hdWallets, hdWallets: hdWallets,
// ); );
} else { } else {
walletAddresses = LitecoinWalletAddresses( walletAddresses = LitecoinWalletAddresses(
walletInfo, walletInfo,
@ -186,7 +186,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
snp = await LitecoinWalletSnapshot.load( snp = await LitecoinWalletSnapshot.load(
encryptionFileUtils, encryptionFileUtils,
name, name,
walletInfo.type, walletInfo,
password, password,
LitecoinNetwork.mainnet, LitecoinNetwork.mainnet,
); );
@ -347,6 +347,19 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// if the confirmations haven't changed, skip updating: // if the confirmations haven't changed, skip updating:
if (tx.confirmations == confirmations) continue; 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.confirmations = confirmations;
tx.isPending = false; tx.isPending = false;
transactionHistory.addOne(tx); transactionHistory.addOne(tx);
@ -786,84 +799,85 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
// @override // @override
// Future<ElectrumBalance> fetchBalances() async { Future<void> updateBalance([Set<String>? scripthashes, bool? wait]) async {
// final balance = await super.fetchBalances(); await super.updateBalance(scripthashes, true);
final balance = this.balance[currency]!;
// if (!mwebEnabled) { if (!mwebEnabled) {
// return balance; return;
// } }
// // update unspent balances: // update unspent balances:
// await updateUnspent(); await updateUnspent();
// int confirmed = balance.confirmed; int confirmed = balance.confirmed;
// int unconfirmed = balance.unconfirmed; int unconfirmed = balance.unconfirmed;
// int confirmedMweb = 0; int confirmedMweb = 0;
// int unconfirmedMweb = 0; int unconfirmedMweb = 0;
// try { try {
// mwebUtxosBox.values.forEach((utxo) { mwebUtxosBox.values.forEach((utxo) {
// if (utxo.height > 0) { if (utxo.height > 0) {
// confirmedMweb += utxo.value.toInt(); confirmedMweb += utxo.value.toInt();
// } else { } else {
// unconfirmedMweb += utxo.value.toInt(); unconfirmedMweb += utxo.value.toInt();
// } }
// }); });
// if (unconfirmedMweb > 0) { if (unconfirmedMweb > 0) {
// unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb); unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
// } }
// } catch (_) {} } catch (_) {}
// for (var addressRecord in walletAddresses.allAddresses) { for (var addressRecord in walletAddresses.allAddresses) {
// addressRecord.balance = 0; addressRecord.balance = 0;
// addressRecord.txCount = 0; addressRecord.txCount = 0;
// } }
// unspentCoins.forEach((coin) { unspentCoins.forEach((coin) {
// final coinInfoList = unspentCoinsInfo.values.where( final coinInfoList = unspentCoinsInfo.values.where(
// (element) => (element) =>
// element.walletId.contains(id) && element.walletId.contains(id) &&
// element.hash.contains(coin.hash) && element.hash.contains(coin.hash) &&
// element.vout == coin.vout, element.vout == coin.vout,
// ); );
// if (coinInfoList.isNotEmpty) { if (coinInfoList.isNotEmpty) {
// final coinInfo = coinInfoList.first; final coinInfo = coinInfoList.first;
// coin.isFrozen = coinInfo.isFrozen; coin.isFrozen = coinInfo.isFrozen;
// coin.isSending = coinInfo.isSending; coin.isSending = coinInfo.isSending;
// coin.note = coinInfo.note; coin.note = coinInfo.note;
// coin.bitcoinAddressRecord.balance += coinInfo.value; coin.bitcoinAddressRecord.balance += coinInfo.value;
// } else { } else {
// super.addCoinInfo(coin); super.addCoinInfo(coin);
// } }
// }); });
// // update the txCount for each address using the tx history, since we can't rely on mwebd // 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: // 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) { for (final tx in transactionHistory.transactions.values) {
// // if (tx.isPending) continue; // if (tx.isPending) continue;
// if (tx.inputAddresses == null || tx.outputAddresses == null) { if (tx.inputAddresses == null || tx.outputAddresses == null) {
// continue; continue;
// } }
// final txAddresses = tx.inputAddresses! + tx.outputAddresses!; final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
// for (final address in txAddresses) { for (final address in txAddresses) {
// final addressRecord = walletAddresses.allAddresses final addressRecord = walletAddresses.allAddresses
// .firstWhereOrNull((addressRecord) => addressRecord.address == address); .firstWhereOrNull((addressRecord) => addressRecord.address == address);
// if (addressRecord == null) { if (addressRecord == null) {
// continue; continue;
// } }
// addressRecord.txCount++; addressRecord.txCount++;
// } }
// } }
// return ElectrumBalance( this.balance[currency] = ElectrumBalance(
// confirmed: confirmed, confirmed: confirmed,
// unconfirmed: unconfirmed, unconfirmed: unconfirmed,
// frozen: balance.frozen, frozen: balance.frozen,
// secondConfirmed: confirmedMweb, secondConfirmed: confirmedMweb,
// secondUnconfirmed: unconfirmedMweb, secondUnconfirmed: unconfirmedMweb,
// ); );
// } }
@override @override
ElectrumTxCreateUtxoDetails createUTXOS({ ElectrumTxCreateUtxoDetails createUTXOS({

View file

@ -72,8 +72,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
? ElectrumWalletAddressesBase.defaultChangeAddressesCount ? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount; : ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
final startIndex = (isChange ? changeAddresses : receiveAddresses) final startIndex = getAddressesByType(addressType, isChange)
.where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType) .where((addr) => (addr as BitcoinAddressRecord).cwDerivationType == derivationType)
.length; .length;
final mwebAddresses = <LitecoinMWEBAddressRecord>[]; final mwebAddresses = <LitecoinMWEBAddressRecord>[];
@ -268,7 +268,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@override @override
String get addressForExchange { String get addressForExchange {
// don't use mweb addresses for exchange refund address: // don't use mweb addresses for exchange refund address:
final addresses = receiveAddresses final addresses = selectedReceiveAddresses
.where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed); .where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed);
return addresses.first.address; return addresses.first.address;
} }
@ -329,6 +329,64 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
addressesSet.addAll(addresses); addressesSet.addAll(addresses);
this.mwebAddresses.clear(); this.mwebAddresses.clear();
this.mwebAddresses.addAll(addressesSet); 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
);
} }
} }

View file

@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.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 { class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
LitecoinWalletSnapshot({ LitecoinWalletSnapshot({
@ -29,10 +29,11 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
static Future<LitecoinWalletSnapshot> load( static Future<LitecoinWalletSnapshot> load(
EncryptionFileUtils encryptionFileUtils, EncryptionFileUtils encryptionFileUtils,
String name, String name,
WalletType type, WalletInfo walletInfo,
String password, String password,
BasedUtxoNetwork network, BasedUtxoNetwork network,
) async { ) async {
final type = walletInfo.type;
final path = await pathForWallet(name: name, type: type); final path = await pathForWallet(name: name, type: type);
final jsonSource = await encryptionFileUtils.read(path: path, password: password); final jsonSource = await encryptionFileUtils.read(path: path, password: password);
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
@ -40,7 +41,7 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load( final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
encryptionFileUtils, encryptionFileUtils,
name, name,
type, walletInfo,
password, password,
network, network,
); );

View file

@ -121,7 +121,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
snp = await ElectrumWalletSnapshot.load( snp = await ElectrumWalletSnapshot.load(
encryptionFileUtils, encryptionFileUtils,
name, name,
walletInfo.type, walletInfo,
password, password,
BitcoinCashNetwork.mainnet, BitcoinCashNetwork.mainnet,
); );

View file

@ -23,6 +23,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
@override @override
Future<void> init() async { Future<void> init() async {
await generateInitialAddresses(type: P2pkhAddressType.p2pkh); await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
allAddresses.forEach((addr) {
print(addr.address);
});
await super.init(); await super.init();
} }

View file

@ -153,7 +153,10 @@ class CWBitcoin extends Bitcoin {
@computed @computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) { List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet; final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesOnReceiveScreen return [
...electrumWallet.walletAddresses.selectedReceiveAddresses,
...electrumWallet.walletAddresses.selectedChangeAddresses
]
.map( .map(
(addr) => ElectrumSubAddress( (addr) => ElectrumSubAddress(
id: addr.index, id: addr.index,

View file

@ -9,7 +9,7 @@ import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
List<TransactionPriority> priorityForWalletType(WalletBase wallet) { List<TransactionPriority> priorityForWallet(WalletBase wallet) {
switch (wallet.type) { switch (wallet.type) {
case WalletType.monero: case WalletType.monero:
return monero!.getTransactionPriorities(); return monero!.getTransactionPriorities();

View file

@ -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/screens/receive/widgets/address_list.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.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/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/keyboard_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/gradient_background.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/share_util.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/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/generated/i18n.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/base_page.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/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
@ -102,13 +116,13 @@ class ReceivePage extends BasePage {
Padding( Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24), padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: QRWidget( child: QRWidget(
addressListViewModel: addressListViewModel, addressListViewModel: addressListViewModel,
formKey: _formKey, formKey: _formKey,
heroTag: _heroTag, heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus, amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController, amountController: _amountController,
isLight: currentTheme.type == ThemeType.light, isLight: currentTheme.type == ThemeType.light,
), ),
), ),
AddressList(addressListViewModel: addressListViewModel), AddressList(addressListViewModel: addressListViewModel),
Padding( Padding(

View file

@ -529,7 +529,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
} }
Future<void> pickTransactionPriority(BuildContext context) async { Future<void> pickTransactionPriority(BuildContext context) async {
final items = priorityForWalletType(sendViewModel.wallet); final items = priorityForWallet(sendViewModel.wallet);
final selectedItem = items.indexOf(sendViewModel.transactionPriority); final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final customItemIndex = sendViewModel.getCustomPriorityIndex(items); final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin; final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;

View file

@ -37,7 +37,7 @@ class OtherSettingsPage extends BasePage {
_otherSettingsViewModel.walletType == WalletType.bitcoin _otherSettingsViewModel.walletType == WalletType.bitcoin
? SettingsPriorityPickerCell( ? SettingsPriorityPickerCell(
title: S.current.settings_fee_priority, title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet), items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority, displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
selectedItem: _otherSettingsViewModel.transactionPriority, selectedItem: _otherSettingsViewModel.transactionPriority,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex, customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
@ -47,7 +47,7 @@ class OtherSettingsPage extends BasePage {
) )
: SettingsPickerCell( : SettingsPickerCell(
title: S.current.settings_fee_priority, title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet), items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet),
displayItem: _otherSettingsViewModel.getDisplayPriority, displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority, selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected, onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,

View file

@ -92,7 +92,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
setTransactionPriority(bitcoinTransactionPriorityMedium); setTransactionPriority(bitcoinTransactionPriorityMedium);
} }
final priority = _settingsStore.priority[wallet.type]; final priority = _settingsStore.priority[wallet.type];
final priorities = priorityForWalletType(wallet); final priorities = priorityForWallet(wallet);
if (priorities.isNotEmpty && !priorities.contains(priority)) { if (priorities.isNotEmpty && !priorities.contains(priority)) {
_settingsStore.priority[wallet.type] = priorities.first; _settingsStore.priority[wallet.type] = priorities.first;
} }

View file

@ -24,7 +24,7 @@ abstract class OtherSettingsViewModelBase with Store {
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version); .then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
final priority = _settingsStore.priority[_wallet.type]; final priority = _settingsStore.priority[_wallet.type];
final priorities = priorityForWalletType(_wallet); final priorities = priorityForWallet(_wallet);
if (!priorities.contains(priority) && priorities.isNotEmpty) { if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[_wallet.type] = priorities.first; _settingsStore.priority[_wallet.type] = priorities.first;
@ -103,7 +103,7 @@ abstract class OtherSettingsViewModelBase with Store {
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble(); double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
int? get customPriorityItemIndex { int? get customPriorityItemIndex {
final priorities = priorityForWalletType(_wallet); final priorities = priorityForWallet(_wallet);
final customItem = priorities final customItem = priorities
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom()); .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
return customItem != null ? priorities.indexOf(customItem) : null; return customItem != null ? priorities.indexOf(customItem) : null;

View file

@ -566,7 +566,7 @@ abstract class TransactionDetailsViewModelBase with Store {
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte')); 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 selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
final customItem = priorities.firstWhereOrNull( final customItem = priorities.firstWhereOrNull(
(element) => element.title == sendViewModel.bitcoinTransactionPriorityCustom.title); (element) => element.title == sendViewModel.bitcoinTransactionPriorityCustom.title);