mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-07 08:04:04 +01:00
Upstream reformatted its whole tree in 16ddc0837. Matching it removes
formatting from the conflict surface of this and every later sync.
1227 lines
40 KiB
Dart
1227 lines
40 KiB
Dart
import 'dart:async' show Zone;
|
|
import 'dart:io' show Platform;
|
|
import 'dart:math';
|
|
import "package:collection/collection.dart";
|
|
|
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
|
import 'package:breez_sdk_spark_flutter/breez_sdk_spark.dart';
|
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
|
import 'package:cw_bitcoin/lightning/lightning_addres_type.dart';
|
|
import 'package:cw_bitcoin/lightning/lightning_wallet.dart';
|
|
import 'package:cw_core/pathForWallet.dart';
|
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
|
import 'package:cw_core/unspent_coin_type.dart';
|
|
import 'package:cw_core/utils/print_verbose.dart';
|
|
import 'package:cw_core/wallet_addresses.dart';
|
|
import 'package:cw_core/generate_name.dart';
|
|
import 'package:cw_core/wallet_info.dart';
|
|
import 'package:cw_core/wallet_type.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
part 'electrum_wallet_addresses.g.dart';
|
|
|
|
class UnsupportedAddressTypeForAccountException implements Exception {
|
|
UnsupportedAddressTypeForAccountException(this.message);
|
|
|
|
final String message;
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
abstract class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
|
|
|
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
|
|
SegwitAddresType.p2wpkh,
|
|
P2pkhAddressType.p2pkh,
|
|
SegwitAddresType.p2tr,
|
|
SegwitAddresType.p2wsh,
|
|
P2shAddressType.p2wpkhInP2sh,
|
|
];
|
|
|
|
const List<BitcoinAddressType> LEGACY_DUPLICATE_ADDRESS_TYPES = [
|
|
SegwitAddresType.p2wpkh,
|
|
SegwitAddresType.p2wsh,
|
|
];
|
|
|
|
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
|
|
SegwitAddresType.p2wpkh,
|
|
SegwitAddresType.mweb,
|
|
];
|
|
|
|
const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
|
|
P2pkhAddressType.p2pkh,
|
|
];
|
|
|
|
const List<BitcoinAddressType> DOGECOIN_ADDRESS_TYPES = [
|
|
P2pkhAddressType.p2pkh,
|
|
];
|
|
|
|
const List<BitcoinAddressType> EXTRA_ACCOUNT_ADDRESS_TYPES = [SegwitAddresType.p2wpkh];
|
|
|
|
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|
ElectrumWalletAddressesBase(
|
|
WalletInfo walletInfo, {
|
|
required this.mainHdByTypeAndAccount,
|
|
required this.sideHdByTypeAndAccount,
|
|
required this.accountIndexes,
|
|
required this.currentAccountIndex,
|
|
required this.legacyMainHd,
|
|
required this.legacySideHd,
|
|
required this.network,
|
|
required this.isHardwareWallet,
|
|
List<BitcoinAddressRecord>? initialAddresses,
|
|
Map<String, int>? initialRegularAddressIndex,
|
|
Map<String, int>? initialChangeAddressIndex,
|
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
|
int initialSilentAddressIndex = 0,
|
|
List<BitcoinAddressRecord>? initialMwebAddresses,
|
|
Bip32Slip10Secp256k1? masterHd,
|
|
BitcoinAddressType? initialAddressPageType,
|
|
this.lightningWallet,
|
|
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
|
addressesByReceiveType =
|
|
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
|
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
|
.toSet()),
|
|
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
|
.where((addressRecord) => addressRecord.isHidden && !addressRecord.isUsed)
|
|
.toSet()),
|
|
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
|
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
|
_addressPageType = _resolveInitialAddressPageType(
|
|
initialAddressPageType: initialAddressPageType,
|
|
walletInfo: walletInfo,
|
|
mainHdByTypeAndAccount: mainHdByTypeAndAccount,
|
|
currentAccountIndex: currentAccountIndex,
|
|
),
|
|
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
|
(initialSilentAddresses ?? []).toSet()),
|
|
currentSilentAddressIndex = initialSilentAddressIndex,
|
|
mwebAddresses =
|
|
ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
|
|
lockedReceiveAddressByType = ObservableMap<BitcoinAddressType, String>(),
|
|
lightningAddress = lightningWallet?.cachedAddress,
|
|
super(walletInfo) {
|
|
if (masterHd != null) {
|
|
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
|
b_scan:
|
|
ECPrivate.fromHex(masterHd.derivePath(SILENT_PAYMENTS_SCAN_PATH).privateKey.toHex()),
|
|
b_spend:
|
|
ECPrivate.fromHex(masterHd.derivePath(SILENT_PAYMENTS_SPEND_PATH).privateKey.toHex()),
|
|
network: network,
|
|
);
|
|
|
|
// Clean the Silent Payment Addresses if the initial addresses are the old SP Addresses
|
|
if (!silentAddresses
|
|
.any((addr) => addr.index == 0 && addr.address == silentAddress.toString())) {
|
|
silentAddresses.clear();
|
|
}
|
|
|
|
if (!silentAddresses.any((addr) => addr.index == 0 && addr.isHidden == false))
|
|
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
|
silentAddress.toString(),
|
|
index: 0,
|
|
isHidden: false,
|
|
name: "",
|
|
silentPaymentTweak: null,
|
|
network: network,
|
|
type: SilentPaymentsAddresType.p2sp,
|
|
));
|
|
for (var i = 0; i < 5; i++) {
|
|
if (!silentAddresses.any((addr) => addr.index == i && addr.isHidden == (i == 0)))
|
|
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
|
silentAddress!.toLabeledSilentPaymentAddress(i).toString(),
|
|
index: i,
|
|
isHidden: i == 0,
|
|
name: "",
|
|
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(i)),
|
|
network: network,
|
|
type: SilentPaymentsAddresType.p2sp,
|
|
));
|
|
}
|
|
}
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
static const defaultReceiveAddressesCount = 22;
|
|
static const defaultChangeAddressesCount = 17;
|
|
static const gap = 20;
|
|
|
|
final ObservableList<BitcoinAddressRecord> _addresses;
|
|
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
|
|
|
// TODO: add this variable in `bitcoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
|
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
|
|
|
// TODO: add this variable in `litecoin_wallet_addresses` and just add a cast in cw_bitcoin to use it
|
|
final ObservableList<BitcoinAddressRecord> mwebAddresses;
|
|
final BasedUtxoNetwork network;
|
|
final Map<int, Map<BitcoinAddressType, Bip32Slip10Secp256k1>> mainHdByTypeAndAccount;
|
|
final Map<int, Map<BitcoinAddressType, Bip32Slip10Secp256k1>> sideHdByTypeAndAccount;
|
|
List<int> accountIndexes;
|
|
|
|
@observable
|
|
int currentAccountIndex;
|
|
final Bip32Slip10Secp256k1 legacyMainHd;
|
|
final Bip32Slip10Secp256k1 legacySideHd;
|
|
final bool isHardwareWallet;
|
|
LightningWallet? lightningWallet;
|
|
|
|
@observable
|
|
ObservableMap<BitcoinAddressType, String> lockedReceiveAddressByType;
|
|
|
|
@observable
|
|
SilentPaymentOwner? silentAddress;
|
|
|
|
@observable
|
|
late BitcoinAddressType _addressPageType;
|
|
|
|
@computed
|
|
BitcoinAddressType get addressPageType => _addressPageType;
|
|
|
|
@observable
|
|
String? activeSilentAddress;
|
|
|
|
@observable
|
|
String? lightningAddress;
|
|
|
|
@computed
|
|
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
|
|
|
@observable
|
|
bool addressRefreshToggle = false;
|
|
|
|
@action
|
|
String getFreshAddress() {
|
|
addressRefreshToggle = !addressRefreshToggle;
|
|
return address;
|
|
}
|
|
|
|
@override
|
|
String get addressForExchange => getFreshAddress();
|
|
|
|
@override
|
|
@computed
|
|
String get address {
|
|
final _ = addressRefreshToggle;
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
if (activeSilentAddress != null) {
|
|
return activeSilentAddress!;
|
|
}
|
|
|
|
return silentAddress.toString();
|
|
}
|
|
|
|
if (addressPageType == LightningAddressType.p2l) {
|
|
return lightningAddress ??
|
|
"Error: Unable to fetch your Lightning address, please check your network connection.";
|
|
}
|
|
|
|
final accountIndexForCheck = walletInfo.type == WalletType.bitcoin ? currentAccountIndex : 0;
|
|
if (!_isAddressTypeSupportedForAccount(addressPageType, accountIndexForCheck)) {
|
|
printV("addressPageType $addressPageType is not supported for account $accountIndexForCheck");
|
|
return "";
|
|
}
|
|
|
|
final typeMatchingAddressesAll = _addresses
|
|
.where((addr) =>
|
|
_isCurrentAccountAddress(addr) && !addr.isHidden && _isAddressPageTypeMatch(addr))
|
|
.toList();
|
|
|
|
// Prefer standard derivation addresses for the current/active address,
|
|
// but keep legacy addresses present in the overall address lists.
|
|
final typeMatchingAddresses = <BitcoinAddressRecord>[
|
|
...typeMatchingAddressesAll.where((a) => !a.isLegacyDerivation),
|
|
...typeMatchingAddressesAll.where((a) => a.isLegacyDerivation),
|
|
];
|
|
|
|
final typeMatchingReceiveAddressesAll = typeMatchingAddressesAll
|
|
.where((addr) => !addr.isUsed && !hiddenAddresses.contains(addr.address))
|
|
.toList();
|
|
final typeMatchingReceiveAddresses = <BitcoinAddressRecord>[
|
|
...typeMatchingReceiveAddressesAll.where((a) => !a.isLegacyDerivation),
|
|
...typeMatchingReceiveAddressesAll.where((a) => a.isLegacyDerivation),
|
|
];
|
|
|
|
if (!isEnabledAutoGenerateSubaddress) {
|
|
if (previousAddressRecord != null && previousAddressRecord!.type == addressPageType) {
|
|
return previousAddressRecord!.address;
|
|
}
|
|
|
|
if (typeMatchingAddresses.isNotEmpty) {
|
|
return typeMatchingAddresses.first.address;
|
|
}
|
|
|
|
return generateNewAddress().address;
|
|
}
|
|
|
|
if (typeMatchingAddresses.isEmpty || typeMatchingReceiveAddresses.isEmpty) {
|
|
return generateNewAddress().address;
|
|
}
|
|
|
|
final locked = lockedReceiveAddressByType[addressPageType];
|
|
if (locked != null && !hiddenAddresses.contains(locked)) return locked;
|
|
|
|
final prev = previousAddressRecord;
|
|
if (prev != null &&
|
|
_isCurrentAccountAddress(prev) &&
|
|
prev.type == addressPageType &&
|
|
!prev.isUsed &&
|
|
!prev.isLegacyDerivation &&
|
|
!hiddenAddresses.contains(prev.address)) {
|
|
return prev.address;
|
|
}
|
|
|
|
return typeMatchingReceiveAddresses.first.address;
|
|
}
|
|
|
|
@observable
|
|
bool isEnabledAutoGenerateSubaddress = true;
|
|
|
|
@override
|
|
set address(String addr) {
|
|
if (addr == "Silent Payments" && SilentPaymentsAddresType.p2sp != addressPageType) {
|
|
return;
|
|
}
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
|
|
|
if (selected.silentPaymentTweak != null && silentAddress != null) {
|
|
activeSilentAddress =
|
|
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
|
|
} else {
|
|
activeSilentAddress = silentAddress!.toString();
|
|
}
|
|
return;
|
|
}
|
|
try {
|
|
final addressRecord = _addresses.firstWhere(
|
|
(addressRecord) => addressRecord.address == addr && !addressRecord.isLegacyDerivation,
|
|
orElse: () => _addresses.firstWhere((r) => r.address == addr),
|
|
);
|
|
|
|
lockedReceiveAddressByType.remove(addressPageType);
|
|
|
|
previousAddressRecord = addressRecord;
|
|
receiveAddresses.remove(addressRecord);
|
|
receiveAddresses.insert(0, addressRecord);
|
|
|
|
if (isEnabledAutoGenerateSubaddress &&
|
|
addressRecord.isUsed &&
|
|
addressRecord.type == addressPageType) {
|
|
lockedReceiveAddressByType[addressPageType] = addr;
|
|
}
|
|
} catch (e) {
|
|
printV("ElectrumWalletAddressBase: set address ($addr): $e");
|
|
}
|
|
}
|
|
|
|
@action
|
|
void clearLockIfMatches(BitcoinAddressType type, String address) {
|
|
final locked = lockedReceiveAddressByType[type];
|
|
if (locked != null && locked == address) {
|
|
lockedReceiveAddressByType.remove(type);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String get primaryAddress {
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
return silentAddress?.toString() ?? '';
|
|
}
|
|
|
|
return _firstAddressForType(addressPageType);
|
|
}
|
|
|
|
String get payjoinCompatibleAddress {
|
|
final addrType = (addressPageType == SilentPaymentsAddresType.p2sp ||
|
|
addressPageType == LightningAddressType.p2l)
|
|
? SegwitAddresType.p2wpkh
|
|
: addressPageType;
|
|
|
|
return _firstAddressForType(addrType);
|
|
}
|
|
|
|
String _firstAddressForType(BitcoinAddressType addrType) {
|
|
final mainMap = walletInfo.type == WalletType.bitcoin
|
|
? mainHdByTypeAndAccount[currentAccountIndex]
|
|
: mainHdByTypeAndAccount[0];
|
|
|
|
final mainHd = mainMap?[addrType] ?? mainMap?.values.first;
|
|
|
|
if (mainHd == null) return '';
|
|
|
|
return getAddress(index: 0, hd: mainHd, addressType: addrType);
|
|
}
|
|
|
|
Map<String, int> currentReceiveAddressIndexByType;
|
|
|
|
int get currentReceiveAddressIndex =>
|
|
currentReceiveAddressIndexByType[_addressPageType.toString()] ?? 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;
|
|
|
|
int currentSilentAddressIndex;
|
|
|
|
@observable
|
|
BitcoinAddressRecord? previousAddressRecord;
|
|
|
|
@computed
|
|
int get totalCountOfReceiveAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
|
if (!addressRecord.isHidden) {
|
|
return acc + 1;
|
|
}
|
|
return acc;
|
|
});
|
|
|
|
@computed
|
|
int get totalCountOfChangeAddresses => addressesByReceiveType.fold(0, (acc, addressRecord) {
|
|
if (addressRecord.isHidden) {
|
|
return acc + 1;
|
|
}
|
|
return acc;
|
|
});
|
|
|
|
@override
|
|
Future<void> init({List<int> accountIndexes = const []}) async {
|
|
final effectiveAccountIndexes =
|
|
accountIndexes.isNotEmpty ? accountIndexes : this.accountIndexes;
|
|
|
|
if (accountIndexes.isNotEmpty) this.accountIndexes = accountIndexes;
|
|
if (walletInfo.type == WalletType.bitcoinCash) {
|
|
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
|
} else if (walletInfo.type == WalletType.litecoin) {
|
|
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
|
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
|
|
await _generateInitialAddresses(type: SegwitAddresType.mweb);
|
|
}
|
|
} else if (walletInfo.type == WalletType.dogecoin) {
|
|
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
|
} else if (walletInfo.type == WalletType.bitcoin) {
|
|
for (final accountIndex in effectiveAccountIndexes) {
|
|
|
|
await prepareAccountAddresses(
|
|
accountIndex,
|
|
types: accountIndex == 0 ? BITCOIN_ADDRESS_TYPES : EXTRA_ACCOUNT_ADDRESS_TYPES,
|
|
includeLegacy: accountIndex == 0,
|
|
);
|
|
}
|
|
}
|
|
|
|
updateAddressesByMatch();
|
|
updateReceiveAddresses();
|
|
updateChangeAddresses();
|
|
await _validateAddresses();
|
|
await updateAddressesInBox();
|
|
|
|
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
|
currentReceiveAddressIndex = 0;
|
|
}
|
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
|
currentChangeAddressIndex = 0;
|
|
}
|
|
}
|
|
|
|
@action
|
|
Future<BitcoinAddressRecord> getChangeAddress(
|
|
{List<BitcoinUnspent>? inputs,
|
|
List<BitcoinOutput>? outputs,
|
|
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) async {
|
|
updateChangeAddresses();
|
|
|
|
if (changeAddresses.isEmpty) {
|
|
final accountChangeCount = _addresses
|
|
.where((addressRecord) =>
|
|
_isCurrentAccountAddress(addressRecord) &&
|
|
addressRecord.isHidden &&
|
|
addressRecord.type == addressPageType)
|
|
.length;
|
|
|
|
final newAddresses = await _createNewAddresses(
|
|
gap,
|
|
startIndex: accountChangeCount,
|
|
isHidden: true,
|
|
type: addressPageType,
|
|
accountIndex: currentAccountIndex,
|
|
);
|
|
|
|
addAddresses(newAddresses);
|
|
}
|
|
|
|
if (currentChangeAddressIndex >= changeAddresses.length) {
|
|
currentChangeAddressIndex = 0;
|
|
}
|
|
|
|
updateChangeAddresses();
|
|
final address = changeAddresses[currentChangeAddressIndex];
|
|
currentChangeAddressIndex += 1;
|
|
return address;
|
|
}
|
|
|
|
bool _isCurrentAccountAddress(BitcoinAddressRecord addressRecord) {
|
|
return walletInfo.type != WalletType.bitcoin ||
|
|
addressRecord.accountIndex == currentAccountIndex;
|
|
}
|
|
|
|
Map<String, String> get labels {
|
|
final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32));
|
|
final labels = <String, String>{};
|
|
for (int i = 0; i < silentAddresses.length; i++) {
|
|
final silentAddressRecord = silentAddresses[i];
|
|
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
|
|
|
|
if (silentPaymentTweak != null &&
|
|
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
|
|
labels[G
|
|
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
|
|
.toHex()] = silentPaymentTweak;
|
|
}
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
Future<void> prepareAccountAddresses(
|
|
int accountIndex, {
|
|
List<BitcoinAddressType> types = BITCOIN_ADDRESS_TYPES,
|
|
bool includeLegacy = false,
|
|
}) async {
|
|
for (final type in types) {
|
|
final shouldSkipHardwareWalletType = isHardwareWallet && type != SegwitAddresType.p2wpkh;
|
|
|
|
if (shouldSkipHardwareWalletType) continue;
|
|
|
|
await _generateInitialAddresses(accountIndex: accountIndex, type: type);
|
|
|
|
// Legacy derivation for these types is identical to the standard one.
|
|
if (includeLegacy && !LEGACY_DUPLICATE_ADDRESS_TYPES.contains(type)) {
|
|
await _generateInitialAddresses(
|
|
accountIndex: accountIndex,
|
|
type: type,
|
|
isLegacyDerivation: true,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@action
|
|
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
|
|
if (addressPageType is LightningAddressType) {
|
|
throw Exception("Lightning addresses cannot be rotated");
|
|
}
|
|
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
|
|
final currentSilentAddressIndex = silentAddresses
|
|
.where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
|
|
.length -
|
|
1;
|
|
|
|
this.currentSilentAddressIndex = currentSilentAddressIndex;
|
|
|
|
final address = BitcoinSilentPaymentAddressRecord(
|
|
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
|
|
index: currentSilentAddressIndex,
|
|
isHidden: false,
|
|
name: label,
|
|
silentPaymentTweak:
|
|
BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
|
|
network: network,
|
|
type: SilentPaymentsAddresType.p2sp,
|
|
);
|
|
|
|
silentAddresses.add(address);
|
|
Future.delayed(Duration.zero, () => updateAddressesByMatch());
|
|
|
|
return address;
|
|
}
|
|
|
|
final accountIndex = walletInfo.type == WalletType.bitcoin ? currentAccountIndex : 0;
|
|
|
|
final newAddressIndex = addressesByReceiveType.fold(
|
|
0,
|
|
(int acc, addressRecord) => addressRecord is BitcoinAddressRecord &&
|
|
addressRecord.accountIndex == accountIndex &&
|
|
addressRecord.isHidden == false
|
|
? acc + 1
|
|
: acc);
|
|
|
|
final hd = _hdForAddressGeneration(
|
|
isHidden: false,
|
|
type: addressPageType,
|
|
isLegacyDerivation: false,
|
|
accountIndex: accountIndex,
|
|
);
|
|
final address = BitcoinAddressRecord(
|
|
getAddress(index: newAddressIndex, hd: hd, addressType: addressPageType),
|
|
index: newAddressIndex,
|
|
accountIndex: accountIndex,
|
|
isHidden: false,
|
|
isHiddenChecked: true,
|
|
isLegacyDerivation: false,
|
|
name: label,
|
|
type: addressPageType,
|
|
network: network,
|
|
);
|
|
Future.delayed(Duration.zero, () {
|
|
_addresses.add(address);
|
|
updateAddressesByMatch();
|
|
});
|
|
return address;
|
|
}
|
|
|
|
String getAddress({
|
|
required int index,
|
|
required Bip32Slip10Secp256k1 hd,
|
|
BitcoinAddressType? addressType,
|
|
}) =>
|
|
'';
|
|
|
|
Future<String> getAddressAsync({
|
|
required int index,
|
|
required Bip32Slip10Secp256k1 hd,
|
|
BitcoinAddressType? addressType,
|
|
}) async =>
|
|
getAddress(index: index, hd: hd, addressType: addressType);
|
|
|
|
void addBitcoinAddressTypes() {
|
|
final lastP2wpkh = _addresses
|
|
.where((addressRecord) =>
|
|
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
|
.lastOrNull;
|
|
if (lastP2wpkh != null) {
|
|
if (lastP2wpkh.address != address) {
|
|
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2WPKH';
|
|
}
|
|
}
|
|
|
|
final lastP2pkh = _addresses.firstWhereOrNull(
|
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
|
if (lastP2pkh != null) {
|
|
if (lastP2pkh.address != address) {
|
|
addressesMap[lastP2pkh.address] = 'P2PKH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2PKH';
|
|
}
|
|
}
|
|
|
|
final lastP2sh = _addresses.firstWhereOrNull((addressRecord) =>
|
|
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
|
|
if (lastP2sh != null) {
|
|
if (lastP2sh.address != address) {
|
|
addressesMap[lastP2sh.address] = 'P2SH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2SH';
|
|
}
|
|
}
|
|
|
|
final lastP2tr = _addresses.firstWhereOrNull(
|
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
|
|
if (lastP2tr != null) {
|
|
if (lastP2tr.address != address) {
|
|
addressesMap[lastP2tr.address] = 'P2TR';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2TR';
|
|
}
|
|
}
|
|
|
|
final lastP2wsh = _addresses.firstWhereOrNull(
|
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
|
|
if (lastP2wsh != null) {
|
|
if (lastP2wsh.address != address) {
|
|
addressesMap[lastP2wsh.address] = 'P2WSH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2WSH';
|
|
}
|
|
}
|
|
|
|
final firstSilentAddressRecord = silentAddresses.firstOrNull;
|
|
if (firstSilentAddressRecord != null) {
|
|
if (firstSilentAddressRecord.address != address) {
|
|
addressesMap[firstSilentAddressRecord.address] = firstSilentAddressRecord.name.isEmpty
|
|
? "Silent Payments"
|
|
: "Silent Payments - ${firstSilentAddressRecord.name}";
|
|
} else {
|
|
addressesMap[address] = 'Active - Silent Payments';
|
|
}
|
|
}
|
|
|
|
if (lightningAddress != null) {
|
|
if (lightningAddress != address) {
|
|
addressesMap[lightningAddress!] = 'LN';
|
|
} else {
|
|
addressesMap[address] = 'Active - LN';
|
|
}
|
|
}
|
|
}
|
|
|
|
void addLitecoinAddressTypes() {
|
|
final lastP2wpkh = _addresses
|
|
.where((addressRecord) =>
|
|
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
|
.toList()
|
|
.last;
|
|
if (lastP2wpkh.address != address) {
|
|
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2WPKH';
|
|
}
|
|
|
|
final lastMweb = _addresses.firstWhere(
|
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
|
|
if (lastMweb.address != address) {
|
|
addressesMap[lastMweb.address] = 'MWEB';
|
|
} else {
|
|
addressesMap[address] = 'Active - MWEB';
|
|
}
|
|
}
|
|
|
|
void addP2PKHAddressTypes() {
|
|
final lastP2pkh = _addresses.firstWhere(
|
|
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
|
if (lastP2pkh.address != address) {
|
|
addressesMap[lastP2pkh.address] = 'P2PKH';
|
|
} else {
|
|
addressesMap[address] = 'Active - P2PKH';
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> updateAddressesInBox() async {
|
|
try {
|
|
addressesMap.clear();
|
|
addressesMap[address] = 'Active';
|
|
|
|
allAddressesMap.clear();
|
|
_addresses.forEach((addressRecord) {
|
|
allAddressesMap[addressRecord.address] = addressRecord.name;
|
|
});
|
|
|
|
switch (walletInfo.type) {
|
|
case WalletType.bitcoin:
|
|
addBitcoinAddressTypes();
|
|
break;
|
|
case WalletType.litecoin:
|
|
addLitecoinAddressTypes();
|
|
break;
|
|
case WalletType.bitcoinCash:
|
|
addP2PKHAddressTypes();
|
|
break;
|
|
case WalletType.dogecoin:
|
|
addP2PKHAddressTypes();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
await saveAddressesInBox();
|
|
} catch (e) {
|
|
printV("updateAddresses $e");
|
|
}
|
|
}
|
|
|
|
@action
|
|
void updateAddress(String address, String label) {
|
|
BaseBitcoinAddressRecord? foundAddress;
|
|
_addresses.forEach((addressRecord) {
|
|
if (addressRecord.address == address) {
|
|
foundAddress = addressRecord;
|
|
}
|
|
});
|
|
silentAddresses.forEach((addressRecord) {
|
|
if (addressRecord.address == address) {
|
|
foundAddress = addressRecord;
|
|
}
|
|
});
|
|
mwebAddresses.forEach((addressRecord) {
|
|
if (addressRecord.address == address) {
|
|
foundAddress = addressRecord;
|
|
}
|
|
});
|
|
|
|
if (foundAddress != null) {
|
|
foundAddress!.setNewName(label);
|
|
|
|
if (foundAddress is BitcoinAddressRecord) {
|
|
final index = _addresses.indexOf(foundAddress);
|
|
_addresses.remove(foundAddress);
|
|
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
|
|
} else {
|
|
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
|
|
silentAddresses.remove(foundAddress);
|
|
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
|
|
}
|
|
}
|
|
}
|
|
|
|
@action
|
|
void updateAddressesByMatch() {
|
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
|
addressesByReceiveType.clear();
|
|
addressesByReceiveType.addAll(silentAddresses);
|
|
return;
|
|
}
|
|
|
|
addressesByReceiveType.clear();
|
|
addressesByReceiveType.addAll(
|
|
_addresses
|
|
.where((addressRecord) =>
|
|
_isCurrentAccountAddress(addressRecord) && _isAddressPageTypeMatch(addressRecord))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
@action
|
|
void updateReceiveAddresses() {
|
|
receiveAddresses.removeRange(0, receiveAddresses.length);
|
|
final newAddresses = _addresses.where((addressRecord) =>
|
|
_isCurrentAccountAddress(addressRecord) &&
|
|
!addressRecord.isHidden &&
|
|
!addressRecord.isUsed);
|
|
receiveAddresses.addAll(newAddresses);
|
|
}
|
|
|
|
@action
|
|
void updateChangeAddresses() {
|
|
changeAddresses.removeRange(0, changeAddresses.length);
|
|
final newAddresses = _addresses.where((addressRecord) =>
|
|
_isCurrentAccountAddress(addressRecord) &&
|
|
addressRecord.isHidden &&
|
|
!addressRecord.isUsed &&
|
|
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
|
|
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
|
|
changeAddresses.addAll(newAddresses);
|
|
}
|
|
|
|
@action
|
|
Future<void> discoverAddresses(
|
|
List<BitcoinAddressRecord> addressList,
|
|
bool isHidden,
|
|
Future<String?> Function(BitcoinAddressRecord) getAddressHistory, {
|
|
BitcoinAddressType type = SegwitAddresType.p2wpkh,
|
|
required bool isLegacyDerivation,
|
|
int accountIndex = 0,
|
|
}) async {
|
|
final newAddresses = await _createNewAddresses(
|
|
gap,
|
|
startIndex: addressList.length,
|
|
isHidden: isHidden,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
type: type,
|
|
accountIndex: accountIndex,
|
|
);
|
|
|
|
addAddresses(newAddresses);
|
|
addressList.addAll(newAddresses);
|
|
|
|
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
|
final isLastAddressUsed = addressesWithHistory.last != null;
|
|
|
|
if (isLastAddressUsed) {
|
|
await discoverAddresses(
|
|
addressList,
|
|
isHidden,
|
|
getAddressHistory,
|
|
type: type,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex,
|
|
);
|
|
}
|
|
}
|
|
|
|
@action
|
|
Future<List<BitcoinAddressRecord>> discoverAddressesBatch(
|
|
List<BitcoinAddressRecord> addressList,
|
|
bool isHidden,
|
|
Future<Set<String>> Function(List<BitcoinAddressRecord>) getUsedAddresses, {
|
|
BitcoinAddressType type = SegwitAddresType.p2wpkh,
|
|
required bool isLegacyDerivation,
|
|
int accountIndex = 0,
|
|
}) async {
|
|
final newAddresses = await _createNewAddresses(
|
|
gap,
|
|
startIndex: addressList.length,
|
|
isHidden: isHidden,
|
|
type: type,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex,
|
|
);
|
|
addAddresses(newAddresses);
|
|
|
|
final usedAddresses = await getUsedAddresses(newAddresses);
|
|
|
|
final hasUsedAddressInGap =
|
|
newAddresses.any((addressRecord) => usedAddresses.contains(addressRecord.address));
|
|
|
|
if (!hasUsedAddressInGap) {
|
|
return newAddresses;
|
|
}
|
|
|
|
final updatedAddressList = [...addressList, ...newAddresses];
|
|
|
|
final moreNewAddresses = await discoverAddressesBatch(
|
|
updatedAddressList,
|
|
isHidden,
|
|
getUsedAddresses,
|
|
type: type,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex,
|
|
);
|
|
|
|
return [...newAddresses, ...moreNewAddresses];
|
|
}
|
|
|
|
Future<void> _generateInitialAddresses({
|
|
BitcoinAddressType type = SegwitAddresType.p2wpkh,
|
|
bool isLegacyDerivation = false,
|
|
int accountIndex = 0,
|
|
}) async {
|
|
if (isLegacyDerivation && accountIndex != 0) return;
|
|
|
|
var countOfReceiveAddresses = 0;
|
|
var countOfHiddenAddresses = 0;
|
|
|
|
_addresses.forEach((addr) {
|
|
if (addr.accountIndex == accountIndex &&
|
|
addr.type == type &&
|
|
addr.isLegacyDerivation == isLegacyDerivation) {
|
|
if (addr.isHidden) {
|
|
countOfHiddenAddresses += 1;
|
|
} else {
|
|
countOfReceiveAddresses += 1;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
|
|
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
|
|
final newAddresses = await _createNewAddresses(addressesCount,
|
|
startIndex: countOfReceiveAddresses,
|
|
isHidden: false,
|
|
type: type,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex);
|
|
|
|
addAddresses(newAddresses);
|
|
}
|
|
|
|
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
|
|
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
|
|
final newAddresses = await _createNewAddresses(addressesCount,
|
|
startIndex: countOfHiddenAddresses,
|
|
isHidden: true,
|
|
type: type,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex);
|
|
addAddresses(newAddresses);
|
|
}
|
|
}
|
|
|
|
Future<List<BitcoinAddressRecord>> _createNewAddresses(
|
|
int count, {
|
|
int startIndex = 0,
|
|
bool isHidden = false,
|
|
BitcoinAddressType? type,
|
|
bool isLegacyDerivation = false,
|
|
int accountIndex = 0,
|
|
}) async {
|
|
final list = <BitcoinAddressRecord>[];
|
|
|
|
for (var i = startIndex; i < count + startIndex; i++) {
|
|
final addrType = type ?? addressPageType;
|
|
|
|
try {
|
|
final hd = _hdForAddressGeneration(
|
|
isHidden: isHidden,
|
|
type: addrType,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
accountIndex: accountIndex,
|
|
);
|
|
|
|
final address = BitcoinAddressRecord(
|
|
await getAddressAsync(index: i, hd: hd, addressType: addrType),
|
|
index: i,
|
|
isHidden: isHidden,
|
|
isHiddenChecked: true,
|
|
isLegacyDerivation: isLegacyDerivation,
|
|
type: addrType,
|
|
network: network,
|
|
accountIndex: accountIndex,
|
|
);
|
|
list.add(address);
|
|
} on UnsupportedAddressTypeForAccountException catch (e) {
|
|
printV("_createNewAddresses: skipping index $i, type $addrType, "
|
|
"account $accountIndex: $e");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
@action
|
|
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
|
final addressesSet = this._addresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this._addresses.clear();
|
|
this._addresses.addAll(addressesSet);
|
|
updateAddressesByMatch();
|
|
updateReceiveAddresses();
|
|
updateChangeAddresses();
|
|
}
|
|
|
|
@action
|
|
void addSilentAddresses(Iterable<BitcoinSilentPaymentAddressRecord> addresses) {
|
|
final addressesSet = this.silentAddresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this.silentAddresses.clear();
|
|
this.silentAddresses.addAll(addressesSet);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
@action
|
|
void addMwebAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
|
final addressesSet = this.mwebAddresses.toSet();
|
|
addressesSet.addAll(addresses);
|
|
this.mwebAddresses.clear();
|
|
this.mwebAddresses.addAll(addressesSet);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
static const _validationTimeSlice = Duration(milliseconds: 16);
|
|
|
|
Future<void> _validateAddresses() async {
|
|
final addresses = _addresses.toList();
|
|
final slice = Stopwatch()..start();
|
|
|
|
for (final element in addresses) {
|
|
try {
|
|
await _validateAddress(element);
|
|
} catch (e, s) {
|
|
Zone.current.handleUncaughtError(e, s);
|
|
}
|
|
|
|
if (slice.elapsed >= _validationTimeSlice) {
|
|
await Future<void>.delayed(Duration.zero);
|
|
slice.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _validateAddress(BitcoinAddressRecord element) async {
|
|
if (element.isHiddenChecked) return;
|
|
|
|
if (element.type == SegwitAddresType.mweb) {
|
|
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Relabel only when the address re-derives from the other chain. A record from a path these
|
|
// keys don't produce matches neither chain, so it keeps its label instead of flipping.
|
|
final otherChainHd = _hdForAddressGeneration(
|
|
isHidden: !element.isHidden,
|
|
type: element.type,
|
|
isLegacyDerivation: element.isLegacyDerivation,
|
|
accountIndex: element.accountIndex,
|
|
);
|
|
final otherChainAddress =
|
|
await getAddressAsync(index: element.index, hd: otherChainHd, addressType: element.type);
|
|
if (element.address == otherChainAddress) {
|
|
element.isHidden = !element.isHidden;
|
|
}
|
|
element.isHiddenChecked = true;
|
|
} on UnsupportedAddressTypeForAccountException catch (e) {
|
|
printV("_validateAddresses: skipping ${element.address}: $e");
|
|
}
|
|
}
|
|
|
|
@override
|
|
String get addressForBuy => super.addressForBuy;
|
|
|
|
@action
|
|
Future<void> setAddressType(BitcoinAddressType type) async {
|
|
|
|
final needsAccountScopedHd =
|
|
type is! LightningAddressType && type != SilentPaymentsAddresType.p2sp;
|
|
|
|
BitcoinAddressType resolvedType = type;
|
|
if (needsAccountScopedHd) {
|
|
final accountIndex = walletInfo.type == WalletType.bitcoin ? currentAccountIndex : 0;
|
|
if (!_isAddressTypeSupportedForAccount(type, accountIndex)) {
|
|
resolvedType = EXTRA_ACCOUNT_ADDRESS_TYPES.first;
|
|
}
|
|
}
|
|
|
|
_addressPageType = resolvedType;
|
|
updateAddressesByMatch();
|
|
walletInfo.addressPageType = addressPageType.toString();
|
|
await walletInfo.save();
|
|
}
|
|
|
|
bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
|
|
return _isAddressByType(addressRecord, addressPageType);
|
|
}
|
|
|
|
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
|
|
|
bool _isUnusedReceiveAddressByType(
|
|
BitcoinAddressRecord addressRecord,
|
|
BitcoinAddressType type,
|
|
) {
|
|
return _isCurrentAccountAddress(addressRecord) &&
|
|
!addressRecord.isHidden &&
|
|
!addressRecord.isUsed &&
|
|
addressRecord.type == type;
|
|
}
|
|
|
|
@action
|
|
void deleteSilentPaymentAddress(String address) {
|
|
final addressRecord = silentAddresses.firstWhere((addressRecord) =>
|
|
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
|
|
|
|
silentAddresses.remove(addressRecord);
|
|
updateAddressesByMatch();
|
|
}
|
|
|
|
// Remove all addresses associated with a specific account index.
|
|
@action
|
|
Future<void> removeAddressesForAccount(int accountIndex) async {
|
|
_addresses.removeWhere((addr) => addr.accountIndex == accountIndex);
|
|
updateAddressesByMatch();
|
|
updateReceiveAddresses();
|
|
updateChangeAddresses();
|
|
await updateAddressesInBox();
|
|
}
|
|
|
|
|
|
bool _isAddressTypeSupportedForAccount(BitcoinAddressType type, int accountIndex) {
|
|
final effectiveAccountIndex = walletInfo.type == WalletType.bitcoin ? accountIndex : 0;
|
|
return mainHdByTypeAndAccount[effectiveAccountIndex]?.containsKey(type) ?? false;
|
|
}
|
|
|
|
static BitcoinAddressType _resolveInitialAddressPageType({
|
|
required BitcoinAddressType? initialAddressPageType,
|
|
required WalletInfo walletInfo,
|
|
required Map<int, Map<BitcoinAddressType, Bip32Slip10Secp256k1>> mainHdByTypeAndAccount,
|
|
required int currentAccountIndex,
|
|
}) {
|
|
final resolved = initialAddressPageType ??
|
|
(walletInfo.addressPageType != null
|
|
? walletInfo.addressPageType == LightningAddressType.p2l.value
|
|
? LightningAddressType.p2l
|
|
: BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
|
: SegwitAddresType.p2wpkh);
|
|
|
|
// Non-account-scoped address types (Lightning, Silent Payments) don't need
|
|
// an HD map entry.
|
|
if (resolved is LightningAddressType || resolved == SilentPaymentsAddresType.p2sp) {
|
|
return resolved;
|
|
}
|
|
|
|
final effectiveAccountIndex = walletInfo.type == WalletType.bitcoin ? currentAccountIndex : 0;
|
|
|
|
final isValid = mainHdByTypeAndAccount[effectiveAccountIndex]?.containsKey(resolved) ?? false;
|
|
|
|
if (!isValid) {
|
|
return EXTRA_ACCOUNT_ADDRESS_TYPES.first;
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
Bip32Slip10Secp256k1 _hdForAddressGeneration({
|
|
required bool isHidden,
|
|
required BitcoinAddressType type,
|
|
required bool isLegacyDerivation,
|
|
int accountIndex = 0,
|
|
}) {
|
|
if (isLegacyDerivation) {
|
|
if (accountIndex != 0) {
|
|
throw UnsupportedAddressTypeForAccountException(
|
|
"Legacy derivation is only supported for the first account");
|
|
}
|
|
|
|
return isHidden ? legacySideHd : legacyMainHd;
|
|
}
|
|
|
|
final effectiveAccountIndex = walletInfo.type == WalletType.bitcoin ? accountIndex : 0;
|
|
|
|
final map = isHidden
|
|
? sideHdByTypeAndAccount[effectiveAccountIndex]
|
|
: mainHdByTypeAndAccount[effectiveAccountIndex];
|
|
|
|
if (map == null) {
|
|
throw UnsupportedAddressTypeForAccountException(
|
|
"HD map not found for account $effectiveAccountIndex");
|
|
}
|
|
|
|
final hd = map[type];
|
|
if (hd == null) {
|
|
throw UnsupportedAddressTypeForAccountException(
|
|
"HD not found for account $accountIndex type $type");
|
|
}
|
|
|
|
return hd;
|
|
}
|
|
|
|
@action
|
|
Future<void> setLightningAddress(String walletName, {String newAddress = ""}) async {
|
|
if (lightningWallet == null) return;
|
|
|
|
try {
|
|
final path = await pathForWalletDir(name: walletName, type: WalletType.bitcoin);
|
|
final initialized = await lightningWallet!.init(path);
|
|
|
|
if (!initialized) {
|
|
printV("Failed to initialize the lightning wallet");
|
|
return;
|
|
}
|
|
|
|
lightningAddress = await lightningWallet!.getAddress();
|
|
|
|
late final String username;
|
|
|
|
if (newAddress.isEmpty) {
|
|
if (lightningAddress != null) return;
|
|
|
|
final randomNumber = Random.secure().nextInt(9999);
|
|
final randomName = await generateName();
|
|
username = "${randomName.replaceAll(" ", "")}$randomNumber".toLowerCase();
|
|
} else {
|
|
username = newAddress;
|
|
}
|
|
|
|
try {
|
|
printV(username);
|
|
lightningAddress = await lightningWallet!.registerAddress(username);
|
|
} catch (e) {
|
|
printV(e);
|
|
printV(username);
|
|
rethrow;
|
|
}
|
|
} on SdkError_NetworkError catch (_) {
|
|
} on SdkError_SparkError catch (e) {
|
|
if (!e.field0.contains("dns") && !e.field0.contains("TimedOut")) rethrow;
|
|
} finally {
|
|
lightningAddress ??= lightningWallet!.cachedAddress;
|
|
}
|
|
}
|
|
}
|