mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 04:09:25 +00:00
add change address checks and handle dynamic querying of addresses depending on wallet/coin
This commit is contained in:
parent
12a8b6aea8
commit
dc9583a5fe
10 changed files with 194 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
.findFirst();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue