mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-09 20:29:57 +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 {
|
class BitcoinWallet extends Bip39HDWallet with ElectrumXMixin {
|
||||||
@override
|
@override
|
||||||
int get isarTransactionVersion => 2;
|
int get isarTransactionVersion => 2; // TODO actually do this
|
||||||
|
|
||||||
BitcoinWallet(Bitcoin cryptoCurrency) : super(cryptoCurrency);
|
BitcoinWallet(Bitcoin cryptoCurrency) : super(cryptoCurrency);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get changeAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardChangeAddressFilters);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get receivingAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardReceivingAddressFilters);
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -23,6 +23,32 @@ class BitcoincashWallet extends Bip39HDWallet with ElectrumXMixin {
|
||||||
|
|
||||||
BitcoincashWallet(Bitcoincash cryptoCurrency) : super(cryptoCurrency);
|
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
|
@override
|
||||||
|
|
|
@ -11,6 +11,14 @@ import 'package:tuple/tuple.dart';
|
||||||
class DogecoinWallet extends Bip39HDWallet with ElectrumXMixin {
|
class DogecoinWallet extends Bip39HDWallet with ElectrumXMixin {
|
||||||
DogecoinWallet(CryptoCurrencyNetwork network) : super(Dogecoin(network));
|
DogecoinWallet(CryptoCurrencyNetwork network) : super(Dogecoin(network));
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get changeAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardChangeAddressFilters);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get receivingAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardReceivingAddressFilters);
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.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/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.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 {
|
class EpiccashWallet extends Bip39Wallet {
|
||||||
EpiccashWallet(Epiccash cryptoCurrency) : super(cryptoCurrency);
|
EpiccashWallet(Epiccash cryptoCurrency) : super(cryptoCurrency);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get changeAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardChangeAddressFilters);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get receivingAddressFilterOperation =>
|
||||||
|
FilterGroup.and(standardReceivingAddressFilters);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TxData> confirmSend({required TxData txData}) {
|
Future<TxData> confirmSend({required TxData txData}) {
|
||||||
// TODO: implement confirmSend
|
// TODO: implement confirmSend
|
||||||
|
|
|
@ -47,6 +47,12 @@ import 'package:tuple/tuple.dart';
|
||||||
class WowneroWallet extends CryptonoteWallet with MultiAddress {
|
class WowneroWallet extends CryptonoteWallet with MultiAddress {
|
||||||
WowneroWallet(Wownero wownero) : super(wownero);
|
WowneroWallet(Wownero wownero) : super(wownero);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get changeAddressFilterOperation => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FilterOperation? get receivingAddressFilterOperation => null;
|
||||||
|
|
||||||
final prepareSendMutex = Mutex();
|
final prepareSendMutex = Mutex();
|
||||||
final estimateFeeMutex = Mutex();
|
final estimateFeeMutex = Mutex();
|
||||||
|
|
||||||
|
@ -946,6 +952,16 @@ class WowneroWallet extends CryptonoteWallet with MultiAddress {
|
||||||
await _pathForWalletDir(name: name, type: type)
|
await _pathForWalletDir(name: name, type: type)
|
||||||
.then((path) => '$path/$name');
|
.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?
|
// TODO: [prio=med/low] is this required?
|
||||||
// bool _isActive = false;
|
// bool _isActive = false;
|
||||||
// @override
|
// @override
|
||||||
|
|
|
@ -14,7 +14,7 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
with MultiAddress<T> {
|
with MultiAddress<T> {
|
||||||
Bip39HDWallet(T cryptoCurrency) : super(cryptoCurrency);
|
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
|
/// are in the current wallet db it will generate at index 0, otherwise the
|
||||||
/// highest index found in the current wallet db.
|
/// highest index found in the current wallet db.
|
||||||
@override
|
@override
|
||||||
|
@ -23,30 +23,10 @@ abstract class Bip39HDWallet<T extends Bip39HDCurrency> extends Bip39Wallet<T>
|
||||||
final index = current?.derivationIndex ?? 0;
|
final index = current?.derivationIndex ?? 0;
|
||||||
const chain = 0; // receiving address
|
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(
|
final address = await _generateAddress(
|
||||||
chain: chain,
|
chain: chain,
|
||||||
index: index,
|
index: index,
|
||||||
derivePathType: derivePathType,
|
derivePathType: DerivePathTypeExt.primaryFor(info.coin),
|
||||||
);
|
);
|
||||||
|
|
||||||
await mainDB.putAddress(address);
|
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 ========================================================
|
// ========== Private ========================================================
|
||||||
|
|
||||||
Future<coinlib.HDPrivateKey> _generateRootHDNode() async {
|
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/crypto_currency/intermediate/bip39_currency.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/mixins/mnemonic_based_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/mixins/mnemonic_based_wallet.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||||
|
@ -6,25 +8,29 @@ abstract class Bip39Wallet<T extends Bip39Currency> extends Wallet<T>
|
||||||
with MnemonicBasedWallet {
|
with MnemonicBasedWallet {
|
||||||
Bip39Wallet(T currency) : super(currency);
|
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 ========================================================
|
// ========== Private ========================================================
|
||||||
|
|
||||||
// ========== Overrides ======================================================
|
// ========== 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
|
@override
|
||||||
Future<void> updateUTXOs() async {
|
Future<void> updateUTXOs() async {
|
||||||
final allAddresses = await fetchAllOwnAddresses();
|
final allAddresses = await fetchAllOwnAddresses();
|
||||||
|
@ -684,7 +720,7 @@ mixin ElectrumXMixin on Bip39HDWallet {
|
||||||
|
|
||||||
Future<List<Address>> fetchAllOwnAddresses();
|
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.
|
/// as blocked as well as give a reason.
|
||||||
({String? blockedReason, bool blocked}) checkBlockUTXO(
|
({String? blockedReason, bool blocked}) checkBlockUTXO(
|
||||||
Map<String, dynamic> jsonUTXO,
|
Map<String, dynamic> jsonUTXO,
|
||||||
|
|
|
@ -4,4 +4,6 @@ import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||||
mixin MultiAddress<T extends CryptoCurrency> on Wallet<T> {
|
mixin MultiAddress<T extends CryptoCurrency> on Wallet<T> {
|
||||||
Future<void> generateNewReceivingAddress();
|
Future<void> generateNewReceivingAddress();
|
||||||
Future<void> checkReceivingAddressForTransactions();
|
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));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
|
||||||
|
|
||||||
|
final fetchFuture = updateTransactions();
|
||||||
|
final utxosRefreshFuture = updateUTXOs();
|
||||||
// if (currentHeight != storedHeight) {
|
// if (currentHeight != storedHeight) {
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId));
|
||||||
|
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, 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;
|
await utxosRefreshFuture;
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
|
||||||
// _feeObject = Future(() => feeObj);
|
|
||||||
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId));
|
||||||
|
|
||||||
await fetchFuture;
|
await fetchFuture;
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.70, walletId));
|
||||||
|
|
||||||
if (this is MultiAddress) {
|
if (this is MultiAddress) {
|
||||||
await (this as MultiAddress).checkReceivingAddressForTransactions();
|
await (this as MultiAddress).checkReceivingAddressForTransactions();
|
||||||
}
|
}
|
||||||
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId));
|
||||||
|
|
||||||
|
if (this is MultiAddress) {
|
||||||
|
await (this as MultiAddress).checkChangeAddressForTransactions();
|
||||||
|
}
|
||||||
// await getAllTxsToWatch();
|
// await getAllTxsToWatch();
|
||||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
|
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId));
|
||||||
|
|
||||||
|
@ -485,13 +484,34 @@ abstract class Wallet<T extends CryptoCurrency> {
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
Future<Address?> getCurrentReceivingAddress() async =>
|
FilterOperation? get receivingAddressFilterOperation;
|
||||||
await mainDB.isar.addresses
|
FilterOperation? get changeAddressFilterOperation;
|
||||||
.where()
|
|
||||||
.walletIdEqualTo(walletId)
|
Future<Address?> getCurrentReceivingAddress() async {
|
||||||
.filter()
|
return await _addressQuery(receivingAddressFilterOperation);
|
||||||
.typeEqualTo(info.mainAddressType)
|
}
|
||||||
.subTypeEqualTo(AddressSubType.receiving)
|
|
||||||
.sortByDerivationIndexDesc()
|
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();
|
.findFirst();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue