diff --git a/cw_bitcoin/lib/bitcoin_address_record.dart b/cw_bitcoin/lib/bitcoin_address_record.dart index 3e496348a..8687c2061 100644 --- a/cw_bitcoin/lib/bitcoin_address_record.dart +++ b/cw_bitcoin/lib/bitcoin_address_record.dart @@ -68,7 +68,19 @@ class BaseBitcoinAddressRecord { final String _derivationPath; - String get derivationPath => _derivationPath; + String get indexedDerivationPath => _derivationPath; + + bool isUnusedReceiveAddress() { + return !isChange && !getIsUsed(); + } + + bool getIsUsed() { + return isUsed || txCount != 0 || balance != 0; + } + + // An address not yet used for receiving funds + bool getIsStillReceiveable(bool autoGenerateAddresses) => + !autoGenerateAddresses || (!getIsUsed() && !isHidden); String toJSON() => json.encode({ 'address': address, @@ -82,7 +94,7 @@ class BaseBitcoinAddressRecord { 'type': type.toString(), 'runtimeType': runtimeType.toString(), 'seedBytesType': seedBytesType?.value, - 'derivationPath': derivationPath, + 'derivationPath': indexedDerivationPath, }); static BaseBitcoinAddressRecord buildFromJSON( @@ -146,7 +158,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { String _derivationPath = ''; @override - String get derivationPath => _derivationPath; + String get indexedDerivationPath => _derivationPath; BitcoinAddressRecord( super.address, { @@ -177,6 +189,10 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { .addElem(Bip32KeyIndex(isChange ? 1 : 0)) .addElem(Bip32KeyIndex(index)) .toString(); + + if (getShouldHideAddressByDefault()) { + isHidden = true; + } } factory BitcoinAddressRecord.fromJSON( @@ -201,7 +217,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { name: base.name, balance: base.balance, type: base.type, - derivationPath: base.derivationPath, + derivationPath: base.indexedDerivationPath, scriptHash: decoded['scriptHash'] as String?, derivationInfo: derivationInfoSnp == null ? seedBytesType != null && !seedBytesType.isElectrum @@ -217,6 +233,19 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord { late String scriptHash; + // Manages the wrong derivation path addresses + bool getShouldHideAddressByDefault() { + final path = derivationInfo.derivationPath.toString(); + if (seedBytesType!.isElectrum) { + return path != BitcoinDerivationInfos.ELECTRUM.derivationPath.toString(); + } + + // TODO: pass network + // return path.toString() != BitcoinDerivationInfos.LITECOIN.derivationPath.toString(); + // return path.toString() != BitcoinDerivationPaths.BCH; + return path != BitcoinAddressUtils.getDerivationFromType(type).derivationPath.toString(); + } + @override String toJSON() { final m = json.decode(super.toJSON()) as Map; @@ -252,7 +281,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { String _derivationPath; @override - String get derivationPath => _derivationPath; + String get indexedDerivationPath => _derivationPath; int get labelIndex => index; final String? labelHex; @@ -301,7 +330,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord { balance: base.balance, type: base.type, labelIndex: base.index, - derivationPath: base.derivationPath, + derivationPath: base.indexedDerivationPath, labelHex: decoded['labelHex'] as String?, ); } @@ -320,7 +349,7 @@ class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord { final String spAddress; @override - String get derivationPath => ''; + String get indexedDerivationPath => ''; BitcoinReceivedSPAddressRecord( super.address, { @@ -390,7 +419,7 @@ class LitecoinMWEBAddressRecord extends BaseBitcoinAddressRecord { String _derivationPath = ''; @override - String get derivationPath => _derivationPath; + String get indexedDerivationPath => _derivationPath; LitecoinMWEBAddressRecord( super.address, { diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 30a2ce748..4b2f23639 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -99,7 +99,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet } autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress; }); } @@ -121,34 +121,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet // If already loaded, no need to generate/discover all initial addresses // so skip // if (!walletAddresses.loadedFromNewSnapshot) { - for (final seedBytesType in hdWallets.keys) { - generateInitialAddresses( - addressType: SegwitAddressType.p2wpkh, - seedBytesType: seedBytesType, - ); + for (final walletAddressType in walletAddresses.walletAddressTypes) { + if (isHardwareWallet && walletAddressType != SegwitAddressType.p2wpkh) continue; - if (!isHardwareWallet) { + for (final seedBytesType in hdWallets.keys) { generateInitialAddresses( - addressType: P2pkhAddressType.p2pkh, - seedBytesType: seedBytesType, - ); - - generateInitialAddresses( - addressType: P2shAddressType.p2wpkhInP2sh, - seedBytesType: seedBytesType, - ); - - generateInitialAddresses( - addressType: SegwitAddressType.p2tr, - seedBytesType: seedBytesType, - ); - - generateInitialAddresses( - addressType: SegwitAddressType.p2wsh, + addressType: walletAddressType, seedBytesType: seedBytesType, ); } } + // } } @@ -517,7 +500,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet @override @action Future updateAllUnspents([ - Set? scripthashes, + List? scripthashes, bool? wait, ]) async { scripthashes ??= this.walletAddresses.allScriptHashes; diff --git a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart index abf9d8e0c..2ed662b9f 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_addresses.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_addresses.dart @@ -19,10 +19,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S required super.hdWallets, required super.network, required super.isHardwareWallet, - super.initialAddresses, - super.initialDiscoveredAddresses, - super.initialReceiveAddressesMapped, - super.initialChangeAddressesMapped, + super.initialAddressesRecords, + super.initialActiveAddressIndex, + super.initialAddressPageType, this.loadedFromNewSnapshot = false, List? initialSilentAddresses, List? initialReceivedSPAddresses, @@ -41,15 +40,14 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S static const _OLD_SP_PATH = "m/352'/1'/0'/#'/0"; + // NOTE: ordered in priority: eg. p2wpkh always first, most used address, etc. @override - final walletAddressTypes = BITCOIN_ADDRESS_TYPES; - - static const BITCOIN_ADDRESS_TYPES = [ + final walletAddressTypes = [ SegwitAddressType.p2wpkh, - P2pkhAddressType.p2pkh, SegwitAddressType.p2tr, - SegwitAddressType.p2wsh, P2shAddressType.p2wpkhInP2sh, + P2pkhAddressType.p2pkh, + SegwitAddressType.p2wsh, ]; @observable @@ -139,7 +137,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S @override @action - void resetActiveChangeAddress() { + void resetActiveAddress() { if (activeSilentAddress != null && (activeSilentAddress!.isChange || activeSilentAddress!.isHidden)) { try { @@ -154,7 +152,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S } catch (_) {} } - super.resetActiveChangeAddress(); + super.resetActiveAddress(); } @override @@ -367,7 +365,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S (addressRecord) => !addressRecord.isChange && addressRecord.labelIndex == 0 && - addressRecord.derivationPath != oldSpendPath.toString(), + addressRecord.indexedDerivationPath != oldSpendPath.toString(), ); final list = [primaryAddress.address]; @@ -376,7 +374,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S (addressRecord) => !addressRecord.isChange && addressRecord.labelIndex == 0 && - addressRecord.derivationPath == oldSpendPath.toString(), + addressRecord.indexedDerivationPath == oldSpendPath.toString(), ); // Do it like this to keep in order, @@ -466,12 +464,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S hdWallets: hdWallets, network: network, isHardwareWallet: isHardwareWallet, - initialAddresses: electrumJson.allAddresses, - initialDiscoveredAddresses: electrumJson.discoveredAddresses, - initialReceiveAddressesMapped: - electrumJson.receiveAddressesMapped, - initialChangeAddressesMapped: - electrumJson.changeAddressesMapped, + initialAddressesRecords: electrumJson.addressesRecords, + initialAddressPageType: electrumJson.addressPageType, + initialActiveAddressIndex: electrumJson.activeIndexByType, initialSilentAddresses: initialSilentAddresses, initialReceivedSPAddresses: initialReceivedSPAddresses, loadedFromNewSnapshot: snp['loadedFromNewSnapshot'] as bool? ?? false, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 65fa917d4..3580f93e4 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -318,7 +318,6 @@ abstract class ElectrumWalletBase } Future init() async { - await walletAddresses.init(); await transactionHistory.init(); _autoSaveTimer = @@ -475,7 +474,7 @@ abstract class ElectrumWalletBase privkey = ECPrivate.fromBip32( bip32: hdWallets[addressRecord.seedBytesType]!.derive( - Bip32PathParser.parse(addressRecord.derivationPath), + Bip32PathParser.parse(addressRecord.indexedDerivationPath), ), ); } @@ -495,7 +494,7 @@ abstract class ElectrumWalletBase } if (utx.bitcoinAddressRecord is BitcoinAddressRecord) { - final derivationPath = utx.bitcoinAddressRecord.derivationPath; + final derivationPath = utx.bitcoinAddressRecord.indexedDerivationPath; publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); } @@ -637,7 +636,7 @@ abstract class ElectrumWalletBase isChange: true, )); - final changeDerivationPath = changeAddress.derivationPath.toString(); + final changeDerivationPath = changeAddress.indexedDerivationPath.toString(); utxoDetails.publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath('', changeDerivationPath); @@ -1113,7 +1112,7 @@ abstract class ElectrumWalletBase @action Future updateAllUnspents([ - Set? scripthashes, + List? scripthashes, bool? wait, ]) async { scripthashes ??= walletAddresses.allScriptHashes; @@ -1540,10 +1539,10 @@ abstract class ElectrumWalletBase } // Identify all change outputs - final changeAddresses = walletAddresses.allChangeAddresses; + final changeAddresses = walletAddresses.allAddresses.where((element) => element.isChange); final List changeOutputs = outputs - .where((output) => changeAddresses - .any((element) => element.address == output.address.toAddress(network))) + .where((output) => + changeAddresses.any((addr) => addr.address == output.address.toAddress(network))) .toList(); int totalChangeAmount = @@ -1931,20 +1930,7 @@ abstract class ElectrumWalletBase try { final scripthashByAddress = await subscribeForStatuses(addresses, true); - walletAddresses.discoveredAddresses.putIfAbsent( - addresses.first.type, - () => { - addresses.first.seedBytesType!: {addresses.first.isChange: true} - }, - ); - walletAddresses.discoveredAddresses[addresses.first.type]!.putIfAbsent( - addresses.first.seedBytesType!, - () => { - addresses.first.isChange: true, - }, - ); - walletAddresses.discoveredAddresses[addresses.first.type]![addresses.first.seedBytesType!]! - .putIfAbsent(addresses.first.isChange, () => true); + walletAddresses.addAddresses(addresses); if (addresses.first.seedBytesType!.isOldDerivation && scripthashByAddress.length == 1) { // Wrong derivation address with no history, discard and do not add to wallet addresses @@ -1969,12 +1955,14 @@ abstract class ElectrumWalletBase isElectrum: seedBytesType.isElectrum, ); - final existingReceiveAddresses = (walletAddresses.receiveAddressesMapped[addressType] - ?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ?? - []); + final existingReceiveAddresses = walletAddresses.addressesRecords.getRecords( + addressType: addressType, + seedBytesType: seedBytesType, + derivationPath: bitcoinDerivationInfo.derivationPath.toString(), + isChange: false, + ); - if (existingReceiveAddresses.length < - ElectrumWalletAddressesBase.defaultReceiveAddressesCount) { + if (existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) { discoverNewAddresses( seedBytesType: seedBytesType, isChange: false, @@ -1984,11 +1972,14 @@ abstract class ElectrumWalletBase ); } - final existingChangeAddresses = (walletAddresses.receiveAddressesMapped[addressType] - ?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ?? - []); + final existingChangeAddresses = walletAddresses.addressesRecords.getRecords( + addressType: addressType, + seedBytesType: seedBytesType, + derivationPath: bitcoinDerivationInfo.derivationPath.toString(), + isChange: true, + ); - if (existingChangeAddresses.length < ElectrumWalletAddressesBase.defaultChangeAddressesCount) { + if (existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) { discoverNewAddresses( seedBytesType: seedBytesType, isChange: true, @@ -2003,48 +1994,45 @@ abstract class ElectrumWalletBase List addresses, ) async { final newAddresses = []; - final discoveredAddresses = - >>>{}; + final discoveredAddresses = BitcoinDiscoveredAddressesMap(); final usedAddresses = addresses.isNotEmpty ? addresses - : walletAddresses.allAddresses.where(walletAddresses.getIsUsed).toList(); + : walletAddresses.allAddresses.where((addr) => addr.getIsUsed()).toList(); for (final usedAddress in usedAddresses) { - final isChange = usedAddress.isChange; - - final alreadyDiscoveredSeedType = discoveredAddresses[usedAddress.seedBytesType!]; - final alreadyDiscoveredAddressType = alreadyDiscoveredSeedType?[usedAddress.type]; - final alreadyDiscoveredDerivationType = - alreadyDiscoveredAddressType?[usedAddress.derivationInfo.derivationType]; - final isAlreadyDiscovered = alreadyDiscoveredDerivationType?.contains(isChange) ?? false; + final isAlreadyDiscovered = discoveredAddresses.getIsDiscovered( + addressType: usedAddress.type, + seedBytesType: usedAddress.seedBytesType!, + derivationPath: usedAddress.derivationInfo.derivationPath.toString(), + isChange: usedAddress.isChange, + ); if (isAlreadyDiscovered) { continue; } - final matchingAddressList = walletAddresses.allAddresses.where( - (addr) => - addr.seedBytesType! == usedAddress.seedBytesType! && - addr.type == usedAddress.type && - addr.derivationInfo.derivationType == usedAddress.derivationInfo.derivationType && - addr.isChange == isChange, + final matchingAddressList = walletAddresses.addressesRecords.getRecords( + seedBytesType: usedAddress.seedBytesType!, + addressType: usedAddress.type, + derivationPath: usedAddress.derivationInfo.derivationPath.toString(), + isChange: usedAddress.isChange, ); final totalMatchingAddresses = matchingAddressList.length; - final matchingGapLimit = (isChange - ? ElectrumWalletAddressesBase.defaultChangeAddressesCount - : ElectrumWalletAddressesBase.defaultReceiveAddressesCount); + final matchingGapLimit = usedAddress.isChange + ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT + : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT; final isAddressUsedAboveGap = usedAddress.index >= totalMatchingAddresses - matchingGapLimit; if (isAddressUsedAboveGap) { - discoveredAddresses.putIfAbsent(usedAddress.seedBytesType!, () => {}); - discoveredAddresses[usedAddress.seedBytesType!]!.putIfAbsent(usedAddress.type, () => {}); - discoveredAddresses[usedAddress.seedBytesType!]![usedAddress.type]! - .putIfAbsent(usedAddress.derivationInfo.derivationType, () => []); - discoveredAddresses[usedAddress.seedBytesType!]![usedAddress.type]![ - usedAddress.derivationInfo.derivationType]! - .add(isChange); + // discoveredAddresses.addDiscovered( + // addressType: addressType, + // seedBytesType: seedBytesType, + // derivationPath: derivationPath, + // addressRecord: addressRecord, + // discovered: discovered, + // ); // final theseAddresses = discoverNewAddresses( // isChange: isChange, @@ -2080,31 +2068,36 @@ abstract class ElectrumWalletBase required BitcoinDerivationInfo derivationInfo, int? startIndex, }) async { - if (walletAddresses.discoveredAddresses[addressType]?[seedBytesType]?[isChange] == true) { + final count = isChange + ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT + : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT; + final recordList = walletAddresses.addressesRecords.getRecords( + seedBytesType: seedBytesType, + addressType: addressType, + derivationPath: derivationInfo.derivationPath.toString(), + isChange: isChange, + ); + + startIndex ??= recordList.length; + + final isDiscovered = walletAddresses.discoveredAddresses.getIsDiscovered( + addressType: addressType, + seedBytesType: seedBytesType, + derivationPath: derivationInfo.derivationPath.toString(), + isChange: isChange, + ); + + if (isDiscovered) { return; } - final count = isChange - ? ElectrumWalletAddressesBase.defaultChangeAddressesCount - : ElectrumWalletAddressesBase.defaultReceiveAddressesCount; - - startIndex ??= ((isChange - ? walletAddresses.changeAddressesMapped[addressType] - : walletAddresses.receiveAddressesMapped[addressType])?[seedBytesType] - ?[derivationInfo.derivationPath.toString()] ?? - []) - .length; - workerSendPort!.send( ElectrumWorkerDiscoverAddressesRequest( id: _messageId, count: count, + walletType: type, startIndex: startIndex, seedBytesType: seedBytesType, - shouldHideAddress: walletAddresses.getShouldHideAddress( - derivationInfo.derivationPath, - addressType, - ), derivationInfo: derivationInfo, isChange: isChange, addressType: addressType, diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 3cffe299d..1dea36396 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'package:collection/collection.dart'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -12,54 +13,51 @@ part 'electrum_wallet_addresses.g.dart'; class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { + static const INITIAL_RECEIVE_COUNT = 22; + static const INITIAL_CHANGE_COUNT = 17; + static const GAP = 20; + ElectrumWalletAddressesBase( WalletInfo walletInfo, { required this.hdWallets, required this.network, required this.isHardwareWallet, - List? initialAddresses, - Map? initialRegularAddressIndex, - Map? initialChangeAddressIndex, + BitcoinAddressRecordMap? initialAddressesRecords, + Map? initialActiveAddressIndex, + BitcoinDiscoveredAddressesMap? initialDiscoveredAddresses, BitcoinAddressType? initialAddressPageType, - Map>>> - initialReceiveAddressesMapped = const {}, - Map>>> - initialChangeAddressesMapped = const {}, - Map>> initialDiscoveredAddresses = - const {}, - }) : _allAddresses = ObservableList.of(initialAddresses ?? []), - currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {}, - currentChangeAddressIndexByType = initialChangeAddressIndex ?? {}, - discoveredAddresses = initialDiscoveredAddresses, - receiveAddressesMapped = initialReceiveAddressesMapped, - changeAddressesMapped = initialChangeAddressesMapped, + }) : addressesRecords = initialAddressesRecords ?? BitcoinAddressRecordMap(), + activeIndexByType = initialActiveAddressIndex ?? {}, + discoveredAddresses = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(), addressPageType = initialAddressPageType ?? (walletInfo.addressPageType != null ? BitcoinAddressType.fromValue(walletInfo.addressPageType!) : SegwitAddressType.p2wpkh), - super(walletInfo); - - static const defaultReceiveAddressesCount = 22; - static const defaultChangeAddressesCount = 17; - static const gap = 20; + super(walletInfo) { + updateAllAddresses(); + } final walletAddressTypes = []; - final ObservableList _allAddresses; + @observable + BitcoinDiscoveredAddressesMap discoveredAddresses; + @observable + BitcoinAddressRecordMap addressesRecords; @observable - // { BitcoinAddressType: { SeedBytesType: { isChange: true = discovered } } } - Map>> discoveredAddresses; + List _allAddresses = []; + @computed + List get allAddresses => _allAddresses; - @observable - // { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } } - Map>>> - receiveAddressesMapped; + @action + List updateAllAddresses() { + _allAddresses = addressesRecords.allRecords().whereType().toList(); - @observable - // { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } } - Map>>> - changeAddressesMapped; + updateAllScriptHashes(); + updateSelectedReceiveAddresses(); + updateSelectedChangeAddresses(); + return _allAddresses; + } final BasedUtxoNetwork network; @@ -81,151 +79,192 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @observable BitcoinAddressType addressPageType; - @computed - List get allChangeAddresses => - _allAddresses.where((addr) => addr.isChange).toList(); + @action + Future setAddressPageType(BitcoinAddressType type) async { + addressPageType = type; + updateSelectedReceiveAddresses(); + updateSelectedChangeAddresses(); + + walletInfo.addressPageType = addressPageType.toString(); + await updateAddressesInBox(); + } + + @computed BitcoinDerivationInfo get _defaultAddressPageDerivationInfo => BitcoinAddressUtils.getDerivationFromType( addressPageType, isElectrum: walletSeedBytesType.isElectrum, ); + @observable + List _selectedReceiveAddresses = []; @computed - List get selectedReceiveAddresses { - return receiveAddressesMapped[addressPageType]?[walletSeedBytesType] - ?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ?? - []; + List get selectedReceiveAddresses => _selectedReceiveAddresses; + + @action + List updateSelectedReceiveAddresses() { + _selectedReceiveAddresses = addressesRecords.getRecords( + addressType: addressPageType, + seedBytesType: walletSeedBytesType, + derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(), + isChange: false, + ); + updateNextReceiveAddress(); + return _selectedReceiveAddresses; } + @observable + BitcoinAddressRecord? _nextReceiveAddress; @computed - List get selectedChangeAddresses => - changeAddressesMapped[addressPageType]?[walletSeedBytesType] - ?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ?? - []; + BitcoinAddressRecord? get nextReceiveAddress => _nextReceiveAddress; - @computed - List get allAddresses => _allAddresses.toList(); + @action + BitcoinAddressRecord? updateNextReceiveAddress() { + final receiveAddresses = selectedReceiveAddresses.whereType(); + if (receiveAddresses.isEmpty) { + return null; + } - @computed - Set get allScriptHashes => - _allAddresses.map((addressRecord) => addressRecord.scriptHash).toSet(); - - BitcoinAddressRecord getFromAddresses(String address) { - return _allAddresses.firstWhere((element) => element.address == address); + _nextReceiveAddress = receiveAddresses.firstWhereOrNull( + (addressRecord) => + addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && + !addressRecord.isChange, + ) ?? + receiveAddresses.first; + return _nextReceiveAddress; } + @observable + List _selectedChangeAddresses = []; + @computed + List get selectedChangeAddresses => _selectedChangeAddresses; + + @action + List updateSelectedChangeAddresses() { + _selectedChangeAddresses = addressesRecords.getRecords( + addressType: addressPageType, + seedBytesType: walletSeedBytesType, + derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(), + isChange: true, + ); + updateNextChangeAddress(); + return _selectedChangeAddresses; + } + + @observable + BitcoinAddressRecord? _nextChangeAddress; + @computed + BitcoinAddressRecord? get nextChangeAddress => _nextChangeAddress; + + @action + BitcoinAddressRecord? updateNextChangeAddress() { + final changeAddresses = selectedChangeAddresses.whereType(); + if (changeAddresses.isEmpty) { + return null; + } + + _nextChangeAddress = changeAddresses.firstWhereOrNull( + (addressRecord) => + addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && + addressRecord.isChange, + ) ?? + changeAddresses.first; + return _nextChangeAddress; + } + + @observable + List _allScriptHashes = []; + @computed + List get allScriptHashes => _allScriptHashes; + + @action + List updateAllScriptHashes() { + _allScriptHashes.clear(); + _allScriptHashes.addAll(allAddresses.map((address) => address.scriptHash)); + return _allScriptHashes; + } + + BaseBitcoinAddressRecord getFromAddresses(String address) => + allAddresses.firstWhere((element) => element.address == address); + // TODO: feature with toggle to switch change address type @observable BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh; @observable - BitcoinAddressRecord? activeAddress; + BitcoinAddressRecord? activeBitcoinAddress; - // TODO: map by type @observable - int activeAddressIndex = 0; + bool isEnabledAutoGenerateNewAddress = true; - @override @action - void resetActiveChangeAddress() { - if (isEnabledAutoGenerateSubaddress) { - try { - activeAddress = _allAddresses.firstWhere( - (addressRecord) => - addressRecord.type == addressPageType && - addressRecord.index == activeAddressIndex && - getIsReceive(addressRecord), - ); + void resetActiveAddress() { + final activeReceiveAddresses = selectedReceiveAddresses.whereType(); - return; - } catch (_) {} - - try { - activeAddress = _allAddresses.firstWhere( - (addressRecord) => addressRecord.type == addressPageType && getIsReceive(addressRecord), - ); - return; - } catch (_) {} - } - - try { - activeAddress = _allAddresses.firstWhere( - (addressRecord) => - addressRecord.type == addressPageType && - addressRecord.index == activeAddressIndex && - !addressRecord.isChange && - !addressRecord.isHidden, - ); - } catch (_) {} + activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull( + (addressRecord) => addressRecord.index == activeIndexByType[addressPageType], + ) ?? + activeReceiveAddresses.first; } @override @computed String get address { - if (activeAddress != null) { - return activeAddress!.address; + if (activeBitcoinAddress != null) { + return activeBitcoinAddress!.address; } - String? receiveAddress = ""; - - if (isEnabledAutoGenerateSubaddress && selectedReceiveAddresses.isEmpty) { - receiveAddress = - selectedReceiveAddresses.firstWhereOrNull((addr) => !getIsUsed(addr))?.address; - } else { - receiveAddress = selectedReceiveAddresses.first.address; - } + final receiveAddress = selectedReceiveAddresses + .firstWhereOrNull( + (addr) => addr.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && !addr.isChange, + ) + ?.address; return receiveAddress ?? ''; } - @observable - bool isEnabledAutoGenerateSubaddress = true; - @override set address(String addr) { - try { - final addressRecord = _allAddresses.firstWhere( - (addressRecord) => addressRecord.address == addr, - ); + final addressRecord = _allAddresses.firstWhereOrNull( + (addressRecord) => addressRecord.address == addr, + ); - activeAddress = addressRecord; + activeBitcoinAddress = addressRecord; - if (getIsReceive(addressRecord)) { - activeAddressIndex = addressRecord.index; - } - } catch (e) { - // printV("ElectrumWalletAddressBase: set address ($addr): $e"); + if (addressRecord != null && + addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && + !addressRecord.isChange) { + activeAddressIndex = addressRecord.index; } } @override - String get primaryAddress => _allAddresses.first.address; + @computed + String get primaryAddress => + selectedReceiveAddresses + .firstWhereOrNull( + (addressRecord) => + addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && + !addressRecord.isChange, + ) + ?.address ?? + ''; - Map currentReceiveAddressIndexByType; + Map activeIndexByType; - int get currentReceiveAddressIndex => - currentReceiveAddressIndexByType[addressPageType.toString()] ?? 0; + int get activeAddressIndex => activeIndexByType[addressPageType] ?? 0; - void set currentReceiveAddressIndex(int index) => - currentReceiveAddressIndexByType[addressPageType.toString()] = index; - - Map currentChangeAddressIndexByType; - - int get currentChangeAddressIndex => - currentChangeAddressIndexByType[addressPageType.toString()] ?? 0; - - void set currentChangeAddressIndex(int index) => - currentChangeAddressIndexByType[addressPageType.toString()] = index; + set activeAddressIndex(int index) => activeIndexByType[addressPageType] = index; @override Future init() async { - await updateAddressesInBox(); + throw UnimplementedError(); } Future getChangeAddress() async { final address = selectedChangeAddresses.firstWhere( - (addr) => addr.isChange && !getIsUsed(addr) && addr.type == changeAddressType, + (addr) => !addr.getIsUsed() && addr.type == changeAddressType, ); return address; } @@ -250,7 +289,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { name: label, type: addressPageType, network: network, - derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressPageType), + derivationInfo: derivationInfo, seedBytesType: walletSeedBytesType, ); return address; @@ -362,14 +401,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void updateAddress(String address, String label) { BaseBitcoinAddressRecord? foundAddress; - _allAddresses.forEach((addressRecord) { + + for (final addressRecord in _allAddresses) { if (addressRecord.address == address) { foundAddress = addressRecord; + break; } - }); + } + // TODO: verify this updates and keeps on re-open if (foundAddress != null) { - foundAddress!.setNewName(label); + foundAddress.setNewName(label); } } @@ -391,82 +433,25 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { @action void addAddresses(Iterable addresses) { - this._allAddresses.addAll(addresses); - final firstAddress = addresses.first; - final addressesMap = { - ...(firstAddress.isChange ? changeAddressesMapped : receiveAddressesMapped) - }; - addressesMap.putIfAbsent( - firstAddress.type, - () => { - firstAddress.seedBytesType!: { - firstAddress.derivationInfo.derivationPath.toString(): addresses.toList(), - }, - }, + addressesRecords.addAddress( + addressType: firstAddress.type, + seedBytesType: firstAddress.seedBytesType!, + derivationPath: firstAddress.derivationInfo.derivationPath.toString(), + addressRecord: firstAddress, ); - addressesMap[firstAddress.type]!.putIfAbsent( - firstAddress.seedBytesType!, - () => { - firstAddress.derivationInfo.derivationPath.toString(): addresses.toList(), - }, - ); - addressesMap[firstAddress.type]![firstAddress.seedBytesType]!.putIfAbsent( - firstAddress.derivationInfo.derivationPath.toString(), - () => addresses.toList(), - ); - - if (firstAddress.isChange) { - changeAddressesMapped = addressesMap; - } else { - receiveAddressesMapped = addressesMap; - } - } - - @action - Future setAddressType(BitcoinAddressType type) async { - addressPageType = type; - walletInfo.addressPageType = addressPageType.toString(); - await walletInfo.save(); - } - - bool isUnusedReceiveAddress(BaseBitcoinAddressRecord addr) { - return !addr.isChange && !getIsUsed(addr); + updateAllAddresses(); } Map toJson() { final json = {}; - json['allAddresses'] = _allAddresses.map((address) => address.toJSON()).toList(); + json['addressesRecords'] = addressesRecords.toJson(); + json['discoveredAddresses'] = discoveredAddresses.toJson(); json['addressPageType'] = addressPageType.toString(); - json['discoveredAddresses'] = discoveredAddresses.map((addressType, v) { - return MapEntry(addressType.value, v.map((seedBytesType, v) { - return MapEntry(seedBytesType.value, v.map((isChange, v) { - return MapEntry(isChange.toString(), v); - })); - })); - }); - - json['receiveAddressesMapped'] = receiveAddressesMapped.map((addressType, v) { - return MapEntry(addressType.value, v.map((seedBytesType, v) { - return MapEntry(seedBytesType.value, v.map((derivationPath, v) { - return MapEntry( - derivationPath.toString(), - v.map((address) => address.toJSON()).toList(), - ); - })); - })); - }); - json['changeAddressesMapped'] = receiveAddressesMapped.map((addressType, v) { - return MapEntry(addressType.value, v.map((seedBytesType, v) { - return MapEntry(seedBytesType.value, v.map((derivationPath, v) { - return MapEntry( - derivationPath.toString(), - v.map((address) => address.toJSON()).toList(), - ); - })); - })); - }); + json['activeIndexByType'] = activeIndexByType.map( + (key, value) => MapEntry(key.toString(), value), + ); return json; } @@ -477,32 +462,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { .map((addr) => BitcoinAddressRecord.fromJSON(addr)) .toList(); - var receiveAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0}; - var changeAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0}; - - try { - receiveAddressIndexByType = { - SegwitAddressType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0') - }; - changeAddressIndexByType = { - SegwitAddressType.p2wpkh.toString(): - int.parse(data['change_address_index'] as String? ?? '0') - }; - } catch (_) { - try { - receiveAddressIndexByType = data["account_index"] as Map? ?? {}; - changeAddressIndexByType = data["change_address_index"] as Map? ?? {}; - } catch (_) {} - } - return { 'allAddresses': addresses, - 'addressPageType': data['address_page_type'] as String?, - 'receiveAddressIndexByType': receiveAddressIndexByType, - 'changeAddressIndexByType': changeAddressIndexByType, - 'discoveredAddresses': data['discoveredAddresses'] as Map? ?? {}, - 'receiveAddressesMapped': data['receiveAddressesMapped'] as Map? ?? {}, - 'changeAddressesMapped': data['changeAddressesMapped'] as Map? ?? {}, + 'addressesRecords': data['addressesRecords'] as Map?, + 'discoveredAddresses': data['discoveredAddresses'] as Map?, + 'addressPageType': data['addressPageType'] as String?, + 'activeIndexByType': (data['activeIndexByType'] as Map?) ?? {}, }; } @@ -513,97 +478,267 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { required BasedUtxoNetwork network, required bool isHardwareWallet, List? initialAddresses, - List? initialSilentAddresses, - List? initialReceivedSPAddresses, - Map>>>? - initialReceiveAddressesMapped, - Map>>>? - initialChangeAddressesMapped, - Map>>? initialDiscoveredAddresses, + BitcoinAddressRecordMap? initialAddressesRecords, + BitcoinDiscoveredAddressesMap? initialDiscoveredAddresses, }) { - initialAddresses ??= (json['allAddresses'] as List) - .map((record) => BitcoinAddressRecord.fromJSON(record as String)) - .toList(); + // TODO: put this into records map (currently unused) + if (json['allAddresses'] != null) + initialAddresses ??= (json['allAddresses'] as List) + .map((record) => BitcoinAddressRecord.fromJSON(record as String)) + .toList(); - initialReceiveAddressesMapped ??= (json['receiveAddressesMapped'] as Map).map( - (addressType, v) => MapEntry( - BitcoinAddressType.fromValue(addressType as String), - (v as Map).map( - (seedBytesType, v) => MapEntry( - SeedBytesType.fromValue(seedBytesType as String), - (v as Map).map( - (derivationPath, v) => MapEntry( - derivationPath as String, - (v as List) - .map((addr) => BaseBitcoinAddressRecord.fromJSON(addr as String)) - .toList(), - ), - ), - ), - ), - ), - ); - initialChangeAddressesMapped ??= (json['changeAddressesMapped'] as Map).map( - (addressType, v) => MapEntry( - BitcoinAddressType.fromValue(addressType as String), - (v as Map).map( - (seedBytesType, v) => MapEntry( - SeedBytesType.fromValue(seedBytesType as String), - (v as Map).map( - (derivationPath, v) => MapEntry( - derivationPath as String, - (v as List) - .map((addr) => BaseBitcoinAddressRecord.fromJSON(addr as String)) - .toList(), - ), - ), - ), - ), - ), - ); + if (json['addressesRecords'] != null) + initialAddressesRecords ??= BitcoinAddressRecordMap.fromJson( + json['addressesRecords'] as Map, + ); - initialDiscoveredAddresses ??= ((json['discoveredAddresses'] as Map?) ?? {}).map( - (addressType, v) => MapEntry( - BitcoinAddressType.fromValue(addressType as String), - (v as Map).map( - (seedBytesType, v) => MapEntry( - SeedBytesType.fromValue(seedBytesType as String), - (v as Map).map( - (isChange, v) => MapEntry(isChange == "true", v as bool), - ), - ), - ), - ), - ); + if (json['discoveredAddresses'] != null) + initialDiscoveredAddresses ??= BitcoinDiscoveredAddressesMap.fromJson( + json['discoveredAddresses'] as Map, + ); return ElectrumWalletAddresses( walletInfo, hdWallets: hdWallets, network: network, isHardwareWallet: isHardwareWallet, - initialAddresses: initialAddresses, + initialAddressesRecords: initialAddressesRecords, initialDiscoveredAddresses: initialDiscoveredAddresses, - initialReceiveAddressesMapped: initialReceiveAddressesMapped, - initialChangeAddressesMapped: initialChangeAddressesMapped, + initialAddressPageType: json['addressPageType'] != null + ? BitcoinAddressType.fromValue(json['addressPageType'] as String) + : null, + initialActiveAddressIndex: (json['activeIndexByType'] as Map?)?.map( + (key, value) => MapEntry(BitcoinAddressType.fromValue(key as String), value as int), + ), + ); + } +} + +typedef AddressRecords = List; + +typedef ItemsByIsChange = Map; +typedef ItemsByDerivationPath = Map>; + +// Maps by each different property with the final item being a list of addresses +typedef AddressRecordsBySeedType = Map>; + +class ItemsRecordMap>> + extends MapBase { + final Map _data = {}; + + @override + T? operator [](Object? key) => _data[key]; + + @override + void operator []=(BitcoinAddressType key, T value) { + _data[key] = value; + } + + @override + void clear() => _data.clear(); + + @override + Iterable get keys => _data.keys; + + @override + Iterable get values => _data.values; + + @override + T? remove(Object? key) => _data.remove(key); +} + +class BitcoinAddressRecordMap extends ItemsRecordMap { + void addAddress({ + required BitcoinAddressType addressType, + required SeedBytesType seedBytesType, + required String derivationPath, + required BaseBitcoinAddressRecord addressRecord, + }) { + _data.putIfAbsent( + addressType, + () => { + seedBytesType: { + derivationPath: {addressRecord.isChange: []}, + }, + }, + ); + _data[addressType]!.putIfAbsent( + seedBytesType, + () => { + derivationPath: {addressRecord.isChange: []}, + }, + ); + _data[addressType]![seedBytesType]!.putIfAbsent( + derivationPath, + () => {addressRecord.isChange: []}, + ); + _data[addressType]![seedBytesType]![derivationPath]! + .putIfAbsent(addressRecord.isChange, () => []); + + _data[addressType]![seedBytesType]![derivationPath]![addressRecord.isChange]! + .add(addressRecord); + } + + List allRecords() { + return _data.values + .expand((seedTypeMap) => seedTypeMap.values) + .expand((derivationMap) => derivationMap.values) + .expand((changeMap) => changeMap.values) + .fold>( + [], + (acc, records) => acc..addAll(records), ); } - bool getIsUsed(BaseBitcoinAddressRecord addr) { - return addr.isUsed || addr.txCount != 0 || addr.balance != 0; + List getRecords({ + required BitcoinAddressType addressType, + required SeedBytesType seedBytesType, + required String derivationPath, + required bool isChange, + }) { + return _data[addressType]?[seedBytesType]?[derivationPath]?[isChange] ?? []; } - bool getIsReceive(BaseBitcoinAddressRecord addr) { - return !getIsUsed(addr) && !addr.isChange && !addr.isHidden; - } + Map toJson() => _data.map( + (addressType, v) => MapEntry( + addressType.value, + v.map( + (seedBytesType, v) => MapEntry( + seedBytesType.value, + v.map( + (derivationPath, v) => MapEntry( + derivationPath.toString(), + v.map( + (isChange, v) => MapEntry( + isChange.toString(), + v.map((address) => address.toJSON()).toList(), + ), + ), + ), + ), + ), + ), + ), + ); - bool getShouldHideAddress(Bip32Path path, BitcoinAddressType addressType) { - if (walletSeedBytesType.isElectrum) { - return path.toString() != BitcoinDerivationInfos.ELECTRUM.derivationPath.toString(); - } + static BitcoinAddressRecordMap fromJson(Map json) { + final res = BitcoinAddressRecordMap(); - return path.toString() != - BitcoinAddressUtils.getDerivationFromType( - addressType, - ).derivationPath.toString(); + final mapped = json.map( + (addressType, v) => MapEntry( + BitcoinAddressType.fromValue(addressType), + (v as Map).map( + (seedBytesType, v) => MapEntry( + SeedBytesType.fromValue(seedBytesType), + (v as Map).map( + (derivationPath, v) => MapEntry( + derivationPath, + (v as Map).map( + (isChange, v) => MapEntry( + isChange == 'true', + (v as List) + .map((address) => BaseBitcoinAddressRecord.fromJSON(address as String)) + .toList(), + ), + ), + ), + ), + ), + ), + ), + ); + + res.addAll(mapped); + return res; + } +} + +// Maps by each different property with the final item being a boolean indicating addresses discovered +typedef DiscoveredAddressRecordsBySeedType = Map>; + +class BitcoinDiscoveredAddressesMap extends ItemsRecordMap { + void addDiscovered({ + required BitcoinAddressType addressType, + required SeedBytesType seedBytesType, + required String derivationPath, + required BaseBitcoinAddressRecord addressRecord, + required bool discovered, + }) { + _data.putIfAbsent( + addressType, + () => { + seedBytesType: { + derivationPath: {addressRecord.isChange: discovered}, + }, + }, + ); + _data[addressType]!.putIfAbsent( + seedBytesType, + () => { + derivationPath: {addressRecord.isChange: discovered}, + }, + ); + _data[addressType]![seedBytesType]!.putIfAbsent( + derivationPath, + () => {addressRecord.isChange: discovered}, + ); + _data[addressType]![seedBytesType]![derivationPath]! + .putIfAbsent(addressRecord.isChange, () => discovered); + + _data[addressType]![seedBytesType]![derivationPath]![addressRecord.isChange] = discovered; + } + + bool getIsDiscovered({ + required BitcoinAddressType addressType, + required SeedBytesType seedBytesType, + required String derivationPath, + required bool isChange, + }) { + return _data[addressType]?[seedBytesType]?[derivationPath]?[isChange] ?? false; + } + + Map toJson() => _data.map( + (addressType, v) => MapEntry( + addressType.value, + v.map( + (seedBytesType, v) => MapEntry( + seedBytesType.value, + v.map( + (derivationPath, v) => MapEntry( + derivationPath.toString(), + v.map( + (isChange, v) => MapEntry(isChange.toString(), v), + ), + ), + ), + ), + ), + ), + ); + + static BitcoinDiscoveredAddressesMap fromJson(Map json) { + final res = BitcoinDiscoveredAddressesMap(); + + final mapped = json.map( + (addressType, v) => MapEntry( + BitcoinAddressType.fromValue(addressType), + (v as Map).map( + (seedBytesType, v) => MapEntry( + SeedBytesType.fromValue(seedBytesType), + (v as Map).map( + (derivationPath, v) => MapEntry( + derivationPath, + (v as Map).map( + (isChange, v) => MapEntry(isChange == 'true', (v as bool)), + ), + ), + ), + ), + ), + ), + ); + + res.addAll(mapped); + return res; } } diff --git a/cw_bitcoin/lib/electrum_worker/electrum_worker.dart b/cw_bitcoin/lib/electrum_worker/electrum_worker.dart index 81e4d9030..c4e383924 100644 --- a/cw_bitcoin/lib/electrum_worker/electrum_worker.dart +++ b/cw_bitcoin/lib/electrum_worker/electrum_worker.dart @@ -1742,7 +1742,6 @@ class ElectrumWorker { ).toAddress(request.network), index: i, isChange: request.isChange, - isHidden: request.shouldHideAddress || request.isChange, type: request.addressType, network: request.network, derivationInfo: request.derivationInfo, diff --git a/cw_bitcoin/lib/electrum_worker/methods/discover_addresses.dart b/cw_bitcoin/lib/electrum_worker/methods/discover_addresses.dart index a7fffdcf6..8d2d1cc5f 100644 --- a/cw_bitcoin/lib/electrum_worker/methods/discover_addresses.dart +++ b/cw_bitcoin/lib/electrum_worker/methods/discover_addresses.dart @@ -5,7 +5,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest { required this.count, required this.startIndex, required this.seedBytesType, - required this.shouldHideAddress, + required this.walletType, required this.derivationInfo, required this.isChange, required this.addressType, @@ -20,8 +20,8 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest { final int count; final int startIndex; - final bool shouldHideAddress; + final WalletType walletType; final SeedBytesType seedBytesType; final BitcoinDerivationInfo derivationInfo; final bool isChange; @@ -38,7 +38,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest { id: json['id'] as int, count: json['count'] as int, startIndex: json['startIndex'] as int, - shouldHideAddress: json['shouldHideAddress'] as bool, + walletType: deserializeFromInt(json['walletType'] as int), seedBytesType: SeedBytesType.fromValue(json['seedBytesType'] as String), derivationInfo: BitcoinDerivationInfo.fromJSON(json['derivationInfo'] as Map), @@ -57,7 +57,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest { 'completed': completed, 'count': count, 'startIndex': startIndex, - 'shouldHideAddress': shouldHideAddress, + 'walletType': serializeToInt(walletType), 'seedBytesType': seedBytesType.value, 'isChange': isChange, 'addressType': addressType.value, diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index f028060a9..784acd74c 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -95,7 +95,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet mwebSyncStatus, (status) async { if (mwebSyncStatus is FailedSyncStatus) { @@ -762,7 +762,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet updateAllUnspents([ - Set? scripthashes, + List? scripthashes, bool? wait, ]) async { if (!mwebEnabled) { @@ -961,7 +961,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet? initialMwebAddresses, }) : mwebAddresses = ObservableList.of((initialMwebAddresses ?? []).toSet()), @@ -41,9 +43,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with } @override - final walletAddressTypes = LITECOIN_ADDRESS_TYPES; - - static const LITECOIN_ADDRESS_TYPES = [SegwitAddressType.p2wpkh]; + final walletAddressTypes = [SegwitAddressType.p2wpkh]; final ObservableList mwebAddresses; @@ -96,7 +96,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with .where((addr) => addr.type == addressType && addr.seedBytesType == seedBytesType) .toList(); - if (existingAddresses.length < ElectrumWalletAddressesBase.defaultReceiveAddressesCount) { + if (existingAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) { await discoverNewMWEBAddresses( seedBytesType: seedBytesType, isChange: false, @@ -110,8 +110,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required bool isChange, }) async { final count = isChange - ? ElectrumWalletAddressesBase.defaultChangeAddressesCount - : ElectrumWalletAddressesBase.defaultReceiveAddressesCount; + ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT + : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT; final startIndex = this.mwebAddresses.length; @@ -325,7 +325,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with String get addressForExchange { // don't use mweb addresses for exchange refund address: final addresses = allAddresses.firstWhere( - (element) => element.type == SegwitAddressType.p2wpkh && !getIsUsed(element), + (addressRecord) => + addressRecord.type == SegwitAddressType.p2wpkh && !addressRecord.getIsUsed(), ); return addresses.address; } @@ -335,7 +336,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with super.updateAddressesInBox(); final lastP2wpkh = - allAddresses.where((addressRecord) => isUnusedReceiveAddress(addressRecord)).toList().last; + allAddresses.where((addressRecord) => addressRecord.isUnusedReceiveAddress()).toList().last; if (lastP2wpkh.address != address) { addressesMap[lastP2wpkh.address] = 'P2WPKH'; } else { @@ -343,7 +344,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with } final lastMweb = mwebAddresses.firstWhere( - (addressRecord) => isUnusedReceiveAddress(addressRecord), + (addressRecord) => addressRecord.isUnusedReceiveAddress(), ); if (lastMweb.address != address) { addressesMap[lastMweb.address] = 'MWEB'; @@ -445,9 +446,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with required bool isHardwareWallet, required bool mwebEnabled, }) { - final initialAddresses = (snp['allAddresses'] as List) - .map((record) => BitcoinAddressRecord.fromJSON(record as String)) - .toList(); + final electrumJson = ElectrumWalletAddressesBase.fromJson( + snp, + walletInfo, + hdWallets: hdWallets, + network: network, + isHardwareWallet: isHardwareWallet, + ); final initialMwebAddresses = (snp['mwebAddresses'] as List) .map( @@ -460,7 +465,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with hdWallets: hdWallets, network: network, isHardwareWallet: isHardwareWallet, - initialAddresses: initialAddresses, + initialAddressesRecords: electrumJson.addressesRecords, + initialAddressPageType: electrumJson.addressPageType, + initialActiveAddressIndex: electrumJson.activeIndexByType, initialMwebAddresses: initialMwebAddresses, mwebEnabled: mwebEnabled, ); diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 91ac7a90e..0d194cb86 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -54,7 +54,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { } autorun((_) { - this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress; }); } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart index ac6faec20..c7c180983 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_addresses.dart @@ -17,43 +17,20 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi required super.hdWallets, required super.network, required super.isHardwareWallet, - super.initialAddresses, + super.initialAddressesRecords, + super.initialActiveAddressIndex, super.initialAddressPageType, }) : super(walletInfo); @override - final walletAddressTypes = BITCOIN_CASH_ADDRESS_TYPES; - - static const BITCOIN_CASH_ADDRESS_TYPES = [P2pkhAddressType.p2pkh]; + final walletAddressTypes = [P2pkhAddressType.p2pkh]; @override - @observable BitcoinAddressType changeAddressType = P2pkhAddressType.p2pkh; @override BitcoinAddressType get addressPageType => P2pkhAddressType.p2pkh; - @override - Future init() async { - // for (final seedBytesType in hdWallets.keys) { - // await generateInitialAddresses( - // addressType: P2pkhAddressType.p2pkh, - // seedBytesType: seedBytesType, - // bitcoinDerivationInfo: BitcoinDerivationInfos.BCH, - // ); - // } - await super.init(); - } - - @override - bool getShouldHideAddress(Bip32Path path, BitcoinAddressType addressType) { - if (walletSeedBytesType.isElectrum) { - return path.toString() != BitcoinDerivationInfos.ELECTRUM.derivationPath.toString(); - } - - return path.toString() != BitcoinDerivationPaths.BCH; - } - static BitcoinCashWalletAddressesBase fromJson( Map json, WalletInfo walletInfo, { @@ -62,6 +39,14 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi required bool isHardwareWallet, List? initialAddresses, }) { + final electrumJson = ElectrumWalletAddressesBase.fromJson( + json, + walletInfo, + hdWallets: hdWallets, + network: network, + isHardwareWallet: isHardwareWallet, + ); + initialAddresses ??= (json['allAddresses'] as List).map((addr) { try { BitcoinCashAddress(addr.address); @@ -92,7 +77,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi hdWallets: hdWallets, network: network, isHardwareWallet: isHardwareWallet, - initialAddresses: initialAddresses, + initialAddressesRecords: electrumJson.addressesRecords, + initialAddressPageType: electrumJson.addressPageType, + initialActiveAddressIndex: electrumJson.activeIndexByType, ); } } diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart index fe933cef5..b0da0e7a1 100644 --- a/cw_core/lib/wallet_addresses.dart +++ b/cw_core/lib/wallet_addresses.dart @@ -78,8 +78,4 @@ abstract class WalletAddresses { bool containsAddress(String address) => addressesMap.containsKey(address) || allAddressesMap.containsKey(address); - - void resetActiveChangeAddress() { - throw UnimplementedError(); - } } diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index d865946f4..a7dadf49f 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -158,7 +158,7 @@ class CWBitcoin extends Bitcoin { id: addr.index, name: addr.name, address: addr.address, - derivationPath: addr.derivationPath, + derivationPath: addr.indexedDerivationPath, txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, @@ -291,10 +291,15 @@ class CWBitcoin extends Bitcoin { @override TransactionPriority getLitecoinTransactionPrioritySlow() => ElectrumTransactionPriority.slow; + @override + void resetActiveAddress(Object wallet) { + (wallet as ElectrumWallet).walletAddresses.resetActiveAddress(); + } + @override Future setAddressType(Object wallet, dynamic option) async { final bitcoinWallet = wallet as ElectrumWallet; - await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType); + await bitcoinWallet.walletAddresses.setAddressPageType(option as BitcoinAddressType); } @override @@ -530,7 +535,7 @@ class CWBitcoin extends Bitcoin { id: addr.index, name: addr.name, address: addr.address, - derivationPath: addr.derivationPath, + derivationPath: addr.indexedDerivationPath, txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, @@ -547,7 +552,7 @@ class CWBitcoin extends Bitcoin { id: addr.index, name: addr.name, address: addr.address, - derivationPath: addr.derivationPath, + derivationPath: addr.indexedDerivationPath, txCount: addr.txCount, balance: addr.balance, isChange: addr.isChange, diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index ab51b97e6..a93411d70 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -98,7 +98,7 @@ class AddressPage extends BasePage { @override Widget body(BuildContext context) { - addressListViewModel.resetActiveChangeAddress(); + addressListViewModel.resetActiveAddress(); _setEffects(context); @@ -162,7 +162,8 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - if (dashboardViewModel.type == WalletType.bitcoin && bitcoin!.isBitcoinReceivePageOption(option)) { + if (dashboardViewModel.type == WalletType.bitcoin && + bitcoin!.isBitcoinReceivePageOption(option)) { addressListViewModel.setAddressType(bitcoin!.getOptionToType(option)); return; } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 4837362b8..d09589a34 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -301,10 +301,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo WalletType get type => wallet.type; @action - void resetActiveChangeAddress() { - try { - wallet.walletAddresses.resetActiveChangeAddress(); - } catch (_) {} + void resetActiveAddress() { + if (isElectrumWallet) { + bitcoin!.resetActiveAddress(wallet); + } } @computed diff --git a/tool/configure.dart b/tool/configure.dart index 363792931..da2e13a8d 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -127,23 +127,6 @@ import 'package:mobx/mobx.dart'; """; const bitcoinCwPart = "part 'cw_bitcoin.dart';"; const bitcoinContent = """ -const List BITCOIN_ADDRESS_TYPES = [ - SegwitAddressType.p2wpkh, - P2pkhAddressType.p2pkh, - SegwitAddressType.p2tr, - SegwitAddressType.p2wsh, - P2shAddressType.p2wpkhInP2sh, -]; - -const List LITECOIN_ADDRESS_TYPES = [ - SegwitAddressType.p2wpkh, - SegwitAddressType.mweb, -]; - -const List BITCOIN_CASH_ADDRESS_TYPES = [ - P2pkhAddressType.p2pkh, -]; - class ElectrumSubAddress { ElectrumSubAddress({ required this.id, @@ -232,6 +215,7 @@ abstract class Bitcoin { Future> compareDerivationMethods( {required String mnemonic, required Node node}); Map> getElectrumDerivations(); + void resetActiveAddress(Object wallet); Future setAddressType(Object wallet, dynamic option); ReceivePageOption getSelectedAddressType(Object wallet); List getBitcoinReceivePageOptions(); @@ -978,15 +962,12 @@ abstract class BitcoinCash { """; const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n'; - const bitcoinCashCWDefinition = - 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; + const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n'; final output = '$bitcoinCashCommonHeaders\n' + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') + - (hasImplementation - ? bitcoinCashCWDefinition - : bitcoinCashEmptyDefinition) + + (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) + '\n' + bitcoinCashContent; @@ -1121,8 +1102,7 @@ abstract class NanoUtil { """; const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n'; - const nanoCWDefinition = - 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n'; + const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n'; final output = '$nanoCommonHeaders\n' + (hasImplementation ? '$nanoCWHeaders\n' : '\n') +