stack_wallet/lib/db/isar/main_db.dart

423 lines
13 KiB
Dart
Raw Normal View History

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';
2023-01-16 21:04:03 +00:00
import 'package:stackwallet/models/isar/models/isar_models.dart';
2023-04-05 22:06:31 +00:00
import 'package:stackwallet/utilities/amount.dart';
2023-03-15 18:01:10 +00:00
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
2023-01-16 21:04:03 +00:00
import 'package:stackwallet/utilities/stack_file_system.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-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,
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;
}
// addresses
QueryBuilder<Address, Address, QAfterWhereClause> getAddresses(
String walletId) =>
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) {
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) {
throw MainDBException(
"failed updateAddress: from=$oldAddress to=$newAddress", e);
}
}
2023-01-16 21:04:03 +00:00
// transactions
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
String walletId) =>
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
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
isar.utxos.where().walletIdEqualTo(walletId);
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);
});
2023-03-07 15:00:00 +00:00
Future<void> updateUTXOs(String walletId, List<UTXO> utxos) async {
await isar.writeTxn(() async {
final set = utxos.toSet();
for (final utxo in utxos) {
// check if utxo exists in db and update accordingly
final storedUtxo = await isar.utxos
.where()
.txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout)
.findFirst();
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
),
);
2023-03-07 15:00:00 +00:00
}
}
await isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
await isar.utxos.putAll(set.toList());
});
}
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
QueryBuilder<TransactionNote, TransactionNote, QAfterWhereClause>
getTransactionNotes(String walletId) =>
isar.transactionNotes.where().walletIdEqualTo(walletId);
Future<void> putTransactionNote(TransactionNote transactionNote) =>
isar.writeTxn(() async {
await isar.transactionNotes.put(transactionNote);
});
Future<void> putTransactionNotes(List<TransactionNote> transactionNotes) =>
isar.writeTxn(() async {
await isar.transactionNotes.putAll(transactionNotes);
});
2023-02-03 19:22:21 +00:00
Future<TransactionNote?> getTransactionNote(
String walletId, String txid) async {
return isar.transactionNotes.getByTxidWalletId(
txid,
walletId,
);
}
Stream<TransactionNote?> watchTransactionNote({
required Id id,
bool fireImmediately = false,
}) {
return isar.transactionNotes
.watchObject(id, fireImmediately: fireImmediately);
}
// address labels
QueryBuilder<AddressLabel, AddressLabel, QAfterWhereClause> getAddressLabels(
String walletId) =>
isar.addressLabels.where().walletIdEqualTo(walletId);
Future<int> putAddressLabel(AddressLabel addressLabel) =>
isar.writeTxn(() async {
return await isar.addressLabels.put(addressLabel);
});
int putAddressLabelSync(AddressLabel addressLabel) => isar.writeTxnSync(() {
return isar.addressLabels.putSync(addressLabel);
});
Future<void> putAddressLabels(List<AddressLabel> addressLabels) =>
isar.writeTxn(() async {
await isar.addressLabels.putAll(addressLabels);
});
Future<AddressLabel?> getAddressLabel(
String walletId, String addressString) async {
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-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) {
final txnIds = await getTransactions(walletId)
2023-01-16 21:04:03 +00:00
.offset(i)
.limit(paginateLimit)
.idProperty()
2023-01-16 21:04:03 +00:00
.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) {
final addressIds = await getAddresses(walletId)
2023-01-16 21:04:03 +00:00
.offset(i)
.limit(paginateLimit)
.idProperty()
2023-01-16 21:04:03 +00:00
.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) {
final utxoIds = await getUTXOs(walletId)
.offset(i)
.limit(paginateLimit)
.idProperty()
.findAll();
await isar.utxos.deleteAll(utxoIds);
}
});
}
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) {
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) {
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
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);
});
Future<void> putEthContracts(List<EthContract> contracts) =>
isar.writeTxn(() async {
await isar.ethContracts.putAll(contracts);
});
2023-01-16 21:04:03 +00:00
}