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>

Omar Hatem committed Aug 9, 2024 at 23:15 UTC fb33a6f23dce32172d9b80f343972dc4e07289c3
18 files changed +433 -144
cw_bitcoin/lib/bitcoin_wallet.dart
+42 -24
@@ -6,15 +6,16 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
6 import 'package:convert/convert.dart';
7 import 'package:cw_bitcoin/bitcoin_address_record.dart';
8 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
9 -import 'package:cw_bitcoin/electrum_derivations.dart';
9 import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
10 import 'package:cw_bitcoin/electrum_balance.dart';
11 +import 'package:cw_bitcoin/electrum_derivations.dart';
12 import 'package:cw_bitcoin/electrum_wallet.dart';
13 import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
14 import 'package:cw_bitcoin/psbt_transaction_builder.dart';
15 import 'package:cw_core/crypto_currency.dart';
16 import 'package:cw_core/unspent_coins_info.dart';
17 import 'package:cw_core/wallet_info.dart';
18 +import 'package:cw_core/wallet_keys_file.dart';
19 import 'package:flutter/foundation.dart';
20 import 'package:hive/hive.dart';
21 import 'package:ledger_bitcoin/ledger_bitcoin.dart';
@@ -143,49 +144,66 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
144 final network = walletInfo.network != null
145 ? BasedUtxoNetwork.fromName(walletInfo.network!)
146 : BitcoinNetwork.mainnet;
146 - final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
147
148 - walletInfo.derivationInfo ??= DerivationInfo(
149 - derivationType: snp.derivationType ?? DerivationType.electrum,
150 - derivationPath: snp.derivationPath,
151 - );
148 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
149 +
150 + ElectrumWalletSnapshot? snp = null;
151 +
152 + try {
153 + snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
154 + } catch (e) {
155 + if (!hasKeysFile) rethrow;
156 + }
157 +
158 + final WalletKeysData keysData;
159 + // Migrate wallet from the old scheme to then new .keys file scheme
160 + if (!hasKeysFile) {
161 + keysData =
162 + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
163 + } else {
164 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
165 + }
166 +
167 + walletInfo.derivationInfo ??= DerivationInfo();
168
169 // set the default if not present:
154 - walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
155 - walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
170 + walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
171 + walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
172
173 Uint8List? seedBytes = null;
174 + final mnemonic = keysData.mnemonic;
175 + final passphrase = keysData.passphrase;
176
159 - if (snp.mnemonic != null) {
177 + if (mnemonic != null) {
178 switch (walletInfo.derivationInfo!.derivationType) {
179 case DerivationType.electrum:
162 - seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
180 + seedBytes = await mnemonicToSeedBytes(mnemonic);
181 break;
182 case DerivationType.bip39:
183 default:
184 seedBytes = await bip39.mnemonicToSeed(
167 - snp.mnemonic!,
168 - passphrase: snp.passphrase ?? '',
185 + mnemonic,
186 + passphrase: passphrase ?? '',
187 );
188 break;
189 }
190 }
191
192 return BitcoinWallet(
175 - mnemonic: snp.mnemonic,
176 - xpub: snp.xpub,
193 + mnemonic: mnemonic,
194 + xpub: keysData.xPub,
195 password: password,
178 - passphrase: snp.passphrase,
196 + passphrase: passphrase,
197 walletInfo: walletInfo,
198 unspentCoinsInfo: unspentCoinsInfo,
181 - initialAddresses: snp.addresses,
182 - initialSilentAddresses: snp.silentAddresses,
183 - initialSilentAddressIndex: snp.silentAddressIndex,
184 - initialBalance: snp.balance,
199 + initialAddresses: snp?.addresses,
200 + initialSilentAddresses: snp?.silentAddresses,
201 + initialSilentAddressIndex: snp?.silentAddressIndex ?? 0,
202 + initialBalance: snp?.balance,
203 seedBytes: seedBytes,
186 - initialRegularAddressIndex: snp.regularAddressIndex,
187 - initialChangeAddressIndex: snp.changeAddressIndex,
188 - addressPageType: snp.addressPageType,
204 + initialRegularAddressIndex: snp?.regularAddressIndex,
205 + initialChangeAddressIndex: snp?.changeAddressIndex,
206 + addressPageType: snp?.addressPageType,
207 networkParam: network,
208 alwaysScan: alwaysScan,
209 );
@@ -249,8 +267,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
267 final accountPath = walletInfo.derivationInfo?.derivationPath;
268 final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
269
252 - final signature = await _bitcoinLedgerApp!
253 - .signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
270 + final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!,
271 + message: ascii.encode(message), signDerivationPath: derivationPath);
272 return base64Encode(signature);
273 }
274
cw_bitcoin/lib/bitcoin_wallet_service.dart
+2
@@ -41,8 +41,10 @@ class BitcoinWalletService extends WalletService<
41 unspentCoinsInfo: unspentCoinsInfoSource,
42 network: network,
43 );
44 +
45 await wallet.save();
46 await wallet.init();
47 +
48 return wallet;
49 }
50
cw_bitcoin/lib/electrum_wallet.dart
+11 -3
@@ -37,6 +37,7 @@ import 'package:cw_core/unspent_coins_info.dart';
37 import 'package:cw_core/utils/file.dart';
38 import 'package:cw_core/wallet_base.dart';
39 import 'package:cw_core/wallet_info.dart';
40 +import 'package:cw_core/wallet_keys_file.dart';
41 import 'package:cw_core/wallet_type.dart';
42 import 'package:cw_core/get_height_by_date.dart';
43 import 'package:flutter/foundation.dart';
@@ -54,7 +55,7 @@ const int TWEAKS_COUNT = 25;
55
56 abstract class ElectrumWalletBase
57 extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
57 - with Store {
58 + with Store, WalletKeysFile {
59 ElectrumWalletBase({
60 required String password,
61 required WalletInfo walletInfo,
@@ -169,6 +170,10 @@ abstract class ElectrumWalletBase
170 @override
171 String? get seed => _mnemonic;
172
173 + @override
174 + WalletKeysData get walletKeysData =>
175 + WalletKeysData(mnemonic: _mnemonic, xPub: xpub, passphrase: passphrase);
176 +
177 bitcoin.NetworkType networkType;
178 BasedUtxoNetwork network;
179
@@ -1076,6 +1081,11 @@ abstract class ElectrumWalletBase
1081
1082 @override
1083 Future<void> save() async {
1084 + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
1085 + await saveKeysFile(_password);
1086 + saveKeysFile(_password, true);
1087 + }
1088 +
1089 final path = await makePath();
1090 await write(path: path, password: _password, data: toJSON());
1091 await transactionHistory.save();
@@ -1131,8 +1141,6 @@ abstract class ElectrumWalletBase
1141 _autoSaveTimer?.cancel();
1142 }
1143
1134 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
1135 -
1144 @action
1145 Future<void> updateAllUnspents() async {
1146 List<BitcoinUnspent> updatedUnspentCoins = [];
cw_bitcoin/lib/electrum_wallet_snapshot.dart
+7 -1
@@ -32,15 +32,21 @@ class ElectrumWalletSnapshot {
32 final WalletType type;
33 final String? addressPageType;
34
35 + @deprecated
36 String? mnemonic;
37 +
38 + @deprecated
39 String? xpub;
40 +
41 + @deprecated
42 + String? passphrase;
43 +
44 List<BitcoinAddressRecord> addresses;
45 List<BitcoinSilentPaymentAddressRecord> silentAddresses;
46 ElectrumBalance balance;
47 Map<String, int> regularAddressIndex;
48 Map<String, int> changeAddressIndex;
49 int silentAddressIndex;
43 - String? passphrase;
50 DerivationType? derivationType;
51 String? derivationPath;
52
cw_bitcoin/lib/litecoin_wallet.dart
+37 -18
@@ -1,20 +1,21 @@
1 +import 'package:bip39/bip39.dart' as bip39;
2 import 'package:bitcoin_base/bitcoin_base.dart';
3 +import 'package:cw_bitcoin/bitcoin_address_record.dart';
4 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
5 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
4 -import 'package:cw_core/crypto_currency.dart';
5 -import 'package:cw_core/unspent_coins_info.dart';
6 +import 'package:cw_bitcoin/electrum_balance.dart';
7 +import 'package:cw_bitcoin/electrum_wallet.dart';
8 +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
9 +import 'package:cw_bitcoin/litecoin_network.dart';
10 import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
11 +import 'package:cw_core/crypto_currency.dart';
12 import 'package:cw_core/transaction_priority.dart';
13 +import 'package:cw_core/unspent_coins_info.dart';
14 +import 'package:cw_core/wallet_info.dart';
15 +import 'package:cw_core/wallet_keys_file.dart';
16 import 'package:flutter/foundation.dart';
17 import 'package:hive/hive.dart';
18 import 'package:mobx/mobx.dart';
11 -import 'package:cw_core/wallet_info.dart';
12 -import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
13 -import 'package:cw_bitcoin/electrum_wallet.dart';
14 -import 'package:cw_bitcoin/bitcoin_address_record.dart';
15 -import 'package:cw_bitcoin/electrum_balance.dart';
16 -import 'package:cw_bitcoin/litecoin_network.dart';
17 -import 'package:bip39/bip39.dart' as bip39;
19
20 part 'litecoin_wallet.g.dart';
21
@@ -101,19 +102,37 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
102 required Box<UnspentCoinsInfo> unspentCoinsInfo,
103 required String password,
104 }) async {
104 - final snp =
105 - await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
105 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
106 +
107 + ElectrumWalletSnapshot? snp = null;
108 +
109 + try {
110 + snp = await ElectrumWalletSnapshot.load(
111 + name, walletInfo.type, password, LitecoinNetwork.mainnet);
112 + } catch (e) {
113 + if (!hasKeysFile) rethrow;
114 + }
115 +
116 + final WalletKeysData keysData;
117 + // Migrate wallet from the old scheme to then new .keys file scheme
118 + if (!hasKeysFile) {
119 + keysData =
120 + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
121 + } else {
122 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
123 + }
124 +
125 return LitecoinWallet(
107 - mnemonic: snp.mnemonic!,
126 + mnemonic: keysData.mnemonic!,
127 password: password,
128 walletInfo: walletInfo,
129 unspentCoinsInfo: unspentCoinsInfo,
111 - initialAddresses: snp.addresses,
112 - initialBalance: snp.balance,
113 - seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
114 - initialRegularAddressIndex: snp.regularAddressIndex,
115 - initialChangeAddressIndex: snp.changeAddressIndex,
116 - addressPageType: snp.addressPageType,
130 + initialAddresses: snp?.addresses,
131 + initialBalance: snp?.balance,
132 + seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!),
133 + initialRegularAddressIndex: snp?.regularAddressIndex,
134 + initialChangeAddressIndex: snp?.changeAddressIndex,
135 + addressPageType: snp?.addressPageType,
136 );
137 }
138
cw_bitcoin/lib/litecoin_wallet_service.dart
+1
@@ -33,6 +33,7 @@ class LitecoinWalletService extends WalletService<
33 passphrase: credentials.passphrase,
34 walletInfo: credentials.walletInfo!,
35 unspentCoinsInfo: unspentCoinsInfoSource);
36 +
37 await wallet.save();
38 await wallet.init();
39
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+27 -8
@@ -12,6 +12,7 @@ import 'package:cw_core/crypto_currency.dart';
12 import 'package:cw_core/transaction_priority.dart';
13 import 'package:cw_core/unspent_coins_info.dart';
14 import 'package:cw_core/wallet_info.dart';
15 +import 'package:cw_core/wallet_keys_file.dart';
16 import 'package:flutter/foundation.dart';
17 import 'package:hive/hive.dart';
18 import 'package:mobx/mobx.dart';
@@ -89,14 +90,32 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
90 required Box<UnspentCoinsInfo> unspentCoinsInfo,
91 required String password,
92 }) async {
92 - final snp = await ElectrumWalletSnapshot.load(
93 - name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
93 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
94 +
95 + ElectrumWalletSnapshot? snp = null;
96 +
97 + try {
98 + snp = await ElectrumWalletSnapshot.load(
99 + name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
100 + } catch (e) {
101 + if (!hasKeysFile) rethrow;
102 + }
103 +
104 + final WalletKeysData keysData;
105 + // Migrate wallet from the old scheme to then new .keys file scheme
106 + if (!hasKeysFile) {
107 + keysData =
108 + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase);
109 + } else {
110 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
111 + }
112 +
113 return BitcoinCashWallet(
95 - mnemonic: snp.mnemonic!,
114 + mnemonic: keysData.mnemonic!,
115 password: password,
116 walletInfo: walletInfo,
117 unspentCoinsInfo: unspentCoinsInfo,
99 - initialAddresses: snp.addresses.map((addr) {
118 + initialAddresses: snp?.addresses.map((addr) {
119 try {
120 BitcoinCashAddress(addr.address);
121 return BitcoinAddressRecord(
@@ -116,10 +135,10 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
135 );
136 }
137 }).toList(),
119 - initialBalance: snp.balance,
120 - seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
121 - initialRegularAddressIndex: snp.regularAddressIndex,
122 - initialChangeAddressIndex: snp.changeAddressIndex,
138 + initialBalance: snp?.balance,
139 + seedBytes: await Mnemonic.toSeed(keysData.mnemonic!),
140 + initialRegularAddressIndex: snp?.regularAddressIndex,
141 + initialChangeAddressIndex: snp?.changeAddressIndex,
142 addressPageType: P2pkhAddressType.p2pkh,
143 );
144 }
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet_service.dart
+2
@@ -34,8 +34,10 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
34 password: credentials.password!,
35 walletInfo: credentials.walletInfo!,
36 unspentCoinsInfo: unspentCoinsInfoSource);
37 +
38 await wallet.save();
39 await wallet.init();
40 +
41 return wallet;
42 }
43
cw_core/lib/wallet_keys_file.dart new
+110
@@ -0,0 +1,110 @@
1 +import 'dart:convert';
2 +import 'dart:developer' as dev;
3 +import 'dart:io';
4 +
5 +import 'package:cw_core/balance.dart';
6 +import 'package:cw_core/pathForWallet.dart';
7 +import 'package:cw_core/transaction_history.dart';
8 +import 'package:cw_core/transaction_info.dart';
9 +import 'package:cw_core/utils/file.dart';
10 +import 'package:cw_core/wallet_base.dart';
11 +import 'package:cw_core/wallet_type.dart';
12 +
13 +mixin WalletKeysFile<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
14 + TransactionType extends TransactionInfo>
15 + on WalletBase<BalanceType, HistoryType, TransactionType> {
16 + Future<String> makePath() => pathForWallet(name: walletInfo.name, type: walletInfo.type);
17 +
18 + // this needs to be overridden
19 + WalletKeysData get walletKeysData;
20 +
21 + Future<String> makeKeysFilePath() async => "${await makePath()}.keys";
22 +
23 + Future<void> saveKeysFile(String password, [bool isBackup = false]) async {
24 + try {
25 + final rootPath = await makeKeysFilePath();
26 + final path = "$rootPath${isBackup ? ".backup" : ""}";
27 + dev.log("Saving .keys file '$path'");
28 + await write(path: path, password: password, data: walletKeysData.toJSON());
29 + } catch (_) {}
30 + }
31 +
32 + static Future<void> createKeysFile(
33 + String name, WalletType type, String password, WalletKeysData walletKeysData,
34 + [bool withBackup = true]) async {
35 + try {
36 + final rootPath = await pathForWallet(name: name, type: type);
37 + final path = "$rootPath.keys";
38 +
39 + dev.log("Saving .keys file '$path'");
40 + await write(path: path, password: password, data: walletKeysData.toJSON());
41 +
42 + if (withBackup) {
43 + dev.log("Saving .keys.backup file '$path.backup'");
44 + await write(path: "$path.backup", password: password, data: walletKeysData.toJSON());
45 + }
46 + } catch (_) {}
47 + }
48 +
49 + static Future<bool> hasKeysFile(String name, WalletType type) async {
50 + try {
51 + final path = await pathForWallet(name: name, type: type);
52 + return File("$path.keys").existsSync() || File("$path.keys.backup").existsSync();
53 + } catch (_) {
54 + return false;
55 + }
56 + }
57 +
58 + static Future<WalletKeysData> readKeysFile(String name, WalletType type, String password) async {
59 + final path = await pathForWallet(name: name, type: type);
60 +
61 + var readPath = "$path.keys";
62 + try {
63 + if (!File(readPath).existsSync()) throw Exception("No .keys file found for $name $type");
64 +
65 + final jsonSource = await read(path: readPath, password: password);
66 + final data = json.decode(jsonSource) as Map<String, dynamic>;
67 + return WalletKeysData.fromJSON(data);
68 + } catch (e) {
69 + dev.log("Failed to read .keys file. Trying .keys.backup file...");
70 +
71 + readPath = "$readPath.backup";
72 + if (!File(readPath).existsSync())
73 + throw Exception("No .keys nor a .keys.backup file found for $name $type");
74 +
75 + final jsonSource = await read(path: readPath, password: password);
76 + final data = json.decode(jsonSource) as Map<String, dynamic>;
77 + final keysData = WalletKeysData.fromJSON(data);
78 +
79 + dev.log("Restoring .keys from .keys.backup");
80 + createKeysFile(name, type, password, keysData, false);
81 + return keysData;
82 + }
83 + }
84 +}
85 +
86 +class WalletKeysData {
87 + final String? privateKey;
88 + final String? mnemonic;
89 + final String? altMnemonic;
90 + final String? passphrase;
91 + final String? xPub;
92 +
93 + WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub});
94 +
95 + String toJSON() => jsonEncode({
96 + "privateKey": privateKey,
97 + "mnemonic": mnemonic,
98 + if (altMnemonic != null) "altMnemonic": altMnemonic,
99 + if (passphrase != null) "passphrase": passphrase,
100 + if (xPub != null) "xPub": xPub
101 + });
102 +
103 + static WalletKeysData fromJSON(Map<String, dynamic> json) => WalletKeysData(
104 + privateKey: json["privateKey"] as String?,
105 + mnemonic: json["mnemonic"] as String?,
106 + altMnemonic: json["altMnemonic"] as String?,
107 + passphrase: json["passphrase"] as String?,
108 + xPub: json["xPub"] as String?,
109 + );
110 +}
cw_ethereum/lib/ethereum_wallet.dart
+26 -7
@@ -6,6 +6,7 @@ import 'package:cw_core/erc20_token.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 import 'package:cw_core/transaction_direction.dart';
8 import 'package:cw_core/wallet_info.dart';
9 +import 'package:cw_core/wallet_keys_file.dart';
10 import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
11 import 'package:cw_ethereum/ethereum_client.dart';
12 import 'package:cw_ethereum/ethereum_transaction_history.dart';
@@ -122,19 +123,37 @@ class EthereumWallet extends EVMChainWallet {
123
124 static Future<EthereumWallet> open(
125 {required String name, required String password, required WalletInfo walletInfo}) async {
126 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
127 final path = await pathForWallet(name: name, type: walletInfo.type);
126 - final jsonSource = await read(path: path, password: password);
127 - final data = json.decode(jsonSource) as Map;
128 - final mnemonic = data['mnemonic'] as String?;
129 - final privateKey = data['private_key'] as String?;
130 - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
128 +
129 + Map<String, dynamic>? data;
130 + try {
131 + final jsonSource = await read(path: path, password: password);
132 +
133 + data = json.decode(jsonSource) as Map<String, dynamic>;
134 + } catch (e) {
135 + if (!hasKeysFile) rethrow;
136 + }
137 +
138 + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
139 EVMChainERC20Balance(BigInt.zero);
140
141 + final WalletKeysData keysData;
142 + // Migrate wallet from the old scheme to then new .keys file scheme
143 + if (!hasKeysFile) {
144 + final mnemonic = data!['mnemonic'] as String?;
145 + final privateKey = data['private_key'] as String?;
146 +
147 + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
148 + } else {
149 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
150 + }
151 +
152 return EthereumWallet(
153 walletInfo: walletInfo,
154 password: password,
136 - mnemonic: mnemonic,
137 - privateKey: privateKey,
155 + mnemonic: keysData.mnemonic,
156 + privateKey: keysData.privateKey,
157 initialBalance: balance,
158 client: EthereumClient(),
159 );
cw_evm/lib/evm_chain_wallet.dart
+9 -2
@@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
16 import 'package:cw_core/wallet_addresses.dart';
17 import 'package:cw_core/wallet_base.dart';
18 import 'package:cw_core/wallet_info.dart';
19 +import 'package:cw_core/wallet_keys_file.dart';
20 import 'package:cw_core/wallet_type.dart';
21 import 'package:cw_evm/evm_chain_client.dart';
22 import 'package:cw_evm/evm_chain_exceptions.dart';
@@ -58,7 +59,7 @@ abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
59
60 abstract class EVMChainWalletBase
61 extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
61 - with Store {
62 + with Store, WalletKeysFile {
63 EVMChainWalletBase({
64 required WalletInfo walletInfo,
65 required EVMChainClient client,
@@ -508,6 +509,11 @@ abstract class EVMChainWalletBase
509
510 @override
511 Future<void> save() async {
512 + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
513 + await saveKeysFile(_password);
514 + saveKeysFile(_password, true);
515 + }
516 +
517 await walletAddresses.updateAddressesInBox();
518 final path = await makePath();
519 await write(path: path, password: _password, data: toJSON());
@@ -522,7 +528,8 @@ abstract class EVMChainWalletBase
528 ? HEX.encode((evmChainPrivateKey as EthPrivateKey).privateKey)
529 : null;
530
525 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
531 + @override
532 + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
533
534 String toJSON() => json.encode({
535 'mnemonic': _mnemonic,
cw_nano/lib/nano_wallet.dart
+49 -32
@@ -1,8 +1,12 @@
1 +import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4
5 +import 'package:bip39/bip39.dart' as bip39;
6 import 'package:cw_core/cake_hive.dart';
7 import 'package:cw_core/crypto_currency.dart';
8 +import 'package:cw_core/n2_node.dart';
9 +import 'package:cw_core/nano_account.dart';
10 import 'package:cw_core/nano_account_info_response.dart';
11 import 'package:cw_core/node.dart';
12 import 'package:cw_core/pathForWallet.dart';
@@ -10,23 +14,20 @@ import 'package:cw_core/pending_transaction.dart';
14 import 'package:cw_core/sync_status.dart';
15 import 'package:cw_core/transaction_direction.dart';
16 import 'package:cw_core/transaction_priority.dart';
17 +import 'package:cw_core/wallet_base.dart';
18 import 'package:cw_core/wallet_info.dart';
19 +import 'package:cw_core/wallet_keys_file.dart';
20 import 'package:cw_nano/file.dart';
15 -import 'package:cw_core/nano_account.dart';
16 -import 'package:cw_core/n2_node.dart';
21 import 'package:cw_nano/nano_balance.dart';
22 import 'package:cw_nano/nano_client.dart';
23 import 'package:cw_nano/nano_transaction_credentials.dart';
24 import 'package:cw_nano/nano_transaction_history.dart';
25 import 'package:cw_nano/nano_transaction_info.dart';
26 +import 'package:cw_nano/nano_wallet_addresses.dart';
27 import 'package:cw_nano/nano_wallet_keys.dart';
28 import 'package:cw_nano/pending_nano_transaction.dart';
29 import 'package:mobx/mobx.dart';
25 -import 'dart:async';
26 -import 'package:cw_nano/nano_wallet_addresses.dart';
27 -import 'package:cw_core/wallet_base.dart';
30 import 'package:nanodart/nanodart.dart';
29 -import 'package:bip39/bip39.dart' as bip39;
31 import 'package:nanoutil/nanoutil.dart';
32
33 part 'nano_wallet.g.dart';
@@ -34,7 +35,8 @@ part 'nano_wallet.g.dart';
35 class NanoWallet = NanoWalletBase with _$NanoWallet;
36
37 abstract class NanoWalletBase
37 - extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
38 + extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo>
39 + with Store, WalletKeysFile {
40 NanoWalletBase({
41 required WalletInfo walletInfo,
42 required String mnemonic,
@@ -70,6 +72,7 @@ abstract class NanoWalletBase
72
73 String? _representativeAddress;
74 int repScore = 100;
75 +
76 bool get isRepOk => repScore >= 90;
77
78 late final NanoClient _client;
@@ -128,14 +131,10 @@ abstract class NanoWalletBase
131 }
132
133 @override
131 - int calculateEstimatedFee(TransactionPriority priority, int? amount) {
132 - return 0; // always 0 :)
133 - }
134 + int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0; // always 0 :)
135
136 @override
136 - Future<void> changePassword(String password) {
137 - throw UnimplementedError("changePassword");
138 - }
137 + Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
138
139 @override
140 void close() {
@@ -170,9 +169,7 @@ abstract class NanoWalletBase
169 }
170
171 @override
173 - Future<void> connectToPowNode({required Node node}) async {
174 - _client.connectPow(node);
175 - }
172 + Future<void> connectToPowNode({required Node node}) async => _client.connectPow(node);
173
174 @override
175 Future<PendingTransaction> createTransaction(Object credentials) async {
@@ -296,9 +293,7 @@ abstract class NanoWalletBase
293 }
294
295 @override
299 - NanoWalletKeys get keys {
300 - return NanoWalletKeys(seedKey: _hexSeed!);
301 - }
296 + NanoWalletKeys get keys => NanoWalletKeys(seedKey: _hexSeed!);
297
298 @override
299 String? get privateKey => _privateKey!;
@@ -312,6 +307,11 @@ abstract class NanoWalletBase
307
308 @override
309 Future<void> save() async {
310 + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
311 + await saveKeysFile(_password);
312 + saveKeysFile(_password, true);
313 + }
314 +
315 await walletAddresses.updateAddressesInBox();
316 final path = await makePath();
317 await write(path: path, password: _password, data: toJSON());
@@ -323,6 +323,9 @@ abstract class NanoWalletBase
323
324 String get hexSeed => _hexSeed!;
325
326 + @override
327 + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, altMnemonic: hexSeed);
328 +
329 String get representative => _representativeAddress ?? "";
330
331 @action
@@ -358,8 +361,6 @@ abstract class NanoWalletBase
361 }
362 }
363
361 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
362 -
364 String toJSON() => json.encode({
365 'seedKey': _hexSeed,
366 'mnemonic': _mnemonic,
@@ -373,31 +374,47 @@ abstract class NanoWalletBase
374 required String password,
375 required WalletInfo walletInfo,
376 }) async {
377 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
378 final path = await pathForWallet(name: name, type: walletInfo.type);
377 - final jsonSource = await read(path: path, password: password);
379
379 - final data = json.decode(jsonSource) as Map;
380 - final mnemonic = data['mnemonic'] as String;
380 + Map<String, dynamic>? data = null;
381 + try {
382 + final jsonSource = await read(path: path, password: password);
383 +
384 + data = json.decode(jsonSource) as Map<String, dynamic>;
385 + } catch (e) {
386 + if (!hasKeysFile) rethrow;
387 + }
388
389 final balance = NanoBalance.fromRawString(
383 - currentBalance: data['currentBalance'] as String? ?? "0",
384 - receivableBalance: data['receivableBalance'] as String? ?? "0",
390 + currentBalance: data?['currentBalance'] as String? ?? "0",
391 + receivableBalance: data?['receivableBalance'] as String? ?? "0",
392 );
393
394 + final WalletKeysData keysData;
395 + // Migrate wallet from the old scheme to then new .keys file scheme
396 + if (!hasKeysFile) {
397 + final mnemonic = data!['mnemonic'] as String;
398 + final isHexSeed = !mnemonic.contains(' ');
399 +
400 + keysData = WalletKeysData(
401 + mnemonic: isHexSeed ? null : mnemonic, altMnemonic: isHexSeed ? mnemonic : null);
402 + } else {
403 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
404 + }
405 +
406 DerivationType derivationType = DerivationType.nano;
388 - if (data['derivationType'] == "DerivationType.bip39") {
407 + if (data?['derivationType'] == "DerivationType.bip39") {
408 derivationType = DerivationType.bip39;
409 }
410
411 walletInfo.derivationInfo ??= DerivationInfo(derivationType: derivationType);
393 - if (walletInfo.derivationInfo!.derivationType == null) {
394 - walletInfo.derivationInfo!.derivationType = derivationType;
395 - }
412 + walletInfo.derivationInfo!.derivationType ??= derivationType;
413
414 return NanoWallet(
415 walletInfo: walletInfo,
416 password: password,
400 - mnemonic: mnemonic,
417 + mnemonic: keysData.mnemonic!,
418 initialBalance: balance,
419 );
420 // init() should always be run after this!
@@ -435,7 +452,7 @@ abstract class NanoWalletBase
452 _representativeAddress = await _client.getRepFromPrefs();
453 throw Exception("Failed to get representative address $e");
454 }
438 -
455 +
456 repScore = await _client.getRepScore(_representativeAddress!);
457 }
458
cw_nano/lib/nano_wallet_service.dart
+1 -1
@@ -39,7 +39,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
39 mnemonic: mnemonic,
40 password: credentials.password!,
41 );
42 - wallet.init();
42 + await wallet.init();
43 return wallet;
44 }
45
cw_polygon/lib/polygon_wallet.dart
+28 -9
@@ -1,11 +1,12 @@
1 import 'dart:convert';
2
3 -import 'package:cw_core/crypto_currency.dart';
3 import 'package:cw_core/cake_hive.dart';
4 +import 'package:cw_core/crypto_currency.dart';
5 import 'package:cw_core/erc20_token.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 import 'package:cw_core/transaction_direction.dart';
8 import 'package:cw_core/wallet_info.dart';
9 +import 'package:cw_core/wallet_keys_file.dart';
10 import 'package:cw_evm/evm_chain_transaction_history.dart';
11 import 'package:cw_evm/evm_chain_transaction_info.dart';
12 import 'package:cw_evm/evm_chain_transaction_model.dart';
@@ -13,9 +14,9 @@ import 'package:cw_evm/evm_chain_wallet.dart';
14 import 'package:cw_evm/evm_erc20_balance.dart';
15 import 'package:cw_evm/file.dart';
16 import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
16 -import 'package:cw_polygon/polygon_transaction_info.dart';
17 import 'package:cw_polygon/polygon_client.dart';
18 import 'package:cw_polygon/polygon_transaction_history.dart';
19 +import 'package:cw_polygon/polygon_transaction_info.dart';
20
21 class PolygonWallet extends EVMChainWallet {
22 PolygonWallet({
@@ -97,19 +98,37 @@ class PolygonWallet extends EVMChainWallet {
98
99 static Future<PolygonWallet> open(
100 {required String name, required String password, required WalletInfo walletInfo}) async {
101 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
102 final path = await pathForWallet(name: name, type: walletInfo.type);
101 - final jsonSource = await read(path: path, password: password);
102 - final data = json.decode(jsonSource) as Map;
103 - final mnemonic = data['mnemonic'] as String?;
104 - final privateKey = data['private_key'] as String?;
105 - final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
103 +
104 + Map<String, dynamic>? data;
105 + try {
106 + final jsonSource = await read(path: path, password: password);
107 +
108 + data = json.decode(jsonSource) as Map<String, dynamic>;
109 + } catch (e) {
110 + if (!hasKeysFile) rethrow;
111 + }
112 +
113 + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String) ??
114 EVMChainERC20Balance(BigInt.zero);
115
116 + final WalletKeysData keysData;
117 + // Migrate wallet from the old scheme to then new .keys file scheme
118 + if (!hasKeysFile) {
119 + final mnemonic = data!['mnemonic'] as String?;
120 + final privateKey = data['private_key'] as String?;
121 +
122 + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
123 + } else {
124 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
125 + }
126 +
127 return PolygonWallet(
128 walletInfo: walletInfo,
129 password: password,
111 - mnemonic: mnemonic,
112 - privateKey: privateKey,
130 + mnemonic: keysData.mnemonic,
131 + privateKey: keysData.privateKey,
132 initialBalance: balance,
133 client: PolygonClient(),
134 );
cw_polygon/lib/polygon_wallet_service.dart
-2
@@ -35,7 +35,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
35 await wallet.init();
36 wallet.addInitialTokens();
37 await wallet.save();
38 -
38 return wallet;
39 }
40
@@ -83,7 +82,6 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
82 await wallet.init();
83 wallet.addInitialTokens();
84 await wallet.save();
86 -
85 return wallet;
86 }
87
cw_solana/lib/solana_wallet.dart
+37 -10
@@ -1,6 +1,7 @@
1 import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4 +
5 import 'package:cw_core/cake_hive.dart';
6 import 'package:cw_core/crypto_currency.dart';
7 import 'package:cw_core/node.dart';
@@ -12,6 +13,7 @@ import 'package:cw_core/transaction_priority.dart';
13 import 'package:cw_core/wallet_addresses.dart';
14 import 'package:cw_core/wallet_base.dart';
15 import 'package:cw_core/wallet_info.dart';
16 +import 'package:cw_core/wallet_keys_file.dart';
17 import 'package:cw_solana/default_spl_tokens.dart';
18 import 'package:cw_solana/file.dart';
19 import 'package:cw_solana/solana_balance.dart';
@@ -36,7 +38,8 @@ part 'solana_wallet.g.dart';
38 class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
39
40 abstract class SolanaWalletBase
39 - extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo> with Store {
41 + extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
42 + with Store, WalletKeysFile {
43 SolanaWalletBase({
44 required WalletInfo walletInfo,
45 String? mnemonic,
@@ -121,6 +124,9 @@ abstract class SolanaWalletBase
124 return privateKey;
125 }
126
127 + @override
128 + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
129 +
130 Future<void> init() async {
131 final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
132
@@ -336,6 +342,11 @@ abstract class SolanaWalletBase
342
343 @override
344 Future<void> save() async {
345 + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
346 + await saveKeysFile(_password);
347 + saveKeysFile(_password, true);
348 + }
349 +
350 await walletAddresses.updateAddressesInBox();
351 final path = await makePath();
352 await write(path: path, password: _password, data: toJSON());
@@ -361,8 +372,6 @@ abstract class SolanaWalletBase
372 }
373 }
374
364 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
365 -
375 String toJSON() => json.encode({
376 'mnemonic': _mnemonic,
377 'private_key': _hexPrivateKey,
@@ -374,18 +383,36 @@ abstract class SolanaWalletBase
383 required String password,
384 required WalletInfo walletInfo,
385 }) async {
386 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
387 final path = await pathForWallet(name: name, type: walletInfo.type);
378 - final jsonSource = await read(path: path, password: password);
379 - final data = json.decode(jsonSource) as Map;
380 - final mnemonic = data['mnemonic'] as String?;
381 - final privateKey = data['private_key'] as String?;
382 - final balance = SolanaBalance.fromJSON(data['balance'] as String) ?? SolanaBalance(0.0);
388 +
389 + Map<String, dynamic>? data;
390 + try {
391 + final jsonSource = await read(path: path, password: password);
392 +
393 + data = json.decode(jsonSource) as Map<String, dynamic>;
394 + } catch (e) {
395 + if (!hasKeysFile) rethrow;
396 + }
397 +
398 + final balance = SolanaBalance.fromJSON(data?['balance'] as String) ?? SolanaBalance(0.0);
399 +
400 + final WalletKeysData keysData;
401 + // Migrate wallet from the old scheme to then new .keys file scheme
402 + if (!hasKeysFile) {
403 + final mnemonic = data!['mnemonic'] as String?;
404 + final privateKey = data['private_key'] as String?;
405 +
406 + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
407 + } else {
408 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
409 + }
410
411 return SolanaWallet(
412 walletInfo: walletInfo,
413 password: password,
387 - mnemonic: mnemonic,
388 - privateKey: privateKey,
414 + mnemonic: keysData.mnemonic,
415 + privateKey: keysData.privateKey,
416 initialBalance: balance,
417 );
418 }
cw_tron/lib/tron_wallet.dart
+41 -25
@@ -16,6 +16,7 @@ import 'package:cw_core/transaction_priority.dart';
16 import 'package:cw_core/wallet_addresses.dart';
17 import 'package:cw_core/wallet_base.dart';
18 import 'package:cw_core/wallet_info.dart';
19 +import 'package:cw_core/wallet_keys_file.dart';
20 import 'package:cw_core/wallet_type.dart';
21 import 'package:cw_tron/default_tron_tokens.dart';
22 import 'package:cw_tron/file.dart';
@@ -37,7 +38,8 @@ part 'tron_wallet.g.dart';
38 class TronWallet = TronWalletBase with _$TronWallet;
39
40 abstract class TronWalletBase
40 - extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store {
41 + extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo>
42 + with Store, WalletKeysFile {
43 TronWalletBase({
44 required WalletInfo walletInfo,
45 String? mnemonic,
@@ -124,18 +126,36 @@ abstract class TronWalletBase
126 required String password,
127 required WalletInfo walletInfo,
128 }) async {
129 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
130 final path = await pathForWallet(name: name, type: walletInfo.type);
128 - final jsonSource = await read(path: path, password: password);
129 - final data = json.decode(jsonSource) as Map;
130 - final mnemonic = data['mnemonic'] as String?;
131 - final privateKey = data['private_key'] as String?;
132 - final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero);
131 +
132 + Map<String, dynamic>? data;
133 + try {
134 + final jsonSource = await read(path: path, password: password);
135 +
136 + data = json.decode(jsonSource) as Map<String, dynamic>;
137 + } catch (e) {
138 + if (!hasKeysFile) rethrow;
139 + }
140 +
141 + final balance = TronBalance.fromJSON(data?['balance'] as String) ?? TronBalance(BigInt.zero);
142 +
143 + final WalletKeysData keysData;
144 + // Migrate wallet from the old scheme to then new .keys file scheme
145 + if (!hasKeysFile) {
146 + final mnemonic = data!['mnemonic'] as String?;
147 + final privateKey = data['private_key'] as String?;
148 +
149 + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey);
150 + } else {
151 + keysData = await WalletKeysFile.readKeysFile(name, walletInfo.type, password);
152 + }
153
154 return TronWallet(
155 walletInfo: walletInfo,
156 password: password,
137 - mnemonic: mnemonic,
138 - privateKey: privateKey,
157 + mnemonic: keysData.mnemonic,
158 + privateKey: keysData.privateKey,
159 initialBalance: balance,
160 );
161 }
@@ -163,9 +183,7 @@ abstract class TronWalletBase
183 }) async {
184 assert(mnemonic != null || privateKey != null);
185
166 - if (privateKey != null) {
167 - return TronPrivateKey(privateKey);
168 - }
186 + if (privateKey != null) return TronPrivateKey(privateKey);
187
188 final seed = bip39.mnemonicToSeed(mnemonic!);
189
@@ -181,14 +199,10 @@ abstract class TronWalletBase
199 int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
200
201 @override
184 - Future<void> changePassword(String password) {
185 - throw UnimplementedError("changePassword");
186 - }
202 + Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
203
204 @override
189 - void close() {
190 - _transactionsUpdateTimer?.cancel();
191 - }
205 + void close() => _transactionsUpdateTimer?.cancel();
206
207 @action
208 @override
@@ -406,12 +420,15 @@ abstract class TronWalletBase
420 Object get keys => throw UnimplementedError("keys");
421
422 @override
409 - Future<void> rescan({required int height}) {
410 - throw UnimplementedError("rescan");
411 - }
423 + Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
424
425 @override
426 Future<void> save() async {
427 + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
428 + await saveKeysFile(_password);
429 + saveKeysFile(_password, true);
430 + }
431 +
432 await walletAddresses.updateAddressesInBox();
433 final path = await makePath();
434 await write(path: path, password: _password, data: toJSON());
@@ -424,7 +441,8 @@ abstract class TronWalletBase
441 @override
442 String get privateKey => _tronPrivateKey.toHex();
443
427 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
444 + @override
445 + WalletKeysData get walletKeysData => WalletKeysData(mnemonic: _mnemonic, privateKey: privateKey);
446
447 String toJSON() => json.encode({
448 'mnemonic': _mnemonic,
@@ -512,7 +530,7 @@ abstract class TronWalletBase
530
531 @override
532 Future<void> renameWalletFiles(String newWalletName) async {
515 - String transactionHistoryFileNameForWallet = 'tron_transactions.json';
533 + const transactionHistoryFileNameForWallet = 'tron_transactions.json';
534
535 final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
536 final currentWalletFile = File(currentWalletPath);
@@ -550,9 +568,7 @@ abstract class TronWalletBase
568 Future<String> signMessage(String message, {String? address}) async =>
569 _tronPrivateKey.signPersonalMessage(ascii.encode(message));
570
553 - String getTronBase58AddressFromHex(String hexAddress) {
554 - return TronAddress(hexAddress).toAddress();
555 - }
571 + String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
572
573 void updateScanProviderUsageState(bool isEnabled) {
574 if (isEnabled) {
cw_tron/lib/tron_wallet_service.dart
+3 -2
@@ -1,6 +1,7 @@
1 import 'dart:io';
2
3 import 'package:bip39/bip39.dart' as bip39;
4 +import 'package:collection/collection.dart';
5 import 'package:cw_core/balance.dart';
6 import 'package:cw_core/pathForWallet.dart';
7 import 'package:cw_core/transaction_history.dart';
@@ -14,7 +15,6 @@ import 'package:cw_tron/tron_exception.dart';
15 import 'package:cw_tron/tron_wallet.dart';
16 import 'package:cw_tron/tron_wallet_creation_credentials.dart';
17 import 'package:hive/hive.dart';
17 -import 'package:collection/collection.dart';
18
19 class TronWalletService extends WalletService<
20 TronNewWalletCredentials,
@@ -153,7 +153,8 @@ class TronWalletService extends WalletService<
153 }
154
155 @override
156 - Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
156 + Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>>
157 + restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
158 // TODO: implement restoreFromHardwareWallet
159 throw UnimplementedError();
160 }