import 'dart:convert'; import 'package:flutter/foundation.dart'; 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'; 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'; import 'package:cake_wallet/src/domain/common/transaction_direction.dart'; part 'bitcoin_transaction_history.g.dart'; // TODO: Think about another transaction store for bitcoin transaction history.. const _transactionsHistoryFileName = 'transactions.json'; class BitcoinTransactionHistory = BitcoinTransactionHistoryBase with _$BitcoinTransactionHistory; abstract class BitcoinTransactionHistoryBase extends TransactionHistoryBase with Store { BitcoinTransactionHistoryBase( {this.eclient, String dirPath, @required String password}) : path = '$dirPath/$_transactionsHistoryFileName', _password = password, _height = 0; BitcoinWalletBase wallet; final ElectrumClient eclient; final String path; final String _password; int _height; Future init() async { final info = await _read(); _height = info['height'] as int ?? _height; transactions = ObservableList.of( info['transactions'] as List ?? []); } @override Future update() async { await super.update(); _updateHeight(); } @override Future> fetchTransactions() async { final addresses = wallet.addresses; final histories = addresses.map((record) => eclient.getHistory(address: record.address)); 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, addresses: addresses.map((record) => record.address).toList())) .toList(); } Future> 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; return {'raw': raw, 'header': header}; } Future add(List transactions) async { this.transactions.addAll(transactions); await save(); } Future addOne(BitcoinTransactionInfo tx) async { transactions.add(tx); await save(); } Future save() async => writeData( path: path, password: _password, data: json.encode({'height': _height, 'transactions': transactions})); Future> _read() async { try { final content = await read(path: path, password: _password); final jsoned = json.decode(content) as Map; final height = jsoned['height'] as int; final transactions = (jsoned['transactions'] as List) .map((dynamic row) { if (row is Map) { return BitcoinTransactionInfo.fromJson(row); } return null; }) .where((el) => el != null) .toList(); return {'transactions': transactions, 'height': height}; } catch (_) { return {'transactions': [], 'height': 0}; } } void _updateHeight() { final newHeight = transactions.fold( 0, (int acc, val) => val.height > acc ? val.height : acc); _height = newHeight > _height ? newHeight : _height; } }