mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Cw 688 avoid wallet file corruption (#1582)
* CW-688 Store Seed and keys in .keys file * CW-688 Open wallet from keys in .keys file and migrate wallets using the old file * CW-688 Open wallet from keys in .keys file and migrate wallets using the old file * CW-688 Restore .keys file from .keys.backup * CW-688 Restore .keys file from .keys.backup * CW-688 Move saving .keys files into the save function instead of the service * CW-688 Handle corrupt wallets * CW-688 Handle corrupt wallets * CW-688 Remove code duplication * CW-688 Reduce cache dependency * wrap any file reading/writing function with try/catch [skip ci] --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
parent
8e4082d680
commit
fb33a6f23d
18 changed files with 433 additions and 144 deletions
|
@ -6,15 +6,16 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.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/electrum_derivations.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
|
@ -143,49 +144,66 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final network = walletInfo.network != null
|
final network = walletInfo.network != null
|
||||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||||
: BitcoinNetwork.mainnet;
|
: BitcoinNetwork.mainnet;
|
||||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
|
||||||
|
|
||||||
walletInfo.derivationInfo ??= DerivationInfo(
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
|
||||||
derivationPath: snp.derivationPath,
|
ElectrumWalletSnapshot? snp = null;
|
||||||
);
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData =
|
||||||
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
walletInfo.derivationInfo ??= DerivationInfo();
|
||||||
|
|
||||||
// set the default if not present:
|
// set the default if not present:
|
||||||
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;
|
Uint8List? seedBytes = null;
|
||||||
|
final mnemonic = keysData.mnemonic;
|
||||||
|
final passphrase = keysData.passphrase;
|
||||||
|
|
||||||
if (snp.mnemonic != null) {
|
if (mnemonic != null) {
|
||||||
switch (walletInfo.derivationInfo!.derivationType) {
|
switch (walletInfo.derivationInfo!.derivationType) {
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
|
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||||
break;
|
break;
|
||||||
case DerivationType.bip39:
|
case DerivationType.bip39:
|
||||||
default:
|
default:
|
||||||
seedBytes = await bip39.mnemonicToSeed(
|
seedBytes = await bip39.mnemonicToSeed(
|
||||||
snp.mnemonic!,
|
mnemonic,
|
||||||
passphrase: snp.passphrase ?? '',
|
passphrase: passphrase ?? '',
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: snp.mnemonic,
|
mnemonic: mnemonic,
|
||||||
xpub: snp.xpub,
|
xpub: keysData.xPub,
|
||||||
password: password,
|
password: password,
|
||||||
passphrase: snp.passphrase,
|
passphrase: passphrase,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp?.addresses,
|
||||||
initialSilentAddresses: snp.silentAddresses,
|
initialSilentAddresses: snp?.silentAddresses,
|
||||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
seedBytes: seedBytes,
|
seedBytes: seedBytes,
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: snp.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
);
|
);
|
||||||
|
@ -249,8 +267,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||||
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||||
|
|
||||||
final signature = await _bitcoinLedgerApp!
|
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
|
||||||
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
|
message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||||
return base64Encode(signature);
|
return base64Encode(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,10 @@ class BitcoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
network: network,
|
network: network,
|
||||||
);
|
);
|
||||||
|
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/utils/file.dart';
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_core/get_height_by_date.dart';
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -54,7 +55,7 @@ const int TWEAKS_COUNT = 25;
|
||||||
|
|
||||||
abstract class ElectrumWalletBase
|
abstract class ElectrumWalletBase
|
||||||
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
|
||||||
with Store {
|
with Store, WalletKeysFile {
|
||||||
ElectrumWalletBase({
|
ElectrumWalletBase({
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
|
@ -169,6 +170,10 @@ abstract class ElectrumWalletBase
|
||||||
@override
|
@override
|
||||||
String? get seed => _mnemonic;
|
String? get seed => _mnemonic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletKeysData get walletKeysData =>
|
||||||
|
WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase);
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
bitcoin.NetworkType networkType;
|
||||||
BasedUtxoNetwork network;
|
BasedUtxoNetwork network;
|
||||||
|
|
||||||
|
@ -1076,6 +1081,11 @@ abstract class ElectrumWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password);
|
||||||
|
saveKeysFile(_password, true);
|
||||||
|
}
|
||||||
|
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await write(path: path, password: _password, data: toJSON());
|
||||||
await transactionHistory.save();
|
await transactionHistory.save();
|
||||||
|
@ -1131,8 +1141,6 @@ abstract class ElectrumWalletBase
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> updateAllUnspents() async {
|
Future<void> updateAllUnspents() async {
|
||||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||||
|
|
|
@ -32,15 +32,21 @@ class ElectrumWalletSnapshot {
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
final String? addressPageType;
|
final String? addressPageType;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
String? mnemonic;
|
String? mnemonic;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
String? xpub;
|
String? xpub;
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
String? passphrase;
|
||||||
|
|
||||||
List<BitcoinAddressRecord> addresses;
|
List<BitcoinAddressRecord> addresses;
|
||||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||||
ElectrumBalance balance;
|
ElectrumBalance balance;
|
||||||
Map<String, int> regularAddressIndex;
|
Map<String, int> regularAddressIndex;
|
||||||
Map<String, int> changeAddressIndex;
|
Map<String, int> changeAddressIndex;
|
||||||
int silentAddressIndex;
|
int silentAddressIndex;
|
||||||
String? passphrase;
|
|
||||||
DerivationType? derivationType;
|
DerivationType? derivationType;
|
||||||
String? derivationPath;
|
String? derivationPath;
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.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/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
|
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
|
|
||||||
part 'litecoin_wallet.g.dart';
|
part 'litecoin_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -101,19 +102,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
final snp =
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
|
||||||
|
ElectrumWalletSnapshot? snp = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
|
name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData =
|
||||||
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return LitecoinWallet(
|
return LitecoinWallet(
|
||||||
mnemonic: snp.mnemonic!,
|
mnemonic: keysData.mnemonic!,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses,
|
initialAddresses: snp?.addresses,
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
|
seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: snp.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ class LitecoinWalletService extends WalletService<
|
||||||
passphrase: credentials.passphrase,
|
passphrase: credentials.passphrase,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -89,14 +90,32 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
required String password,
|
required String password,
|
||||||
}) async {
|
}) async {
|
||||||
final snp = await ElectrumWalletSnapshot.load(
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
|
|
||||||
|
ElectrumWalletSnapshot? snp = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
snp = await ElectrumWalletSnapshot.load(
|
||||||
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
keysData =
|
||||||
|
WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return BitcoinCashWallet(
|
return BitcoinCashWallet(
|
||||||
mnemonic: snp.mnemonic!,
|
mnemonic: keysData.mnemonic!,
|
||||||
password: password,
|
password: password,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
unspentCoinsInfo: unspentCoinsInfo,
|
||||||
initialAddresses: snp.addresses.map((addr) {
|
initialAddresses: snp?.addresses.map((addr) {
|
||||||
try {
|
try {
|
||||||
BitcoinCashAddress(addr.address);
|
BitcoinCashAddress(addr.address);
|
||||||
return BitcoinAddressRecord(
|
return BitcoinAddressRecord(
|
||||||
|
@ -116,10 +135,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).toList(),
|
}).toList(),
|
||||||
initialBalance: snp.balance,
|
initialBalance: snp?.balance,
|
||||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
|
seedBytes: await Mnemonic.toSeed(keysData.mnemonic!),
|
||||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||||
addressPageType: P2pkhAddressType.p2pkh,
|
addressPageType: P2pkhAddressType.p2pkh,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,10 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
110
cw_core/lib/wallet_keys_file.dart
Normal file
110
cw_core/lib/wallet_keys_file.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as dev;
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/utils/file.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
|
mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
|
||||||
|
TransactionType extends TransactionInfo>
|
||||||
|
on WalletBase<BalanceType, HistoryType, TransactionType> {
|
||||||
|
Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
// this needs to be overridden
|
||||||
|
WalletKeysData get walletKeysData;
|
||||||
|
|
||||||
|
Future<String> makeKeysFilePath() async => "${await makePath()}.keys";
|
||||||
|
|
||||||
|
Future<void> saveKeysFile(String password, [bool isBackup = false]) async {
|
||||||
|
try {
|
||||||
|
final rootPath = await makeKeysFilePath();
|
||||||
|
final path = "$rootPath${isBackup ? ".backup" : ""}";
|
||||||
|
dev.log("Saving .keys file '$path'");
|
||||||
|
await write(path: path, password: password, data: walletKeysData.toJSON());
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> createKeysFile(
|
||||||
|
String name, WalletType type, String password, WalletKeysData walletKeysData,
|
||||||
|
[bool withBackup = true]) async {
|
||||||
|
try {
|
||||||
|
final rootPath = await pathForWallet(name: name, type: type);
|
||||||
|
final path = "$rootPath.keys";
|
||||||
|
|
||||||
|
dev.log("Saving .keys file '$path'");
|
||||||
|
await write(path: path, password: password, data: walletKeysData.toJSON());
|
||||||
|
|
||||||
|
if (withBackup) {
|
||||||
|
dev.log("Saving .keys.backup file '$path.backup'");
|
||||||
|
await write(path: "$path.backup", password: password, data: walletKeysData.toJSON());
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> hasKeysFile(String name, WalletType type) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync();
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<WalletKeysData> readKeysFile(String name, WalletType type, String password) async {
|
||||||
|
final path = await pathForWallet(name: name, type: type);
|
||||||
|
|
||||||
|
var readPath = "$path.keys";
|
||||||
|
try {
|
||||||
|
if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type");
|
||||||
|
|
||||||
|
final jsonSource = await read(path: readPath, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
return WalletKeysData.fromJSON(data);
|
||||||
|
} catch (e) {
|
||||||
|
dev.log("Failed to read .keys file. Trying .keys.backup file...");
|
||||||
|
|
||||||
|
readPath = "$readPath.backup";
|
||||||
|
if (!File(readPath).existsSync())
|
||||||
|
throw Exception("No .keys nor a .keys.backup file found for $name $type");
|
||||||
|
|
||||||
|
final jsonSource = await read(path: readPath, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
final keysData = WalletKeysData.fromJSON(data);
|
||||||
|
|
||||||
|
dev.log("Restoring .keys from .keys.backup");
|
||||||
|
createKeysFile(name, type, password, keysData, false);
|
||||||
|
return keysData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WalletKeysData {
|
||||||
|
final String? privateKey;
|
||||||
|
final String? mnemonic;
|
||||||
|
final String? altMnemonic;
|
||||||
|
final String? passphrase;
|
||||||
|
final String? xPub;
|
||||||
|
|
||||||
|
WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub});
|
||||||
|
|
||||||
|
String toJSON() => jsonEncode({
|
||||||
|
"privateKey": privateKey,
|
||||||
|
"mnemonic": mnemonic,
|
||||||
|
if (altMnemonic != null) "altMnemonic": altMnemonic,
|
||||||
|
if (passphrase != null) "passphrase": passphrase,
|
||||||
|
if (xPub != null) "xPub": xPub
|
||||||
|
});
|
||||||
|
|
||||||
|
static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData(
|
||||||
|
privateKey: json["privateKey"] as String?,
|
||||||
|
mnemonic: json["mnemonic"] as String?,
|
||||||
|
altMnemonic: json["altMnemonic"] as String?,
|
||||||
|
passphrase: json["passphrase"] as String?,
|
||||||
|
xPub: json["xPub"] as String?,
|
||||||
|
);
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
||||||
import 'package:cw_ethereum/ethereum_client.dart';
|
import 'package:cw_ethereum/ethereum_client.dart';
|
||||||
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
import 'package:cw_ethereum/ethereum_transaction_history.dart';
|
||||||
|
@ -122,19 +123,37 @@ class EthereumWallet extends EVMChainWallet {
|
||||||
|
|
||||||
static Future<EthereumWallet> open(
|
static Future<EthereumWallet> open(
|
||||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
{required String name, required String password, required WalletInfo walletInfo}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Map<String, dynamic>? data;
|
||||||
|
try {
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
final privateKey = data['private_key'] as String?;
|
} catch (e) {
|
||||||
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
|
||||||
EVMChainERC20Balance(BigInt.zero);
|
EVMChainERC20Balance(BigInt.zero);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String?;
|
||||||
|
final privateKey = data['private_key'] as String?;
|
||||||
|
|
||||||
|
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return EthereumWallet(
|
return EthereumWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic,
|
||||||
privateKey: privateKey,
|
privateKey: keysData.privateKey,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
client: EthereumClient(),
|
client: EthereumClient(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_evm/evm_chain_client.dart';
|
import 'package:cw_evm/evm_chain_client.dart';
|
||||||
import 'package:cw_evm/evm_chain_exceptions.dart';
|
import 'package:cw_evm/evm_chain_exceptions.dart';
|
||||||
|
@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
|
||||||
|
|
||||||
abstract class EVMChainWalletBase
|
abstract class EVMChainWalletBase
|
||||||
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
|
extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
|
||||||
with Store {
|
with Store, WalletKeysFile {
|
||||||
EVMChainWalletBase({
|
EVMChainWalletBase({
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required EVMChainClient client,
|
required EVMChainClient client,
|
||||||
|
@ -508,6 +509,11 @@ abstract class EVMChainWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password);
|
||||||
|
saveKeysFile(_password, true);
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
@ -522,7 +528,8 @@ abstract class EVMChainWalletBase
|
||||||
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
|
? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
@override
|
||||||
|
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': _mnemonic,
|
'mnemonic': _mnemonic,
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/n2_node.dart';
|
||||||
|
import 'package:cw_core/nano_account.dart';
|
||||||
import 'package:cw_core/nano_account_info_response.dart';
|
import 'package:cw_core/nano_account_info_response.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
@ -10,23 +14,20 @@ import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/sync_status.dart';
|
import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_nano/file.dart';
|
import 'package:cw_nano/file.dart';
|
||||||
import 'package:cw_core/nano_account.dart';
|
|
||||||
import 'package:cw_core/n2_node.dart';
|
|
||||||
import 'package:cw_nano/nano_balance.dart';
|
import 'package:cw_nano/nano_balance.dart';
|
||||||
import 'package:cw_nano/nano_client.dart';
|
import 'package:cw_nano/nano_client.dart';
|
||||||
import 'package:cw_nano/nano_transaction_credentials.dart';
|
import 'package:cw_nano/nano_transaction_credentials.dart';
|
||||||
import 'package:cw_nano/nano_transaction_history.dart';
|
import 'package:cw_nano/nano_transaction_history.dart';
|
||||||
import 'package:cw_nano/nano_transaction_info.dart';
|
import 'package:cw_nano/nano_transaction_info.dart';
|
||||||
|
import 'package:cw_nano/nano_wallet_addresses.dart';
|
||||||
import 'package:cw_nano/nano_wallet_keys.dart';
|
import 'package:cw_nano/nano_wallet_keys.dart';
|
||||||
import 'package:cw_nano/pending_nano_transaction.dart';
|
import 'package:cw_nano/pending_nano_transaction.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'dart:async';
|
|
||||||
import 'package:cw_nano/nano_wallet_addresses.dart';
|
|
||||||
import 'package:cw_core/wallet_base.dart';
|
|
||||||
import 'package:nanodart/nanodart.dart';
|
import 'package:nanodart/nanodart.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
|
||||||
import 'package:nanoutil/nanoutil.dart';
|
import 'package:nanoutil/nanoutil.dart';
|
||||||
|
|
||||||
part 'nano_wallet.g.dart';
|
part 'nano_wallet.g.dart';
|
||||||
|
@ -34,7 +35,8 @@ part 'nano_wallet.g.dart';
|
||||||
class NanoWallet = NanoWalletBase with _$NanoWallet;
|
class NanoWallet = NanoWalletBase with _$NanoWallet;
|
||||||
|
|
||||||
abstract class NanoWalletBase
|
abstract class NanoWalletBase
|
||||||
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
|
extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo>
|
||||||
|
with Store, WalletKeysFile {
|
||||||
NanoWalletBase({
|
NanoWalletBase({
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required String mnemonic,
|
required String mnemonic,
|
||||||
|
@ -70,6 +72,7 @@ abstract class NanoWalletBase
|
||||||
|
|
||||||
String? _representativeAddress;
|
String? _representativeAddress;
|
||||||
int repScore = 100;
|
int repScore = 100;
|
||||||
|
|
||||||
bool get isRepOk => repScore >= 90;
|
bool get isRepOk => repScore >= 90;
|
||||||
|
|
||||||
late final NanoClient _client;
|
late final NanoClient _client;
|
||||||
|
@ -128,14 +131,10 @@ abstract class NanoWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :)
|
||||||
return 0; // always 0 :)
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> changePassword(String password) {
|
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||||
throw UnimplementedError("changePassword");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void close() {
|
void close() {
|
||||||
|
@ -170,9 +169,7 @@ abstract class NanoWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connectToPowNode({required Node node}) async {
|
Future<void> connectToPowNode({required Node node}) async => _client.connectPow(node);
|
||||||
_client.connectPow(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
@ -296,9 +293,7 @@ abstract class NanoWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NanoWalletKeys get keys {
|
NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!);
|
||||||
return NanoWalletKeys(seedKey: _hexSeed!);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get privateKey => _privateKey!;
|
String? get privateKey => _privateKey!;
|
||||||
|
@ -312,6 +307,11 @@ abstract class NanoWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password);
|
||||||
|
saveKeysFile(_password, true);
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
@ -323,6 +323,9 @@ abstract class NanoWalletBase
|
||||||
|
|
||||||
String get hexSeed => _hexSeed!;
|
String get hexSeed => _hexSeed!;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed);
|
||||||
|
|
||||||
String get representative => _representativeAddress ?? "";
|
String get representative => _representativeAddress ?? "";
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -358,8 +361,6 @@ abstract class NanoWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'seedKey': _hexSeed,
|
'seedKey': _hexSeed,
|
||||||
'mnemonic': _mnemonic,
|
'mnemonic': _mnemonic,
|
||||||
|
@ -373,31 +374,47 @@ abstract class NanoWalletBase
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
}) async {
|
}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Map<String, dynamic>? data = null;
|
||||||
|
try {
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
|
|
||||||
final data = json.decode(jsonSource) as Map;
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
final mnemonic = data['mnemonic'] as String;
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
final balance = NanoBalance.fromRawString(
|
final balance = NanoBalance.fromRawString(
|
||||||
currentBalance: data['currentBalance'] as String? ?? "0",
|
currentBalance: data?['currentBalance'] as String? ?? "0",
|
||||||
receivableBalance: data['receivableBalance'] as String? ?? "0",
|
receivableBalance: data?['receivableBalance'] as String? ?? "0",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String;
|
||||||
|
final isHexSeed = !mnemonic.contains(' ');
|
||||||
|
|
||||||
|
keysData = WalletKeysData(
|
||||||
|
mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
DerivationType derivationType = DerivationType.nano;
|
DerivationType derivationType = DerivationType.nano;
|
||||||
if (data['derivationType'] == "DerivationType.bip39") {
|
if (data?['derivationType'] == "DerivationType.bip39") {
|
||||||
derivationType = DerivationType.bip39;
|
derivationType = DerivationType.bip39;
|
||||||
}
|
}
|
||||||
|
|
||||||
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
|
walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
|
||||||
if (walletInfo.derivationInfo!.derivationType == null) {
|
walletInfo.derivationInfo!.derivationType ??= derivationType;
|
||||||
walletInfo.derivationInfo!.derivationType = derivationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NanoWallet(
|
return NanoWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic!,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
);
|
);
|
||||||
// init() should always be run after this!
|
// init() should always be run after this!
|
||||||
|
|
|
@ -39,7 +39,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
);
|
);
|
||||||
wallet.init();
|
await wallet.init();
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/erc20_token.dart';
|
import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_history.dart';
|
import 'package:cw_evm/evm_chain_transaction_history.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
import 'package:cw_evm/evm_chain_transaction_info.dart';
|
||||||
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
import 'package:cw_evm/evm_chain_transaction_model.dart';
|
||||||
|
@ -13,9 +14,9 @@ import 'package:cw_evm/evm_chain_wallet.dart';
|
||||||
import 'package:cw_evm/evm_erc20_balance.dart';
|
import 'package:cw_evm/evm_erc20_balance.dart';
|
||||||
import 'package:cw_evm/file.dart';
|
import 'package:cw_evm/file.dart';
|
||||||
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
|
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
|
||||||
import 'package:cw_polygon/polygon_transaction_info.dart';
|
|
||||||
import 'package:cw_polygon/polygon_client.dart';
|
import 'package:cw_polygon/polygon_client.dart';
|
||||||
import 'package:cw_polygon/polygon_transaction_history.dart';
|
import 'package:cw_polygon/polygon_transaction_history.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_info.dart';
|
||||||
|
|
||||||
class PolygonWallet extends EVMChainWallet {
|
class PolygonWallet extends EVMChainWallet {
|
||||||
PolygonWallet({
|
PolygonWallet({
|
||||||
|
@ -97,19 +98,37 @@ class PolygonWallet extends EVMChainWallet {
|
||||||
|
|
||||||
static Future<PolygonWallet> open(
|
static Future<PolygonWallet> open(
|
||||||
{required String name, required String password, required WalletInfo walletInfo}) async {
|
{required String name, required String password, required WalletInfo walletInfo}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Map<String, dynamic>? data;
|
||||||
|
try {
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
final privateKey = data['private_key'] as String?;
|
} catch (e) {
|
||||||
final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
|
||||||
EVMChainERC20Balance(BigInt.zero);
|
EVMChainERC20Balance(BigInt.zero);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String?;
|
||||||
|
final privateKey = data['private_key'] as String?;
|
||||||
|
|
||||||
|
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return PolygonWallet(
|
return PolygonWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic,
|
||||||
privateKey: privateKey,
|
privateKey: keysData.privateKey,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
client: PolygonClient(),
|
client: PolygonClient(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,7 +35,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
wallet.addInitialTokens();
|
wallet.addInitialTokens();
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +82,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
wallet.addInitialTokens();
|
wallet.addInitialTokens();
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
|
@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_solana/default_spl_tokens.dart';
|
import 'package:cw_solana/default_spl_tokens.dart';
|
||||||
import 'package:cw_solana/file.dart';
|
import 'package:cw_solana/file.dart';
|
||||||
import 'package:cw_solana/solana_balance.dart';
|
import 'package:cw_solana/solana_balance.dart';
|
||||||
|
@ -36,7 +38,8 @@ part 'solana_wallet.g.dart';
|
||||||
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
||||||
|
|
||||||
abstract class SolanaWalletBase
|
abstract class SolanaWalletBase
|
||||||
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store {
|
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
|
||||||
|
with Store, WalletKeysFile {
|
||||||
SolanaWalletBase({
|
SolanaWalletBase({
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
|
@ -121,6 +124,9 @@ abstract class SolanaWalletBase
|
||||||
return privateKey;
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||||
|
|
||||||
|
@ -336,6 +342,11 @@ abstract class SolanaWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password);
|
||||||
|
saveKeysFile(_password, true);
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
@ -361,8 +372,6 @@ abstract class SolanaWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': _mnemonic,
|
'mnemonic': _mnemonic,
|
||||||
'private_key': _hexPrivateKey,
|
'private_key': _hexPrivateKey,
|
||||||
|
@ -374,18 +383,36 @@ abstract class SolanaWalletBase
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
}) async {
|
}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Map<String, dynamic>? data;
|
||||||
|
try {
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String?;
|
||||||
final privateKey = data['private_key'] as String?;
|
final privateKey = data['private_key'] as String?;
|
||||||
final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0);
|
|
||||||
|
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return SolanaWallet(
|
return SolanaWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic,
|
||||||
privateKey: privateKey,
|
privateKey: keysData.privateKey,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cw_tron/default_tron_tokens.dart';
|
import 'package:cw_tron/default_tron_tokens.dart';
|
||||||
import 'package:cw_tron/file.dart';
|
import 'package:cw_tron/file.dart';
|
||||||
|
@ -37,7 +38,8 @@ part 'tron_wallet.g.dart';
|
||||||
class TronWallet = TronWalletBase with _$TronWallet;
|
class TronWallet = TronWalletBase with _$TronWallet;
|
||||||
|
|
||||||
abstract class TronWalletBase
|
abstract class TronWalletBase
|
||||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store {
|
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo>
|
||||||
|
with Store, WalletKeysFile {
|
||||||
TronWalletBase({
|
TronWalletBase({
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
|
@ -124,18 +126,36 @@ abstract class TronWalletBase
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
}) async {
|
}) async {
|
||||||
|
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
|
||||||
|
Map<String, dynamic>? data;
|
||||||
|
try {
|
||||||
final jsonSource = await read(path: path, password: password);
|
final jsonSource = await read(path: path, password: password);
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final mnemonic = data['mnemonic'] as String?;
|
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||||
|
} catch (e) {
|
||||||
|
if (!hasKeysFile) rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final balance = TronBalance.fromJSON(data?['balance'] as String) ?? TronBalance(BigInt.zero);
|
||||||
|
|
||||||
|
final WalletKeysData keysData;
|
||||||
|
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||||
|
if (!hasKeysFile) {
|
||||||
|
final mnemonic = data!['mnemonic'] as String?;
|
||||||
final privateKey = data['private_key'] as String?;
|
final privateKey = data['private_key'] as String?;
|
||||||
final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero);
|
|
||||||
|
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
|
||||||
|
} else {
|
||||||
|
keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
|
||||||
|
}
|
||||||
|
|
||||||
return TronWallet(
|
return TronWallet(
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
password: password,
|
password: password,
|
||||||
mnemonic: mnemonic,
|
mnemonic: keysData.mnemonic,
|
||||||
privateKey: privateKey,
|
privateKey: keysData.privateKey,
|
||||||
initialBalance: balance,
|
initialBalance: balance,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -163,9 +183,7 @@ abstract class TronWalletBase
|
||||||
}) async {
|
}) async {
|
||||||
assert(mnemonic != null || privateKey != null);
|
assert(mnemonic != null || privateKey != null);
|
||||||
|
|
||||||
if (privateKey != null) {
|
if (privateKey != null) return TronPrivateKey(privateKey);
|
||||||
return TronPrivateKey(privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||||
|
|
||||||
|
@ -181,14 +199,10 @@ abstract class TronWalletBase
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> changePassword(String password) {
|
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||||
throw UnimplementedError("changePassword");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void close() {
|
void close() => _transactionsUpdateTimer?.cancel();
|
||||||
_transactionsUpdateTimer?.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@override
|
@override
|
||||||
|
@ -406,12 +420,15 @@ abstract class TronWalletBase
|
||||||
Object get keys => throw UnimplementedError("keys");
|
Object get keys => throw UnimplementedError("keys");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> rescan({required int height}) {
|
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||||
throw UnimplementedError("rescan");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||||
|
await saveKeysFile(_password);
|
||||||
|
saveKeysFile(_password, true);
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
final path = await makePath();
|
final path = await makePath();
|
||||||
await write(path: path, password: _password, data: toJSON());
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
@ -424,7 +441,8 @@ abstract class TronWalletBase
|
||||||
@override
|
@override
|
||||||
String get privateKey => _tronPrivateKey.toHex();
|
String get privateKey => _tronPrivateKey.toHex();
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
@override
|
||||||
|
WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
'mnemonic': _mnemonic,
|
'mnemonic': _mnemonic,
|
||||||
|
@ -512,7 +530,7 @@ abstract class TronWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> renameWalletFiles(String newWalletName) async {
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
String transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
const transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||||
|
|
||||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||||
final currentWalletFile = File(currentWalletPath);
|
final currentWalletFile = File(currentWalletPath);
|
||||||
|
@ -550,9 +568,7 @@ abstract class TronWalletBase
|
||||||
Future<String> signMessage(String message, {String? address}) async =>
|
Future<String> signMessage(String message, {String? address}) async =>
|
||||||
_tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
_tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||||
|
|
||||||
String getTronBase58AddressFromHex(String hexAddress) {
|
String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
|
||||||
return TronAddress(hexAddress).toAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateScanProviderUsageState(bool isEnabled) {
|
void updateScanProviderUsageState(bool isEnabled) {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/transaction_history.dart';
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
@ -14,7 +15,6 @@ import 'package:cw_tron/tron_exception.dart';
|
||||||
import 'package:cw_tron/tron_wallet.dart';
|
import 'package:cw_tron/tron_wallet.dart';
|
||||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
class TronWalletService extends WalletService<
|
class TronWalletService extends WalletService<
|
||||||
TronNewWalletCredentials,
|
TronNewWalletCredentials,
|
||||||
|
@ -153,7 +153,8 @@ class TronWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>>
|
||||||
|
restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||||
// TODO: implement restoreFromHardwareWallet
|
// TODO: implement restoreFromHardwareWallet
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue