mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-05-05 23:12:13 +00:00
feat: misc reviews
This commit is contained in:
parent
7da30d85b7
commit
309dca9ac9
29 changed files with 568 additions and 338 deletions
cw_bitcoin/lib
bitcoin_address_record.dartbitcoin_unspent.dartbitcoin_wallet.dartbitcoin_wallet_addresses.dartbitcoin_wallet_snapshot.dartelectrum_transaction_info.dartelectrum_wallet.dartelectrum_wallet_addresses.dartelectrum_wallet_snapshot.dart
electrum_worker
litecoin_wallet.dartlitecoin_wallet_addresses.dartlitecoin_wallet_snapshot.dartcw_bitcoin_cash/lib/src
lib
bitcoin
entities
src/screens
view_model
|
@ -4,6 +4,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
abstract class BaseBitcoinAddressRecord {
|
abstract class BaseBitcoinAddressRecord {
|
||||||
BaseBitcoinAddressRecord(
|
BaseBitcoinAddressRecord(
|
||||||
|
@ -20,16 +21,15 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
_balance = balance,
|
_balance = balance,
|
||||||
_name = name,
|
_name = name,
|
||||||
_isUsed = isUsed,
|
_isUsed = isUsed,
|
||||||
_isHidden = isHidden ?? isChange,
|
isHidden = isHidden ?? isChange,
|
||||||
_isChange = isChange;
|
_isChange = isChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
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;
|
||||||
bool _isHidden;
|
bool isHidden;
|
||||||
|
|
||||||
bool get isHidden => _isHidden;
|
|
||||||
final bool _isChange;
|
final bool _isChange;
|
||||||
|
|
||||||
bool get isChange => _isChange;
|
bool get isChange => _isChange;
|
||||||
|
@ -53,7 +53,7 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
|
|
||||||
void setAsUsed() {
|
void setAsUsed() {
|
||||||
_isUsed = true;
|
_isUsed = true;
|
||||||
_isHidden = true;
|
isHidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setNewName(String label) => _name = label;
|
void setNewName(String label) => _name = label;
|
||||||
|
@ -75,11 +75,15 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
'runtimeType': runtimeType.toString(),
|
'runtimeType': runtimeType.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
static BaseBitcoinAddressRecord fromJSON(String jsonSource) {
|
static BaseBitcoinAddressRecord fromJSON(
|
||||||
|
String jsonSource, [
|
||||||
|
DerivationInfo? derivationInfo,
|
||||||
|
BasedUtxoNetwork? network,
|
||||||
|
]) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
|
||||||
if (decoded['runtimeType'] == 'BitcoinAddressRecord') {
|
if (decoded['runtimeType'] == 'BitcoinAddressRecord') {
|
||||||
return BitcoinAddressRecord.fromJSON(jsonSource);
|
return BitcoinAddressRecord.fromJSON(jsonSource, derivationInfo, network);
|
||||||
} else if (decoded['runtimeType'] == 'BitcoinSilentPaymentAddressRecord') {
|
} else if (decoded['runtimeType'] == 'BitcoinSilentPaymentAddressRecord') {
|
||||||
return BitcoinSilentPaymentAddressRecord.fromJSON(jsonSource);
|
return BitcoinSilentPaymentAddressRecord.fromJSON(jsonSource);
|
||||||
} else if (decoded['runtimeType'] == 'BitcoinReceivedSPAddressRecord') {
|
} else if (decoded['runtimeType'] == 'BitcoinReceivedSPAddressRecord') {
|
||||||
|
@ -87,7 +91,7 @@ abstract class BaseBitcoinAddressRecord {
|
||||||
} else if (decoded['runtimeType'] == 'LitecoinMWEBAddressRecord') {
|
} else if (decoded['runtimeType'] == 'LitecoinMWEBAddressRecord') {
|
||||||
return LitecoinMWEBAddressRecord.fromJSON(jsonSource);
|
return LitecoinMWEBAddressRecord.fromJSON(jsonSource);
|
||||||
} else {
|
} else {
|
||||||
throw ArgumentError('Unknown runtimeType');
|
return BitcoinAddressRecord.fromJSON(jsonSource, derivationInfo, network);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,17 +124,34 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
factory BitcoinAddressRecord.fromJSON(
|
||||||
|
String jsonSource, [
|
||||||
|
DerivationInfo? derivationInfo,
|
||||||
|
BasedUtxoNetwork? network,
|
||||||
|
]) {
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
final decoded = json.decode(jsonSource) as Map;
|
||||||
|
final derivationInfoSnp = decoded['derivationInfo'] as Map<String, dynamic>?;
|
||||||
|
final derivationTypeSnp = decoded['derivationType'] as int?;
|
||||||
|
final cwDerivationType = derivationTypeSnp != null
|
||||||
|
? CWBitcoinDerivationType.values[derivationTypeSnp]
|
||||||
|
: derivationInfo!.derivationType == DerivationType.bip39
|
||||||
|
? CWBitcoinDerivationType.old_bip39
|
||||||
|
: CWBitcoinDerivationType.old_electrum;
|
||||||
|
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
decoded['address'] as String,
|
decoded['address'] as String,
|
||||||
index: decoded['index'] as int,
|
index: decoded['index'] as int,
|
||||||
derivationInfo: BitcoinDerivationInfo.fromJSON(
|
derivationInfo: derivationInfoSnp == null
|
||||||
decoded['derivationInfo'] as Map<String, dynamic>,
|
? [CWBitcoinDerivationType.bip39, CWBitcoinDerivationType.old_bip39]
|
||||||
),
|
.contains(cwDerivationType)
|
||||||
// TODO: make nullable maybe?
|
? BitcoinDerivationInfo.fromDerivationAndAddress(
|
||||||
cwDerivationType: CWBitcoinDerivationType.values[decoded['derivationType'] as int],
|
BitcoinDerivationType.bip39,
|
||||||
|
decoded['address'] as String,
|
||||||
|
network!,
|
||||||
|
)
|
||||||
|
: BitcoinDerivationInfos.ELECTRUM
|
||||||
|
: BitcoinDerivationInfo.fromJSON(derivationInfoSnp),
|
||||||
|
cwDerivationType: cwDerivationType,
|
||||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||||
isChange: decoded['isChange'] as bool? ?? false,
|
isChange: decoded['isChange'] as bool? ?? false,
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
|
@ -218,7 +239,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||||
return BitcoinSilentPaymentAddressRecord(
|
return BitcoinSilentPaymentAddressRecord(
|
||||||
decoded['address'] as String,
|
decoded['address'] as String,
|
||||||
derivationPath:
|
derivationPath:
|
||||||
decoded['derivationPath'] as String? ?? BitcoinWalletAddressesBase.OLD_SP_SPEND_PATH,
|
(decoded['derivationPath'] as String?) ?? BitcoinWalletAddressesBase.OLD_SP_PATH,
|
||||||
labelIndex: decoded['index'] as int,
|
labelIndex: decoded['index'] as int,
|
||||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||||
txCount: decoded['txCount'] as int? ?? 0,
|
txCount: decoded['txCount'] as int? ?? 0,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_core/unspent_transaction_output.dart';
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
class BitcoinUnspent extends Unspent {
|
class BitcoinUnspent extends Unspent {
|
||||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||||
|
@ -10,9 +11,19 @@ class BitcoinUnspent extends Unspent {
|
||||||
factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) =>
|
factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) =>
|
||||||
BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout);
|
BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout);
|
||||||
|
|
||||||
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) {
|
factory BitcoinUnspent.fromJSON(
|
||||||
|
BaseBitcoinAddressRecord? address,
|
||||||
|
Map<String, dynamic> json, [
|
||||||
|
DerivationInfo? derivationInfo,
|
||||||
|
BasedUtxoNetwork? network,
|
||||||
|
]) {
|
||||||
return BitcoinUnspent(
|
return BitcoinUnspent(
|
||||||
address ?? BaseBitcoinAddressRecord.fromJSON(json['address_record'] as String),
|
address ??
|
||||||
|
BaseBitcoinAddressRecord.fromJSON(
|
||||||
|
json['address_record'] as String,
|
||||||
|
derivationInfo,
|
||||||
|
network,
|
||||||
|
),
|
||||||
json['tx_hash'] as String,
|
json['tx_hash'] as String,
|
||||||
int.parse(json['value'].toString()),
|
int.parse(json['value'].toString()),
|
||||||
int.parse(json['tx_pos'].toString()),
|
int.parse(json['tx_pos'].toString()),
|
||||||
|
|
|
@ -144,7 +144,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
snp = await BitcoinWalletSnapshot.load(
|
snp = await BitcoinWalletSnapshot.load(
|
||||||
encryptionFileUtils,
|
encryptionFileUtils,
|
||||||
name,
|
name,
|
||||||
walletInfo.type,
|
walletInfo,
|
||||||
password,
|
password,
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
|
@ -209,27 +209,29 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final isNamedElectrs = node?.uri.host.contains("electrs") ?? false;
|
final isNamedElectrs = node?.uri.host.contains("electrs") ?? false;
|
||||||
if (isNamedElectrs) {
|
if (isNamedElectrs) {
|
||||||
node!.isElectrs = true;
|
node!.isElectrs = true;
|
||||||
|
node!.save();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isNamedFulcrum = node!.uri.host.contains("fulcrum");
|
final isNamedFulcrum = node!.uri.host.contains("fulcrum");
|
||||||
if (isNamedFulcrum) {
|
if (isNamedFulcrum) {
|
||||||
node!.isElectrs = false;
|
node!.isElectrs = false;
|
||||||
|
node!.save();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node!.isElectrs == null) {
|
final version = await waitSendWorker(ElectrumWorkerGetVersionRequest());
|
||||||
final version = await waitSendWorker(ElectrumWorkerGetVersionRequest());
|
|
||||||
|
|
||||||
if (version is List<String> && version.isNotEmpty) {
|
if (version is List<String> && version.isNotEmpty) {
|
||||||
final server = version[0];
|
final server = version[0];
|
||||||
|
|
||||||
if (server.toLowerCase().contains('electrs')) {
|
if (server.toLowerCase().contains('electrs')) {
|
||||||
node!.isElectrs = true;
|
|
||||||
}
|
|
||||||
} else if (version is String && version.toLowerCase().contains('electrs')) {
|
|
||||||
node!.isElectrs = true;
|
node!.isElectrs = true;
|
||||||
} else {
|
|
||||||
node!.isElectrs = false;
|
|
||||||
}
|
}
|
||||||
|
} else if (version is String && version.toLowerCase().contains('electrs')) {
|
||||||
|
node!.isElectrs = true;
|
||||||
|
} else {
|
||||||
|
node!.isElectrs = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
node!.save();
|
node!.save();
|
||||||
|
@ -293,8 +295,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
||||||
for (final utxo in utxos) {
|
for (final utxo in utxos) {
|
||||||
final rawTx =
|
final rawTx = await getTransactionHex(hash: utxo.utxo.txHash);
|
||||||
(await getTransactionExpanded(hash: utxo.utxo.txHash)).originalTransaction.toHex();
|
|
||||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||||
|
|
||||||
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
||||||
|
@ -373,7 +374,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> setSilentPaymentsScanning(bool active) async {
|
Future<void> setSilentPaymentsScanning(bool active, [int? height, bool? doSingleScan]) async {
|
||||||
silentPaymentsScanningActive = active;
|
silentPaymentsScanningActive = active;
|
||||||
final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments();
|
final nodeSupportsSilentPayments = await getNodeSupportsSilentPayments();
|
||||||
final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning;
|
final isAllowedToScan = nodeSupportsSilentPayments || allowedToSwitchNodesForScanning;
|
||||||
|
@ -382,14 +383,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
syncStatus = AttemptingScanSyncStatus();
|
syncStatus = AttemptingScanSyncStatus();
|
||||||
|
|
||||||
final tip = currentChainTip!;
|
final tip = currentChainTip!;
|
||||||
|
final beginHeight = height ?? walletInfo.restoreHeight;
|
||||||
|
|
||||||
if (tip == walletInfo.restoreHeight) {
|
if (tip == beginHeight) {
|
||||||
syncStatus = SyncedTipSyncStatus(tip);
|
syncStatus = SyncedTipSyncStatus(tip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tip > walletInfo.restoreHeight) {
|
if (tip > beginHeight) {
|
||||||
_setListeners(walletInfo.restoreHeight);
|
_requestTweakScanning(beginHeight, doSingleScan: doSingleScan);
|
||||||
}
|
}
|
||||||
} else if (syncStatus is! SyncedSyncStatus) {
|
} else if (syncStatus is! SyncedSyncStatus) {
|
||||||
await waitSendWorker(ElectrumWorkerStopScanningRequest());
|
await waitSendWorker(ElectrumWorkerStopScanningRequest());
|
||||||
|
@ -501,23 +503,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
@override
|
|
||||||
Future<void> startSync() async {
|
|
||||||
await _setInitialScanHeight();
|
|
||||||
|
|
||||||
await super.startSync();
|
|
||||||
|
|
||||||
if (alwaysScan == true) {
|
|
||||||
_setListeners(walletInfo.restoreHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
Future<void> rescan({required int height, bool? doSingleScan}) async {
|
Future<void> rescan({required int height, bool? doSingleScan}) async {
|
||||||
silentPaymentsScanningActive = true;
|
setSilentPaymentsScanning(true, height, doSingleScan);
|
||||||
_setListeners(height, doSingleScan: doSingleScan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -645,14 +634,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
final height = result.height;
|
final height = result.height;
|
||||||
if (height != null) {
|
if (height != null && result.wasSingleBlock == false) {
|
||||||
await walletInfo.updateRestoreHeight(height);
|
await walletInfo.updateRestoreHeight(height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> _setListeners(int height, {bool? doSingleScan}) async {
|
Future<void> _requestTweakScanning(int height, {bool? doSingleScan}) async {
|
||||||
if (currentChainTip == null) {
|
if (currentChainTip == null) {
|
||||||
throw Exception("currentChainTip is null");
|
throw Exception("currentChainTip is null");
|
||||||
}
|
}
|
||||||
|
@ -763,7 +752,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
|
|
||||||
// New headers received, start scanning
|
// New headers received, start scanning
|
||||||
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
|
if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
|
||||||
_setListeners(walletInfo.restoreHeight);
|
_requestTweakScanning(walletInfo.restoreHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
silentPaymentWallets = [silentPaymentWallet!];
|
silentPaymentWallets = [silentPaymentWallet!];
|
||||||
}
|
}
|
||||||
|
|
||||||
static const OLD_SP_SPEND_PATH = "m/352'/1'/0'/0'/0";
|
static const OLD_SP_PATH = "m/352'/1'/0'/#'/0";
|
||||||
static const BITCOIN_ADDRESS_TYPES = [
|
static const BITCOIN_ADDRESS_TYPES = [
|
||||||
SegwitAddressType.p2wpkh,
|
SegwitAddressType.p2wpkh,
|
||||||
P2pkhAddressType.p2pkh,
|
P2pkhAddressType.p2pkh,
|
||||||
|
@ -74,8 +74,8 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
|
|
||||||
if (silentPaymentAddresses.isEmpty) {
|
if (silentPaymentAddresses.isEmpty) {
|
||||||
if (walletInfo.isRecovery) {
|
if (walletInfo.isRecovery) {
|
||||||
final oldScanPath = Bip32PathParser.parse("m/352'/1'/0'/1'/0");
|
final oldScanPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "1"));
|
||||||
final oldSpendPath = Bip32PathParser.parse("m/352'/1'/0'/0'/0");
|
final oldSpendPath = Bip32PathParser.parse(OLD_SP_PATH.replaceFirst("#", "0"));
|
||||||
|
|
||||||
final oldSilentPaymentWallet = SilentPaymentOwner.fromPrivateKeys(
|
final oldSilentPaymentWallet = SilentPaymentOwner.fromPrivateKeys(
|
||||||
b_scan: ECPrivate(hdWallet.derive(oldScanPath).privateKey),
|
b_scan: ECPrivate(hdWallet.derive(oldScanPath).privateKey),
|
||||||
|
@ -315,7 +315,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
final usableSilentPaymentAddresses = silentPaymentAddresses
|
final usableSilentPaymentAddresses = silentPaymentAddresses
|
||||||
.where((a) =>
|
.where((a) =>
|
||||||
a.type != SegwitAddressType.p2tr &&
|
a.type != SegwitAddressType.p2tr &&
|
||||||
a.derivationPath != OLD_SP_SPEND_PATH &&
|
a.derivationPath != OLD_SP_PATH &&
|
||||||
a.isChange == false)
|
a.isChange == false)
|
||||||
.toList();
|
.toList();
|
||||||
final nextSPLabelIndex = usableSilentPaymentAddresses.length;
|
final nextSPLabelIndex = usableSilentPaymentAddresses.length;
|
||||||
|
@ -330,7 +330,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
);
|
);
|
||||||
|
|
||||||
silentPaymentAddresses.add(address);
|
silentPaymentAddresses.add(address);
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
@ -382,14 +382,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@action
|
@action
|
||||||
void updateAddressesOnReceiveScreen() {
|
void updateAddressesByType() {
|
||||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||||
addressesOnReceiveScreen.clear();
|
receiveAddressesByType.clear();
|
||||||
addressesOnReceiveScreen.addAll(silentPaymentAddresses);
|
receiveAddressesByType[SilentPaymentsAddresType.p2sp] = silentPaymentAddresses
|
||||||
|
.where((addressRecord) =>
|
||||||
|
addressRecord.type == SilentPaymentsAddresType.p2sp && !addressRecord.isChange)
|
||||||
|
.toList();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.updateAddressesOnReceiveScreen();
|
super.updateAddressesByType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -398,7 +401,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
addressesSet.addAll(addresses);
|
addressesSet.addAll(addresses);
|
||||||
this.silentPaymentAddresses.clear();
|
this.silentPaymentAddresses.clear();
|
||||||
this.silentPaymentAddresses.addAll(addressesSet);
|
this.silentPaymentAddresses.addAll(addressesSet);
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -407,7 +410,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
addressesSet.addAll(addresses);
|
addressesSet.addAll(addresses);
|
||||||
this.receivedSPAddresses.clear();
|
this.receivedSPAddresses.clear();
|
||||||
this.receivedSPAddresses.addAll(addressesSet);
|
this.receivedSPAddresses.addAll(addressesSet);
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -416,7 +419,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
|
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
|
||||||
|
|
||||||
silentPaymentAddresses.remove(addressRecord);
|
silentPaymentAddresses.remove(addressRecord);
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> get labels {
|
Map<String, String> get labels {
|
||||||
|
@ -492,6 +495,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets,
|
required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required bool isHardwareWallet,
|
required bool isHardwareWallet,
|
||||||
|
// TODO: make it used
|
||||||
List<BitcoinAddressRecord>? initialAddresses,
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||||
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
|
List<BitcoinReceivedSPAddressRecord>? initialReceivedSPAddresses,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
|
class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
BitcoinWalletSnapshot({
|
BitcoinWalletSnapshot({
|
||||||
|
@ -27,10 +27,11 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
static Future<BitcoinWalletSnapshot> load(
|
static Future<BitcoinWalletSnapshot> load(
|
||||||
EncryptionFileUtils encryptionFileUtils,
|
EncryptionFileUtils encryptionFileUtils,
|
||||||
String name,
|
String name,
|
||||||
WalletType type,
|
WalletInfo walletInfo,
|
||||||
String password,
|
String password,
|
||||||
BasedUtxoNetwork network,
|
BasedUtxoNetwork network,
|
||||||
) async {
|
) async {
|
||||||
|
final type = walletInfo.type;
|
||||||
final path = await pathForWallet(name: name, type: type);
|
final path = await pathForWallet(name: name, type: type);
|
||||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
@ -38,7 +39,7 @@ class BitcoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
|
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
|
||||||
encryptionFileUtils,
|
encryptionFileUtils,
|
||||||
name,
|
name,
|
||||||
type,
|
walletInfo,
|
||||||
password,
|
password,
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
|
|
|
@ -160,6 +160,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
List<String> inputAddresses = [];
|
List<String> inputAddresses = [];
|
||||||
List<String> outputAddresses = [];
|
List<String> outputAddresses = [];
|
||||||
|
|
||||||
|
final sentAmounts = <int>[];
|
||||||
if (bundle.ins.length > 0) {
|
if (bundle.ins.length > 0) {
|
||||||
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||||
final input = bundle.originalTransaction.inputs[i];
|
final input = bundle.originalTransaction.inputs[i];
|
||||||
|
@ -167,11 +168,13 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
final outTransaction = inputTransaction.outputs[input.txIndex];
|
final outTransaction = inputTransaction.outputs[input.txIndex];
|
||||||
inputAmount += outTransaction.amount.toInt();
|
inputAmount += outTransaction.amount.toInt();
|
||||||
if (addresses.contains(
|
if (addresses.contains(
|
||||||
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network),
|
||||||
|
)) {
|
||||||
direction = TransactionDirection.outgoing;
|
direction = TransactionDirection.outgoing;
|
||||||
inputAddresses.add(
|
inputAddresses.add(
|
||||||
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network),
|
BitcoinAddressUtils.addressFromOutputScript(outTransaction.scriptPubKey, network),
|
||||||
);
|
);
|
||||||
|
sentAmounts.add(outTransaction.amount.toInt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +217,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
||||||
// Self-send
|
// Self-send
|
||||||
direction = TransactionDirection.incoming;
|
direction = TransactionDirection.incoming;
|
||||||
amount = receivedAmounts.reduce((a, b) => a + b);
|
amount = receivedAmounts.reduce((a, b) => a + b);
|
||||||
|
} else if (sentAmounts.length > 0) {
|
||||||
|
amount = sentAmounts.reduce((a, b) => a + b);
|
||||||
}
|
}
|
||||||
|
|
||||||
final fee = inputAmount - totalOutAmount;
|
final fee = inputAmount - totalOutAmount;
|
||||||
|
|
|
@ -186,8 +186,7 @@ abstract class ElectrumWalletBase
|
||||||
onBalanceResponse(response.result);
|
onBalanceResponse(response.result);
|
||||||
break;
|
break;
|
||||||
case ElectrumRequestMethods.getHistoryMethod:
|
case ElectrumRequestMethods.getHistoryMethod:
|
||||||
final response = ElectrumWorkerGetHistoryResponse.fromJson(messageJson);
|
onHistoriesResponse(ElectrumWorkerGetHistoryResponse.fromJson(messageJson));
|
||||||
onHistoriesResponse(response.result);
|
|
||||||
break;
|
break;
|
||||||
case ElectrumRequestMethods.listunspentMethod:
|
case ElectrumRequestMethods.listunspentMethod:
|
||||||
final response = ElectrumWorkerListUnspentResponse.fromJson(messageJson);
|
final response = ElectrumWorkerListUnspentResponse.fromJson(messageJson);
|
||||||
|
@ -1270,12 +1269,10 @@ abstract class ElectrumWalletBase
|
||||||
await Future.forEach(walletAddresses.allAddresses, (BitcoinAddressRecord addressRecord) async {
|
await Future.forEach(walletAddresses.allAddresses, (BitcoinAddressRecord addressRecord) async {
|
||||||
final isChange = addressRecord.isChange;
|
final isChange = addressRecord.isChange;
|
||||||
|
|
||||||
final matchingAddressList =
|
final matchingAddressList = walletAddresses
|
||||||
(isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where(
|
.getAddressesByType(addressRecord.type, isChange)
|
||||||
(element) =>
|
.where((element) =>
|
||||||
element.type == addressRecord.type &&
|
(element as BitcoinAddressRecord).cwDerivationType == addressRecord.cwDerivationType);
|
||||||
element.cwDerivationType == addressRecord.cwDerivationType,
|
|
||||||
);
|
|
||||||
final totalMatchingAddresses = matchingAddressList.length;
|
final totalMatchingAddresses = matchingAddressList.length;
|
||||||
|
|
||||||
final matchingGapLimit = (isChange
|
final matchingGapLimit = (isChange
|
||||||
|
@ -1305,11 +1302,11 @@ abstract class ElectrumWalletBase
|
||||||
walletAddresses.updateAdresses(newAddresses);
|
walletAddresses.updateAdresses(newAddresses);
|
||||||
|
|
||||||
final newMatchingAddressList =
|
final newMatchingAddressList =
|
||||||
(isChange ? walletAddresses.changeAddresses : walletAddresses.receiveAddresses).where(
|
walletAddresses.getAddressesByType(addressRecord.type, isChange).where(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.type == addressRecord.type &&
|
element.type == addressRecord.type &&
|
||||||
element.cwDerivationType == addressRecord.cwDerivationType,
|
(element as BitcoinAddressRecord) == addressRecord.cwDerivationType,
|
||||||
);
|
);
|
||||||
printV(
|
printV(
|
||||||
"discovered ${newAddresses.length} new ${isChange ? "change" : "receive"} addresses");
|
"discovered ${newAddresses.length} new ${isChange ? "change" : "receive"} addresses");
|
||||||
printV(
|
printV(
|
||||||
|
@ -1329,7 +1326,8 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> onHistoriesResponse(List<AddressHistoriesResponse> histories) async {
|
Future<void> onHistoriesResponse(ElectrumWorkerGetHistoryResponse response) async {
|
||||||
|
final histories = response.result;
|
||||||
if (histories.isNotEmpty) {
|
if (histories.isNotEmpty) {
|
||||||
final addressesWithHistory = <BitcoinAddressRecord>[];
|
final addressesWithHistory = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
|
@ -1350,8 +1348,8 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
} else {
|
} else if (response.completed) {
|
||||||
// checkAddressesGap();
|
checkAddressesGap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1582,7 +1580,7 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identify all change outputs
|
// Identify all change outputs
|
||||||
final changeAddresses = walletAddresses.changeAddresses;
|
final changeAddresses = walletAddresses.allChangeAddresses;
|
||||||
final List<BitcoinOutput> changeOutputs = outputs
|
final List<BitcoinOutput> changeOutputs = outputs
|
||||||
.where((output) => changeAddresses
|
.where((output) => changeAddresses
|
||||||
.any((element) => element.address == output.address.toAddress(network)))
|
.any((element) => element.address == output.address.toAddress(network)))
|
||||||
|
@ -1643,6 +1641,12 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getTransactionHex({required String hash}) async {
|
||||||
|
return await waitSendWorker(
|
||||||
|
ElectrumWorkerTxHexRequest(txHash: hash, currentChainTip: currentChainTip!),
|
||||||
|
) as String;
|
||||||
|
}
|
||||||
|
|
||||||
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
|
||||||
return await waitSendWorker(
|
return await waitSendWorker(
|
||||||
ElectrumWorkerTxExpandedRequest(txHash: hash, currentChainTip: currentChainTip!),
|
ElectrumWorkerTxExpandedRequest(txHash: hash, currentChainTip: currentChainTip!),
|
||||||
|
|
|
@ -28,12 +28,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
Map<String, int>? initialChangeAddressIndex,
|
Map<String, int>? initialChangeAddressIndex,
|
||||||
BitcoinAddressType? initialAddressPageType,
|
BitcoinAddressType? initialAddressPageType,
|
||||||
}) : _allAddresses = ObservableList.of(initialAddresses ?? []),
|
}) : _allAddresses = ObservableList.of(initialAddresses ?? []),
|
||||||
addressesOnReceiveScreen =
|
|
||||||
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
|
||||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
|
|
||||||
(initialAddresses ?? []).where((addressRecord) => !addressRecord.isChange).toSet()),
|
|
||||||
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
|
|
||||||
(initialAddresses ?? []).where((addressRecord) => addressRecord.isChange).toSet()),
|
|
||||||
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
||||||
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
||||||
_addressPageType = initialAddressPageType ??
|
_addressPageType = initialAddressPageType ??
|
||||||
|
@ -46,10 +40,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
static const defaultChangeAddressesCount = 17;
|
static const defaultChangeAddressesCount = 17;
|
||||||
static const gap = 20;
|
static const gap = 20;
|
||||||
|
|
||||||
|
final walletAddressTypes = <BitcoinAddressType>[];
|
||||||
|
|
||||||
final ObservableList<BitcoinAddressRecord> _allAddresses;
|
final ObservableList<BitcoinAddressRecord> _allAddresses;
|
||||||
final ObservableList<BaseBitcoinAddressRecord> addressesOnReceiveScreen;
|
@observable
|
||||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> receiveAddressesByType = {};
|
||||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
@observable
|
||||||
|
Map<BitcoinAddressType, List<BaseBitcoinAddressRecord>> changeAddressesByType = {};
|
||||||
|
|
||||||
final BasedUtxoNetwork network;
|
final BasedUtxoNetwork network;
|
||||||
|
|
||||||
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
|
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets;
|
||||||
|
@ -61,6 +59,24 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@observable
|
@observable
|
||||||
late BitcoinAddressType _addressPageType;
|
late BitcoinAddressType _addressPageType;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<BitcoinAddressRecord> get allChangeAddresses =>
|
||||||
|
_allAddresses.where((addr) => addr.isChange).toList();
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<BaseBitcoinAddressRecord> get selectedReceiveAddresses =>
|
||||||
|
receiveAddressesByType[_addressPageType]!;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<BaseBitcoinAddressRecord> get selectedChangeAddresses =>
|
||||||
|
receiveAddressesByType[_addressPageType]!;
|
||||||
|
|
||||||
|
List<BaseBitcoinAddressRecord> getAddressesByType(
|
||||||
|
BitcoinAddressType type, [
|
||||||
|
bool isChange = false,
|
||||||
|
]) =>
|
||||||
|
isChange ? changeAddressesByType[type]! : receiveAddressesByType[type]!;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
BitcoinAddressType get addressPageType => _addressPageType;
|
BitcoinAddressType get addressPageType => _addressPageType;
|
||||||
|
|
||||||
|
@ -75,6 +91,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
return _allAddresses.firstWhere((element) => element.address == address);
|
return _allAddresses.firstWhere((element) => element.address == address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: toggle to switch
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
|
BitcoinAddressType changeAddressType = SegwitAddressType.p2wpkh;
|
||||||
|
|
||||||
|
@ -83,9 +100,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
String get address {
|
String get address {
|
||||||
String receiveAddress = "";
|
String receiveAddress = "";
|
||||||
|
|
||||||
final typeMatchingReceiveAddresses = addressesOnReceiveScreen.where((addr) => !addr.isUsed);
|
final typeMatchingReceiveAddresses = selectedReceiveAddresses.where(
|
||||||
|
(addressRecord) => !addressRecord.isUsed,
|
||||||
|
);
|
||||||
|
|
||||||
if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
|
if ((isEnabledAutoGenerateSubaddress && selectedReceiveAddresses.isEmpty) ||
|
||||||
typeMatchingReceiveAddresses.isEmpty) {
|
typeMatchingReceiveAddresses.isEmpty) {
|
||||||
receiveAddress = generateNewAddress().address;
|
receiveAddress = generateNewAddress().address;
|
||||||
} else {
|
} else {
|
||||||
|
@ -94,7 +113,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
if (typeMatchingReceiveAddresses.isNotEmpty) {
|
if (typeMatchingReceiveAddresses.isNotEmpty) {
|
||||||
if (previousAddressMatchesType &&
|
if (previousAddressMatchesType &&
|
||||||
typeMatchingReceiveAddresses.first.address != addressesOnReceiveScreen.first.address) {
|
typeMatchingReceiveAddresses.first.address != selectedReceiveAddresses.first.address) {
|
||||||
receiveAddress = previousAddressRecord!.address;
|
receiveAddress = previousAddressRecord!.address;
|
||||||
} else {
|
} else {
|
||||||
receiveAddress = typeMatchingReceiveAddresses.first.address;
|
receiveAddress = typeMatchingReceiveAddresses.first.address;
|
||||||
|
@ -143,22 +162,6 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@observable
|
@observable
|
||||||
BitcoinAddressRecord? previousAddressRecord;
|
BitcoinAddressRecord? previousAddressRecord;
|
||||||
|
|
||||||
@computed
|
|
||||||
int get totalCountOfReceiveAddresses => addressesOnReceiveScreen.fold(0, (acc, addressRecord) {
|
|
||||||
if (!addressRecord.isChange) {
|
|
||||||
return acc + 1;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
|
|
||||||
@computed
|
|
||||||
int get totalCountOfChangeAddresses => addressesOnReceiveScreen.fold(0, (acc, addressRecord) {
|
|
||||||
if (addressRecord.isChange) {
|
|
||||||
return acc + 1;
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
|
|
||||||
CWBitcoinDerivationType getHDWalletType() {
|
CWBitcoinDerivationType getHDWalletType() {
|
||||||
if (hdWallets.containsKey(CWBitcoinDerivationType.bip39)) {
|
if (hdWallets.containsKey(CWBitcoinDerivationType.bip39)) {
|
||||||
return CWBitcoinDerivationType.bip39;
|
return CWBitcoinDerivationType.bip39;
|
||||||
|
@ -171,25 +174,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
updateReceiveAddresses();
|
|
||||||
updateChangeAddresses();
|
|
||||||
updateHiddenAddresses();
|
updateHiddenAddresses();
|
||||||
await updateAddressesInBox();
|
await updateAddressesInBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<BaseBitcoinAddressRecord> getChangeAddress() async {
|
Future<BaseBitcoinAddressRecord> getChangeAddress() async {
|
||||||
updateChangeAddresses();
|
final address = selectedChangeAddresses.firstWhere(
|
||||||
|
(addr) => _isUnusedChangeAddressByType(addr, changeAddressType),
|
||||||
final address = changeAddresses.firstWhere(
|
|
||||||
(addressRecord) => _isUnusedChangeAddressByType(addressRecord, changeAddressType),
|
|
||||||
);
|
);
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
|
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
|
||||||
final newAddressIndex = addressesOnReceiveScreen.fold(
|
final newAddressIndex = selectedReceiveAddresses.fold(
|
||||||
0,
|
0,
|
||||||
(int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc,
|
(int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc,
|
||||||
);
|
);
|
||||||
|
@ -282,21 +281,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void updateAddressesOnReceiveScreen() {
|
void updateAddressesByType() {
|
||||||
addressesOnReceiveScreen.clear();
|
receiveAddressesByType.clear();
|
||||||
addressesOnReceiveScreen.addAll(_allAddresses.where(_isAddressPageTypeMatch).toList());
|
walletAddressTypes.forEach((type) {
|
||||||
}
|
receiveAddressesByType[type] =
|
||||||
|
_allAddresses.where((addr) => _isAddressByType(addr, type)).toList();
|
||||||
@action
|
changeAddressesByType[type] =
|
||||||
void updateReceiveAddresses() {
|
_allAddresses.where((addr) => _isAddressByType(addr, type)).toList();
|
||||||
receiveAddresses.clear();
|
});
|
||||||
receiveAddresses.addAll(_allAddresses.where((addressRecord) => !addressRecord.isChange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
void updateChangeAddresses() {
|
|
||||||
changeAddresses.clear();
|
|
||||||
changeAddresses.addAll(_allAddresses.where((addressRecord) => addressRecord.isChange));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -310,8 +302,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
||||||
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
|
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
|
||||||
|
|
||||||
final startIndex = (isChange ? changeAddresses : receiveAddresses)
|
final startIndex = (isChange ? selectedChangeAddresses : selectedReceiveAddresses)
|
||||||
.where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType)
|
.where((addr) =>
|
||||||
|
(addr as BitcoinAddressRecord).cwDerivationType == derivationType &&
|
||||||
|
addr.type == addressType)
|
||||||
.length;
|
.length;
|
||||||
|
|
||||||
final newAddresses = <BitcoinAddressRecord>[];
|
final newAddresses = <BitcoinAddressRecord>[];
|
||||||
|
@ -420,9 +414,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
if (replacedAddresses.isNotEmpty) {
|
if (replacedAddresses.isNotEmpty) {
|
||||||
_allAddresses.addAll(replacedAddresses);
|
_allAddresses.addAll(replacedAddresses);
|
||||||
} else {
|
} else {
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
updateReceiveAddresses();
|
|
||||||
updateChangeAddresses();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,9 +423,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
this._allAddresses.addAll(addresses);
|
this._allAddresses.addAll(addresses);
|
||||||
|
|
||||||
updateHiddenAddresses();
|
updateHiddenAddresses();
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
updateReceiveAddresses();
|
|
||||||
updateChangeAddresses();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -447,18 +437,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
@action
|
@action
|
||||||
Future<void> setAddressType(BitcoinAddressType type) async {
|
Future<void> setAddressType(BitcoinAddressType type) async {
|
||||||
_addressPageType = type;
|
_addressPageType = type;
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
walletInfo.addressPageType = addressPageType.toString();
|
walletInfo.addressPageType = addressPageType.toString();
|
||||||
await walletInfo.save();
|
await walletInfo.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isAddressPageTypeMatch(BitcoinAddressRecord addressRecord) {
|
|
||||||
return _isAddressByType(addressRecord, addressPageType);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||||
|
|
||||||
bool _isUnusedChangeAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) {
|
bool _isUnusedChangeAddressByType(BaseBitcoinAddressRecord addr, BitcoinAddressType type) {
|
||||||
return addr.isChange && !addr.isUsed && addr.type == type;
|
return addr.isChange && !addr.isUsed && addr.type == type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,11 @@ class ElectrumWalletSnapshot {
|
||||||
static Future<ElectrumWalletSnapshot> load(
|
static Future<ElectrumWalletSnapshot> load(
|
||||||
EncryptionFileUtils encryptionFileUtils,
|
EncryptionFileUtils encryptionFileUtils,
|
||||||
String name,
|
String name,
|
||||||
WalletType type,
|
WalletInfo walletInfo,
|
||||||
String password,
|
String password,
|
||||||
BasedUtxoNetwork network,
|
BasedUtxoNetwork network,
|
||||||
) async {
|
) async {
|
||||||
|
final type = walletInfo.type;
|
||||||
final path = await pathForWallet(name: name, type: type);
|
final path = await pathForWallet(name: name, type: type);
|
||||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
@ -80,8 +81,14 @@ class ElectrumWalletSnapshot {
|
||||||
derivationType: derivationType,
|
derivationType: derivationType,
|
||||||
derivationPath: derivationPath,
|
derivationPath: derivationPath,
|
||||||
unspentCoins: (data['unspent_coins'] as List?)
|
unspentCoins: (data['unspent_coins'] as List?)
|
||||||
?.map((e) => BitcoinUnspent.fromJSON(null, e as Map<String, dynamic>))
|
?.map((e) => BitcoinUnspent.fromJSON(
|
||||||
.toList() ?? [],
|
null,
|
||||||
|
e as Map<String, dynamic>,
|
||||||
|
walletInfo.derivationInfo!,
|
||||||
|
network,
|
||||||
|
))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
didInitialSync: data['didInitialSync'] as bool?,
|
didInitialSync: data['didInitialSync'] as bool?,
|
||||||
walletAddressesSnapshot: walletAddressesSnapshot,
|
walletAddressesSnapshot: walletAddressesSnapshot,
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,6 +24,7 @@ class ElectrumWorker {
|
||||||
final SendPort sendPort;
|
final SendPort sendPort;
|
||||||
ElectrumProvider? _electrumClient;
|
ElectrumProvider? _electrumClient;
|
||||||
ServerCapability? _serverCapability;
|
ServerCapability? _serverCapability;
|
||||||
|
String? get version => _serverCapability?.version;
|
||||||
BehaviorSubject<Map<String, dynamic>>? _scanningStream;
|
BehaviorSubject<Map<String, dynamic>>? _scanningStream;
|
||||||
|
|
||||||
BasedUtxoNetwork? _network;
|
BasedUtxoNetwork? _network;
|
||||||
|
@ -72,6 +73,11 @@ class ElectrumWorker {
|
||||||
ElectrumWorkerTxExpandedRequest.fromJson(messageJson),
|
ElectrumWorkerTxExpandedRequest.fromJson(messageJson),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case ElectrumWorkerMethods.txHexMethod:
|
||||||
|
await _handleGetTxHex(
|
||||||
|
ElectrumWorkerTxHexRequest.fromJson(messageJson),
|
||||||
|
);
|
||||||
|
break;
|
||||||
case ElectrumRequestMethods.headersSubscribeMethod:
|
case ElectrumRequestMethods.headersSubscribeMethod:
|
||||||
await _handleHeadersSubscribe(
|
await _handleHeadersSubscribe(
|
||||||
ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson),
|
ElectrumWorkerHeadersSubscribeRequest.fromJson(messageJson),
|
||||||
|
@ -257,6 +263,8 @@ class ElectrumWorker {
|
||||||
completed: true,
|
completed: true,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}, onError: () {
|
||||||
|
_serverCapability!.supportsBatching = false;
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
|
@ -488,9 +496,7 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txVerbose?.isEmpty ?? true) {
|
if (txVerbose?.isEmpty ?? true) {
|
||||||
txHex = await _electrumClient!.request(
|
txHex = await _getTransactionHex(hash: hash);
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: hash),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
txHex = txVerbose!['hex'] as String;
|
txHex = txVerbose!['hex'] as String;
|
||||||
}
|
}
|
||||||
|
@ -624,11 +630,11 @@ class ElectrumWorker {
|
||||||
|
|
||||||
// date is validated when the API responds with the same date at least twice
|
// date is validated when the API responds with the same date at least twice
|
||||||
// since sometimes the mempool api returns the wrong date at first
|
// since sometimes the mempool api returns the wrong date at first
|
||||||
|
final canValidateDate = request.mempoolAPIEnabled || _serverCapability!.supportsTxVerbose;
|
||||||
if (tx == null ||
|
if (tx == null ||
|
||||||
tx.original == null ||
|
tx.original == null ||
|
||||||
// TODO: use mempool api or tx verbose
|
(tx.isDateValidated != true && canValidateDate) ||
|
||||||
// (tx.isDateValidated != true && request.mempoolAPIEnabled)) {
|
tx.time == null) {
|
||||||
(tx.isDateValidated != true)) {
|
|
||||||
transactionsByIds[txid] = TxToFetch(height: height, tx: tx);
|
transactionsByIds[txid] = TxToFetch(height: height, tx: tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -832,9 +838,7 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txVerbose?.isEmpty ?? true) {
|
if (txVerbose?.isEmpty ?? true) {
|
||||||
txHex = await _electrumClient!.request(
|
txHex = await _getTransactionHex(hash: hash);
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: hash),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
txHex = txVerbose!['hex'] as String;
|
txHex = txVerbose!['hex'] as String;
|
||||||
}
|
}
|
||||||
|
@ -869,10 +873,7 @@ class ElectrumWorker {
|
||||||
final inputTransactionHexes = <String, String>{};
|
final inputTransactionHexes = <String, String>{};
|
||||||
|
|
||||||
await Future.wait(original.inputs.map((e) => e.txId).toList().map((inHash) async {
|
await Future.wait(original.inputs.map((e) => e.txId).toList().map((inHash) async {
|
||||||
final hex = await _electrumClient!.request(
|
final hex = await _getTransactionHex(hash: inHash);
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: inHash),
|
|
||||||
);
|
|
||||||
|
|
||||||
inputTransactionHexes[inHash] = hex;
|
inputTransactionHexes[inHash] = hex;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1058,6 +1059,11 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _handleGetTxHex(ElectrumWorkerTxHexRequest request) async {
|
||||||
|
final hex = await _getTransactionHex(hash: request.txHash);
|
||||||
|
_sendResponse(ElectrumWorkerTxHexResponse(hex: hex, id: request.id));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleGetTxExpanded(ElectrumWorkerTxExpandedRequest request) async {
|
Future<void> _handleGetTxExpanded(ElectrumWorkerTxExpandedRequest request) async {
|
||||||
final tx = await _getTransactionExpanded(
|
final tx = await _getTransactionExpanded(
|
||||||
hash: request.txHash,
|
hash: request.txHash,
|
||||||
|
@ -1129,11 +1135,8 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await Future.wait(hashes.map((hash) async {
|
await Future.wait(hashes.map((hash) async {
|
||||||
final history = await _electrumClient!.request(
|
final hex = await _getTransactionHex(hash: hash);
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: hash),
|
transactionHexes.add(hex);
|
||||||
);
|
|
||||||
|
|
||||||
transactionHexes.add(history);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1248,9 +1251,7 @@ class ElectrumWorker {
|
||||||
time = transactionVerbose['time'] as int?;
|
time = transactionVerbose['time'] as int?;
|
||||||
confirmations = transactionVerbose['confirmations'] as int?;
|
confirmations = transactionVerbose['confirmations'] as int?;
|
||||||
} else {
|
} else {
|
||||||
transactionHex = await _electrumClient!.request(
|
transactionHex = await _getTransactionHex(hash: hash);
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: hash),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getTime && _walletType == WalletType.bitcoin) {
|
if (getTime && _walletType == WalletType.bitcoin) {
|
||||||
|
@ -1271,11 +1272,7 @@ class ElectrumWorker {
|
||||||
final ins = <BtcTransaction>[];
|
final ins = <BtcTransaction>[];
|
||||||
|
|
||||||
for (final vin in original.inputs) {
|
for (final vin in original.inputs) {
|
||||||
final inputTransactionHex = await _electrumClient!.request(
|
final inputTransactionHex = await _getTransactionHex(hash: vin.txId);
|
||||||
// TODO: _getTXHex
|
|
||||||
ElectrumRequestGetTransactionHex(transactionHash: vin.txId),
|
|
||||||
);
|
|
||||||
|
|
||||||
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1311,12 +1308,7 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await Future.wait(hashes.map((hash) async {
|
await Future.wait(hashes.map((hash) async {
|
||||||
final hex = await _electrumClient!.request(
|
final hex = await _getTransactionHex(hash: hash);
|
||||||
ElectrumRequestGetTransactionHex(
|
|
||||||
transactionHash: hash,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
inputTransactionHexById[hash] = hex;
|
inputTransactionHexById[hash] = hex;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -1324,6 +1316,14 @@ class ElectrumWorker {
|
||||||
return inputTransactionHexById;
|
return inputTransactionHexById;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> _getTransactionHex({required String hash}) async {
|
||||||
|
final hex = await _electrumClient!.request(
|
||||||
|
ElectrumRequestGetTransactionHex(transactionHash: hash),
|
||||||
|
);
|
||||||
|
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleGetFeeRates(ElectrumWorkerGetFeesRequest request) async {
|
Future<void> _handleGetFeeRates(ElectrumWorkerGetFeesRequest request) async {
|
||||||
if (request.mempoolAPIEnabled && _walletType == WalletType.bitcoin) {
|
if (request.mempoolAPIEnabled && _walletType == WalletType.bitcoin) {
|
||||||
try {
|
try {
|
||||||
|
@ -1350,7 +1350,7 @@ class ElectrumWorker {
|
||||||
// this guarantees that, even if all fees are low and equal,
|
// this guarantees that, even if all fees are low and equal,
|
||||||
// higher priority fee txs can be consumed when chain fees start surging
|
// higher priority fee txs can be consumed when chain fees start surging
|
||||||
|
|
||||||
_sendResponse(
|
return _sendResponse(
|
||||||
ElectrumWorkerGetFeesResponse(
|
ElectrumWorkerGetFeesResponse(
|
||||||
result: BitcoinAPITransactionPriorities(
|
result: BitcoinAPITransactionPriorities(
|
||||||
minimum: minimum,
|
minimum: minimum,
|
||||||
|
@ -1362,24 +1362,17 @@ class ElectrumWorker {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (_) {}
|
||||||
_sendResponse(
|
|
||||||
ElectrumWorkerGetFeesResponse(
|
|
||||||
result: ElectrumTransactionPriorities.fromList(
|
|
||||||
await _electrumClient!.getFeeRates(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_sendResponse(
|
|
||||||
ElectrumWorkerGetFeesResponse(
|
|
||||||
result: ElectrumTransactionPriorities.fromList(
|
|
||||||
await _electrumClient!.getFeeRates(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the above didn't run or failed, fallback to Electrum fees anyway
|
||||||
|
_sendResponse(
|
||||||
|
ElectrumWorkerGetFeesResponse(
|
||||||
|
result: ElectrumTransactionPriorities.fromList(
|
||||||
|
await _electrumClient!.getFeeRates(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleCheckTweaks(ElectrumWorkerCheckTweaksRequest request) async {
|
Future<void> _handleCheckTweaks(ElectrumWorkerCheckTweaksRequest request) async {
|
||||||
|
@ -1440,11 +1433,11 @@ class ElectrumWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial status UI update, send how many blocks in total to scan
|
// Initial status UI update, send how many blocks in total to scan
|
||||||
// TODO: isSingleScan : dont update restoreHeight
|
|
||||||
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
result: TweaksSyncResponse(
|
result: TweaksSyncResponse(
|
||||||
height: syncHeight,
|
height: syncHeight,
|
||||||
syncStatus: StartingScanSyncStatus(syncHeight),
|
syncStatus: StartingScanSyncStatus(syncHeight),
|
||||||
|
wasSingleBlock: scanData.isSingleScan,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1493,7 +1486,11 @@ class ElectrumWorker {
|
||||||
? SyncingSyncStatus(1, 0)
|
? SyncingSyncStatus(1, 0)
|
||||||
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
||||||
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
result: TweaksSyncResponse(height: syncHeight, syncStatus: syncingStatus),
|
result: TweaksSyncResponse(
|
||||||
|
height: syncHeight,
|
||||||
|
syncStatus: syncingStatus,
|
||||||
|
wasSingleBlock: scanData.isSingleScan,
|
||||||
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
final tweakHeight = response.block;
|
final tweakHeight = response.block;
|
||||||
|
@ -1509,20 +1506,21 @@ class ElectrumWorker {
|
||||||
try {
|
try {
|
||||||
final addToWallet = {};
|
final addToWallet = {};
|
||||||
|
|
||||||
receivers.forEach((receiver) {
|
// receivers.forEach((receiver) {
|
||||||
// scanOutputs called from rust here
|
// scanOutputs called from rust here
|
||||||
final scanResult = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver);
|
final receiver = receivers.first;
|
||||||
|
final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
|
||||||
|
|
||||||
if (scanResult.isEmpty) {
|
if (scanResult.isEmpty) {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addToWallet[receiver.BSpend] == null) {
|
if (addToWallet[receiver.BSpend] == null) {
|
||||||
addToWallet[receiver.BSpend] = scanResult;
|
addToWallet[receiver.BSpend] = scanResult;
|
||||||
} else {
|
} else {
|
||||||
addToWallet[receiver.BSpend].addAll(scanResult);
|
addToWallet[receiver.BSpend].addAll(scanResult);
|
||||||
}
|
}
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (addToWallet.isEmpty) {
|
if (addToWallet.isEmpty) {
|
||||||
// no results tx, continue to next tx
|
// no results tx, continue to next tx
|
||||||
|
@ -1588,6 +1586,7 @@ class ElectrumWorker {
|
||||||
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
|
||||||
result: TweaksSyncResponse(
|
result: TweaksSyncResponse(
|
||||||
transactions: {txInfo.id: TweakResponseData(txInfo: txInfo, unspents: unspents)},
|
transactions: {txInfo.id: TweakResponseData(txInfo: txInfo, unspents: unspents)},
|
||||||
|
wasSingleBlock: scanData.isSingleScan,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1604,7 +1603,7 @@ class ElectrumWorker {
|
||||||
|
|
||||||
syncHeight = tweakHeight;
|
syncHeight = tweakHeight;
|
||||||
|
|
||||||
if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) {
|
if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
|
||||||
_sendResponse(
|
_sendResponse(
|
||||||
ElectrumWorkerTweaksSubscribeResponse(
|
ElectrumWorkerTweaksSubscribeResponse(
|
||||||
result: TweaksSyncResponse(
|
result: TweaksSyncResponse(
|
||||||
|
@ -1612,6 +1611,7 @@ class ElectrumWorker {
|
||||||
syncStatus: scanData.isSingleScan
|
syncStatus: scanData.isSingleScan
|
||||||
? SyncedSyncStatus()
|
? SyncedSyncStatus()
|
||||||
: SyncedTipSyncStatus(scanData.chainTip),
|
: SyncedTipSyncStatus(scanData.chainTip),
|
||||||
|
wasSingleBlock: scanData.isSingleScan,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1627,15 +1627,7 @@ class ElectrumWorker {
|
||||||
|
|
||||||
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
|
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {
|
||||||
_sendResponse(
|
_sendResponse(
|
||||||
ElectrumWorkerGetVersionResponse(
|
ElectrumWorkerGetVersionResponse(result: version!, id: request.id),
|
||||||
result: await _electrumClient!.request(
|
|
||||||
ElectrumRequestVersion(
|
|
||||||
clientName: "",
|
|
||||||
protocolVersion: "1.4",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
id: request.id,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,14 @@ class ElectrumWorkerMethods {
|
||||||
static const String connectionMethod = "connection";
|
static const String connectionMethod = "connection";
|
||||||
static const String unknownMethod = "unknown";
|
static const String unknownMethod = "unknown";
|
||||||
static const String txHashMethod = "txHash";
|
static const String txHashMethod = "txHash";
|
||||||
|
static const String txHexMethod = "txHex";
|
||||||
static const String checkTweaksMethod = "checkTweaks";
|
static const String checkTweaksMethod = "checkTweaks";
|
||||||
static const String stopScanningMethod = "stopScanning";
|
static const String stopScanningMethod = "stopScanning";
|
||||||
|
|
||||||
static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod);
|
static const ElectrumWorkerMethods connect = ElectrumWorkerMethods._(connectionMethod);
|
||||||
static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod);
|
static const ElectrumWorkerMethods unknown = ElectrumWorkerMethods._(unknownMethod);
|
||||||
static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod);
|
static const ElectrumWorkerMethods txHash = ElectrumWorkerMethods._(txHashMethod);
|
||||||
|
static const ElectrumWorkerMethods txHex = ElectrumWorkerMethods._(txHexMethod);
|
||||||
static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod);
|
static const ElectrumWorkerMethods checkTweaks = ElectrumWorkerMethods._(checkTweaksMethod);
|
||||||
static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod);
|
static const ElectrumWorkerMethods stopScanning = ElectrumWorkerMethods._(stopScanningMethod);
|
||||||
|
|
||||||
|
|
77
cw_bitcoin/lib/electrum_worker/methods/get_tx_hex.dart
Normal file
77
cw_bitcoin/lib/electrum_worker/methods/get_tx_hex.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
part of 'methods.dart';
|
||||||
|
|
||||||
|
class ElectrumWorkerTxHexRequest implements ElectrumWorkerRequest {
|
||||||
|
ElectrumWorkerTxHexRequest({
|
||||||
|
required this.txHash,
|
||||||
|
required this.currentChainTip,
|
||||||
|
this.mempoolAPIEnabled = false,
|
||||||
|
this.id,
|
||||||
|
this.completed = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String txHash;
|
||||||
|
final int currentChainTip;
|
||||||
|
final bool mempoolAPIEnabled;
|
||||||
|
final int? id;
|
||||||
|
final bool completed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String method = ElectrumWorkerMethods.txHex.method;
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTxHexRequest.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTxHexRequest(
|
||||||
|
txHash: json['txHash'] as String,
|
||||||
|
currentChainTip: json['currentChainTip'] as int,
|
||||||
|
mempoolAPIEnabled: json['mempoolAPIEnabled'] as bool,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
completed: json['completed'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'method': method,
|
||||||
|
'id': id,
|
||||||
|
'completed': completed,
|
||||||
|
'txHash': txHash,
|
||||||
|
'currentChainTip': currentChainTip,
|
||||||
|
'mempoolAPIEnabled': mempoolAPIEnabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTxHexError extends ElectrumWorkerErrorResponse {
|
||||||
|
ElectrumWorkerTxHexError({
|
||||||
|
required String error,
|
||||||
|
super.id,
|
||||||
|
}) : super(error: error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get method => ElectrumWorkerMethods.txHex.method;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrumWorkerTxHexResponse extends ElectrumWorkerResponse<String, String> {
|
||||||
|
ElectrumWorkerTxHexResponse({
|
||||||
|
required String hex,
|
||||||
|
super.error,
|
||||||
|
super.id,
|
||||||
|
super.completed,
|
||||||
|
}) : super(result: hex, method: ElectrumWorkerMethods.txHex.method);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String resultJson(result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
factory ElectrumWorkerTxHexResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ElectrumWorkerTxHexResponse(
|
||||||
|
hex: json['result'] as String,
|
||||||
|
error: json['error'] as String?,
|
||||||
|
id: json['id'] as int?,
|
||||||
|
completed: json['completed'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ part 'scripthashes_subscribe.dart';
|
||||||
part 'get_balance.dart';
|
part 'get_balance.dart';
|
||||||
part 'get_history.dart';
|
part 'get_history.dart';
|
||||||
part 'get_tx_expanded.dart';
|
part 'get_tx_expanded.dart';
|
||||||
|
part 'get_tx_hex.dart';
|
||||||
part 'broadcast.dart';
|
part 'broadcast.dart';
|
||||||
part 'list_unspent.dart';
|
part 'list_unspent.dart';
|
||||||
part 'tweaks_subscribe.dart';
|
part 'tweaks_subscribe.dart';
|
||||||
|
|
|
@ -143,14 +143,21 @@ class TweaksSyncResponse {
|
||||||
int? height;
|
int? height;
|
||||||
SyncStatus? syncStatus;
|
SyncStatus? syncStatus;
|
||||||
Map<String, TweakResponseData>? transactions = {};
|
Map<String, TweakResponseData>? transactions = {};
|
||||||
|
final bool wasSingleBlock;
|
||||||
|
|
||||||
TweaksSyncResponse({this.height, this.syncStatus, this.transactions});
|
TweaksSyncResponse({
|
||||||
|
required this.wasSingleBlock,
|
||||||
|
this.height,
|
||||||
|
this.syncStatus,
|
||||||
|
this.transactions,
|
||||||
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'height': height,
|
'height': height,
|
||||||
'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!),
|
'syncStatus': syncStatus == null ? null : syncStatusToJson(syncStatus!),
|
||||||
'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())),
|
'transactions': transactions?.map((key, value) => MapEntry(key, value.toJson())),
|
||||||
|
'wasSingleBlock': wasSingleBlock,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +175,7 @@ class TweaksSyncResponse {
|
||||||
TweakResponseData.fromJson(value as Map<String, dynamic>),
|
TweakResponseData.fromJson(value as Map<String, dynamic>),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
wasSingleBlock: json['wasSingleBlock'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class ElectrumWorkerGetVersionError extends ElectrumWorkerErrorResponse {
|
||||||
String get method => ElectrumRequestMethods.version.method;
|
String get method => ElectrumRequestMethods.version.method;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<String>, List<String>> {
|
class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<String, String> {
|
||||||
ElectrumWorkerGetVersionResponse({
|
ElectrumWorkerGetVersionResponse({
|
||||||
required super.result,
|
required super.result,
|
||||||
super.error,
|
super.error,
|
||||||
|
@ -49,14 +49,14 @@ class ElectrumWorkerGetVersionResponse extends ElectrumWorkerResponse<List<Strin
|
||||||
}) : super(method: ElectrumRequestMethods.version.method);
|
}) : super(method: ElectrumRequestMethods.version.method);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> resultJson(result) {
|
String resultJson(result) {
|
||||||
return result;
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) {
|
factory ElectrumWorkerGetVersionResponse.fromJson(Map<String, dynamic> json) {
|
||||||
return ElectrumWorkerGetVersionResponse(
|
return ElectrumWorkerGetVersionResponse(
|
||||||
result: json['result'] as List<String>,
|
result: json['result'] as String,
|
||||||
error: json['error'] as String?,
|
error: json['error'] as String?,
|
||||||
id: json['id'] as int?,
|
id: json['id'] as int?,
|
||||||
completed: json['completed'] as bool? ?? false,
|
completed: json['completed'] as bool? ?? false,
|
||||||
|
|
|
@ -7,15 +7,24 @@ class ServerCapability {
|
||||||
|
|
||||||
bool supportsBatching;
|
bool supportsBatching;
|
||||||
bool supportsTxVerbose;
|
bool supportsTxVerbose;
|
||||||
|
String version;
|
||||||
|
|
||||||
ServerCapability({required this.supportsBatching, required this.supportsTxVerbose});
|
ServerCapability({
|
||||||
|
required this.supportsBatching,
|
||||||
|
required this.supportsTxVerbose,
|
||||||
|
required this.version,
|
||||||
|
});
|
||||||
|
|
||||||
static ServerCapability fromVersion(List<String> serverVersion) {
|
static ServerCapability fromVersion(List<String> serverVersion) {
|
||||||
if (serverVersion.isNotEmpty) {
|
if (serverVersion.isNotEmpty) {
|
||||||
final server = serverVersion.first.toLowerCase();
|
final server = serverVersion.first.toLowerCase();
|
||||||
|
|
||||||
if (server.contains('electrumx')) {
|
if (server.contains('electrumx')) {
|
||||||
return ServerCapability(supportsBatching: true, supportsTxVerbose: true);
|
return ServerCapability(
|
||||||
|
supportsBatching: true,
|
||||||
|
supportsTxVerbose: true,
|
||||||
|
version: server,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.startsWith('electrs/')) {
|
if (server.startsWith('electrs/')) {
|
||||||
|
@ -28,13 +37,21 @@ class ServerCapability {
|
||||||
try {
|
try {
|
||||||
final version = ElectrumVersion.fromStr(electrsVersion);
|
final version = ElectrumVersion.fromStr(electrsVersion);
|
||||||
if (version.compareTo(ELECTRS_MIN_BATCHING_VERSION) >= 0) {
|
if (version.compareTo(ELECTRS_MIN_BATCHING_VERSION) >= 0) {
|
||||||
return ServerCapability(supportsBatching: true, supportsTxVerbose: false);
|
return ServerCapability(
|
||||||
|
supportsBatching: true,
|
||||||
|
supportsTxVerbose: false,
|
||||||
|
version: server,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore version parsing errors
|
// ignore version parsing errors
|
||||||
}
|
}
|
||||||
|
|
||||||
return ServerCapability(supportsBatching: false, supportsTxVerbose: false);
|
return ServerCapability(
|
||||||
|
supportsBatching: false,
|
||||||
|
supportsTxVerbose: false,
|
||||||
|
version: server,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.startsWith('fulcrum')) {
|
if (server.startsWith('fulcrum')) {
|
||||||
|
@ -43,7 +60,11 @@ class ServerCapability {
|
||||||
try {
|
try {
|
||||||
final version = ElectrumVersion.fromStr(fulcrumVersion);
|
final version = ElectrumVersion.fromStr(fulcrumVersion);
|
||||||
if (version.compareTo(FULCRUM_MIN_BATCHING_VERSION) >= 0) {
|
if (version.compareTo(FULCRUM_MIN_BATCHING_VERSION) >= 0) {
|
||||||
return ServerCapability(supportsBatching: true, supportsTxVerbose: true);
|
return ServerCapability(
|
||||||
|
supportsBatching: true,
|
||||||
|
supportsTxVerbose: true,
|
||||||
|
version: server,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +80,11 @@ class ServerCapability {
|
||||||
try {
|
try {
|
||||||
final version = ElectrumVersion.fromStr(mempoolElectrsVersion);
|
final version = ElectrumVersion.fromStr(mempoolElectrsVersion);
|
||||||
if (version.compareTo(MEMPOOL_ELECTRS_MIN_BATCHING_VERSION) > 0) {
|
if (version.compareTo(MEMPOOL_ELECTRS_MIN_BATCHING_VERSION) > 0) {
|
||||||
return ServerCapability(supportsBatching: true, supportsTxVerbose: false);
|
return ServerCapability(
|
||||||
|
supportsBatching: true,
|
||||||
|
supportsTxVerbose: false,
|
||||||
|
version: server,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore version parsing errors
|
// ignore version parsing errors
|
||||||
|
@ -67,6 +92,10 @@ class ServerCapability {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ServerCapability(supportsBatching: false, supportsTxVerbose: false);
|
return ServerCapability(
|
||||||
|
supportsBatching: false,
|
||||||
|
supportsTxVerbose: false,
|
||||||
|
version: "unknown",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,13 +72,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
mwebEnabled = alwaysScan ?? false;
|
mwebEnabled = alwaysScan ?? false;
|
||||||
|
|
||||||
if (walletAddressesSnapshot != null) {
|
if (walletAddressesSnapshot != null) {
|
||||||
// walletAddresses = LitecoinWalletAddresses.fromJson(
|
walletAddresses = LitecoinWalletAddressesBase.fromJson(
|
||||||
// walletAddressesSnapshot,
|
walletAddressesSnapshot,
|
||||||
// walletInfo,
|
walletInfo,
|
||||||
// network: network,
|
network: network,
|
||||||
// isHardwareWallet: isHardwareWallet,
|
isHardwareWallet: isHardwareWallet,
|
||||||
// hdWallets: hdWallets,
|
hdWallets: hdWallets,
|
||||||
// );
|
);
|
||||||
} else {
|
} else {
|
||||||
walletAddresses = LitecoinWalletAddresses(
|
walletAddresses = LitecoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
|
@ -186,7 +186,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
snp = await LitecoinWalletSnapshot.load(
|
snp = await LitecoinWalletSnapshot.load(
|
||||||
encryptionFileUtils,
|
encryptionFileUtils,
|
||||||
name,
|
name,
|
||||||
walletInfo.type,
|
walletInfo,
|
||||||
password,
|
password,
|
||||||
LitecoinNetwork.mainnet,
|
LitecoinNetwork.mainnet,
|
||||||
);
|
);
|
||||||
|
@ -347,6 +347,19 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
// if the confirmations haven't changed, skip updating:
|
// if the confirmations haven't changed, skip updating:
|
||||||
if (tx.confirmations == confirmations) continue;
|
if (tx.confirmations == confirmations) continue;
|
||||||
|
|
||||||
|
// if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin):
|
||||||
|
if (confirmations >= 2 && tx.direction == TransactionDirection.outgoing) {
|
||||||
|
for (var coin in unspentCoins) {
|
||||||
|
if (tx.inputAddresses?.contains(coin.address) ?? false) {
|
||||||
|
final utxo = mwebUtxosBox.get(coin.address);
|
||||||
|
if (utxo != null) {
|
||||||
|
printV("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||||
|
await mwebUtxosBox.delete(coin.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tx.confirmations = confirmations;
|
tx.confirmations = confirmations;
|
||||||
tx.isPending = false;
|
tx.isPending = false;
|
||||||
transactionHistory.addOne(tx);
|
transactionHistory.addOne(tx);
|
||||||
|
@ -786,84 +799,85 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
// Future<ElectrumBalance> fetchBalances() async {
|
Future<void> updateBalance([Set<String>? scripthashes, bool? wait]) async {
|
||||||
// final balance = await super.fetchBalances();
|
await super.updateBalance(scripthashes, true);
|
||||||
|
final balance = this.balance[currency]!;
|
||||||
|
|
||||||
// if (!mwebEnabled) {
|
if (!mwebEnabled) {
|
||||||
// return balance;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // 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;
|
||||||
// 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(
|
this.balance[currency] = 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
|
||||||
ElectrumTxCreateUtxoDetails createUTXOS({
|
ElectrumTxCreateUtxoDetails createUTXOS({
|
||||||
|
|
|
@ -72,8 +72,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
|
||||||
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
|
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
|
||||||
|
|
||||||
final startIndex = (isChange ? changeAddresses : receiveAddresses)
|
final startIndex = getAddressesByType(addressType, isChange)
|
||||||
.where((addr) => addr.cwDerivationType == derivationType && addr.type == addressType)
|
.where((addr) => (addr as BitcoinAddressRecord).cwDerivationType == derivationType)
|
||||||
.length;
|
.length;
|
||||||
|
|
||||||
final mwebAddresses = <LitecoinMWEBAddressRecord>[];
|
final mwebAddresses = <LitecoinMWEBAddressRecord>[];
|
||||||
|
@ -268,7 +268,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
@override
|
@override
|
||||||
String get addressForExchange {
|
String get addressForExchange {
|
||||||
// don't use mweb addresses for exchange refund address:
|
// don't use mweb addresses for exchange refund address:
|
||||||
final addresses = receiveAddresses
|
final addresses = selectedReceiveAddresses
|
||||||
.where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed);
|
.where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed);
|
||||||
return addresses.first.address;
|
return addresses.first.address;
|
||||||
}
|
}
|
||||||
|
@ -329,6 +329,64 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
||||||
addressesSet.addAll(addresses);
|
addressesSet.addAll(addresses);
|
||||||
this.mwebAddresses.clear();
|
this.mwebAddresses.clear();
|
||||||
this.mwebAddresses.addAll(addressesSet);
|
this.mwebAddresses.addAll(addressesSet);
|
||||||
updateAddressesOnReceiveScreen();
|
updateAddressesByType();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = super.toJson();
|
||||||
|
json['mwebAddresses'] = mwebAddresses.map((address) => address.toJSON()).toList();
|
||||||
|
// json['mwebAddressIndex'] =
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> fromSnapshot(Map<dynamic, dynamic> data) {
|
||||||
|
final electrumSnapshot = ElectrumWalletAddressesBase.fromSnapshot(data);
|
||||||
|
|
||||||
|
final mwebAddresses = data['mweb_addresses'] as List? ??
|
||||||
|
<Object>[].map((e) => LitecoinMWEBAddressRecord.fromJSON(e as String)).toList();
|
||||||
|
|
||||||
|
// var mwebAddressIndex = 0;
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// mwebAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
|
||||||
|
// } catch (_) {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'allAddresses': electrumSnapshot["addresses"],
|
||||||
|
'addressPageType': data['address_page_type'] as String?,
|
||||||
|
'receiveAddressIndexByType': electrumSnapshot["receiveAddressIndexByType"],
|
||||||
|
'changeAddressIndexByType': electrumSnapshot["changeAddressIndexByType"],
|
||||||
|
'mwebAddresses': mwebAddresses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static LitecoinWalletAddressesBase fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
WalletInfo walletInfo, {
|
||||||
|
required Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets,
|
||||||
|
required BasedUtxoNetwork network,
|
||||||
|
required bool isHardwareWallet,
|
||||||
|
List<BitcoinAddressRecord>? initialAddresses,
|
||||||
|
List<LitecoinMWEBAddressRecord>? initialMwebAddresses,
|
||||||
|
}) {
|
||||||
|
initialAddresses ??= (json['allAddresses'] as List)
|
||||||
|
.map((record) => BitcoinAddressRecord.fromJSON(record as String))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
initialMwebAddresses ??= (json['mwebAddresses'] as List)
|
||||||
|
.map(
|
||||||
|
(address) => LitecoinMWEBAddressRecord.fromJSON(address as String),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return LitecoinWalletAddresses(
|
||||||
|
walletInfo,
|
||||||
|
hdWallets: hdWallets,
|
||||||
|
network: network,
|
||||||
|
isHardwareWallet: isHardwareWallet,
|
||||||
|
initialAddresses: initialAddresses,
|
||||||
|
initialMwebAddresses: initialMwebAddresses,
|
||||||
|
mwebEnabled: true, // TODO
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
|
class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
LitecoinWalletSnapshot({
|
LitecoinWalletSnapshot({
|
||||||
|
@ -29,10 +29,11 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
static Future<LitecoinWalletSnapshot> load(
|
static Future<LitecoinWalletSnapshot> load(
|
||||||
EncryptionFileUtils encryptionFileUtils,
|
EncryptionFileUtils encryptionFileUtils,
|
||||||
String name,
|
String name,
|
||||||
WalletType type,
|
WalletInfo walletInfo,
|
||||||
String password,
|
String password,
|
||||||
BasedUtxoNetwork network,
|
BasedUtxoNetwork network,
|
||||||
) async {
|
) async {
|
||||||
|
final type = walletInfo.type;
|
||||||
final path = await pathForWallet(name: name, type: type);
|
final path = await pathForWallet(name: name, type: type);
|
||||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
@ -40,7 +41,7 @@ class LitecoinWalletSnapshot extends ElectrumWalletSnapshot {
|
||||||
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
|
final ElectrumWalletSnapshot electrumWalletSnapshot = await ElectrumWalletSnapshot.load(
|
||||||
encryptionFileUtils,
|
encryptionFileUtils,
|
||||||
name,
|
name,
|
||||||
type,
|
walletInfo,
|
||||||
password,
|
password,
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
|
|
|
@ -121,7 +121,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
snp = await ElectrumWalletSnapshot.load(
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
encryptionFileUtils,
|
encryptionFileUtils,
|
||||||
name,
|
name,
|
||||||
walletInfo.type,
|
walletInfo,
|
||||||
password,
|
password,
|
||||||
BitcoinCashNetwork.mainnet,
|
BitcoinCashNetwork.mainnet,
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||||
|
allAddresses.forEach((addr) {
|
||||||
|
print(addr.address);
|
||||||
|
});
|
||||||
await super.init();
|
await super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,10 @@ class CWBitcoin extends Bitcoin {
|
||||||
@computed
|
@computed
|
||||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||||
final electrumWallet = wallet as ElectrumWallet;
|
final electrumWallet = wallet as ElectrumWallet;
|
||||||
return electrumWallet.walletAddresses.addressesOnReceiveScreen
|
return [
|
||||||
|
...electrumWallet.walletAddresses.selectedReceiveAddresses,
|
||||||
|
...electrumWallet.walletAddresses.selectedChangeAddresses
|
||||||
|
]
|
||||||
.map(
|
.map(
|
||||||
(addr) => ElectrumSubAddress(
|
(addr) => ElectrumSubAddress(
|
||||||
id: addr.index,
|
id: addr.index,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
List<TransactionPriority> priorityForWalletType(WalletBase wallet) {
|
List<TransactionPriority> priorityForWallet(WalletBase wallet) {
|
||||||
switch (wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return monero!.getTransactionPriorities();
|
return monero!.getTransactionPriorities();
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
|
import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
|
||||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
import 'package:cake_wallet/utils/share_util.dart';
|
import 'package:cake_wallet/utils/share_util.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
||||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||||
|
@ -102,13 +116,13 @@ class ReceivePage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||||
child: QRWidget(
|
child: QRWidget(
|
||||||
addressListViewModel: addressListViewModel,
|
addressListViewModel: addressListViewModel,
|
||||||
formKey: _formKey,
|
formKey: _formKey,
|
||||||
heroTag: _heroTag,
|
heroTag: _heroTag,
|
||||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||||
amountController: _amountController,
|
amountController: _amountController,
|
||||||
isLight: currentTheme.type == ThemeType.light,
|
isLight: currentTheme.type == ThemeType.light,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AddressList(addressListViewModel: addressListViewModel),
|
AddressList(addressListViewModel: addressListViewModel),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
|
@ -529,7 +529,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pickTransactionPriority(BuildContext context) async {
|
Future<void> pickTransactionPriority(BuildContext context) async {
|
||||||
final items = priorityForWalletType(sendViewModel.wallet);
|
final items = priorityForWallet(sendViewModel.wallet);
|
||||||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||||
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
|
||||||
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
|
||||||
|
|
|
@ -37,7 +37,7 @@ class OtherSettingsPage extends BasePage {
|
||||||
_otherSettingsViewModel.walletType == WalletType.bitcoin
|
_otherSettingsViewModel.walletType == WalletType.bitcoin
|
||||||
? SettingsPriorityPickerCell(
|
? SettingsPriorityPickerCell(
|
||||||
title: S.current.settings_fee_priority,
|
title: S.current.settings_fee_priority,
|
||||||
items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet),
|
items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet),
|
||||||
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
|
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
|
||||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||||
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
|
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
|
||||||
|
@ -47,7 +47,7 @@ class OtherSettingsPage extends BasePage {
|
||||||
)
|
)
|
||||||
: SettingsPickerCell(
|
: SettingsPickerCell(
|
||||||
title: S.current.settings_fee_priority,
|
title: S.current.settings_fee_priority,
|
||||||
items: priorityForWalletType(_otherSettingsViewModel.sendViewModel.wallet),
|
items: priorityForWallet(_otherSettingsViewModel.sendViewModel.wallet),
|
||||||
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
displayItem: _otherSettingsViewModel.getDisplayPriority,
|
||||||
selectedItem: _otherSettingsViewModel.transactionPriority,
|
selectedItem: _otherSettingsViewModel.transactionPriority,
|
||||||
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
|
||||||
|
|
|
@ -92,7 +92,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
setTransactionPriority(bitcoinTransactionPriorityMedium);
|
setTransactionPriority(bitcoinTransactionPriorityMedium);
|
||||||
}
|
}
|
||||||
final priority = _settingsStore.priority[wallet.type];
|
final priority = _settingsStore.priority[wallet.type];
|
||||||
final priorities = priorityForWalletType(wallet);
|
final priorities = priorityForWallet(wallet);
|
||||||
if (priorities.isNotEmpty && !priorities.contains(priority)) {
|
if (priorities.isNotEmpty && !priorities.contains(priority)) {
|
||||||
_settingsStore.priority[wallet.type] = priorities.first;
|
_settingsStore.priority[wallet.type] = priorities.first;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
|
||||||
|
|
||||||
final priority = _settingsStore.priority[_wallet.type];
|
final priority = _settingsStore.priority[_wallet.type];
|
||||||
final priorities = priorityForWalletType(_wallet);
|
final priorities = priorityForWallet(_wallet);
|
||||||
|
|
||||||
if (!priorities.contains(priority) && priorities.isNotEmpty) {
|
if (!priorities.contains(priority) && priorities.isNotEmpty) {
|
||||||
_settingsStore.priority[_wallet.type] = priorities.first;
|
_settingsStore.priority[_wallet.type] = priorities.first;
|
||||||
|
@ -103,7 +103,7 @@ abstract class OtherSettingsViewModelBase with Store {
|
||||||
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
|
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
|
||||||
|
|
||||||
int? get customPriorityItemIndex {
|
int? get customPriorityItemIndex {
|
||||||
final priorities = priorityForWalletType(_wallet);
|
final priorities = priorityForWallet(_wallet);
|
||||||
final customItem = priorities
|
final customItem = priorities
|
||||||
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
|
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
|
||||||
return customItem != null ? priorities.indexOf(customItem) : null;
|
return customItem != null ? priorities.indexOf(customItem) : null;
|
||||||
|
|
|
@ -566,7 +566,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||||
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
|
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
|
||||||
}
|
}
|
||||||
|
|
||||||
final priorities = priorityForWalletType(wallet);
|
final priorities = priorityForWallet(wallet);
|
||||||
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
|
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
|
||||||
final customItem = priorities.firstWhereOrNull(
|
final customItem = priorities.firstWhereOrNull(
|
||||||
(element) => element.title == sendViewModel.bitcoinTransactionPriorityCustom.title);
|
(element) => element.title == sendViewModel.bitcoinTransactionPriorityCustom.title);
|
||||||
|
|
Loading…
Reference in a new issue