mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-03 09:19:22 +00:00
xmr/wow coin control
This commit is contained in:
parent
7d60793c88
commit
29e63ca97d
8 changed files with 215 additions and 38 deletions
|
@ -1 +1 @@
|
|||
Subproject commit fd9892cfa3ab85d50354f334a18a537b4af7d00e
|
||||
Subproject commit 9ae2f4d1a1826ac83ef82def915e5dba0350f705
|
|
@ -8,6 +8,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
@ -84,6 +85,20 @@ class UTXO {
|
|||
return confirmations >= minimumConfirms;
|
||||
}
|
||||
|
||||
@ignore
|
||||
String? get keyImage {
|
||||
if (otherData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final map = jsonDecode(otherData!) as Map;
|
||||
return map["keyImage"] as String;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
UTXO copyWith({
|
||||
Id? id,
|
||||
String? walletId,
|
||||
|
|
|
@ -13,9 +13,9 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../db/isar/main_db.dart';
|
||||
import '../../models/isar/models/isar_models.dart';
|
||||
import '../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import '../../providers/global/wallets_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/amount/amount.dart';
|
||||
|
@ -33,6 +33,7 @@ import '../../widgets/desktop/desktop_dialog_close_button.dart';
|
|||
import '../../widgets/desktop/secondary_button.dart';
|
||||
import '../../widgets/icon_widgets/utxo_status_icon.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
|
||||
class UtxoDetailsView extends ConsumerStatefulWidget {
|
||||
const UtxoDetailsView({
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:cw_core/node.dart';
|
|||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utxo.dart' as cw;
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -26,6 +27,7 @@ import 'package:tuple/tuple.dart';
|
|||
import '../../../db/hive/db.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
import '../../../models/isar/models/blockchain_data/utxo.dart';
|
||||
import '../../../models/keys/cw_key_data.dart';
|
||||
import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import '../../../services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
|
@ -72,6 +74,26 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
await updateNode();
|
||||
},
|
||||
);
|
||||
|
||||
// Potentially dangerous hack. See comments in _startInit()
|
||||
_startInit();
|
||||
}
|
||||
|
||||
// cw based wallet listener to handle synchronization of utxo frozen states
|
||||
late final StreamSubscription<List<UTXO>> _streamSub;
|
||||
Future<void> _startInit() async {
|
||||
// Delay required as `mainDB` is not initialized in constructor.
|
||||
// This is a hack and could lead to a race condition.
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
_streamSub = mainDB.isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.watch(fireImmediately: true)
|
||||
.listen((utxos) async {
|
||||
await onUTXOsCHanged(utxos);
|
||||
await updateBalance(shouldUpdateUtxos: false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -614,8 +636,31 @@ class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
priority: feePriority,
|
||||
);
|
||||
|
||||
final height = await chainHeight;
|
||||
final inputs = txData.utxos
|
||||
?.map(
|
||||
(e) => cw.UTXO(
|
||||
address: e.address!,
|
||||
hash: e.txid,
|
||||
keyImage: e.keyImage!,
|
||||
value: e.value,
|
||||
isFrozen: e.isBlocked,
|
||||
isUnlocked: e.blockHeight != null &&
|
||||
(height - (e.blockHeight ?? 0)) >=
|
||||
cryptoCurrency.minConfirms,
|
||||
height: e.blockHeight ?? 0,
|
||||
vout: e.vout,
|
||||
spent: e.used ?? false,
|
||||
coinbase: e.isCoinbase,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
await prepareSendMutex.protect(() async {
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(tmp);
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(
|
||||
tmp,
|
||||
inputs: inputs,
|
||||
);
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:cw_core/node.dart';
|
|||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utxo.dart' as cw;
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -28,6 +29,7 @@ import 'package:tuple/tuple.dart';
|
|||
import '../../../db/hive/db.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
import '../../../models/isar/models/blockchain_data/utxo.dart';
|
||||
import '../../../models/keys/cw_key_data.dart';
|
||||
import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import '../../../services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
|
@ -74,6 +76,26 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
await updateNode();
|
||||
},
|
||||
);
|
||||
|
||||
// Potentially dangerous hack. See comments in _startInit()
|
||||
_startInit();
|
||||
}
|
||||
|
||||
// cw based wallet listener to handle synchronization of utxo frozen states
|
||||
late final StreamSubscription<List<UTXO>> _streamSub;
|
||||
Future<void> _startInit() async {
|
||||
// Delay required as `mainDB` is not initialized in constructor.
|
||||
// This is a hack and could lead to a race condition.
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
_streamSub = mainDB.isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.watch(fireImmediately: true)
|
||||
.listen((utxos) async {
|
||||
await onUTXOsCHanged(utxos);
|
||||
await updateBalance(shouldUpdateUtxos: false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -660,8 +682,31 @@ class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
|||
priority: feePriority,
|
||||
);
|
||||
|
||||
final height = await chainHeight;
|
||||
final inputs = txData.utxos
|
||||
?.map(
|
||||
(e) => cw.UTXO(
|
||||
address: e.address!,
|
||||
hash: e.txid,
|
||||
keyImage: e.keyImage!,
|
||||
value: e.value,
|
||||
isFrozen: e.isBlocked,
|
||||
isUnlocked: e.blockHeight != null &&
|
||||
(height - (e.blockHeight ?? 0)) >=
|
||||
cryptoCurrency.minConfirms,
|
||||
height: e.blockHeight ?? 0,
|
||||
vout: e.vout,
|
||||
spent: e.used ?? false,
|
||||
coinbase: e.isCoinbase,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
await prepareSendMutex.protect(() async {
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(tmp);
|
||||
awaitPendingTransaction = cwWalletBase!.createTransaction(
|
||||
tmp,
|
||||
inputs: inputs,
|
||||
);
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
|
|
@ -1,37 +1,9 @@
|
|||
import 'dart:async';
|
||||
|
||||
import '../../crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../wallet.dart';
|
||||
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
|
||||
abstract class CryptonoteWallet<T extends CryptonoteCurrency> extends Wallet<T>
|
||||
with MnemonicInterface<T> {
|
||||
with MnemonicInterface<T>, CoinControlInterface<T> {
|
||||
CryptonoteWallet(super.currency);
|
||||
|
||||
// ========== 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();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateUTXOs() async {
|
||||
// do nothing for now
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||
import '../intermediate/bip39_hd_wallet.dart';
|
||||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../wallet.dart';
|
||||
|
||||
mixin CoinControlInterface<T extends Bip39HDCurrency> on Bip39HDWallet<T> {
|
||||
mixin CoinControlInterface<T extends CryptoCurrency> on Wallet<T> {
|
||||
// any required here?
|
||||
// currently only used to id which wallets support coin control
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utxo.dart' as cw;
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -14,6 +16,8 @@ import 'package:mutex/mutex.dart';
|
|||
|
||||
import '../../../models/balance.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
import '../../../models/isar/models/blockchain_data/utxo.dart';
|
||||
import '../../../models/keys/cw_key_data.dart';
|
||||
import '../../../models/paymint/fee_object_model.dart';
|
||||
import '../../../services/event_bus/events/global/blocks_remaining_event.dart';
|
||||
|
@ -76,6 +80,44 @@ mixin CwBasedInterface<T extends CryptonoteCurrency, U extends WalletBase,
|
|||
_refreshTxDataHelper();
|
||||
}
|
||||
|
||||
final _utxosUpdateLock = Mutex();
|
||||
Future<void> onUTXOsCHanged(List<UTXO> utxos) async {
|
||||
await _utxosUpdateLock.protect(() async {
|
||||
final cwUtxos = cwWalletBase?.utxos ?? [];
|
||||
|
||||
bool changed = false;
|
||||
|
||||
for (final cw in cwUtxos) {
|
||||
final match = utxos.where(
|
||||
(e) =>
|
||||
e.keyImage != null &&
|
||||
e.keyImage!.isNotEmpty &&
|
||||
e.keyImage == cw.keyImage,
|
||||
);
|
||||
|
||||
if (match.isNotEmpty) {
|
||||
final u = match.first;
|
||||
|
||||
if (u.isBlocked) {
|
||||
if (!cw.isFrozen) {
|
||||
await cwWalletBase?.freeze(cw.keyImage);
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (cw.isFrozen) {
|
||||
await cwWalletBase?.thaw(cw.keyImage);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
await cwWalletBase?.updateUTXOs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void onNewTransaction() {
|
||||
// TODO: [prio=low] get rid of UpdatedInBackgroundEvent and move to
|
||||
// adding the v2 tx to the db which would update ui automagically since the
|
||||
|
@ -246,7 +288,64 @@ mixin CwBasedInterface<T extends CryptonoteCurrency, U extends WalletBase,
|
|||
FilterOperation? get receivingAddressFilterOperation => null;
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
Future<bool> updateUTXOs() async {
|
||||
await cwWalletBase?.updateUTXOs();
|
||||
|
||||
final List<UTXO> outputArray = [];
|
||||
for (final rawUTXO in (cwWalletBase?.utxos ?? <cw.UTXO>[])) {
|
||||
if (!rawUTXO.spent) {
|
||||
final current = await mainDB.isar.utxos
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.voutEqualTo(rawUTXO.vout)
|
||||
.and()
|
||||
.txidEqualTo(rawUTXO.hash)
|
||||
.findFirst();
|
||||
final tx = await mainDB.isar.transactions
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(rawUTXO.hash)
|
||||
.findFirst();
|
||||
|
||||
final otherDataMap = {
|
||||
"keyImage": rawUTXO.keyImage,
|
||||
"spent": rawUTXO.spent,
|
||||
};
|
||||
|
||||
final utxo = UTXO(
|
||||
address: rawUTXO.address,
|
||||
walletId: walletId,
|
||||
txid: rawUTXO.hash,
|
||||
vout: rawUTXO.vout,
|
||||
value: rawUTXO.value,
|
||||
name: current?.name ?? "",
|
||||
isBlocked: current?.isBlocked ?? rawUTXO.isFrozen,
|
||||
blockedReason: current?.blockedReason ?? "",
|
||||
isCoinbase: rawUTXO.coinbase,
|
||||
blockHash: "",
|
||||
blockHeight:
|
||||
tx?.height ?? (rawUTXO.height > 0 ? rawUTXO.height : null),
|
||||
blockTime: tx?.timestamp,
|
||||
otherData: jsonEncode(otherDataMap),
|
||||
);
|
||||
|
||||
outputArray.add(utxo);
|
||||
}
|
||||
}
|
||||
|
||||
await mainDB.updateUTXOs(walletId, outputArray);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance({bool shouldUpdateUtxos = true}) async {
|
||||
if (shouldUpdateUtxos) {
|
||||
await updateUTXOs();
|
||||
}
|
||||
|
||||
final total = await totalBalance;
|
||||
final available = await availableBalance;
|
||||
|
||||
|
|
Loading…
Reference in a new issue