mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
feat: tx history worker
This commit is contained in:
parent
02fabf8594
commit
4a4250a905
13 changed files with 904 additions and 681 deletions
|
@ -24,7 +24,7 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
||||||
|
|
||||||
final String address;
|
final String address;
|
||||||
final bool _isHidden;
|
bool _isHidden;
|
||||||
bool get isHidden => _isHidden;
|
bool get isHidden => _isHidden;
|
||||||
final bool _isChange;
|
final bool _isChange;
|
||||||
bool get isChange => _isChange;
|
bool get isChange => _isChange;
|
||||||
|
@ -46,7 +46,12 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
|
|
||||||
bool get isUsed => _isUsed;
|
bool get isUsed => _isUsed;
|
||||||
|
|
||||||
void setAsUsed() => _isUsed = true;
|
void setAsUsed() {
|
||||||
|
_isUsed = true;
|
||||||
|
// TODO: check is hidden flow on addr list
|
||||||
|
_isHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
void setNewName(String label) => _name = label;
|
void setNewName(String label) => _name = label;
|
||||||
|
|
||||||
int get hashCode => address.hashCode;
|
int get hashCode => address.hashCode;
|
||||||
|
@ -119,6 +124,26 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
'type': type.toString(),
|
'type': type.toString(),
|
||||||
'scriptHash': scriptHash,
|
'scriptHash': scriptHash,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is BitcoinAddressRecord &&
|
||||||
|
other.address == address &&
|
||||||
|
other.index == index &&
|
||||||
|
other.derivationInfo == derivationInfo &&
|
||||||
|
other.scriptHash == scriptHash &&
|
||||||
|
other.type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
address.hashCode ^
|
||||||
|
index.hashCode ^
|
||||||
|
derivationInfo.hashCode ^
|
||||||
|
scriptHash.hashCode ^
|
||||||
|
type.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
// import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
|
@ -240,6 +240,36 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> getNodeSupportsSilentPayments() async {
|
||||||
|
return true;
|
||||||
|
// As of today (august 2024), only ElectrumRS supports silent payments
|
||||||
|
// if (!(await getNodeIsElectrs())) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (node == null) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// final tweaksResponse = await electrumClient.getTweaks(height: 0);
|
||||||
|
|
||||||
|
// if (tweaksResponse != null) {
|
||||||
|
// node!.supportsSilentPayments = true;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
// }
|
||||||
|
// } on RequestFailedTimeoutException catch (_) {
|
||||||
|
// node!.supportsSilentPayments = false;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
|
// node!.supportsSilentPayments = false;
|
||||||
|
// node!.save();
|
||||||
|
// return node!.supportsSilentPayments!;
|
||||||
|
}
|
||||||
|
|
||||||
LedgerConnection? _ledgerConnection;
|
LedgerConnection? _ledgerConnection;
|
||||||
BitcoinLedgerApp? _bitcoinLedgerApp;
|
BitcoinLedgerApp? _bitcoinLedgerApp;
|
||||||
|
|
||||||
|
@ -327,11 +357,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
_isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
_isolate?.then((value) => value.kill(priority: Isolate.immediate));
|
||||||
|
|
||||||
if (rpc!.isConnected) {
|
// if (rpc!.isConnected) {
|
||||||
syncStatus = SyncedSyncStatus();
|
// syncStatus = SyncedSyncStatus();
|
||||||
} else {
|
// } else {
|
||||||
syncStatus = NotConnectedSyncStatus();
|
// syncStatus = NotConnectedSyncStatus();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +397,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateCoins(unspentCoins);
|
await updateCoins(unspentCoins.toSet());
|
||||||
await refreshUnspentCoinsInfo();
|
await refreshUnspentCoinsInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,6 +479,20 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> registerSilentPaymentsKey() async {
|
||||||
|
final registered = await electrumClient.tweaksRegister(
|
||||||
|
secViewKey: walletAddresses.silentAddress!.b_scan.toHex(),
|
||||||
|
pubSpendKey: walletAddresses.silentAddress!.B_spend.toHex(),
|
||||||
|
labels: walletAddresses.silentAddresses
|
||||||
|
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.labelIndex >= 1)
|
||||||
|
.map((addr) => addr.labelIndex)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
print("registered: $registered");
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _updateSilentAddressRecord(BitcoinUnspent unspent) {
|
void _updateSilentAddressRecord(BitcoinUnspent unspent) {
|
||||||
final receiveAddressRecord = unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord;
|
final receiveAddressRecord = unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord;
|
||||||
|
@ -593,41 +637,42 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
try {
|
throw UnimplementedError();
|
||||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
// try {
|
||||||
|
// final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
|
|
||||||
await Future.wait(
|
// await Future.wait(
|
||||||
BITCOIN_ADDRESS_TYPES.map(
|
// BITCOIN_ADDRESS_TYPES.map(
|
||||||
(type) => fetchTransactionsForAddressType(historiesWithDetails, type),
|
// (type) => fetchTransactionsForAddressType(historiesWithDetails, type),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
|
|
||||||
transactionHistory.transactions.values.forEach((tx) async {
|
// transactionHistory.transactions.values.forEach((tx) async {
|
||||||
final isPendingSilentPaymentUtxo =
|
// final isPendingSilentPaymentUtxo =
|
||||||
(tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null;
|
// (tx.isPending || tx.confirmations == 0) && historiesWithDetails[tx.id] == null;
|
||||||
|
|
||||||
if (isPendingSilentPaymentUtxo) {
|
// if (isPendingSilentPaymentUtxo) {
|
||||||
final info = await fetchTransactionInfo(hash: tx.id, height: tx.height);
|
// final info = await fetchTransactionInfo(hash: tx.id, height: tx.height);
|
||||||
|
|
||||||
if (info != null) {
|
// if (info != null) {
|
||||||
tx.confirmations = info.confirmations;
|
// tx.confirmations = info.confirmations;
|
||||||
tx.isPending = tx.confirmations == 0;
|
// tx.isPending = tx.confirmations == 0;
|
||||||
transactionHistory.addOne(tx);
|
// transactionHistory.addOne(tx);
|
||||||
await transactionHistory.save();
|
// await transactionHistory.save();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
return historiesWithDetails;
|
// return historiesWithDetails;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print("fetchTransactions $e");
|
// print("fetchTransactions $e");
|
||||||
return {};
|
// return {};
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<void> updateTransactions() async {
|
Future<void> updateTransactions([List<BitcoinAddressRecord>? addresses]) async {
|
||||||
super.updateTransactions();
|
super.updateTransactions();
|
||||||
|
|
||||||
transactionHistory.transactions.values.forEach((tx) {
|
transactionHistory.transactions.values.forEach((tx) {
|
||||||
|
@ -641,32 +686,32 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
// @action
|
||||||
Future<ElectrumBalance> fetchBalances() async {
|
// Future<ElectrumBalance> fetchBalances() async {
|
||||||
final balance = await super.fetchBalances();
|
// final balance = await super.fetchBalances();
|
||||||
|
|
||||||
int totalFrozen = balance.frozen;
|
// int totalFrozen = balance.frozen;
|
||||||
int totalConfirmed = balance.confirmed;
|
// int totalConfirmed = balance.confirmed;
|
||||||
|
|
||||||
// Add values from unspent coins that are not fetched by the address list
|
// // Add values from unspent coins that are not fetched by the address list
|
||||||
// i.e. scanned silent payments
|
// // i.e. scanned silent payments
|
||||||
transactionHistory.transactions.values.forEach((tx) {
|
// transactionHistory.transactions.values.forEach((tx) {
|
||||||
if (tx.unspents != null) {
|
// if (tx.unspents != null) {
|
||||||
tx.unspents!.forEach((unspent) {
|
// tx.unspents!.forEach((unspent) {
|
||||||
if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
// if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||||
if (unspent.isFrozen) totalFrozen += unspent.value;
|
// if (unspent.isFrozen) totalFrozen += unspent.value;
|
||||||
totalConfirmed += unspent.value;
|
// totalConfirmed += unspent.value;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
return ElectrumBalance(
|
// return ElectrumBalance(
|
||||||
confirmed: totalConfirmed,
|
// confirmed: totalConfirmed,
|
||||||
unconfirmed: balance.unconfirmed,
|
// unconfirmed: balance.unconfirmed,
|
||||||
frozen: totalFrozen,
|
// frozen: totalFrozen,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
|
@ -713,15 +758,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
@action
|
// @action
|
||||||
void onHeadersResponse(ElectrumHeaderResponse response) {
|
// void onHeadersResponse(ElectrumHeaderResponse response) {
|
||||||
super.onHeadersResponse(response);
|
// super.onHeadersResponse(response);
|
||||||
|
|
||||||
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
|
// if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
|
||||||
_setListeners(walletInfo.restoreHeight);
|
// _setListeners(walletInfo.restoreHeight);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:io';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
@ -14,7 +13,6 @@ import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
|
|
||||||
class BitcoinWalletService extends WalletService<
|
class BitcoinWalletService extends WalletService<
|
||||||
BitcoinNewWalletCredentials,
|
BitcoinNewWalletCredentials,
|
||||||
|
@ -172,10 +170,6 @@ class BitcoinWalletService extends WalletService<
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||||
{bool? isTestnet}) async {
|
{bool? isTestnet}) async {
|
||||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||||
credentials.walletInfo?.network = network.value;
|
credentials.walletInfo?.network = network.value;
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/get_height_by_date.dart';
|
// import 'package:cw_core/get_height_by_date.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
@ -35,13 +35,13 @@ import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_keys_file.dart';
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
// import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_core/unspent_coin_type.dart';
|
import 'package:cw_core/unspent_coin_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:http/http.dart' as http;
|
// import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
part 'electrum_wallet.g.dart';
|
part 'electrum_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ abstract class ElectrumWalletBase
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
isEnabledAutoGenerateSubaddress = true,
|
isEnabledAutoGenerateSubaddress = true,
|
||||||
// TODO: inital unspent coins
|
// TODO: inital unspent coins
|
||||||
unspentCoins = ObservableSet(),
|
unspentCoins = BitcoinUnspentCoins(),
|
||||||
scripthashesListening = {},
|
scripthashesListening = [],
|
||||||
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
|
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
|
||||||
? {
|
? {
|
||||||
currency: initialBalance ??
|
currency: initialBalance ??
|
||||||
|
@ -107,7 +107,7 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _handleWorkerResponse(dynamic message) {
|
Future<void> _handleWorkerResponse(dynamic message) async {
|
||||||
print('Main: received message: $message');
|
print('Main: received message: $message');
|
||||||
|
|
||||||
Map<String, dynamic> messageJson;
|
Map<String, dynamic> messageJson;
|
||||||
|
@ -146,15 +146,17 @@ abstract class ElectrumWalletBase
|
||||||
break;
|
break;
|
||||||
case ElectrumRequestMethods.headersSubscribeMethod:
|
case ElectrumRequestMethods.headersSubscribeMethod:
|
||||||
final response = ElectrumWorkerHeadersSubscribeResponse.fromJson(messageJson);
|
final response = ElectrumWorkerHeadersSubscribeResponse.fromJson(messageJson);
|
||||||
onHeadersResponse(response.result);
|
await onHeadersResponse(response.result);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.getBalanceMethod:
|
||||||
|
final response = ElectrumWorkerGetBalanceResponse.fromJson(messageJson);
|
||||||
|
onBalanceResponse(response.result);
|
||||||
|
break;
|
||||||
|
case ElectrumRequestMethods.getHistoryMethod:
|
||||||
|
final response = ElectrumWorkerGetHistoryResponse.fromJson(messageJson);
|
||||||
|
onHistoriesResponse(response.result);
|
||||||
break;
|
break;
|
||||||
// case 'fetchBalances':
|
|
||||||
// final balance = ElectrumBalance.fromJSON(
|
|
||||||
// jsonDecode(workerResponse.data.toString()).toString(),
|
|
||||||
// );
|
|
||||||
// Update the balance state
|
|
||||||
// this.balance[currency] = balance!;
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,8 +221,6 @@ abstract class ElectrumWalletBase
|
||||||
bool isEnabledAutoGenerateSubaddress;
|
bool isEnabledAutoGenerateSubaddress;
|
||||||
|
|
||||||
late ElectrumClient electrumClient;
|
late ElectrumClient electrumClient;
|
||||||
ElectrumApiProvider? electrumClient2;
|
|
||||||
BitcoinBaseElectrumRPCService? get rpc => electrumClient2?.rpc;
|
|
||||||
ApiProvider? apiProvider;
|
ApiProvider? apiProvider;
|
||||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||||
|
|
||||||
|
@ -235,10 +235,10 @@ abstract class ElectrumWalletBase
|
||||||
@observable
|
@observable
|
||||||
SyncStatus syncStatus;
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
Set<String> get addressesSet => walletAddresses.allAddresses
|
List<String> get addressesSet => walletAddresses.allAddresses
|
||||||
.where((element) => element.type != SegwitAddresType.mweb)
|
.where((element) => element.type != SegwitAddresType.mweb)
|
||||||
.map((addr) => addr.address)
|
.map((addr) => addr.address)
|
||||||
.toSet();
|
.toList();
|
||||||
|
|
||||||
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
|
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
|
||||||
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
|
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
|
||||||
|
@ -288,14 +288,14 @@ abstract class ElectrumWalletBase
|
||||||
);
|
);
|
||||||
|
|
||||||
String _password;
|
String _password;
|
||||||
ObservableSet<BitcoinUnspent> unspentCoins;
|
BitcoinUnspentCoins unspentCoins;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
TransactionPriorities? feeRates;
|
TransactionPriorities? feeRates;
|
||||||
int feeRate(TransactionPriority priority) => feeRates![priority];
|
int feeRate(TransactionPriority priority) => feeRates![priority];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
Set<String> scripthashesListening;
|
List<String> scripthashesListening;
|
||||||
|
|
||||||
bool _chainTipListenerOn = false;
|
bool _chainTipListenerOn = false;
|
||||||
bool _isTransactionUpdating;
|
bool _isTransactionUpdating;
|
||||||
|
@ -323,16 +323,22 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
syncStatus = SynchronizingSyncStatus();
|
syncStatus = SynchronizingSyncStatus();
|
||||||
|
|
||||||
|
// INFO: FIRST: Call subscribe for headers, get the initial chainTip update in case it is zero
|
||||||
await subscribeForHeaders();
|
await subscribeForHeaders();
|
||||||
await subscribeForUpdates();
|
|
||||||
|
|
||||||
// await updateTransactions();
|
// INFO: SECOND: Start loading transaction histories for every address, this will help discover addresses until the unused gap limit has been reached, which will help finding the full balance and unspents later.
|
||||||
|
await updateTransactions();
|
||||||
|
|
||||||
// await updateAllUnspents();
|
// await updateAllUnspents();
|
||||||
// await updateBalance();
|
// INFO: THIRD: Start loading the TX history
|
||||||
|
await updateBalance();
|
||||||
|
|
||||||
|
// await subscribeForUpdates();
|
||||||
|
|
||||||
// await updateFeeRates();
|
// await updateFeeRates();
|
||||||
|
|
||||||
_updateFeeRateTimer ??=
|
// _updateFeeRateTimer ??=
|
||||||
Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
|
// Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
|
||||||
|
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
|
@ -344,20 +350,6 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> registerSilentPaymentsKey() async {
|
|
||||||
final registered = await electrumClient.tweaksRegister(
|
|
||||||
secViewKey: walletAddresses.silentAddress!.b_scan.toHex(),
|
|
||||||
pubSpendKey: walletAddresses.silentAddress!.B_spend.toHex(),
|
|
||||||
labels: walletAddresses.silentAddresses
|
|
||||||
.where((addr) => addr.type == SilentPaymentsAddresType.p2sp && addr.labelIndex >= 1)
|
|
||||||
.map((addr) => addr.labelIndex)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
print("registered: $registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void callError(FlutterErrorDetails error) {
|
void callError(FlutterErrorDetails error) {
|
||||||
_onError?.call(error);
|
_onError?.call(error);
|
||||||
|
@ -366,9 +358,9 @@ abstract class ElectrumWalletBase
|
||||||
@action
|
@action
|
||||||
Future<void> updateFeeRates() async {
|
Future<void> updateFeeRates() async {
|
||||||
try {
|
try {
|
||||||
feeRates = BitcoinElectrumTransactionPriorities.fromList(
|
// feeRates = BitcoinElectrumTransactionPriorities.fromList(
|
||||||
await electrumClient2!.getFeeRates(),
|
// await electrumClient2!.getFeeRates(),
|
||||||
);
|
// );
|
||||||
} catch (e, stacktrace) {
|
} catch (e, stacktrace) {
|
||||||
// _onError?.call(FlutterErrorDetails(
|
// _onError?.call(FlutterErrorDetails(
|
||||||
// exception: e,
|
// exception: e,
|
||||||
|
@ -403,36 +395,6 @@ abstract class ElectrumWalletBase
|
||||||
return node!.isElectrs!;
|
return node!.isElectrs!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> getNodeSupportsSilentPayments() async {
|
|
||||||
return true;
|
|
||||||
// As of today (august 2024), only ElectrumRS supports silent payments
|
|
||||||
if (!(await getNodeIsElectrs())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final tweaksResponse = await electrumClient.getTweaks(height: 0);
|
|
||||||
|
|
||||||
if (tweaksResponse != null) {
|
|
||||||
node!.supportsSilentPayments = true;
|
|
||||||
node!.save();
|
|
||||||
return node!.supportsSilentPayments!;
|
|
||||||
}
|
|
||||||
} on RequestFailedTimeoutException catch (_) {
|
|
||||||
node!.supportsSilentPayments = false;
|
|
||||||
node!.save();
|
|
||||||
return node!.supportsSilentPayments!;
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
node!.supportsSilentPayments = false;
|
|
||||||
node!.save();
|
|
||||||
return node!.supportsSilentPayments!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
Future<void> connectToNode({required Node node}) async {
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
@ -1176,7 +1138,7 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||||
// await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1226,28 +1188,23 @@ abstract class ElectrumWalletBase
|
||||||
Future<void> updateAllUnspents() async {
|
Future<void> updateAllUnspents() async {
|
||||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
|
|
||||||
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
|
Set<String> scripthashes = {};
|
||||||
walletAddresses.allAddresses
|
walletAddresses.allAddresses.forEach((addressRecord) {
|
||||||
.where((element) => element.type != SegwitAddresType.mweb)
|
scripthashes.add(addressRecord.scriptHash);
|
||||||
.forEach((addr) {
|
|
||||||
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
workerSendPort!.send(
|
||||||
|
ElectrumWorkerGetBalanceRequest(scripthashes: scripthashes).toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
await Future.wait(walletAddresses.allAddresses
|
await Future.wait(walletAddresses.allAddresses
|
||||||
.where((element) => element.type != SegwitAddresType.mweb)
|
.where((element) => element.type != SegwitAddresType.mweb)
|
||||||
.map((address) async {
|
.map((address) async {
|
||||||
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
unspentCoins.addAll(updatedUnspentCoins);
|
await updateCoins(unspentCoins.toSet());
|
||||||
|
await refreshUnspentCoinsInfo();
|
||||||
if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
|
|
||||||
unspentCoins.forEach((coin) => addCoinInfo(coin));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateCoins(unspentCoins);
|
|
||||||
// await refreshUnspentCoinsInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -1294,18 +1251,17 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
|
Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
|
||||||
|
List<Map<String, dynamic>> unspents = [];
|
||||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
|
|
||||||
final unspents = await electrumClient2!.request(
|
unspents = await electrumClient.getListUnspent(address.scriptHash);
|
||||||
ElectrumScriptHashListUnspent(scriptHash: address.scriptHash),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Future.wait(unspents.map((unspent) async {
|
await Future.wait(unspents.map((unspent) async {
|
||||||
try {
|
try {
|
||||||
final coin = BitcoinUnspent.fromUTXO(address, unspent);
|
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
||||||
final tx = await fetchTransactionInfo(hash: coin.hash);
|
// final tx = await fetchTransactionInfo(hash: coin.hash);
|
||||||
coin.isChange = address.isChange;
|
coin.isChange = address.isHidden;
|
||||||
coin.confirmations = tx?.confirmations;
|
// coin.confirmations = tx?.confirmations;
|
||||||
|
|
||||||
updatedUnspentCoins.add(coin);
|
updatedUnspentCoins.add(coin);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
@ -1332,6 +1288,7 @@ abstract class ElectrumWalletBase
|
||||||
await unspentCoinsInfo.add(newInfo);
|
await unspentCoinsInfo.add(newInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: ?
|
||||||
Future<void> refreshUnspentCoinsInfo() async {
|
Future<void> refreshUnspentCoinsInfo() async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> keys = <dynamic>[];
|
final List<dynamic> keys = <dynamic>[];
|
||||||
|
@ -1415,7 +1372,7 @@ abstract class ElectrumWalletBase
|
||||||
final vout = input.txIndex;
|
final vout = input.txIndex;
|
||||||
final outTransaction = inputTransaction.outputs[vout];
|
final outTransaction = inputTransaction.outputs[vout];
|
||||||
final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
|
final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
|
||||||
allInputsAmount += outTransaction.amount.toInt();
|
// allInputsAmount += outTransaction.amount.toInt();
|
||||||
|
|
||||||
final addressRecord =
|
final addressRecord =
|
||||||
walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
walletAddresses.allAddresses.firstWhere((element) => element.address == address);
|
||||||
|
@ -1565,72 +1522,15 @@ abstract class ElectrumWalletBase
|
||||||
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
||||||
int? time;
|
int? time;
|
||||||
int? height;
|
int? height;
|
||||||
|
final transactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
final transactionHex = await electrumClient2!.request(
|
|
||||||
ElectrumGetTransactionHex(transactionHash: hash),
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// if (mempoolAPIEnabled) {
|
|
||||||
if (true) {
|
|
||||||
try {
|
|
||||||
final txVerbose = await http.get(
|
|
||||||
Uri.parse(
|
|
||||||
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (txVerbose.statusCode == 200 &&
|
|
||||||
txVerbose.body.isNotEmpty &&
|
|
||||||
jsonDecode(txVerbose.body) != null) {
|
|
||||||
height = jsonDecode(txVerbose.body)['block_height'] as int;
|
|
||||||
|
|
||||||
final blockHash = await http.get(
|
|
||||||
Uri.parse(
|
|
||||||
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (blockHash.statusCode == 200 &&
|
|
||||||
blockHash.body.isNotEmpty &&
|
|
||||||
jsonDecode(blockHash.body) != null) {
|
|
||||||
final blockResponse = await http.get(
|
|
||||||
Uri.parse(
|
|
||||||
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (blockResponse.statusCode == 200 &&
|
|
||||||
blockResponse.body.isNotEmpty &&
|
|
||||||
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
|
||||||
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
int? confirmations;
|
int? confirmations;
|
||||||
|
|
||||||
if (height != null) {
|
|
||||||
if (time == null && height > 0) {
|
|
||||||
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
|
|
||||||
}
|
|
||||||
|
|
||||||
final tip = currentChainTip!;
|
|
||||||
if (tip > 0 && height > 0) {
|
|
||||||
// Add one because the block itself is the first confirmation
|
|
||||||
confirmations = tip - height + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final original = BtcTransaction.fromRaw(transactionHex);
|
final original = BtcTransaction.fromRaw(transactionHex);
|
||||||
final ins = <BtcTransaction>[];
|
final ins = <BtcTransaction>[];
|
||||||
|
|
||||||
for (final vin in original.inputs) {
|
for (final vin in original.inputs) {
|
||||||
final inputTransactionHex = await electrumClient2!.request(
|
final inputTransactionHex = await electrumClient.getTransactionHex(hash: hash);
|
||||||
ElectrumGetTransactionHex(transactionHash: vin.txId),
|
|
||||||
);
|
|
||||||
|
|
||||||
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||||
}
|
}
|
||||||
|
@ -1643,207 +1543,62 @@ abstract class ElectrumWalletBase
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo({required String hash, int? height}) async {
|
|
||||||
try {
|
|
||||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
|
||||||
await getTransactionExpanded(hash: hash),
|
|
||||||
walletInfo.type,
|
|
||||||
network,
|
|
||||||
addresses: addressesSet,
|
|
||||||
height: height,
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
print([e, s]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
try {
|
throw UnimplementedError();
|
||||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
|
||||||
|
|
||||||
if (type == WalletType.bitcoinCash) {
|
|
||||||
await Future.wait(BITCOIN_CASH_ADDRESS_TYPES
|
|
||||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
|
||||||
} else if (type == WalletType.litecoin) {
|
|
||||||
await Future.wait(LITECOIN_ADDRESS_TYPES
|
|
||||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return historiesWithDetails;
|
@action
|
||||||
} catch (e) {
|
Future<void> updateTransactions([List<BitcoinAddressRecord>? addresses]) async {
|
||||||
print("fetchTransactions $e");
|
// TODO: all
|
||||||
return {};
|
addresses ??= walletAddresses.allAddresses
|
||||||
}
|
.where(
|
||||||
}
|
(element) => element.type == SegwitAddresType.p2wpkh && element.isChange == false,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
Future<void> fetchTransactionsForAddressType(
|
workerSendPort!.send(
|
||||||
Map<String, ElectrumTransactionInfo> historiesWithDetails,
|
ElectrumWorkerGetHistoryRequest(
|
||||||
BitcoinAddressType type,
|
addresses: addresses,
|
||||||
) async {
|
storedTxs: transactionHistory.transactions.values.toList(),
|
||||||
final addressesByType = walletAddresses.allAddresses.where((addr) => addr.type == type);
|
walletType: type,
|
||||||
await Future.wait(addressesByType.map((addressRecord) async {
|
// If we still don't have currentChainTip, txs will still be fetched but shown
|
||||||
final history = await _fetchAddressHistory(addressRecord);
|
// with confirmations as 0 but will be auto fixed on onHeadersResponse
|
||||||
|
chainTip: currentChainTip ?? 0,
|
||||||
if (history.isNotEmpty) {
|
network: network,
|
||||||
historiesWithDetails.addAll(history);
|
// mempoolAPIEnabled: mempoolAPIEnabled,
|
||||||
}
|
// TODO:
|
||||||
}));
|
mempoolAPIEnabled: true,
|
||||||
}
|
).toJson(),
|
||||||
|
|
||||||
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
|
|
||||||
BitcoinAddressRecord addressRecord,
|
|
||||||
) async {
|
|
||||||
String txid = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
|
||||||
|
|
||||||
final history = await electrumClient2!.request(ElectrumScriptHashGetHistory(
|
|
||||||
scriptHash: addressRecord.scriptHash,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (history.isNotEmpty) {
|
|
||||||
addressRecord.setAsUsed();
|
|
||||||
addressRecord.txCount = history.length;
|
|
||||||
|
|
||||||
await Future.wait(history.map((transaction) async {
|
|
||||||
txid = transaction['tx_hash'] as String;
|
|
||||||
|
|
||||||
final height = transaction['height'] as int;
|
|
||||||
final storedTx = transactionHistory.transactions[txid];
|
|
||||||
|
|
||||||
if (storedTx != null) {
|
|
||||||
if (height > 0) {
|
|
||||||
storedTx.height = height;
|
|
||||||
// the tx's block itself is the first confirmation so add 1
|
|
||||||
if ((currentChainTip ?? 0) > 0) {
|
|
||||||
storedTx.confirmations = currentChainTip! - height + 1;
|
|
||||||
}
|
|
||||||
storedTx.isPending = storedTx.confirmations == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
historiesWithDetails[txid] = storedTx;
|
|
||||||
} else {
|
|
||||||
final tx = await fetchTransactionInfo(hash: txid, height: height);
|
|
||||||
|
|
||||||
if (tx != null) {
|
|
||||||
historiesWithDetails[txid] = tx;
|
|
||||||
|
|
||||||
// Got a new transaction fetched, add it to the transaction history
|
|
||||||
// instead of waiting all to finish, and next time it will be faster
|
|
||||||
transactionHistory.addOne(tx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Future.value(null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
final totalAddresses = (addressRecord.isChange
|
|
||||||
? walletAddresses.changeAddresses
|
|
||||||
.where((addr) => addr.type == addressRecord.type)
|
|
||||||
.length
|
|
||||||
: walletAddresses.receiveAddresses
|
|
||||||
.where((addr) => addr.type == addressRecord.type)
|
|
||||||
.length);
|
|
||||||
final gapLimit = (addressRecord.isChange
|
|
||||||
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
|
||||||
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
|
|
||||||
|
|
||||||
final isUsedAddressUnderGap = addressRecord.index < totalAddresses &&
|
|
||||||
(addressRecord.index >= totalAddresses - gapLimit);
|
|
||||||
|
|
||||||
if (isUsedAddressUnderGap) {
|
|
||||||
// Discover new addresses for the same address type until the gap limit is respected
|
|
||||||
await walletAddresses.discoverAddresses(
|
|
||||||
isChange: addressRecord.isChange,
|
|
||||||
gap: gapLimit,
|
|
||||||
type: addressRecord.type,
|
|
||||||
derivationInfo: BitcoinAddressUtils.getDerivationFromType(addressRecord.type),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return historiesWithDetails;
|
|
||||||
} catch (e, stacktrace) {
|
|
||||||
_onError?.call(FlutterErrorDetails(
|
|
||||||
exception: "$txid - $e",
|
|
||||||
stack: stacktrace,
|
|
||||||
library: this.runtimeType.toString(),
|
|
||||||
));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> updateTransactions() async {
|
Future<void> subscribeForUpdates([Iterable<String>? unsubscribedScriptHashes]) async {
|
||||||
try {
|
unsubscribedScriptHashes ??= walletAddresses.allScriptHashes.where(
|
||||||
if (_isTransactionUpdating) {
|
(sh) => !scripthashesListening.contains(sh),
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isTransactionUpdating = true;
|
|
||||||
await fetchTransactions();
|
|
||||||
walletAddresses.updateReceiveAddresses();
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
} catch (e, stacktrace) {
|
|
||||||
print(stacktrace);
|
|
||||||
print(e);
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> subscribeForUpdates([
|
|
||||||
Iterable<BitcoinAddressRecord>? unsubscribedScriptHashes,
|
|
||||||
]) async {
|
|
||||||
unsubscribedScriptHashes ??= walletAddresses.allAddresses.where(
|
|
||||||
(address) => !scripthashesListening.contains(address.scriptHash),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, String> scripthashByAddress = {};
|
Map<String, String> scripthashByAddress = {};
|
||||||
List<String> scriptHashesList = [];
|
|
||||||
walletAddresses.allAddresses.forEach((addressRecord) {
|
walletAddresses.allAddresses.forEach((addressRecord) {
|
||||||
scripthashByAddress[addressRecord.address] = addressRecord.scriptHash;
|
scripthashByAddress[addressRecord.address] = addressRecord.scriptHash;
|
||||||
scriptHashesList.add(addressRecord.scriptHash);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
workerSendPort!.send(
|
workerSendPort!.send(
|
||||||
ElectrumWorkerScripthashesSubscribeRequest(scripthashByAddress: scripthashByAddress).toJson(),
|
ElectrumWorkerScripthashesSubscribeRequest(
|
||||||
|
scripthashByAddress: scripthashByAddress,
|
||||||
|
).toJson(),
|
||||||
);
|
);
|
||||||
scripthashesListening.addAll(scriptHashesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
scripthashesListening.addAll(scripthashByAddress.values);
|
||||||
Future<ElectrumBalance> fetchBalances() async {
|
|
||||||
var totalFrozen = 0;
|
|
||||||
var totalConfirmed = 0;
|
|
||||||
var totalUnconfirmed = 0;
|
|
||||||
|
|
||||||
unspentCoins.forEach((element) {
|
|
||||||
if (element.isFrozen) {
|
|
||||||
totalFrozen += element.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.confirmations == 0) {
|
|
||||||
totalUnconfirmed += element.value;
|
|
||||||
} else {
|
|
||||||
totalConfirmed += element.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return ElectrumBalance(
|
|
||||||
confirmed: totalConfirmed,
|
|
||||||
unconfirmed: totalUnconfirmed,
|
|
||||||
frozen: totalFrozen,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> updateBalance() async {
|
Future<void> updateBalance() async {
|
||||||
balance[currency] = await fetchBalances();
|
workerSendPort!.send(
|
||||||
|
ElectrumWorkerGetBalanceRequest(scripthashes: walletAddresses.allScriptHashes).toJson(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1925,12 +1680,102 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void onHeadersResponse(ElectrumHeaderResponse response) {
|
Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async {
|
||||||
|
final firstAddress = histories.first;
|
||||||
|
final isChange = firstAddress.addressRecord.isChange;
|
||||||
|
final type = firstAddress.addressRecord.type;
|
||||||
|
|
||||||
|
final totalAddresses = histories.length;
|
||||||
|
final gapLimit = (isChange
|
||||||
|
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
||||||
|
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
|
||||||
|
bool hasUsedAddressesUnderGap = false;
|
||||||
|
|
||||||
|
final addressesWithHistory = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
|
for (final addressHistory in histories) {
|
||||||
|
final txs = addressHistory.txs;
|
||||||
|
|
||||||
|
if (txs.isNotEmpty) {
|
||||||
|
final address = addressHistory.addressRecord;
|
||||||
|
addressesWithHistory.add(address);
|
||||||
|
|
||||||
|
hasUsedAddressesUnderGap =
|
||||||
|
address.index < totalAddresses && (address.index >= totalAddresses - gapLimit);
|
||||||
|
|
||||||
|
for (final tx in txs) {
|
||||||
|
transactionHistory.addOne(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addressesWithHistory.isNotEmpty) {
|
||||||
|
walletAddresses.updateAdresses(addressesWithHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUsedAddressesUnderGap) {
|
||||||
|
// Discover new addresses for the same address type until the gap limit is respected
|
||||||
|
final newAddresses = await walletAddresses.discoverAddresses(
|
||||||
|
isChange: isChange,
|
||||||
|
gap: gapLimit,
|
||||||
|
type: type,
|
||||||
|
derivationInfo: BitcoinAddressUtils.getDerivationFromType(type),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newAddresses.isNotEmpty) {
|
||||||
|
// Update the transactions for the new discovered addresses
|
||||||
|
await updateTransactions(newAddresses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void onBalanceResponse(ElectrumBalance balanceResult) {
|
||||||
|
var totalFrozen = 0;
|
||||||
|
var totalConfirmed = balanceResult.confirmed;
|
||||||
|
var totalUnconfirmed = balanceResult.unconfirmed;
|
||||||
|
|
||||||
|
unspentCoins.forInfo(unspentCoinsInfo.values).forEach((unspentCoinInfo) {
|
||||||
|
if (unspentCoinInfo.isFrozen) {
|
||||||
|
// TODO: verify this works well
|
||||||
|
totalFrozen += unspentCoinInfo.value;
|
||||||
|
totalConfirmed -= unspentCoinInfo.value;
|
||||||
|
totalUnconfirmed -= unspentCoinInfo.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
balance[currency] = ElectrumBalance(
|
||||||
|
confirmed: totalConfirmed,
|
||||||
|
unconfirmed: totalUnconfirmed,
|
||||||
|
frozen: totalFrozen,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> onHeadersResponse(ElectrumHeaderResponse response) async {
|
||||||
currentChainTip = response.height;
|
currentChainTip = response.height;
|
||||||
|
|
||||||
|
bool updated = false;
|
||||||
|
transactionHistory.transactions.values.forEach((tx) {
|
||||||
|
if (tx.height != null && tx.height! > 0) {
|
||||||
|
final newConfirmations = currentChainTip! - tx.height! + 1;
|
||||||
|
|
||||||
|
if (tx.confirmations != newConfirmations) {
|
||||||
|
tx.confirmations = newConfirmations;
|
||||||
|
tx.isPending = tx.confirmations == 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
await save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> subscribeForHeaders() async {
|
Future<void> subscribeForHeaders() async {
|
||||||
|
print(_chainTipListenerOn);
|
||||||
if (_chainTipListenerOn) return;
|
if (_chainTipListenerOn) return;
|
||||||
|
|
||||||
workerSendPort!.send(ElectrumWorkerHeadersSubscribeRequest().toJson());
|
workerSendPort!.send(ElectrumWorkerHeadersSubscribeRequest().toJson());
|
||||||
|
@ -1970,12 +1815,17 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void syncStatusReaction(SyncStatus syncStatus) {
|
void syncStatusReaction(SyncStatus syncStatus) {
|
||||||
if (syncStatus is NotConnectedSyncStatus || syncStatus is LostConnectionSyncStatus) {
|
final isDisconnectedStatus =
|
||||||
|
syncStatus is NotConnectedSyncStatus || syncStatus is LostConnectionSyncStatus;
|
||||||
|
|
||||||
|
if (syncStatus is ConnectingSyncStatus || isDisconnectedStatus) {
|
||||||
// Needs to re-subscribe to all scripthashes when reconnected
|
// Needs to re-subscribe to all scripthashes when reconnected
|
||||||
scripthashesListening = {};
|
scripthashesListening = [];
|
||||||
_isTransactionUpdating = false;
|
_isTransactionUpdating = false;
|
||||||
_chainTipListenerOn = false;
|
_chainTipListenerOn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisconnectedStatus) {
|
||||||
if (_isTryingToConnect) return;
|
if (_isTryingToConnect) return;
|
||||||
|
|
||||||
_isTryingToConnect = true;
|
_isTryingToConnect = true;
|
||||||
|
@ -1985,10 +1835,7 @@ abstract class ElectrumWalletBase
|
||||||
this.syncStatus is LostConnectionSyncStatus) {
|
this.syncStatus is LostConnectionSyncStatus) {
|
||||||
if (node == null) return;
|
if (node == null) return;
|
||||||
|
|
||||||
this.electrumClient.connectToUri(
|
connectToNode(node: this.node!);
|
||||||
node!.uri,
|
|
||||||
useSSL: node!.useSSL ?? false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
_isTryingToConnect = false;
|
_isTryingToConnect = false;
|
||||||
});
|
});
|
||||||
|
@ -2102,3 +1949,35 @@ class TxCreateUtxoDetails {
|
||||||
required this.spendsUnconfirmedTX,
|
required this.spendsUnconfirmedTX,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BitcoinUnspentCoins extends ObservableList<BitcoinUnspent> {
|
||||||
|
BitcoinUnspentCoins() : super();
|
||||||
|
|
||||||
|
List<UnspentCoinsInfo> forInfo(Iterable<UnspentCoinsInfo> unspentCoinsInfo) {
|
||||||
|
return unspentCoinsInfo.where((element) {
|
||||||
|
final info = this.firstWhereOrNull(
|
||||||
|
(info) =>
|
||||||
|
element.hash == info.hash &&
|
||||||
|
element.vout == info.vout &&
|
||||||
|
element.address == info.bitcoinAddressRecord.address &&
|
||||||
|
element.value == info.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
return info != null;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BitcoinUnspent> fromInfo(Iterable<UnspentCoinsInfo> unspentCoinsInfo) {
|
||||||
|
return this.where((element) {
|
||||||
|
final info = unspentCoinsInfo.firstWhereOrNull(
|
||||||
|
(info) =>
|
||||||
|
element.hash == info.hash &&
|
||||||
|
element.vout == info.vout &&
|
||||||
|
element.bitcoinAddressRecord.address == info.address &&
|
||||||
|
element.value == info.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
return info != null;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
int initialSilentAddressIndex = 0,
|
int initialSilentAddressIndex = 0,
|
||||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||||
BitcoinAddressType? initialAddressPageType,
|
BitcoinAddressType? initialAddressPageType,
|
||||||
}) : _allAddresses = ObservableSet.of(initialAddresses ?? []),
|
}) : _allAddresses = ObservableList.of(initialAddresses ?? []),
|
||||||
addressesByReceiveType =
|
addressesByReceiveType =
|
||||||
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
||||||
|
@ -89,7 +89,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
static const defaultChangeAddressesCount = 17;
|
static const defaultChangeAddressesCount = 17;
|
||||||
static const gap = 20;
|
static const gap = 20;
|
||||||
|
|
||||||
final ObservableSet<BitcoinAddressRecord> _allAddresses;
|
final ObservableList<BitcoinAddressRecord> _allAddresses;
|
||||||
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||||
|
@ -116,6 +116,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@computed
|
@computed
|
||||||
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
|
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
|
||||||
|
|
||||||
|
@computed
|
||||||
|
Set<String> get allScriptHashes =>
|
||||||
|
_allAddresses.map((addressRecord) => addressRecord.scriptHash).toSet();
|
||||||
|
|
||||||
BitcoinAddressRecord getFromAddresses(String address) {
|
BitcoinAddressRecord getFromAddresses(String address) {
|
||||||
return _allAddresses.firstWhere((element) => element.address == address);
|
return _allAddresses.firstWhere((element) => element.address == address);
|
||||||
}
|
}
|
||||||
|
@ -629,6 +633,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void updateAdresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||||
|
for (final address in addresses) {
|
||||||
|
_allAddresses.replaceRange(address.index, address.index + 1, [address]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||||
this._allAddresses.addAll(addresses);
|
this._allAddresses.addAll(addresses);
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
|
||||||
// import 'package:cw_bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
|
||||||
|
|
||||||
class ElectrumWorker {
|
|
||||||
final SendPort sendPort;
|
|
||||||
ElectrumApiProvider? _electrumClient;
|
|
||||||
|
|
||||||
ElectrumWorker._(this.sendPort, {ElectrumApiProvider? electrumClient})
|
|
||||||
: _electrumClient = electrumClient;
|
|
||||||
|
|
||||||
static void run(SendPort sendPort) {
|
|
||||||
final worker = ElectrumWorker._(sendPort);
|
|
||||||
final receivePort = ReceivePort();
|
|
||||||
|
|
||||||
sendPort.send(receivePort.sendPort);
|
|
||||||
|
|
||||||
receivePort.listen(worker.handleMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _sendResponse(ElectrumWorkerResponse response) {
|
|
||||||
sendPort.send(jsonEncode(response.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _sendError(ElectrumWorkerErrorResponse response) {
|
|
||||||
sendPort.send(jsonEncode(response.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleMessage(dynamic message) async {
|
|
||||||
print("Worker: received message: $message");
|
|
||||||
|
|
||||||
try {
|
|
||||||
Map<String, dynamic> messageJson;
|
|
||||||
if (message is String) {
|
|
||||||
messageJson = jsonDecode(message) as Map<String, dynamic>;
|
|
||||||
} else {
|
|
||||||
messageJson = message as Map<String, dynamic>;
|
|
||||||
}
|
|
||||||
final workerMethod = messageJson['method'] as String;
|
|
||||||
|
|
||||||
switch (workerMethod) {
|
|
||||||
case ElectrumWorkerMethods.connectionMethod:
|
|
||||||
await _handleConnect(ElectrumWorkerConnectRequest.fromJson(messageJson));
|
|
||||||
break;
|
|
||||||
// case 'blockchain.headers.subscribe':
|
|
||||||
// await _handleHeadersSubscribe();
|
|
||||||
// break;
|
|
||||||
// case 'blockchain.scripthash.get_balance':
|
|
||||||
// await _handleGetBalance(message);
|
|
||||||
// break;
|
|
||||||
case 'blockchain.scripthash.get_history':
|
|
||||||
// await _handleGetHistory(workerMessage);
|
|
||||||
break;
|
|
||||||
case 'blockchain.scripthash.listunspent':
|
|
||||||
// await _handleListUnspent(workerMessage);
|
|
||||||
break;
|
|
||||||
// Add other method handlers here
|
|
||||||
// default:
|
|
||||||
// _sendError(workerMethod, 'Unsupported method: ${workerMessage.method}');
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
print(s);
|
|
||||||
_sendError(ElectrumWorkerErrorResponse(error: e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleConnect(ElectrumWorkerConnectRequest request) async {
|
|
||||||
_electrumClient = ElectrumApiProvider(
|
|
||||||
await ElectrumTCPService.connect(
|
|
||||||
request.uri,
|
|
||||||
onConnectionStatusChange: (status) {
|
|
||||||
_sendResponse(ElectrumWorkerConnectResponse(status: status.toString()));
|
|
||||||
},
|
|
||||||
defaultRequestTimeOut: const Duration(seconds: 5),
|
|
||||||
connectionTimeOut: const Duration(seconds: 5),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Future<void> _handleHeadersSubscribe() async {
|
|
||||||
// final listener = _electrumClient!.subscribe(ElectrumHeaderSubscribe());
|
|
||||||
// if (listener == null) {
|
|
||||||
// _sendError('blockchain.headers.subscribe', 'Failed to subscribe');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// listener((event) {
|
|
||||||
// _sendResponse('blockchain.headers.subscribe', event);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleGetBalance(ElectrumWorkerRequest message) async {
|
|
||||||
// try {
|
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
|
||||||
// final result = await _electrumClient!.request(
|
|
||||||
// ElectrumGetScriptHashBalance(scriptHash: scriptHash),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// final balance = ElectrumBalance(
|
|
||||||
// confirmed: result['confirmed'] as int? ?? 0,
|
|
||||||
// unconfirmed: result['unconfirmed'] as int? ?? 0,
|
|
||||||
// frozen: 0,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// _sendResponse(message.method, balance.toJSON());
|
|
||||||
// } catch (e, s) {
|
|
||||||
// print(s);
|
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleGetHistory(ElectrumWorkerMessage message) async {
|
|
||||||
// try {
|
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
|
||||||
// final result = await electrumClient.getHistory(scriptHash);
|
|
||||||
// _sendResponse(message.method, jsonEncode(result));
|
|
||||||
// } catch (e) {
|
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleListUnspent(ElectrumWorkerMessage message) async {
|
|
||||||
// try {
|
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
|
||||||
// final result = await electrumClient.listUnspent(scriptHash);
|
|
||||||
// _sendResponse(message.method, jsonEncode(result));
|
|
||||||
// } catch (e) {
|
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -3,10 +3,16 @@ import 'dart:convert';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
||||||
// import 'package:cw_bitcoin/electrum_balance.dart';
|
// import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
// TODO: ping
|
||||||
|
|
||||||
class ElectrumWorker {
|
class ElectrumWorker {
|
||||||
final SendPort sendPort;
|
final SendPort sendPort;
|
||||||
|
@ -58,11 +64,15 @@ class ElectrumWorker {
|
||||||
ElectrumWorkerScripthashesSubscribeRequest.fromJson(messageJson),
|
ElectrumWorkerScripthashesSubscribeRequest.fromJson(messageJson),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
// case 'blockchain.scripthash.get_balance':
|
case ElectrumRequestMethods.getBalanceMethod:
|
||||||
// await _handleGetBalance(message);
|
await _handleGetBalance(
|
||||||
// break;
|
ElectrumWorkerGetBalanceRequest.fromJson(messageJson),
|
||||||
case 'blockchain.scripthash.get_history':
|
);
|
||||||
// await _handleGetHistory(workerMessage);
|
break;
|
||||||
|
case ElectrumRequestMethods.getHistoryMethod:
|
||||||
|
await _handleGetHistory(
|
||||||
|
ElectrumWorkerGetHistoryRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'blockchain.scripthash.listunspent':
|
case 'blockchain.scripthash.listunspent':
|
||||||
// await _handleListUnspent(workerMessage);
|
// await _handleListUnspent(workerMessage);
|
||||||
|
@ -108,6 +118,7 @@ class ElectrumWorker {
|
||||||
await Future.wait(request.scripthashByAddress.entries.map((entry) async {
|
await Future.wait(request.scripthashByAddress.entries.map((entry) async {
|
||||||
final address = entry.key;
|
final address = entry.key;
|
||||||
final scripthash = entry.value;
|
final scripthash = entry.value;
|
||||||
|
|
||||||
final listener = await _electrumClient!.subscribe(
|
final listener = await _electrumClient!.subscribe(
|
||||||
ElectrumScriptHashSubscribe(scriptHash: scripthash),
|
ElectrumScriptHashSubscribe(scriptHash: scripthash),
|
||||||
);
|
);
|
||||||
|
@ -129,43 +140,214 @@ class ElectrumWorker {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> _handleGetBalance(ElectrumWorkerRequest message) async {
|
Future<void> _handleGetHistory(ElectrumWorkerGetHistoryRequest result) async {
|
||||||
// try {
|
final Map<String, AddressHistoriesResponse> histories = {};
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
final addresses = result.addresses;
|
||||||
// final result = await _electrumClient!.request(
|
|
||||||
// ElectrumGetScriptHashBalance(scriptHash: scriptHash),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// final balance = ElectrumBalance(
|
await Future.wait(addresses.map((addressRecord) async {
|
||||||
// confirmed: result['confirmed'] as int? ?? 0,
|
final history = await _electrumClient!.request(ElectrumScriptHashGetHistory(
|
||||||
// unconfirmed: result['unconfirmed'] as int? ?? 0,
|
scriptHash: addressRecord.scriptHash,
|
||||||
// frozen: 0,
|
));
|
||||||
// );
|
|
||||||
|
|
||||||
// _sendResponse(message.method, balance.toJSON());
|
if (history.isNotEmpty) {
|
||||||
// } catch (e, s) {
|
addressRecord.setAsUsed();
|
||||||
// print(s);
|
addressRecord.txCount = history.length;
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleGetHistory(ElectrumWorkerMessage message) async {
|
await Future.wait(history.map((transaction) async {
|
||||||
// try {
|
final txid = transaction['tx_hash'] as String;
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
final height = transaction['height'] as int;
|
||||||
// final result = await electrumClient.getHistory(scriptHash);
|
late ElectrumTransactionInfo tx;
|
||||||
// _sendResponse(message.method, jsonEncode(result));
|
|
||||||
// } catch (e) {
|
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleListUnspent(ElectrumWorkerMessage message) async {
|
try {
|
||||||
// try {
|
// Exception thrown on null
|
||||||
// final scriptHash = message.params['scriptHash'] as String;
|
tx = result.storedTxs.firstWhere((tx) => tx.id == txid);
|
||||||
// final result = await electrumClient.listUnspent(scriptHash);
|
|
||||||
// _sendResponse(message.method, jsonEncode(result));
|
if (height > 0) {
|
||||||
// } catch (e) {
|
tx.height = height;
|
||||||
// _sendError(message.method, e.toString());
|
|
||||||
// }
|
// the tx's block itself is the first confirmation so add 1
|
||||||
// }
|
tx.confirmations = result.chainTip - height + 1;
|
||||||
|
tx.isPending = tx.confirmations == 0;
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
tx = ElectrumTransactionInfo.fromElectrumBundle(
|
||||||
|
await getTransactionExpanded(
|
||||||
|
hash: txid,
|
||||||
|
currentChainTip: result.chainTip,
|
||||||
|
mempoolAPIEnabled: result.mempoolAPIEnabled,
|
||||||
|
),
|
||||||
|
result.walletType,
|
||||||
|
result.network,
|
||||||
|
addresses: result.addresses.map((addr) => addr.address).toSet(),
|
||||||
|
height: height,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final addressHistories = histories[addressRecord.address];
|
||||||
|
if (addressHistories != null) {
|
||||||
|
addressHistories.txs.add(tx);
|
||||||
|
} else {
|
||||||
|
histories[addressRecord.address] = AddressHistoriesResponse(
|
||||||
|
addressRecord: addressRecord,
|
||||||
|
txs: [tx],
|
||||||
|
walletType: result.walletType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Future.value(null);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return histories;
|
||||||
|
}));
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerGetHistoryResponse(result: histories.values.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ElectrumTransactionBundle> getTransactionExpanded({
|
||||||
|
required String hash,
|
||||||
|
required int currentChainTip,
|
||||||
|
required bool mempoolAPIEnabled,
|
||||||
|
bool getConfirmations = true,
|
||||||
|
}) async {
|
||||||
|
int? time;
|
||||||
|
int? height;
|
||||||
|
int? confirmations;
|
||||||
|
|
||||||
|
final transactionHex = await _electrumClient!.request(
|
||||||
|
ElectrumGetTransactionHex(transactionHash: hash),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getConfirmations) {
|
||||||
|
if (mempoolAPIEnabled) {
|
||||||
|
try {
|
||||||
|
final txVerbose = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (txVerbose.statusCode == 200 &&
|
||||||
|
txVerbose.body.isNotEmpty &&
|
||||||
|
jsonDecode(txVerbose.body) != null) {
|
||||||
|
height = jsonDecode(txVerbose.body)['block_height'] as int;
|
||||||
|
|
||||||
|
final blockHash = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (blockHash.statusCode == 200 &&
|
||||||
|
blockHash.body.isNotEmpty &&
|
||||||
|
jsonDecode(blockHash.body) != null) {
|
||||||
|
final blockResponse = await http.get(
|
||||||
|
Uri.parse(
|
||||||
|
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (blockResponse.statusCode == 200 &&
|
||||||
|
blockResponse.body.isNotEmpty &&
|
||||||
|
jsonDecode(blockResponse.body)['timestamp'] != null) {
|
||||||
|
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height != null) {
|
||||||
|
if (time == null && height > 0) {
|
||||||
|
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
|
||||||
|
}
|
||||||
|
|
||||||
|
final tip = currentChainTip;
|
||||||
|
if (tip > 0 && height > 0) {
|
||||||
|
// Add one because the block itself is the first confirmation
|
||||||
|
confirmations = tip - height + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final original = BtcTransaction.fromRaw(transactionHex);
|
||||||
|
final ins = <BtcTransaction>[];
|
||||||
|
|
||||||
|
for (final vin in original.inputs) {
|
||||||
|
final inputTransactionHex = await _electrumClient!.request(
|
||||||
|
ElectrumGetTransactionHex(transactionHash: vin.txId),
|
||||||
|
);
|
||||||
|
|
||||||
|
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ElectrumTransactionBundle(
|
||||||
|
original,
|
||||||
|
ins: ins,
|
||||||
|
time: time,
|
||||||
|
confirmations: confirmations ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<void> _handleListUnspents(ElectrumWorkerGetBalanceRequest request) async {
|
||||||
|
// final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
// for (final scripthash in request.scripthashes) {
|
||||||
|
// final balanceFuture = _electrumClient!.request(
|
||||||
|
// ElectrumGetScriptHashBalance(scriptHash: scripthash),
|
||||||
|
// );
|
||||||
|
// balanceFutures.add(balanceFuture);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var totalConfirmed = 0;
|
||||||
|
// var totalUnconfirmed = 0;
|
||||||
|
|
||||||
|
// final balances = await Future.wait(balanceFutures);
|
||||||
|
|
||||||
|
// for (final balance in balances) {
|
||||||
|
// final confirmed = balance['confirmed'] as int? ?? 0;
|
||||||
|
// final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
||||||
|
// totalConfirmed += confirmed;
|
||||||
|
// totalUnconfirmed += unconfirmed;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _sendResponse(ElectrumWorkerGetBalanceResponse(
|
||||||
|
// result: ElectrumBalance(
|
||||||
|
// confirmed: totalConfirmed,
|
||||||
|
// unconfirmed: totalUnconfirmed,
|
||||||
|
// frozen: 0,
|
||||||
|
// ),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> _handleGetBalance(ElectrumWorkerGetBalanceRequest request) async {
|
||||||
|
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||||
|
|
||||||
|
for (final scripthash in request.scripthashes) {
|
||||||
|
final balanceFuture = _electrumClient!.request(
|
||||||
|
ElectrumGetScriptHashBalance(scriptHash: scripthash),
|
||||||
|
);
|
||||||
|
balanceFutures.add(balanceFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalConfirmed = 0;
|
||||||
|
var totalUnconfirmed = 0;
|
||||||
|
|
||||||
|
final balances = await Future.wait(balanceFutures);
|
||||||
|
|
||||||
|
for (final balance in balances) {
|
||||||
|
final confirmed = balance['confirmed'] as int? ?? 0;
|
||||||
|
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
|
||||||
|
totalConfirmed += confirmed;
|
||||||
|
totalUnconfirmed += unconfirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendResponse(ElectrumWorkerGetBalanceResponse(
|
||||||
|
result: ElectrumBalance(
|
||||||
|
confirmed: totalConfirmed,
|
||||||
|
unconfirmed: totalUnconfirmed,
|
||||||
|
frozen: 0,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
52
cw_bitcoin/lib/electrum_worker/methods/get_balance.dart
Normal file
52
cw_bitcoin/lib/electrum_worker/methods/get_balance.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetBalanceRequest({required this.scripthashes});
|
||||||
|
|
||||||
|
final Set<String> scripthashes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetBalanceRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetBalanceRequest(
|
||||||
|
scripthashes: (json['scripthashes'] as List<String>).toSet(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'method': method, 'scripthashes': scripthashes.toList()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetBalanceError({required String error}) : super(error: error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetBalanceResponse
|
||||||
|
extends ElectrumWorkerResponse<ElectrumBalance, Map<String, int>?> {
|
||||||
|
ElectrumWorkerGetBalanceResponse({required super.result, super.error})
|
||||||
|
: super(method: ElectrumRequestMethods.getBalance.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, int>? resultJson(result) {
|
||||||
|
return {"confirmed": result.confirmed, "unconfirmed": result.unconfirmed};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetBalanceResponse(
|
||||||
|
result: ElectrumBalance(
|
||||||
|
confirmed: json['result']['confirmed'] as int,
|
||||||
|
unconfirmed: json['result']['unconfirmed'] as int,
|
||||||
|
frozen: 0,
|
||||||
|
),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
110
cw_bitcoin/lib/electrum_worker/methods/get_history.dart
Normal file
110
cw_bitcoin/lib/electrum_worker/methods/get_history.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerGetHistoryRequest({
|
||||||
|
required this.addresses,
|
||||||
|
required this.storedTxs,
|
||||||
|
required this.walletType,
|
||||||
|
required this.chainTip,
|
||||||
|
required this.network,
|
||||||
|
required this.mempoolAPIEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<BitcoinAddressRecord> addresses;
|
||||||
|
final List<ElectrumTransactionInfo> storedTxs;
|
||||||
|
final WalletType walletType;
|
||||||
|
final int chainTip;
|
||||||
|
final BasedUtxoNetwork network;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getHistory.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetHistoryRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
final walletType = WalletType.values[json['walletType'] as int];
|
||||||
|
|
||||||
|
return ElectrumWorkerGetHistoryRequest(
|
||||||
|
addresses: (json['addresses'] as List)
|
||||||
|
.map((e) => BitcoinAddressRecord.fromJSON(e as String))
|
||||||
|
.toList(),
|
||||||
|
storedTxs: (json['storedTxIds'] as List)
|
||||||
|
.map((e) => ElectrumTransactionInfo.fromJson(e as Map<String, dynamic>, walletType))
|
||||||
|
.toList(),
|
||||||
|
walletType: walletType,
|
||||||
|
chainTip: json['chainTip'] as int,
|
||||||
|
network: BasedUtxoNetwork.fromName(json['network'] as String),
|
||||||
|
mempoolAPIEnabled: json['mempoolAPIEnabled'] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'method': method,
|
||||||
|
'addresses': addresses.map((e) => e.toJSON()).toList(),
|
||||||
|
'storedTxIds': storedTxs.map((e) => e.toJson()).toList(),
|
||||||
|
'walletType': walletType.index,
|
||||||
|
'chainTip': chainTip,
|
||||||
|
'network': network.value,
|
||||||
|
'mempoolAPIEnabled': mempoolAPIEnabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerGetHistoryError({required String error}) : super(error: error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumRequestMethods.getHistory.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddressHistoriesResponse {
|
||||||
|
final BitcoinAddressRecord addressRecord;
|
||||||
|
final List<ElectrumTransactionInfo> txs;
|
||||||
|
final WalletType walletType;
|
||||||
|
|
||||||
|
AddressHistoriesResponse(
|
||||||
|
{required this.addressRecord, required this.txs, required this.walletType});
|
||||||
|
|
||||||
|
factory AddressHistoriesResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
final walletType = WalletType.values[json['walletType'] as int];
|
||||||
|
|
||||||
|
return AddressHistoriesResponse(
|
||||||
|
addressRecord: BitcoinAddressRecord.fromJSON(json['address'] as String),
|
||||||
|
txs: (json['txs'] as List)
|
||||||
|
.map((e) => ElectrumTransactionInfo.fromJson(e as Map<String, dynamic>, walletType))
|
||||||
|
.toList(),
|
||||||
|
walletType: walletType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'address': addressRecord.toJSON(),
|
||||||
|
'txs': txs.map((e) => e.toJson()).toList(),
|
||||||
|
'walletType': walletType.index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerGetHistoryResponse
|
||||||
|
extends ElectrumWorkerResponse<List<AddressHistoriesResponse>, List<Map<String, dynamic>>> {
|
||||||
|
ElectrumWorkerGetHistoryResponse({required super.result, super.error})
|
||||||
|
: super(method: ElectrumRequestMethods.getHistory.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Map<String, dynamic>> resultJson(result) {
|
||||||
|
return result.map((e) => e.toJson()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerGetHistoryResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerGetHistoryResponse(
|
||||||
|
result: (json['result'] as List)
|
||||||
|
.map((e) => AddressHistoriesResponse.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
error: json['error'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
53
cw_bitcoin/lib/electrum_worker/methods/list_unspents.dart
Normal file
53
cw_bitcoin/lib/electrum_worker/methods/list_unspents.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// part of 'methods.dart';
|
||||||
|
|
||||||
|
// class ElectrumWorkerGetBalanceRequest implements ElectrumWorkerRequest {
|
||||||
|
// ElectrumWorkerGetBalanceRequest({required this.scripthashes});
|
||||||
|
|
||||||
|
// final Set<String> scripthashes;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// factory ElectrumWorkerGetBalanceRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
// return ElectrumWorkerGetBalanceRequest(
|
||||||
|
// scripthashes: (json['scripthashes'] as List<String>).toSet(),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Map<String, dynamic> toJson() {
|
||||||
|
// return {'method': method, 'scripthashes': scripthashes.toList()};
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class ElectrumWorkerGetBalanceError extends ElectrumWorkerErrorResponse {
|
||||||
|
// ElectrumWorkerGetBalanceError({required String error}) : super(error: error);
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// final String method = ElectrumRequestMethods.getBalance.method;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class ElectrumWorkerGetBalanceResponse
|
||||||
|
// extends ElectrumWorkerResponse<ElectrumBalance, Map<String, int>?> {
|
||||||
|
// ElectrumWorkerGetBalanceResponse({required super.result, super.error})
|
||||||
|
// : super(method: ElectrumRequestMethods.getBalance.method);
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Map<String, int>? resultJson(result) {
|
||||||
|
// return {"confirmed": result.confirmed, "unconfirmed": result.unconfirmed};
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// factory ElectrumWorkerGetBalanceResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
// return ElectrumWorkerGetBalanceResponse(
|
||||||
|
// result: ElectrumBalance(
|
||||||
|
// confirmed: json['result']['confirmed'] as int,
|
||||||
|
// unconfirmed: json['result']['unconfirmed'] as int,
|
||||||
|
// frozen: 0,
|
||||||
|
// ),
|
||||||
|
// error: json['error'] as String?,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_methods.dart';
|
||||||
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
part 'connection.dart';
|
part 'connection.dart';
|
||||||
part 'headers_subscribe.dart';
|
part 'headers_subscribe.dart';
|
||||||
part 'scripthashes_subscribe.dart';
|
part 'scripthashes_subscribe.dart';
|
||||||
|
part 'get_balance.dart';
|
||||||
|
part 'get_history.dart';
|
||||||
|
|
|
@ -774,113 +774,114 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
try {
|
throw UnimplementedError();
|
||||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
// try {
|
||||||
|
// final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||||
|
|
||||||
await Future.wait(LITECOIN_ADDRESS_TYPES
|
// await Future.wait(LITECOIN_ADDRESS_TYPES
|
||||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
// .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
||||||
|
|
||||||
return historiesWithDetails;
|
// return historiesWithDetails;
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
print("fetchTransactions $e");
|
// print("fetchTransactions $e");
|
||||||
return {};
|
// return {};
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
@action
|
// @action
|
||||||
Future<void> subscribeForUpdates([
|
// Future<void> subscribeForUpdates([
|
||||||
Iterable<BitcoinAddressRecord>? unsubscribedScriptHashes,
|
// Iterable<BitcoinAddressRecord>? unsubscribedScriptHashes,
|
||||||
]) async {
|
// ]) async {
|
||||||
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
|
// final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
|
||||||
(address) =>
|
// (address) =>
|
||||||
!scripthashesListening.contains(address.scriptHash) &&
|
// !scripthashesListening.contains(address.scriptHash) &&
|
||||||
address.type != SegwitAddresType.mweb,
|
// address.type != SegwitAddresType.mweb,
|
||||||
);
|
// );
|
||||||
|
|
||||||
return super.subscribeForUpdates(unsubscribedScriptHashes);
|
// return super.subscribeForUpdates(unsubscribedScriptHashes);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Future<ElectrumBalance> fetchBalances() async {
|
// Future<ElectrumBalance> fetchBalances() async {
|
||||||
final balance = await super.fetchBalances();
|
// final balance = await super.fetchBalances();
|
||||||
|
|
||||||
if (!mwebEnabled) {
|
// if (!mwebEnabled) {
|
||||||
return balance;
|
// return balance;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// update unspent balances:
|
// // update unspent balances:
|
||||||
await updateUnspent();
|
// await updateUnspent();
|
||||||
|
|
||||||
int confirmed = balance.confirmed;
|
// int confirmed = balance.confirmed;
|
||||||
int unconfirmed = balance.unconfirmed;
|
// int unconfirmed = balance.unconfirmed;
|
||||||
int confirmedMweb = 0;
|
// int confirmedMweb = 0;
|
||||||
int unconfirmedMweb = 0;
|
// int unconfirmedMweb = 0;
|
||||||
try {
|
// try {
|
||||||
mwebUtxosBox.values.forEach((utxo) {
|
// mwebUtxosBox.values.forEach((utxo) {
|
||||||
if (utxo.height > 0) {
|
// if (utxo.height > 0) {
|
||||||
confirmedMweb += utxo.value.toInt();
|
// confirmedMweb += utxo.value.toInt();
|
||||||
} else {
|
// } else {
|
||||||
unconfirmedMweb += utxo.value.toInt();
|
// unconfirmedMweb += utxo.value.toInt();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
if (unconfirmedMweb > 0) {
|
// if (unconfirmedMweb > 0) {
|
||||||
unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
|
// unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
|
||||||
}
|
// }
|
||||||
} catch (_) {}
|
// } catch (_) {}
|
||||||
|
|
||||||
for (var addressRecord in walletAddresses.allAddresses) {
|
// for (var addressRecord in walletAddresses.allAddresses) {
|
||||||
addressRecord.balance = 0;
|
// addressRecord.balance = 0;
|
||||||
addressRecord.txCount = 0;
|
// addressRecord.txCount = 0;
|
||||||
}
|
// }
|
||||||
|
|
||||||
unspentCoins.forEach((coin) {
|
// unspentCoins.forEach((coin) {
|
||||||
final coinInfoList = unspentCoinsInfo.values.where(
|
// final coinInfoList = unspentCoinsInfo.values.where(
|
||||||
(element) =>
|
// (element) =>
|
||||||
element.walletId.contains(id) &&
|
// element.walletId.contains(id) &&
|
||||||
element.hash.contains(coin.hash) &&
|
// element.hash.contains(coin.hash) &&
|
||||||
element.vout == coin.vout,
|
// element.vout == coin.vout,
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
// if (coinInfoList.isNotEmpty) {
|
||||||
final coinInfo = coinInfoList.first;
|
// final coinInfo = coinInfoList.first;
|
||||||
|
|
||||||
coin.isFrozen = coinInfo.isFrozen;
|
// coin.isFrozen = coinInfo.isFrozen;
|
||||||
coin.isSending = coinInfo.isSending;
|
// coin.isSending = coinInfo.isSending;
|
||||||
coin.note = coinInfo.note;
|
// coin.note = coinInfo.note;
|
||||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
// if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
// coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||||
} else {
|
// } else {
|
||||||
super.addCoinInfo(coin);
|
// super.addCoinInfo(coin);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
// update the txCount for each address using the tx history, since we can't rely on mwebd
|
// // update the txCount for each address using the tx history, since we can't rely on mwebd
|
||||||
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
// // to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
||||||
for (final tx in transactionHistory.transactions.values) {
|
// for (final tx in transactionHistory.transactions.values) {
|
||||||
// if (tx.isPending) continue;
|
// // if (tx.isPending) continue;
|
||||||
if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
// if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
// final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
||||||
for (final address in txAddresses) {
|
// for (final address in txAddresses) {
|
||||||
final addressRecord = walletAddresses.allAddresses
|
// final addressRecord = walletAddresses.allAddresses
|
||||||
.firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
// .firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
||||||
if (addressRecord == null) {
|
// if (addressRecord == null) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
addressRecord.txCount++;
|
// addressRecord.txCount++;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return ElectrumBalance(
|
// return ElectrumBalance(
|
||||||
confirmed: confirmed,
|
// confirmed: confirmed,
|
||||||
unconfirmed: unconfirmed,
|
// unconfirmed: unconfirmed,
|
||||||
frozen: balance.frozen,
|
// frozen: balance.frozen,
|
||||||
secondConfirmed: confirmedMweb,
|
// secondConfirmed: confirmedMweb,
|
||||||
secondUnconfirmed: unconfirmedMweb,
|
// secondUnconfirmed: unconfirmedMweb,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int feeRate(TransactionPriority priority) {
|
int feeRate(TransactionPriority priority) {
|
||||||
|
|
|
@ -603,7 +603,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> registerSilentPaymentsKey(Object wallet, bool active) async {
|
Future<void> registerSilentPaymentsKey(Object wallet, bool active) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
return await bitcoinWallet.registerSilentPaymentsKey();
|
return await bitcoinWallet.registerSilentPaymentsKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -634,7 +634,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as BitcoinWallet;
|
||||||
return bitcoinWallet.getNodeSupportsSilentPayments();
|
return bitcoinWallet.getNodeSupportsSilentPayments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue