fix: restore flow slow, checking unspents

This commit is contained in:
Rafael Saes 2024-10-29 20:52:19 -03:00
parent 7339b7876f
commit 64caf8479e
23 changed files with 396 additions and 484 deletions

View file

@ -12,15 +12,19 @@ abstract class BaseBitcoinAddressRecord {
String name = '',
bool isUsed = false,
required this.type,
bool? isHidden,
}) : _txCount = txCount,
_balance = balance,
_name = name,
_isUsed = isUsed;
_isUsed = isUsed,
_isHidden = isHidden ?? isChange;
@override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
final String address;
final bool _isHidden;
bool get isHidden => _isHidden;
bool isChange;
final int index;
int _txCount;
@ -54,6 +58,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
BitcoinAddressRecord(
super.address, {
required super.index,
super.isHidden,
super.isChange = false,
super.txCount = 0,
super.balance = 0,
@ -76,6 +81,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
return BitcoinAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isChange: decoded['isChange'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
@ -95,6 +101,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'isChange': isChange,
'isUsed': isUsed,
'txCount': txCount,
@ -117,6 +124,7 @@ class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
super.name = '',
super.isUsed = false,
super.type = SilentPaymentsAddresType.p2sp,
super.isHidden,
this.labelHex,
}) : super(index: labelIndex, isChange: labelIndex == 0) {
if (labelIndex != 1 && labelHex == null) {
@ -165,7 +173,7 @@ class BitcoinReceivedSPAddressRecord extends BitcoinSilentPaymentAddressRecord {
required this.spendKey,
super.type = SegwitAddresType.p2tr,
super.labelHex,
});
}) : super(isHidden: true);
factory BitcoinReceivedSPAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map;

View file

@ -22,11 +22,10 @@ class BitcoinHardwareWalletService {
for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'";
final xpub = await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
Bip32Slip10Secp256k1 hd =
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
final bip32 = Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
accounts.add(HardwareAccountData(
address: P2wpkhAddress.fromBip32(bip32: hd, account: i, index: 0)
address: P2wpkhAddress.fromBip32(bip32: bip32, isChange: false, index: i)
.toAddress(BitcoinNetwork.mainnet),
accountIndex: i,
derivationPath: derivationPath,

View file

@ -29,4 +29,10 @@ class BitcoinUnspent extends Unspent {
}
final BaseBitcoinAddressRecord bitcoinAddressRecord;
@override
bool operator ==(Object o) {
print('BitcoinUnspent operator ==');
return o is BitcoinUnspent && hash == o.hash && vout == o.vout;
}
}

View file

@ -7,6 +7,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
@ -45,7 +46,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required EncryptionFileUtils encryptionFileUtils,
Uint8List? seedBytes,
List<int>? seedBytes,
String? mnemonic,
String? xpub,
String? addressPageType,
@ -89,6 +90,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialSilentAddressIndex: initialSilentAddressIndex,
bip32: bip32,
network: networkParam ?? network,
isHardwareWallet: walletInfo.isHardwareWallet,
);
autorun((_) {
@ -113,20 +115,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
int initialSilentAddressIndex = 0,
required bool mempoolAPIEnabled,
}) async {
late Uint8List seedBytes;
late List<int> seedBytes;
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
break;
}
return BitcoinWallet(
mnemonic: mnemonic,
passphrase: passphrase ?? "",
@ -199,7 +199,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
List<int>? seedBytes = null;
final mnemonic = keysData.mnemonic;
final passphrase = keysData.passphrase;
@ -360,7 +360,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
updatedUnspentCoins.addAll(await fetchUnspent(address));
}));
unspentCoins = updatedUnspentCoins;
unspentCoins = updatedUnspentCoins.toSet();
if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
unspentCoins.forEach((coin) => addCoinInfo(coin));
@ -642,8 +642,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}
@action
Future<ElectrumBalance> fetchBalances(List<BitcoinAddressRecord> addresses) async {
final balance = await super.fetchBalances(addresses);
Future<ElectrumBalance> fetchBalances() async {
final balance = await super.fetchBalances();
int totalFrozen = balance.frozen;
int totalConfirmed = balance.confirmed;

View file

@ -1,5 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
@ -21,34 +20,47 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
super.initialSilentAddressIndex = 0,
}) : super(walletInfo);
@override
Future<void> init() async {
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if (!isHardwareWallet) {
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await generateInitialAddresses(type: SegwitAddresType.p2tr);
await generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
await updateAddressesInBox();
}
@override
BitcoinBaseAddress generateAddress({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) {
switch (addressType) {
case P2pkhAddressType.p2pkh:
return P2pkhAddress.fromBip32(account: account, bip32: hd, index: index);
return P2pkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
case SegwitAddresType.p2tr:
return P2trAddress.fromBip32(account: account, bip32: hd, index: index);
return P2trAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
case SegwitAddresType.p2wsh:
return P2wshAddress.fromBip32(account: account, bip32: hd, index: index);
return P2wshAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
case P2shAddressType.p2wpkhInP2sh:
return P2shAddress.fromBip32(
account: account,
bip32: hd,
bip32: bip32,
isChange: isChange,
index: index,
type: P2shAddressType.p2wpkhInP2sh,
);
case SegwitAddresType.p2wpkh:
return P2wpkhAddress.fromBip32(
account: account,
bip32: hd,
bip32: bip32,
isChange: isChange,
index: index,
isElectrum: true,
); // TODO:
isElectrum: false, // TODO:
);
default:
throw ArgumentError('Invalid address type');
}

View file

@ -7,8 +7,6 @@ class BitcoinNewWalletCredentials extends WalletCredentials {
required String name,
WalletInfo? walletInfo,
String? password,
DerivationType? derivationType,
String? derivationPath,
String? passphrase,
this.mnemonic,
String? parentAddress,
@ -29,18 +27,13 @@ class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
required String password,
required this.mnemonic,
WalletInfo? walletInfo,
required DerivationType derivationType,
required String derivationPath,
String? passphrase,
}) : super(
name: name,
password: password,
passphrase: passphrase,
walletInfo: walletInfo,
derivationInfo: DerivationInfo(
derivationType: derivationType,
derivationPath: derivationPath,
));
name: name,
password: password,
passphrase: passphrase,
walletInfo: walletInfo,
);
final String mnemonic;
}

View file

@ -89,7 +89,7 @@ class BitcoinWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
mempoolAPIEnabled: mempoolAPIEnabled,
encryptionFileUtils: encryptionFileUtilsFor(false),
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
saveBackup(name);
@ -103,7 +103,7 @@ class BitcoinWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
mempoolAPIEnabled: mempoolAPIEnabled,
encryptionFileUtils: encryptionFileUtilsFor(false),
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
return wallet;
@ -189,7 +189,6 @@ class BitcoinWalletService extends WalletService<
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
mempoolAPIEnabled: mempoolAPIEnabled,
);
await wallet.save();
await wallet.init();
return wallet;
}

View file

@ -56,7 +56,7 @@ abstract class ElectrumWalletBase
required this.encryptionFileUtils,
String? xpub,
String? mnemonic,
Uint8List? seedBytes,
List<int>? seedBytes,
this.passphrase,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumClient? electrumClient,
@ -69,7 +69,7 @@ abstract class ElectrumWalletBase
_password = password,
_isTransactionUpdating = false,
isEnabledAutoGenerateSubaddress = true,
unspentCoins = [],
unspentCoins = {},
scripthashesListening = {},
balance = ObservableMap<CryptoCurrency, ElectrumBalance>.of(currency != null
? {
@ -99,7 +99,7 @@ abstract class ElectrumWalletBase
}
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
List<int>? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) {
throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen");
@ -121,7 +121,7 @@ abstract class ElectrumWalletBase
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
}
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(List<int> seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
@ -221,7 +221,8 @@ abstract class ElectrumWalletBase
);
String _password;
List<BitcoinUnspent> unspentCoins;
@observable
Set<BitcoinUnspent> unspentCoins;
@observable
TransactionPriorities? feeRates;
@ -242,7 +243,6 @@ abstract class ElectrumWalletBase
Future<void> init() async {
await walletAddresses.init();
await transactionHistory.init();
await save();
_autoSaveTimer =
Timer.periodic(Duration(minutes: _autoSaveInterval), (_) async => await save());
@ -263,13 +263,15 @@ abstract class ElectrumWalletBase
// await updateTransactions();
// await updateAllUnspents();
// await updateBalance();
await updateBalance();
await updateFeeRates();
_updateFeeRateTimer ??=
Timer.periodic(const Duration(seconds: 5), (timer) async => await updateFeeRates());
syncStatus = SyncedSyncStatus();
await save();
} catch (e, stacktrace) {
print(stacktrace);
print("startSync $e");
@ -459,7 +461,7 @@ abstract class ElectrumWalletBase
} else if (!isHardwareWallet) {
privkey = ECPrivate.fromBip32(
bip32: walletAddresses.bip32,
account: utx.bitcoinAddressRecord.isChange ? 1 : 0,
account: BitcoinAddressUtils.getAccountFromChange(utx.bitcoinAddressRecord.isChange),
index: utx.bitcoinAddressRecord.index,
);
}
@ -660,11 +662,11 @@ abstract class ElectrumWalletBase
isChange: true,
));
// Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
final changeDerivationPath =
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
"/${changeAddress.isHidden ? "1" : "0"}"
"/${changeAddress.index}";
final changeDerivationPath = BitcoinAddressUtils.getDerivationPath(
type: changeAddress.type,
account: changeAddress.isChange ? 1 : 0,
index: changeAddress.index,
);
utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath);
@ -1105,12 +1107,12 @@ abstract class ElectrumWalletBase
Future<void> save() async {
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
await saveKeysFile(_password, encryptionFileUtils);
saveKeysFile(_password, encryptionFileUtils, true);
await saveKeysFile(_password, encryptionFileUtils, true);
}
final path = await makePath();
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
// await transactionHistory.save();
}
@override
@ -1174,7 +1176,7 @@ abstract class ElectrumWalletBase
updatedUnspentCoins.addAll(await fetchUnspent(address));
}));
unspentCoins = updatedUnspentCoins;
unspentCoins = updatedUnspentCoins.toSet();
if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
unspentCoins.forEach((coin) => addCoinInfo(coin));
@ -1182,9 +1184,10 @@ abstract class ElectrumWalletBase
}
await updateCoins(unspentCoins);
await refreshUnspentCoinsInfo();
// await refreshUnspentCoinsInfo();
}
@action
void updateCoin(BitcoinUnspent coin) {
final coinInfoList = unspentCoinsInfo.values.where(
(element) =>
@ -1204,7 +1207,8 @@ abstract class ElectrumWalletBase
}
}
Future<void> updateCoins(List<BitcoinUnspent> newUnspentCoins) async {
@action
Future<void> updateCoins(Set<BitcoinUnspent> newUnspentCoins) async {
if (newUnspentCoins.isEmpty) {
return;
}
@ -1213,8 +1217,26 @@ abstract class ElectrumWalletBase
@action
Future<void> updateUnspentsForAddress(BitcoinAddressRecord addressRecord) async {
final newUnspentCoins = await fetchUnspent(addressRecord);
final newUnspentCoins = (await fetchUnspent(addressRecord)).toSet();
await updateCoins(newUnspentCoins);
print([1, unspentCoins.containsAll(newUnspentCoins)]);
if (!unspentCoins.containsAll(newUnspentCoins)) {
newUnspentCoins.forEach((coin) {
print(unspentCoins.contains(coin));
print([coin.vout, coin.hash]);
print([unspentCoins.first.vout, unspentCoins.first.hash]);
if (!unspentCoins.contains(coin)) {
unspentCoins.add(coin);
}
});
}
// if (unspentCoinsInfo.length != unspentCoins.length) {
// unspentCoins.forEach(addCoinInfo);
// }
// await refreshUnspentCoinsInfo();
}
@action
@ -1231,11 +1253,6 @@ abstract class ElectrumWalletBase
final tx = await fetchTransactionInfo(hash: coin.hash);
coin.isChange = address.isChange;
coin.confirmations = tx?.confirmations;
if (coin.isFrozen) {
balance[currency]!.frozen += coin.value;
} else {
balance[currency]!.confirmed += coin.value;
}
updatedUnspentCoins.add(coin);
} catch (_) {}
@ -1492,64 +1509,65 @@ abstract class ElectrumWalletBase
}
}
Future<ElectrumTransactionBundle> getTransactionExpanded(
{required String hash, int? height}) async {
String transactionHex = '';
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
int? time;
int? confirmations;
int? height;
try {
final verboseTransaction = await electrumClient2!.request(
ElectrumGetTransactionVerbose(transactionHash: hash),
);
final transactionHex = await electrumClient2!.request(
ElectrumGetTransactionHex(transactionHash: hash),
);
transactionHex = verboseTransaction['hex'] as String;
time = verboseTransaction['time'] as int?;
confirmations = verboseTransaction['confirmations'] as int?;
} catch (e) {
if (e is RPCError || e is TimeoutException) {
transactionHex = await electrumClient2!.request(
ElectrumGetTransactionHex(transactionHash: hash),
// TODO:
// if (mempoolAPIEnabled) {
if (true) {
try {
final txVerbose = await http.get(
Uri.parse(
"http://mempool.cakewallet.com:8999/api/v1/tx/$hash/status",
),
);
if (height != null && height > 0 && mempoolAPIEnabled) {
try {
final blockHash = await http.get(
if (txVerbose.statusCode == 200 &&
txVerbose.body.isNotEmpty &&
jsonDecode(txVerbose.body) != null) {
height = jsonDecode(txVerbose.body)['block_height'] as int;
final blockHash = await http.get(
Uri.parse(
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
),
);
if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) {
final blockResponse = await http.get(
Uri.parse(
"http://mempool.cakewallet.com:8999/api/v1/block-height/$height",
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
),
);
if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) {
final blockResponse = await http.get(
Uri.parse(
"http://mempool.cakewallet.com:8999/api/v1/block/${blockHash.body}",
),
);
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
}
if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) {
time = int.parse(jsonDecode(blockResponse.body)['timestamp'].toString());
}
} catch (_) {}
}
}
}
} catch (_) {}
}
int? confirmations;
if (height != null) {
if (time == null && height > 0) {
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
}
if (confirmations == null) {
final tip = currentChainTip!;
if (tip > 0 && height > 0) {
// Add one because the block itself is the first confirmation
confirmations = tip - height + 1;
}
final tip = currentChainTip!;
if (tip > 0 && height > 0) {
// Add one because the block itself is the first confirmation
confirmations = tip - height + 1;
}
}
@ -1557,19 +1575,9 @@ abstract class ElectrumWalletBase
final ins = <BtcTransaction>[];
for (final vin in original.inputs) {
String inputTransactionHex = "";
try {
final verboseTransaction = await electrumClient2!.request(
ElectrumGetTransactionVerbose(transactionHash: vin.txId),
);
inputTransactionHex = verboseTransaction['hex'] as String;
} catch (e) {
if (e is RPCError || e is TimeoutException) {
inputTransactionHex = await electrumClient2!.request(
ElectrumGetTransactionHex(transactionHash: vin.txId),
);
}
}
final inputTransactionHex = await electrumClient2!.request(
ElectrumGetTransactionHex(transactionHash: vin.txId),
);
ins.add(BtcTransaction.fromRaw(inputTransactionHex));
}
@ -1585,7 +1593,7 @@ abstract class ElectrumWalletBase
Future<ElectrumTransactionInfo?> fetchTransactionInfo({required String hash, int? height}) async {
try {
return ElectrumTransactionInfo.fromElectrumBundle(
await getTransactionExpanded(hash: hash, height: height),
await getTransactionExpanded(hash: hash),
walletInfo.type,
network,
addresses: addressesSet,
@ -1674,7 +1682,6 @@ abstract class ElectrumWalletBase
// Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster
transactionHistory.addOne(tx);
await transactionHistory.save();
}
}
@ -1682,34 +1689,26 @@ abstract class ElectrumWalletBase
}));
final totalAddresses = (addressRecord.isChange
? walletAddresses.allAddresses
.where((addr) => addr.isChange && addr.type == addressRecord.type)
? walletAddresses.changeAddresses
.where((addr) => addr.type == addressRecord.type)
.length
: walletAddresses.allAddresses
.where((addr) => !addr.isChange && addr.type == addressRecord.type)
: walletAddresses.receiveAddresses
.where((addr) => addr.type == addressRecord.type)
.length);
final gapLimit = (addressRecord.isChange
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
print("gapLimit: $gapLimit");
print("index: ${addressRecord.index}");
final isUsedAddressUnderGap = addressRecord.index >= totalAddresses - gapLimit;
print("isUsedAddressAtGapLimit: $isUsedAddressUnderGap");
print("total: $totalAddresses");
final isUsedAddressUnderGap = addressRecord.index < totalAddresses &&
(addressRecord.index >= totalAddresses - gapLimit);
if (isUsedAddressUnderGap) {
// Discover new addresses for the same address type until the gap limit is respected
final newAddresses = await walletAddresses.discoverAddresses(
walletAddresses.allAddresses
.where((addr) =>
(addressRecord.isChange ? addr.isChange : !addr.isChange) &&
addr.type == addressRecord.type)
.toList(),
addressRecord.isChange,
await walletAddresses.discoverAddresses(
isChange: addressRecord.isChange,
gap: gapLimit,
type: addressRecord.type,
);
await subscribeForUpdates(newAddresses);
}
}
@ -1765,59 +1764,30 @@ abstract class ElectrumWalletBase
print("status: $status");
await _fetchAddressHistory(addressRecord);
print("_fetchAddressHistory: ${addressRecord.address}");
await updateUnspentsForAddress(addressRecord);
print("updateUnspentsForAddress: ${addressRecord.address}");
});
}
}));
}
@action
Future<ElectrumBalance> fetchBalances(List<BitcoinAddressRecord> addresses) async {
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i];
final balanceFuture = electrumClient2!.request(
ElectrumGetScriptHashBalance(scriptHash: addressRecord.scriptHash),
);
balanceFutures.add(balanceFuture);
}
Future<ElectrumBalance> fetchBalances() async {
var totalFrozen = 0;
var totalConfirmed = 0;
var totalUnconfirmed = 0;
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
unspentCoins.forEach((element) {
if (element.isFrozen) {
totalFrozen += element.value;
}
if (element.confirmations == 0) {
totalUnconfirmed += element.value;
} else {
totalConfirmed += element.value;
}
});
final balances = await Future.wait(balanceFutures);
for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i];
final balance = balances[i];
try {
final confirmed = balance['confirmed'] as int? ?? 0;
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
totalConfirmed += confirmed;
totalUnconfirmed += unconfirmed;
addressRecord.balance = confirmed + unconfirmed;
if (confirmed > 0 || unconfirmed > 0) {
addressRecord.setAsUsed();
}
} catch (_) {}
}
return ElectrumBalance(
confirmed: totalConfirmed,
unconfirmed: totalUnconfirmed,
@ -1825,22 +1795,9 @@ abstract class ElectrumWalletBase
);
}
@action
Future<void> updateBalanceForAddress(BitcoinAddressRecord addressRecord) async {
final updatedBalance = await fetchBalances([addressRecord]);
if (balance[currency] == null) {
balance[currency] = updatedBalance;
} else {
balance[currency]!.confirmed += updatedBalance.confirmed;
balance[currency]!.unconfirmed += updatedBalance.unconfirmed;
balance[currency]!.frozen += updatedBalance.frozen;
}
}
@action
Future<void> updateBalance() async {
balance[currency] = await fetchBalances(walletAddresses.allAddresses);
await save();
balance[currency] = await fetchBalances();
}
@override

View file

@ -43,15 +43,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
int initialSilentAddressIndex = 0,
List<BitcoinAddressRecord>? initialMwebAddresses,
BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
}) : _allAddresses = (initialAddresses ?? []).toSet(),
addressesByReceiveType =
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isChange && !addressRecord.isUsed)
.toSet()),
changeAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => addressRecord.isChange && !addressRecord.isUsed)
.toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? []).where((addressRecord) => !addressRecord.isChange).toSet()),
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
changeAddresses = ObservableList<BitcoinAddressRecord>.of(
(initialAddresses ?? []).where((addressRecord) => addressRecord.isChange).toSet()),
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
_addressPageType = initialAddressPageType ??
@ -92,7 +91,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const defaultChangeAddressesCount = 17;
static const gap = 20;
final ObservableList<BitcoinAddressRecord> _addresses;
@observable
final Set<BitcoinAddressRecord> _allAddresses;
final ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses;
@ -102,6 +102,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final ObservableList<BitcoinAddressRecord> mwebAddresses;
final BasedUtxoNetwork network;
final Bip32Slip10Secp256k1 bip32;
final bool isHardwareWallet;
@observable
SilentPaymentOwner? silentAddress;
@ -116,7 +117,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String? activeSilentAddress;
@computed
List<BitcoinAddressRecord> get allAddresses => _addresses;
List<BitcoinAddressRecord> get allAddresses => _allAddresses.toList();
@override
@computed
@ -177,21 +178,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return;
}
try {
final addressRecord = _addresses.firstWhere(
final addressRecord = _allAddresses.firstWhere(
(addressRecord) => addressRecord.address == addr,
);
previousAddressRecord = addressRecord;
receiveAddresses.remove(addressRecord);
receiveAddresses.insert(0, addressRecord);
} catch (e) {
print("ElectrumWalletAddressBase: set address ($addr): $e");
}
}
@override
String get primaryAddress =>
getAddress(account: 0, index: 0, hd: bip32, addressType: addressPageType);
String get primaryAddress => getAddress(isChange: false, index: 0, addressType: addressPageType);
Map<String, int> currentReceiveAddressIndexByType;
@ -233,19 +231,19 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override
Future<void> init() async {
if (walletInfo.type == WalletType.bitcoinCash) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
await _generateInitialAddresses(type: SegwitAddresType.mweb);
await generateInitialAddresses(type: SegwitAddresType.mweb);
}
} else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses();
await generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if (!isHardwareWallet) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
await generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await generateInitialAddresses(type: SegwitAddresType.p2tr);
await generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
}
@ -265,14 +263,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@action
Future<String> getChangeAddress(
Future<BitcoinAddressRecord> getChangeAddress(
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
updateChangeAddresses();
if (changeAddresses.isEmpty) {
final newAddresses = await _createNewAddresses(gap,
startIndex: totalCountOfChangeAddresses > 0 ? totalCountOfChangeAddresses - 1 : 0,
isHidden: true);
final newAddresses = await _createNewAddresses(gap, isChange: true);
addAddresses(newAddresses);
}
@ -331,47 +327,45 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
0, (int acc, addressRecord) => addressRecord.isChange == false ? acc + 1 : acc);
final address = BitcoinAddressRecord(
getAddress(account: 0, index: newAddressIndex, hd: bip32, addressType: addressPageType),
getAddress(isChange: false, index: newAddressIndex, addressType: addressPageType),
index: newAddressIndex,
isChange: false,
name: label,
type: addressPageType,
network: network,
);
_addresses.add(address);
_allAddresses.add(address);
Future.delayed(Duration.zero, () => updateAddressesByMatch());
return address;
}
BitcoinBaseAddress generateAddress({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) {
throw UnimplementedError();
}
String getAddress({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) {
return generateAddress(account: account, index: index, hd: hd, addressType: addressType)
return generateAddress(isChange: isChange, index: index, addressType: addressType)
.toAddress(network);
}
Future<String> getAddressAsync({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) async =>
getAddress(account: account, index: index, hd: hd, addressType: addressType);
getAddress(isChange: isChange, index: index, addressType: addressType);
@action
void addBitcoinAddressTypes() {
final lastP2wpkh = _addresses
final lastP2wpkh = _allAddresses
.where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
@ -382,7 +376,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = 'Active - P2WPKH';
}
final lastP2pkh = _addresses.firstWhere(
final lastP2pkh = _allAddresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
if (lastP2pkh.address != address) {
addressesMap[lastP2pkh.address] = 'P2PKH';
@ -390,7 +384,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = 'Active - P2PKH';
}
final lastP2sh = _addresses.firstWhere((addressRecord) =>
final lastP2sh = _allAddresses.firstWhere((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
if (lastP2sh.address != address) {
addressesMap[lastP2sh.address] = 'P2SH';
@ -398,7 +392,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = 'Active - P2SH';
}
final lastP2tr = _addresses.firstWhere(
final lastP2tr = _allAddresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
if (lastP2tr.address != address) {
addressesMap[lastP2tr.address] = 'P2TR';
@ -406,7 +400,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = 'Active - P2TR';
}
final lastP2wsh = _addresses.firstWhere(
final lastP2wsh = _allAddresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
if (lastP2wsh.address != address) {
addressesMap[lastP2wsh.address] = 'P2WSH';
@ -429,8 +423,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
});
}
@action
void addLitecoinAddressTypes() {
final lastP2wpkh = _addresses
final lastP2wpkh = _allAddresses
.where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
@ -441,7 +436,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = 'Active - P2WPKH';
}
final lastMweb = _addresses.firstWhere(
final lastMweb = _allAddresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
if (lastMweb.address != address) {
addressesMap[lastMweb.address] = 'MWEB';
@ -450,8 +445,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
}
@action
void addBitcoinCashAddressTypes() {
final lastP2pkh = _addresses.firstWhere(
final lastP2pkh = _allAddresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
if (lastP2pkh.address != address) {
addressesMap[lastP2pkh.address] = 'P2PKH';
@ -461,13 +457,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@override
@action
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = 'Active';
allAddressesMap.clear();
_addresses.forEach((addressRecord) {
_allAddresses.forEach((addressRecord) {
allAddressesMap[addressRecord.address] = addressRecord.name;
});
@ -494,7 +491,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void updateAddress(String address, String label) {
BaseBitcoinAddressRecord? foundAddress;
_addresses.forEach((addressRecord) {
_allAddresses.forEach((addressRecord) {
if (addressRecord.address == address) {
foundAddress = addressRecord;
}
@ -513,11 +510,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (foundAddress != null) {
foundAddress!.setNewName(label);
if (foundAddress is BitcoinAddressRecord) {
final index = _addresses.indexOf(foundAddress);
_addresses.remove(foundAddress);
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
} else {
if (foundAddress is! BitcoinAddressRecord) {
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
silentAddresses.remove(foundAddress);
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
@ -534,88 +527,62 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
addressesByReceiveType.clear();
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
addressesByReceiveType.addAll(_allAddresses.where(_isAddressPageTypeMatch).toList());
}
@action
void updateReceiveAddresses() {
receiveAddresses.removeRange(0, receiveAddresses.length);
final newAddresses =
_addresses.where((addressRecord) => !addressRecord.isChange && !addressRecord.isUsed);
final newAddresses = _allAddresses.where((addressRecord) => !addressRecord.isChange);
receiveAddresses.addAll(newAddresses);
}
@action
void updateChangeAddresses() {
changeAddresses.removeRange(0, changeAddresses.length);
final newAddresses = _addresses.where((addressRecord) =>
final newAddresses = _allAddresses.where((addressRecord) =>
addressRecord.isChange &&
!addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
changeAddresses.addAll(newAddresses);
}
@action
Future<List<BitcoinAddressRecord>> discoverAddresses(
List<BitcoinAddressRecord> addressList,
bool isHidden, {
BitcoinAddressType type = SegwitAddresType.p2wpkh,
Future<List<BitcoinAddressRecord>> discoverAddresses({
required bool isChange,
required int gap,
required BitcoinAddressType type,
}) async {
final newAddresses = await _createNewAddresses(
gap,
startIndex: addressList.length,
isHidden: isHidden,
type: type,
);
print("_allAddresses: ${_allAddresses.length}");
final newAddresses = await _createNewAddresses(gap, isChange: isChange, type: type);
addAddresses(newAddresses);
print("_allAddresses: ${_allAddresses.length}");
return newAddresses;
}
Future<void> _generateInitialAddresses(
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0;
_addresses.forEach((addr) {
if (addr.type == type) {
if (addr.isChange) {
countOfHiddenAddresses += 1;
return;
}
countOfReceiveAddresses += 1;
}
});
if (countOfReceiveAddresses < defaultReceiveAddressesCount) {
final addressesCount = defaultReceiveAddressesCount - countOfReceiveAddresses;
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfReceiveAddresses, isHidden: false, type: type);
addAddresses(newAddresses);
}
if (countOfHiddenAddresses < defaultChangeAddressesCount) {
final addressesCount = defaultChangeAddressesCount - countOfHiddenAddresses;
final newAddresses = await _createNewAddresses(addressesCount,
startIndex: countOfHiddenAddresses, isHidden: true, type: type);
addAddresses(newAddresses);
}
@action
Future<void> generateInitialAddresses({required BitcoinAddressType type}) async {
await discoverAddresses(isChange: false, gap: defaultReceiveAddressesCount, type: type);
await discoverAddresses(isChange: true, gap: defaultChangeAddressesCount, type: type);
}
Future<List<BitcoinAddressRecord>> _createNewAddresses(int count,
{int startIndex = 0, bool isHidden = false, BitcoinAddressType? type}) async {
@action
Future<List<BitcoinAddressRecord>> _createNewAddresses(
int count, {
bool isChange = false,
BitcoinAddressType? type,
}) async {
final list = <BitcoinAddressRecord>[];
final startIndex = isChange ? totalCountOfChangeAddresses : totalCountOfReceiveAddresses;
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(
await getAddressAsync(
account: _getAccount(isHidden),
index: i,
hd: bip32,
addressType: type ?? addressPageType),
isChange: isChange,
index: i,
addressType: type ?? addressPageType,
),
index: i,
isChange: isHidden,
isChange: isChange,
type: type ?? addressPageType,
network: network,
);
@ -627,11 +594,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action
void addAddresses(Iterable<BitcoinAddressRecord> addresses) {
final addressesSet = this._addresses.toSet();
addressesSet.addAll(addresses);
this._addresses.clear();
this._addresses.addAll(addressesSet);
this._allAddresses.addAll(addresses);
updateAddressesByMatch();
updateReceiveAddresses();
updateChangeAddresses();
}
@action
@ -653,7 +619,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
void _validateAddresses() {
_addresses.forEach((element) async {
_allAddresses.forEach((element) async {
if (element.type == SegwitAddresType.mweb) {
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
return;
@ -661,18 +627,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (!element.isChange &&
element.address !=
await getAddressAsync(
account: 0,
isChange: false,
index: element.index,
hd: bip32,
addressType: element.type,
)) {
element.isChange = true;
} else if (element.isChange &&
element.address !=
await getAddressAsync(
account: 1,
isChange: true,
index: element.index,
hd: bip32,
addressType: element.type,
)) {
element.isChange = false;
@ -692,11 +656,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return _isAddressByType(addressRecord, addressPageType);
}
int _getAccount(bool isHidden) => isHidden ? 1 : 0;
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
!addr.isChange && !addr.isUsed && addr.type == type;
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) {
return !addr.isChange && !addr.isUsed && addr.type == type;
}
@action
void deleteSilentPaymentAddress(String address) {

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
@ -12,8 +11,7 @@ class LitecoinHardwareWalletService {
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
await litecoinLedgerApp.getVersion();
@ -27,14 +25,13 @@ class LitecoinHardwareWalletService {
final xpub = await litecoinLedgerApp.getXPubKey(
accountsDerivationPath: derivationPath,
xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
.childKey(Bip32KeyIndex(0));
final bip32 =
Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(
hd: hd, index: 0, network: LitecoinNetwork.mainnet);
final address = P2wpkhAddress.fromBip32(bip32: bip32, isChange: false, index: 0);
accounts.add(HardwareAccountData(
address: address,
address: address.toAddress(LitecoinNetwork.mainnet),
accountIndex: i,
derivationPath: derivationPath,
xpub: xpub,

View file

@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart' as convert;
import 'dart:math';
@ -87,8 +86,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mempoolAPIEnabled: mempoolAPIEnabled,
) {
if (seedBytes != null) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
"m/1000'") as Bip32Slip10Secp256k1;
mwebHd =
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false;
} else {
mwebHd = null;
@ -772,7 +771,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
});
// copy coin control attributes to mwebCoins:
await updateCoins(mwebUnspentCoins);
await updateCoins(mwebUnspentCoins.toSet());
// get regular ltc unspents (this resets unspentCoins):
await super.updateAllUnspents();
// add the mwebCoins:
@ -810,11 +809,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}
@override
Future<ElectrumBalance> fetchBalances(List<BitcoinAddressRecord> addresses) async {
final nonMwebAddresses = walletAddresses.allAddresses
.where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress)
.toList();
final balance = await super.fetchBalances(nonMwebAddresses);
Future<ElectrumBalance> fetchBalances() async {
final balance = await super.fetchBalances();
if (!mwebEnabled) {
return balance;
@ -980,8 +976,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) {
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: false))
(await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
.address;
return tx;
}
@ -1021,10 +1016,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular))
.address;
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular))
.address;
if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx;
@ -1058,7 +1052,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
.firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
final key = ECPrivate.fromBip32(
bip32: walletAddresses.bip32,
account: utxo.bitcoinAddressRecord.isChange ? 1 : 0,
account: BitcoinAddressUtils.getAccountFromChange(utxo.bitcoinAddressRecord.isChange),
index: utxo.bitcoinAddressRecord.index,
);
final digest = tx2.getTransactionSegwitDigit(
@ -1277,8 +1271,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override
void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection;
_litecoinLedgerApp =
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
_litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
@ -1314,19 +1308,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
}
final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs,
outputs: outputs
.map((e) => TransactionOutput.fromBigInt(
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(),
changePath: changePath,
sigHashType: 0x01,
additionals: ["bech32"],
isSegWit: true,
useTrustedInputForSegwit: true
);
inputs: readyInputs,
outputs: outputs
.map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(),
changePath: changePath,
sigHashType: 0x01,
additionals: ["bech32"],
isSegWit: true,
useTrustedInputForSegwit: true);
return BtcTransaction.fromRaw(rawHex);
}

View file

@ -6,7 +6,6 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_mweb/cw_mweb.dart';
@ -15,11 +14,9 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
with _$LitecoinWalletAddresses;
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
with Store {
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
LitecoinWalletAddressesBase(
WalletInfo walletInfo, {
required super.bip32,
@ -44,14 +41,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
List<String> mwebAddrs = [];
bool generating = false;
List<int> get scanSecret =>
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendPubkey =>
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@override
Future<void> init() async {
if (!isHardwareWallet) await initMwebAddresses();
if (!super.isHardwareWallet) await initMwebAddresses();
await super.init();
}
@ -122,31 +118,29 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
@override
BitcoinBaseAddress generateAddress({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) {
if (addressType == SegwitAddresType.mweb) {
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network);
}
return P2wpkhAddress.fromBip32(account: account, bip32: hd, index: index);
return P2wpkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
}
}
@override
Future<String> getAddressAsync({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) async {
if (addressType == SegwitAddresType.mweb) {
await ensureMwebAddressUpToIndexExists(index);
}
return getAddress(account: account, index: index, hd: hd, addressType: addressType);
return getAddress(isChange: isChange, index: index, addressType: addressType);
}
@action

View file

@ -170,6 +170,7 @@ class LitecoinWalletService extends WalletService<
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
mempoolAPIEnabled: mempoolAPIEnabled,
);
await wallet.save();
await wallet.init();

View file

@ -564,10 +564,10 @@ packages:
dependency: "direct main"
description:
name: ledger_flutter_plus
sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
sha256: c7b04008553193dbca7e17b430768eecc372a72b0ff3625b5e7fc5e5c8d3231b
url: "https://pub.dev"
source: hosted
version: "1.2.5"
version: "1.4.1"
ledger_litecoin:
dependency: "direct main"
description:

View file

@ -28,10 +28,6 @@ dependencies:
cryptography: ^2.0.5
blockchain_utils:
path: /home/rafael/Working/blockchain_utils/
ledger_flutter: ^1.0.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-bitcoin
cw_mweb:
path: ../cw_mweb
grpc: ^3.2.4

View file

@ -1,5 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
@ -22,10 +21,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
@override
BitcoinBaseAddress generateAddress({
required int account,
required bool isChange,
required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType,
}) =>
P2pkhAddress.fromBip32(account: account, bip32: hd, index: index);
P2pkhAddress.fromBip32(bip32: bip32, isChange: isChange, index: index);
}

View file

@ -27,7 +27,10 @@ mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends Transactio
final path = "$rootPath${isBackup ? ".backup" : ""}";
dev.log("Saving .keys file '$path'");
await encryptionFileUtils.write(
path: path, password: password, data: walletKeysData.toJSON());
path: path,
password: password,
data: walletKeysData.toJSON(),
);
} catch (_) {}
}

View file

@ -5,16 +5,12 @@ class CWBitcoin extends Bitcoin {
required String name,
required String mnemonic,
required String password,
required DerivationType derivationType,
required String derivationPath,
String? passphrase,
}) =>
BitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: mnemonic,
password: password,
derivationType: derivationType,
derivationPath: derivationPath,
passphrase: passphrase,
);
@ -373,66 +369,71 @@ class CWBitcoin extends Bitcoin {
}
for (DerivationType dType in electrum_derivations.keys) {
late Uint8List seedBytes;
if (dType == DerivationType.electrum) {
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
} else if (dType == DerivationType.bip39) {
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
}
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
try {
DerivationInfo dInfoCopy = DerivationInfo(
derivationType: dInfo.derivationType,
derivationPath: dInfo.derivationPath,
description: dInfo.description,
scriptType: dInfo.scriptType,
);
String balancePath = dInfoCopy.derivationPath!;
int derivationDepth = _countCharOccurrences(balancePath, '/');
// for BIP44
if (derivationDepth == 3 || derivationDepth == 1) {
// we add "/0" so that we generate account 0
balancePath += "/0";
}
final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath)
as Bip32Slip10Secp256k1;
// derive address at index 0:
String? address;
switch (dInfoCopy.scriptType) {
case "p2wpkh":
address = P2wpkhAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network);
break;
case "p2pkh":
address = P2pkhAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network);
break;
case "p2wpkh-p2sh":
address = P2shAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network);
break;
case "p2tr":
address = P2trAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network);
break;
default:
continue;
}
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e, s) {
print("derivationInfoError: $e");
print("derivationInfoStack: $s");
try {
late List<int> seedBytes;
if (dType == DerivationType.electrum) {
seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} else if (dType == DerivationType.bip39) {
seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
}
for (DerivationInfo dInfo in electrum_derivations[dType]!) {
try {
DerivationInfo dInfoCopy = DerivationInfo(
derivationType: dInfo.derivationType,
derivationPath: dInfo.derivationPath,
description: dInfo.description,
scriptType: dInfo.scriptType,
);
String balancePath = dInfoCopy.derivationPath!;
int derivationDepth = _countCharOccurrences(balancePath, '/');
// for BIP44
if (derivationDepth == 3 || derivationDepth == 1) {
// we add "/0" so that we generate account 0
balancePath += "/0";
}
final bip32 = Bip32Slip10Secp256k1.fromSeed(seedBytes);
final bip32BalancePath = Bip32PathParser.parse(balancePath);
// derive address at index 0:
final path = bip32BalancePath.addElem(Bip32KeyIndex(0));
String? address;
switch (dInfoCopy.scriptType) {
case "p2wpkh":
address = P2wpkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2pkh":
address = P2pkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2wpkh-p2sh":
address = P2shAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
case "p2tr":
address = P2trAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break;
default:
continue;
}
final sh = BitcoinAddressUtils.scriptHash(address, network: network);
final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy);
} catch (e, s) {
print("derivationInfoError: $e");
print("derivationInfoStack: $s");
}
}
} catch (e) {
print("seed error: $e");
}
}

View file

@ -54,8 +54,10 @@ class WalletRestorePage extends BasePage {
_validateOnChange(isPolyseed: isPolyseed);
},
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword));
onPasswordChange: (String password) =>
walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) =>
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword));
break;
case WalletRestoreMode.keys:
_pages.add(WalletRestoreFromKeysFrom(
@ -69,8 +71,10 @@ class WalletRestorePage extends BasePage {
},
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword,
onPasswordChange: (String password) =>
walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) =>
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break;
default:
@ -379,38 +383,40 @@ class WalletRestorePage extends BasePage {
walletRestoreViewModel.state = IsExecutingState();
DerivationInfo? dInfo;
if (walletRestoreViewModel.type == WalletType.nano) {
DerivationInfo? dInfo;
// get info about the different derivations:
List<DerivationInfo> derivations =
await walletRestoreViewModel.getDerivationInfo(_credentials());
// get info about the different derivations:
List<DerivationInfo> derivations =
await walletRestoreViewModel.getDerivationInfo(_credentials());
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
if (derivations[i].transactionsCount > 0) {
derivationsWithHistory++;
derivationWithHistoryIndex = i;
int derivationsWithHistory = 0;
int derivationWithHistoryIndex = 0;
for (int i = 0; i < derivations.length; i++) {
if (derivations[i].transactionsCount > 0) {
derivationsWithHistory++;
derivationWithHistoryIndex = i;
}
}
}
if (derivationsWithHistory > 1) {
dInfo = await Navigator.of(context).pushNamed(
Routes.restoreWalletChooseDerivation,
arguments: derivations,
) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
dInfo = derivations[derivationWithHistoryIndex];
} else if (derivations.length == 1) {
// we only return 1 derivation if we're pretty sure we know which one to use:
dInfo = derivations.first;
} else {
// if we have multiple possible derivations, and none (or multiple) have histories
// we just default to the most common one:
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
if (derivationsWithHistory > 1) {
dInfo = await Navigator.of(context).pushNamed(
Routes.restoreWalletChooseDerivation,
arguments: derivations,
) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
dInfo = derivations[derivationWithHistoryIndex];
} else if (derivations.length == 1) {
// we only return 1 derivation if we're pretty sure we know which one to use:
dInfo = derivations.first;
} else {
// if we have multiple possible derivations, and none (or multiple) have histories
// we just default to the most common one:
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
this.derivationInfo = dInfo;
this.derivationInfo = dInfo;
}
await walletRestoreViewModel.create(options: _credentials());
seedSettingsViewModel.setPassphrase(null);

View file

@ -602,6 +602,7 @@ abstract class SettingsStoreBase with Store {
static const defaultActionsMode = 11;
static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenMinutes;
static const defaultAutoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.initialized;
// static final walletPasswordDirectInput = Platform.isLinux;
static final walletPasswordDirectInput = false;
static const defaultSeedPhraseLength = SeedPhraseLength.twelveWords;
static const defaultMoneroSeedType = MoneroSeedType.defaultSeedType;

View file

@ -38,7 +38,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
wif = '',
address = '',
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true);
type: type, isRecovery: true);
@observable
int height;
@ -113,21 +113,11 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
);
case WalletType.bitcoin:
case WalletType.litecoin:
final derivationInfoList = await getDerivationInfoFromQRCredentials(restoreWallet);
DerivationInfo derivationInfo;
if (derivationInfoList.isEmpty) {
derivationInfo = getDefaultCreateDerivation()!;
} else {
derivationInfo = derivationInfoList.first;
}
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
password: password,
passphrase: restoreWallet.passphrase,
derivationType: derivationInfo.derivationType!,
derivationPath: derivationInfo.derivationPath!,
);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
@ -144,8 +134,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
passphrase: restoreWallet.passphrase,
);
case WalletType.nano:
final derivationInfo =
(await getDerivationInfoFromQRCredentials(restoreWallet)).first;
final derivationInfo = (await getDerivationInfoFromQRCredentials(restoreWallet)).first;
return nano!.createNanoRestoreWalletFromSeedCredentials(
name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '',
@ -190,8 +179,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
}
@override
Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials,
RestoredWallet restoreWallet) async {
Future<WalletBase> processFromRestoredWallet(
WalletCredentials credentials, RestoredWallet restoreWallet) async {
try {
switch (restoreWallet.restoreMode) {
case WalletRestoreMode.keys:

View file

@ -105,8 +105,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
mnemonic: seed,
password: password,
passphrase: passphrase,
derivationType: derivationInfo!.derivationType!,
derivationPath: derivationInfo.derivationPath!,
);
case WalletType.haven:
return haven!.createHavenRestoreWalletFromSeedCredentials(

View file

@ -148,8 +148,6 @@ abstract class Bitcoin {
required String name,
required String mnemonic,
required String password,
required DerivationType derivationType,
required String derivationPath,
String? passphrase,
});
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});