mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Cw 1551 quick bitcoin wallet sync (#3446)
* add Wallet Accounts page and integrate UI * account model refactor * add walletInfoAccount table and API * add WalletAccountList API * add multi-account support for Electrum wallets * add Bitcoin lightning card and card style handling * refine account filtering and card selection * update current account balance on wallet changes * update configure.dar * Use named parameters for card design * Add Bitcoin account handling and refactor lookup * refactor dashboard * refactor cards UI and account balance handling * Improve Bitcoin account selection and balances * restrict unspent coin usage to current account * filter transactions by current Bitcoin account * refactor electrum wallet * refactor address generation * add accountIndex to address fetch logic * Update configure.dart * reload transactions on Bitcoin account change * restore multiple BTC accounts and improve fetch * show account balance with in btc * track account index in Bitcoin transactions * discover btc BIP39 accounts during restore * fix account switching and card ordering bugs * fix card customizer * limit account name length in modal * update BTC account list balance display * auto-reformat * formating fix * skip HD map validation * fix 0 balance issue * minor fix * quick bitcoin wallet sync * restore shuffle change output * restore address lookup fix * Update electrum.dart * remove account customizer * Merge branch 'CW-1142-Add-accounts-feature-to-BTC' into CW-1551-Quick-Bitcoin-Wallet-Sync * probe only segwit in account discovery * add account discovery limit and tracking * Improve Electrum sync account handling [skip ci] * remove legacy monero account and address list UI * rework BTC account balance refres * avoid unnecessary BTC account reloads * refactor account header in addresses page * fix blank popup when editing an account * fix: dedupe UTXO balance * minor fix * add Bitcoin current account API * Apply batched suggestions from code review [skip ci] Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * address PR review comments [skip ci] * remove account customizer and fix accounts init * restrict extra BTC accounts to SegWit * add BTC multi-account toggle * Fix BTC sync gating and account balance update * minor fix * Update electrum_wallet.dart * store selected account on WalletInfo * Refine wallet account reset visibility * refactor BTC address prep and move accounts page * refactor account stack and customizer routing * migrate legacy Lightning card styles * unify dashboard account change state * fix account routing and Electrum balances * fix account setup and wallet cards UI * disable Bitcoin Accounts for all hardware * merge conflicts fix * minor fixes * update cards_view [skip ci] * reverted cards view update [skip ci] * fix asset name * moved the transaction filtering into the electrum wallet * localization * disable accounts for watch-only wallets * fix: stop card render from switching the wallet account * fix: restore Lightning transactions in tx history * minor fix * fix: skip duplicate legacy address generation [skip ci] * fix: correct account balance/design mismatch bugs * addressing review comments --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
co-authored by
Omar Hatem
Robert Malikowski
parent
0503d54290
commit
d7ebf42814
@@ -9,6 +9,7 @@ abstract class BaseBitcoinAddressRecord {
|
||||
BaseBitcoinAddressRecord(
|
||||
this.address, {
|
||||
required this.index,
|
||||
this.accountIndex = 0,
|
||||
this.isHidden = false,
|
||||
this.isLegacyDerivation = false,
|
||||
int txCount = 0,
|
||||
@@ -29,6 +30,7 @@ abstract class BaseBitcoinAddressRecord {
|
||||
bool isHidden;
|
||||
bool isLegacyDerivation;
|
||||
final int index;
|
||||
final int accountIndex;
|
||||
int _txCount;
|
||||
int _balance;
|
||||
String _name;
|
||||
@@ -63,6 +65,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||
BitcoinAddressRecord(
|
||||
super.address, {
|
||||
required super.index,
|
||||
super.accountIndex = 0,
|
||||
super.isHidden = false,
|
||||
super.isLegacyDerivation = false,
|
||||
super.txCount = 0,
|
||||
@@ -105,6 +108,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||
return BitcoinAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
accountIndex: decoded['accountIndex'] as int? ?? 0,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isLegacyDerivation: parsedIsLegacy,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
@@ -162,7 +166,8 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||
|
||||
final coinType = _coinTypeForNetwork();
|
||||
final purpose = _purposeForType(type);
|
||||
final accountPath = isLegacyDerivation ? electrum_path : "m/$purpose'/$coinType'/0'";
|
||||
final accountPath =
|
||||
isLegacyDerivation ? electrum_path : "m/$purpose'/$coinType'/$accountIndex'";
|
||||
|
||||
final chain = isHidden ? 1 : 0;
|
||||
return "$accountPath/$chain/$index";
|
||||
@@ -172,6 +177,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
'accountIndex': accountIndex,
|
||||
'isHidden': isHidden,
|
||||
'isLegacyDerivation': isLegacyDerivation,
|
||||
'isUsed': isUsed,
|
||||
@@ -187,6 +193,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||
BitcoinSilentPaymentAddressRecord(
|
||||
super.address, {
|
||||
required super.index,
|
||||
super.accountIndex = 0,
|
||||
super.isHidden = false,
|
||||
super.txCount = 0,
|
||||
super.balance = 0,
|
||||
@@ -205,6 +212,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||
return BitcoinSilentPaymentAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
accountIndex: decoded['accountIndex'] as int? ?? 0,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
txCount: decoded['txCount'] as int? ?? 0,
|
||||
@@ -233,6 +241,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
'accountIndex': accountIndex,
|
||||
'isHidden': isHidden,
|
||||
'isUsed': isUsed,
|
||||
'txCount': txCount,
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/locktime.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
|
||||
@@ -128,8 +129,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
mainHdByType: mainHdByType,
|
||||
sideHdByType: sideHdByType,
|
||||
mainHdByTypeAndAccount: mainHdByTypeAndAccount,
|
||||
sideHdByTypeAndAccount: sideHdByTypeAndAccount,
|
||||
accountIndexes: [currentAccountIndex],
|
||||
currentAccountIndex: currentAccountIndex,
|
||||
legacyMainHd: mainHd,
|
||||
legacySideHd: sideHd,
|
||||
network: networkParam ?? network,
|
||||
@@ -612,10 +615,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
return tx;
|
||||
}
|
||||
|
||||
List<UtxoWithPrivateKey> getUtxoWithPrivateKeys({bool confirmedOnly = false}) => unspentCoins
|
||||
.where((e) => e.isSending && !e.isFrozen && (!confirmedOnly || (e.confirmations ?? 0) > 0))
|
||||
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
||||
.toList();
|
||||
List<UtxoWithPrivateKey> getUtxoWithPrivateKeys({bool confirmedOnly = false}) =>
|
||||
unspentCoinsForCurrentAccount
|
||||
.where(
|
||||
(e) => e.isSending && !e.isFrozen && (!confirmedOnly || (e.confirmations ?? 0) > 0))
|
||||
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
||||
.toList();
|
||||
|
||||
Future<void> commitPsbt(String finalizedPsbt) {
|
||||
final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt));
|
||||
@@ -625,6 +630,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
return PendingBitcoinTransaction(
|
||||
btcTx,
|
||||
type,
|
||||
accountIndex: currentAccountIndex,
|
||||
electrumClient: electrumClient,
|
||||
amount: Money.zero(currency),
|
||||
fee: Money.zero(currency),
|
||||
@@ -683,6 +689,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
return PendingBitcoinTransaction(
|
||||
btcTx,
|
||||
type,
|
||||
accountIndex: currentAccountIndex,
|
||||
electrumClient: electrumClient,
|
||||
amount: Money.zero(currency),
|
||||
fee: Money.zero(currency),
|
||||
@@ -716,12 +723,24 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
|
||||
@override
|
||||
bool receiveOptionAvailable(ReceivePageOption option) {
|
||||
if(option == BitcoinReceivePageOption.lightning) {
|
||||
return hasLightningSupport;
|
||||
final isExtraAccount = currentAccountIndex > 0;
|
||||
|
||||
if (option == BitcoinReceivePageOption.lightning) {
|
||||
// Lightning is tied to the primary account only
|
||||
return !isExtraAccount && hasLightningSupport;
|
||||
}
|
||||
|
||||
if(option == BitcoinReceivePageOption.silent_payments) {
|
||||
return hasSilentPaymentsScanning;
|
||||
if (option == BitcoinReceivePageOption.silent_payments) {
|
||||
// Silent payments are tied to the primary account only
|
||||
return !isExtraAccount && hasSilentPaymentsScanning;
|
||||
}
|
||||
|
||||
// Restrict extra Bitcoin accounts (accountIndex > 0) to the allowed address types
|
||||
if (isExtraAccount && option is BitcoinReceivePageOption) {
|
||||
final addressType = option.toType();
|
||||
if (!EXTRA_ACCOUNT_ADDRESS_TYPES.contains(addressType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -23,8 +23,10 @@ class BitcoinWalletAddresses = BitcoinWalletAddressesBase with _$BitcoinWalletAd
|
||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||
BitcoinWalletAddressesBase(
|
||||
WalletInfo walletInfo, {
|
||||
required super.mainHdByType,
|
||||
required super.sideHdByType,
|
||||
required super.mainHdByTypeAndAccount,
|
||||
required super.sideHdByTypeAndAccount,
|
||||
required super.accountIndexes,
|
||||
required super.currentAccountIndex,
|
||||
required super.legacySideHd,
|
||||
required super.legacyMainHd,
|
||||
required super.network,
|
||||
@@ -145,7 +147,11 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||
try {
|
||||
return super
|
||||
.allAddresses
|
||||
.firstWhere((e) => e.type == SegwitAddresType.p2wpkh && !e.isHidden && !e.isUsed)
|
||||
.firstWhere((e) =>
|
||||
e.accountIndex == currentAccountIndex &&
|
||||
e.type == SegwitAddresType.p2wpkh &&
|
||||
!e.isHidden &&
|
||||
!e.isUsed)
|
||||
.address;
|
||||
} catch (_) {
|
||||
return super.addressForBuy;
|
||||
|
||||
@@ -2,9 +2,17 @@ import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import 'package:cw_core/currency.dart';
|
||||
|
||||
class ElectrumBalance extends Balance {
|
||||
|
||||
factory ElectrumBalance.zero(CryptoCurrency currency) => ElectrumBalance(
|
||||
confirmed: Money.zero(currency),
|
||||
unconfirmed: Money.zero(currency),
|
||||
frozen: Money.zero(currency),
|
||||
);
|
||||
|
||||
ElectrumBalance({
|
||||
required this.confirmed,
|
||||
required this.unconfirmed,
|
||||
@@ -49,4 +57,26 @@ class ElectrumBalance extends Balance {
|
||||
'secondConfirmed': secondAvailable?.amount.toInt() ?? 0,
|
||||
'secondUnconfirmed': secondUnavailable?.amount.toInt() ?? 0,
|
||||
});
|
||||
|
||||
// Value equality so instances can be compared/deduped (e.g. by MobX reactions via MapEquality)
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ElectrumBalance &&
|
||||
other.confirmed == confirmed &&
|
||||
other.unconfirmed == unconfirmed &&
|
||||
other.frozen == frozen &&
|
||||
other.secondConfirmed == secondConfirmed &&
|
||||
other.secondUnconfirmed == secondUnconfirmed);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
confirmed,
|
||||
unconfirmed,
|
||||
frozen,
|
||||
secondConfirmed,
|
||||
secondUnconfirmed,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ class ElectrumTransactionBundle {
|
||||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
int? accountIndex;
|
||||
List<BitcoinSilentPaymentsUnspent>? unspents;
|
||||
bool isReceivedSilentPayment;
|
||||
bool isHogEx;
|
||||
@@ -41,6 +42,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||
bool isReplaced = false,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
this.accountIndex,
|
||||
String? to,
|
||||
this.unspents,
|
||||
this.isReceivedSilentPayment = false,
|
||||
@@ -231,6 +233,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||
isPending: data['isPending'] as bool,
|
||||
isReplaced: data['isReplaced'] as bool? ?? false,
|
||||
confirmations: data['confirmations'] as int,
|
||||
accountIndex: data['accountIndex'] as int?,
|
||||
inputAddresses:
|
||||
inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(),
|
||||
outputAddresses:
|
||||
@@ -260,6 +263,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||
inputAddresses: inputAddresses,
|
||||
outputAddresses: outputAddresses,
|
||||
confirmations: info.confirmations,
|
||||
accountIndex: accountIndex,
|
||||
additionalInfo: additionalInfo);
|
||||
}
|
||||
|
||||
@@ -273,6 +277,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||
m['isPending'] = isPending;
|
||||
m['isReplaced'] = isReplaced;
|
||||
m['confirmations'] = confirmations;
|
||||
m['accountIndex'] = accountIndex;
|
||||
m['fee'] = fee?.amount.toInt();
|
||||
m['to'] = to;
|
||||
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
|
||||
@@ -284,6 +289,6 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
|
||||
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, accountIndex: $accountIndex, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
|
||||
}
|
||||
}
|
||||
|
||||
+941
-375
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -20,6 +21,15 @@ 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 = [
|
||||
@@ -30,6 +40,11 @@ const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
|
||||
P2shAddressType.p2wpkhInP2sh,
|
||||
];
|
||||
|
||||
const List<BitcoinAddressType> LEGACY_DUPLICATE_ADDRESS_TYPES = [
|
||||
SegwitAddresType.p2wpkh,
|
||||
SegwitAddresType.p2wsh,
|
||||
];
|
||||
|
||||
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
|
||||
SegwitAddresType.p2wpkh,
|
||||
SegwitAddresType.mweb,
|
||||
@@ -43,11 +58,15 @@ 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.mainHdByType,
|
||||
required this.sideHdByType,
|
||||
required this.mainHdByTypeAndAccount,
|
||||
required this.sideHdByTypeAndAccount,
|
||||
required this.accountIndexes,
|
||||
required this.currentAccountIndex,
|
||||
required this.legacyMainHd,
|
||||
required this.legacySideHd,
|
||||
required this.network,
|
||||
@@ -72,12 +91,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
.toSet()),
|
||||
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
||||
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
||||
_addressPageType = initialAddressPageType ??
|
||||
(walletInfo.addressPageType != null
|
||||
? walletInfo.addressPageType == LightningAddressType.p2l.value
|
||||
? LightningAddressType.p2l
|
||||
: BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||
: SegwitAddresType.p2wpkh),
|
||||
_addressPageType = _resolveInitialAddressPageType(
|
||||
initialAddressPageType: initialAddressPageType,
|
||||
walletInfo: walletInfo,
|
||||
mainHdByTypeAndAccount: mainHdByTypeAndAccount,
|
||||
currentAccountIndex: currentAccountIndex,
|
||||
),
|
||||
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||
(initialSilentAddresses ?? []).toSet()),
|
||||
currentSilentAddressIndex = initialSilentAddressIndex,
|
||||
@@ -142,8 +161,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
// 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;
|
||||
Map<BitcoinAddressType, Bip32Slip10Secp256k1> mainHdByType;
|
||||
Map<BitcoinAddressType, Bip32Slip10Secp256k1> sideHdByType;
|
||||
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;
|
||||
@@ -199,8 +222,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
"Error: Unable to fetch your Lightning address, please check your network connection.";
|
||||
}
|
||||
|
||||
final typeMatchingAddressesAll =
|
||||
_addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList();
|
||||
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.
|
||||
@@ -238,6 +269,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
|
||||
final prev = previousAddressRecord;
|
||||
if (prev != null &&
|
||||
_isCurrentAccountAddress(prev) &&
|
||||
prev.type == addressPageType &&
|
||||
!prev.isUsed &&
|
||||
!prev.isLegacyDerivation &&
|
||||
@@ -303,8 +335,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
return silentAddress?.toString() ?? '';
|
||||
}
|
||||
|
||||
final mainHd = mainHdByType[addressPageType] ?? mainHdByType.values.first;
|
||||
return getAddress(index: 0, hd: mainHd, addressType: addressPageType);
|
||||
return _firstAddressForType(addressPageType);
|
||||
}
|
||||
|
||||
String get payjoinCompatibleAddress {
|
||||
@@ -313,7 +344,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
? SegwitAddresType.p2wpkh
|
||||
: addressPageType;
|
||||
|
||||
final mainHd = mainHdByType[addrType] ?? mainHdByType.values.first;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -355,7 +397,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
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) {
|
||||
@@ -366,21 +412,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
} else if (walletInfo.type == WalletType.dogecoin) {
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
} else if (walletInfo.type == WalletType.bitcoin) {
|
||||
await _generateInitialAddresses(isLegacyDerivation: true);
|
||||
await _generateInitialAddresses();
|
||||
if (!isHardwareWallet) {
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh, isLegacyDerivation: true);
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
for (final accountIndex in effectiveAccountIndexes) {
|
||||
|
||||
await _generateInitialAddresses(
|
||||
type: P2shAddressType.p2wpkhInP2sh, isLegacyDerivation: true);
|
||||
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2tr, isLegacyDerivation: true);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh, isLegacyDerivation: true);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||
await prepareAccountAddresses(
|
||||
accountIndex,
|
||||
types: accountIndex == 0 ? BITCOIN_ADDRESS_TYPES : EXTRA_ACCOUNT_ADDRESS_TYPES,
|
||||
includeLegacy: accountIndex == 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,9 +445,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
updateChangeAddresses();
|
||||
|
||||
if (changeAddresses.isEmpty) {
|
||||
final newAddresses = await _createNewAddresses(gap,
|
||||
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
|
||||
isHidden: true);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -423,6 +473,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
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>{};
|
||||
@@ -440,6 +495,29 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
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) {
|
||||
@@ -471,13 +549,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
return address;
|
||||
}
|
||||
|
||||
final newAddressIndex = addressesByReceiveType.fold(
|
||||
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
|
||||
final accountIndex = walletInfo.type == WalletType.bitcoin ? currentAccountIndex : 0;
|
||||
|
||||
final hd = _hdFor(isHidden: false, type: addressPageType, isLegacyDerivation: false);
|
||||
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,
|
||||
isLegacyDerivation: false,
|
||||
name: label,
|
||||
@@ -508,45 +599,54 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
void addBitcoinAddressTypes() {
|
||||
final lastP2wpkh = _addresses
|
||||
.where((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||
.toList()
|
||||
.last;
|
||||
if (lastP2wpkh.address != address) {
|
||||
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WPKH';
|
||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||
.lastOrNull;
|
||||
if (lastP2wpkh != null) {
|
||||
if (lastP2wpkh.address != address) {
|
||||
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WPKH';
|
||||
}
|
||||
}
|
||||
|
||||
final lastP2pkh = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
||||
if (lastP2pkh.address != address) {
|
||||
addressesMap[lastP2pkh.address] = 'P2PKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2PKH';
|
||||
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.firstWhere((addressRecord) =>
|
||||
final lastP2sh = _addresses.firstWhereOrNull((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
|
||||
if (lastP2sh.address != address) {
|
||||
addressesMap[lastP2sh.address] = 'P2SH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2SH';
|
||||
if (lastP2sh != null) {
|
||||
if (lastP2sh.address != address) {
|
||||
addressesMap[lastP2sh.address] = 'P2SH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2SH';
|
||||
}
|
||||
}
|
||||
|
||||
final lastP2tr = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
|
||||
if (lastP2tr.address != address) {
|
||||
addressesMap[lastP2tr.address] = 'P2TR';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2TR';
|
||||
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.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
|
||||
if (lastP2wsh.address != address) {
|
||||
addressesMap[lastP2wsh.address] = 'P2WSH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WSH';
|
||||
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;
|
||||
@@ -678,15 +778,20 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
|
||||
addressesByReceiveType.clear();
|
||||
addressesByReceiveType.addAll(
|
||||
_addresses.where(_isAddressPageTypeMatch).toList(),
|
||||
_addresses
|
||||
.where((addressRecord) =>
|
||||
_isCurrentAccountAddress(addressRecord) && _isAddressPageTypeMatch(addressRecord))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
void updateReceiveAddresses() {
|
||||
receiveAddresses.removeRange(0, receiveAddresses.length);
|
||||
final newAddresses =
|
||||
_addresses.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed);
|
||||
final newAddresses = _addresses.where((addressRecord) =>
|
||||
_isCurrentAccountAddress(addressRecord) &&
|
||||
!addressRecord.isHidden &&
|
||||
!addressRecord.isUsed);
|
||||
receiveAddresses.addAll(newAddresses);
|
||||
}
|
||||
|
||||
@@ -694,6 +799,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
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
|
||||
@@ -708,6 +814,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
Future<String?> Function(BitcoinAddressRecord) getAddressHistory, {
|
||||
BitcoinAddressType type = SegwitAddresType.p2wpkh,
|
||||
required bool isLegacyDerivation,
|
||||
int accountIndex = 0,
|
||||
}) async {
|
||||
final newAddresses = await _createNewAddresses(
|
||||
gap,
|
||||
@@ -715,6 +822,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
isHidden: isHidden,
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
type: type,
|
||||
accountIndex: accountIndex,
|
||||
);
|
||||
|
||||
addAddresses(newAddresses);
|
||||
@@ -730,6 +838,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
getAddressHistory,
|
||||
type: type,
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
accountIndex: accountIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -741,6 +850,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
Future<Set<String>> Function(List<BitcoinAddressRecord>) getUsedAddresses, {
|
||||
BitcoinAddressType type = SegwitAddresType.p2wpkh,
|
||||
required bool isLegacyDerivation,
|
||||
int accountIndex = 0,
|
||||
}) async {
|
||||
final newAddresses = await _createNewAddresses(
|
||||
gap,
|
||||
@@ -748,6 +858,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
isHidden: isHidden,
|
||||
type: type,
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
accountIndex: accountIndex,
|
||||
);
|
||||
addAddresses(newAddresses);
|
||||
|
||||
@@ -768,24 +879,26 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
getUsedAddresses,
|
||||
type: type,
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
accountIndex: accountIndex,
|
||||
);
|
||||
|
||||
return [...newAddresses, ...moreNewAddresses];
|
||||
}
|
||||
|
||||
Future<void> _generateInitialAddresses(
|
||||
{BitcoinAddressType type = SegwitAddresType.p2wpkh, bool isLegacyDerivation = false}) async {
|
||||
// Legacy derivation produces the same addresses as standard for these types.
|
||||
// Don't generate a legacy set to avoid duplicates.
|
||||
if (isLegacyDerivation && (type == SegwitAddresType.p2wpkh || type == SegwitAddresType.p2wsh)) {
|
||||
return;
|
||||
}
|
||||
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.type == type && addr.isLegacyDerivation == isLegacyDerivation) {
|
||||
if (addr.accountIndex == accountIndex &&
|
||||
addr.type == type &&
|
||||
addr.isLegacyDerivation == isLegacyDerivation) {
|
||||
if (addr.isHidden) {
|
||||
countOfHiddenAddresses += 1;
|
||||
} else {
|
||||
@@ -800,7 +913,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
startIndex: countOfReceiveAddresses,
|
||||
isHidden: false,
|
||||
type: type,
|
||||
isLegacyDerivation: isLegacyDerivation);
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
accountIndex: accountIndex);
|
||||
|
||||
addAddresses(newAddresses);
|
||||
}
|
||||
|
||||
@@ -810,31 +925,48 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
startIndex: countOfHiddenAddresses,
|
||||
isHidden: true,
|
||||
type: type,
|
||||
isLegacyDerivation: isLegacyDerivation);
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
accountIndex: accountIndex);
|
||||
addAddresses(newAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
|
||||
{int startIndex = 0,
|
||||
bool isHidden = false,
|
||||
BitcoinAddressType? type,
|
||||
bool isLegacyDerivation = false}) async {
|
||||
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;
|
||||
final hd = _hdFor(isHidden: isHidden, type: addrType, isLegacyDerivation: isLegacyDerivation);
|
||||
|
||||
final address = BitcoinAddressRecord(
|
||||
await getAddressAsync(index: i, hd: hd, addressType: addrType),
|
||||
index: i,
|
||||
isHidden: isHidden,
|
||||
isLegacyDerivation: isLegacyDerivation,
|
||||
type: addrType,
|
||||
network: network,
|
||||
);
|
||||
list.add(address);
|
||||
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,
|
||||
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;
|
||||
@@ -876,18 +1008,30 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
return;
|
||||
}
|
||||
|
||||
final mainHd = _hdFor(
|
||||
isHidden: false, type: element.type, isLegacyDerivation: element.isLegacyDerivation);
|
||||
final sideHd = _hdFor(
|
||||
isHidden: true, type: element.type, isLegacyDerivation: element.isLegacyDerivation);
|
||||
if (!element.isHidden &&
|
||||
element.address !=
|
||||
await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden &&
|
||||
element.address !=
|
||||
await getAddressAsync(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
element.isHidden = false;
|
||||
try {
|
||||
final mainHd = _hdForAddressGeneration(
|
||||
isHidden: false,
|
||||
type: element.type,
|
||||
isLegacyDerivation: element.isLegacyDerivation,
|
||||
accountIndex: element.accountIndex);
|
||||
final sideHd = _hdForAddressGeneration(
|
||||
isHidden: true,
|
||||
type: element.type,
|
||||
isLegacyDerivation: element.isLegacyDerivation,
|
||||
accountIndex: element.accountIndex);
|
||||
if (!element.isHidden &&
|
||||
element.address !=
|
||||
await getAddressAsync(
|
||||
index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden &&
|
||||
element.address !=
|
||||
await getAddressAsync(
|
||||
index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
element.isHidden = false;
|
||||
}
|
||||
} on UnsupportedAddressTypeForAccountException catch (e) {
|
||||
printV("_validateAddresses: skipping ${element.address}: $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -897,7 +1041,19 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
|
||||
@action
|
||||
Future<void> setAddressType(BitcoinAddressType type) async {
|
||||
_addressPageType = type;
|
||||
|
||||
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();
|
||||
@@ -909,8 +1065,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
|
||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||
|
||||
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
||||
bool _isUnusedReceiveAddressByType(
|
||||
BitcoinAddressRecord addressRecord,
|
||||
BitcoinAddressType type,
|
||||
) {
|
||||
return _isCurrentAccountAddress(addressRecord) &&
|
||||
!addressRecord.isHidden &&
|
||||
!addressRecord.isUsed &&
|
||||
addressRecord.type == type;
|
||||
}
|
||||
|
||||
@action
|
||||
void deleteSilentPaymentAddress(String address) {
|
||||
@@ -921,16 +1084,84 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
Bip32Slip10Secp256k1 _hdFor({
|
||||
// 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) return isHidden ? legacySideHd : legacyMainHd;
|
||||
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 map = isHidden ? sideHdByType : mainHdByType;
|
||||
final hd = map[type];
|
||||
if (hd == null) throw Exception("HD not found for type $type");
|
||||
if (hd == null) {
|
||||
throw UnsupportedAddressTypeForAccountException(
|
||||
"HD not found for account $accountIndex type $type");
|
||||
}
|
||||
|
||||
return hd;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
initialMwebAddresses: initialMwebAddresses,
|
||||
mainHdByType: mainHdByType,
|
||||
sideHdByType: sideHdByType,
|
||||
mainHdByTypeAndAccount: mainHdByTypeAndAccount,
|
||||
sideHdByTypeAndAccount: sideHdByTypeAndAccount,
|
||||
accountIndexes: [currentAccountIndex],
|
||||
currentAccountIndex: currentAccountIndex,
|
||||
legacyMainHd: mainHd,
|
||||
legacySideHd: sideHd,
|
||||
network: network,
|
||||
@@ -1365,6 +1367,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||
final tx = PendingBitcoinTransaction(
|
||||
btcTx,
|
||||
type,
|
||||
accountIndex: 0,
|
||||
electrumClient: electrumClient,
|
||||
amount: Money.zero(currency),
|
||||
fee: Money.fromInt(resp.fee.toInt(), currency),
|
||||
|
||||
@@ -25,8 +25,10 @@ class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalle
|
||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
|
||||
LitecoinWalletAddressesBase(
|
||||
WalletInfo walletInfo, {
|
||||
required super.mainHdByType,
|
||||
required super.sideHdByType,
|
||||
required super.mainHdByTypeAndAccount,
|
||||
required super.sideHdByTypeAndAccount,
|
||||
required super.accountIndexes,
|
||||
required super.currentAccountIndex,
|
||||
required super.legacyMainHd,
|
||||
required super.legacySideHd,
|
||||
required super.network,
|
||||
@@ -66,9 +68,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||
List.filled(32, 0);
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
Future<void> init({List<int> accountIndexes = const []}) async {
|
||||
if (!isHardwareWallet) await initMwebAddresses();
|
||||
await super.init();
|
||||
await super.init(accountIndexes: accountIndexes);
|
||||
}
|
||||
|
||||
@computed
|
||||
|
||||
@@ -21,6 +21,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||
PendingBitcoinTransaction(
|
||||
this._tx,
|
||||
this.type, {
|
||||
required this.accountIndex,
|
||||
required this.electrumClient,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
@@ -40,6 +41,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||
|
||||
final WalletType type;
|
||||
final BtcTransaction _tx;
|
||||
final int? accountIndex;
|
||||
final ElectrumClient electrumClient;
|
||||
final String feeRate;
|
||||
final BasedUtxoNetwork? network;
|
||||
@@ -182,6 +184,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||
isPending: true,
|
||||
isReplaced: false,
|
||||
confirmations: 0,
|
||||
accountIndex: accountIndex,
|
||||
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
|
||||
outputAddresses: outputAddresses,
|
||||
fee: fee,
|
||||
|
||||
Reference in New Issue
Block a user