refactor: improve namings and add utility classes for managing addresses

This commit is contained in:
Rafael Saes 2025-04-18 06:05:32 -03:00
parent 8bbf568033
commit e09d4d6f64
16 changed files with 617 additions and 507 deletions

View file

@ -68,7 +68,19 @@ class BaseBitcoinAddressRecord {
final String _derivationPath; 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({ String toJSON() => json.encode({
'address': address, 'address': address,
@ -82,7 +94,7 @@ class BaseBitcoinAddressRecord {
'type': type.toString(), 'type': type.toString(),
'runtimeType': runtimeType.toString(), 'runtimeType': runtimeType.toString(),
'seedBytesType': seedBytesType?.value, 'seedBytesType': seedBytesType?.value,
'derivationPath': derivationPath, 'derivationPath': indexedDerivationPath,
}); });
static BaseBitcoinAddressRecord buildFromJSON( static BaseBitcoinAddressRecord buildFromJSON(
@ -146,7 +158,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
String _derivationPath = ''; String _derivationPath = '';
@override @override
String get derivationPath => _derivationPath; String get indexedDerivationPath => _derivationPath;
BitcoinAddressRecord( BitcoinAddressRecord(
super.address, { super.address, {
@ -177,6 +189,10 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
.addElem(Bip32KeyIndex(isChange ? 1 : 0)) .addElem(Bip32KeyIndex(isChange ? 1 : 0))
.addElem(Bip32KeyIndex(index)) .addElem(Bip32KeyIndex(index))
.toString(); .toString();
if (getShouldHideAddressByDefault()) {
isHidden = true;
}
} }
factory BitcoinAddressRecord.fromJSON( factory BitcoinAddressRecord.fromJSON(
@ -201,7 +217,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
name: base.name, name: base.name,
balance: base.balance, balance: base.balance,
type: base.type, type: base.type,
derivationPath: base.derivationPath, derivationPath: base.indexedDerivationPath,
scriptHash: decoded['scriptHash'] as String?, scriptHash: decoded['scriptHash'] as String?,
derivationInfo: derivationInfoSnp == null derivationInfo: derivationInfoSnp == null
? seedBytesType != null && !seedBytesType.isElectrum ? seedBytesType != null && !seedBytesType.isElectrum
@ -217,6 +233,19 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
late String scriptHash; 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 @override
String toJSON() { String toJSON() {
final m = json.decode(super.toJSON()) as Map<String, dynamic>; final m = json.decode(super.toJSON()) as Map<String, dynamic>;
@ -252,7 +281,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
String _derivationPath; String _derivationPath;
@override @override
String get derivationPath => _derivationPath; String get indexedDerivationPath => _derivationPath;
int get labelIndex => index; int get labelIndex => index;
final String? labelHex; final String? labelHex;
@ -301,7 +330,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
balance: base.balance, balance: base.balance,
type: base.type, type: base.type,
labelIndex: base.index, labelIndex: base.index,
derivationPath: base.derivationPath, derivationPath: base.indexedDerivationPath,
labelHex: decoded['labelHex'] as String?, labelHex: decoded['labelHex'] as String?,
); );
} }
@ -320,7 +349,7 @@ class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
final String spAddress; final String spAddress;
@override @override
String get derivationPath => ''; String get indexedDerivationPath => '';
BitcoinReceivedSPAddressRecord( BitcoinReceivedSPAddressRecord(
super.address, { super.address, {
@ -390,7 +419,7 @@ class LitecoinMWEBAddressRecord extends BaseBitcoinAddressRecord {
String _derivationPath = ''; String _derivationPath = '';
@override @override
String get derivationPath => _derivationPath; String get indexedDerivationPath => _derivationPath;
LitecoinMWEBAddressRecord( LitecoinMWEBAddressRecord(
super.address, { super.address, {

View file

@ -99,7 +99,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
} }
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress;
}); });
} }
@ -121,34 +121,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
// If already loaded, no need to generate/discover all initial addresses // If already loaded, no need to generate/discover all initial addresses
// so skip // so skip
// if (!walletAddresses.loadedFromNewSnapshot) { // if (!walletAddresses.loadedFromNewSnapshot) {
for (final seedBytesType in hdWallets.keys) { for (final walletAddressType in walletAddresses.walletAddressTypes) {
generateInitialAddresses( if (isHardwareWallet && walletAddressType != SegwitAddressType.p2wpkh) continue;
addressType: SegwitAddressType.p2wpkh,
seedBytesType: seedBytesType,
);
if (!isHardwareWallet) { for (final seedBytesType in hdWallets.keys) {
generateInitialAddresses( generateInitialAddresses(
addressType: P2pkhAddressType.p2pkh, addressType: walletAddressType,
seedBytesType: seedBytesType,
);
generateInitialAddresses(
addressType: P2shAddressType.p2wpkhInP2sh,
seedBytesType: seedBytesType,
);
generateInitialAddresses(
addressType: SegwitAddressType.p2tr,
seedBytesType: seedBytesType,
);
generateInitialAddresses(
addressType: SegwitAddressType.p2wsh,
seedBytesType: seedBytesType, seedBytesType: seedBytesType,
); );
} }
} }
// } // }
} }
@ -517,7 +500,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
@override @override
@action @action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([ Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes, List<String>? scripthashes,
bool? wait, bool? wait,
]) async { ]) async {
scripthashes ??= this.walletAddresses.allScriptHashes; scripthashes ??= this.walletAddresses.allScriptHashes;

View file

@ -19,10 +19,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.hdWallets, required super.hdWallets,
required super.network, required super.network,
required super.isHardwareWallet, required super.isHardwareWallet,
super.initialAddresses, super.initialAddressesRecords,
super.initialDiscoveredAddresses, super.initialActiveAddressIndex,
super.initialReceiveAddressesMapped, super.initialAddressPageType,
super.initialChangeAddressesMapped,
this.loadedFromNewSnapshot = false, this.loadedFromNewSnapshot = false,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses, List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
@ -41,15 +40,14 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
static const _OLD_SP_PATH = "m/352'/1'/0'/#'/0"; static const _OLD_SP_PATH = "m/352'/1'/0'/#'/0";
// NOTE: ordered in priority: eg. p2wpkh always first, most used address, etc.
@override @override
final walletAddressTypes = BITCOIN_ADDRESS_TYPES; final walletAddressTypes = [
static const BITCOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh, SegwitAddressType.p2wpkh,
P2pkhAddressType.p2pkh,
SegwitAddressType.p2tr, SegwitAddressType.p2tr,
SegwitAddressType.p2wsh,
P2shAddressType.p2wpkhInP2sh, P2shAddressType.p2wpkhInP2sh,
P2pkhAddressType.p2pkh,
SegwitAddressType.p2wsh,
]; ];
@observable @observable
@ -139,7 +137,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
@override @override
@action @action
void resetActiveChangeAddress() { void resetActiveAddress() {
if (activeSilentAddress != null && if (activeSilentAddress != null &&
(activeSilentAddress!.isChange || activeSilentAddress!.isHidden)) { (activeSilentAddress!.isChange || activeSilentAddress!.isHidden)) {
try { try {
@ -154,7 +152,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
} catch (_) {} } catch (_) {}
} }
super.resetActiveChangeAddress(); super.resetActiveAddress();
} }
@override @override
@ -367,7 +365,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
(addressRecord) => (addressRecord) =>
!addressRecord.isChange && !addressRecord.isChange &&
addressRecord.labelIndex == 0 && addressRecord.labelIndex == 0 &&
addressRecord.derivationPath != oldSpendPath.toString(), addressRecord.indexedDerivationPath != oldSpendPath.toString(),
); );
final list = [primaryAddress.address]; final list = [primaryAddress.address];
@ -376,7 +374,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
(addressRecord) => (addressRecord) =>
!addressRecord.isChange && !addressRecord.isChange &&
addressRecord.labelIndex == 0 && addressRecord.labelIndex == 0 &&
addressRecord.derivationPath == oldSpendPath.toString(), addressRecord.indexedDerivationPath == oldSpendPath.toString(),
); );
// Do it like this to keep in order, // Do it like this to keep in order,
@ -466,12 +464,9 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
hdWallets: hdWallets, hdWallets: hdWallets,
network: network, network: network,
isHardwareWallet: isHardwareWallet, isHardwareWallet: isHardwareWallet,
initialAddresses: electrumJson.allAddresses, initialAddressesRecords: electrumJson.addressesRecords,
initialDiscoveredAddresses: electrumJson.discoveredAddresses, initialAddressPageType: electrumJson.addressPageType,
initialReceiveAddressesMapped: initialActiveAddressIndex: electrumJson.activeIndexByType,
electrumJson.receiveAddressesMapped,
initialChangeAddressesMapped:
electrumJson.changeAddressesMapped,
initialSilentAddresses: initialSilentAddresses, initialSilentAddresses: initialSilentAddresses,
initialReceivedSPAddresses: initialReceivedSPAddresses, initialReceivedSPAddresses: initialReceivedSPAddresses,
loadedFromNewSnapshot: snp['loadedFromNewSnapshot'] as bool? ?? false, loadedFromNewSnapshot: snp['loadedFromNewSnapshot'] as bool? ?? false,

View file

@ -318,7 +318,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
} }
Future<void> init() async { Future<void> init() async {
await walletAddresses.init();
await transactionHistory.init(); await transactionHistory.init();
_autoSaveTimer = _autoSaveTimer =
@ -475,7 +474,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
privkey = ECPrivate.fromBip32( privkey = ECPrivate.fromBip32(
bip32: hdWallets[addressRecord.seedBytesType]!.derive( bip32: hdWallets[addressRecord.seedBytesType]!.derive(
Bip32PathParser.parse(addressRecord.derivationPath), Bip32PathParser.parse(addressRecord.indexedDerivationPath),
), ),
); );
} }
@ -495,7 +494,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
} }
if (utx.bitcoinAddressRecord is BitcoinAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinAddressRecord) {
final derivationPath = utx.bitcoinAddressRecord.derivationPath; final derivationPath = utx.bitcoinAddressRecord.indexedDerivationPath;
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
} }
@ -637,7 +636,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
isChange: true, isChange: true,
)); ));
final changeDerivationPath = changeAddress.derivationPath.toString(); final changeDerivationPath = changeAddress.indexedDerivationPath.toString();
utxoDetails.publicKeys[address.pubKeyHash()] = utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath); PublicKeyWithDerivationPath('', changeDerivationPath);
@ -1113,7 +1112,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
@action @action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([ Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes, List<String>? scripthashes,
bool? wait, bool? wait,
]) async { ]) async {
scripthashes ??= walletAddresses.allScriptHashes; scripthashes ??= walletAddresses.allScriptHashes;
@ -1540,10 +1539,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
} }
// Identify all change outputs // Identify all change outputs
final changeAddresses = walletAddresses.allChangeAddresses; final changeAddresses = walletAddresses.allAddresses.where((element) => element.isChange);
final List<BitcoinOutput> changeOutputs = outputs final List<BitcoinOutput> changeOutputs = outputs
.where((output) => changeAddresses .where((output) =>
.any((element) => element.address == output.address.toAddress(network))) changeAddresses.any((addr) => addr.address == output.address.toAddress(network)))
.toList(); .toList();
int totalChangeAmount = int totalChangeAmount =
@ -1931,20 +1930,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
try { try {
final scripthashByAddress = await subscribeForStatuses(addresses, true); final scripthashByAddress = await subscribeForStatuses(addresses, true);
walletAddresses.discoveredAddresses.putIfAbsent( walletAddresses.addAddresses(addresses);
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);
if (addresses.first.seedBytesType!.isOldDerivation && scripthashByAddress.length == 1) { if (addresses.first.seedBytesType!.isOldDerivation && scripthashByAddress.length == 1) {
// Wrong derivation address with no history, discard and do not add to wallet addresses // Wrong derivation address with no history, discard and do not add to wallet addresses
@ -1969,12 +1955,14 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
isElectrum: seedBytesType.isElectrum, isElectrum: seedBytesType.isElectrum,
); );
final existingReceiveAddresses = (walletAddresses.receiveAddressesMapped[addressType] final existingReceiveAddresses = walletAddresses.addressesRecords.getRecords(
?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ?? addressType: addressType,
<BaseBitcoinAddressRecord>[]); seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: false,
);
if (existingReceiveAddresses.length < if (existingReceiveAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
ElectrumWalletAddressesBase.defaultReceiveAddressesCount) {
discoverNewAddresses( discoverNewAddresses(
seedBytesType: seedBytesType, seedBytesType: seedBytesType,
isChange: false, isChange: false,
@ -1984,11 +1972,14 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
); );
} }
final existingChangeAddresses = (walletAddresses.receiveAddressesMapped[addressType] final existingChangeAddresses = walletAddresses.addressesRecords.getRecords(
?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ?? addressType: addressType,
<BaseBitcoinAddressRecord>[]); seedBytesType: seedBytesType,
derivationPath: bitcoinDerivationInfo.derivationPath.toString(),
isChange: true,
);
if (existingChangeAddresses.length < ElectrumWalletAddressesBase.defaultChangeAddressesCount) { if (existingChangeAddresses.length < ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT) {
discoverNewAddresses( discoverNewAddresses(
seedBytesType: seedBytesType, seedBytesType: seedBytesType,
isChange: true, isChange: true,
@ -2003,48 +1994,45 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
List<BitcoinAddressRecord> addresses, List<BitcoinAddressRecord> addresses,
) async { ) async {
final newAddresses = <BitcoinAddressRecord>[]; final newAddresses = <BitcoinAddressRecord>[];
final discoveredAddresses = final discoveredAddresses = BitcoinDiscoveredAddressesMap();
<SeedBytesType, Map<BitcoinAddressType, Map<BitcoinDerivationType, List<bool>>>>{};
final usedAddresses = addresses.isNotEmpty final usedAddresses = addresses.isNotEmpty
? addresses ? addresses
: walletAddresses.allAddresses.where(walletAddresses.getIsUsed).toList(); : walletAddresses.allAddresses.where((addr) => addr.getIsUsed()).toList();
for (final usedAddress in usedAddresses) { for (final usedAddress in usedAddresses) {
final isChange = usedAddress.isChange; final isAlreadyDiscovered = discoveredAddresses.getIsDiscovered(
addressType: usedAddress.type,
final alreadyDiscoveredSeedType = discoveredAddresses[usedAddress.seedBytesType!]; seedBytesType: usedAddress.seedBytesType!,
final alreadyDiscoveredAddressType = alreadyDiscoveredSeedType?[usedAddress.type]; derivationPath: usedAddress.derivationInfo.derivationPath.toString(),
final alreadyDiscoveredDerivationType = isChange: usedAddress.isChange,
alreadyDiscoveredAddressType?[usedAddress.derivationInfo.derivationType]; );
final isAlreadyDiscovered = alreadyDiscoveredDerivationType?.contains(isChange) ?? false;
if (isAlreadyDiscovered) { if (isAlreadyDiscovered) {
continue; continue;
} }
final matchingAddressList = walletAddresses.allAddresses.where( final matchingAddressList = walletAddresses.addressesRecords.getRecords(
(addr) => seedBytesType: usedAddress.seedBytesType!,
addr.seedBytesType! == usedAddress.seedBytesType! && addressType: usedAddress.type,
addr.type == usedAddress.type && derivationPath: usedAddress.derivationInfo.derivationPath.toString(),
addr.derivationInfo.derivationType == usedAddress.derivationInfo.derivationType && isChange: usedAddress.isChange,
addr.isChange == isChange,
); );
final totalMatchingAddresses = matchingAddressList.length; final totalMatchingAddresses = matchingAddressList.length;
final matchingGapLimit = (isChange final matchingGapLimit = usedAddress.isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount); : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT;
final isAddressUsedAboveGap = usedAddress.index >= totalMatchingAddresses - matchingGapLimit; final isAddressUsedAboveGap = usedAddress.index >= totalMatchingAddresses - matchingGapLimit;
if (isAddressUsedAboveGap) { if (isAddressUsedAboveGap) {
discoveredAddresses.putIfAbsent(usedAddress.seedBytesType!, () => {}); // discoveredAddresses.addDiscovered(
discoveredAddresses[usedAddress.seedBytesType!]!.putIfAbsent(usedAddress.type, () => {}); // addressType: addressType,
discoveredAddresses[usedAddress.seedBytesType!]![usedAddress.type]! // seedBytesType: seedBytesType,
.putIfAbsent(usedAddress.derivationInfo.derivationType, () => []); // derivationPath: derivationPath,
discoveredAddresses[usedAddress.seedBytesType!]![usedAddress.type]![ // addressRecord: addressRecord,
usedAddress.derivationInfo.derivationType]! // discovered: discovered,
.add(isChange); // );
// final theseAddresses = discoverNewAddresses( // final theseAddresses = discoverNewAddresses(
// isChange: isChange, // isChange: isChange,
@ -2080,31 +2068,36 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
required BitcoinDerivationInfo derivationInfo, required BitcoinDerivationInfo derivationInfo,
int? startIndex, int? startIndex,
}) async { }) 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; return;
} }
final count = isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
startIndex ??= ((isChange
? walletAddresses.changeAddressesMapped[addressType]
: walletAddresses.receiveAddressesMapped[addressType])?[seedBytesType]
?[derivationInfo.derivationPath.toString()] ??
[])
.length;
workerSendPort!.send( workerSendPort!.send(
ElectrumWorkerDiscoverAddressesRequest( ElectrumWorkerDiscoverAddressesRequest(
id: _messageId, id: _messageId,
count: count, count: count,
walletType: type,
startIndex: startIndex, startIndex: startIndex,
seedBytesType: seedBytesType, seedBytesType: seedBytesType,
shouldHideAddress: walletAddresses.getShouldHideAddress(
derivationInfo.derivationPath,
addressType,
),
derivationInfo: derivationInfo, derivationInfo: derivationInfo,
isChange: isChange, isChange: isChange,
addressType: addressType, addressType: addressType,

View file

@ -1,3 +1,4 @@
import 'dart:collection';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
@ -12,54 +13,51 @@ part 'electrum_wallet_addresses.g.dart';
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const INITIAL_RECEIVE_COUNT = 22;
static const INITIAL_CHANGE_COUNT = 17;
static const GAP = 20;
ElectrumWalletAddressesBase( ElectrumWalletAddressesBase(
WalletInfo walletInfo, { WalletInfo walletInfo, {
required this.hdWallets, required this.hdWallets,
required this.network, required this.network,
required this.isHardwareWallet, required this.isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses, BitcoinAddressRecordMap? initialAddressesRecords,
Map<String, int>? initialRegularAddressIndex, Map<BitcoinAddressType, int>? initialActiveAddressIndex,
Map<String, int>? initialChangeAddressIndex, BitcoinDiscoveredAddressesMap? initialDiscoveredAddresses,
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>> }) : addressesRecords = initialAddressesRecords ?? BitcoinAddressRecordMap(),
initialReceiveAddressesMapped = const {}, activeIndexByType = initialActiveAddressIndex ?? {},
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>> discoveredAddresses = initialDiscoveredAddresses ?? BitcoinDiscoveredAddressesMap(),
initialChangeAddressesMapped = const {},
Map<BitcoinAddressType, Map<SeedBytesType, Map<bool, bool>>> initialDiscoveredAddresses =
const {},
}) : _allAddresses = ObservableList.of(initialAddresses ?? []),
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
discoveredAddresses = initialDiscoveredAddresses,
receiveAddressesMapped = initialReceiveAddressesMapped,
changeAddressesMapped = initialChangeAddressesMapped,
addressPageType = initialAddressPageType ?? addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null (walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!) ? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddressType.p2wpkh), : SegwitAddressType.p2wpkh),
super(walletInfo); super(walletInfo) {
updateAllAddresses();
static const defaultReceiveAddressesCount = 22; }
static const defaultChangeAddressesCount = 17;
static const gap = 20;
final walletAddressTypes = <BitcoinAddressType>[]; final walletAddressTypes = <BitcoinAddressType>[];
final ObservableList<BitcoinAddressRecord> _allAddresses; @observable
BitcoinDiscoveredAddressesMap discoveredAddresses;
@observable
BitcoinAddressRecordMap addressesRecords;
@observable @observable
// { BitcoinAddressType: { SeedBytesType: { isChange: true = discovered } } } List<BitcoinAddressRecord> _allAddresses = [];
Map<BitcoinAddressType, Map<SeedBytesType, Map<bool, bool>>> discoveredAddresses; @computed
List<BitcoinAddressRecord> get allAddresses => _allAddresses;
@observable @action
// { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } } List<BitcoinAddressRecord> updateAllAddresses() {
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>> _allAddresses = addressesRecords.allRecords().whereType<BitcoinAddressRecord>().toList();
receiveAddressesMapped;
@observable updateAllScriptHashes();
// { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } } updateSelectedReceiveAddresses();
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>> updateSelectedChangeAddresses();
changeAddressesMapped; return _allAddresses;
}
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
@ -81,151 +79,192 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
BitcoinAddressType addressPageType; BitcoinAddressType addressPageType;
@computed @action
List<BitcoinAddressRecord> get allChangeAddresses => Future<void> setAddressPageType(BitcoinAddressType type) async {
_allAddresses.where((addr) => addr.isChange).toList(); addressPageType = type;
updateSelectedReceiveAddresses();
updateSelectedChangeAddresses();
walletInfo.addressPageType = addressPageType.toString();
await updateAddressesInBox();
}
@computed
BitcoinDerivationInfo get _defaultAddressPageDerivationInfo => BitcoinDerivationInfo get _defaultAddressPageDerivationInfo =>
BitcoinAddressUtils.getDerivationFromType( BitcoinAddressUtils.getDerivationFromType(
addressPageType, addressPageType,
isElectrum: walletSeedBytesType.isElectrum, isElectrum: walletSeedBytesType.isElectrum,
); );
@observable
List<BaseBitcoinAddressRecord> _selectedReceiveAddresses = [];
@computed @computed
List<BaseBitcoinAddressRecord> get selectedReceiveAddresses { List<BaseBitcoinAddressRecord> get selectedReceiveAddresses => _selectedReceiveAddresses;
return receiveAddressesMapped[addressPageType]?[walletSeedBytesType]
?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ?? @action
[]; List<BaseBitcoinAddressRecord> updateSelectedReceiveAddresses() {
_selectedReceiveAddresses = addressesRecords.getRecords(
addressType: addressPageType,
seedBytesType: walletSeedBytesType,
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
isChange: false,
);
updateNextReceiveAddress();
return _selectedReceiveAddresses;
} }
@observable
BitcoinAddressRecord? _nextReceiveAddress;
@computed @computed
List<BaseBitcoinAddressRecord> get selectedChangeAddresses => BitcoinAddressRecord? get nextReceiveAddress => _nextReceiveAddress;
changeAddressesMapped[addressPageType]?[walletSeedBytesType]
?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ??
[];
@computed @action
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList(); BitcoinAddressRecord? updateNextReceiveAddress() {
final receiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
if (receiveAddresses.isEmpty) {
return null;
}
@computed _nextReceiveAddress = receiveAddresses.firstWhereOrNull(
Set<String> get allScriptHashes => (addressRecord) =>
_allAddresses.map((addressRecord) => addressRecord.scriptHash).toSet(); addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
!addressRecord.isChange,
BitcoinAddressRecord getFromAddresses(String address) { ) ??
return _allAddresses.firstWhere((element) => element.address == address); receiveAddresses.first;
return _nextReceiveAddress;
} }
@observable
List<BaseBitcoinAddressRecord> _selectedChangeAddresses = [];
@computed
List<BaseBitcoinAddressRecord> get selectedChangeAddresses => _selectedChangeAddresses;
@action
List<BaseBitcoinAddressRecord> 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<BitcoinAddressRecord>();
if (changeAddresses.isEmpty) {
return null;
}
_nextChangeAddress = changeAddresses.firstWhereOrNull(
(addressRecord) =>
addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
addressRecord.isChange,
) ??
changeAddresses.first;
return _nextChangeAddress;
}
@observable
List<String> _allScriptHashes = [];
@computed
List<String> get allScriptHashes => _allScriptHashes;
@action
List<String> 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 // TODO: feature with toggle to switch change address type
@observable @observable
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh; BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
@observable @observable
BitcoinAddressRecord? activeAddress; BitcoinAddressRecord? activeBitcoinAddress;
// TODO: map by type
@observable @observable
int activeAddressIndex = 0; bool isEnabledAutoGenerateNewAddress = true;
@override
@action @action
void resetActiveChangeAddress() { void resetActiveAddress() {
if (isEnabledAutoGenerateSubaddress) { final activeReceiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
try {
activeAddress = _allAddresses.firstWhere(
(addressRecord) =>
addressRecord.type == addressPageType &&
addressRecord.index == activeAddressIndex &&
getIsReceive(addressRecord),
);
return; activeBitcoinAddress = activeReceiveAddresses.firstWhereOrNull(
} catch (_) {} (addressRecord) => addressRecord.index == activeIndexByType[addressPageType],
) ??
try { activeReceiveAddresses.first;
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 (_) {}
} }
@override @override
@computed @computed
String get address { String get address {
if (activeAddress != null) { if (activeBitcoinAddress != null) {
return activeAddress!.address; return activeBitcoinAddress!.address;
} }
String? receiveAddress = ""; final receiveAddress = selectedReceiveAddresses
.firstWhereOrNull(
if (isEnabledAutoGenerateSubaddress && selectedReceiveAddresses.isEmpty) { (addr) => addr.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) && !addr.isChange,
receiveAddress = )
selectedReceiveAddresses.firstWhereOrNull((addr) => !getIsUsed(addr))?.address; ?.address;
} else {
receiveAddress = selectedReceiveAddresses.first.address;
}
return receiveAddress ?? ''; return receiveAddress ?? '';
} }
@observable
bool isEnabledAutoGenerateSubaddress = true;
@override @override
set address(String addr) { set address(String addr) {
try { final addressRecord = _allAddresses.firstWhereOrNull(
final addressRecord = _allAddresses.firstWhere( (addressRecord) => addressRecord.address == addr,
(addressRecord) => addressRecord.address == addr, );
);
activeAddress = addressRecord; activeBitcoinAddress = addressRecord;
if (getIsReceive(addressRecord)) { if (addressRecord != null &&
activeAddressIndex = addressRecord.index; addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
} !addressRecord.isChange) {
} catch (e) { activeAddressIndex = addressRecord.index;
// printV("ElectrumWalletAddressBase: set address ($addr): $e");
} }
} }
@override @override
String get primaryAddress => _allAddresses.first.address; @computed
String get primaryAddress =>
selectedReceiveAddresses
.firstWhereOrNull(
(addressRecord) =>
addressRecord.getIsStillReceiveable(isEnabledAutoGenerateNewAddress) &&
!addressRecord.isChange,
)
?.address ??
'';
Map<String, int> currentReceiveAddressIndexByType; Map<BitcoinAddressType, int> activeIndexByType;
int get currentReceiveAddressIndex => int get activeAddressIndex => activeIndexByType[addressPageType] ?? 0;
currentReceiveAddressIndexByType[addressPageType.toString()] ?? 0;
void set currentReceiveAddressIndex(int index) => set activeAddressIndex(int index) => activeIndexByType[addressPageType] = index;
currentReceiveAddressIndexByType[addressPageType.toString()] = index;
Map<String, int> currentChangeAddressIndexByType;
int get currentChangeAddressIndex =>
currentChangeAddressIndexByType[addressPageType.toString()] ?? 0;
void set currentChangeAddressIndex(int index) =>
currentChangeAddressIndexByType[addressPageType.toString()] = index;
@override @override
Future<void> init() async { Future<void> init() async {
await updateAddressesInBox(); throw UnimplementedError();
} }
Future<BaseBitcoinAddressRecord> getChangeAddress() async { Future<BaseBitcoinAddressRecord> getChangeAddress() async {
final address = selectedChangeAddresses.firstWhere( final address = selectedChangeAddresses.firstWhere(
(addr) => addr.isChange && !getIsUsed(addr) && addr.type == changeAddressType, (addr) => !addr.getIsUsed() && addr.type == changeAddressType,
); );
return address; return address;
} }
@ -250,7 +289,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
name: label, name: label,
type: addressPageType, type: addressPageType,
network: network, network: network,
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressPageType), derivationInfo: derivationInfo,
seedBytesType: walletSeedBytesType, seedBytesType: walletSeedBytesType,
); );
return address; return address;
@ -362,14 +401,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
void updateAddress(String address, String label) { void updateAddress(String address, String label) {
BaseBitcoinAddressRecord? foundAddress; BaseBitcoinAddressRecord? foundAddress;
_allAddresses.forEach((addressRecord) {
for (final addressRecord in _allAddresses) {
if (addressRecord.address == address) { if (addressRecord.address == address) {
foundAddress = addressRecord; foundAddress = addressRecord;
break;
} }
}); }
// TODO: verify this updates and keeps on re-open
if (foundAddress != null) { if (foundAddress != null) {
foundAddress!.setNewName(label); foundAddress.setNewName(label);
} }
} }
@ -391,82 +433,25 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
void addAddresses(Iterable<BitcoinAddressRecord> addresses) { void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
this._allAddresses.addAll(addresses);
final firstAddress = addresses.first; final firstAddress = addresses.first;
final addressesMap = {
...(firstAddress.isChange ? changeAddressesMapped : receiveAddressesMapped)
};
addressesMap.putIfAbsent( addressesRecords.addAddress(
firstAddress.type, addressType: firstAddress.type,
() => { seedBytesType: firstAddress.seedBytesType!,
firstAddress.seedBytesType!: { derivationPath: firstAddress.derivationInfo.derivationPath.toString(),
firstAddress.derivationInfo.derivationPath.toString(): addresses.toList(), addressRecord: firstAddress,
},
},
); );
addressesMap[firstAddress.type]!.putIfAbsent( updateAllAddresses();
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<void> setAddressType(BitcoinAddressType type) async {
addressPageType = type;
walletInfo.addressPageType = addressPageType.toString();
await walletInfo.save();
}
bool isUnusedReceiveAddress(BaseBitcoinAddressRecord addr) {
return !addr.isChange && !getIsUsed(addr);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json['allAddresses'] = _allAddresses.map((address) => address.toJSON()).toList(); json['addressesRecords'] = addressesRecords.toJson();
json['discoveredAddresses'] = discoveredAddresses.toJson();
json['addressPageType'] = addressPageType.toString(); json['addressPageType'] = addressPageType.toString();
json['discoveredAddresses'] = discoveredAddresses.map((addressType, v) { json['activeIndexByType'] = activeIndexByType.map(
return MapEntry(addressType.value, v.map((seedBytesType, v) { (key, value) => MapEntry(key.toString(), value),
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(),
);
}));
}));
});
return json; return json;
} }
@ -477,32 +462,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
.map((addr) => BitcoinAddressRecord.fromJSON(addr)) .map((addr) => BitcoinAddressRecord.fromJSON(addr))
.toList(); .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<String, int>? ?? {};
changeAddressIndexByType = data["change_address_index"] as Map<String, int>? ?? {};
} catch (_) {}
}
return { return {
'allAddresses': addresses, 'allAddresses': addresses,
'addressPageType': data['address_page_type'] as String?, 'addressesRecords': data['addressesRecords'] as Map<String, dynamic>?,
'receiveAddressIndexByType': receiveAddressIndexByType, 'discoveredAddresses': data['discoveredAddresses'] as Map<String, dynamic>?,
'changeAddressIndexByType': changeAddressIndexByType, 'addressPageType': data['addressPageType'] as String?,
'discoveredAddresses': data['discoveredAddresses'] as Map<String, dynamic>? ?? {}, 'activeIndexByType': (data['activeIndexByType'] as Map<dynamic, dynamic>?) ?? {},
'receiveAddressesMapped': data['receiveAddressesMapped'] as Map<String, dynamic>? ?? {},
'changeAddressesMapped': data['changeAddressesMapped'] as Map<String, dynamic>? ?? {},
}; };
} }
@ -513,97 +478,267 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required BasedUtxoNetwork network, required BasedUtxoNetwork network,
required bool isHardwareWallet, required bool isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses, BitcoinAddressRecordMap? initialAddressesRecords,
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses, BitcoinDiscoveredAddressesMap? initialDiscoveredAddresses,
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>?
initialReceiveAddressesMapped,
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>?
initialChangeAddressesMapped,
Map<BitcoinAddressType, Map<SeedBytesType, Map<bool, bool>>>? initialDiscoveredAddresses,
}) { }) {
initialAddresses ??= (json['allAddresses'] as List) // TODO: put this into records map (currently unused)
.map((record) => BitcoinAddressRecord.fromJSON(record as String)) if (json['allAddresses'] != null)
.toList(); initialAddresses ??= (json['allAddresses'] as List)
.map((record) => BitcoinAddressRecord.fromJSON(record as String))
.toList();
initialReceiveAddressesMapped ??= (json['receiveAddressesMapped'] as Map).map( if (json['addressesRecords'] != null)
(addressType, v) => MapEntry( initialAddressesRecords ??= BitcoinAddressRecordMap.fromJson(
BitcoinAddressType.fromValue(addressType as String), json['addressesRecords'] as Map<String, dynamic>,
(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(),
),
),
),
),
),
);
initialDiscoveredAddresses ??= ((json['discoveredAddresses'] as Map?) ?? {}).map( if (json['discoveredAddresses'] != null)
(addressType, v) => MapEntry( initialDiscoveredAddresses ??= BitcoinDiscoveredAddressesMap.fromJson(
BitcoinAddressType.fromValue(addressType as String), json['discoveredAddresses'] as Map<String, dynamic>,
(v as Map).map( );
(seedBytesType, v) => MapEntry(
SeedBytesType.fromValue(seedBytesType as String),
(v as Map).map(
(isChange, v) => MapEntry(isChange == "true", v as bool),
),
),
),
),
);
return ElectrumWalletAddresses( return ElectrumWalletAddresses(
walletInfo, walletInfo,
hdWallets: hdWallets, hdWallets: hdWallets,
network: network, network: network,
isHardwareWallet: isHardwareWallet, isHardwareWallet: isHardwareWallet,
initialAddresses: initialAddresses, initialAddressesRecords: initialAddressesRecords,
initialDiscoveredAddresses: initialDiscoveredAddresses, initialDiscoveredAddresses: initialDiscoveredAddresses,
initialReceiveAddressesMapped: initialReceiveAddressesMapped, initialAddressPageType: json['addressPageType'] != null
initialChangeAddressesMapped: initialChangeAddressesMapped, ? BitcoinAddressType.fromValue(json['addressPageType'] as String)
: null,
initialActiveAddressIndex: (json['activeIndexByType'] as Map<dynamic, dynamic>?)?.map(
(key, value) => MapEntry(BitcoinAddressType.fromValue(key as String), value as int),
),
);
}
}
typedef AddressRecords = List<BaseBitcoinAddressRecord>;
typedef ItemsByIsChange<T> = Map<bool, T>;
typedef ItemsByDerivationPath<T> = Map<String, ItemsByIsChange<T>>;
// Maps by each different property with the final item being a list of addresses
typedef AddressRecordsBySeedType = Map<SeedBytesType, ItemsByDerivationPath<AddressRecords>>;
class ItemsRecordMap<T extends Map<SeedBytesType, ItemsByDerivationPath<dynamic>>>
extends MapBase<BitcoinAddressType, T> {
final Map<BitcoinAddressType, T> _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<BitcoinAddressType> get keys => _data.keys;
@override
Iterable<T> get values => _data.values;
@override
T? remove(Object? key) => _data.remove(key);
}
class BitcoinAddressRecordMap extends ItemsRecordMap<AddressRecordsBySeedType> {
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<BaseBitcoinAddressRecord> allRecords() {
return _data.values
.expand((seedTypeMap) => seedTypeMap.values)
.expand((derivationMap) => derivationMap.values)
.expand((changeMap) => changeMap.values)
.fold<List<BaseBitcoinAddressRecord>>(
[],
(acc, records) => acc..addAll(records),
); );
} }
bool getIsUsed(BaseBitcoinAddressRecord addr) { List<BaseBitcoinAddressRecord> getRecords({
return addr.isUsed || addr.txCount != 0 || addr.balance != 0; required BitcoinAddressType addressType,
required SeedBytesType seedBytesType,
required String derivationPath,
required bool isChange,
}) {
return _data[addressType]?[seedBytesType]?[derivationPath]?[isChange] ?? [];
} }
bool getIsReceive(BaseBitcoinAddressRecord addr) { Map<String, dynamic> toJson() => _data.map(
return !getIsUsed(addr) && !addr.isChange && !addr.isHidden; (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) { static BitcoinAddressRecordMap fromJson(Map<String, dynamic> json) {
if (walletSeedBytesType.isElectrum) { final res = BitcoinAddressRecordMap();
return path.toString() != BitcoinDerivationInfos.ELECTRUM.derivationPath.toString();
}
return path.toString() != final mapped = json.map(
BitcoinAddressUtils.getDerivationFromType( (addressType, v) => MapEntry(
addressType, BitcoinAddressType.fromValue(addressType),
).derivationPath.toString(); (v as Map<String, dynamic>).map(
(seedBytesType, v) => MapEntry(
SeedBytesType.fromValue(seedBytesType),
(v as Map<String, dynamic>).map(
(derivationPath, v) => MapEntry(
derivationPath,
(v as Map).map(
(isChange, v) => MapEntry(
isChange == 'true',
(v as List<dynamic>)
.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<SeedBytesType, ItemsByDerivationPath<bool>>;
class BitcoinDiscoveredAddressesMap extends ItemsRecordMap<DiscoveredAddressRecordsBySeedType> {
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<String, dynamic> 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<String, dynamic> json) {
final res = BitcoinDiscoveredAddressesMap();
final mapped = json.map(
(addressType, v) => MapEntry(
BitcoinAddressType.fromValue(addressType),
(v as Map<String, dynamic>).map(
(seedBytesType, v) => MapEntry(
SeedBytesType.fromValue(seedBytesType),
(v as Map<String, dynamic>).map(
(derivationPath, v) => MapEntry(
derivationPath,
(v as Map).map(
(isChange, v) => MapEntry(isChange == 'true', (v as bool)),
),
),
),
),
),
),
);
res.addAll(mapped);
return res;
} }
} }

View file

@ -1742,7 +1742,6 @@ class ElectrumWorker {
).toAddress(request.network), ).toAddress(request.network),
index: i, index: i,
isChange: request.isChange, isChange: request.isChange,
isHidden: request.shouldHideAddress || request.isChange,
type: request.addressType, type: request.addressType,
network: request.network, network: request.network,
derivationInfo: request.derivationInfo, derivationInfo: request.derivationInfo,

View file

@ -5,7 +5,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest {
required this.count, required this.count,
required this.startIndex, required this.startIndex,
required this.seedBytesType, required this.seedBytesType,
required this.shouldHideAddress, required this.walletType,
required this.derivationInfo, required this.derivationInfo,
required this.isChange, required this.isChange,
required this.addressType, required this.addressType,
@ -20,8 +20,8 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest {
final int count; final int count;
final int startIndex; final int startIndex;
final bool shouldHideAddress;
final WalletType walletType;
final SeedBytesType seedBytesType; final SeedBytesType seedBytesType;
final BitcoinDerivationInfo derivationInfo; final BitcoinDerivationInfo derivationInfo;
final bool isChange; final bool isChange;
@ -38,7 +38,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest {
id: json['id'] as int, id: json['id'] as int,
count: json['count'] as int, count: json['count'] as int,
startIndex: json['startIndex'] 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), seedBytesType: SeedBytesType.fromValue(json['seedBytesType'] as String),
derivationInfo: derivationInfo:
BitcoinDerivationInfo.fromJSON(json['derivationInfo'] as Map<String, dynamic>), BitcoinDerivationInfo.fromJSON(json['derivationInfo'] as Map<String, dynamic>),
@ -57,7 +57,7 @@ class ElectrumWorkerDiscoverAddressesRequest implements ElectrumWorkerRequest {
'completed': completed, 'completed': completed,
'count': count, 'count': count,
'startIndex': startIndex, 'startIndex': startIndex,
'shouldHideAddress': shouldHideAddress, 'walletType': serializeToInt(walletType),
'seedBytesType': seedBytesType.value, 'seedBytesType': seedBytesType.value,
'isChange': isChange, 'isChange': isChange,
'addressType': addressType.value, 'addressType': addressType.value,

View file

@ -95,7 +95,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
} }
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress;
}); });
reaction((_) => mwebSyncStatus, (status) async { reaction((_) => mwebSyncStatus, (status) async {
if (mwebSyncStatus is FailedSyncStatus) { if (mwebSyncStatus is FailedSyncStatus) {
@ -762,7 +762,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
@override @override
@action @action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([ Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes, List<String>? scripthashes,
bool? wait, bool? wait,
]) async { ]) async {
if (!mwebEnabled) { if (!mwebEnabled) {
@ -961,7 +961,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
ECPrivate? privkey; ECPrivate? privkey;
if (!isHardwareWallet) { if (!isHardwareWallet) {
final path = Bip32PathParser.parse(utx.bitcoinAddressRecord.derivationPath); final path = Bip32PathParser.parse(utx.bitcoinAddressRecord.indexedDerivationPath);
privkey = ECPrivate.fromBip32(bip32: hdWallet.derive(path)); privkey = ECPrivate.fromBip32(bip32: hdWallet.derive(path));
} }
@ -979,7 +979,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
} }
if (utx.bitcoinAddressRecord is BitcoinAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinAddressRecord) {
final derivationPath = utx.bitcoinAddressRecord.derivationPath; final derivationPath = utx.bitcoinAddressRecord.indexedDerivationPath;
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath); publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
} }
@ -1128,7 +1128,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
)); ));
// Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets // Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
final changeDerivationPath = changeAddress.derivationPath; final changeDerivationPath = changeAddress.indexedDerivationPath;
utxoDetails.publicKeys[address.pubKeyHash()] = utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath); PublicKeyWithDerivationPath('', changeDerivationPath);

View file

@ -27,7 +27,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required super.network, required super.network,
required super.isHardwareWallet, required super.isHardwareWallet,
required this.mwebEnabled, required this.mwebEnabled,
super.initialAddresses, super.initialAddressesRecords,
super.initialActiveAddressIndex,
super.initialAddressPageType,
List<LitecoinMWEBAddressRecord>? initialMwebAddresses, List<LitecoinMWEBAddressRecord>? initialMwebAddresses,
}) : mwebAddresses = }) : mwebAddresses =
ObservableList<LitecoinMWEBAddressRecord>.of((initialMwebAddresses ?? []).toSet()), ObservableList<LitecoinMWEBAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
@ -41,9 +43,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
} }
@override @override
final walletAddressTypes = LITECOIN_ADDRESS_TYPES; final walletAddressTypes = [SegwitAddressType.p2wpkh];
static const LITECOIN_ADDRESS_TYPES = [SegwitAddressType.p2wpkh];
final ObservableList<LitecoinMWEBAddressRecord> mwebAddresses; final ObservableList<LitecoinMWEBAddressRecord> mwebAddresses;
@ -96,7 +96,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
.where((addr) => addr.type == addressType && addr.seedBytesType == seedBytesType) .where((addr) => addr.type == addressType && addr.seedBytesType == seedBytesType)
.toList(); .toList();
if (existingAddresses.length < ElectrumWalletAddressesBase.defaultReceiveAddressesCount) { if (existingAddresses.length < ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT) {
await discoverNewMWEBAddresses( await discoverNewMWEBAddresses(
seedBytesType: seedBytesType, seedBytesType: seedBytesType,
isChange: false, isChange: false,
@ -110,8 +110,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required bool isChange, required bool isChange,
}) async { }) async {
final count = isChange final count = isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount ? ElectrumWalletAddressesBase.INITIAL_CHANGE_COUNT
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount; : ElectrumWalletAddressesBase.INITIAL_RECEIVE_COUNT;
final startIndex = this.mwebAddresses.length; final startIndex = this.mwebAddresses.length;
@ -325,7 +325,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
String get addressForExchange { String get addressForExchange {
// don't use mweb addresses for exchange refund address: // don't use mweb addresses for exchange refund address:
final addresses = allAddresses.firstWhere( final addresses = allAddresses.firstWhere(
(element) => element.type == SegwitAddressType.p2wpkh && !getIsUsed(element), (addressRecord) =>
addressRecord.type == SegwitAddressType.p2wpkh && !addressRecord.getIsUsed(),
); );
return addresses.address; return addresses.address;
} }
@ -335,7 +336,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
super.updateAddressesInBox(); super.updateAddressesInBox();
final lastP2wpkh = final lastP2wpkh =
allAddresses.where((addressRecord) => isUnusedReceiveAddress(addressRecord)).toList().last; allAddresses.where((addressRecord) => addressRecord.isUnusedReceiveAddress()).toList().last;
if (lastP2wpkh.address != address) { if (lastP2wpkh.address != address) {
addressesMap[lastP2wpkh.address] = 'P2WPKH'; addressesMap[lastP2wpkh.address] = 'P2WPKH';
} else { } else {
@ -343,7 +344,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
} }
final lastMweb = mwebAddresses.firstWhere( final lastMweb = mwebAddresses.firstWhere(
(addressRecord) => isUnusedReceiveAddress(addressRecord), (addressRecord) => addressRecord.isUnusedReceiveAddress(),
); );
if (lastMweb.address != address) { if (lastMweb.address != address) {
addressesMap[lastMweb.address] = 'MWEB'; addressesMap[lastMweb.address] = 'MWEB';
@ -445,9 +446,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required bool isHardwareWallet, required bool isHardwareWallet,
required bool mwebEnabled, required bool mwebEnabled,
}) { }) {
final initialAddresses = (snp['allAddresses'] as List) final electrumJson = ElectrumWalletAddressesBase.fromJson(
.map((record) => BitcoinAddressRecord.fromJSON(record as String)) snp,
.toList(); walletInfo,
hdWallets: hdWallets,
network: network,
isHardwareWallet: isHardwareWallet,
);
final initialMwebAddresses = (snp['mwebAddresses'] as List) final initialMwebAddresses = (snp['mwebAddresses'] as List)
.map( .map(
@ -460,7 +465,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
hdWallets: hdWallets, hdWallets: hdWallets,
network: network, network: network,
isHardwareWallet: isHardwareWallet, isHardwareWallet: isHardwareWallet,
initialAddresses: initialAddresses, initialAddressesRecords: electrumJson.addressesRecords,
initialAddressPageType: electrumJson.addressPageType,
initialActiveAddressIndex: electrumJson.activeIndexByType,
initialMwebAddresses: initialMwebAddresses, initialMwebAddresses: initialMwebAddresses,
mwebEnabled: mwebEnabled, mwebEnabled: mwebEnabled,
); );

View file

@ -54,7 +54,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
} }
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress;
}); });
} }

View file

@ -17,43 +17,20 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required super.hdWallets, required super.hdWallets,
required super.network, required super.network,
required super.isHardwareWallet, required super.isHardwareWallet,
super.initialAddresses, super.initialAddressesRecords,
super.initialActiveAddressIndex,
super.initialAddressPageType, super.initialAddressPageType,
}) : super(walletInfo); }) : super(walletInfo);
@override @override
final walletAddressTypes = BITCOIN_CASH_ADDRESS_TYPES; final walletAddressTypes = [P2pkhAddressType.p2pkh];
static const BITCOIN_CASH_ADDRESS_TYPES = [P2pkhAddressType.p2pkh];
@override @override
@observable
BitcoinAddressType changeAddressType = P2pkhAddressType.p2pkh; BitcoinAddressType changeAddressType = P2pkhAddressType.p2pkh;
@override @override
BitcoinAddressType get addressPageType => P2pkhAddressType.p2pkh; BitcoinAddressType get addressPageType => P2pkhAddressType.p2pkh;
@override
Future<void> 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( static BitcoinCashWalletAddressesBase fromJson(
Map<String, dynamic> json, Map<String, dynamic> json,
WalletInfo walletInfo, { WalletInfo walletInfo, {
@ -62,6 +39,14 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required bool isHardwareWallet, required bool isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
}) { }) {
final electrumJson = ElectrumWalletAddressesBase.fromJson(
json,
walletInfo,
hdWallets: hdWallets,
network: network,
isHardwareWallet: isHardwareWallet,
);
initialAddresses ??= (json['allAddresses'] as List).map((addr) { initialAddresses ??= (json['allAddresses'] as List).map((addr) {
try { try {
BitcoinCashAddress(addr.address); BitcoinCashAddress(addr.address);
@ -92,7 +77,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
hdWallets: hdWallets, hdWallets: hdWallets,
network: network, network: network,
isHardwareWallet: isHardwareWallet, isHardwareWallet: isHardwareWallet,
initialAddresses: initialAddresses, initialAddressesRecords: electrumJson.addressesRecords,
initialAddressPageType: electrumJson.addressPageType,
initialActiveAddressIndex: electrumJson.activeIndexByType,
); );
} }
} }

View file

@ -78,8 +78,4 @@ abstract class WalletAddresses {
bool containsAddress(String address) => bool containsAddress(String address) =>
addressesMap.containsKey(address) || allAddressesMap.containsKey(address); addressesMap.containsKey(address) || allAddressesMap.containsKey(address);
void resetActiveChangeAddress() {
throw UnimplementedError();
}
} }

View file

@ -158,7 +158,7 @@ class CWBitcoin extends Bitcoin {
id: addr.index, id: addr.index,
name: addr.name, name: addr.name,
address: addr.address, address: addr.address,
derivationPath: addr.derivationPath, derivationPath: addr.indexedDerivationPath,
txCount: addr.txCount, txCount: addr.txCount,
balance: addr.balance, balance: addr.balance,
isChange: addr.isChange, isChange: addr.isChange,
@ -291,10 +291,15 @@ class CWBitcoin extends Bitcoin {
@override @override
TransactionPriority getLitecoinTransactionPrioritySlow() => ElectrumTransactionPriority.slow; TransactionPriority getLitecoinTransactionPrioritySlow() => ElectrumTransactionPriority.slow;
@override
void resetActiveAddress(Object wallet) {
(wallet as ElectrumWallet).walletAddresses.resetActiveAddress();
}
@override @override
Future<void> setAddressType(Object wallet, dynamic option) async { Future<void> setAddressType(Object wallet, dynamic option) async {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.setAddressType(option as BitcoinAddressType); await bitcoinWallet.walletAddresses.setAddressPageType(option as BitcoinAddressType);
} }
@override @override
@ -530,7 +535,7 @@ class CWBitcoin extends Bitcoin {
id: addr.index, id: addr.index,
name: addr.name, name: addr.name,
address: addr.address, address: addr.address,
derivationPath: addr.derivationPath, derivationPath: addr.indexedDerivationPath,
txCount: addr.txCount, txCount: addr.txCount,
balance: addr.balance, balance: addr.balance,
isChange: addr.isChange, isChange: addr.isChange,
@ -547,7 +552,7 @@ class CWBitcoin extends Bitcoin {
id: addr.index, id: addr.index,
name: addr.name, name: addr.name,
address: addr.address, address: addr.address,
derivationPath: addr.derivationPath, derivationPath: addr.indexedDerivationPath,
txCount: addr.txCount, txCount: addr.txCount,
balance: addr.balance, balance: addr.balance,
isChange: addr.isChange, isChange: addr.isChange,

View file

@ -98,7 +98,7 @@ class AddressPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
addressListViewModel.resetActiveChangeAddress(); addressListViewModel.resetActiveAddress();
_setEffects(context); _setEffects(context);
@ -162,7 +162,8 @@ class AddressPage extends BasePage {
} }
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { 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)); addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
return; return;
} }

View file

@ -301,10 +301,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type; WalletType get type => wallet.type;
@action @action
void resetActiveChangeAddress() { void resetActiveAddress() {
try { if (isElectrumWallet) {
wallet.walletAddresses.resetActiveChangeAddress(); bitcoin!.resetActiveAddress(wallet);
} catch (_) {} }
} }
@computed @computed

View file

@ -127,23 +127,6 @@ import 'package:mobx/mobx.dart';
"""; """;
const bitcoinCwPart = "part 'cw_bitcoin.dart';"; const bitcoinCwPart = "part 'cw_bitcoin.dart';";
const bitcoinContent = """ const bitcoinContent = """
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh,
P2pkhAddressType.p2pkh,
SegwitAddressType.p2tr,
SegwitAddressType.p2wsh,
P2shAddressType.p2wpkhInP2sh,
];
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh,
SegwitAddressType.mweb,
];
const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
P2pkhAddressType.p2pkh,
];
class ElectrumSubAddress { class ElectrumSubAddress {
ElectrumSubAddress({ ElectrumSubAddress({
required this.id, required this.id,
@ -232,6 +215,7 @@ abstract class Bitcoin {
Future<List<DerivationType>> compareDerivationMethods( Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node}); {required String mnemonic, required Node node});
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations(); Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
void resetActiveAddress(Object wallet);
Future<void> setAddressType(Object wallet, dynamic option); Future<void> setAddressType(Object wallet, dynamic option);
ReceivePageOption getSelectedAddressType(Object wallet); ReceivePageOption getSelectedAddressType(Object wallet);
List<ReceivePageOption> getBitcoinReceivePageOptions(); List<ReceivePageOption> getBitcoinReceivePageOptions();
@ -978,15 +962,12 @@ abstract class BitcoinCash {
"""; """;
const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n'; const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n';
const bitcoinCashCWDefinition = const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
final output = '$bitcoinCashCommonHeaders\n' + final output = '$bitcoinCashCommonHeaders\n' +
(hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') + (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') +
(hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') + (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') +
(hasImplementation (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) +
? bitcoinCashCWDefinition
: bitcoinCashEmptyDefinition) +
'\n' + '\n' +
bitcoinCashContent; bitcoinCashContent;
@ -1121,8 +1102,7 @@ abstract class NanoUtil {
"""; """;
const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n'; const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n';
const nanoCWDefinition = const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
final output = '$nanoCommonHeaders\n' + final output = '$nanoCommonHeaders\n' +
(hasImplementation ? '$nanoCWHeaders\n' : '\n') + (hasImplementation ? '$nanoCWHeaders\n' : '\n') +