fix: discovery & startSync [skip ci]

This commit is contained in:
Rafael Saes 2025-04-22 18:47:13 -03:00
parent 67ac4e8d97
commit 2502a14b50
7 changed files with 337 additions and 220 deletions

View file

@ -114,68 +114,92 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
}
@override
void initAddresses() async {
// if (didInitAddresses) return;
Future<bool?> initAddresses([bool? sync]) async {
var isDiscovered = await super.initAddresses(sync);
bool? discovered;
// If already loaded, no need to generate/discover all initial addresses
// so skip
// if (!walletAddresses.loadedFromNewSnapshot) {
for (final walletAddressType in walletAddresses.walletAddressTypes) {
if (isHardwareWallet && walletAddressType != SegwitAddressType.p2wpkh) continue;
// NOTE: will initiate by priority from the first walletAddressTypes
// then proceeds to following ones after got fully discovered response from worker response
if (isDiscovered != false) {
for (final addressType in walletAddresses.walletAddressTypes) {
if (isHardwareWallet && addressType != SegwitAddressType.p2wpkh) continue;
for (final seedBytesType in hdWallets.keys) {
generateInitialAddresses(
addressType: walletAddressType,
seedBytesType: seedBytesType,
);
}
}
for (final seedBytesType in hdWallets.keys) {
// p2wpkh has always had the right derivations, skip if creating old derivations
if (seedBytesType.isOldDerivation && addressType == SegwitAddressType.p2wpkh) {
continue;
}
// }
}
final bitcoinDerivationInfo = BitcoinAddressUtils.getDerivationFromType(
addressType,
network: network,
isElectrum: seedBytesType.isElectrum,
);
@override
@action
void generateInitialAddresses({
required BitcoinAddressType addressType,
required SeedBytesType seedBytesType,
BitcoinDerivationInfo? bitcoinDerivationInfo,
}) {
// p2wpkh has always had the right derivations, skip if creating old derivations
if (seedBytesType.isOldDerivation && addressType == SegwitAddressType.p2wpkh) {
return;
}
bool alreadyDidDerivation = false;
final bitcoinDerivationInfo = BitcoinAddressUtils.getDerivationFromType(
addressType,
network: network,
isElectrum: seedBytesType.isElectrum,
);
for (final derivationInfo in [
bitcoinDerivationInfo,
BitcoinDerivationInfos.BIP84,
BitcoinDerivationInfos.ELECTRUM,
]) {
final derivationPath = derivationInfo.derivationPath.toString();
if (seedBytesType.isOldDerivation) {
for (final derivationInfo in [
bitcoinDerivationInfo,
BitcoinDerivationInfos.ELECTRUM,
BitcoinDerivationInfos.BIP84,
]) {
if (derivationInfo.derivationPath.toString() ==
bitcoinDerivationInfo.derivationPath.toString()) {
continue;
if (alreadyDidDerivation &&
derivationPath == bitcoinDerivationInfo.derivationPath.toString()) {
continue;
}
alreadyDidDerivation = true;
for (final isChange in [true, false]) {
isDiscovered = walletAddresses.discoveredAddressesRecord.getIsDiscovered(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: derivationPath,
isChange: isChange,
);
if (isDiscovered == false) {
break;
} else if (sync == true)
subscribeForStatuses(
walletAddresses.addressesRecords
.getRecords(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: derivationPath,
isChange: isChange,
)
.whereType<BitcoinAddressRecord>()
.toList(),
);
}
if (isDiscovered == false) {
discovered = await generateInitialAddresses(
addressType: addressType,
seedBytesType: seedBytesType,
bitcoinDerivationInfo: derivationInfo,
);
break;
}
}
if (isDiscovered == false) break;
}
super.generateInitialAddresses(
addressType: addressType,
seedBytesType: seedBytesType,
bitcoinDerivationInfo: derivationInfo,
);
if (isDiscovered == false) break;
}
} else {
super.generateInitialAddresses(
addressType: addressType,
seedBytesType: seedBytesType,
bitcoinDerivationInfo: bitcoinDerivationInfo,
);
}
if (isDiscovered == true && sync == false)
initAddresses(true);
else if (isDiscovered == true)
syncStatus = SyncedSyncStatus();
else if (isDiscovered == false && discovered == false) initAddresses(sync);
return isDiscovered;
}
static Future<BitcoinWallet> create({

View file

@ -44,10 +44,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
@override
final walletAddressTypes = [
SegwitAddressType.p2wpkh,
// SegwitAddressType.p2tr,
// P2shAddressType.p2wpkhInP2sh,
// P2pkhAddressType.p2pkh,
// SegwitAddressType.p2wsh,
SegwitAddressType.p2tr,
P2shAddressType.p2wpkhInP2sh,
P2pkhAddressType.p2pkh,
SegwitAddressType.p2wsh,
];
@observable

View file

@ -181,7 +181,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
break;
case ElectrumRequestMethods.scripthashesSubscribeMethod:
final response = ElectrumWorkerScripthashesSubscribeResponse.fromJson(messageJson);
await onScripthashesStatusResponse(response.result.last);
await onScripthashesStatusResponse(response.result);
break;
case ElectrumRequestMethods.getBalanceMethod:
final response = ElectrumWorkerGetBalanceResponse.fromJson(messageJson);
@ -309,8 +309,44 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
Timer? _updateFeeRateTimer;
static const int _autoSaveInterval = 1;
void initAddresses() {
throw UnimplementedError();
Future<bool?> initAddresses([bool? sync]) async {
bool? isDiscovered = null;
// NOTE: will initiate by priority from the first walletAddressTypes
// then proceeds to following ones after got fully discovered response from worker response
for (final addressType in walletAddresses.walletAddressTypes) {
if (isHardwareWallet && addressType != SegwitAddressType.p2wpkh) continue;
final bitcoinDerivationInfo = BitcoinAddressUtils.getDerivationFromType(
addressType,
network: network,
isElectrum: walletAddresses.walletSeedBytesType.isElectrum,
);
for (final isChange in [true, false]) {
isDiscovered = walletAddresses.discoveredAddressesRecord.getIsDiscovered(
addressType: addressType,
seedBytesType: walletAddresses.walletSeedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: isChange,
);
if (isDiscovered == false) {
break;
}
}
if (isDiscovered == false) {
await generateInitialAddresses(
addressType: addressType,
seedBytesType: walletAddresses.walletSeedBytesType,
bitcoinDerivationInfo: bitcoinDerivationInfo,
);
break;
}
}
return isDiscovered;
}
Future<void> init() async {
@ -324,49 +360,16 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
@override
Future<void> startSync() async {
try {
// if (syncStatus is SynchronizingSyncStatus) {
// return;
// }
syncStatus = SynchronizingSyncStatus();
// syncStatus = SynchronizingSyncStatus();
await subscribeForHeaders(true);
// // INFO: FIRST (always): Call subscribe for headers, wait for completion to update currentChainTip (needed for other methods)
// await subscribeForHeaders(true);
await initAddresses(false);
// final responses = await subscribeForStatuses(addresses, true);
await updateFeeRates();
// final addressesWithHistory = walletAddresses.allAddresses
// .where((e) => scripthashesWithStatus.contains(e.scriptHash))
// .toList();
// // INFO: SECOND: Start loading transaction histories for every address, this will help discover addresses until the unused gap limit has been reached, which will help finding the full balance and unspents next
// await updateTransactions(addressesWithHistory);
// // INFO: THIRD: Get the full wallet's balance with all addresses considered
// await updateBalance(scripthashesWithStatus, true);
// await updateAllUnspents(scripthashesWithStatus, true);
// syncStatus = SyncedSyncStatus();
// // INFO: FOURTH: Get the latest recommended fee rates and start update timer
// await updateFeeRates();
// _updateFeeRateTimer ??=
// Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
// await save();
} catch (e, stacktrace) {
printV(stacktrace);
printV("startSync $e");
syncStatus = FailedSyncStatus();
}
}
Future<void> syncAddresses(List<BitcoinAddressRecord> addresses) async {
try {
updateTransactions(addresses);
updateBalance(scripthashesWithStatus);
updateAllUnspents(scripthashesWithStatus);
_updateFeeRateTimer ??=
Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
await save();
} catch (e, stacktrace) {
@ -1239,12 +1242,26 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
}
@action
Future<void> onScripthashesStatusResponse(ElectrumWorkerScripthashesResponse result) async {
if (result.status == null) {
Future<void> onScripthashesStatusResponse(
List<ElectrumWorkerScripthashesResponse> result,
) async {
final noItemsWithStatus = result.length == 1 && result.first.status == null;
if (noItemsWithStatus) {
return;
}
scripthashesWithStatus.add(result.scripthash);
final addresses = walletAddresses.allAddresses
.where(
(address) => result.any((e) => e.status != null && e.scripthash == address.scriptHash),
)
.toList();
final scripthashesWithStatus =
result.where((e) => e.status != null).map((e) => e.scripthash).toList();
updateTransactions(addresses);
updateBalance(scripthashesWithStatus);
updateAllUnspents(scripthashesWithStatus);
}
@action
@ -1292,7 +1309,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
final addressesWithHistory = <BitcoinAddressRecord>[];
if (histories.isNotEmpty) {
await Future.wait(histories.map((addressHistory) async {
for (final addressHistory in histories) {
final txs = addressHistory.txs;
if (txs.isNotEmpty) {
@ -1302,20 +1319,11 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
transactionHistory.addOne(tx);
}
}
}));
}
if (addressesWithHistory.isNotEmpty) {
walletAddresses.updateAddresses(addressesWithHistory);
walletAddresses.addAddresses(addressesWithHistory);
}
} else if (response.completed) {
final firstAddress = addressesWithHistory.first;
discoverNewAddresses(
seedBytesType: firstAddress.seedBytesType!,
isChange: firstAddress.isChange,
addressType: firstAddress.type,
derivationInfo: firstAddress.derivationInfo,
);
}
}
@ -1675,7 +1683,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
}
@action
Future<List<ElectrumWorkerScripthashesResponse>> subscribeForStatuses([
Future<List<ElectrumWorkerScripthashesResponse>?> subscribeForStatuses([
List<BitcoinAddressRecord>? addresses,
bool? wait,
]) async {
@ -1686,14 +1694,24 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
addressByScripthashes[addressRecord.scriptHash] = addressRecord.address;
});
return ElectrumWorkerScripthashesSubscribeResponse.fromJson(
await waitSendWorker(
if (wait == true)
return ElectrumWorkerScripthashesSubscribeResponse.fromJson(
await waitSendWorker(
ElectrumWorkerScripthashesSubscribeRequest(
scripthashByAddress: scripthashByAddress,
addressByScripthashes: addressByScripthashes,
),
),
).result;
else
sendWorker(
ElectrumWorkerScripthashesSubscribeRequest(
scripthashByAddress: scripthashByAddress,
addressByScripthashes: addressByScripthashes,
),
),
).result;
);
return null;
}
@action
@ -1823,9 +1841,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
syncStatus = ConnectedSyncStatus();
}
// TODO: only once
initAddresses();
break;
case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus &&
@ -1933,29 +1948,69 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
@action
Future<void> _onAddressesDiscovered(List<BitcoinAddressRecord> addresses) async {
try {
final scripthashByAddress = await subscribeForStatuses(addresses, true);
final scripthashByAddress = await subscribeForStatuses(addresses, true);
final noItemsWithStatus =
scripthashByAddress!.length == 1 && scripthashByAddress.first.status == null;
print("addresses: ${addresses.first.address}");
final firstAddress = addresses.first;
if (addresses.first.seedBytesType!.isOldDerivation && scripthashByAddress.length == 1) {
// Wrong derivation address with no history, discard and do not add to wallet addresses
// Was only used to find transactions if any
await save();
return;
}
// NOTE: Did not find any status for old/wrong derivation addresses, discard them
// (don't add to addresses list)
if (firstAddress.seedBytesType!.isOldDerivation && noItemsWithStatus) {
walletAddresses.discoveredAddressesRecord.addDiscovered(
addressType: firstAddress.type,
seedBytesType: firstAddress.seedBytesType!,
derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
isChange: firstAddress.isChange,
discovered: true,
);
initAddresses();
return;
}
walletAddresses.addAddresses(addresses);
await syncAddresses(addresses);
} catch (_) {}
walletAddresses.addAddresses(addresses);
walletAddresses.discoveredAddressesRecord.addDiscovered(
addressType: firstAddress.type,
seedBytesType: firstAddress.seedBytesType!,
derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
isChange: firstAddress.isChange,
discovered: true,
);
// NOTE: Has items with status under gap limit, continue discovering
if (!noItemsWithStatus)
discoverNewAddresses(
seedBytesType: firstAddress.seedBytesType!,
isChange: firstAddress.isChange,
addressType: firstAddress.type,
derivationInfo: firstAddress.derivationInfo,
scripthashStatuses: scripthashByAddress,
);
// NOTE: Otherwise, sync all the discovered addresses so far
else {
subscribeForStatuses(
walletAddresses.addressesRecords
.getRecords(
seedBytesType: firstAddress.seedBytesType!,
addressType: firstAddress.type,
derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
isChange: firstAddress.isChange,
)
.whereType<BitcoinAddressRecord>()
.toList(),
);
initAddresses();
}
}
@action
void generateInitialAddresses({
Future<bool> generateInitialAddresses({
required BitcoinAddressType addressType,
required SeedBytesType seedBytesType,
BitcoinDerivationInfo? bitcoinDerivationInfo,
}) {
}) async {
bool discovered = false;
bitcoinDerivationInfo ??= BitcoinAddressUtils.getDerivationFromType(
addressType,
network: network,
@ -1968,15 +2023,32 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false,
);
final discoveredExistingReceiveAddresses =
walletAddresses.discoveredAddressesRecord.getIsDiscovered(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false,
);
if (existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
if (!discoveredExistingReceiveAddresses &&
existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
discoverNewAddresses(
seedBytesType: seedBytesType,
isChange: false,
addressType: addressType,
seedBytesType: seedBytesType,
derivationInfo: bitcoinDerivationInfo,
isChange: false,
startIndex: existingReceiveAddresses.length,
);
discovered = true;
} else {
walletAddresses.discoveredAddressesRecord.addDiscovered(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false,
discovered: true,
);
}
final existingChangeAddresses = walletAddresses.addressesRecords.getRecords(
@ -1985,16 +2057,35 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true,
);
final discoveredExistingChangeAddresses =
walletAddresses.discoveredAddressesRecord.getIsDiscovered(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true,
);
if (existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) {
if (!discoveredExistingChangeAddresses &&
existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) {
discoverNewAddresses(
seedBytesType: seedBytesType,
isChange: true,
addressType: addressType,
seedBytesType: seedBytesType,
derivationInfo: bitcoinDerivationInfo,
isChange: true,
startIndex: existingChangeAddresses.length,
);
discovered = true;
} else {
walletAddresses.discoveredAddressesRecord.addDiscovered(
addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true,
discovered: true,
);
}
return discovered;
}
void discoverNewAddresses({
@ -2003,8 +2094,9 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo,
int? startIndex,
List<ElectrumWorkerScripthashesResponse>? scripthashStatuses,
}) async {
final count = isChange
final countToDiscover = isChange
? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT
: ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT;
@ -2018,20 +2110,21 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
startIndex ??= recordList.length;
late bool needsToDiscover;
print(addressType);
print(seedBytesType);
print(derivationInfo.derivationPath.toString());
print(isChange);
print([addressType, recordList.length]);
if (recordList.length < count) {
if (recordList.length < countToDiscover) {
needsToDiscover = true;
} else if (recordList.length == count) {
} else if (recordList.length == countToDiscover) {
needsToDiscover = recordList.any((record) => !record.getIsUsed());
} else {
needsToDiscover = recordList.sublist(recordList.length - count).any(
(record) => record.getIsUsed(),
);
needsToDiscover = recordList.sublist(recordList.length - countToDiscover).any(
(record) {
return scripthashStatuses?.any(
(scripthashStatus) =>
scripthashStatus.scripthash == (record as BitcoinAddressRecord).scriptHash,
) ??
record.getIsUsed();
},
);
}
if (!needsToDiscover) {
@ -2041,7 +2134,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
workerSendPort!.send(
ElectrumWorkerDiscoverAddressesRequest(
id: _messageId,
count: count,
count: countToDiscover,
walletType: type,
startIndex: startIndex,
seedBytesType: seedBytesType,

View file

@ -28,7 +28,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
BitcoinAddressType? initialAddressPageType,
}) : addressesRecords = initialAddressesRecords ?? BitcoinAddressRecordMap(),
activeIndexByType = initialActiveAddressIndex ?? {},
discoveredAddressTypes = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(),
discoveredAddressesRecord = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(),
addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
@ -49,6 +49,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get xpub => hdWallet.publicKey.toExtended;
String get xpriv => hdWallet.privateKey.toExtended;
// NOTE: order matters in priority
List<SeedBytesType> get seedBytesTypes {
final seedBytesTypes = <SeedBytesType>[];
if (hdWallets.containsKey(SeedBytesType.bip39)) {
seedBytesTypes.add(SeedBytesType.bip39);
}
if (hdWallets.containsKey(SeedBytesType.electrum)) {
seedBytesTypes.add(SeedBytesType.electrum);
}
return seedBytesTypes;
}
SeedBytesType get walletSeedBytesType => hdWallets.containsKey(SeedBytesType.bip39)
? SeedBytesType.bip39
: (hdWallets.containsKey(SeedBytesType.electrum)
@ -83,11 +95,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
bool isEnabledAutoGenerateNewAddress = true;
@observable
BitcoinDiscoveredAddressesMap discoveredAddressTypes;
BitcoinDiscoveredAddressesMap discoveredAddressesRecord;
@observable
BitcoinAddressRecordMap addressesRecords;
Set<String> get hiddenAddresses =>
allAddresses.where((address) => address.isHidden).map((address) => address.address).toSet();
@observable
List<BitcoinAddressRecord> _allAddresses = [];
@computed
@ -257,12 +272,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void resetActiveAddress() {
final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
try {
final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
(addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
) ??
activeReceiveAddresses.first;
activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
(addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
) ??
activeReceiveAddresses.first;
} catch (_) {}
}
@override
@ -272,11 +289,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return activeBitcoinAddress!.address;
}
final receiveAddress = selectedReceiveAddresses
.firstWhereOrNull(
(addr) => addr.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && !addr.isChange,
)
?.address;
final receiveAddress = nextReceiveAddress?.address;
return receiveAddress ?? '';
}
@ -442,22 +455,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
}
@action
void updateAddresses(Iterable<BitcoinAddressRecord> newAddresses) {
final replacedAddresses = newAddresses.toList();
for (final address in newAddresses) {
final index = _allAddresses.indexWhere((element) => element.address == address.address);
if (index >= 0) {
_allAddresses.replaceRange(index, index + 1, [address]);
replacedAddresses.remove(address);
}
}
if (replacedAddresses.isNotEmpty) {
_allAddresses.addAll(replacedAddresses);
}
}
@action
void addAddresses(List<BitcoinAddressRecord> addresses) {
final firstAddress = addresses.first;
@ -469,28 +466,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
isChange: firstAddress.isChange,
addressRecords: addresses,
);
print("addressesRecords.allRecords().length");
print(firstAddress.type);
print(firstAddress.seedBytesType!);
print(firstAddress.derivationInfo.derivationPath.toString());
print(firstAddress.isChange);
print(addressesRecords
.getRecords(
addressType: firstAddress.type,
seedBytesType: firstAddress.seedBytesType!,
derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
isChange: firstAddress.isChange,
)
.length);
updateAllAddresses();
}
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json['addressesRecords'] = addressesRecords.toJson();
json['discoveredAddressTypes'] = discoveredAddressTypes.toJson();
json['discoveredAddressesRecord'] = discoveredAddressesRecord.toJson();
json['addressPageType'] = addressPageType.toString();
json['activeIndexByType'] = activeIndexByType.map(
(key, value) => MapEntry(key.toString(), value),
@ -508,7 +490,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return {
'allAddresses': addresses,
'addressesRecords': data['addressesRecords'] as Map<String, dynamic>?,
'discoveredAddressTypes': data['discoveredAddressTypes'] as Map<String, dynamic>?,
'discoveredAddressesRecord': data['discoveredAddressesRecord'] as Map<String, dynamic>?,
'addressPageType': data['addressPageType'] as String?,
'activeIndexByType': (data['activeIndexByType'] as Map<dynamic, dynamic>?) ?? {},
};
@ -529,9 +511,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
json['addressesRecords'] as Map<String, dynamic>,
);
if (json['discoveredAddressTypes'] != null)
if (json['discoveredAddressesRecord'] != null)
initialDiscoveredAddresses ??= BitcoinDiscoveredAddressesMap.fromJson(
json['discoveredAddressTypes'] as Map<String, dynamic>,
json['discoveredAddressesRecord'] as Map<String, dynamic>,
);
return ElectrumWalletAddresses(
@ -612,7 +594,24 @@ class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
);
_data[addressType]![seedBytesType]![derivationPath]!.putIfAbsent(isChange, () => []);
_data[addressType]![seedBytesType]![derivationPath]![isChange]!.addAll(addressRecords);
final recordsList = _data[addressType]![seedBytesType]![derivationPath]![isChange]!;
if (recordsList.isEmpty) {
recordsList.addAll(addressRecords);
} else {
for (final addressRecord in addressRecords) {
final existingRecordIndex =
recordsList.indexWhere((record) => record.address == addressRecord.address);
if (existingRecordIndex >= 0) {
recordsList.replaceRange(existingRecordIndex, existingRecordIndex + 1, [addressRecord]);
} else {
recordsList.add(addressRecord);
}
}
}
_data[addressType]![seedBytesType]![derivationPath]![isChange] = recordsList;
}
List<BaseBitcoinAddressRecord> allRecords() {
@ -697,31 +696,30 @@ class BitcoinDiscoveredAddressesMap extends ItemsRecordMap<DiscoveredAddressReco
required BitcoinAddressType addressType,
required SeedBytesType seedBytesType,
required String derivationPath,
required BaseBitcoinAddressRecord addressRecord,
required bool isChange,
required bool discovered,
}) {
_data.putIfAbsent(
addressType,
() => {
seedBytesType: {
derivationPath: {addressRecord.isChange: discovered},
derivationPath: {isChange: discovered},
},
},
);
_data[addressType]!.putIfAbsent(
seedBytesType,
() => {
derivationPath: {addressRecord.isChange: discovered},
derivationPath: {isChange: discovered},
},
);
_data[addressType]![seedBytesType]!.putIfAbsent(
derivationPath,
() => {addressRecord.isChange: discovered},
() => {isChange: discovered},
);
_data[addressType]![seedBytesType]![derivationPath]!
.putIfAbsent(addressRecord.isChange, () => discovered);
_data[addressType]![seedBytesType]![derivationPath]!.putIfAbsent(isChange, () => discovered);
_data[addressType]![seedBytesType]![derivationPath]![addressRecord.isChange] = discovered;
_data[addressType]![seedBytesType]![derivationPath]![isChange] = discovered;
}
bool getIsDiscovered({

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';

View file

@ -65,8 +65,8 @@ class ElectrumWorkerScripthashesResponse {
static ElectrumWorkerScripthashesResponse fromJson(Map<String, dynamic> json) {
return ElectrumWorkerScripthashesResponse(
address: json['address'] as String,
scripthash: json['scripthash'] as String,
address: json['address'] as String? ?? '',
scripthash: json['scripthash'] as String? ?? '',
status: json['status'] as String?,
);
}
@ -89,9 +89,17 @@ class ElectrumWorkerScripthashesSubscribeResponse extends ElectrumWorkerResponse
@override
factory ElectrumWorkerScripthashesSubscribeResponse.fromJson(Map<String, dynamic> json) {
return ElectrumWorkerScripthashesSubscribeResponse(
result: (json['result'] as List<dynamic>)
.map((e) => ElectrumWorkerScripthashesResponse.fromJson(e as Map<String, dynamic>))
.toList(),
result: (json['result'] as List<dynamic>).map((e) {
if (e is String) {
return ElectrumWorkerScripthashesResponse.fromJson(
jsonDecode(e) as Map<String, dynamic>,
);
}
return ElectrumWorkerScripthashesResponse.fromJson(
e as Map<String, dynamic>,
);
}).toList(),
error: json['error'] as String?,
id: json['id'] as int?,
completed: json['completed'] as bool? ?? false,

View file

@ -66,13 +66,6 @@ class TransactionsPage extends StatelessWidget {
dashboardViewModel: dashboardViewModel,
key: ValueKey('transactions_page_header_row_key'),
),
Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).extension<ExchangePageTheme>()!.firstGradientBottomPanelColor,
),
)),
Expanded(
child: Observer(
builder: (_) {