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: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,

View file

@ -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()),

View file

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

View file

@ -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,

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_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,
);

View file

@ -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;

View file

@ -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!),

View file

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

View file

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

View file

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

View file

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

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_history.dart';
part 'get_tx_expanded.dart';
part 'get_tx_hex.dart';
part 'broadcast.dart';
part 'list_unspent.dart';
part 'tweaks_subscribe.dart';

View file

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

View file

@ -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,

View file

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

View file

@ -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({

View file

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

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_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,
);

View file

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

View file

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

View file

@ -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,

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_type.dart';
List<TransactionPriority> priorityForWalletType(WalletBase wallet) {
List<TransactionPriority> priorityForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.monero:
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/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(

View file

@ -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;

View file

@ -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,

View file

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

View file

@ -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;

View file

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