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

View file

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

View file

@ -181,7 +181,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
break; break;
case ElectrumRequestMethods.scripthashesSubscribeMethod: case ElectrumRequestMethods.scripthashesSubscribeMethod:
final response = ElectrumWorkerScripthashesSubscribeResponse.fromJson(messageJson); final response = ElectrumWorkerScripthashesSubscribeResponse.fromJson(messageJson);
await onScripthashesStatusResponse(response.result.last); await onScripthashesStatusResponse(response.result);
break; break;
case ElectrumRequestMethods.getBalanceMethod: case ElectrumRequestMethods.getBalanceMethod:
final response = ElectrumWorkerGetBalanceResponse.fromJson(messageJson); final response = ElectrumWorkerGetBalanceResponse.fromJson(messageJson);
@ -309,8 +309,44 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
Timer? _updateFeeRateTimer; Timer? _updateFeeRateTimer;
static const int _autoSaveInterval = 1; static const int _autoSaveInterval = 1;
void initAddresses() { Future<bool?> initAddresses([bool? sync]) async {
throw UnimplementedError(); 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 { Future<void> init() async {
@ -324,49 +360,16 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
@override @override
Future<void> startSync() async { Future<void> startSync() async {
try { try {
// if (syncStatus is SynchronizingSyncStatus) { syncStatus = SynchronizingSyncStatus();
// return;
// }
// syncStatus = SynchronizingSyncStatus(); await subscribeForHeaders(true);
// // INFO: FIRST (always): Call subscribe for headers, wait for completion to update currentChainTip (needed for other methods) await initAddresses(false);
// await subscribeForHeaders(true);
// final responses = await subscribeForStatuses(addresses, true); await updateFeeRates();
// final addressesWithHistory = walletAddresses.allAddresses _updateFeeRateTimer ??=
// .where((e) => scripthashesWithStatus.contains(e.scriptHash)) Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
// .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);
await save(); await save();
} catch (e, stacktrace) { } catch (e, stacktrace) {
@ -1239,12 +1242,26 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
} }
@action @action
Future<void> onScripthashesStatusResponse(ElectrumWorkerScripthashesResponse result) async { Future<void> onScripthashesStatusResponse(
if (result.status == null) { List<ElectrumWorkerScripthashesResponse> result,
) async {
final noItemsWithStatus = result.length == 1 && result.first.status == null;
if (noItemsWithStatus) {
return; 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 @action
@ -1292,7 +1309,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
final addressesWithHistory = <BitcoinAddressRecord>[]; final addressesWithHistory = <BitcoinAddressRecord>[];
if (histories.isNotEmpty) { if (histories.isNotEmpty) {
await Future.wait(histories.map((addressHistory) async { for (final addressHistory in histories) {
final txs = addressHistory.txs; final txs = addressHistory.txs;
if (txs.isNotEmpty) { if (txs.isNotEmpty) {
@ -1302,20 +1319,11 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
transactionHistory.addOne(tx); transactionHistory.addOne(tx);
} }
} }
})); }
if (addressesWithHistory.isNotEmpty) { 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 @action
Future<List<ElectrumWorkerScripthashesResponse>> subscribeForStatuses([ Future<List<ElectrumWorkerScripthashesResponse>?> subscribeForStatuses([
List<BitcoinAddressRecord>? addresses, List<BitcoinAddressRecord>? addresses,
bool? wait, bool? wait,
]) async { ]) async {
@ -1686,6 +1694,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
addressByScripthashes[addressRecord.scriptHash] = addressRecord.address; addressByScripthashes[addressRecord.scriptHash] = addressRecord.address;
}); });
if (wait == true)
return ElectrumWorkerScripthashesSubscribeResponse.fromJson( return ElectrumWorkerScripthashesSubscribeResponse.fromJson(
await waitSendWorker( await waitSendWorker(
ElectrumWorkerScripthashesSubscribeRequest( ElectrumWorkerScripthashesSubscribeRequest(
@ -1694,6 +1703,15 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
), ),
), ),
).result; ).result;
else
sendWorker(
ElectrumWorkerScripthashesSubscribeRequest(
scripthashByAddress: scripthashByAddress,
addressByScripthashes: addressByScripthashes,
),
);
return null;
} }
@action @action
@ -1823,9 +1841,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();
} }
// TODO: only once
initAddresses();
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus && if (syncStatus is! NotConnectedSyncStatus &&
@ -1933,29 +1948,69 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
@action @action
Future<void> _onAddressesDiscovered(List<BitcoinAddressRecord> addresses) async { 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) { // NOTE: Did not find any status for old/wrong derivation addresses, discard them
// Wrong derivation address with no history, discard and do not add to wallet addresses // (don't add to addresses list)
// Was only used to find transactions if any if (firstAddress.seedBytesType!.isOldDerivation && noItemsWithStatus) {
await save(); walletAddresses.discoveredAddressesRecord.addDiscovered(
addressType: firstAddress.type,
seedBytesType: firstAddress.seedBytesType!,
derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
isChange: firstAddress.isChange,
discovered: true,
);
initAddresses();
return; return;
} }
walletAddresses.addAddresses(addresses); walletAddresses.addAddresses(addresses);
await syncAddresses(addresses); walletAddresses.discoveredAddressesRecord.addDiscovered(
} catch (_) {} 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 @action
void generateInitialAddresses({ Future<bool> generateInitialAddresses({
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required SeedBytesType seedBytesType, required SeedBytesType seedBytesType,
BitcoinDerivationInfo? bitcoinDerivationInfo, BitcoinDerivationInfo? bitcoinDerivationInfo,
}) { }) async {
bool discovered = false;
bitcoinDerivationInfo ??= BitcoinAddressUtils.getDerivationFromType( bitcoinDerivationInfo ??= BitcoinAddressUtils.getDerivationFromType(
addressType, addressType,
network: network, network: network,
@ -1968,15 +2023,32 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
derivationPath: bitcoinDerivationInfo.derivationPath.toString(), derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false, isChange: false,
); );
final discoveredExistingReceiveAddresses =
if (existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) { walletAddresses.discoveredAddressesRecord.getIsDiscovered(
discoverNewAddresses(
seedBytesType: seedBytesType,
isChange: false,
addressType: addressType, addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false,
);
if (!discoveredExistingReceiveAddresses &&
existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
discoverNewAddresses(
addressType: addressType,
seedBytesType: seedBytesType,
derivationInfo: bitcoinDerivationInfo, derivationInfo: bitcoinDerivationInfo,
isChange: false,
startIndex: existingReceiveAddresses.length, 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( final existingChangeAddresses = walletAddresses.addressesRecords.getRecords(
@ -1985,16 +2057,35 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
derivationPath: bitcoinDerivationInfo.derivationPath.toString(), derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true, isChange: true,
); );
final discoveredExistingChangeAddresses =
if (existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) { walletAddresses.discoveredAddressesRecord.getIsDiscovered(
discoverNewAddresses(
seedBytesType: seedBytesType,
isChange: true,
addressType: addressType, addressType: addressType,
seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true,
);
if (!discoveredExistingChangeAddresses &&
existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) {
discoverNewAddresses(
addressType: addressType,
seedBytesType: seedBytesType,
derivationInfo: bitcoinDerivationInfo, derivationInfo: bitcoinDerivationInfo,
isChange: true,
startIndex: existingChangeAddresses.length, 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({ void discoverNewAddresses({
@ -2003,8 +2094,9 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required BitcoinDerivationInfo derivationInfo, required BitcoinDerivationInfo derivationInfo,
int? startIndex, int? startIndex,
List<ElectrumWorkerScripthashesResponse>? scripthashStatuses,
}) async { }) async {
final count = isChange final countToDiscover = isChange
? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT
: ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT; : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT;
@ -2018,19 +2110,20 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
startIndex ??= recordList.length; startIndex ??= recordList.length;
late bool needsToDiscover; 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; needsToDiscover = true;
} else if (recordList.length == count) { } else if (recordList.length == countToDiscover) {
needsToDiscover = recordList.any((record) => !record.getIsUsed()); needsToDiscover = recordList.any((record) => !record.getIsUsed());
} else { } else {
needsToDiscover = recordList.sublist(recordList.length - count).any( needsToDiscover = recordList.sublist(recordList.length - countToDiscover).any(
(record) => record.getIsUsed(), (record) {
return scripthashStatuses?.any(
(scripthashStatus) =>
scripthashStatus.scripthash == (record as BitcoinAddressRecord).scriptHash,
) ??
record.getIsUsed();
},
); );
} }
@ -2041,7 +2134,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
workerSendPort!.send( workerSendPort!.send(
ElectrumWorkerDiscoverAddressesRequest( ElectrumWorkerDiscoverAddressesRequest(
id: _messageId, id: _messageId,
count: count, count: countToDiscover,
walletType: type, walletType: type,
startIndex: startIndex, startIndex: startIndex,
seedBytesType: seedBytesType, seedBytesType: seedBytesType,

View file

@ -28,7 +28,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
}) : addressesRecords = initialAddressesRecords ?? BitcoinAddressRecordMap(), }) : addressesRecords = initialAddressesRecords ?? BitcoinAddressRecordMap(),
activeIndexByType = initialActiveAddressIndex ?? {}, activeIndexByType = initialActiveAddressIndex ?? {},
discoveredAddressTypes = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(), discoveredAddressesRecord = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(),
addressPageType = initialAddressPageType ?? addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null (walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!) ? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
@ -49,6 +49,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get xpub => hdWallet.publicKey.toExtended; String get xpub => hdWallet.publicKey.toExtended;
String get xpriv => hdWallet.privateKey.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 get walletSeedBytesType => hdWallets.containsKey(SeedBytesType.bip39)
? SeedBytesType.bip39 ? SeedBytesType.bip39
: (hdWallets.containsKey(SeedBytesType.electrum) : (hdWallets.containsKey(SeedBytesType.electrum)
@ -83,11 +95,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
bool isEnabledAutoGenerateNewAddress = true; bool isEnabledAutoGenerateNewAddress = true;
@observable @observable
BitcoinDiscoveredAddressesMap discoveredAddressTypes; BitcoinDiscoveredAddressesMap discoveredAddressesRecord;
@observable @observable
BitcoinAddressRecordMap addressesRecords; BitcoinAddressRecordMap addressesRecords;
Set<String> get hiddenAddresses =>
allAddresses.where((address) => address.isHidden).map((address) => address.address).toSet();
@observable @observable
List<BitcoinAddressRecord> _allAddresses = []; List<BitcoinAddressRecord> _allAddresses = [];
@computed @computed
@ -257,12 +272,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
void resetActiveAddress() { void resetActiveAddress() {
try {
final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>(); final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull( activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
(addressRecord) => addressRecord.index == activeIndexByType[addressPageType], (addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
) ?? ) ??
activeReceiveAddresses.first; activeReceiveAddresses.first;
} catch (_) {}
} }
@override @override
@ -272,11 +289,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return activeBitcoinAddress!.address; return activeBitcoinAddress!.address;
} }
final receiveAddress = selectedReceiveAddresses final receiveAddress = nextReceiveAddress?.address;
.firstWhereOrNull(
(addr) => addr.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && !addr.isChange,
)
?.address;
return receiveAddress ?? ''; 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 @action
void addAddresses(List<BitcoinAddressRecord> addresses) { void addAddresses(List<BitcoinAddressRecord> addresses) {
final firstAddress = addresses.first; final firstAddress = addresses.first;
@ -469,28 +466,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
isChange: firstAddress.isChange, isChange: firstAddress.isChange,
addressRecords: addresses, 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(); updateAllAddresses();
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json['addressesRecords'] = addressesRecords.toJson(); json['addressesRecords'] = addressesRecords.toJson();
json['discoveredAddressTypes'] = discoveredAddressTypes.toJson(); json['discoveredAddressesRecord'] = discoveredAddressesRecord.toJson();
json['addressPageType'] = addressPageType.toString(); json['addressPageType'] = addressPageType.toString();
json['activeIndexByType'] = activeIndexByType.map( json['activeIndexByType'] = activeIndexByType.map(
(key, value) => MapEntry(key.toString(), value), (key, value) => MapEntry(key.toString(), value),
@ -508,7 +490,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return { return {
'allAddresses': addresses, 'allAddresses': addresses,
'addressesRecords': data['addressesRecords'] as Map<String, dynamic>?, '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?, 'addressPageType': data['addressPageType'] as String?,
'activeIndexByType': (data['activeIndexByType'] as Map<dynamic, dynamic>?) ?? {}, 'activeIndexByType': (data['activeIndexByType'] as Map<dynamic, dynamic>?) ?? {},
}; };
@ -529,9 +511,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
json['addressesRecords'] as Map<String, dynamic>, json['addressesRecords'] as Map<String, dynamic>,
); );
if (json['discoveredAddressTypes'] != null) if (json['discoveredAddressesRecord'] != null)
initialDiscoveredAddresses ??= BitcoinDiscoveredAddressesMap.fromJson( initialDiscoveredAddresses ??= BitcoinDiscoveredAddressesMap.fromJson(
json['discoveredAddressTypes'] as Map<String, dynamic>, json['discoveredAddressesRecord'] as Map<String, dynamic>,
); );
return ElectrumWalletAddresses( return ElectrumWalletAddresses(
@ -612,7 +594,24 @@ class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
); );
_data[addressType]![seedBytesType]![derivationPath]!.putIfAbsent(isChange, () => []); _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() { List<BaseBitcoinAddressRecord> allRecords() {
@ -697,31 +696,30 @@ class BitcoinDiscoveredAddressesMap extends ItemsRecordMap<DiscoveredAddressReco
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
required SeedBytesType seedBytesType, required SeedBytesType seedBytesType,
required String derivationPath, required String derivationPath,
required BaseBitcoinAddressRecord addressRecord, required bool isChange,
required bool discovered, required bool discovered,
}) { }) {
_data.putIfAbsent( _data.putIfAbsent(
addressType, addressType,
() => { () => {
seedBytesType: { seedBytesType: {
derivationPath: {addressRecord.isChange: discovered}, derivationPath: {isChange: discovered},
}, },
}, },
); );
_data[addressType]!.putIfAbsent( _data[addressType]!.putIfAbsent(
seedBytesType, seedBytesType,
() => { () => {
derivationPath: {addressRecord.isChange: discovered}, derivationPath: {isChange: discovered},
}, },
); );
_data[addressType]![seedBytesType]!.putIfAbsent( _data[addressType]![seedBytesType]!.putIfAbsent(
derivationPath, derivationPath,
() => {addressRecord.isChange: discovered}, () => {isChange: discovered},
); );
_data[addressType]![seedBytesType]![derivationPath]! _data[addressType]![seedBytesType]![derivationPath]!.putIfAbsent(isChange, () => discovered);
.putIfAbsent(addressRecord.isChange, () => discovered);
_data[addressType]![seedBytesType]![derivationPath]![addressRecord.isChange] = discovered; _data[addressType]![seedBytesType]![derivationPath]![isChange] = discovered;
} }
bool getIsDiscovered({ bool getIsDiscovered({

View file

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

View file

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

@ -66,13 +66,6 @@ class TransactionsPage extends StatelessWidget {
dashboardViewModel: dashboardViewModel, dashboardViewModel: dashboardViewModel,
key: ValueKey('transactions_page_header_row_key'), 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( Expanded(
child: Observer( child: Observer(
builder: (_) { builder: (_) {