2020-05-12 12:04:54 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter/foundation.dart';
|
2020-06-20 07:10:00 +00:00
|
|
|
import 'package:mobx/mobx.dart';
|
|
|
|
import 'package:cake_wallet/core/transaction_history.dart';
|
|
|
|
import 'package:cake_wallet/bitcoin/file.dart';
|
|
|
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
2020-05-12 12:04:54 +00:00
|
|
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
|
|
|
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
|
|
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
2020-06-20 07:10:00 +00:00
|
|
|
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
|
|
|
|
|
|
|
|
part 'bitcoin_transaction_history.g.dart';
|
2020-05-12 12:04:54 +00:00
|
|
|
|
2020-06-20 07:10:00 +00:00
|
|
|
// TODO: Think about another transaction store for bitcoin transaction history..
|
|
|
|
|
|
|
|
const _transactionsHistoryFileName = 'transactions.json';
|
|
|
|
|
|
|
|
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
|
|
|
|
with _$BitcoinTransactionHistory;
|
|
|
|
|
|
|
|
abstract class BitcoinTransactionHistoryBase
|
|
|
|
extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
|
|
|
|
BitcoinTransactionHistoryBase(
|
|
|
|
{this.eclient, String dirPath, @required String password})
|
|
|
|
: path = '$dirPath/$_transactionsHistoryFileName',
|
2020-05-12 12:04:54 +00:00
|
|
|
_password = password,
|
|
|
|
_height = 0;
|
|
|
|
|
2020-07-06 20:09:03 +00:00
|
|
|
BitcoinWalletBase wallet;
|
2020-05-12 12:04:54 +00:00
|
|
|
final ElectrumClient eclient;
|
|
|
|
final String path;
|
|
|
|
final String _password;
|
|
|
|
int _height;
|
|
|
|
|
|
|
|
Future<void> init() async {
|
|
|
|
final info = await _read();
|
2020-07-06 20:09:03 +00:00
|
|
|
_height = info['height'] as int ?? _height;
|
|
|
|
transactions = ObservableList.of(
|
|
|
|
info['transactions'] as List<BitcoinTransactionInfo> ??
|
|
|
|
<BitcoinTransactionInfo>[]);
|
2020-05-12 12:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future update() async {
|
2020-06-20 07:10:00 +00:00
|
|
|
await super.update();
|
|
|
|
_updateHeight();
|
2020-05-12 12:04:54 +00:00
|
|
|
}
|
|
|
|
|
2020-06-20 07:10:00 +00:00
|
|
|
@override
|
2020-05-12 12:04:54 +00:00
|
|
|
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
2020-06-20 07:10:00 +00:00
|
|
|
final addresses = wallet.addresses;
|
2020-05-12 12:04:54 +00:00
|
|
|
final histories =
|
2020-06-20 07:10:00 +00:00
|
|
|
addresses.map((record) => eclient.getHistory(address: record.address));
|
2020-05-12 12:04:54 +00:00
|
|
|
final _historiesWithDetails = await Future.wait(histories)
|
|
|
|
.then((histories) => histories
|
|
|
|
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
|
|
|
|
.expand((i) => i)
|
|
|
|
.toList())
|
|
|
|
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
|
|
|
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
|
|
|
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
|
|
|
|
|
|
|
return historiesWithDetails
|
|
|
|
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
|
|
|
info['raw'] as String, info['header'] as Map<String, Object>,
|
2020-06-20 07:10:00 +00:00
|
|
|
addresses: addresses.map((record) => record.address).toList()))
|
2020-05-12 12:04:54 +00:00
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
|
2020-06-20 07:10:00 +00:00
|
|
|
Future<Map<String, Object>> fetchTransactionInfo(
|
|
|
|
{@required String hash, @required int height}) async {
|
|
|
|
final rawFetching = eclient.getTransactionRaw(hash: hash);
|
|
|
|
final headerFetching = eclient.getHeader(height: height);
|
|
|
|
final result = await Future.wait([rawFetching, headerFetching]);
|
|
|
|
final raw = result.first as String;
|
|
|
|
final header = result[1] as Map<String, Object>;
|
|
|
|
|
|
|
|
return {'raw': raw, 'header': header};
|
|
|
|
}
|
|
|
|
|
2020-05-12 12:04:54 +00:00
|
|
|
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
2020-06-20 07:10:00 +00:00
|
|
|
this.transactions.addAll(transactions);
|
|
|
|
await save();
|
2020-05-12 12:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
2020-06-20 07:10:00 +00:00
|
|
|
transactions.add(tx);
|
|
|
|
await save();
|
2020-05-12 12:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> save() async => writeData(
|
|
|
|
path: path,
|
|
|
|
password: _password,
|
2020-06-20 07:10:00 +00:00
|
|
|
data: json.encode({'height': _height, 'transactions': transactions}));
|
2020-05-12 12:04:54 +00:00
|
|
|
|
|
|
|
Future<Map<String, Object>> _read() async {
|
|
|
|
try {
|
|
|
|
final content = await read(path: path, password: _password);
|
|
|
|
final jsoned = json.decode(content) as Map<String, Object>;
|
|
|
|
final height = jsoned['height'] as int;
|
|
|
|
final transactions = (jsoned['transactions'] as List<dynamic>)
|
|
|
|
.map((dynamic row) {
|
|
|
|
if (row is Map<String, Object>) {
|
|
|
|
return BitcoinTransactionInfo.fromJson(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.where((el) => el != null)
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
return {'transactions': transactions, 'height': height};
|
|
|
|
} catch (_) {
|
2020-07-06 20:09:03 +00:00
|
|
|
return {'transactions': <BitcoinTransactionInfo>[], 'height': 0};
|
2020-05-12 12:04:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _updateHeight() {
|
2020-06-20 07:10:00 +00:00
|
|
|
final newHeight = transactions.fold(
|
|
|
|
0, (int acc, val) => val.height > acc ? val.height : acc);
|
2020-05-12 12:04:54 +00:00
|
|
|
_height = newHeight > _height ? newHeight : _height;
|
|
|
|
}
|
|
|
|
}
|