mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
fix: restore flow slow, checking unspents
This commit is contained in:
parent
7339b7876f
commit
64caf8479e
23 changed files with 396 additions and 484 deletions
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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});
|
||||||
|
|
Loading…
Reference in a new issue