migrate monero_wallet.dart to isar transactions, addresses, and utxos, as well as the cleaner balance model

This commit is contained in:
julian 2023-01-11 21:09:08 -06:00
parent 275e3ab4cb
commit c83ec074de

View file

@ -21,13 +21,13 @@ import 'package:flutter_libmonero/core/key_service.dart';
import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart';
import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/monero/monero.dart';
import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output; import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output;
import 'package:http/http.dart'; import 'package:isar/isar.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/models/paymint/utxo_model.dart';
import 'package:stackwallet/services/coins/coin_service.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/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/refresh_percent_changed_event.dart';
@ -35,7 +35,6 @@ import 'package:stackwallet/services/event_bus/events/global/updated_in_backgrou
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_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/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/price.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
@ -51,10 +50,11 @@ const int MINIMUM_CONFIRMATIONS = 10;
class MoneroWallet extends CoinServiceAPI { class MoneroWallet extends CoinServiceAPI {
final String _walletId; final String _walletId;
final Coin _coin; final Coin _coin;
final PriceAPI _priceAPI;
final SecureStorageInterface _secureStorage; final SecureStorageInterface _secureStorage;
final Prefs _prefs; final Prefs _prefs;
late Isar isar;
String _walletName; String _walletName;
bool _shouldAutoSync = false; bool _shouldAutoSync = false;
bool _isConnected = false; bool _isConnected = false;
@ -68,9 +68,9 @@ class MoneroWallet extends CoinServiceAPI {
WalletCreationService? _walletCreationService; WalletCreationService? _walletCreationService;
Timer? _autoSaveTimer; Timer? _autoSaveTimer;
Future<String>? _currentReceivingAddress; Future<isar_models.Address?> get _currentReceivingAddress =>
isar.addresses.where().sortByDerivationIndexDesc().findFirst();
Future<FeeObject>? _feeObject; Future<FeeObject>? _feeObject;
Future<TransactionData>? _transactionData;
Mutex prepareSendMutex = Mutex(); Mutex prepareSendMutex = Mutex();
Mutex estimateFeeMutex = Mutex(); Mutex estimateFeeMutex = Mutex();
@ -80,15 +80,29 @@ class MoneroWallet extends CoinServiceAPI {
required String walletName, required String walletName,
required Coin coin, required Coin coin,
required SecureStorageInterface secureStorage, required SecureStorageInterface secureStorage,
PriceAPI? priceAPI,
Prefs? prefs, Prefs? prefs,
}) : _walletId = walletId, }) : _walletId = walletId,
_walletName = walletName, _walletName = walletName,
_coin = coin, _coin = coin,
_priceAPI = priceAPI ?? PriceAPI(Client()),
_secureStorage = secureStorage, _secureStorage = secureStorage,
_prefs = prefs ?? Prefs.instance; _prefs = prefs ?? Prefs.instance;
Future<void> _isarInit() async {
isar = await Isar.open(
[
isar_models.TransactionSchema,
isar_models.TransactionNoteSchema,
isar_models.InputSchema,
isar_models.OutputSchema,
isar_models.UTXOSchema,
isar_models.AddressSchema,
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
inspector: false,
name: walletId,
);
}
@override @override
bool get isFavorite { bool get isFavorite {
try { try {
@ -139,23 +153,6 @@ class MoneroWallet extends CoinServiceAPI {
@override @override
set walletName(String newName) => _walletName = newName; set walletName(String newName) => _walletName = newName;
@override
// not used for monero
Future<List<String>> get allOwnAddresses async => [];
@override
Future<Decimal> get availableBalance async {
int runningBalance = 0;
for (final entry in walletBase!.balance!.entries) {
runningBalance += entry.value.unlockedBalance;
}
return Format.satoshisToAmount(runningBalance, coin: coin);
}
@override
// not used
Future<Decimal> get balanceMinusMaxFee => throw UnimplementedError();
@override @override
Coin get coin => _coin; Coin get coin => _coin;
@ -184,8 +181,8 @@ class MoneroWallet extends CoinServiceAPI {
} }
@override @override
Future<String> get currentReceivingAddress => Future<String> get currentReceivingAddress async =>
_currentReceivingAddress ??= _getCurrentAddressForChain(0); (await _currentReceivingAddress)!.value;
@override @override
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async { Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
@ -225,6 +222,7 @@ class MoneroWallet extends CoinServiceAPI {
_autoSaveTimer?.cancel(); _autoSaveTimer?.cancel();
await walletBase?.save(prioritySave: true); await walletBase?.save(prioritySave: true);
walletBase?.close(); walletBase?.close();
await isar.close();
} }
} }
@ -244,22 +242,20 @@ class MoneroWallet extends CoinServiceAPI {
@override @override
Future<bool> generateNewAddress() async { Future<bool> generateNewAddress() async {
try { try {
const String indexKey = "receivingIndex"; final currentReceiving = await _currentReceivingAddress;
// First increment the receiving index
await _incrementAddressIndexForChain(0); final newReceivingIndex = currentReceiving!.derivationIndex + 1;
final newReceivingIndex =
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
// Use new index to derive a new receiving address // Use new index to derive a new receiving address
final newReceivingAddress = final newReceivingAddress = await _generateAddressForChain(
await _generateAddressForChain(0, newReceivingIndex); 0,
newReceivingIndex,
);
// Add that new receiving address to the array of receiving addresses // Add that new receiving address
await _addToAddressesArrayForChain(newReceivingAddress, 0); await isar.writeTxn(() async {
await isar.addresses.put(newReceivingAddress);
// Set the new receiving address that the service });
_currentReceivingAddress = Future(() => newReceivingAddress);
return true; return true;
} catch (e, s) { } catch (e, s) {
@ -290,6 +286,7 @@ class MoneroWallet extends CoinServiceAPI {
keysStorage = KeyService(_secureStorage); keysStorage = KeyService(_secureStorage);
await _prefs.init(); await _prefs.init();
await _isarInit();
// final data = // final data =
// DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model") // DB.instance.get<dynamic>(boxName: walletId, key: "latest_tx_model")
// as TransactionData?; // as TransactionData?;
@ -314,14 +311,14 @@ class MoneroWallet extends CoinServiceAPI {
); );
// Wallet already exists, triggers for a returning user // Wallet already exists, triggers for a returning user
String indexKey = "receivingIndex"; // String indexKey = "receivingIndex";
final curIndex = // final curIndex =
await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int; // await DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
// Use new index to derive a new receiving address // // Use new index to derive a new receiving address
final newReceivingAddress = await _generateAddressForChain(0, curIndex); // final newReceivingAddress = await _generateAddressForChain(0, curIndex);
Logging.instance.log("xmr address in init existing: $newReceivingAddress", // Logging.instance.log("xmr address in init existing: $newReceivingAddress",
level: LogLevel.Info); // level: LogLevel.Info);
_currentReceivingAddress = Future(() => newReceivingAddress); // _currentReceivingAddress = Future(() => newReceivingAddress);
} }
@override @override
@ -424,18 +421,12 @@ class MoneroWallet extends CoinServiceAPI {
// Generate and add addresses to relevant arrays // Generate and add addresses to relevant arrays
final initialReceivingAddress = await _generateAddressForChain(0, 0); final initialReceivingAddress = await _generateAddressForChain(0, 0);
// final initialChangeAddress = await _generateAddressForChain(1, 0); // final initialChangeAddress = await _generateAddressForChain(1, 0);
await _isarInit();
await _addToAddressesArrayForChain(initialReceivingAddress, 0); await isar.writeTxn(() async {
// await _addToAddressesArrayForChain(initialChangeAddress, 1); await isar.addresses.put(initialReceivingAddress);
});
await DB.instance.put<dynamic>(
boxName: walletId,
key: 'receivingAddresses',
value: [initialReceivingAddress]);
await DB.instance
.put<dynamic>(boxName: walletId, key: "receivingIndex", value: 0);
_currentReceivingAddress = Future(() => initialReceivingAddress);
walletBase?.close(); walletBase?.close();
Logging.instance Logging.instance
.log("initializeNew for $walletName $walletId", level: LogLevel.Info); .log("initializeNew for $walletName $walletId", level: LogLevel.Info);
@ -462,10 +453,6 @@ class MoneroWallet extends CoinServiceAPI {
return data; return data;
} }
@override
// not used in xmr
Future<Decimal> get pendingBalance => throw UnimplementedError();
@override @override
Future<Map<String, dynamic>> prepareSend({ Future<Map<String, dynamic>> prepareSend({
required String address, required String address,
@ -493,16 +480,15 @@ class MoneroWallet extends CoinServiceAPI {
try { try {
// check for send all // check for send all
bool isSendAll = false; bool isSendAll = false;
final balance = await availableBalance; final balance = await _availableBalance;
final satInDecimal = if (satoshiAmount == balance) {
Format.satoshisToAmount(satoshiAmount, coin: coin);
if (satInDecimal == balance) {
isSendAll = true; isSendAll = true;
} }
Logging.instance Logging.instance
.log("$toAddress $satoshiAmount $args", level: LogLevel.Info); .log("$toAddress $satoshiAmount $args", level: LogLevel.Info);
String amountToSend = satInDecimal String amountToSend =
.toStringAsFixed(Constants.decimalPlacesForCoin(coin)); Format.satoshisToAmount(satoshiAmount, coin: coin)
.toStringAsFixed(Constants.decimalPlacesForCoin(coin));
Logging.instance Logging.instance
.log("$satoshiAmount $amountToSend", level: LogLevel.Info); .log("$satoshiAmount $amountToSend", level: LogLevel.Info);
@ -702,22 +688,10 @@ class MoneroWallet extends CoinServiceAPI {
), ),
); );
final newTxData = await _fetchTransactionData(); await _refreshTransactions();
_transactionData = Future(() => newTxData); await _updateBalance();
await _checkCurrentReceivingAddressesForTransactions(); await _checkCurrentReceivingAddressesForTransactions();
String indexKey = "receivingIndex";
final curIndex =
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
// Use new index to derive a new receiving address
try {
final newReceivingAddress = await _generateAddressForChain(0, curIndex);
_currentReceivingAddress = Future(() => newReceivingAddress);
} catch (e, s) {
Logging.instance.log(
"Failed to call _generateAddressForChain(0, $curIndex): $e\n$s",
level: LogLevel.Error);
}
if (walletBase?.syncStatus is SyncedSyncStatus) { if (walletBase?.syncStatus is SyncedSyncStatus) {
refreshMutex = false; refreshMutex = false;
@ -731,16 +705,6 @@ class MoneroWallet extends CoinServiceAPI {
} }
} }
@override
Future<String> send({
required String toAddress,
required int amount,
Map<String, String> args = const {},
}) {
// not used for xmr
throw UnimplementedError();
}
@override @override
Future<bool> testNetworkConnection() async { Future<bool> testNetworkConnection() async {
return await walletBase?.isConnected() ?? false; return await walletBase?.isConnected() ?? false;
@ -807,8 +771,31 @@ class MoneroWallet extends CoinServiceAPI {
) as int? ?? ) as int? ??
0; 0;
@override Future<void> _updateBalance() async {
Future<Decimal> get totalBalance async { final total = await _totalBalance;
final available = await _availableBalance;
_balance = Balance(
coin: coin,
total: total,
spendable: available,
blockedTotal: 0,
pendingSpendable: total - available,
);
}
Future<int> get _availableBalance async {
try {
int runningBalance = 0;
for (final entry in walletBase!.balance!.entries) {
runningBalance += entry.value.unlockedBalance;
}
return runningBalance;
} catch (_) {
return 0;
}
}
Future<int> get _totalBalance async {
try { try {
final balanceEntries = walletBase?.balance?.entries; final balanceEntries = walletBase?.balance?.entries;
if (balanceEntries != null) { if (balanceEntries != null) {
@ -817,7 +804,7 @@ class MoneroWallet extends CoinServiceAPI {
bal = bal + element.value.fullBalance; bal = bal + element.value.fullBalance;
} }
await _updateCachedBalance(bal); await _updateCachedBalance(bal);
return Format.satoshisToAmount(bal, coin: coin); return bal;
} else { } else {
final transactions = walletBase!.transactionHistory!.transactions; final transactions = walletBase!.transactionHistory!.transactions;
int transactionBalance = 0; int transactionBalance = 0;
@ -830,21 +817,13 @@ class MoneroWallet extends CoinServiceAPI {
} }
await _updateCachedBalance(transactionBalance); await _updateCachedBalance(transactionBalance);
return Format.satoshisToAmount(transactionBalance, coin: coin); return transactionBalance;
} }
} catch (_) { } catch (_) {
return Format.satoshisToAmount(_getCachedBalance(), coin: coin); return _getCachedBalance();
} }
} }
@override
Future<TransactionData> get transactionData =>
_transactionData ??= _fetchTransactionData();
@override
// not used for xmr
Future<List<UtxoObject>> get unspentOutputs => throw UnimplementedError();
@override @override
Future<void> updateNode(bool shouldRefresh) async { Future<void> updateNode(bool shouldRefresh) async {
final node = await _getCurrentNode(); final node = await _getCurrentNode();
@ -873,66 +852,21 @@ class MoneroWallet extends CoinServiceAPI {
@override @override
String get walletId => _walletId; String get walletId => _walletId;
/// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] Future<isar_models.Address> _generateAddressForChain(
/// and int chain,
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! int index,
Future<String> _getCurrentAddressForChain(int chain) async { ) async {
// Here, we assume that chain == 1 if it isn't 0
String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses";
final internalChainArray = (DB.instance
.get<dynamic>(boxName: walletId, key: arrayKey)) as List<dynamic>;
return internalChainArray.last as String;
}
/// Increases the index for either the internal or external chain, depending on [chain].
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value!
Future<void> _incrementAddressIndexForChain(int chain) async {
// Here we assume chain == 1 if it isn't 0
String indexKey = chain == 0 ? "receivingIndex" : "changeIndex";
final newIndex =
(DB.instance.get<dynamic>(boxName: walletId, key: indexKey)) + 1;
await DB.instance
.put<dynamic>(boxName: walletId, key: indexKey, value: newIndex);
}
Future<String> _generateAddressForChain(int chain, int index) async {
// //
String address = walletBase!.getTransactionAddress(chain, index); String address = walletBase!.getTransactionAddress(chain, index);
return address; return isar_models.Address()
} ..derivationIndex = index
..value = address
/// Adds [address] to the relevant chain's address array, which is determined by [chain]. ..publicKey = []
/// [address] - Expects a standard native segwit address ..type = isar_models.AddressType.cryptonote
/// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! ..subType = chain == 0
Future<void> _addToAddressesArrayForChain(String address, int chain) async { ? isar_models.AddressSubType.receiving
String chainArray = ''; : isar_models.AddressSubType.change;
if (chain == 0) {
chainArray = 'receivingAddresses';
} else {
chainArray = 'changeAddresses';
}
final addressArray =
DB.instance.get<dynamic>(boxName: walletId, key: chainArray);
if (addressArray == null) {
Logging.instance.log(
'Attempting to add the following to $chainArray array for chain $chain:${[
address
]}',
level: LogLevel.Info);
await DB.instance
.put<dynamic>(boxName: walletId, key: chainArray, value: [address]);
} else {
// Make a deep copy of the existing list
final List<String> newArray = [];
addressArray
.forEach((dynamic _address) => newArray.add(_address as String));
newArray.add(address); // Add the address passed into the method
await DB.instance
.put<dynamic>(boxName: walletId, key: chainArray, value: newArray);
}
} }
Future<FeeObject> _getFees() async { Future<FeeObject> _getFees() async {
@ -947,7 +881,7 @@ class MoneroWallet extends CoinServiceAPI {
); );
} }
Future<TransactionData> _fetchTransactionData() async { Future<void> _refreshTransactions() async {
await walletBase!.updateTransactions(); await walletBase!.updateTransactions();
final transactions = walletBase?.transactionHistory!.transactions; final transactions = walletBase?.transactionHistory!.transactions;
@ -965,123 +899,112 @@ class MoneroWallet extends CoinServiceAPI {
// //
// final Set<String> cachedTxids = Set<String>.from(txidsList); // final Set<String> cachedTxids = Set<String>.from(txidsList);
// sort thing stuff final List<isar_models.Transaction> txns = [];
// change to get Monero price
final priceData =
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final List<Map<String, dynamic>> midSortedArray = [];
if (transactions != null) { if (transactions != null) {
for (var tx in transactions.entries) { for (var tx in transactions.entries) {
// cachedTxids.add(tx.value.id); // cachedTxids.add(tx.value.id);
Logging.instance.log( // Logging.instance.log(
"${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " // "${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.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.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}"
" ${tx.value.keyIndex}", // " ${tx.value.keyIndex}",
level: LogLevel.Info); // level: LogLevel.Info);
final worthNow = (currentPrice *
Format.satoshisToAmount( final int txHeight = tx.value.height ?? 0;
tx.value.amount!, final txn = isar_models.Transaction();
coin: coin, txn.txid = tx.value.id;
)) txn.timestamp = (tx.value.date.millisecondsSinceEpoch ~/ 1000);
.toStringAsFixed(2);
Map<String, dynamic> midSortedTx = {};
// // create final tx map
midSortedTx["txid"] = tx.value.id;
midSortedTx["confirmed_status"] = !tx.value.isPending &&
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) { if (tx.value.direction == TransactionDirection.incoming) {
final addressInfo = tx.value.additionalInfo; final addressInfo = tx.value.additionalInfo;
midSortedTx["address"] = walletBase?.getTransactionAddress( txn.address = walletBase?.getTransactionAddress(
addressInfo!['accountIndex'] as int, addressInfo!['accountIndex'] as int,
addressInfo['addressIndex'] as int, addressInfo['addressIndex'] as int,
); ) ??
"";
txn.type = isar_models.TransactionType.incoming;
} else { } else {
midSortedTx["address"] = ""; txn.address = "";
txn.type = isar_models.TransactionType.outgoing;
} }
final int txHeight = tx.value.height ?? 0; txn.amount = tx.value.amount ?? 0;
midSortedTx["height"] = txHeight;
// if (txHeight >= latestTxnBlockHeight) {
// latestTxnBlockHeight = txHeight;
// }
midSortedTx["aliens"] = <dynamic>[]; // TODO: other subtypes
midSortedTx["inputSize"] = 0; txn.subType = isar_models.TransactionSubType.none;
midSortedTx["outputSize"] = 0;
midSortedTx["inputs"] = <dynamic>[]; txn.fee = tx.value.fee ?? 0;
midSortedTx["outputs"] = <dynamic>[];
midSortedArray.add(midSortedTx); txn.height = txHeight;
txn.isCancelled = false;
txn.slateId = null;
txn.otherData = null;
txns.add(txn);
} }
} }
// sort by date ---- await isar.writeTxn(() async {
midSortedArray await isar.transactions.putAll(txns);
.sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); });
Logging.instance.log(midSortedArray, level: LogLevel.Info);
// buildDateTimeChunks // // sort by date ----
final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]}; // midSortedArray
final dateArray = <dynamic>[]; // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int));
// Logging.instance.log(midSortedArray, level: LogLevel.Info);
for (int i = 0; i < midSortedArray.length; i++) { //
final txObject = midSortedArray[i]; // // buildDateTimeChunks
final date = extractDateFromTimestamp(txObject["timestamp"] as int); // final Map<String, dynamic> result = {"dateTimeChunks": <dynamic>[]};
final txTimeArray = [txObject["timestamp"], date]; // final dateArray = <dynamic>[];
//
if (dateArray.contains(txTimeArray[1])) { // for (int i = 0; i < midSortedArray.length; i++) {
result["dateTimeChunks"].forEach((dynamic chunk) { // final txObject = midSortedArray[i];
if (extractDateFromTimestamp(chunk["timestamp"] as int) == // final date = extractDateFromTimestamp(txObject["timestamp"] as int);
txTimeArray[1]) { // final txTimeArray = [txObject["timestamp"], date];
if (chunk["transactions"] == null) { //
chunk["transactions"] = <Map<String, dynamic>>[]; // if (dateArray.contains(txTimeArray[1])) {
} // result["dateTimeChunks"].forEach((dynamic chunk) {
chunk["transactions"].add(txObject); // if (extractDateFromTimestamp(chunk["timestamp"] as int) ==
} // txTimeArray[1]) {
}); // if (chunk["transactions"] == null) {
} else { // chunk["transactions"] = <Map<String, dynamic>>[];
dateArray.add(txTimeArray[1]); // }
final chunk = { // chunk["transactions"].add(txObject);
"timestamp": txTimeArray[0], // }
"transactions": [txObject], // });
}; // } else {
result["dateTimeChunks"].add(chunk); // dateArray.add(txTimeArray[1]);
} // final chunk = {
} // "timestamp": txTimeArray[0],
// "transactions": [txObject],
// final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; // };
final Map<String, Transaction> transactionsMap = {}; // result["dateTimeChunks"].add(chunk);
transactionsMap // }
.addAll(TransactionData.fromJson(result).getAllTransactions()); // }
//
final txModel = TransactionData.fromMap(transactionsMap); // // final transactionsMap = cachedTransactions?.getAllTransactions() ?? {};
// final Map<String, Transaction> transactionsMap = {};
// await DB.instance.put<dynamic>( // transactionsMap
// boxName: walletId, // .addAll(TransactionData.fromJson(result).getAllTransactions());
// key: 'storedTxnDataHeight', //
// value: latestTxnBlockHeight); // final txModel = TransactionData.fromMap(transactionsMap);
// await DB.instance.put<dynamic>( //
// boxName: walletId, key: 'latest_tx_model', value: txModel); // // await DB.instance.put<dynamic>(
// await DB.instance.put<dynamic>( // // boxName: walletId,
// boxName: walletId, // // key: 'storedTxnDataHeight',
// key: 'cachedTxids', // // value: latestTxnBlockHeight);
// value: cachedTxids.toList(growable: false)); // // await DB.instance.put<dynamic>(
// // boxName: walletId, key: 'latest_tx_model', value: txModel);
return txModel; // // await DB.instance.put<dynamic>(
// // boxName: walletId,
// // key: 'cachedTxids',
// // value: cachedTxids.toList(growable: false));
//
// return txModel;
} }
Future<String> _pathForWalletDir({ Future<String> _pathForWalletDir({
@ -1164,12 +1087,12 @@ class MoneroWallet extends CoinServiceAPI {
} }
Future<void> _refreshTxData() async { Future<void> _refreshTxData() async {
final txnData = await _fetchTransactionData(); await _refreshTransactions();
final count = txnData.getAllTransactions().length; final count = await isar.transactions.count();
if (count > _txCount) { if (count > _txCount) {
_txCount = count; _txCount = count;
_transactionData = Future(() => txnData); await _updateBalance();
GlobalEventBus.instance.fire( GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent( UpdatedInBackgroundEvent(
"New transaction data found in $walletId $walletName!", "New transaction data found in $walletId $walletName!",
@ -1293,23 +1216,21 @@ class MoneroWallet extends CoinServiceAPI {
} }
// Check the new receiving index // Check the new receiving index
String indexKey = "receivingIndex"; final currentReceiving = await _currentReceivingAddress;
final curIndex = final curIndex = currentReceiving!.derivationIndex;
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
if (highestIndex >= curIndex) { if (highestIndex >= curIndex) {
// First increment the receiving index // First increment the receiving index
await _incrementAddressIndexForChain(0); final newReceivingIndex = curIndex + 1;
final newReceivingIndex =
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
// Use new index to derive a new receiving address // Use new index to derive a new receiving address
final newReceivingAddress = final newReceivingAddress =
await _generateAddressForChain(0, newReceivingIndex); await _generateAddressForChain(0, newReceivingIndex);
// Add that new receiving address to the array of receiving addresses // Add that new receiving address
await _addToAddressesArrayForChain(newReceivingAddress, 0); await isar.writeTxn(() async {
await isar.addresses.put(newReceivingAddress);
_currentReceivingAddress = Future(() => newReceivingAddress); });
} }
} on SocketException catch (se, s) { } on SocketException catch (se, s) {
Logging.instance.log( Logging.instance.log(
@ -1338,4 +1259,16 @@ class MoneroWallet extends CoinServiceAPI {
@override @override
// TODO: implement storedChainHeight // TODO: implement storedChainHeight
int get storedChainHeight => throw UnimplementedError(); int get storedChainHeight => throw UnimplementedError();
@override
Balance get balance => _balance!;
Balance? _balance;
@override
Future<List<isar_models.Transaction>> get transactions =>
isar.transactions.where().sortByTimestampDesc().findAll();
@override
// TODO: implement utxos
Future<List<isar_models.UTXO>> get utxos => throw UnimplementedError();
} }