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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart' as convert; import 'package:convert/convert.dart' as convert;
import 'dart:math'; import 'dart:math';
@ -87,8 +86,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mempoolAPIEnabled: mempoolAPIEnabled, mempoolAPIEnabled: mempoolAPIEnabled,
) { ) {
if (seedBytes != null) { if (seedBytes != null) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( mwebHd =
"m/1000'") as Bip32Slip10Secp256k1; Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false; mwebEnabled = alwaysScan ?? false;
} else { } else {
mwebHd = null; mwebHd = null;
@ -772,7 +771,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}); });
// copy coin control attributes to mwebCoins: // copy coin control attributes to mwebCoins:
await updateCoins(mwebUnspentCoins); await updateCoins(mwebUnspentCoins.toSet());
// get regular ltc unspents (this resets unspentCoins): // get regular ltc unspents (this resets unspentCoins):
await super.updateAllUnspents(); await super.updateAllUnspents();
// add the mwebCoins: // add the mwebCoins:
@ -810,11 +809,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
@override @override
Future<ElectrumBalance> fetchBalances(List<BitcoinAddressRecord> addresses) async { Future<ElectrumBalance> fetchBalances() async {
final nonMwebAddresses = walletAddresses.allAddresses final balance = await super.fetchBalances();
.where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress)
.toList();
final balance = await super.fetchBalances(nonMwebAddresses);
if (!mwebEnabled) { if (!mwebEnabled) {
return balance; return balance;
@ -980,8 +976,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) { if (!mwebEnabled) {
tx.changeAddressOverride = tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses) (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
.getChangeAddress(isPegIn: false))
.address; .address;
return tx; return tx;
} }
@ -1021,8 +1016,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool isPegIn = !hasMwebInput && hasMwebOutput; bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput; bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride = tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular)) .getChangeAddress(isPegIn: isPegIn || isRegular))
.address; .address;
if (!hasMwebInput && !hasMwebOutput) { if (!hasMwebInput && !hasMwebOutput) {
@ -1058,7 +1052,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
.firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex); .firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
final key = ECPrivate.fromBip32( final key = ECPrivate.fromBip32(
bip32: walletAddresses.bip32, bip32: walletAddresses.bip32,
account: utxo.bitcoinAddressRecord.isChange ? 1 : 0, account: BitcoinAddressUtils.getAccountFromChange(utxo.bitcoinAddressRecord.isChange),
index: utxo.bitcoinAddressRecord.index, index: utxo.bitcoinAddressRecord.index,
); );
final digest = tx2.getTransactionSegwitDigit( final digest = tx2.getTransactionSegwitDigit(
@ -1277,8 +1271,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override @override
void setLedgerConnection(LedgerConnection connection) { void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection; _ledgerConnection = connection;
_litecoinLedgerApp = _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override @override
@ -1314,19 +1308,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
} }
final rawHex = await _litecoinLedgerApp!.createTransaction( final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs, inputs: readyInputs,
outputs: outputs outputs: outputs
.map((e) => TransactionOutput.fromBigInt( .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(), .toList(),
changePath: changePath, changePath: changePath,
sigHashType: 0x01, sigHashType: 0x01,
additionals: ["bech32"], additionals: ["bech32"],
isSegWit: true, isSegWit: true,
useTrustedInputForSegwit: true useTrustedInputForSegwit: true);
);
return BtcTransaction.fromRaw(rawHex); 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:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_unspent.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_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_mweb/cw_mweb.dart'; import 'package:cw_mweb/cw_mweb.dart';
@ -15,11 +14,9 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart'; part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
with Store {
LitecoinWalletAddressesBase( LitecoinWalletAddressesBase(
WalletInfo walletInfo, { WalletInfo walletInfo, {
required super.bip32, required super.bip32,
@ -44,14 +41,13 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
List<String> mwebAddrs = []; List<String> mwebAddrs = [];
bool generating = false; bool generating = false;
List<int> get scanSecret => List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendPubkey => List<int> get spendPubkey =>
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@override @override
Future<void> init() async { Future<void> init() async {
if (!isHardwareWallet) await initMwebAddresses(); if (!super.isHardwareWallet) await initMwebAddresses();
await super.init(); await super.init();
} }
@ -122,31 +118,29 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
@override @override
BitcoinBaseAddress generateAddress({ BitcoinBaseAddress generateAddress({
required int account, required bool isChange,
required int index, required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
}) { }) {
if (addressType == SegwitAddresType.mweb) { if (addressType == SegwitAddresType.mweb) {
return MwebAddress.fromAddress(address: mwebAddrs[0], network: network); 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 @override
Future<String> getAddressAsync({ Future<String> getAddressAsync({
required int account, required bool isChange,
required int index, required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType, required BitcoinAddressType addressType,
}) async { }) async {
if (addressType == SegwitAddresType.mweb) { if (addressType == SegwitAddresType.mweb) {
await ensureMwebAddressUpToIndexExists(index); await ensureMwebAddressUpToIndexExists(index);
} }
return getAddress(account: account, index: index, hd: hd, addressType: addressType); return getAddress(isChange: isChange, index: index, addressType: addressType);
} }
@action @action

View file

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

View file

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

View file

@ -28,10 +28,6 @@ dependencies:
cryptography: ^2.0.5 cryptography: ^2.0.5
blockchain_utils: blockchain_utils:
path: /home/rafael/Working/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: cw_mweb:
path: ../cw_mweb path: ../cw_mweb
grpc: ^3.2.4 grpc: ^3.2.4

View file

@ -1,5 +1,4 @@
import 'package:bitcoin_base/bitcoin_base.dart'; 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_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -22,10 +21,9 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
@override @override
BitcoinBaseAddress generateAddress({ BitcoinBaseAddress generateAddress({
required int account, required bool isChange,
required int index, required int index,
required Bip32Slip10Secp256k1 hd,
required BitcoinAddressType addressType, 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" : ""}"; final path = "$rootPath${isBackup ? ".backup" : ""}";
dev.log("Saving .keys file '$path'"); dev.log("Saving .keys file '$path'");
await encryptionFileUtils.write( await encryptionFileUtils.write(
path: path, password: password, data: walletKeysData.toJSON()); path: path,
password: password,
data: walletKeysData.toJSON(),
);
} catch (_) {} } catch (_) {}
} }

View file

@ -5,16 +5,12 @@ class CWBitcoin extends Bitcoin {
required String name, required String name,
required String mnemonic, required String mnemonic,
required String password, required String password,
required DerivationType derivationType,
required String derivationPath,
String? passphrase, String? passphrase,
}) => }) =>
BitcoinRestoreWalletFromSeedCredentials( BitcoinRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
derivationType: derivationType,
derivationPath: derivationPath,
passphrase: passphrase, passphrase: passphrase,
); );
@ -373,11 +369,12 @@ class CWBitcoin extends Bitcoin {
} }
for (DerivationType dType in electrum_derivations.keys) { for (DerivationType dType in electrum_derivations.keys) {
late Uint8List seedBytes; try {
late List<int> seedBytes;
if (dType == DerivationType.electrum) { if (dType == DerivationType.electrum) {
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); seedBytes = ElectrumV2SeedGenerator.generateFromString(mnemonic, passphrase);
} else if (dType == DerivationType.bip39) { } else if (dType == DerivationType.bip39) {
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? ''); seedBytes = Bip39SeedGenerator.generateFromString(mnemonic, passphrase);
} }
for (DerivationInfo dInfo in electrum_derivations[dType]!) { for (DerivationInfo dInfo in electrum_derivations[dType]!) {
@ -398,23 +395,24 @@ class CWBitcoin extends Bitcoin {
balancePath += "/0"; balancePath += "/0";
} }
final hd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(balancePath) final bip32 = Bip32Slip10Secp256k1.fromSeed(seedBytes);
as Bip32Slip10Secp256k1; final bip32BalancePath = Bip32PathParser.parse(balancePath);
// derive address at index 0: // derive address at index 0:
final path = bip32BalancePath.addElem(Bip32KeyIndex(0));
String? address; String? address;
switch (dInfoCopy.scriptType) { switch (dInfoCopy.scriptType) {
case "p2wpkh": case "p2wpkh":
address = P2wpkhAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network); address = P2wpkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break; break;
case "p2pkh": case "p2pkh":
address = P2pkhAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network); address = P2pkhAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break; break;
case "p2wpkh-p2sh": case "p2wpkh-p2sh":
address = P2shAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network); address = P2shAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break; break;
case "p2tr": case "p2tr":
address = P2trAddress.fromBip32(bip32: hd, account: 0, index: 0).toAddress(network); address = P2trAddress.fromPath(bip32: bip32, path: path).toAddress(network);
break; break;
default: default:
continue; continue;
@ -434,6 +432,9 @@ class CWBitcoin extends Bitcoin {
print("derivationInfoStack: $s"); print("derivationInfoStack: $s");
} }
} }
} catch (e) {
print("seed error: $e");
}
} }
// sort the list such that derivations with the most transactions are first: // sort the list such that derivations with the most transactions are first:

View file

@ -54,8 +54,10 @@ class WalletRestorePage extends BasePage {
_validateOnChange(isPolyseed: isPolyseed); _validateOnChange(isPolyseed: isPolyseed);
}, },
displayWalletPassword: walletRestoreViewModel.hasWalletPassword, displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password, onPasswordChange: (String password) =>
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword)); walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) =>
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword));
break; break;
case WalletRestoreMode.keys: case WalletRestoreMode.keys:
_pages.add(WalletRestoreFromKeysFrom( _pages.add(WalletRestoreFromKeysFrom(
@ -69,8 +71,10 @@ class WalletRestorePage extends BasePage {
}, },
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey, displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
displayWalletPassword: walletRestoreViewModel.hasWalletPassword, displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password, onPasswordChange: (String password) =>
onRepeatedPasswordChange: (String repeatedPassword) => walletRestoreViewModel.repeatedWalletPassword = repeatedPassword, walletRestoreViewModel.walletPassword = password,
onRepeatedPasswordChange: (String repeatedPassword) =>
walletRestoreViewModel.repeatedWalletPassword = repeatedPassword,
onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value)); onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
break; break;
default: default:
@ -379,6 +383,7 @@ class WalletRestorePage extends BasePage {
walletRestoreViewModel.state = IsExecutingState(); walletRestoreViewModel.state = IsExecutingState();
if (walletRestoreViewModel.type == WalletType.nano) {
DerivationInfo? dInfo; DerivationInfo? dInfo;
// get info about the different derivations: // get info about the different derivations:
@ -411,6 +416,7 @@ class WalletRestorePage extends BasePage {
} }
this.derivationInfo = dInfo; this.derivationInfo = dInfo;
}
await walletRestoreViewModel.create(options: _credentials()); await walletRestoreViewModel.create(options: _credentials());
seedSettingsViewModel.setPassphrase(null); seedSettingsViewModel.setPassphrase(null);

View file

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

View file

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

View file

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

View file

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