refactor: misc

This commit is contained in:
Rafael Saes 2024-11-16 14:53:00 -03:00
parent d4b0165141
commit 3950f6cd17
21 changed files with 585 additions and 361 deletions

View file

@ -153,6 +153,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
}
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
final String derivationPath;
int get labelIndex => index;
final String? labelHex;
@ -161,6 +162,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
BitcoinSilentPaymentAddressRecord(
super.address, {
required int labelIndex,
this.derivationPath = BitcoinDerivationPaths.SILENT_PAYMENTS_SPEND,
super.txCount = 0,
super.balance = 0,
super.name = '',
@ -180,6 +182,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
return BitcoinSilentPaymentAddressRecord(
decoded['address'] as String,
derivationPath: decoded['derivationPath'] as String,
labelIndex: decoded['labelIndex'] as int,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
@ -192,6 +195,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
@override
String toJSON() => json.encode({
'address': address,
'derivationPath': derivationPath,
'labelIndex': labelIndex,
'isUsed': isUsed,
'txCount': txCount,
@ -222,13 +226,15 @@ class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
return BitcoinReceivedSPAddressRecord(
decoded['address'] as String,
labelIndex: decoded['index'] as int,
labelIndex: decoded['index'] as int? ?? 1,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0,
labelHex: decoded['label'] as String?,
spendKey: ECPrivate.fromHex(decoded['spendKey'] as String),
spendKey: (decoded['spendKey'] as String?) == null
? ECPrivate.random()
: ECPrivate.fromHex(decoded['spendKey'] as String),
);
}

View file

@ -10,17 +10,27 @@ class BitcoinUnspent extends Unspent {
factory BitcoinUnspent.fromUTXO(BaseBitcoinAddressRecord address, ElectrumUtxo utxo) =>
BitcoinUnspent(address, utxo.txId, utxo.value.toInt(), utxo.vout);
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
BitcoinUnspent(
address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
json['tx_hash'] as String,
int.parse(json['value'].toString()),
int.parse(json['tx_pos'].toString()),
);
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) {
final addressType = json['address_runtimetype'] as String?;
final addressRecord = json['address_record'].toString();
return BitcoinUnspent(
address ??
(addressType == null
? BitcoinAddressRecord.fromJSON(addressRecord)
: addressType.contains("SP")
? BitcoinReceivedSPAddressRecord.fromJSON(addressRecord)
: BitcoinSilentPaymentAddressRecord.fromJSON(addressRecord)),
json['tx_hash'] as String,
int.parse(json['value'].toString()),
int.parse(json['tx_pos'].toString()),
);
}
Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'address_runtimetype': bitcoinAddressRecord.runtimeType.toString(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,

View file

@ -55,6 +55,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
bool? alwaysScan,
required bool mempoolAPIEnabled,
super.hdWallets,
super.initialUnspentCoins,
}) : super(
mnemonic: mnemonic,
passphrase: passphrase,
@ -111,6 +112,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final Map<CWBitcoinDerivationType, Bip32Slip10Secp256k1> hdWallets = {};
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.description?.contains("SP") ?? false) {
continue;
}
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
@ -134,8 +139,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
}
hdWallets[CWBitcoinDerivationType.old] =
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
if (hdWallets[CWBitcoinDerivationType.bip39] != null) {
hdWallets[CWBitcoinDerivationType.old_bip39] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
if (hdWallets[CWBitcoinDerivationType.electrum] != null) {
hdWallets[CWBitcoinDerivationType.old_electrum] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
return BitcoinWallet(
mnemonic: mnemonic,
@ -155,6 +164,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
networkParam: network,
mempoolAPIEnabled: mempoolAPIEnabled,
hdWallets: hdWallets,
initialUnspentCoins: [],
);
}
@ -217,6 +227,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
if (mnemonic != null) {
for (final derivation in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivation.description?.contains("SP") ?? false) {
continue;
}
if (derivation.derivationType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
hdWallets[CWBitcoinDerivationType.bip39] = Bip32Slip10Secp256k1.fromSeed(seedBytes);
@ -242,8 +256,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
}
hdWallets[CWBitcoinDerivationType.old] =
hdWallets[CWBitcoinDerivationType.bip39] ?? hdWallets[CWBitcoinDerivationType.electrum]!;
if (hdWallets[CWBitcoinDerivationType.bip39] != null) {
hdWallets[CWBitcoinDerivationType.old_bip39] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
if (hdWallets[CWBitcoinDerivationType.electrum] != null) {
hdWallets[CWBitcoinDerivationType.old_electrum] = hdWallets[CWBitcoinDerivationType.bip39]!;
}
}
return BitcoinWallet(
@ -266,6 +284,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
alwaysScan: alwaysScan,
mempoolAPIEnabled: mempoolAPIEnabled,
hdWallets: hdWallets,
initialUnspentCoins: snp?.unspentCoins ?? [],
);
}
@ -413,41 +432,69 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
}
// @override
// @action
// Future<void> updateAllUnspents() async {
// List<BitcoinUnspent> updatedUnspentCoins = [];
@override
@action
Future<void> updateAllUnspents() async {
List<BitcoinUnspent> updatedUnspentCoins = [];
// // Update unspents stored from scanned silent payment transactions
// transactionHistory.transactions.values.forEach((tx) {
// if (tx.unspents != null) {
// updatedUnspentCoins.addAll(tx.unspents!);
// }
// });
// Update unspents stored from scanned silent payment transactions
transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null) {
updatedUnspentCoins.addAll(tx.unspents!);
}
});
// // Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
// walletAddresses.allAddresses
// .where((element) => element.type != SegwitAddresType.mweb)
// .forEach((addr) {
// if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
// });
unspentCoins.addAll(updatedUnspentCoins);
// await Future.wait(walletAddresses.allAddresses
// .where((element) => element.type != SegwitAddresType.mweb)
// .map((address) async {
// updatedUnspentCoins.addAll(await fetchUnspent(address));
// }));
await super.updateAllUnspents();
// unspentCoins.addAll(updatedUnspentCoins);
final walletAddresses = this.walletAddresses as BitcoinWalletAddresses;
// if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
// unspentCoins.forEach((coin) => addCoinInfo(coin));
// return;
// }
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
addressRecord.txCount = 0;
addressRecord.balance = 0;
});
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
addressRecord.txCount = 0;
addressRecord.balance = 0;
});
// await updateCoins(unspentCoins.toSet());
// await refreshUnspentCoinsInfo();
// }
final silentPaymentWallet = walletAddresses.silentPaymentWallet;
unspentCoins.forEach((unspent) {
if (unspent.bitcoinAddressRecord is BitcoinReceivedSPAddressRecord) {
_updateSilentAddressRecord(unspent);
final receiveAddressRecord = unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord;
final silentPaymentAddress = SilentPaymentAddress(
version: silentPaymentWallet!.version,
B_scan: silentPaymentWallet.B_scan,
B_spend: receiveAddressRecord.labelHex != null
? silentPaymentWallet.B_spend.tweakAdd(
BigintUtils.fromBytes(
BytesUtils.fromHexString(receiveAddressRecord.labelHex!),
),
)
: silentPaymentWallet.B_spend,
);
walletAddresses.silentPaymentAddresses.forEach((addressRecord) {
if (addressRecord.address == silentPaymentAddress.toAddress(network)) {
addressRecord.txCount += 1;
addressRecord.balance += unspent.value;
}
});
walletAddresses.receivedSPAddresses.forEach((addressRecord) {
if (addressRecord.address == receiveAddressRecord.address) {
addressRecord.txCount += 1;
addressRecord.balance += unspent.value;
}
});
}
});
await walletAddresses.updateAddressesInBox();
}
@override
void updateCoin(BitcoinUnspent coin) {
@ -536,26 +583,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@action
void _updateSilentAddressRecord(BitcoinUnspent unspent) {
final receiveAddressRecord = unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord;
final walletAddresses = this.walletAddresses as BitcoinWalletAddresses;
final silentPaymentWallet = walletAddresses.silentPaymentWallet;
final silentPaymentAddress = SilentPaymentAddress(
version: silentPaymentWallet.version,
B_scan: silentPaymentWallet.B_scan,
B_spend: receiveAddressRecord.labelHex != null
? silentPaymentWallet.B_spend.tweakAdd(
BigintUtils.fromBytes(BytesUtils.fromHexString(receiveAddressRecord.labelHex!)),
)
: silentPaymentWallet.B_spend,
);
final addressRecord = walletAddresses.silentPaymentAddresses
.firstWhere((address) => address.address == silentPaymentAddress.toString());
addressRecord.txCount += 1;
addressRecord.balance += unspent.value;
walletAddresses.addSilentAddresses(
[unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord],
walletAddresses.addReceivedSPAddresses(
[unspent.bitcoinAddressRecord as BitcoinReceivedSPAddressRecord],
);
}
@ -583,6 +613,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
@action
Future<void> onTweaksSyncResponse(TweaksSyncResponse result) async {
if (result.transactions?.isNotEmpty == true) {
(walletAddresses as BitcoinWalletAddresses).silentPaymentAddresses.forEach((addressRecord) {
addressRecord.txCount = 0;
addressRecord.balance = 0;
});
(walletAddresses as BitcoinWalletAddresses).receivedSPAddresses.forEach((addressRecord) {
addressRecord.txCount = 0;
addressRecord.balance = 0;
});
for (final map in result.transactions!.entries) {
final txid = map.key;
final tx = map.value;
@ -628,9 +667,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
// else: First time seeing this TX after scanning
tx.unspents!.forEach(_updateSilentAddressRecord);
// Add new TX record
transactionHistory.addOne(tx);
// Update balance record
balance[currency]!.confirmed += tx.amount;
}
@ -654,6 +691,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
await walletInfo.updateRestoreHeight(result.height!);
}
await save();
}
@action
@ -675,7 +714,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
workerSendPort!.send(
ElectrumWorkerTweaksSubscribeRequest(
scanData: ScanData(
silentAddress: walletAddresses.silentPaymentWallet,
silentPaymentsWallets: walletAddresses.silentPaymentWallets,
network: network,
height: height,
chainTip: chainTip,

View file

@ -26,13 +26,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
),
super(walletInfo) {
silentPaymentWallet = SilentPaymentOwner.fromBip32(hdWallet);
silentPaymentWallets = [silentPaymentWallet!];
}
@observable
late SilentPaymentOwner silentPaymentWallet;
SilentPaymentOwner? silentPaymentWallet;
final ObservableList<BitcoinSilentPaymentAddressRecord> silentPaymentAddresses;
final ObservableList<BitcoinReceivedSPAddressRecord> receivedSPAddresses;
@observable
List<SilentPaymentOwner> silentPaymentWallets = [];
@observable
String? activeSilentAddress;
@ -48,19 +52,70 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
}
if (silentPaymentAddresses.length == 0) {
silentPaymentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentPaymentWallet.toString(),
labelIndex: 1,
name: "",
addressType: SilentPaymentsAddresType.p2sp,
));
silentPaymentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentPaymentWallet.toLabeledSilentPaymentAddress(0).toString(),
name: "",
labelIndex: 0,
labelHex: BytesUtils.toHexString(silentPaymentWallet.generateLabel(0)),
addressType: SilentPaymentsAddresType.p2sp,
));
Bip32Path? oldSpendPath;
Bip32Path? oldScanPath;
for (final derivationInfo in walletInfo.derivations ?? <DerivationInfo>[]) {
if (derivationInfo.description?.contains("SP") ?? false) {
if (derivationInfo.description?.toLowerCase().contains("spend") ?? false) {
oldSpendPath = Bip32PathParser.parse(derivationInfo.derivationPath ?? "");
} else if (derivationInfo.description?.toLowerCase().contains("scan") ?? false) {
oldScanPath = Bip32PathParser.parse(derivationInfo.derivationPath ?? "");
}
}
}
if (oldSpendPath != null && oldScanPath != null) {
final oldSpendPriv = hdWallet.derive(oldSpendPath).privateKey;
final oldScanPriv = hdWallet.derive(oldScanPath).privateKey;
final oldSilentPaymentWallet = SilentPaymentOwner(
b_scan: ECPrivate(oldScanPriv),
b_spend: ECPrivate(oldSpendPriv),
B_scan: ECPublic.fromBip32(oldScanPriv.publicKey),
B_spend: ECPublic.fromBip32(oldSpendPriv.publicKey),
version: 0,
);
silentPaymentWallets.add(oldSilentPaymentWallet);
silentPaymentAddresses.addAll(
[
BitcoinSilentPaymentAddressRecord(
oldSilentPaymentWallet.toString(),
labelIndex: 1,
name: "",
addressType: SilentPaymentsAddresType.p2sp,
derivationPath: oldSpendPath.toString(),
isHidden: true,
),
BitcoinSilentPaymentAddressRecord(
oldSilentPaymentWallet.toLabeledSilentPaymentAddress(0).toString(),
name: "",
labelIndex: 0,
labelHex: BytesUtils.toHexString(oldSilentPaymentWallet.generateLabel(0)),
addressType: SilentPaymentsAddresType.p2sp,
derivationPath: oldSpendPath.toString(),
isHidden: true,
),
],
);
}
silentPaymentAddresses.addAll([
BitcoinSilentPaymentAddressRecord(
silentPaymentWallet.toString(),
labelIndex: 1,
name: "",
addressType: SilentPaymentsAddresType.p2sp,
),
BitcoinSilentPaymentAddressRecord(
silentPaymentWallet!.toLabeledSilentPaymentAddress(0).toString(),
name: "",
labelIndex: 0,
labelHex: BytesUtils.toHexString(silentPaymentWallet!.generateLabel(0)),
addressType: SilentPaymentsAddresType.p2sp,
),
]);
}
await updateAddressesInBox();
@ -97,7 +152,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
if (selected.labelHex != null) {
activeSilentAddress =
silentPaymentWallet.toLabeledSilentPaymentAddress(selected.labelIndex).toString();
silentPaymentWallet!.toLabeledSilentPaymentAddress(selected.labelIndex).toString();
} else {
activeSilentAddress = silentPaymentWallet.toString();
}
@ -117,27 +172,27 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
}) {
final hdWallet = hdWallets[derivationType]!;
if (derivationType == CWBitcoinDerivationType.old) {
final pub = hdWallet
.childKey(Bip32KeyIndex(isChange ? 1 : 0))
.childKey(Bip32KeyIndex(index))
.publicKey;
// if (OLD_DERIVATION_TYPES.contains(derivationType)) {
// final pub = hdWallet
// .childKey(Bip32KeyIndex(isChange ? 1 : 0))
// .childKey(Bip32KeyIndex(index))
// .publicKey;
switch (addressType) {
case P2pkhAddressType.p2pkh:
return ECPublic.fromBip32(pub).toP2pkhAddress();
case SegwitAddresType.p2tr:
return ECPublic.fromBip32(pub).toP2trAddress();
case SegwitAddresType.p2wsh:
return ECPublic.fromBip32(pub).toP2wshAddress();
case P2shAddressType.p2wpkhInP2sh:
return ECPublic.fromBip32(pub).toP2wpkhInP2sh();
case SegwitAddresType.p2wpkh:
return ECPublic.fromBip32(pub).toP2wpkhAddress();
default:
throw ArgumentError('Invalid address type');
}
}
// switch (addressType) {
// case P2pkhAddressType.p2pkh:
// return ECPublic.fromBip32(pub).toP2pkhAddress();
// case SegwitAddresType.p2tr:
// return ECPublic.fromBip32(pub).toP2trAddress();
// case SegwitAddresType.p2wsh:
// return ECPublic.fromBip32(pub).toP2wshAddress();
// case P2shAddressType.p2wpkhInP2sh:
// return ECPublic.fromBip32(pub).toP2wpkhInP2sh();
// case SegwitAddresType.p2wpkh:
// return ECPublic.fromBip32(pub).toP2wpkhAddress();
// default:
// throw ArgumentError('Invalid address type');
// }
// }
switch (addressType) {
case P2pkhAddressType.p2pkh:
@ -191,10 +246,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
1;
final address = BitcoinSilentPaymentAddressRecord(
silentPaymentWallet.toLabeledSilentPaymentAddress(currentSPLabelIndex).toString(),
silentPaymentWallet!.toLabeledSilentPaymentAddress(currentSPLabelIndex).toString(),
labelIndex: currentSPLabelIndex,
name: label,
labelHex: BytesUtils.toHexString(silentPaymentWallet.generateLabel(currentSPLabelIndex)),
labelHex: BytesUtils.toHexString(silentPaymentWallet!.generateLabel(currentSPLabelIndex)),
addressType: SilentPaymentsAddresType.p2sp,
);
@ -270,6 +325,15 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
updateAddressesByMatch();
}
@action
void addReceivedSPAddresses(Iterable<BitcoinReceivedSPAddressRecord> addresses) {
final addressesSet = this.receivedSPAddresses.toSet();
addressesSet.addAll(addresses);
this.receivedSPAddresses.clear();
this.receivedSPAddresses.addAll(addressesSet);
updateAddressesByMatch();
}
@action
void deleteSilentPaymentAddress(String address) {
final addressRecord = silentPaymentAddresses.firstWhere((addressRecord) =>

View file

@ -15,13 +15,13 @@ class ElectrumTransactionBundle {
required this.ins,
required this.confirmations,
this.time,
this.dateValidated,
this.isDateValidated,
});
final BtcTransaction originalTransaction;
final List<BtcTransaction> ins;
final int? time;
final bool? dateValidated;
final bool? isDateValidated;
final int confirmations;
Map<String, dynamic> toJson() {
@ -39,7 +39,7 @@ class ElectrumTransactionBundle {
ins: (data['ins'] as List<Object>).map((e) => BtcTransaction.fromRaw(e as String)).toList(),
confirmations: data['confirmations'] as int,
time: data['time'] as int?,
dateValidated: data['dateValidated'] as bool?,
isDateValidated: data['isDateValidated'] as bool?,
);
}
}
@ -62,7 +62,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
bool isReplaced = false,
required DateTime date,
required int? time,
bool? dateValidated,
bool? isDateValidated,
required int confirmations,
String? to,
this.unspents,
@ -81,7 +81,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.isPending = isPending;
this.isReplaced = isReplaced;
this.confirmations = confirmations;
this.dateValidated = dateValidated;
this.isDateValidated = isDateValidated;
this.to = to;
this.additionalInfo = additionalInfo ?? {};
}
@ -235,7 +235,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
date: date,
confirmations: bundle.confirmations,
time: bundle.time,
dateValidated: bundle.dateValidated,
isDateValidated: bundle.isDateValidated,
);
}
@ -265,7 +265,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
.toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
time: data['time'] as int?,
dateValidated: data['dateValidated'] as bool?,
isDateValidated: data['isDateValidated'] as bool?,
additionalInfo: data['additionalInfo'] as Map<String, dynamic>?,
);
}
@ -326,7 +326,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['outputAddresses'] = outputAddresses;
m['isReceivedSilentPayment'] = isReceivedSilentPayment;
m['additionalInfo'] = additionalInfo;
m['dateValidated'] = dateValidated;
m['isDateValidated'] = isDateValidated;
return m;
}

View file

@ -71,6 +71,7 @@ abstract class ElectrumWalletBase
CryptoCurrency? currency,
this.alwaysScan,
required this.mempoolAPIEnabled,
List<BitcoinUnspent> initialUnspentCoins = const [],
}) : hdWallets = hdWallets ??
{
CWBitcoinDerivationType.bip39: getAccountHDWallet(
@ -84,8 +85,7 @@ abstract class ElectrumWalletBase
syncStatus = NotConnectedSyncStatus(),
_password = password,
isEnabledAutoGenerateSubaddress = true,
// TODO: inital unspent coins
unspentCoins = BitcoinUnspentCoins(),
unspentCoins = BitcoinUnspentCoins.of(initialUnspentCoins),
scripthashesListening = [],
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
? {
@ -419,6 +419,7 @@ abstract class ElectrumWalletBase
workerSendPort!.send(
ElectrumWorkerConnectionRequest(
uri: node.uri,
useSSL: node.useSSL ?? false,
network: network,
).toJson(),
);
@ -1036,6 +1037,7 @@ abstract class ElectrumWalletBase
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
'derivationPath': walletInfo.derivationInfo?.derivationPath,
'alwaysScan': alwaysScan,
'unspents': unspentCoins.map((e) => e.toJson()).toList(),
});
int feeAmountForPriority(TransactionPriority priority, int inputsCount, int outputsCount,
@ -1214,7 +1216,6 @@ abstract class ElectrumWalletBase
}));
}));
unspentCoins.clear();
unspentCoins.addAll(updatedUnspentCoins);
unspentCoins.forEach(updateCoin);
@ -1918,9 +1919,15 @@ class TxCreateUtxoDetails {
});
}
class BitcoinUnspentCoins extends ObservableList<BitcoinUnspent> {
class BitcoinUnspentCoins extends ObservableSet<BitcoinUnspent> {
BitcoinUnspentCoins() : super();
static BitcoinUnspentCoins of(Iterable<BitcoinUnspent> unspentCoins) {
final coins = BitcoinUnspentCoins();
coins.addAll(unspentCoins);
return coins;
}
List<UnspentCoinsInfo> forInfo(Iterable<UnspentCoinsInfo> unspentCoinsInfo) {
return unspentCoinsInfo.where((element) {
final info = this.firstWhereOrNull(

View file

@ -11,27 +11,15 @@ import 'package:mobx/mobx.dart';
part 'electrum_wallet_addresses.g.dart';
enum CWBitcoinDerivationType { old, electrum, bip39, mweb }
enum CWBitcoinDerivationType { old_electrum, electrum, old_bip39, bip39, mweb }
const OLD_DERIVATION_TYPES = [
CWBitcoinDerivationType.old_electrum,
CWBitcoinDerivationType.old_bip39
];
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
SegwitAddresType.p2wpkh,
P2pkhAddressType.p2pkh,
SegwitAddresType.p2tr,
SegwitAddresType.p2wsh,
P2shAddressType.p2wpkhInP2sh,
];
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
SegwitAddresType.p2wpkh,
SegwitAddresType.mweb,
];
const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
P2pkhAddressType.p2pkh,
];
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
ElectrumWalletAddressesBase(
WalletInfo walletInfo, {
@ -435,6 +423,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
.length;
final newAddresses = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(
await getAddressAsync(
@ -446,7 +435,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
),
index: i,
isChange: isChange,
isHidden: derivationType == CWBitcoinDerivationType.old,
isHidden: OLD_DERIVATION_TYPES.contains(derivationType),
addressType: addressType,
network: network,
derivationInfo: derivationInfo,
@ -466,27 +455,38 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
for (final derivationType in hdWallets.keys) {
if (derivationType == CWBitcoinDerivationType.old && addressType == SegwitAddresType.p2wpkh) {
// p2wpkh has always had the right derivations, skip if creating old derivations
if (OLD_DERIVATION_TYPES.contains(derivationType) && addressType == SegwitAddresType.p2wpkh) {
continue;
}
final derivationInfo = BitcoinAddressUtils.getDerivationFromType(
addressType,
isElectrum: derivationType == CWBitcoinDerivationType.electrum,
final isElectrum = derivationType == CWBitcoinDerivationType.electrum ||
derivationType == CWBitcoinDerivationType.old_electrum;
final derivationInfos = walletInfo.derivations?.where(
(element) => element.scriptType == addressType.toString(),
);
await discoverNewAddresses(
derivationType: derivationType,
isChange: false,
addressType: addressType,
derivationInfo: derivationInfo,
);
await discoverNewAddresses(
derivationType: derivationType,
isChange: true,
addressType: addressType,
derivationInfo: derivationInfo,
);
for (final derivationInfo in derivationInfos ?? <DerivationInfo>[]) {
final bitcoinDerivationInfo = BitcoinDerivationInfo(
derivationType: isElectrum ? BitcoinDerivationType.electrum : BitcoinDerivationType.bip39,
derivationPath: derivationInfo.derivationPath!,
scriptType: addressType,
);
await discoverNewAddresses(
derivationType: derivationType,
isChange: false,
addressType: addressType,
derivationInfo: bitcoinDerivationInfo,
);
await discoverNewAddresses(
derivationType: derivationType,
isChange: true,
addressType: addressType,
derivationInfo: bitcoinDerivationInfo,
);
}
}
}

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart';
@ -23,6 +24,7 @@ class ElectrumWalletSnapshot {
required this.silentAddressIndex,
required this.mwebAddresses,
required this.alwaysScan,
required this.unspentCoins,
this.passphrase,
this.derivationType,
this.derivationPath,
@ -32,6 +34,7 @@ class ElectrumWalletSnapshot {
final String password;
final WalletType type;
final String? addressPageType;
List<BitcoinUnspent> unspentCoins;
@deprecated
String? mnemonic;
@ -127,6 +130,12 @@ class ElectrumWalletSnapshot {
silentAddressIndex: silentAddressIndex,
mwebAddresses: mwebAddresses,
alwaysScan: alwaysScan,
unspentCoins: (data['unspent_coins'] as List)
.map((e) => BitcoinUnspent.fromJSON(
null,
e as Map<String, dynamic>,
))
.toList(),
);
}
}

View file

@ -123,27 +123,41 @@ class ElectrumWorker {
_network = request.network;
_electrumClient = await ElectrumApiProvider.connect(
ElectrumTCPService.connect(
request.uri,
onConnectionStatusChange: (status) {
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
},
defaultRequestTimeOut: const Duration(seconds: 5),
connectionTimeOut: const Duration(seconds: 5),
),
request.useSSL
? ElectrumSSLService.connect(
request.uri,
onConnectionStatusChange: (status) {
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
},
defaultRequestTimeOut: const Duration(seconds: 5),
connectionTimeOut: const Duration(seconds: 5),
)
: ElectrumTCPService.connect(
request.uri,
onConnectionStatusChange: (status) {
_sendResponse(ElectrumWorkerConnectionResponse(status: status, id: request.id));
},
defaultRequestTimeOut: const Duration(seconds: 5),
connectionTimeOut: const Duration(seconds: 5),
),
);
}
Future<void> _handleHeadersSubscribe(ElectrumWorkerHeadersSubscribeRequest request) async {
final listener = _electrumClient!.subscribe(ElectrumHeaderSubscribe());
if (listener == null) {
final req = ElectrumHeaderSubscribe();
final stream = _electrumClient!.subscribe(req);
if (stream == null) {
_sendError(ElectrumWorkerHeadersSubscribeError(error: 'Failed to subscribe'));
return;
}
listener((event) {
stream.listen((event) {
_sendResponse(
ElectrumWorkerHeadersSubscribeResponse(result: event, id: request.id),
ElectrumWorkerHeadersSubscribeResponse(
result: req.onResponse(event),
id: request.id,
),
);
});
}
@ -155,22 +169,22 @@ class ElectrumWorker {
final address = entry.key;
final scripthash = entry.value;
final listener = await _electrumClient!.subscribe(
ElectrumScriptHashSubscribe(scriptHash: scripthash),
);
final req = ElectrumScriptHashSubscribe(scriptHash: scripthash);
if (listener == null) {
final stream = await _electrumClient!.subscribe(req);
if (stream == null) {
_sendError(ElectrumWorkerScripthashesSubscribeError(error: 'Failed to subscribe'));
return;
}
// https://electrumx.readthedocs.io/en/latest/protocol-basics.html#status
// The status of the script hash is the hash of the tx history, or null if the string is empty because there are no transactions
listener((status) async {
stream.listen((status) async {
print("status: $status");
_sendResponse(ElectrumWorkerScripthashesSubscribeResponse(
result: {address: status},
result: {address: req.onResponse(status)},
id: request.id,
));
});
@ -210,7 +224,7 @@ class ElectrumWorker {
// 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, and we update
if (tx?.dateValidated != true) {
if (tx?.isDateValidated != true) {
tx = ElectrumTransactionInfo.fromElectrumBundle(
await _getTransactionExpanded(
hash: txid,
@ -358,7 +372,7 @@ class ElectrumWorker {
}) async {
int? time;
int? height;
bool? dateValidated;
bool? isDateValidated;
final transactionHex = await _electrumClient!.request(
ElectrumGetTransactionHex(transactionHash: hash),
@ -397,7 +411,7 @@ class ElectrumWorker {
if (date != null) {
final newDate = DateTime.fromMillisecondsSinceEpoch(time * 1000);
dateValidated = newDate == date;
isDateValidated = newDate == date;
}
}
}
@ -430,7 +444,7 @@ class ElectrumWorker {
ins: ins,
time: time,
confirmations: confirmations ?? 0,
dateValidated: dateValidated,
isDateValidated: isDateValidated,
);
}
@ -498,12 +512,16 @@ class ElectrumWorker {
return amountLeft;
}
final receiver = Receiver(
scanData.silentAddress.b_scan.toHex(),
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
scanData.labelIndexes.length,
final receivers = scanData.silentPaymentsWallets.map(
(wallet) {
return Receiver(
wallet.b_scan.toHex(),
wallet.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
scanData.labelIndexes.length,
);
},
);
// Initial status UI update, send how many blocks in total to scan
@ -515,24 +533,38 @@ class ElectrumWorker {
),
));
final listener = await _electrumClient!.subscribe(
ElectrumTweaksSubscribe(height: syncHeight, count: initialCount),
final req = ElectrumTweaksSubscribe(
height: syncHeight,
count: initialCount,
historicalMode: false,
);
Future<void> listenFn(ElectrumTweaksSubscribeResponse response) async {
final stream = await _electrumClient!.subscribe(req);
Future<void> listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) async {
final response = req.onResponse(event);
// success or error msg
final noData = response.message != null;
if (noData) {
if (scanData.isSingleScan) {
return;
}
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
final nextCount = getCountPerRequest(nextHeight);
if (nextCount > 0) {
final nextListener = await _electrumClient!.subscribe(
ElectrumTweaksSubscribe(height: syncHeight, count: initialCount),
final nextStream = await _electrumClient!.subscribe(
ElectrumTweaksSubscribe(
height: syncHeight,
count: initialCount,
historicalMode: false,
),
);
nextListener?.call(listenFn);
nextStream?.listen((event) => listenFn(event, req));
}
return;
@ -558,7 +590,18 @@ class ElectrumWorker {
try {
// scanOutputs called from rust here
final addToWallet = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver);
final addToWallet = {};
receivers.forEach((receiver) {
final scanResult = scanOutputs(outputPubkeys.keys.toList(), tweak, receiver);
addToWallet.addAll(scanResult);
});
// final addToWallet = scanOutputs(
// outputPubkeys.keys.toList(),
// tweak,
// receivers.last,
// );
if (addToWallet.isEmpty) {
// no results tx, continue to next tx
@ -601,7 +644,7 @@ class ElectrumWorker {
receivingOutputAddress,
labelIndex: 1, // TODO: get actual index/label
isUsed: true,
spendKey: scanData.silentAddress.b_spend.tweakAdd(
spendKey: scanData.silentPaymentsWallets.first.b_spend.tweakAdd(
BigintUtils.fromBytes(BytesUtils.fromHexString(t_k)),
),
txCount: 1,
@ -618,6 +661,8 @@ class ElectrumWorker {
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(transactions: {txInfo.id: txInfo}),
));
return;
} catch (e, stacktrace) {
print(stacktrace);
print(e.toString());
@ -631,23 +676,23 @@ class ElectrumWorker {
syncHeight = tweakHeight;
if (tweakHeight >= scanData.chainTip || scanData.isSingleScan) {
if (tweakHeight >= scanData.chainTip)
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
_sendResponse(
ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(
height: syncHeight,
syncStatus: SyncedTipSyncStatus(scanData.chainTip),
syncStatus: scanData.isSingleScan
? SyncedSyncStatus()
: SyncedTipSyncStatus(scanData.chainTip),
),
));
),
);
if (scanData.isSingleScan) {
_sendResponse(ElectrumWorkerTweaksSubscribeResponse(
result: TweaksSyncResponse(height: syncHeight, syncStatus: SyncedSyncStatus()),
));
}
stream?.close();
return;
}
}
listener?.call(listenFn);
stream?.listen((event) => listenFn(event, req));
}
Future<void> _handleGetVersion(ElectrumWorkerGetVersionRequest request) async {

View file

@ -4,10 +4,12 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
ElectrumWorkerConnectionRequest({
required this.uri,
required this.network,
required this.useSSL,
this.id,
});
final Uri uri;
final bool useSSL;
final BasedUtxoNetwork network;
final int? id;
@ -21,6 +23,7 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
network: BasedUtxoNetwork.values.firstWhere(
(e) => e.toString() == json['network'] as String,
),
useSSL: json['useSSL'] as bool,
id: json['id'] as int?,
);
}
@ -31,6 +34,7 @@ class ElectrumWorkerConnectionRequest implements ElectrumWorkerRequest {
'method': method,
'uri': uri.toString(),
'network': network.toString(),
'useSSL': useSSL,
};
}
}

View file

@ -1,7 +1,7 @@
part of 'methods.dart';
class ScanData {
final SilentPaymentOwner silentAddress;
final List<SilentPaymentOwner> silentPaymentsWallets;
final int height;
final BasedUtxoNetwork network;
final int chainTip;
@ -11,7 +11,7 @@ class ScanData {
final bool isSingleScan;
ScanData({
required this.silentAddress,
required this.silentPaymentsWallets,
required this.height,
required this.network,
required this.chainTip,
@ -23,7 +23,7 @@ class ScanData {
factory ScanData.fromHeight(ScanData scanData, int newHeight) {
return ScanData(
silentAddress: scanData.silentAddress,
silentPaymentsWallets: scanData.silentPaymentsWallets,
height: newHeight,
network: scanData.network,
chainTip: scanData.chainTip,
@ -36,7 +36,7 @@ class ScanData {
Map<String, dynamic> toJson() {
return {
'silentAddress': silentAddress.toJson(),
'silentAddress': silentPaymentsWallets.map((e) => e.toJson()).toList(),
'height': height,
'network': network.value,
'chainTip': chainTip,
@ -49,7 +49,9 @@ class ScanData {
static ScanData fromJson(Map<String, dynamic> json) {
return ScanData(
silentAddress: SilentPaymentOwner.fromJson(json['silentAddress'] as Map<String, dynamic>),
silentPaymentsWallets: (json['silentAddress'] as List)
.map((e) => SilentPaymentOwner.fromJson(e as Map<String, dynamic>))
.toList(),
height: json['height'] as int,
network: BasedUtxoNetwork.fromName(json['network'] as String),
chainTip: json['chainTip'] as int,
@ -123,11 +125,9 @@ class TweaksSyncResponse {
? null
: (json['transactions'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
ElectrumTransactionInfo.fromJson(
value as Map<String, dynamic>,
WalletType.bitcoin,
)),
key,
ElectrumTransactionInfo.fromJson(value as Map<String, dynamic>, WalletType.bitcoin),
),
),
);
}

View file

@ -101,6 +101,7 @@ Map<String, dynamic> syncStatusToJson(SyncStatus? status) {
if (status == null) {
return {};
}
return {
'progress': status.progress(),
'type': status.runtimeType.toString(),
@ -127,6 +128,8 @@ SyncStatus syncStatusFromJson(Map<String, dynamic> json) {
return SyncingSyncStatus(data!['blocksLeft'] as int, data['ptc'] as double);
case 'SyncedTipSyncStatus':
return SyncedTipSyncStatus(data!['tip'] as int);
case 'SyncedSyncStatus':
return SyncedSyncStatus();
case 'FailedSyncStatus':
return FailedSyncStatus(error: data!['error'] as String?);
case 'SynchronizingSyncStatus':

View file

@ -9,7 +9,7 @@ abstract class TransactionInfo extends Object with Keyable {
late TransactionDirection direction;
late bool isPending;
late DateTime date;
bool? dateValidated;
bool? isDateValidated;
int? height;
late int confirmations;
String amountFormatted();

View file

@ -19,6 +19,8 @@ enum DerivationType {
bip39,
@HiveField(4)
electrum,
@HiveField(5)
old,
}
@HiveType(typeId: HARDWARE_WALLET_TYPE_TYPE_ID)

View file

@ -136,13 +136,24 @@ class CWBitcoin extends Bitcoin {
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesByReceiveType
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
.map(
(addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
derivationPath: (addr as BitcoinAddressRecord)
.derivationInfo
.derivationPath
.addElem(
Bip32KeyIndex(addr.isChange ? 1 : 0),
)
.addElem(Bip32KeyIndex(addr.index))
.toString(),
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isChange))
isChange: addr.isChange,
),
)
.toList();
}
@ -336,12 +347,38 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<List<BitcoinDerivationInfo>> getDerivationsFromMnemonic({
List<DerivationInfo> getOldDerivationInfos(List<DerivationInfo> list) {
final oldList = <DerivationInfo>[];
oldList.addAll(list);
for (var derivationInfo in list) {
final isElectrum = derivationInfo.derivationType == DerivationType.electrum;
oldList.add(
DerivationInfo(
derivationType: DerivationType.old,
derivationPath: isElectrum
? derivationInfo.derivationPath
: BitcoinAddressUtils.getDerivationFromType(
SegwitAddresType.p2wpkh,
).derivationPath.toString(),
scriptType: derivationInfo.scriptType,
),
);
}
oldList.addAll(bitcoin!.getOldSPDerivationInfos());
return oldList;
}
@override
Future<List<DerivationInfo>> getDerivationInfosFromMnemonic({
required String mnemonic,
required Node node,
String? passphrase,
}) async {
List<BitcoinDerivationInfo> list = [];
final list = <DerivationInfo>[];
late BasedUtxoNetwork network;
switch (node.type) {
@ -371,7 +408,15 @@ class CWBitcoin extends Bitcoin {
}
if (electrumSeedBytes != null) {
list.add(BitcoinDerivationInfos.ELECTRUM);
for (final addressType in BITCOIN_ADDRESS_TYPES) {
list.add(
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
scriptType: addressType.value,
),
);
}
}
var bip39SeedBytes;
@ -380,7 +425,17 @@ class CWBitcoin extends Bitcoin {
} catch (_) {}
if (bip39SeedBytes != null) {
list.add(BitcoinDerivationInfos.BIP84);
for (final addressType in BITCOIN_ADDRESS_TYPES) {
list.add(
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: BitcoinAddressUtils.getDerivationFromType(
addressType,
).derivationPath.toString(),
scriptType: addressType.value,
),
);
}
}
return list;
@ -490,6 +545,22 @@ class CWBitcoin extends Bitcoin {
}
}
@override
List<DerivationInfo> getOldSPDerivationInfos() {
return [
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/352'/1'/0'/1'/0",
description: "Old SP Scan",
),
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/352'/1'/0'/0'/0",
description: "Old SP Spend",
),
];
}
@override
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
final walletAddresses = (wallet as BitcoinWallet).walletAddresses as BitcoinWalletAddresses;
@ -498,6 +569,9 @@ class CWBitcoin extends Bitcoin {
id: addr.index,
name: addr.name,
address: addr.address,
derivationPath: Bip32PathParser.parse(addr.derivationPath)
.addElem(Bip32KeyIndex(addr.index))
.toString(),
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isChange,
@ -508,14 +582,16 @@ class CWBitcoin extends Bitcoin {
@override
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet) {
final walletAddresses = (wallet as BitcoinWallet).walletAddresses as BitcoinWalletAddresses;
return walletAddresses.silentPaymentAddresses
return walletAddresses.receivedSPAddresses
.map((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isChange))
id: addr.index,
name: addr.name,
address: addr.address,
derivationPath: "",
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isChange,
))
.toList();
}

View file

@ -7,23 +7,25 @@ import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class AddressCell extends StatelessWidget {
AddressCell(
{required this.address,
required this.name,
required this.isCurrent,
required this.isPrimary,
required this.backgroundColor,
required this.textColor,
this.onTap,
this.onEdit,
this.onHide,
this.isHidden = false,
this.onDelete,
this.txCount,
this.balance,
this.isChange = false,
this.hasBalance = false,
this.hasReceived = false});
AddressCell({
required this.address,
required this.derivationPath,
required this.name,
required this.isCurrent,
required this.isPrimary,
required this.backgroundColor,
required this.textColor,
this.onTap,
this.onEdit,
this.onHide,
this.isHidden = false,
this.onDelete,
this.txCount,
this.balance,
this.isChange = false,
this.hasBalance = false,
this.hasReceived = false,
});
factory AddressCell.fromItem(
WalletAddressListItem item, {
@ -39,24 +41,27 @@ class AddressCell extends StatelessWidget {
Function()? onDelete,
}) =>
AddressCell(
address: item.address,
name: item.name ?? '',
isCurrent: isCurrent,
isPrimary: item.isPrimary,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: onTap,
onEdit: onEdit,
onHide: onHide,
isHidden: isHidden,
onDelete: onDelete,
txCount: item.txCount,
balance: item.balance,
isChange: item.isChange,
hasBalance: hasBalance,
hasReceived: hasReceived,);
address: item.address,
derivationPath: item.derivationPath,
name: item.name ?? '',
isCurrent: isCurrent,
isPrimary: item.isPrimary,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: onTap,
onEdit: onEdit,
onHide: onHide,
isHidden: isHidden,
onDelete: onDelete,
txCount: item.txCount,
balance: item.balance,
isChange: item.isChange,
hasBalance: hasBalance,
hasReceived: hasReceived,
);
final String address;
final String derivationPath;
final String name;
final bool isCurrent;
final bool isPrimary;
@ -102,7 +107,9 @@ class AddressCell extends StatelessWidget {
child: Column(
children: [
Row(
mainAxisAlignment: name.isNotEmpty ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center,
mainAxisAlignment: name.isNotEmpty
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Row(
@ -151,6 +158,21 @@ class AddressCell extends StatelessWidget {
),
],
),
if (derivationPath.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Flexible(
child: AutoSizeText(
derivationPath,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: isChange ? 10 : 14,
color: textColor,
),
),
),
),
if (hasBalance || hasReceived)
Padding(
padding: const EdgeInsets.only(top: 8.0),

View file

@ -4,6 +4,7 @@ class WalletAddressListItem extends ListItem {
WalletAddressListItem({
required this.address,
required this.isPrimary,
this.derivationPath = "",
this.id,
this.name,
this.txCount,
@ -18,6 +19,7 @@ class WalletAddressListItem extends ListItem {
final int? id;
final bool isPrimary;
final String address;
final String derivationPath;
final String? name;
final int? txCount;
final String? balance;

View file

@ -31,8 +31,7 @@ import 'package:mobx/mobx.dart';
part 'wallet_address_list_view_model.g.dart';
class WalletAddressListViewModel = WalletAddressListViewModelBase
with _$WalletAddressListViewModel;
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
abstract class PaymentURI {
PaymentURI({required this.amount, required this.address});
@ -205,8 +204,7 @@ class WowneroURI extends PaymentURI {
}
}
abstract class WalletAddressListViewModelBase
extends WalletChangeListenerViewModel with Store {
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
required this.yatStore,
@ -227,8 +225,7 @@ abstract class WalletAddressListViewModelBase
_init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(wallet.type);
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven].contains(wallet.type);
}
static const String _cryptoNumberPattern = '0.00000000';
@ -241,8 +238,7 @@ abstract class WalletAddressListViewModelBase
double? _fiatRate;
String _rawAmount = '';
List<Currency> get currencies =>
[walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
String get buttonTitle {
if (isElectrumWallet) {
@ -268,8 +264,8 @@ abstract class WalletAddressListViewModelBase
WalletType get type => wallet.type;
@computed
WalletAddressListItem get address => WalletAddressListItem(
address: wallet.walletAddresses.address, isPrimary: false);
WalletAddressListItem get address =>
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
@computed
PaymentURI get uri {
@ -313,10 +309,8 @@ abstract class WalletAddressListViewModelBase
final addressList = ObservableList<ListItem>();
if (wallet.type == WalletType.monero) {
final primaryAddress =
monero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first;
final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -332,10 +326,8 @@ abstract class WalletAddressListViewModelBase
}
if (wallet.type == WalletType.wownero) {
final primaryAddress =
wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -348,10 +340,8 @@ abstract class WalletAddressListViewModelBase
}
if (wallet.type == WalletType.haven) {
final primaryAddress =
haven!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first;
final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem(
@ -365,14 +355,14 @@ abstract class WalletAddressListViewModelBase
if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems =
bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0;
return WalletAddressListItem(
id: address.id,
isPrimary: isPrimary,
name: address.name,
derivationPath: address.derivationPath,
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
@ -390,6 +380,7 @@ abstract class WalletAddressListViewModelBase
isPrimary: false,
name: address.name,
address: address.address,
derivationPath: address.derivationPath,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
@ -407,6 +398,7 @@ abstract class WalletAddressListViewModelBase
isPrimary: isPrimary,
name: subaddress.name,
address: subaddress.address,
derivationPath: subaddress.derivationPath,
txCount: subaddress.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), subaddress.balance),
@ -417,8 +409,7 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
// find the index of the last item with a txCount > 0
final addressItemsList = addressItems.toList();
int index = addressItemsList
.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
if (index == -1) {
index = 0;
}
@ -432,22 +423,19 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet);
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.nano) {
@ -461,21 +449,18 @@ abstract class WalletAddressListViewModelBase
if (wallet.type == WalletType.tron) {
final primaryAddress = tron!.getAddress(wallet);
addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet
.walletAddresses.hiddenAddresses
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
.contains((addressList[i] as WalletAddressListItem).address);
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isManual = wallet
.walletAddresses.manualAddresses
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
.contains((addressList[i] as WalletAddressListItem).address);
}
@ -493,8 +478,7 @@ abstract class WalletAddressListViewModelBase
Future<void> toggleHideAddress(WalletAddressListItem item) async {
if (item.isHidden) {
wallet.walletAddresses.hiddenAddresses
.removeWhere((element) => element == item.address);
wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address);
} else {
wallet.walletAddresses.hiddenAddresses.add(item.address);
}
@ -543,28 +527,22 @@ abstract class WalletAddressListViewModelBase
].contains(wallet.type);
@computed
bool get isElectrumWallet => [
WalletType.bitcoin,
WalletType.litecoin,
WalletType.bitcoinCash
].contains(wallet.type);
bool get isElectrumWallet =>
[WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type);
@computed
bool get isBalanceAvailable => isElectrumWallet;
@computed
bool get isReceivedAvailable =>
[WalletType.monero, WalletType.wownero].contains(wallet.type);
bool get isReceivedAvailable => [WalletType.monero, WalletType.wownero].contains(wallet.type);
@computed
bool get isSilentPayments =>
wallet.type == WalletType.bitcoin &&
bitcoin!.hasSelectedSilentPayments(wallet);
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@computed
bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus !=
AutoGenerateSubaddressStatus.disabled &&
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments;
@computed
@ -647,8 +625,7 @@ abstract class WalletAddressListViewModelBase
@action
void _convertAmountToCrypto() {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final fiatRate =
_fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
if (fiatRate <= 0.0) {
dev.log("Invalid Fiat Rate $fiatRate");

View file

@ -8,7 +8,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cw_core/pathForWallet.dart';
@ -193,7 +192,7 @@ abstract class WalletCreationVMBase with Store {
Future<List<DerivationInfo>> getDerivationInfoFromQRCredentials(
RestoredWallet restoreWallet) async {
var list = <DerivationInfo>[];
final list = <DerivationInfo>[];
final walletType = restoreWallet.type;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
@ -201,37 +200,11 @@ abstract class WalletCreationVMBase with Store {
switch (walletType) {
case WalletType.bitcoin:
case WalletType.litecoin:
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
return await bitcoin!.getDerivationInfosFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,
node: node,
passphrase: restoreWallet.passphrase,
);
List<DerivationInfo> list = [];
for (var derivation in bitcoinDerivations) {
if (derivation.derivationType == DerivationType.electrum) {
list.add(
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
);
} else {
list.add(
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
);
}
}
return list;
case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!,

View file

@ -91,14 +91,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final height = options['height'] as int? ?? 0;
name = options['name'] as String;
DerivationInfo? derivationInfo = options["derivationInfo"] as DerivationInfo?;
List<DerivationInfo>? derivations = options["derivations"] as List<DerivationInfo>?;
if (mode == WalletRestoreMode.seed) {
final seed = options['seed'] as String;
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name, height: height, mnemonic: seed, password: password);
name: name,
height: height,
mnemonic: seed,
password: password,
);
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
@ -106,7 +109,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
mnemonic: seed,
password: password,
passphrase: passphrase,
derivations: derivations,
derivations: options["derivations"] as List<DerivationInfo>?,
);
case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials(
@ -256,36 +259,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.litecoin:
String? mnemonic = credentials['seed'] as String?;
String? passphrase = credentials['passphrase'] as String?;
final bitcoinDerivations = await bitcoin!.getDerivationsFromMnemonic(
final list = await bitcoin!.getDerivationInfosFromMnemonic(
mnemonic: mnemonic!,
node: node,
passphrase: passphrase,
);
List<DerivationInfo> list = [];
for (var derivation in bitcoinDerivations) {
if (derivation.derivationType.toString().endsWith("electrum")) {
list.add(
DerivationInfo(
derivationType: DerivationType.electrum,
derivationPath: "m/0'",
description: "Electrum",
scriptType: "p2wpkh",
),
);
} else {
list.add(
DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
),
);
}
}
// is restoring? = add old used derivations
final oldList = bitcoin!.getOldDerivationInfos(list);
return list;
return oldList;
case WalletType.nano:
String? mnemonic = credentials['seed'] as String?;
String? seedKey = credentials['private_key'] as String?;

View file

@ -176,6 +176,7 @@ abstract class Bitcoin {
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, {int? customRate});
List<Unspent> getUnspents(Object wallet, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any});
List<DerivationInfo> getOldSPDerivationInfos();
Future<void> updateUnspents(Object wallet);
WalletService createBitcoinWalletService(
Box<WalletInfo> walletInfoSource,
@ -198,6 +199,7 @@ abstract class Bitcoin {
TransactionPriority getLitecoinTransactionPrioritySlow();
Future<List<DerivationType>> compareDerivationMethods(
{required String mnemonic, required Node node});
List<DerivationInfo> getOldDerivationInfos(List<DerivationInfo> list);
Future<List<BitcoinDerivationInfo>> getDerivationsFromMnemonic(
{required String mnemonic, required Node node, String? passphrase});
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();