mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 05:04:35 +00:00
migrate monero_wallet.dart to isar transactions, addresses, and utxos, as well as the cleaner balance model
This commit is contained in:
parent
275e3ab4cb
commit
c83ec074de
1 changed files with 212 additions and 279 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue