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;
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<String, dynamic>;
@ -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, {

View file

@ -99,7 +99,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet<BitcoinWalletAddresses>
}
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
// 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<BitcoinWalletAddresses>
@override
@action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes,
List<String>? scripthashes,
bool? wait,
]) async {
scripthashes ??= this.walletAddresses.allScriptHashes;

View file

@ -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<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
List<BitcoinReceivedSPAddressRecord>? 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,

View file

@ -318,7 +318,6 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
}
Future<void> init() async {
await walletAddresses.init();
await transactionHistory.init();
_autoSaveTimer =
@ -475,7 +474,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
privkey = ECPrivate.fromBip32(
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) {
final derivationPath = utx.bitcoinAddressRecord.derivationPath;
final derivationPath = utx.bitcoinAddressRecord.indexedDerivationPath;
publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
}
@ -637,7 +636,7 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
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<T extends ElectrumWalletAddresses>
@action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes,
List<String>? scripthashes,
bool? wait,
]) async {
scripthashes ??= walletAddresses.allScriptHashes;
@ -1540,10 +1539,10 @@ abstract class ElectrumWalletBase<T extends ElectrumWalletAddresses>
}
// Identify all change outputs
final changeAddresses = walletAddresses.allChangeAddresses;
final changeAddresses = walletAddresses.allAddresses.where((element) => element.isChange);
final List<BitcoinOutput> 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<T extends ElectrumWalletAddresses>
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<T extends ElectrumWalletAddresses>
isElectrum: seedBytesType.isElectrum,
);
final existingReceiveAddresses = (walletAddresses.receiveAddressesMapped[addressType]
?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ??
<BaseBitcoinAddressRecord>[]);
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<T extends ElectrumWalletAddresses>
);
}
final existingChangeAddresses = (walletAddresses.receiveAddressesMapped[addressType]
?[seedBytesType]?[bitcoinDerivationInfo.derivationPath.toString()] ??
<BaseBitcoinAddressRecord>[]);
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<T extends ElectrumWalletAddresses>
List<BitcoinAddressRecord> addresses,
) async {
final newAddresses = <BitcoinAddressRecord>[];
final discoveredAddresses =
<SeedBytesType, Map<BitcoinAddressType, Map<BitcoinDerivationType, List<bool>>>>{};
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<T extends ElectrumWalletAddresses>
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,

View file

@ -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<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
BitcoinAddressRecordMap? initialAddressesRecords,
Map<BitcoinAddressType, int>? initialActiveAddressIndex,
BitcoinDiscoveredAddressesMap? initialDiscoveredAddresses,
BitcoinAddressType? initialAddressPageType,
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>
initialReceiveAddressesMapped = const {},
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>
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,
}) : 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 = <BitcoinAddressType>[];
final ObservableList<BitcoinAddressRecord> _allAddresses;
@observable
BitcoinDiscoveredAddressesMap discoveredAddresses;
@observable
BitcoinAddressRecordMap addressesRecords;
@observable
// { BitcoinAddressType: { SeedBytesType: { isChange: true = discovered } } }
Map<BitcoinAddressType, Map<SeedBytesType, Map<bool, bool>>> discoveredAddresses;
List<BitcoinAddressRecord> _allAddresses = [];
@computed
List<BitcoinAddressRecord> get allAddresses => _allAddresses;
@observable
// { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } }
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>
receiveAddressesMapped;
@action
List<BitcoinAddressRecord> updateAllAddresses() {
_allAddresses = addressesRecords.allRecords().whereType<BitcoinAddressRecord>().toList();
@observable
// { BitcoinAddressType: { SeedBytesType: { derivationPath: [BaseBitcoinAddressRecord] } } }
Map<BitcoinAddressType, Map<SeedBytesType, Map<String, List<BaseBitcoinAddressRecord>>>>
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<BitcoinAddressRecord> get allChangeAddresses =>
_allAddresses.where((addr) => addr.isChange).toList();
@action
Future<void> 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<BaseBitcoinAddressRecord> _selectedReceiveAddresses = [];
@computed
List<BaseBitcoinAddressRecord> get selectedReceiveAddresses {
return receiveAddressesMapped[addressPageType]?[walletSeedBytesType]
?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ??
[];
List<BaseBitcoinAddressRecord> get selectedReceiveAddresses => _selectedReceiveAddresses;
@action
List<BaseBitcoinAddressRecord> updateSelectedReceiveAddresses() {
_selectedReceiveAddresses = addressesRecords.getRecords(
addressType: addressPageType,
seedBytesType: walletSeedBytesType,
derivationPath: _defaultAddressPageDerivationInfo.derivationPath.toString(),
isChange: false,
);
updateNextReceiveAddress();
return _selectedReceiveAddresses;
}
@observable
BitcoinAddressRecord? _nextReceiveAddress;
@computed
List<BaseBitcoinAddressRecord> get selectedChangeAddresses =>
changeAddressesMapped[addressPageType]?[walletSeedBytesType]
?[_defaultAddressPageDerivationInfo.derivationPath.toString()] ??
[];
BitcoinAddressRecord? get nextReceiveAddress => _nextReceiveAddress;
@computed
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
@action
BitcoinAddressRecord? updateNextReceiveAddress() {
final receiveAddresses = selectedReceiveAddresses.whereType<BitcoinAddressRecord>();
if (receiveAddresses.isEmpty) {
return null;
}
@computed
Set<String> 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<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
@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<BitcoinAddressRecord>();
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<String, int> currentReceiveAddressIndexByType;
Map<BitcoinAddressType, int> activeIndexByType;
int get currentReceiveAddressIndex =>
currentReceiveAddressIndexByType[addressPageType.toString()] ?? 0;
int get activeAddressIndex => activeIndexByType[addressPageType] ?? 0;
void set currentReceiveAddressIndex(int 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;
set activeAddressIndex(int index) => activeIndexByType[addressPageType] = index;
@override
Future<void> init() async {
await updateAddressesInBox();
throw UnimplementedError();
}
Future<BaseBitcoinAddressRecord> 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<BitcoinAddressRecord> 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<void> setAddressType(BitcoinAddressType type) async {
addressPageType = type;
walletInfo.addressPageType = addressPageType.toString();
await walletInfo.save();
}
bool isUnusedReceiveAddress(BaseBitcoinAddressRecord addr) {
return !addr.isChange && !getIsUsed(addr);
updateAllAddresses();
}
Map<String, dynamic> toJson() {
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['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<String, int>? ?? {};
changeAddressIndexByType = data["change_address_index"] as Map<String, int>? ?? {};
} catch (_) {}
}
return {
'allAddresses': addresses,
'addressPageType': data['address_page_type'] as String?,
'receiveAddressIndexByType': receiveAddressIndexByType,
'changeAddressIndexByType': changeAddressIndexByType,
'discoveredAddresses': data['discoveredAddresses'] as Map<String, dynamic>? ?? {},
'receiveAddressesMapped': data['receiveAddressesMapped'] as Map<String, dynamic>? ?? {},
'changeAddressesMapped': data['changeAddressesMapped'] as Map<String, dynamic>? ?? {},
'addressesRecords': data['addressesRecords'] as Map<String, dynamic>?,
'discoveredAddresses': data['discoveredAddresses'] as Map<String, dynamic>?,
'addressPageType': data['addressPageType'] as String?,
'activeIndexByType': (data['activeIndexByType'] as Map<dynamic, dynamic>?) ?? {},
};
}
@ -513,97 +478,267 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required BasedUtxoNetwork network,
required bool isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
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,
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<String, dynamic>,
);
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<String, dynamic>,
);
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<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) {
return addr.isUsed || addr.txCount != 0 || addr.balance != 0;
List<BaseBitcoinAddressRecord> 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<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.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<String, dynamic> 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<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),
index: i,
isChange: request.isChange,
isHidden: request.shouldHideAddress || request.isChange,
type: request.addressType,
network: request.network,
derivationInfo: request.derivationInfo,

View file

@ -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<String, dynamic>),
@ -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,

View file

@ -95,7 +95,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
}
autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
this.walletAddresses.isEnabledAutoGenerateNewAddress = this.isEnabledAutoGenerateSubaddress;
});
reaction((_) => mwebSyncStatus, (status) async {
if (mwebSyncStatus is FailedSyncStatus) {
@ -762,7 +762,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
@override
@action
Future<ElectrumWorkerListUnspentResponse?> updateAllUnspents([
Set<String>? scripthashes,
List<String>? scripthashes,
bool? wait,
]) async {
if (!mwebEnabled) {
@ -961,7 +961,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
ECPrivate? privkey;
if (!isHardwareWallet) {
final path = Bip32PathParser.parse(utx.bitcoinAddressRecord.derivationPath);
final path = Bip32PathParser.parse(utx.bitcoinAddressRecord.indexedDerivationPath);
privkey = ECPrivate.fromBip32(bip32: hdWallet.derive(path));
}
@ -979,7 +979,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
}
if (utx.bitcoinAddressRecord is BitcoinAddressRecord) {
final derivationPath = utx.bitcoinAddressRecord.derivationPath;
final derivationPath = utx.bitcoinAddressRecord.indexedDerivationPath;
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
final changeDerivationPath = changeAddress.derivationPath;
final changeDerivationPath = changeAddress.indexedDerivationPath;
utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath);

View file

@ -27,7 +27,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required super.network,
required super.isHardwareWallet,
required this.mwebEnabled,
super.initialAddresses,
super.initialAddressesRecords,
super.initialActiveAddressIndex,
super.initialAddressPageType,
List<LitecoinMWEBAddressRecord>? initialMwebAddresses,
}) : mwebAddresses =
ObservableList<LitecoinMWEBAddressRecord>.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<LitecoinMWEBAddressRecord> 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,
);

View file

@ -54,7 +54,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
}
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.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<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(
Map<String, dynamic> json,
WalletInfo walletInfo, {
@ -62,6 +39,14 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required bool isHardwareWallet,
List<BitcoinAddressRecord>? 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,
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -127,23 +127,6 @@ import 'package:mobx/mobx.dart';
""";
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
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 {
ElectrumSubAddress({
required this.id,
@ -232,6 +215,7 @@ abstract class Bitcoin {
Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node});
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
void resetActiveAddress(Object wallet);
Future<void> setAddressType(Object wallet, dynamic option);
ReceivePageOption getSelectedAddressType(Object wallet);
List<ReceivePageOption> 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') +