add change address checks and handle dynamic querying of addresses depending on wallet/coin

This commit is contained in:
julian 2023-11-07 10:25:04 -06:00
parent 12a8b6aea8
commit dc9583a5fe
10 changed files with 194 additions and 63 deletions

View file

@ -10,10 +10,18 @@ import 'package:tuple/tuple.dart';
class BitcoinWallet extends Bip39HDWallet with ElectrumXMixin {
@override
int get isarTransactionVersion => 2;
int get isarTransactionVersion => 2; // TODO actually do this
BitcoinWallet(Bitcoin cryptoCurrency) : super(cryptoCurrency);
@override
FilterOperation? get changeAddressFilterOperation =>
FilterGroup.and(standardChangeAddressFilters);
@override
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
// ===========================================================================
@override

View file

@ -23,6 +23,32 @@ class BitcoincashWallet extends Bip39HDWallet with ElectrumXMixin {
BitcoincashWallet(Bitcoincash cryptoCurrency) : super(cryptoCurrency);
@override
FilterOperation? get changeAddressFilterOperation => FilterGroup.and(
[
...standardChangeAddressFilters,
FilterGroup.not(
const FilterCondition.startsWith(
property: "derivationPath",
value: "m/44'/0'",
),
),
],
);
@override
FilterOperation? get receivingAddressFilterOperation => FilterGroup.and(
[
...standardReceivingAddressFilters,
FilterGroup.not(
const FilterCondition.startsWith(
property: "derivationPath",
value: "m/44'/0'",
),
),
],
);
// ===========================================================================
@override

View file

@ -11,6 +11,14 @@ import 'package:tuple/tuple.dart';
class DogecoinWallet extends Bip39HDWallet with ElectrumXMixin {
DogecoinWallet(CryptoCurrencyNetwork network) : super(Dogecoin(network));
@override
FilterOperation? get changeAddressFilterOperation =>
FilterGroup.and(standardChangeAddressFilters);
@override
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
// ===========================================================================
@override

View file

@ -1,3 +1,4 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -10,6 +11,14 @@ import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart';
class EpiccashWallet extends Bip39Wallet {
EpiccashWallet(Epiccash cryptoCurrency) : super(cryptoCurrency);
@override
FilterOperation? get changeAddressFilterOperation =>
FilterGroup.and(standardChangeAddressFilters);
@override
FilterOperation? get receivingAddressFilterOperation =>
FilterGroup.and(standardReceivingAddressFilters);
@override
Future<TxData> confirmSend({required TxData txData}) {
// TODO: implement confirmSend

View file

@ -47,6 +47,12 @@ import 'package:tuple/tuple.dart';
class WowneroWallet extends CryptonoteWallet with MultiAddress {
WowneroWallet(Wownero wownero) : super(wownero);
@override
FilterOperation? get changeAddressFilterOperation => null;
@override
FilterOperation? get receivingAddressFilterOperation => null;
final prepareSendMutex = Mutex();
final estimateFeeMutex = Mutex();
@ -946,6 +952,16 @@ class WowneroWallet extends CryptonoteWallet with MultiAddress {
await _pathForWalletDir(name: name, type: type)
.then((path) => '$path/$name');
@override
Future<void> checkChangeAddressForTransactions() async {
// do nothing
}
@override
Future<void> generateNewChangeAddress() async {
// do nothing
}
// TODO: [prio=med/low] is this required?
// bool _isActive = false;
// @override

View file

@ -14,7 +14,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
with MultiAddress<T> {
Bip39HDWallet(T cryptoCurrency) : super(cryptoCurrency);
/// Generates a receiving address of [info.mainAddressType]. If none
/// Generates a receiving address. If none
/// are in the current wallet db it will generate at index 0, otherwise the
/// highest index found in the current wallet db.
@override
@ -23,30 +23,10 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
final index = current?.derivationIndex ?? 0;
const chain = 0; // receiving address
final DerivePathType derivePathType;
switch (info.mainAddressType) {
case AddressType.p2pkh:
derivePathType = DerivePathType.bip44;
break;
case AddressType.p2sh:
derivePathType = DerivePathType.bip49;
break;
case AddressType.p2wpkh:
derivePathType = DerivePathType.bip84;
break;
default:
throw Exception(
"Invalid AddressType accessed in $runtimeType generateNewReceivingAddress()",
);
}
final address = await _generateAddress(
chain: chain,
index: index,
derivePathType: derivePathType,
derivePathType: DerivePathTypeExt.primaryFor(info.coin),
);
await mainDB.putAddress(address);
@ -56,6 +36,26 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
);
}
/// Generates a change address. If none
/// are in the current wallet db it will generate at index 0, otherwise the
/// highest index found in the current wallet db.
@override
Future<void> generateNewChangeAddress() async {
await mainDB.isar.writeTxn(() async {
final current = await getCurrentChangeAddress();
final index = current?.derivationIndex ?? 0;
const chain = 1; // change address
final address = await _generateAddress(
chain: chain,
index: index,
derivePathType: DerivePathTypeExt.primaryFor(info.coin),
);
await mainDB.isar.addresses.put(address);
});
}
// ========== Private ========================================================
Future<coinlib.HDPrivateKey> _generateRootHDNode() async {

View file

@ -1,3 +1,5 @@
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.dart';
import 'package:stackwallet/wallets/wallet/mixins/mnemonic_based_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet.dart';
@ -6,25 +8,29 @@ abstract class Bip39Wallet<T extends Bip39Currency> extends Wallet<T>
with MnemonicBasedWallet {
Bip39Wallet(T currency) : super(currency);
List<FilterOperation> get standardReceivingAddressFilters => [
FilterCondition.equalTo(
property: "type",
value: [info.mainAddressType],
),
const FilterCondition.equalTo(
property: "subType",
value: [AddressSubType.receiving],
),
];
List<FilterOperation> get standardChangeAddressFilters => [
FilterCondition.equalTo(
property: "type",
value: [info.mainAddressType],
),
const FilterCondition.equalTo(
property: "subType",
value: [AddressSubType.change],
),
];
// ========== Private ========================================================
// ========== Overrides ======================================================
// @override
// Future<TxData> confirmSend({required TxData txData}) {
// // TODO: implement confirmSend
// throw UnimplementedError();
// }
//
// @override
// Future<TxData> prepareSend({required TxData txData}) {
// // TODO: implement prepareSend
// throw UnimplementedError();
// }
//
// @override
// Future<void> recover({required bool isRescan}) {
// // TODO: implement recover
// throw UnimplementedError();
// }
}

View file

@ -621,6 +621,42 @@ mixin ElectrumXMixin on Bip39HDWallet {
}
}
@override
Future<void> checkChangeAddressForTransactions() async {
try {
final currentChange = await getCurrentChangeAddress();
final bool needsGenerate;
if (currentChange == null) {
// no addresses in db yet for some reason.
// Should not happen at this point...
needsGenerate = true;
} else {
final txCount = await fetchTxCount(
addressScriptHash: currentChange.value,
);
needsGenerate = txCount > 0 || currentChange.derivationIndex < 0;
}
if (needsGenerate) {
await generateNewChangeAddress();
// TODO: get rid of this? Could cause problems (long loading/infinite loop or something)
// keep checking until address with no tx history is set as current
await checkChangeAddressForTransactions();
}
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from _checkReceivingAddressForTransactions"
"($cryptoCurrency): $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
@override
Future<void> updateUTXOs() async {
final allAddresses = await fetchAllOwnAddresses();
@ -684,7 +720,7 @@ mixin ElectrumXMixin on Bip39HDWallet {
Future<List<Address>> fetchAllOwnAddresses();
/// callback to pass to [parseUTXO] to check if the utxo should be marked
/// Certain coins need to check if the utxo should be marked
/// as blocked as well as give a reason.
({String? blockedReason, bool blocked}) checkBlockUTXO(
Map<String, dynamic> jsonUTXO,

View file

@ -4,4 +4,6 @@ import 'package:stackwallet/wallets/wallet/wallet.dart';
mixin MultiAddress<T extends CryptoCurrency> on Wallet<T> {
Future<void> generateNewReceivingAddress();
Future<void> checkReceivingAddressForTransactions();
Future<void> generateNewChangeAddress();
Future<void> checkChangeAddressForTransactions();
}

View file

@ -391,30 +391,29 @@ abstract class Wallet<T extends CryptoCurrency> {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
final fetchFuture = updateTransactions();
final utxosRefreshFuture = updateUTXOs();
// if (currentHeight != storedHeight) {
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
// await _checkCurrentReceivingAddressesForTransactions();
final fetchFuture = updateTransactions();
final utxosRefreshFuture = updateUTXOs();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
// final feeObj = _getFees();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
await utxosRefreshFuture;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
// _feeObject = Future(() => feeObj);
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
await fetchFuture;
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
if (this is MultiAddress) {
await (this as MultiAddress).checkReceivingAddressForTransactions();
}
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
if (this is MultiAddress) {
await (this as MultiAddress).checkChangeAddressForTransactions();
}
// await getAllTxsToWatch();
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
@ -485,13 +484,34 @@ abstract class Wallet<T extends CryptoCurrency> {
// ===========================================================================
Future<Address?> getCurrentReceivingAddress() async =>
await mainDB.isar.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(info.mainAddressType)
.subTypeEqualTo(AddressSubType.receiving)
.sortByDerivationIndexDesc()
FilterOperation? get receivingAddressFilterOperation;
FilterOperation? get changeAddressFilterOperation;
Future<Address?> getCurrentReceivingAddress() async {
return await _addressQuery(receivingAddressFilterOperation);
}
Future<Address?> getCurrentChangeAddress() async {
return await _addressQuery(changeAddressFilterOperation);
}
Future<Address?> _addressQuery(FilterOperation? filterOperation) async {
return await mainDB.isar.addresses
.buildQuery<Address>(
whereClauses: [
IndexWhereClause.equalTo(
indexName: "walletId",
value: [walletId],
),
],
filter: filterOperation,
sortBy: [
const SortProperty(
property: "derivationIndex",
sort: Sort.desc,
),
],
)
.findFirst();
}
}