stack_wallet/lib/db/isar/main_db.dart

571 lines
17 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
*
*/
2023-03-15 18:01:10 +00:00
import 'package:decimal/decimal.dart';
import 'package:flutter_native_splash/cli_commands.dart';
2023-01-16 21:04:03 +00:00
import 'package:isar/isar.dart';
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
import 'package:stackwallet/models/isar/models/block_explorer.dart';
2023-05-14 15:06:47 +00:00
import 'package:stackwallet/models/isar/models/contact_entry.dart';
2023-01-16 21:04:03 +00:00
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/ordinal.dart';
import 'package:stackwallet/models/isar/stack_theme.dart';
2023-04-06 21:24:56 +00:00
import 'package:stackwallet/utilities/amount/amount.dart';
2023-03-15 18:01:10 +00:00
import 'package:stackwallet/utilities/enums/coin_enum.dart';
2023-01-16 21:04:03 +00:00
import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:stackwallet/wallets/isar_models/wallet_info.dart';
import 'package:tuple/tuple.dart';
2023-01-16 21:04:03 +00:00
2023-03-22 15:25:21 +00:00
part '../queries/queries.dart';
2023-03-15 18:01:10 +00:00
2023-05-26 19:25:59 +00:00
/*
* This file includes the functions that are used to interact with the main database.
* To add a new function, add it in the class MainDB.
*/
2023-01-16 21:04:03 +00:00
class MainDB {
MainDB._();
static MainDB? _instance;
static MainDB get instance => _instance ??= MainDB._();
Isar? _isar;
Isar get isar => _isar!;
Future<bool> initMainDB({Isar? mock}) async {
2023-01-20 16:22:18 +00:00
if (mock != null) {
_isar = mock;
return true;
}
2023-01-16 21:04:03 +00:00
if (_isar != null && isar.isOpen) return false;
_isar = await Isar.open(
[
TransactionSchema,
TransactionNoteSchema,
UTXOSchema,
AddressSchema,
2023-02-03 19:22:21 +00:00
AddressLabelSchema,
2023-03-02 20:08:29 +00:00
EthContractSchema,
TransactionBlockExplorerSchema,
StackThemeSchema,
2023-05-14 15:06:47 +00:00
ContactEntrySchema,
OrdinalSchema,
2023-07-24 15:06:48 +00:00
LelantusCoinSchema,
WalletInfoSchema,
2023-01-16 21:04:03 +00:00
],
directory: (await StackFileSystem.applicationIsarDirectory()).path,
// inspector: kDebugMode,
inspector: false,
2023-01-16 21:04:03 +00:00
name: "wallet_data",
2023-02-11 00:41:21 +00:00
maxSizeMiB: 512,
2023-01-16 21:04:03 +00:00
);
return true;
}
Future<void> putWalletInfo(WalletInfo walletInfo) async {
try {
await isar.writeTxn(() async {
await isar.walletInfo.put(walletInfo);
});
} catch (e) {
throw MainDBException("failed putWalletInfo()", e);
}
}
Future<void> updateWalletInfo(WalletInfo walletInfo) async {
try {
await isar.writeTxn(() async {
final info = await isar.walletInfo
.where()
.walletIdEqualTo(walletInfo.walletId)
.findFirst();
if (info == null) {
throw Exception("updateWalletInfo() called with new WalletInfo."
" Use putWalletInfo()");
}
await isar.walletInfo.deleteByWalletId(walletInfo.walletId);
await isar.walletInfo.put(walletInfo);
});
} catch (e) {
throw MainDBException("failed updateWalletInfo()", e);
}
}
// TODO refactor this elsewhere as it not only interacts with MainDB's isar
Future<void> deleteWallet({required String walletId}) async {
//
}
2023-05-14 15:06:47 +00:00
// contact entries
List<ContactEntry> getContactEntries() {
2023-07-04 23:40:06 +00:00
return isar.contactEntrys.where().sortByName().findAllSync();
2023-05-14 15:06:47 +00:00
}
Future<bool> deleteContactEntry({required String id}) {
try {
return isar.writeTxn(() async {
await isar.contactEntrys.deleteByCustomId(id);
return true;
});
} catch (e) {
throw MainDBException("failed deleteContactEntry: $id", e);
}
}
Future<bool> isContactEntryExists({required String id}) async {
2023-06-05 22:25:54 +00:00
return isar.contactEntrys
.where()
.customIdEqualTo(id)
.count()
.then((value) => value > 0);
2023-05-14 15:06:47 +00:00
}
ContactEntry? getContactEntry({required String id}) {
return isar.contactEntrys.where().customIdEqualTo(id).findFirstSync();
}
Future<bool> putContactEntry({required ContactEntry contactEntry}) async {
try {
return await isar.writeTxn(() async {
await isar.contactEntrys.put(contactEntry);
return true;
});
} catch (e) {
throw MainDBException("failed putContactEntry: $contactEntry", e);
}
}
// tx block explorers
TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) {
2023-06-05 22:25:54 +00:00
return isar.transactionBlockExplorers
.where()
.tickerEqualTo(coin.ticker)
.findFirstSync();
}
2023-06-05 22:25:54 +00:00
Future<int> putTransactionBlockExplorer(
TransactionBlockExplorer explorer) async {
try {
return await isar.writeTxn(() async {
return await isar.transactionBlockExplorers.put(explorer);
});
} catch (e) {
throw MainDBException("failed putTransactionBlockExplorer: $explorer", e);
}
}
2023-01-16 21:04:03 +00:00
// addresses
2023-06-05 22:25:54 +00:00
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
String walletId) =>
2023-01-16 21:04:03 +00:00
isar.addresses.where().walletIdEqualTo(walletId);
Future<int> putAddress(Address address) async {
try {
return await isar.writeTxn(() async {
return await isar.addresses.put(address);
2023-01-16 21:04:03 +00:00
});
} catch (e) {
throw MainDBException("failed putAddress: $address", e);
}
}
2023-01-16 21:04:03 +00:00
Future<List<int>> putAddresses(List<Address> addresses) async {
try {
return await isar.writeTxn(() async {
return await isar.addresses.putAll(addresses);
2023-01-16 21:04:03 +00:00
});
} catch (e) {
throw MainDBException("failed putAddresses: $addresses", e);
}
}
2023-01-16 21:04:03 +00:00
Future<List<int>> updateOrPutAddresses(List<Address> addresses) async {
try {
List<int> ids = [];
await isar.writeTxn(() async {
for (final address in addresses) {
2023-06-05 22:25:54 +00:00
final storedAddress = await isar.addresses
.getByValueWalletId(address.value, address.walletId);
int id;
if (storedAddress == null) {
id = await isar.addresses.put(address);
} else {
address.id = storedAddress.id;
await storedAddress.transactions.load();
final txns = storedAddress.transactions.toList();
await isar.addresses.delete(storedAddress.id);
id = await isar.addresses.put(address);
address.transactions.addAll(txns);
await address.transactions.save();
}
ids.add(id);
}
});
return ids;
} catch (e) {
throw MainDBException("failed updateOrPutAddresses: $addresses", e);
}
}
2023-01-27 17:19:44 +00:00
Future<Address?> getAddress(String walletId, String address) async {
return isar.addresses.getByValueWalletId(address, walletId);
}
Future<int> updateAddress(Address oldAddress, Address newAddress) async {
try {
return await isar.writeTxn(() async {
newAddress.id = oldAddress.id;
await oldAddress.transactions.load();
final txns = oldAddress.transactions.toList();
await isar.addresses.delete(oldAddress.id);
final id = await isar.addresses.put(newAddress);
newAddress.transactions.addAll(txns);
await newAddress.transactions.save();
return id;
});
} catch (e) {
2023-06-05 22:25:54 +00:00
throw MainDBException(
"failed updateAddress: from=$oldAddress to=$newAddress", e);
}
}
2023-01-16 21:04:03 +00:00
// transactions
2023-06-05 22:25:54 +00:00
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
String walletId) =>
2023-01-16 21:04:03 +00:00
isar.transactions.where().walletIdEqualTo(walletId);
Future<int> putTransaction(Transaction transaction) async {
try {
return await isar.writeTxn(() async {
return await isar.transactions.put(transaction);
2023-01-16 21:04:03 +00:00
});
} catch (e) {
throw MainDBException("failed putTransaction: $transaction", e);
}
}
2023-01-16 21:04:03 +00:00
Future<List<int>> putTransactions(List<Transaction> transactions) async {
try {
return await isar.writeTxn(() async {
return await isar.transactions.putAll(transactions);
2023-01-16 21:04:03 +00:00
});
} catch (e) {
throw MainDBException("failed putTransactions: $transactions", e);
}
}
2023-01-16 21:04:03 +00:00
Future<Transaction?> getTransaction(String walletId, String txid) async {
return isar.transactions.getByTxidWalletId(txid, walletId);
}
2023-02-03 19:22:21 +00:00
Stream<Transaction?> watchTransaction({
required Id id,
bool fireImmediately = false,
}) {
return isar.transactions.watchObject(id, fireImmediately: fireImmediately);
}
2023-01-16 21:04:03 +00:00
// utxos
2023-06-05 22:25:54 +00:00
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
isar.utxos.where().walletIdEqualTo(walletId);
2023-01-16 21:04:03 +00:00
Future<void> putUTXO(UTXO utxo) => isar.writeTxn(() async {
await isar.utxos.put(utxo);
});
Future<void> putUTXOs(List<UTXO> utxos) => isar.writeTxn(() async {
await isar.utxos.putAll(utxos);
});
Future<bool> updateUTXOs(String walletId, List<UTXO> utxos) async {
bool newUTXO = false;
2023-03-07 15:00:00 +00:00
await isar.writeTxn(() async {
final set = utxos.toSet();
for (final utxo in utxos) {
// check if utxo exists in db and update accordingly
2023-06-05 22:25:54 +00:00
final storedUtxo = await isar.utxos
.where()
.txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout)
.findFirst();
2023-03-07 15:00:00 +00:00
if (storedUtxo != null) {
// update
set.remove(utxo);
2023-03-07 17:11:57 +00:00
set.add(
storedUtxo.copyWith(
value: utxo.value,
address: utxo.address,
blockTime: utxo.blockTime,
blockHeight: utxo.blockHeight,
2023-03-07 20:54:39 +00:00
blockHash: utxo.blockHash,
2023-03-07 17:11:57 +00:00
),
);
} else {
newUTXO = true;
2023-03-07 15:00:00 +00:00
}
}
await isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
await isar.utxos.putAll(set.toList());
});
return newUTXO;
2023-03-07 15:00:00 +00:00
}
Stream<UTXO?> watchUTXO({
required Id id,
bool fireImmediately = false,
}) {
return isar.utxos.watchObject(id, fireImmediately: fireImmediately);
}
2023-01-16 21:04:03 +00:00
// transaction notes
2023-06-05 22:25:54 +00:00
QueryBuilder<TransactionNote, TransactionNote, QAfterWhereClause>
getTransactionNotes(String walletId) =>
isar.transactionNotes.where().walletIdEqualTo(walletId);
2023-01-16 21:04:03 +00:00
2023-06-05 22:25:54 +00:00
Future<void> putTransactionNote(TransactionNote transactionNote) =>
isar.writeTxn(() async {
2023-01-16 21:04:03 +00:00
await isar.transactionNotes.put(transactionNote);
});
2023-06-05 22:25:54 +00:00
Future<void> putTransactionNotes(List<TransactionNote> transactionNotes) =>
isar.writeTxn(() async {
2023-01-16 21:04:03 +00:00
await isar.transactionNotes.putAll(transactionNotes);
});
2023-06-05 22:25:54 +00:00
Future<TransactionNote?> getTransactionNote(
String walletId, String txid) async {
2023-02-03 19:22:21 +00:00
return isar.transactionNotes.getByTxidWalletId(
txid,
walletId,
);
}
Stream<TransactionNote?> watchTransactionNote({
required Id id,
bool fireImmediately = false,
}) {
2023-06-05 22:25:54 +00:00
return isar.transactionNotes
.watchObject(id, fireImmediately: fireImmediately);
2023-02-03 19:22:21 +00:00
}
// address labels
2023-06-05 22:25:54 +00:00
QueryBuilder<AddressLabel, AddressLabel, QAfterWhereClause> getAddressLabels(
String walletId) =>
2023-02-03 19:22:21 +00:00
isar.addressLabels.where().walletIdEqualTo(walletId);
2023-06-05 22:25:54 +00:00
Future<int> putAddressLabel(AddressLabel addressLabel) =>
isar.writeTxn(() async {
2023-02-03 19:22:21 +00:00
return await isar.addressLabels.put(addressLabel);
});
int putAddressLabelSync(AddressLabel addressLabel) => isar.writeTxnSync(() {
return isar.addressLabels.putSync(addressLabel);
});
2023-06-05 22:25:54 +00:00
Future<void> putAddressLabels(List<AddressLabel> addressLabels) =>
isar.writeTxn(() async {
2023-02-03 19:22:21 +00:00
await isar.addressLabels.putAll(addressLabels);
});
2023-06-05 22:25:54 +00:00
Future<AddressLabel?> getAddressLabel(
String walletId, String addressString) async {
2023-02-03 19:22:21 +00:00
return isar.addressLabels.getByAddressStringWalletId(
addressString,
walletId,
);
}
AddressLabel? getAddressLabelSync(String walletId, String addressString) {
return isar.addressLabels.getByAddressStringWalletIdSync(
addressString,
walletId,
);
}
Stream<AddressLabel?> watchAddressLabel({
required Id id,
bool fireImmediately = false,
}) {
return isar.addressLabels.watchObject(id, fireImmediately: fireImmediately);
}
Future<int> updateAddressLabel(AddressLabel addressLabel) async {
try {
return await isar.writeTxn(() async {
return await isar.addressLabels.put(addressLabel);
});
} catch (e) {
throw MainDBException("failed updateAddressLabel", e);
}
}
2023-01-16 21:04:03 +00:00
//
Future<void> deleteWalletBlockchainData(String walletId) async {
2023-01-16 23:17:35 +00:00
final transactionCount = await getTransactions(walletId).count();
final addressCount = await getAddresses(walletId).count();
final utxoCount = await getUTXOs(walletId).count();
2023-07-25 18:07:44 +00:00
final lelantusCoinCount =
await isar.lelantusCoins.where().walletIdEqualTo(walletId).count();
2023-01-16 23:17:35 +00:00
2023-01-16 21:04:03 +00:00
await isar.writeTxn(() async {
const paginateLimit = 50;
// transactions
2023-01-16 23:17:35 +00:00
for (int i = 0; i < transactionCount; i += paginateLimit) {
2023-06-05 22:25:54 +00:00
final txnIds = await getTransactions(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.transactions.deleteAll(txnIds);
2023-01-16 21:04:03 +00:00
}
// addresses
2023-01-16 23:17:35 +00:00
for (int i = 0; i < addressCount; i += paginateLimit) {
2023-06-05 22:25:54 +00:00
final addressIds = await getAddresses(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.addresses.deleteAll(addressIds);
2023-01-16 21:04:03 +00:00
}
// utxos
2023-01-16 23:17:35 +00:00
for (int i = 0; i < utxoCount; i += paginateLimit) {
2023-06-05 22:25:54 +00:00
final utxoIds = await getUTXOs(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.utxos.deleteAll(utxoIds);
}
2023-07-25 17:18:21 +00:00
// lelantusCoins
for (int i = 0; i < lelantusCoinCount; i += paginateLimit) {
2023-07-25 18:07:44 +00:00
final lelantusCoinIds = await isar.lelantusCoins
.where()
.walletIdEqualTo(walletId)
2023-07-25 17:18:21 +00:00
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.lelantusCoins.deleteAll(lelantusCoinIds);
}
});
}
Future<void> deleteAddressLabels(String walletId) async {
final addressLabelCount = await getAddressLabels(walletId).count();
await isar.writeTxn(() async {
const paginateLimit = 50;
for (int i = 0; i < addressLabelCount; i += paginateLimit) {
2023-06-05 22:25:54 +00:00
final labelIds = await getAddressLabels(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.addressLabels.deleteAll(labelIds);
}
});
}
Future<void> deleteTransactionNotes(String walletId) async {
final noteCount = await getTransactionNotes(walletId).count();
await isar.writeTxn(() async {
const paginateLimit = 50;
for (int i = 0; i < noteCount; i += paginateLimit) {
2023-06-05 22:25:54 +00:00
final labelIds = await getTransactionNotes(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.transactionNotes.deleteAll(labelIds);
2023-01-16 21:04:03 +00:00
}
});
}
Future<void> addNewTransactionData(
2023-02-03 19:22:21 +00:00
List<Tuple2<Transaction, Address?>> transactionsData,
String walletId,
) async {
try {
await isar.writeTxn(() async {
for (final data in transactionsData) {
final tx = data.item1;
2023-02-03 19:22:21 +00:00
final potentiallyUnconfirmedTx = await getTransaction(
walletId,
tx.txid,
);
if (potentiallyUnconfirmedTx != null) {
// update use id to replace tx
tx.id = potentiallyUnconfirmedTx.id;
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
}
// save transaction
await isar.transactions.put(tx);
2023-02-03 19:22:21 +00:00
if (data.item2 != null) {
final address = await getAddress(walletId, data.item2!.value);
// check if address exists in db and add if it does not
if (address == null) {
2023-02-03 19:22:21 +00:00
await isar.addresses.put(data.item2!);
}
// link and save address
2023-02-03 19:22:21 +00:00
tx.address.value = address ?? data.item2!;
await tx.address.save();
}
}
});
} catch (e) {
throw MainDBException("failed addNewTransactionData", e);
}
}
// ========== Ethereum =======================================================
// eth contracts
2023-06-05 22:25:54 +00:00
QueryBuilder<EthContract, EthContract, QWhere> getEthContracts() =>
isar.ethContracts.where();
Future<EthContract?> getEthContract(String contractAddress) =>
isar.ethContracts.where().addressEqualTo(contractAddress).findFirst();
EthContract? getEthContractSync(String contractAddress) =>
isar.ethContracts.where().addressEqualTo(contractAddress).findFirstSync();
Future<int> putEthContract(EthContract contract) => isar.writeTxn(() async {
return await isar.ethContracts.put(contract);
});
2023-06-05 22:25:54 +00:00
Future<void> putEthContracts(List<EthContract> contracts) =>
isar.writeTxn(() async {
await isar.ethContracts.putAll(contracts);
});
2023-07-26 17:47:49 +00:00
// ========== Lelantus =======================================================
Future<int?> getHighestUsedMintIndex({required String walletId}) async {
return await isar.lelantusCoins
.where()
.walletIdEqualTo(walletId)
.sortByMintIndexDesc()
.mintIndexProperty()
.findFirst();
}
2023-01-16 21:04:03 +00:00
}