stack_wallet/lib/services/coins/wownero/wownero_wallet.dart

1353 lines
42 KiB
Dart
Raw Normal View History

2023-05-26 21:21:16 +00:00
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
2022-09-27 08:09:31 +00:00
import 'dart:async';
import 'dart:io';
2022-12-30 22:15:03 +00:00
import 'dart:math';
2022-09-27 08:09:31 +00:00
import 'package:cw_core/monero_transaction_priority.dart';
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/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_wownero/api/exceptions/creation_transaction_exception.dart';
import 'package:cw_wownero/api/wallet.dart';
2022-09-28 15:46:46 +00:00
import 'package:cw_wownero/pending_wownero_transaction.dart';
2022-09-27 08:09:31 +00:00
import 'package:cw_wownero/wownero_wallet.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.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;
2022-09-28 15:46:46 +00:00
import 'package:flutter_libmonero/wownero/wownero.dart';
import 'package:isar/isar.dart';
2022-09-27 08:09:31 +00:00
import 'package:mutex/mutex.dart';
2023-03-01 21:52:13 +00:00
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
2022-09-27 08:09:31 +00:00
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/coins/coin_service.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';
2023-01-12 18:46:01 +00:00
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
2022-09-27 08:09:31 +00:00
import 'package:stackwallet/services/node_service.dart';
2023-04-06 21:24:56 +00:00
import 'package:stackwallet/utilities/amount/amount.dart';
2022-09-27 08:09:31 +00:00
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
2022-11-12 22:04:16 +00:00
import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:tuple/tuple.dart';
2022-09-27 08:09:31 +00:00
2023-04-20 17:00:52 +00:00
const int MINIMUM_CONFIRMATIONS = 15;
2022-09-27 08:09:31 +00:00
2023-01-12 18:46:01 +00:00
class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB {
WowneroWallet({
required String walletId,
required String walletName,
required Coin coin,
required SecureStorageInterface secureStorage,
Prefs? prefs,
MainDB? mockableOverride,
}) {
_walletId = walletId;
_walletName = walletName;
_coin = coin;
_secureStorage = secureStorage;
_prefs = prefs ?? Prefs.instance;
initCache(walletId, coin);
initWalletDB(mockableOverride: mockableOverride);
}
late final String _walletId;
late final Coin _coin;
late final SecureStorageInterface _secureStorage;
late final Prefs _prefs;
late String _walletName;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
bool _shouldAutoSync = false;
bool _isConnected = false;
bool _hasCalledExit = false;
bool refreshMutex = false;
bool longMutex = false;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
WalletService? walletService;
KeyService? keysStorage;
WowneroWalletBase? walletBase;
WalletCreationService? _walletCreationService;
Timer? _autoSaveTimer;
2022-09-27 08:09:31 +00:00
Future<isar_models.Address?> get _currentReceivingAddress =>
2023-01-16 21:04:03 +00:00
db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst();
2022-12-30 22:15:03 +00:00
Future<FeeObject>? _feeObject;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Mutex prepareSendMutex = Mutex();
Mutex estimateFeeMutex = Mutex();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
set isFavorite(bool markFavorite) {
_isFavorite = markFavorite;
updateCachedIsFavorite(markFavorite);
2022-12-30 22:15:03 +00:00
}
2022-09-27 08:09:31 +00:00
@override
bool get isFavorite => _isFavorite ??= getCachedIsFavorite();
bool? _isFavorite;
2022-09-27 08:09:31 +00:00
@override
bool get shouldAutoSync => _shouldAutoSync;
@override
set shouldAutoSync(bool shouldAutoSync) {
if (_shouldAutoSync != shouldAutoSync) {
_shouldAutoSync = shouldAutoSync;
2022-12-30 22:15:03 +00:00
// wow wallets cannot be open at the same time
// leave following commented out for now
// if (!shouldAutoSync) {
// timer?.cancel();
// moneroAutosaveTimer?.cancel();
// timer = null;
// moneroAutosaveTimer = null;
// stopNetworkAlivePinging();
// } else {
// startNetworkAlivePinging();
// // Walletbase needs to be open for this to work
// refresh();
// }
2022-09-27 08:09:31 +00:00
}
}
@override
2022-12-30 22:15:03 +00:00
String get walletName => _walletName;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// setter for updating on rename
@override
set walletName(String newName) => _walletName = newName;
2022-09-27 08:09:31 +00:00
@override
2022-12-30 22:15:03 +00:00
Coin get coin => _coin;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
Future<String> confirmSend({required Map<String, dynamic> txData}) async {
2022-09-27 08:09:31 +00:00
try {
2022-12-30 22:15:03 +00:00
Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info);
final pendingWowneroTransaction =
txData['pendingWowneroTransaction'] as PendingWowneroTransaction;
try {
await pendingWowneroTransaction.commit();
Logging.instance.log(
"transaction ${pendingWowneroTransaction.id} has been sent",
level: LogLevel.Info);
return pendingWowneroTransaction.id;
} catch (e, s) {
Logging.instance.log("$walletName wownero confirmSend: $e\n$s",
level: LogLevel.Error);
rethrow;
2022-09-27 08:09:31 +00:00
}
} catch (e, s) {
2022-12-30 22:15:03 +00:00
Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s",
level: LogLevel.Info);
rethrow;
2022-09-27 08:09:31 +00:00
}
}
2022-12-30 22:15:03 +00:00
@override
Future<String> get currentReceivingAddress async =>
2023-01-17 23:19:09 +00:00
(await _currentReceivingAddress)?.value ??
(await _generateAddressForChain(0, 0)).value;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
2023-04-05 22:06:31 +00:00
Future<Amount> estimateFeeFor(Amount amount, int feeRate) async {
2022-12-30 22:15:03 +00:00
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;
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
var aprox;
await estimateFeeMutex.protect(() async {
{
try {
aprox = (await prepareSend(
// This address is only used for getting an approximate fee, never for sending
address: "WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy",
2023-04-05 22:06:31 +00:00
amount: amount,
2022-12-30 22:15:03 +00:00
args: {"feeRate": feeRateType}))['fee'];
2023-04-05 22:06:31 +00:00
await Future<void>.delayed(const Duration(milliseconds: 500));
2022-12-30 22:15:03 +00:00
} catch (e, s) {
2023-04-05 22:06:31 +00:00
aprox = walletBase!.calculateEstimatedFee(
priority,
amount.raw.toInt(),
);
2022-12-30 22:15:03 +00:00
}
}
});
2022-09-27 08:09:31 +00:00
2023-04-05 22:06:31 +00:00
print("this is the aprox fee $aprox for $amount");
if (aprox is Amount) {
return aprox as Amount;
} else {
return Amount(
rawValue: BigInt.from(aprox as int),
fractionDigits: coin.decimals,
);
}
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
@override
Future<void> exit() async {
if (!_hasCalledExit) {
walletBase?.onNewBlock = null;
walletBase?.onNewTransaction = null;
walletBase?.syncStatusChanged = null;
_hasCalledExit = true;
_autoSaveTimer?.cancel();
await walletBase?.save(prioritySave: true);
walletBase?.close();
}
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
@override
Future<FeeObject> get fees => _feeObject ??= _getFees();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
Future<void> fullRescan(
int maxUnusedAddressGap,
int maxNumberOfIndexesToCheck,
) async {
// clear blockchain info
2023-01-16 21:04:03 +00:00
await db.deleteWalletBlockchainData(walletId);
2022-12-30 22:15:03 +00:00
var restoreHeight = walletBase?.walletInfo.restoreHeight;
highestPercentCached = 0;
await walletBase?.rescan(height: restoreHeight);
await refresh();
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
@override
Future<bool> generateNewAddress() async {
2022-09-27 08:09:31 +00:00
try {
final currentReceiving = await _currentReceivingAddress;
2022-09-27 08:09:31 +00:00
final newReceivingIndex = currentReceiving!.derivationIndex + 1;
2022-09-27 08:09:31 +00:00
// Use new index to derive a new receiving address
final newReceivingAddress = await _generateAddressForChain(
0,
newReceivingIndex,
);
2022-09-27 08:09:31 +00:00
// Add that new receiving address
2023-01-16 21:04:03 +00:00
await db.putAddress(newReceivingAddress);
2022-12-30 22:15:03 +00:00
return true;
2022-09-27 08:09:31 +00:00
} catch (e, s) {
Logging.instance.log(
2022-12-30 22:15:03 +00:00
"Exception rethrown from generateNewAddress(): $e\n$s",
2022-09-27 08:09:31 +00:00
level: LogLevel.Error);
2022-12-30 22:15:03 +00:00
return false;
2022-09-27 08:09:31 +00:00
}
}
@override
2022-12-30 22:15:03 +00:00
bool get hasCalledExit => _hasCalledExit;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
Future<void> initializeExisting() async {
Logging.instance.log(
"initializeExisting() ${coin.prettyName} wallet $walletName...",
2022-12-30 22:15:03 +00:00
level: LogLevel.Info);
2022-09-27 08:09:31 +00:00
if (getCachedId() == null) {
2022-12-30 22:15:03 +00:00
//todo: check if print needed
// debugPrint("Exception was thrown");
throw Exception(
"Attempted to initialize an existing wallet using an unknown wallet ID!");
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
walletService =
wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
keysStorage = KeyService(_secureStorage);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
await _prefs.init();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
String? password;
2022-09-27 08:09:31 +00:00
try {
2022-12-30 22:15:03 +00:00
password = await keysStorage?.getWalletPassword(walletName: _walletId);
} catch (e, s) {
throw Exception("Password not found $e, $s");
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
walletBase = (await walletService?.openWallet(_walletId, password!))
as WowneroWalletBase;
2022-09-27 08:09:31 +00:00
// await _checkCurrentReceivingAddressesForTransactions();
2023-01-23 16:32:53 +00:00
2022-12-30 22:15:03 +00:00
Logging.instance.log(
"Opened existing ${coin.prettyName} wallet $walletName",
level: LogLevel.Info,
2022-09-27 08:09:31 +00:00
);
}
2022-12-30 22:15:03 +00:00
@override
Future<void> initializeNew({int seedWordsLength = 14}) async {
await _prefs.init();
2022-09-27 08:09:31 +00:00
// this should never fail
if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) {
2022-09-27 08:09:31 +00:00
throw Exception(
"Attempted to overwrite mnemonic on generate new wallet!");
}
// TODO: Wallet Service may need to be switched to Wownero
walletService =
wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
2022-12-30 22:15:03 +00:00
keysStorage = KeyService(_secureStorage);
2022-09-27 08:09:31 +00:00
WalletInfo walletInfo;
WalletCredentials credentials;
try {
String name = _walletId;
final dirPath =
2022-12-30 22:15:03 +00:00
await _pathForWalletDir(name: name, type: WalletType.wownero);
final path = await _pathForWallet(name: name, type: WalletType.wownero);
2022-09-27 08:09:31 +00:00
credentials = wownero.createWowneroNewWalletCredentials(
2022-12-30 22:15:03 +00:00
name: name,
language: "English",
seedWordsLength: seedWordsLength,
);
2022-09-27 08:09:31 +00:00
walletInfo = WalletInfo.external(
2022-12-30 22:15:03 +00:00
id: WalletBase.idFor(name, WalletType.wownero),
name: name,
type: WalletType.wownero,
isRecovery: false,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
// TODO: find out what to put for address
address: '',
);
2022-09-27 08:09:31 +00:00
credentials.walletInfo = walletInfo;
_walletCreationService = WalletCreationService(
2022-12-30 22:15:03 +00:00
secureStorage: _secureStorage,
2022-09-27 08:09:31 +00:00
walletService: walletService,
keyService: keysStorage,
);
_walletCreationService?.changeWalletType();
// To restore from a seed
final wallet = await _walletCreationService?.create(credentials);
final bufferedCreateHeight = (seedWordsLength == 14)
? getSeedHeightSync(wallet?.seed.trim() as String)
: wownero.getHeightByDate(
date: DateTime.now().subtract(const Duration(
days:
2))); // subtract a couple days to ensure we have a buffer for SWB
2022-09-27 08:09:31 +00:00
await DB.instance.put<dynamic>(
boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight);
walletInfo.restoreHeight = bufferedCreateHeight;
2022-12-30 22:15:03 +00:00
await _secureStorage.write(
2022-09-27 08:09:31 +00:00
key: '${_walletId}_mnemonic', value: wallet?.seed.trim());
await _secureStorage.write(
key: '${_walletId}_mnemonicPassphrase',
value: "",
);
2022-09-27 08:09:31 +00:00
walletInfo.address = wallet?.walletAddresses.address;
await DB.instance
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
walletBase?.close();
walletBase = wallet as WowneroWalletBase;
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
2022-12-30 22:15:03 +00:00
walletBase?.close();
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
final node = await _getCurrentNode();
2022-09-27 08:09:31 +00:00
final host = Uri.parse(node.host).host;
await walletBase?.connectToNode(
node: Node(
uri: "$host:${node.port}",
type: WalletType.wownero,
trusted: node.trusted ?? false,
),
);
2022-09-27 08:09:31 +00:00
await walletBase?.startSync();
await Future.wait([
updateCachedId(walletId),
updateCachedIsFavorite(false),
]);
2022-09-27 08:09:31 +00:00
// Generate and add addresses to relevant arrays
final initialReceivingAddress = await _generateAddressForChain(0, 0);
// final initialChangeAddress = await _generateAddressForChain(1, 0);
2023-01-16 21:04:03 +00:00
await db.putAddress(initialReceivingAddress);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
walletBase?.close();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Logging.instance
.log("initializeNew for $walletName $walletId", level: LogLevel.Info);
2022-09-27 08:09:31 +00:00
}
@override
2022-12-30 22:15:03 +00:00
bool get isConnected => _isConnected;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
bool get isRefreshing => refreshMutex;
2022-09-27 08:09:31 +00:00
@override
2022-12-30 22:15:03 +00:00
// not used in wow
Future<int> get maxFee => throw UnimplementedError();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
Future<List<String>> get mnemonic async {
final _mnemonicString = await mnemonicString;
if (_mnemonicString == null) {
2022-12-30 22:15:03 +00:00
return [];
2022-09-27 08:09:31 +00:00
}
final List<String> data = _mnemonicString.split(' ');
2022-12-30 22:15:03 +00:00
return data;
2022-09-27 08:09:31 +00:00
}
@override
Future<String?> get mnemonicString =>
_secureStorage.read(key: '${_walletId}_mnemonic');
@override
Future<String?> get mnemonicPassphrase => _secureStorage.read(
key: '${_walletId}_mnemonicPassphrase',
);
2022-09-27 08:09:31 +00:00
@override
2022-12-30 22:15:03 +00:00
Future<Map<String, dynamic>> prepareSend({
required String address,
2023-04-05 22:06:31 +00:00
required Amount amount,
2022-12-30 22:15:03 +00:00
Map<String, dynamic>? args,
}) async {
try {
final feeRate = args?["feeRate"];
if (feeRate is FeeRateType) {
MoneroTransactionPriority feePriority;
switch (feeRate) {
case FeeRateType.fast:
feePriority = MoneroTransactionPriority.fast;
break;
case FeeRateType.average:
feePriority = MoneroTransactionPriority.regular;
break;
case FeeRateType.slow:
feePriority = MoneroTransactionPriority.slow;
break;
default:
throw ArgumentError("Invalid use of custom fee");
2022-12-30 22:15:03 +00:00
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Future<PendingTransaction>? awaitPendingTransaction;
try {
// check for send all
bool isSendAll = false;
final balance = await _availableBalance;
2023-04-05 22:06:31 +00:00
if (amount == balance) {
2022-12-30 22:15:03 +00:00
isSendAll = true;
}
2023-04-05 22:06:31 +00:00
Logging.instance.log("$address $amount $args", level: LogLevel.Info);
String amountToSend = amount.decimal.toString();
Logging.instance.log("$amount $amountToSend", level: LogLevel.Info);
2022-12-30 22:15:03 +00:00
wownero_output.Output output = wownero_output.Output(walletBase!);
output.address = address;
output.sendAll = isSendAll;
output.setCryptoAmount(amountToSend);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
List<wownero_output.Output> outputs = [output];
Object tmp = wownero.createWowneroTransactionCreationCredentials(
outputs: outputs,
priority: feePriority,
);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
await prepareSendMutex.protect(() async {
awaitPendingTransaction = walletBase!.createTransaction(tmp);
});
} catch (e, s) {
Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s",
level: LogLevel.Warning);
}
2022-11-12 22:04:16 +00:00
2022-12-30 22:15:03 +00:00
PendingWowneroTransaction pendingWowneroTransaction =
await (awaitPendingTransaction!) as PendingWowneroTransaction;
2023-04-05 22:06:31 +00:00
final int realFee = Amount.fromDecimal(
Decimal.parse(pendingWowneroTransaction.feeFormatted),
fractionDigits: coin.decimals,
).raw.toInt();
2022-12-30 22:15:03 +00:00
Map<String, dynamic> txData = {
"pendingWowneroTransaction": pendingWowneroTransaction,
2023-04-05 22:06:31 +00:00
"fee": realFee,
2022-12-30 22:15:03 +00:00
"addresss": address,
2023-04-05 22:06:31 +00:00
"recipientAmt": amount,
2022-12-30 22:15:03 +00:00
};
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Logging.instance.log("prepare send: $txData", level: LogLevel.Info);
return txData;
} else {
throw ArgumentError("Invalid fee rate argument provided!");
}
} catch (e, s) {
Logging.instance.log("Exception rethrown from prepare send(): $e\n$s",
level: LogLevel.Info);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
if (e.toString().contains("Incorrect unlocked balance")) {
throw Exception("Insufficient balance!");
} else if (e is CreationTransactionException) {
throw Exception("Insufficient funds to pay for transaction fee!");
} else {
throw Exception("Transaction failed with error code $e");
}
}
2022-09-27 08:09:31 +00:00
}
@override
Future<void> recoverFromMnemonic({
required String mnemonic,
String? mnemonicPassphrase, // not used at the moment
2022-09-27 08:09:31 +00:00
required int maxUnusedAddressGap,
required int maxNumberOfIndexesToCheck,
required int height,
}) async {
2022-11-08 18:00:10 +00:00
final int seedLength = mnemonic.trim().split(" ").length;
if (!(seedLength == 14 || seedLength == 25)) {
throw Exception("Invalid wownero mnemonic length found: $seedLength");
}
2022-09-27 08:09:31 +00:00
await _prefs.init();
longMutex = true;
final start = DateTime.now();
try {
// check to make sure we aren't overwriting a mnemonic
// this should never fail
if ((await mnemonicString) != null ||
(await this.mnemonicPassphrase) != null) {
2022-09-27 08:09:31 +00:00
longMutex = false;
throw Exception("Attempted to overwrite mnemonic on restore!");
}
2022-12-30 22:15:03 +00:00
await _secureStorage.write(
2022-09-27 08:09:31 +00:00
key: '${_walletId}_mnemonic', value: mnemonic.trim());
await _secureStorage.write(
key: '${_walletId}_mnemonicPassphrase',
value: mnemonicPassphrase ?? "",
);
2022-09-27 08:09:31 +00:00
2022-11-08 18:00:10 +00:00
// extract seed height from 14 word seed
if (seedLength == 14) {
height = getSeedHeightSync(mnemonic.trim());
} else {
// 25 word seed. TODO validate
if (height == 0) {
height = wownero.getHeightByDate(
date: DateTime.now().subtract(const Duration(
days:
2))); // subtract a couple days to ensure we have a buffer for SWB\
}
2022-11-08 18:00:10 +00:00
}
2022-09-27 08:09:31 +00:00
await DB.instance
.put<dynamic>(boxName: walletId, key: "restoreHeight", value: height);
walletService =
wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox);
2022-12-30 22:15:03 +00:00
keysStorage = KeyService(_secureStorage);
2022-09-27 08:09:31 +00:00
WalletInfo walletInfo;
WalletCredentials credentials;
String name = _walletId;
final dirPath =
2022-12-30 22:15:03 +00:00
await _pathForWalletDir(name: name, type: WalletType.wownero);
final path = await _pathForWallet(name: name, type: WalletType.wownero);
2022-09-27 08:09:31 +00:00
credentials = wownero.createWowneroRestoreWalletFromSeedCredentials(
name: name,
height: height,
mnemonic: mnemonic.trim(),
);
try {
walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, WalletType.wownero),
name: name,
type: WalletType.wownero,
isRecovery: false,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: path,
dirPath: dirPath,
// TODO: find out what to put for address
address: '');
credentials.walletInfo = walletInfo;
_walletCreationService = WalletCreationService(
2022-12-30 22:15:03 +00:00
secureStorage: _secureStorage,
2022-09-27 08:09:31 +00:00
walletService: walletService,
keyService: keysStorage,
);
_walletCreationService!.changeWalletType();
// To restore from a seed
final wallet =
await _walletCreationService!.restoreFromSeed(credentials);
walletInfo.address = wallet.walletAddresses.address;
await DB.instance
.add<WalletInfo>(boxName: WalletInfo.boxName, value: walletInfo);
walletBase?.close();
walletBase = wallet as WowneroWalletBase;
2023-01-12 18:54:22 +00:00
await Future.wait([
updateCachedId(walletId),
updateCachedIsFavorite(false),
]);
2022-09-27 08:09:31 +00:00
} catch (e, s) {
2022-12-13 00:17:02 +00:00
//todo: come back to this
2022-09-27 08:09:31 +00:00
debugPrint(e.toString());
debugPrint(s.toString());
}
2022-12-30 22:15:03 +00:00
final node = await _getCurrentNode();
2022-09-27 08:09:31 +00:00
final host = Uri.parse(node.host).host;
await walletBase?.connectToNode(
node: Node(
uri: "$host:${node.port}",
type: WalletType.wownero,
trusted: node.trusted ?? false,
),
);
2022-09-27 08:09:31 +00:00
await walletBase?.rescan(height: credentials.height);
2022-12-30 22:15:03 +00:00
walletBase?.close();
2022-09-27 08:09:31 +00:00
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from recoverFromMnemonic(): $e\n$s",
level: LogLevel.Error);
longMutex = false;
rethrow;
}
longMutex = false;
final end = DateTime.now();
Logging.instance.log(
"$walletName Recovery time: ${end.difference(start).inMilliseconds} millis",
level: LogLevel.Info);
}
@override
2022-12-30 22:15:03 +00:00
Future<void> refresh() async {
if (refreshMutex) {
Logging.instance.log("$walletId $walletName refreshMutex denied",
level: LogLevel.Info);
return;
} else {
refreshMutex = true;
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
walletId,
coin,
),
2022-09-27 08:09:31 +00:00
);
await _refreshTransactions();
await _updateBalance();
2022-12-30 22:15:03 +00:00
await _checkCurrentReceivingAddressesForTransactions();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
if (walletBase?.syncStatus is SyncedSyncStatus) {
refreshMutex = false;
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.synced,
walletId,
coin,
),
);
}
2022-09-27 08:09:31 +00:00
}
@override
2022-12-30 22:15:03 +00:00
Future<bool> testNetworkConnection() async {
return await walletBase?.isConnected() ?? false;
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
bool _isActive = false;
2022-09-27 08:09:31 +00:00
@override
void Function(bool)? get onIsActiveWalletChanged => (isActive) async {
2022-12-30 22:15:03 +00:00
if (_isActive == isActive) {
return;
}
_isActive = isActive;
2022-09-27 08:09:31 +00:00
if (isActive) {
2022-12-30 22:15:03 +00:00
_hasCalledExit = false;
2022-09-27 08:09:31 +00:00
String? password;
try {
password =
await keysStorage?.getWalletPassword(walletName: _walletId);
} catch (e, s) {
2022-12-30 22:15:03 +00:00
throw Exception("Password not found $e, $s");
2022-09-27 08:09:31 +00:00
}
walletBase = (await walletService?.openWallet(_walletId, password!))
as WowneroWalletBase?;
2022-12-30 22:15:03 +00:00
walletBase!.onNewBlock = onNewBlock;
walletBase!.onNewTransaction = onNewTransaction;
walletBase!.syncStatusChanged = syncStatusChanged;
2022-09-27 08:09:31 +00:00
if (!(await walletBase!.isConnected())) {
2022-12-30 22:15:03 +00:00
final node = await _getCurrentNode();
2022-09-27 08:09:31 +00:00
final host = Uri.parse(node.host).host;
await walletBase?.connectToNode(
node: Node(
uri: "$host:${node.port}",
type: WalletType.wownero,
trusted: node.trusted ?? false,
),
);
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
await walletBase?.startSync();
2022-09-27 08:09:31 +00:00
await refresh();
2022-12-30 22:15:03 +00:00
_autoSaveTimer?.cancel();
_autoSaveTimer = Timer.periodic(
2023-01-02 22:43:04 +00:00
const Duration(seconds: 193),
2022-12-30 22:15:03 +00:00
(_) async => await walletBase?.save(),
);
} else {
await exit();
2022-09-27 08:09:31 +00:00
}
};
Future<void> _updateCachedBalance(int sats) async {
await DB.instance.put<dynamic>(
boxName: walletId,
key: "cachedWowneroBalanceSats",
value: sats,
);
}
int _getCachedBalance() =>
DB.instance.get<dynamic>(
boxName: walletId,
key: "cachedWowneroBalanceSats",
) as int? ??
0;
Future<void> _updateBalance() async {
final total = await _totalBalance;
final available = await _availableBalance;
_balance = Balance(
total: total,
spendable: available,
2023-04-05 22:06:31 +00:00
blockedTotal: Amount(
rawValue: BigInt.zero,
fractionDigits: coin.decimals,
),
pendingSpendable: total - available,
);
await updateCachedBalance(_balance!);
}
2023-04-05 22:06:31 +00:00
Future<Amount> get _availableBalance async {
try {
int runningBalance = 0;
for (final entry in walletBase!.balance!.entries) {
runningBalance += entry.value.unlockedBalance;
}
2023-04-05 22:06:31 +00:00
return Amount(
rawValue: BigInt.from(runningBalance),
fractionDigits: coin.decimals,
);
} catch (_) {
2023-04-05 22:06:31 +00:00
return Amount(
rawValue: BigInt.zero,
fractionDigits: coin.decimals,
);
}
}
2023-04-05 22:06:31 +00:00
Future<Amount> get _totalBalance async {
try {
final balanceEntries = walletBase?.balance?.entries;
if (balanceEntries != null) {
int bal = 0;
for (var element in balanceEntries) {
bal = bal + element.value.fullBalance;
}
await _updateCachedBalance(bal);
2023-04-05 22:06:31 +00:00
return Amount(
rawValue: BigInt.from(bal),
fractionDigits: coin.decimals,
);
} else {
final transactions = walletBase!.transactionHistory!.transactions;
int transactionBalance = 0;
for (var tx in transactions!.entries) {
if (tx.value.direction == TransactionDirection.incoming) {
transactionBalance += tx.value.amount!;
} else {
transactionBalance += -tx.value.amount! - tx.value.fee!;
}
2022-12-30 22:15:03 +00:00
}
await _updateCachedBalance(transactionBalance);
2023-04-05 22:06:31 +00:00
return Amount(
rawValue: BigInt.from(transactionBalance),
fractionDigits: coin.decimals,
);
}
} catch (_) {
2023-04-05 22:06:31 +00:00
return Amount(
rawValue: BigInt.from(_getCachedBalance()),
fractionDigits: coin.decimals,
);
2022-12-30 22:15:03 +00:00
}
}
2022-09-27 08:09:31 +00:00
@override
2022-12-30 22:15:03 +00:00
Future<void> updateNode(bool shouldRefresh) async {
final node = await _getCurrentNode();
2022-12-30 22:15:03 +00:00
final host = Uri.parse(node.host).host;
await walletBase?.connectToNode(
node: Node(
uri: "$host:${node.port}",
type: WalletType.wownero,
trusted: node.trusted ?? false,
),
);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// TODO: is this sync call needed? Do we need to notify ui here?
await walletBase?.startSync();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
if (shouldRefresh) {
await refresh();
}
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
// not used for xmr
return;
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
@override
bool validateAddress(String address) => walletBase!.validateAddress(address);
@override
String get walletId => _walletId;
Future<isar_models.Address> _generateAddressForChain(
int chain,
int index,
) async {
2022-12-30 22:15:03 +00:00
//
String address = walletBase!.getTransactionAddress(chain, index);
2023-01-16 22:37:00 +00:00
return isar_models.Address(
walletId: walletId,
derivationIndex: index,
derivationPath: null,
2023-01-16 22:37:00 +00:00
value: address,
publicKey: [],
type: isar_models.AddressType.cryptonote,
subType: chain == 0
? isar_models.AddressSubType.receiving
2023-01-16 22:37:00 +00:00
: isar_models.AddressSubType.change,
);
2022-12-30 22:15:03 +00:00
}
Future<FeeObject> _getFees() async {
// TODO: not use random hard coded values here
return FeeObject(
numberOfBlocksFast: 10,
numberOfBlocksAverage: 15,
numberOfBlocksSlow: 20,
fast: MoneroTransactionPriority.fast.raw!,
medium: MoneroTransactionPriority.regular.raw!,
slow: MoneroTransactionPriority.slow.raw!,
);
}
Future<void> _refreshTransactions() async {
2022-12-30 22:15:03 +00:00
await walletBase!.updateTransactions();
final transactions = walletBase?.transactionHistory!.transactions;
// final cachedTransactions =
// DB.instance.get<dynamic>(boxName: walletId, key: 'latest_tx_model')
// as TransactionData?;
// int latestTxnBlockHeight =
// DB.instance.get<dynamic>(boxName: walletId, key: "storedTxnDataHeight")
// as int? ??
// 0;
//
// final txidsList = DB.instance
// .get<dynamic>(boxName: walletId, key: "cachedTxids") as List? ??
// [];
//
// final Set<String> cachedTxids = Set<String>.from(txidsList);
// TODO: filter to skip cached + confirmed txn processing in next step
// final unconfirmedCachedTransactions =
// cachedTransactions?.getAllTransactions() ?? {};
// unconfirmedCachedTransactions
// .removeWhere((key, value) => value.confirmedStatus);
//
// if (cachedTransactions != null) {
// for (final tx in allTxHashes.toList(growable: false)) {
// final txHeight = tx["height"] as int;
// if (txHeight > 0 &&
// txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) {
2022-09-27 08:09:31 +00:00
// if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) {
// allTxHashes.remove(tx);
// }
// }
// }
// }
2023-02-03 19:22:21 +00:00
final List<Tuple2<isar_models.Transaction, isar_models.Address?>> txnsData =
[];
2022-09-27 08:09:31 +00:00
if (transactions != null) {
for (var tx in transactions.entries) {
2022-12-30 22:15:03 +00:00
// cachedTxids.add(tx.value.id);
// Logging.instance.log(
// "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} "
// "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} "
// "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
// " ${tx.value.keyIndex}",
// level: LogLevel.Info);
// String am = wowneroAmountToString(amount: tx.value.amount!);
// final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2);
// Map<String, dynamic> midSortedTx = {};
// // // create final tx map
// midSortedTx["txid"] = tx.value.id;
// midSortedTx["confirmed_status"] = !tx.value.isPending &&
// tx.value.confirmations != null &&
// tx.value.confirmations! >= MINIMUM_CONFIRMATIONS;
// midSortedTx["confirmations"] = tx.value.confirmations ?? 0;
// midSortedTx["timestamp"] =
// (tx.value.date.millisecondsSinceEpoch ~/ 1000);
// midSortedTx["txType"] =
// tx.value.direction == TransactionDirection.incoming
// ? "Received"
// : "Sent";
// midSortedTx["amount"] = tx.value.amount;
// midSortedTx["worthNow"] = worthNow;
// midSortedTx["worthAtBlockTimestamp"] = worthNow;
// midSortedTx["fees"] = tx.value.fee;
// if (tx.value.direction == TransactionDirection.incoming) {
// final addressInfo = tx.value.additionalInfo;
//
// midSortedTx["address"] = walletBase?.getTransactionAddress(
// addressInfo!['accountIndex'] as int,
// addressInfo['addressIndex'] as int,
// );
// } else {
// midSortedTx["address"] = "";
// }
//
// final int txHeight = tx.value.height ?? 0;
// midSortedTx["height"] = txHeight;
// // if (txHeight >= latestTxnBlockHeight) {
// // latestTxnBlockHeight = txHeight;
// // }
//
// midSortedTx["aliens"] = <dynamic>[];
// midSortedTx["inputSize"] = 0;
// midSortedTx["outputSize"] = 0;
// midSortedTx["inputs"] = <dynamic>[];
// midSortedTx["outputs"] = <dynamic>[];
// midSortedArray.add(midSortedTx);
isar_models.Address? address;
2023-01-16 22:37:00 +00:00
isar_models.TransactionType type;
2022-09-27 08:09:31 +00:00
if (tx.value.direction == TransactionDirection.incoming) {
final addressInfo = tx.value.additionalInfo;
final addressString = walletBase?.getTransactionAddress(
addressInfo!['accountIndex'] as int,
addressInfo['addressIndex'] as int,
);
if (addressString != null) {
2023-01-16 21:04:03 +00:00
address = await db
.getAddresses(walletId)
.filter()
.valueEqualTo(addressString)
.findFirst();
}
2023-01-16 22:37:00 +00:00
type = isar_models.TransactionType.incoming;
2022-09-27 08:09:31 +00:00
} else {
// txn.address = "";
2023-01-16 22:37:00 +00:00
type = isar_models.TransactionType.outgoing;
2022-09-27 08:09:31 +00:00
}
2023-01-16 22:37:00 +00:00
final txn = isar_models.Transaction(
walletId: walletId,
txid: tx.value.id,
timestamp: (tx.value.date.millisecondsSinceEpoch ~/ 1000),
type: type,
subType: isar_models.TransactionSubType.none,
amount: tx.value.amount ?? 0,
amountString: Amount(
rawValue: BigInt.from(tx.value.amount ?? 0),
fractionDigits: coin.decimals,
).toJsonString(),
2023-01-16 22:37:00 +00:00
fee: tx.value.fee ?? 0,
height: tx.value.height,
isCancelled: false,
isLelantus: false,
slateId: null,
otherData: null,
2023-03-31 16:15:42 +00:00
nonce: null,
2023-02-03 19:22:21 +00:00
inputs: [],
outputs: [],
numberOfMessages: null,
2023-01-16 22:37:00 +00:00
);
2023-02-03 19:22:21 +00:00
txnsData.add(Tuple2(txn, address));
2022-09-27 08:09:31 +00:00
}
}
await db.addNewTransactionData(txnsData, walletId);
// quick hack to notify manager to call notifyListeners if
// transactions changed
if (txnsData.isNotEmpty) {
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"Transactions updated/added for: $walletId $walletName ",
walletId,
),
);
}
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
Future<String> _pathForWalletDir({
required String name,
required WalletType type,
}) async {
Directory root = await StackFileSystem.applicationRootDirectory();
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
final prefix = walletTypeToString(type).toLowerCase();
final walletsDir = Directory('${root.path}/wallets');
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
if (!walletDire.existsSync()) {
walletDire.createSync(recursive: true);
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
return walletDire.path;
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Future<String> _pathForWallet({
required String name,
required WalletType type,
}) async =>
await _pathForWalletDir(name: name, type: type)
.then((path) => '$path/$name');
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
Future<NodeModel> _getCurrentNode() async {
return NodeService(secureStorageInterface: _secureStorage)
.getPrimaryNodeFor(coin: coin) ??
DefaultNodes.getNodeFor(coin);
2022-09-27 08:09:31 +00:00
}
2023-01-20 17:48:59 +00:00
void onNewBlock({required int height, required int blocksLeft}) {
2022-12-30 22:15:03 +00:00
//
print("=============================");
print("New Wownero Block! :: $walletName");
print("=============================");
2023-01-20 17:48:59 +00:00
updateCachedChainHeight(height);
_refreshTxDataHelper();
}
bool _txRefreshLock = false;
int _lastCheckedHeight = -1;
int _txCount = 0;
Future<void> _refreshTxDataHelper() async {
if (_txRefreshLock) return;
_txRefreshLock = true;
final syncStatus = walletBase?.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 _refreshTransactions();
2023-01-16 21:04:03 +00:00
final count = await db.getTransactions(walletId).count();
if (count > _txCount) {
_txCount = count;
await _updateBalance();
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"New transaction data found in $walletId $walletName!",
walletId,
),
);
}
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
void onNewTransaction() {
//
print("=============================");
print("New Wownero Transaction! :: $walletName");
print("=============================");
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// call this here?
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"New data found in $walletId $walletName in background!",
walletId,
),
);
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
void syncStatusChanged() async {
final syncStatus = walletBase?.syncStatus;
if (syncStatus != null) {
if (syncStatus.progress() == 1) {
refreshMutex = false;
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
WalletSyncStatus? status;
_isConnected = true;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
if (syncStatus is SyncingSyncStatus) {
final int blocksLeft = syncStatus.blocksLeft;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// ensure at least 1 to prevent math errors
final int height = max(1, syncStatus.height);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
final nodeHeight = height + blocksLeft;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
final percent = height / nodeHeight;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
final highest = max(highestPercentCached, percent);
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// update cached
if (highestPercentCached < percent) {
highestPercentCached = percent;
}
2023-01-12 22:07:15 +00:00
await updateCachedChainHeight(height);
2022-12-30 22:15:03 +00:00
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;
_isConnected = false;
} else if (syncStatus is StartingSyncStatus) {
status = WalletSyncStatus.syncing;
GlobalEventBus.instance.fire(
RefreshPercentChangedEvent(
highestPercentCached,
walletId,
),
);
} else if (syncStatus is FailedSyncStatus) {
status = WalletSyncStatus.unableToSync;
_isConnected = 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;
_isConnected = false;
2022-09-27 08:09:31 +00:00
}
2022-12-30 22:15:03 +00:00
if (status != null) {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
status,
walletId,
coin,
),
);
2022-09-27 08:09:31 +00:00
}
}
}
2022-12-30 22:15:03 +00:00
Future<void> _checkCurrentReceivingAddressesForTransactions() async {
try {
await _checkReceivingAddressForTransactions();
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s",
level: LogLevel.Error);
rethrow;
2022-09-27 08:09:31 +00:00
}
}
2022-12-30 22:15:03 +00:00
Future<void> _checkReceivingAddressForTransactions() async {
2022-09-27 08:09:31 +00:00
try {
2022-12-30 22:15:03 +00:00
int highestIndex = -1;
for (var element
in walletBase!.transactionHistory!.transactions!.entries) {
if (element.value.direction == TransactionDirection.incoming) {
int curAddressIndex =
element.value.additionalInfo!['addressIndex'] as int;
if (curAddressIndex > highestIndex) {
highestIndex = curAddressIndex;
}
}
}
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// Check the new receiving index
final currentReceiving = await _currentReceivingAddress;
2023-01-17 23:19:09 +00:00
final curIndex = currentReceiving?.derivationIndex ?? -1;
2022-12-30 22:15:03 +00:00
if (highestIndex >= curIndex) {
// First increment the receiving index
final newReceivingIndex = curIndex + 1;
2022-09-27 08:09:31 +00:00
2022-12-30 22:15:03 +00:00
// Use new index to derive a new receiving address
final newReceivingAddress =
await _generateAddressForChain(0, newReceivingIndex);
2022-09-27 08:09:31 +00:00
final existing = await db
.getAddresses(walletId)
.filter()
.valueEqualTo(newReceivingAddress.value)
.findFirst();
if (existing == null) {
// Add that new change address
await db.putAddress(newReceivingAddress);
} else {
// we need to update the address
await db.updateAddress(existing, newReceivingAddress);
}
2023-01-23 16:32:53 +00:00
// keep checking until address with no tx history is set as current
await _checkReceivingAddressForTransactions();
2022-12-30 22:15:03 +00:00
}
} on SocketException catch (se, s) {
Logging.instance.log(
"SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s",
level: LogLevel.Error);
return;
2022-09-27 08:09:31 +00:00
} catch (e, s) {
Logging.instance.log(
2022-12-30 22:15:03 +00:00
"Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s",
2022-09-27 08:09:31 +00:00
level: LogLevel.Error);
2022-12-30 22:15:03 +00:00
rethrow;
2022-09-27 08:09:31 +00:00
}
}
2022-12-30 22:15:03 +00:00
double get highestPercentCached =>
DB.instance.get<dynamic>(boxName: walletId, key: "highestPercentCached")
as double? ??
0;
set highestPercentCached(double value) => DB.instance.put<dynamic>(
boxName: walletId,
key: "highestPercentCached",
value: value,
);
2023-01-10 23:50:22 +00:00
@override
2023-01-12 22:07:15 +00:00
int get storedChainHeight => getCachedChainHeight();
@override
Balance get balance => _balance ??= getCachedBalance();
Balance? _balance;
@override
Future<List<isar_models.Transaction>> get transactions =>
2023-01-16 21:04:03 +00:00
db.getTransactions(walletId).sortByTimestampDesc().findAll();
@override
// TODO: implement utxos
Future<List<isar_models.UTXO>> get utxos => throw UnimplementedError();
2022-09-27 08:09:31 +00:00
}