mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 01:37:54 +00:00
add some xmr/wow fixes and extract a shared cw base wallet interface
This commit is contained in:
parent
ee992623ed
commit
152b516947
7 changed files with 648 additions and 1087 deletions
|
@ -22,9 +22,11 @@ import 'package:stackwallet/utilities/amount/amount.dart';
|
|||
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:stackwallet/widgets/coin_card.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
|
||||
|
@ -112,8 +114,17 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
|||
),
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||
await ref.read(pWallets).getWallet(walletId).init();
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
await wallet.init();
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (mounted) {
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (mounted) {
|
||||
if (Util.isDesktop) {
|
||||
|
|
|
@ -22,7 +22,10 @@ import 'package:stackwallet/themes/stack_colors.dart';
|
|||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class WalletListItem extends ConsumerWidget {
|
||||
|
@ -60,8 +63,16 @@ class WalletListItem extends ConsumerWidget {
|
|||
.read(pWallets)
|
||||
.wallets
|
||||
.firstWhere((e) => e.info.coin == coin);
|
||||
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||
await wallet.init();
|
||||
await wallet.init();
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (context.mounted) {
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||
|
@ -18,6 +16,9 @@ import 'package:stackwallet/providers/global/wallets_provider.dart';
|
|||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart';
|
||||
|
||||
|
@ -79,16 +80,24 @@ class CoinWalletsTable extends ConsumerWidget {
|
|||
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(walletIds[i]);
|
||||
if (wallet.info.coin == Coin.monero ||
|
||||
wallet.info.coin == Coin.wownero) {
|
||||
// TODO: this can cause ui lag if awaited
|
||||
unawaited(wallet.init());
|
||||
await wallet.init();
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (context.mounted) {
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
DesktopWalletView.routeName,
|
||||
arguments: walletIds[i],
|
||||
);
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DesktopWalletView.routeName,
|
||||
arguments: walletIds[i],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
|
@ -10,174 +8,131 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:cw_monero/pending_monero_transaction.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_libmonero/core/key_service.dart';
|
||||
import 'package:flutter_libmonero/core/wallet_creation_service.dart';
|
||||
import 'package:flutter_libmonero/monero/monero.dart' as xmr_dart;
|
||||
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/monero.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/cryptonote_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
||||
class MoneroWallet extends CryptonoteWallet with CwBasedInterface {
|
||||
MoneroWallet(CryptoCurrencyNetwork network) : super(Monero(network));
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation => null;
|
||||
Address addressFor({required int index, int account = 0}) {
|
||||
String address = (cwWalletBase as MoneroWalletBase)
|
||||
.getTransactionAddress(account, index);
|
||||
|
||||
final newReceivingAddress = Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: index,
|
||||
derivationPath: null,
|
||||
value: address,
|
||||
publicKey: [],
|
||||
type: AddressType.cryptonote,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
return newReceivingAddress;
|
||||
}
|
||||
|
||||
@override
|
||||
FilterOperation? get receivingAddressFilterOperation => null;
|
||||
Future<void> exitCwWallet() async {
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewBlock = null;
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewTransaction = null;
|
||||
(cwWalletBase as MoneroWalletBase?)?.syncStatusChanged = null;
|
||||
await (cwWalletBase as MoneroWalletBase?)?.save(prioritySave: true);
|
||||
}
|
||||
|
||||
final prepareSendMutex = Mutex();
|
||||
final estimateFeeMutex = Mutex();
|
||||
@override
|
||||
Future<void> open() async {
|
||||
String? password;
|
||||
try {
|
||||
password = await cwKeysStorage.getWalletPassword(walletName: walletId);
|
||||
} catch (e, s) {
|
||||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
|
||||
bool _hasCalledExit = false;
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as MoneroWalletBase;
|
||||
|
||||
WalletService? cwWalletService;
|
||||
KeyService? cwKeysStorage;
|
||||
MoneroWalletBase? cwWalletBase;
|
||||
WalletCreationService? cwWalletCreationService;
|
||||
Timer? _autoSaveTimer;
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewBlock = onNewBlock;
|
||||
(cwWalletBase as MoneroWalletBase?)?.onNewTransaction = onNewTransaction;
|
||||
(cwWalletBase as MoneroWalletBase?)?.syncStatusChanged = syncStatusChanged;
|
||||
|
||||
bool _txRefreshLock = false;
|
||||
int _lastCheckedHeight = -1;
|
||||
int _txCount = 0;
|
||||
int _currentKnownChainHeight = 0;
|
||||
double _highestPercentCached = 0;
|
||||
await updateNode();
|
||||
|
||||
await cwWalletBase?.startSync();
|
||||
unawaited(refresh());
|
||||
autoSaveTimer?.cancel();
|
||||
autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||
if (cwWalletBase == null || cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
return Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
MoneroTransactionPriority priority;
|
||||
FeeRateType feeRateType = FeeRateType.slow;
|
||||
switch (feeRate) {
|
||||
case 1:
|
||||
priority = MoneroTransactionPriority.regular;
|
||||
feeRateType = FeeRateType.average;
|
||||
break;
|
||||
case 2:
|
||||
priority = MoneroTransactionPriority.medium;
|
||||
feeRateType = FeeRateType.average;
|
||||
break;
|
||||
case 3:
|
||||
priority = MoneroTransactionPriority.fast;
|
||||
feeRateType = FeeRateType.fast;
|
||||
break;
|
||||
case 4:
|
||||
priority = MoneroTransactionPriority.fastest;
|
||||
feeRateType = FeeRateType.fast;
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
priority = MoneroTransactionPriority.slow;
|
||||
feeRateType = FeeRateType.slow;
|
||||
break;
|
||||
}
|
||||
|
||||
dynamic approximateFee;
|
||||
int approximateFee = 0;
|
||||
await estimateFeeMutex.protect(() async {
|
||||
{
|
||||
try {
|
||||
final data = await prepareSend(
|
||||
txData: TxData(
|
||||
recipients: [
|
||||
// This address is only used for getting an approximate fee, never for sending
|
||||
(
|
||||
address:
|
||||
"WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy",
|
||||
amount: amount,
|
||||
isChange: false,
|
||||
),
|
||||
],
|
||||
feeRateType: feeRateType,
|
||||
),
|
||||
);
|
||||
approximateFee = data.fee!;
|
||||
|
||||
// unsure why this delay?
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
} catch (e) {
|
||||
approximateFee = cwWalletBase!.calculateEstimatedFee(
|
||||
priority,
|
||||
amount.raw.toInt(),
|
||||
);
|
||||
}
|
||||
}
|
||||
approximateFee = cwWalletBase!.calculateEstimatedFee(
|
||||
priority,
|
||||
amount.raw.toInt(),
|
||||
);
|
||||
});
|
||||
|
||||
if (approximateFee is Amount) {
|
||||
return approximateFee as Amount;
|
||||
} else {
|
||||
return Amount(
|
||||
rawValue: BigInt.from(approximateFee as int),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
return Amount(
|
||||
rawValue: BigInt.from(approximateFee),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FeeObject> get fees async => FeeObject(
|
||||
numberOfBlocksFast: 10,
|
||||
numberOfBlocksAverage: 15,
|
||||
numberOfBlocksSlow: 20,
|
||||
fast: MoneroTransactionPriority.fast.raw!,
|
||||
medium: MoneroTransactionPriority.regular.raw!,
|
||||
slow: MoneroTransactionPriority.slow.raw!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
return await cwWalletBase?.isConnected() ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final total = await _totalBalance;
|
||||
final available = await _availableBalance;
|
||||
|
||||
final balance = Balance(
|
||||
total: total,
|
||||
spendable: available,
|
||||
blockedTotal: Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
pendingSpendable: total - available,
|
||||
);
|
||||
|
||||
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChainHeight() async {
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: _currentKnownChainHeight,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
return await (cwWalletBase as MoneroWalletBase?)?.isConnected() ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -192,19 +147,13 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
trusted: node.trusted ?? false,
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: is this sync call needed? Do we need to notify ui here?
|
||||
// await cwWalletBase?.startSync();
|
||||
|
||||
// if (shouldRefresh) {
|
||||
// await refresh();
|
||||
// }
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
await cwWalletBase!.updateTransactions();
|
||||
final transactions = cwWalletBase?.transactionHistory!.transactions;
|
||||
await (cwWalletBase as MoneroWalletBase?)?.updateTransactions();
|
||||
final transactions =
|
||||
(cwWalletBase as MoneroWalletBase?)?.transactionHistory?.transactions;
|
||||
|
||||
// final cachedTransactions =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
||||
|
@ -241,13 +190,14 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
final List<Tuple2<Transaction, Address?>> txnsData = [];
|
||||
|
||||
if (transactions != null) {
|
||||
for (var tx in transactions.entries) {
|
||||
for (final tx in transactions.entries) {
|
||||
Address? address;
|
||||
TransactionType type;
|
||||
if (tx.value.direction == TransactionDirection.incoming) {
|
||||
final addressInfo = tx.value.additionalInfo;
|
||||
|
||||
final addressString = cwWalletBase?.getTransactionAddress(
|
||||
final addressString =
|
||||
(cwWalletBase as MoneroWalletBase?)?.getTransactionAddress(
|
||||
addressInfo!['accountIndex'] as int,
|
||||
addressInfo['addressIndex'] as int,
|
||||
);
|
||||
|
@ -300,27 +250,15 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
Future<void> init() async {
|
||||
cwWalletService = xmr_dart.monero
|
||||
.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
cwKeysStorage = KeyService(secureStorageInterface);
|
||||
|
||||
if (await cwWalletService!.isWalletExit(walletId)) {
|
||||
String? password;
|
||||
try {
|
||||
password = await cwKeysStorage!.getWalletPassword(walletName: walletId);
|
||||
} catch (e, s) {
|
||||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as MoneroWalletBase;
|
||||
|
||||
unawaited(_start());
|
||||
} else {
|
||||
if (!(await cwWalletService!.isWalletExit(walletId))) {
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
try {
|
||||
String name = walletId;
|
||||
final dirPath =
|
||||
await _pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final path = await _pathForWallet(name: name, type: WalletType.monero);
|
||||
await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
credentials = xmr_dart.monero.createMoneroNewWalletCredentials(
|
||||
name: name,
|
||||
language: "English",
|
||||
|
@ -335,7 +273,6 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
date: DateTime.now(),
|
||||
path: path,
|
||||
dirPath: dirPath,
|
||||
// TODO: find out what to put for address
|
||||
address: '',
|
||||
);
|
||||
credentials.walletInfo = walletInfo;
|
||||
|
@ -375,175 +312,17 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
await DB.instance
|
||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = wallet as MoneroWalletBase;
|
||||
unawaited(_start());
|
||||
wallet.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
cwWalletBase?.close();
|
||||
}
|
||||
await updateNode();
|
||||
await cwWalletBase?.startSync();
|
||||
|
||||
// cwWalletBase?.close();
|
||||
}
|
||||
|
||||
return super.init();
|
||||
}
|
||||
|
||||
Future<void> _start() async {
|
||||
cwWalletBase?.onNewBlock = onNewBlock;
|
||||
cwWalletBase?.onNewTransaction = onNewTransaction;
|
||||
cwWalletBase?.syncStatusChanged = syncStatusChanged;
|
||||
|
||||
if (cwWalletBase != null && !(await cwWalletBase!.isConnected())) {
|
||||
final node = getCurrentNode();
|
||||
final host = Uri.parse(node.host).host;
|
||||
await cwWalletBase?.connectToNode(
|
||||
node: Node(
|
||||
uri: "$host:${node.port}",
|
||||
type: WalletType.monero,
|
||||
trusted: node.trusted ?? false,
|
||||
),
|
||||
);
|
||||
}
|
||||
await cwWalletBase?.startSync();
|
||||
unawaited(refresh());
|
||||
_autoSaveTimer?.cancel();
|
||||
_autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
if (!_hasCalledExit) {
|
||||
_hasCalledExit = true;
|
||||
cwWalletBase?.onNewBlock = null;
|
||||
cwWalletBase?.onNewTransaction = null;
|
||||
cwWalletBase?.syncStatusChanged = null;
|
||||
_autoSaveTimer?.cancel();
|
||||
await cwWalletBase?.save(prioritySave: true);
|
||||
cwWalletBase?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewReceivingAddress() async {
|
||||
try {
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
|
||||
final newReceivingIndex =
|
||||
currentReceiving == null ? 0 : currentReceiving.derivationIndex + 1;
|
||||
|
||||
final newReceivingAddress = _addressFor(index: newReceivingIndex);
|
||||
|
||||
// Add that new receiving address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: newReceivingAddress.value,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception in generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkReceivingAddressForTransactions() async {
|
||||
try {
|
||||
int highestIndex = -1;
|
||||
for (var element
|
||||
in cwWalletBase!.transactionHistory!.transactions!.entries) {
|
||||
if (element.value.direction == TransactionDirection.incoming) {
|
||||
int curAddressIndex =
|
||||
element.value.additionalInfo!['addressIndex'] as int;
|
||||
if (curAddressIndex > highestIndex) {
|
||||
highestIndex = curAddressIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the new receiving index
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
final curIndex = currentReceiving?.derivationIndex ?? -1;
|
||||
|
||||
if (highestIndex >= curIndex) {
|
||||
// First increment the receiving index
|
||||
final newReceivingIndex = curIndex + 1;
|
||||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = _addressFor(index: newReceivingIndex);
|
||||
|
||||
final existing = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(newReceivingAddress.value)
|
||||
.findFirst();
|
||||
if (existing == null) {
|
||||
// Add that new change address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await mainDB.updateAddress(existing, newReceivingAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await checkReceivingAddressForTransactions();
|
||||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh() async {
|
||||
// Awaiting this lock could be dangerous.
|
||||
// Since refresh is periodic (generally)
|
||||
if (refreshMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this acquire should be almost instant due to above check.
|
||||
// Slight possibility of race but should be irrelevant
|
||||
await refreshMutex.acquire();
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.syncing,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
|
||||
await updateTransactions();
|
||||
await updateBalance();
|
||||
|
||||
await checkReceivingAddressForTransactions();
|
||||
|
||||
if (cwWalletBase?.syncStatus is SyncedSyncStatus) {
|
||||
refreshMutex.release();
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.synced,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
if (isRescan) {
|
||||
|
@ -552,10 +331,10 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
|
||||
var restoreHeight = cwWalletBase?.walletInfo.restoreHeight;
|
||||
_highestPercentCached = 0;
|
||||
highestPercentCached = 0;
|
||||
await cwWalletBase?.rescan(height: restoreHeight);
|
||||
});
|
||||
await refresh();
|
||||
unawaited(refresh());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -582,19 +361,19 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: info.updateRestoreHeight
|
||||
// await DB.instance
|
||||
// .put<dynamic>(boxName: walletId, key: "restoreHeight", value: height);
|
||||
await info.updateRestoreHeight(
|
||||
newRestoreHeight: height,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
|
||||
cwWalletService = xmr_dart.monero
|
||||
.createMoneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
cwKeysStorage = KeyService(secureStorageInterface);
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
String name = walletId;
|
||||
final dirPath =
|
||||
await _pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final path = await _pathForWallet(name: name, type: WalletType.monero);
|
||||
await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||
credentials =
|
||||
xmr_dart.monero.createMoneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
|
@ -603,27 +382,27 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
);
|
||||
try {
|
||||
walletInfo = WalletInfo.external(
|
||||
id: WalletBase.idFor(name, WalletType.monero),
|
||||
name: name,
|
||||
type: WalletType.monero,
|
||||
isRecovery: false,
|
||||
restoreHeight: credentials.height ?? 0,
|
||||
date: DateTime.now(),
|
||||
path: path,
|
||||
dirPath: dirPath,
|
||||
// TODO: find out what to put for address
|
||||
address: '');
|
||||
id: WalletBase.idFor(name, WalletType.monero),
|
||||
name: name,
|
||||
type: WalletType.monero,
|
||||
isRecovery: false,
|
||||
restoreHeight: credentials.height ?? 0,
|
||||
date: DateTime.now(),
|
||||
path: path,
|
||||
dirPath: dirPath,
|
||||
address: '',
|
||||
);
|
||||
credentials.walletInfo = walletInfo;
|
||||
|
||||
cwWalletCreationService = WalletCreationService(
|
||||
final cwWalletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
cwWalletCreationService!.changeWalletType();
|
||||
cwWalletCreationService.type = WalletType.monero;
|
||||
// To restore from a seed
|
||||
final wallet =
|
||||
await cwWalletCreationService!.restoreFromSeed(credentials);
|
||||
await cwWalletCreationService.restoreFromSeed(credentials);
|
||||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await DB.instance
|
||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||
|
@ -669,7 +448,7 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
try {
|
||||
// check for send all
|
||||
bool isSendAll = false;
|
||||
final balance = await _availableBalance;
|
||||
final balance = await availableBalance;
|
||||
if (txData.amount! == balance &&
|
||||
txData.recipients!.first.amount == balance) {
|
||||
isSendAll = true;
|
||||
|
@ -747,133 +526,12 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
}
|
||||
}
|
||||
|
||||
// ====== private ============================================================
|
||||
|
||||
void onNewBlock({required int height, required int blocksLeft}) {
|
||||
_currentKnownChainHeight = height;
|
||||
updateChainHeight();
|
||||
_refreshTxDataHelper();
|
||||
}
|
||||
|
||||
void onNewTransaction() {
|
||||
// call this here?
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New data found in $walletId ${info.name} in background!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void syncStatusChanged() async {
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
if (syncStatus != null) {
|
||||
if (syncStatus.progress() == 1 && refreshMutex.isLocked) {
|
||||
refreshMutex.release();
|
||||
}
|
||||
|
||||
WalletSyncStatus? status;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true);
|
||||
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
|
||||
// ensure at least 1 to prevent math errors
|
||||
final int height = max(1, syncStatus.height);
|
||||
|
||||
final nodeHeight = height + blocksLeft;
|
||||
_currentKnownChainHeight = nodeHeight;
|
||||
|
||||
final percent = height / nodeHeight;
|
||||
|
||||
final highest = max(_highestPercentCached, percent);
|
||||
|
||||
// update cached
|
||||
if (_highestPercentCached < percent) {
|
||||
_highestPercentCached = percent;
|
||||
}
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highest,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
GlobalEventBus.instance.fire(
|
||||
BlocksRemainingEvent(
|
||||
blocksLeft,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is SyncedSyncStatus) {
|
||||
status = WalletSyncStatus.synced;
|
||||
} else if (syncStatus is NotConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is StartingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is FailedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is ConnectingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is ConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is LostConnectionSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
status,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Address _addressFor({required int index, int account = 0}) {
|
||||
String address = cwWalletBase!.getTransactionAddress(account, index);
|
||||
|
||||
final newReceivingAddress = Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: index,
|
||||
derivationPath: null,
|
||||
value: address,
|
||||
publicKey: [],
|
||||
type: AddressType.cryptonote,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
return newReceivingAddress;
|
||||
}
|
||||
|
||||
Future<Amount> get _availableBalance async {
|
||||
@override
|
||||
Future<Amount> get availableBalance async {
|
||||
try {
|
||||
int runningBalance = 0;
|
||||
for (final entry in cwWalletBase!.balance!.entries) {
|
||||
for (final entry
|
||||
in (cwWalletBase as MoneroWalletBase?)!.balance!.entries) {
|
||||
runningBalance += entry.value.unlockedBalance;
|
||||
}
|
||||
return Amount(
|
||||
|
@ -885,9 +543,11 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Amount> get _totalBalance async {
|
||||
@override
|
||||
Future<Amount> get totalBalance async {
|
||||
try {
|
||||
final balanceEntries = cwWalletBase?.balance?.entries;
|
||||
final balanceEntries =
|
||||
(cwWalletBase as MoneroWalletBase?)?.balance?.entries;
|
||||
if (balanceEntries != null) {
|
||||
int bal = 0;
|
||||
for (var element in balanceEntries) {
|
||||
|
@ -898,7 +558,9 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
} else {
|
||||
final transactions = cwWalletBase!.transactionHistory!.transactions;
|
||||
final transactions = (cwWalletBase as MoneroWalletBase?)!
|
||||
.transactionHistory!
|
||||
.transactions;
|
||||
int transactionBalance = 0;
|
||||
for (var tx in transactions!.entries) {
|
||||
if (tx.value.direction == TransactionDirection.incoming) {
|
||||
|
@ -917,124 +579,4 @@ class MoneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
return info.cachedBalance.total;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshTxDataHelper() async {
|
||||
if (_txRefreshLock) return;
|
||||
_txRefreshLock = true;
|
||||
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
|
||||
if (syncStatus != null && syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
final tenKChange = blocksLeft ~/ 10000;
|
||||
|
||||
// only refresh transactions periodically during a sync
|
||||
if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) {
|
||||
_lastCheckedHeight = tenKChange;
|
||||
await _refreshTxData();
|
||||
}
|
||||
} else {
|
||||
await _refreshTxData();
|
||||
}
|
||||
|
||||
_txRefreshLock = false;
|
||||
}
|
||||
|
||||
Future<void> _refreshTxData() async {
|
||||
await updateTransactions();
|
||||
final count = await mainDB.getTransactions(walletId).count();
|
||||
|
||||
if (count > _txCount) {
|
||||
_txCount = count;
|
||||
await updateBalance();
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New transaction data found in $walletId ${info.name}!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _pathForWalletDir({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async {
|
||||
Directory root = await StackFileSystem.applicationRootDirectory();
|
||||
|
||||
final prefix = walletTypeToString(type).toLowerCase();
|
||||
final walletsDir = Directory('${root.path}/wallets');
|
||||
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
|
||||
|
||||
if (!walletDire.existsSync()) {
|
||||
walletDire.createSync(recursive: true);
|
||||
}
|
||||
|
||||
return walletDire.path;
|
||||
}
|
||||
|
||||
Future<String> _pathForWallet({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async =>
|
||||
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
|
||||
// void Function(bool)? get onIsActiveWalletChanged => (isActive) async {
|
||||
// if (_isActive == isActive) {
|
||||
// return;
|
||||
// }
|
||||
// _isActive = isActive;
|
||||
//
|
||||
// if (isActive) {
|
||||
// _hasCalledExit = false;
|
||||
// String? password;
|
||||
// try {
|
||||
// password =
|
||||
// await keysStorage?.getWalletPassword(walletName: _walletId);
|
||||
// } catch (e, s) {
|
||||
// throw Exception("Password not found $e, $s");
|
||||
// }
|
||||
// walletBase = (await walletService?.openWallet(_walletId, password!))
|
||||
// as MoneroWalletBase?;
|
||||
//
|
||||
// walletBase!.onNewBlock = onNewBlock;
|
||||
// walletBase!.onNewTransaction = onNewTransaction;
|
||||
// walletBase!.syncStatusChanged = syncStatusChanged;
|
||||
//
|
||||
// if (!(await walletBase!.isConnected())) {
|
||||
// final node = await _getCurrentNode();
|
||||
// final host = Uri.parse(node.host).host;
|
||||
// await walletBase?.connectToNode(
|
||||
// node: Node(
|
||||
// uri: "$host:${node.port}",
|
||||
// type: WalletType.Monero,
|
||||
// trusted: node.trusted ?? false,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// await walletBase?.startSync();
|
||||
// await refresh();
|
||||
// _autoSaveTimer?.cancel();
|
||||
// _autoSaveTimer = Timer.periodic(
|
||||
// const Duration(seconds: 193),
|
||||
// (_) async => await walletBase?.save(),
|
||||
// );
|
||||
// } else {
|
||||
// await exit();
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
|
@ -10,70 +8,60 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart';
|
||||
import 'package:cw_wownero/api/wallet.dart';
|
||||
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||
import 'package:cw_wownero/wownero_wallet.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_libmonero/core/key_service.dart';
|
||||
import 'package:flutter_libmonero/core/wallet_creation_service.dart';
|
||||
import 'package:flutter_libmonero/view_model/send/output.dart'
|
||||
as wownero_output;
|
||||
import 'package:flutter_libmonero/wownero/wownero.dart' as wow_dart;
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/wownero.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/cryptonote_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
||||
class WowneroWallet extends CryptonoteWallet with CwBasedInterface {
|
||||
WowneroWallet(CryptoCurrencyNetwork network) : super(Wownero(network));
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation => null;
|
||||
Address addressFor({required int index, int account = 0}) {
|
||||
String address = (cwWalletBase as WowneroWalletBase)
|
||||
.getTransactionAddress(account, index);
|
||||
|
||||
@override
|
||||
FilterOperation? get receivingAddressFilterOperation => null;
|
||||
final newReceivingAddress = Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: index,
|
||||
derivationPath: null,
|
||||
value: address,
|
||||
publicKey: [],
|
||||
type: AddressType.cryptonote,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
final prepareSendMutex = Mutex();
|
||||
final estimateFeeMutex = Mutex();
|
||||
|
||||
bool _hasCalledExit = false;
|
||||
|
||||
WalletService? cwWalletService;
|
||||
KeyService? cwKeysStorage;
|
||||
WowneroWalletBase? cwWalletBase;
|
||||
WalletCreationService? cwWalletCreationService;
|
||||
Timer? _autoSaveTimer;
|
||||
|
||||
bool _txRefreshLock = false;
|
||||
int _lastCheckedHeight = -1;
|
||||
int _txCount = 0;
|
||||
int _currentKnownChainHeight = 0;
|
||||
double _highestPercentCached = 0;
|
||||
return newReceivingAddress;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
|
||||
if (cwWalletBase == null || cwWalletBase?.syncStatus is! SyncedSyncStatus) {
|
||||
return Amount.zeroWith(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
MoneroTransactionPriority priority;
|
||||
FeeRateType feeRateType = FeeRateType.slow;
|
||||
switch (feeRate) {
|
||||
|
@ -141,45 +129,9 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FeeObject> get fees async => FeeObject(
|
||||
numberOfBlocksFast: 10,
|
||||
numberOfBlocksAverage: 15,
|
||||
numberOfBlocksSlow: 20,
|
||||
fast: MoneroTransactionPriority.fast.raw!,
|
||||
medium: MoneroTransactionPriority.regular.raw!,
|
||||
slow: MoneroTransactionPriority.slow.raw!,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
return await cwWalletBase?.isConnected() ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final total = await _totalBalance;
|
||||
final available = await _availableBalance;
|
||||
|
||||
final balance = Balance(
|
||||
total: total,
|
||||
spendable: available,
|
||||
blockedTotal: Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
pendingSpendable: total - available,
|
||||
);
|
||||
|
||||
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateChainHeight() async {
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: _currentKnownChainHeight,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
return await (cwWalletBase as WowneroWalletBase?)?.isConnected() ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -194,19 +146,13 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
trusted: node.trusted ?? false,
|
||||
),
|
||||
);
|
||||
|
||||
// TODO: is this sync call needed? Do we need to notify ui here?
|
||||
// await cwWalletBase?.startSync();
|
||||
|
||||
// if (shouldRefresh) {
|
||||
// await refresh();
|
||||
// }
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
await cwWalletBase!.updateTransactions();
|
||||
final transactions = cwWalletBase?.transactionHistory!.transactions;
|
||||
await (cwWalletBase as WowneroWalletBase?)?.updateTransactions();
|
||||
final transactions =
|
||||
(cwWalletBase as WowneroWalletBase?)?.transactionHistory?.transactions;
|
||||
|
||||
// final cachedTransactions =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
|
||||
|
@ -249,7 +195,8 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
if (tx.value.direction == TransactionDirection.incoming) {
|
||||
final addressInfo = tx.value.additionalInfo;
|
||||
|
||||
final addressString = cwWalletBase?.getTransactionAddress(
|
||||
final addressString =
|
||||
(cwWalletBase as WowneroWalletBase?)?.getTransactionAddress(
|
||||
addressInfo!['accountIndex'] as int,
|
||||
addressInfo['addressIndex'] as int,
|
||||
);
|
||||
|
@ -302,26 +249,15 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
Future<void> init() async {
|
||||
cwWalletService = wow_dart.wownero
|
||||
.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
cwKeysStorage = KeyService(secureStorageInterface);
|
||||
|
||||
if (await cwWalletService!.isWalletExit(walletId)) {
|
||||
String? password;
|
||||
try {
|
||||
password = await cwKeysStorage!.getWalletPassword(walletName: walletId);
|
||||
} catch (e, s) {
|
||||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as WowneroWalletBase;
|
||||
unawaited(_start());
|
||||
} else {
|
||||
if (!(await cwWalletService!.isWalletExit(walletId))) {
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
try {
|
||||
String name = walletId;
|
||||
final dirPath =
|
||||
await _pathForWalletDir(name: name, type: WalletType.wownero);
|
||||
final path = await _pathForWallet(name: name, type: WalletType.wownero);
|
||||
await pathForWalletDir(name: name, type: WalletType.wownero);
|
||||
final path = await pathForWallet(name: name, type: WalletType.wownero);
|
||||
credentials = wow_dart.wownero.createWowneroNewWalletCredentials(
|
||||
name: name,
|
||||
language: "English",
|
||||
|
@ -337,7 +273,6 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
date: DateTime.now(),
|
||||
path: path,
|
||||
dirPath: dirPath,
|
||||
// TODO: find out what to put for address
|
||||
address: '',
|
||||
);
|
||||
credentials.walletInfo = walletInfo;
|
||||
|
@ -382,148 +317,51 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
await DB.instance
|
||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = wallet as WowneroWalletBase;
|
||||
unawaited(_start());
|
||||
wallet.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
cwWalletBase?.close();
|
||||
}
|
||||
await updateNode();
|
||||
await cwWalletBase?.startSync();
|
||||
|
||||
// cwWalletBase?.close();
|
||||
}
|
||||
|
||||
return super.init();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
if (!_hasCalledExit) {
|
||||
_hasCalledExit = true;
|
||||
cwWalletBase?.onNewBlock = null;
|
||||
cwWalletBase?.onNewTransaction = null;
|
||||
cwWalletBase?.syncStatusChanged = null;
|
||||
_autoSaveTimer?.cancel();
|
||||
await cwWalletBase?.save(prioritySave: true);
|
||||
cwWalletBase?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewReceivingAddress() async {
|
||||
Future<void> open() async {
|
||||
String? password;
|
||||
try {
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
|
||||
final newReceivingIndex =
|
||||
currentReceiving == null ? 0 : currentReceiving.derivationIndex + 1;
|
||||
|
||||
final newReceivingAddress = _addressFor(index: newReceivingIndex);
|
||||
|
||||
// Add that new receiving address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: newReceivingAddress.value,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
password = await cwKeysStorage.getWalletPassword(walletName: walletId);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception in generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkReceivingAddressForTransactions() async {
|
||||
try {
|
||||
int highestIndex = -1;
|
||||
for (var element
|
||||
in cwWalletBase!.transactionHistory!.transactions!.entries) {
|
||||
if (element.value.direction == TransactionDirection.incoming) {
|
||||
int curAddressIndex =
|
||||
element.value.additionalInfo!['addressIndex'] as int;
|
||||
if (curAddressIndex > highestIndex) {
|
||||
highestIndex = curAddressIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the new receiving index
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
final curIndex = currentReceiving?.derivationIndex ?? -1;
|
||||
|
||||
if (highestIndex >= curIndex) {
|
||||
// First increment the receiving index
|
||||
final newReceivingIndex = curIndex + 1;
|
||||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = _addressFor(index: newReceivingIndex);
|
||||
|
||||
final existing = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(newReceivingAddress.value)
|
||||
.findFirst();
|
||||
if (existing == null) {
|
||||
// Add that new change address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await mainDB.updateAddress(existing, newReceivingAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await checkReceivingAddressForTransactions();
|
||||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh() async {
|
||||
// Awaiting this lock could be dangerous.
|
||||
// Since refresh is periodic (generally)
|
||||
if (refreshMutex.isLocked) {
|
||||
return;
|
||||
throw Exception("Password not found $e, $s");
|
||||
}
|
||||
|
||||
// this acquire should be almost instant due to above check.
|
||||
// Slight possibility of race but should be irrelevant
|
||||
await refreshMutex.acquire();
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = (await cwWalletService!.openWallet(walletId, password))
|
||||
as WowneroWalletBase;
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.syncing,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewBlock = onNewBlock;
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewTransaction = onNewTransaction;
|
||||
(cwWalletBase as WowneroWalletBase?)?.syncStatusChanged = syncStatusChanged;
|
||||
|
||||
await updateNode();
|
||||
|
||||
await (cwWalletBase as WowneroWalletBase?)?.startSync();
|
||||
unawaited(refresh());
|
||||
autoSaveTimer?.cancel();
|
||||
autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
);
|
||||
}
|
||||
|
||||
await updateTransactions();
|
||||
await updateBalance();
|
||||
|
||||
await checkReceivingAddressForTransactions();
|
||||
|
||||
if (cwWalletBase?.syncStatus is SyncedSyncStatus) {
|
||||
refreshMutex.release();
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.synced,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Future<void> exitCwWallet() async {
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewBlock = null;
|
||||
(cwWalletBase as WowneroWalletBase?)?.onNewTransaction = null;
|
||||
(cwWalletBase as WowneroWalletBase?)?.syncStatusChanged = null;
|
||||
await (cwWalletBase as WowneroWalletBase?)?.save(prioritySave: true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -534,10 +372,10 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
|
||||
var restoreHeight = cwWalletBase?.walletInfo.restoreHeight;
|
||||
_highestPercentCached = 0;
|
||||
highestPercentCached = 0;
|
||||
await cwWalletBase?.rescan(height: restoreHeight);
|
||||
});
|
||||
await refresh();
|
||||
unawaited(refresh());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -575,13 +413,12 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
|
||||
cwWalletService = wow_dart.wownero
|
||||
.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
|
||||
cwKeysStorage = KeyService(secureStorageInterface);
|
||||
WalletInfo walletInfo;
|
||||
WalletCredentials credentials;
|
||||
String name = walletId;
|
||||
final dirPath =
|
||||
await _pathForWalletDir(name: name, type: WalletType.wownero);
|
||||
final path = await _pathForWallet(name: name, type: WalletType.wownero);
|
||||
await pathForWalletDir(name: name, type: WalletType.wownero);
|
||||
final path = await pathForWallet(name: name, type: WalletType.wownero);
|
||||
credentials =
|
||||
wow_dart.wownero.createWowneroRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
|
@ -602,20 +439,20 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
address: '');
|
||||
credentials.walletInfo = walletInfo;
|
||||
|
||||
cwWalletCreationService = WalletCreationService(
|
||||
final cwWalletCreationService = WalletCreationService(
|
||||
secureStorage: secureStorageInterface,
|
||||
walletService: cwWalletService,
|
||||
keyService: cwKeysStorage,
|
||||
);
|
||||
cwWalletCreationService!.changeWalletType();
|
||||
cwWalletCreationService.type = WalletType.wownero;
|
||||
// To restore from a seed
|
||||
final wallet =
|
||||
await cwWalletCreationService!.restoreFromSeed(credentials);
|
||||
final wallet = await cwWalletCreationService
|
||||
.restoreFromSeed(credentials) as WowneroWalletBase;
|
||||
walletInfo.address = wallet.walletAddresses.address;
|
||||
await DB.instance
|
||||
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
|
||||
cwWalletBase?.close();
|
||||
cwWalletBase = wallet as WowneroWalletBase;
|
||||
cwWalletBase = wallet;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
}
|
||||
|
@ -656,7 +493,7 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
try {
|
||||
// check for send all
|
||||
bool isSendAll = false;
|
||||
final balance = await _availableBalance;
|
||||
final balance = await availableBalance;
|
||||
if (txData.amount! == balance &&
|
||||
txData.recipients!.first.amount == balance) {
|
||||
isSendAll = true;
|
||||
|
@ -734,158 +571,12 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
}
|
||||
}
|
||||
|
||||
// ====== private ============================================================
|
||||
|
||||
Future<void> _start() async {
|
||||
cwWalletBase?.onNewBlock = onNewBlock;
|
||||
cwWalletBase?.onNewTransaction = onNewTransaction;
|
||||
cwWalletBase?.syncStatusChanged = syncStatusChanged;
|
||||
|
||||
if (cwWalletBase != null && !(await cwWalletBase!.isConnected())) {
|
||||
final node = getCurrentNode();
|
||||
final host = Uri.parse(node.host).host;
|
||||
await cwWalletBase?.connectToNode(
|
||||
node: Node(
|
||||
uri: "$host:${node.port}",
|
||||
type: WalletType.monero,
|
||||
trusted: node.trusted ?? false,
|
||||
),
|
||||
);
|
||||
}
|
||||
await cwWalletBase?.startSync();
|
||||
unawaited(refresh());
|
||||
_autoSaveTimer?.cancel();
|
||||
_autoSaveTimer = Timer.periodic(
|
||||
const Duration(seconds: 193),
|
||||
(_) async => await cwWalletBase?.save(),
|
||||
);
|
||||
}
|
||||
|
||||
void onNewBlock({required int height, required int blocksLeft}) {
|
||||
_currentKnownChainHeight = height;
|
||||
updateChainHeight();
|
||||
_refreshTxDataHelper();
|
||||
}
|
||||
|
||||
void onNewTransaction() {
|
||||
// call this here?
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New data found in $walletId ${info.name} in background!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void syncStatusChanged() async {
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
if (syncStatus != null) {
|
||||
if (syncStatus.progress() == 1 && refreshMutex.isLocked) {
|
||||
refreshMutex.release();
|
||||
}
|
||||
|
||||
WalletSyncStatus? status;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true);
|
||||
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
|
||||
// ensure at least 1 to prevent math errors
|
||||
final int height = max(1, syncStatus.height);
|
||||
|
||||
final nodeHeight = height + blocksLeft;
|
||||
_currentKnownChainHeight = nodeHeight;
|
||||
|
||||
final percent = height / nodeHeight;
|
||||
|
||||
final highest = max(_highestPercentCached, percent);
|
||||
|
||||
// update cached
|
||||
if (_highestPercentCached < percent) {
|
||||
_highestPercentCached = percent;
|
||||
}
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highest,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
GlobalEventBus.instance.fire(
|
||||
BlocksRemainingEvent(
|
||||
blocksLeft,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is SyncedSyncStatus) {
|
||||
status = WalletSyncStatus.synced;
|
||||
} else if (syncStatus is NotConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is StartingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is FailedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is ConnectingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is ConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
_highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is LostConnectionSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
status,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Address _addressFor({required int index, int account = 0}) {
|
||||
String address = cwWalletBase!.getTransactionAddress(account, index);
|
||||
|
||||
final newReceivingAddress = Address(
|
||||
walletId: walletId,
|
||||
derivationIndex: index,
|
||||
derivationPath: null,
|
||||
value: address,
|
||||
publicKey: [],
|
||||
type: AddressType.cryptonote,
|
||||
subType: AddressSubType.receiving,
|
||||
);
|
||||
|
||||
return newReceivingAddress;
|
||||
}
|
||||
|
||||
Future<Amount> get _availableBalance async {
|
||||
@override
|
||||
Future<Amount> get availableBalance async {
|
||||
try {
|
||||
int runningBalance = 0;
|
||||
for (final entry in cwWalletBase!.balance!.entries) {
|
||||
for (final entry
|
||||
in (cwWalletBase as WowneroWalletBase?)!.balance!.entries) {
|
||||
runningBalance += entry.value.unlockedBalance;
|
||||
}
|
||||
return Amount(
|
||||
|
@ -897,9 +588,11 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Amount> get _totalBalance async {
|
||||
@override
|
||||
Future<Amount> get totalBalance async {
|
||||
try {
|
||||
final balanceEntries = cwWalletBase?.balance?.entries;
|
||||
final balanceEntries =
|
||||
(cwWalletBase as WowneroWalletBase?)?.balance?.entries;
|
||||
if (balanceEntries != null) {
|
||||
int bal = 0;
|
||||
for (var element in balanceEntries) {
|
||||
|
@ -929,124 +622,4 @@ class WowneroWallet extends CryptonoteWallet with MultiAddressInterface {
|
|||
return info.cachedBalance.total;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _refreshTxDataHelper() async {
|
||||
if (_txRefreshLock) return;
|
||||
_txRefreshLock = true;
|
||||
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
|
||||
if (syncStatus != null && syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
final tenKChange = blocksLeft ~/ 10000;
|
||||
|
||||
// only refresh transactions periodically during a sync
|
||||
if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) {
|
||||
_lastCheckedHeight = tenKChange;
|
||||
await _refreshTxData();
|
||||
}
|
||||
} else {
|
||||
await _refreshTxData();
|
||||
}
|
||||
|
||||
_txRefreshLock = false;
|
||||
}
|
||||
|
||||
Future<void> _refreshTxData() async {
|
||||
await updateTransactions();
|
||||
final count = await mainDB.getTransactions(walletId).count();
|
||||
|
||||
if (count > _txCount) {
|
||||
_txCount = count;
|
||||
await updateBalance();
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New transaction data found in $walletId ${info.name}!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _pathForWalletDir({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async {
|
||||
Directory root = await StackFileSystem.applicationRootDirectory();
|
||||
|
||||
final prefix = walletTypeToString(type).toLowerCase();
|
||||
final walletsDir = Directory('${root.path}/wallets');
|
||||
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
|
||||
|
||||
if (!walletDire.existsSync()) {
|
||||
walletDire.createSync(recursive: true);
|
||||
}
|
||||
|
||||
return walletDire.path;
|
||||
}
|
||||
|
||||
Future<String> _pathForWallet({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async =>
|
||||
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
|
||||
// void Function(bool)? get onIsActiveWalletChanged => (isActive) async {
|
||||
// if (_isActive == isActive) {
|
||||
// return;
|
||||
// }
|
||||
// _isActive = isActive;
|
||||
//
|
||||
// if (isActive) {
|
||||
// _hasCalledExit = false;
|
||||
// String? password;
|
||||
// try {
|
||||
// password =
|
||||
// await keysStorage?.getWalletPassword(walletName: _walletId);
|
||||
// } catch (e, s) {
|
||||
// throw Exception("Password not found $e, $s");
|
||||
// }
|
||||
// walletBase = (await walletService?.openWallet(_walletId, password!))
|
||||
// as WowneroWalletBase?;
|
||||
//
|
||||
// walletBase!.onNewBlock = onNewBlock;
|
||||
// walletBase!.onNewTransaction = onNewTransaction;
|
||||
// walletBase!.syncStatusChanged = syncStatusChanged;
|
||||
//
|
||||
// if (!(await walletBase!.isConnected())) {
|
||||
// final node = await _getCurrentNode();
|
||||
// final host = Uri.parse(node.host).host;
|
||||
// await walletBase?.connectToNode(
|
||||
// node: Node(
|
||||
// uri: "$host:${node.port}",
|
||||
// type: WalletType.wownero,
|
||||
// trusted: node.trusted ?? false,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// await walletBase?.startSync();
|
||||
// await refresh();
|
||||
// _autoSaveTimer?.cancel();
|
||||
// _autoSaveTimer = Timer.periodic(
|
||||
// const Duration(seconds: 193),
|
||||
// (_) async => await walletBase?.save(),
|
||||
// );
|
||||
// } else {
|
||||
// await exit();
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,409 @@
|
|||
import 'dart:async';
|
||||
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/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter_libmonero/core/key_service.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/cryptonote_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
|
||||
mixin CwBasedInterface<T extends CryptonoteCurrency> on CryptonoteWallet<T>
|
||||
implements MultiAddressInterface<T> {
|
||||
final prepareSendMutex = Mutex();
|
||||
final estimateFeeMutex = Mutex();
|
||||
|
||||
KeyService? _cwKeysStorageCached;
|
||||
KeyService get cwKeysStorage =>
|
||||
_cwKeysStorageCached ??= KeyService(secureStorageInterface);
|
||||
|
||||
WalletService? cwWalletService;
|
||||
WalletBase? cwWalletBase;
|
||||
|
||||
bool _hasCalledExit = false;
|
||||
bool _txRefreshLock = false;
|
||||
int _lastCheckedHeight = -1;
|
||||
int _txCount = 0;
|
||||
int currentKnownChainHeight = 0;
|
||||
double highestPercentCached = 0;
|
||||
|
||||
Timer? autoSaveTimer;
|
||||
|
||||
Future<String> pathForWalletDir({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async {
|
||||
final Directory root = await StackFileSystem.applicationRootDirectory();
|
||||
|
||||
final prefix = walletTypeToString(type).toLowerCase();
|
||||
final walletsDir = Directory('${root.path}/wallets');
|
||||
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
|
||||
|
||||
if (!walletDire.existsSync()) {
|
||||
walletDire.createSync(recursive: true);
|
||||
}
|
||||
|
||||
return walletDire.path;
|
||||
}
|
||||
|
||||
Future<String> pathForWallet({
|
||||
required String name,
|
||||
required WalletType type,
|
||||
}) async =>
|
||||
await pathForWalletDir(name: name, type: type)
|
||||
.then((path) => '$path/$name');
|
||||
|
||||
void onNewBlock({required int height, required int blocksLeft}) {
|
||||
currentKnownChainHeight = height;
|
||||
updateChainHeight();
|
||||
_refreshTxDataHelper();
|
||||
}
|
||||
|
||||
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
|
||||
// db is watched by the ui
|
||||
// call this here?
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New data found in $walletId ${info.name} in background!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void syncStatusChanged() async {
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
if (syncStatus != null) {
|
||||
if (syncStatus.progress() == 1 && refreshMutex.isLocked) {
|
||||
refreshMutex.release();
|
||||
}
|
||||
|
||||
WalletSyncStatus? status;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true);
|
||||
|
||||
if (syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
|
||||
// ensure at least 1 to prevent math errors
|
||||
final int height = max(1, syncStatus.height);
|
||||
|
||||
final nodeHeight = height + blocksLeft;
|
||||
currentKnownChainHeight = nodeHeight;
|
||||
|
||||
final percent = height / nodeHeight;
|
||||
|
||||
final highest = max(highestPercentCached, percent);
|
||||
|
||||
// update cached
|
||||
if (highestPercentCached < percent) {
|
||||
highestPercentCached = percent;
|
||||
}
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highest,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
GlobalEventBus.instance.fire(
|
||||
BlocksRemainingEvent(
|
||||
blocksLeft,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is SyncedSyncStatus) {
|
||||
status = WalletSyncStatus.synced;
|
||||
} else if (syncStatus is NotConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is StartingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is FailedSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
} else if (syncStatus is ConnectingSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is ConnectedSyncStatus) {
|
||||
status = WalletSyncStatus.syncing;
|
||||
GlobalEventBus.instance.fire(
|
||||
RefreshPercentChangedEvent(
|
||||
highestPercentCached,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
} else if (syncStatus is LostConnectionSyncStatus) {
|
||||
status = WalletSyncStatus.unableToSync;
|
||||
xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(false);
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
status,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Interface ====================================================
|
||||
|
||||
Future<Amount> get availableBalance;
|
||||
Future<Amount> get totalBalance;
|
||||
|
||||
Future<void> exitCwWallet();
|
||||
|
||||
Future<void> open();
|
||||
|
||||
Address addressFor({required int index, int account = 0});
|
||||
|
||||
// ============ Private ======================================================
|
||||
Future<void> _refreshTxDataHelper() async {
|
||||
if (_txRefreshLock) return;
|
||||
_txRefreshLock = true;
|
||||
|
||||
final syncStatus = cwWalletBase?.syncStatus;
|
||||
|
||||
if (syncStatus != null && syncStatus is SyncingSyncStatus) {
|
||||
final int blocksLeft = syncStatus.blocksLeft;
|
||||
final tenKChange = blocksLeft ~/ 10000;
|
||||
|
||||
// only refresh transactions periodically during a sync
|
||||
if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) {
|
||||
_lastCheckedHeight = tenKChange;
|
||||
await _refreshTxData();
|
||||
}
|
||||
} else {
|
||||
await _refreshTxData();
|
||||
}
|
||||
|
||||
_txRefreshLock = false;
|
||||
}
|
||||
|
||||
Future<void> _refreshTxData() async {
|
||||
await updateTransactions();
|
||||
final count = await mainDB.getTransactions(walletId).count();
|
||||
|
||||
if (count > _txCount) {
|
||||
_txCount = count;
|
||||
await updateBalance();
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"New transaction data found in $walletId ${info.name}!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Overrides ====================================================
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation => null;
|
||||
|
||||
@override
|
||||
FilterOperation? get receivingAddressFilterOperation => null;
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final total = await totalBalance;
|
||||
final available = await availableBalance;
|
||||
|
||||
final balance = Balance(
|
||||
total: total,
|
||||
spendable: available,
|
||||
blockedTotal: Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
),
|
||||
pendingSpendable: total - available,
|
||||
);
|
||||
|
||||
await info.updateBalance(newBalance: balance, isar: mainDB.isar);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> refresh() async {
|
||||
// Awaiting this lock could be dangerous.
|
||||
// Since refresh is periodic (generally)
|
||||
if (refreshMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this acquire should be almost instant due to above check.
|
||||
// Slight possibility of race but should be irrelevant
|
||||
await refreshMutex.acquire();
|
||||
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.syncing,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
|
||||
await updateTransactions();
|
||||
await updateBalance();
|
||||
|
||||
await checkReceivingAddressForTransactions();
|
||||
|
||||
if (cwWalletBase?.syncStatus is SyncedSyncStatus) {
|
||||
refreshMutex.release();
|
||||
GlobalEventBus.instance.fire(
|
||||
WalletSyncStatusChangedEvent(
|
||||
WalletSyncStatus.synced,
|
||||
walletId,
|
||||
info.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
if (!_hasCalledExit) {
|
||||
_hasCalledExit = true;
|
||||
autoSaveTimer?.cancel();
|
||||
await exitCwWallet();
|
||||
cwWalletBase?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewReceivingAddress() async {
|
||||
try {
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
|
||||
final newReceivingIndex =
|
||||
currentReceiving == null ? 0 : currentReceiving.derivationIndex + 1;
|
||||
|
||||
final newReceivingAddress = addressFor(index: newReceivingIndex);
|
||||
|
||||
// Add that new receiving address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: newReceivingAddress.value,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception in generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkReceivingAddressForTransactions() async {
|
||||
try {
|
||||
int highestIndex = -1;
|
||||
for (var element
|
||||
in cwWalletBase!.transactionHistory!.transactions!.entries) {
|
||||
if (element.value.direction == TransactionDirection.incoming) {
|
||||
int curAddressIndex =
|
||||
element.value.additionalInfo!['addressIndex'] as int;
|
||||
if (curAddressIndex > highestIndex) {
|
||||
highestIndex = curAddressIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the new receiving index
|
||||
final currentReceiving = await getCurrentReceivingAddress();
|
||||
final curIndex = currentReceiving?.derivationIndex ?? -1;
|
||||
|
||||
if (highestIndex >= curIndex) {
|
||||
// First increment the receiving index
|
||||
final newReceivingIndex = curIndex + 1;
|
||||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = addressFor(index: newReceivingIndex);
|
||||
|
||||
final existing = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(newReceivingAddress.value)
|
||||
.findFirst();
|
||||
if (existing == null) {
|
||||
// Add that new change address
|
||||
await mainDB.putAddress(newReceivingAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await mainDB.updateAddress(existing, newReceivingAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await checkReceivingAddressForTransactions();
|
||||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FeeObject> get fees async => FeeObject(
|
||||
numberOfBlocksFast: 10,
|
||||
numberOfBlocksAverage: 15,
|
||||
numberOfBlocksSlow: 20,
|
||||
fast: MoneroTransactionPriority.fast.raw!,
|
||||
medium: MoneroTransactionPriority.regular.raw!,
|
||||
slow: MoneroTransactionPriority.slow.raw!,
|
||||
);
|
||||
@override
|
||||
Future<void> updateChainHeight() async {
|
||||
await info.updateCachedChainHeight(
|
||||
newHeight: currentKnownChainHeight,
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> checkChangeAddressForTransactions() async {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewChangeAddress() async {
|
||||
// do nothing
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/des
|
|||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
|
@ -28,6 +27,7 @@ import 'package:stackwallet/wallets/isar/providers/eth/current_token_wallet_prov
|
|||
import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
|
||||
|
@ -93,11 +93,17 @@ class SimpleWalletCard extends ConsumerWidget {
|
|||
final nav = Navigator.of(context);
|
||||
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet.info.coin == Coin.monero || wallet.info.coin == Coin.wownero) {
|
||||
// TODO: this can cause ui lag if awaited
|
||||
unawaited(wallet.init());
|
||||
}
|
||||
await wallet.init();
|
||||
|
||||
if (context.mounted) {
|
||||
if (wallet is CwBasedInterface) {
|
||||
await showLoading(
|
||||
whileFuture: wallet.open(),
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
isDesktop: Util.isDesktop,
|
||||
);
|
||||
}
|
||||
if (popPrevious) nav.pop();
|
||||
|
||||
if (desktopNavigatorState != null) {
|
||||
|
|
Loading…
Reference in a new issue