dev
dart 214 lines 8.65 KB
Raw
1 import 'dart:async';
2
3 import 'package:cake_wallet/core/generate_wallet_password.dart';
4 import 'package:cake_wallet/core/key_service.dart';
5 import 'package:cake_wallet/entities/preferences_key.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 import 'package:cake_wallet/main.dart';
8 import 'package:cake_wallet/new-ui/widgets/wallet_deprecation_popup.dart';
9 import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
10 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
11 import 'package:cake_wallet/utils/exception_handler.dart';
12 import 'package:cake_wallet/utils/show_pop_up.dart';
13 import 'package:cw_core/exceptions.dart' show WalletDeprecationException;
14 import 'package:cw_core/utils/print_verbose.dart';
15 import 'package:cw_core/wallet_base.dart';
16 import 'package:cw_core/wallet_info.dart';
17 import 'package:cw_core/wallet_service.dart';
18 import 'package:cw_core/wallet_type.dart';
19 import 'package:flutter/material.dart';
20 import 'package:flutter/services.dart';
21 import 'package:shared_preferences/shared_preferences.dart';
22
23 class WalletLoadingService {
24 WalletLoadingService(
25 this.sharedPreferences,
26 this.keyService,
27 this.walletServiceFactory,
28 );
29
30 final SharedPreferences sharedPreferences;
31 final KeyService keyService;
32 final WalletService Function(WalletType type) walletServiceFactory;
33
34 Future<void> renameWallet(WalletType type, String name, String newName,
35 {String? password}) async {
36 try {
37 final walletService = walletServiceFactory.call(type);
38 final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
39
40 // Save the current wallet's password to the new wallet name's key
41 await keyService.saveWalletPassword(walletName: newName, password: walletPassword);
42
43 await walletService.rename(name, walletPassword, newName);
44 // Delete previous wallet name from keyService to keep only new wallet's name
45 // otherwise keeps duplicate (old and new names)
46 await keyService.deleteWalletPassword(walletName: name);
47
48 // set shared preferences flag based on previous wallet name
49 if (type == WalletType.monero) {
50 final oldNameKey = PreferencesKey.moneroWalletUpdateV1Key(name);
51 final isPasswordUpdated = sharedPreferences.getBool(oldNameKey) ?? false;
52 final newNameKey = PreferencesKey.moneroWalletUpdateV1Key(newName);
53 await sharedPreferences.setBool(newNameKey, isPasswordUpdated);
54 }
55 } catch (error, stack) {
56 await ExceptionHandler.resetLastPopupDate();
57 await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
58 }
59 }
60
61 Future<WalletBase> load(WalletType type, String name,
62 {String? password, bool isBackground = false}) async {
63 try {
64 if (!isBackground) {
65 await sharedPreferences.setString(
66 PreferencesKey.backgroundSyncLastTrigger(name), DateTime.now().toIso8601String());
67 }
68 final walletService = walletServiceFactory.call(type);
69 final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
70 final wallet = await walletService.openWallet(name, walletPassword);
71
72 if (type == WalletType.monero) {
73 await updateMoneroWalletPassword(wallet);
74 }
75
76 return wallet;
77 } catch (error, stack) {
78 String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):";
79
80 if (error is WalletDeprecationException) {
81 if (navigatorKey.currentContext != null) {
82 showModalBottomSheet(
83 context: navigatorKey.currentContext!,
84 builder: (context) => WalletDeprecationPopup(
85 type: type,
86 seed: error.seed,
87 ));
88 }
89 } else {
90 await ExceptionHandler.resetLastPopupDate();
91 final isLedgerError = await ExceptionHandler.isLedgerError(error);
92 if (isLedgerError || await requireHardwareWalletConnection(type, name)) rethrow;
93 await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
94 }
95
96 // try fetching the seeds of the corrupted wallet to show it to the user
97 try {
98 corruptedWalletsSeeds += await _getCorruptedWalletSeeds(name, type);
99 } catch (e) {
100 corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
101 }
102
103 // try opening another wallet that is not corrupted to give user access to the app
104 WalletBase? wallet;
105 for (var walletInfo in await WalletInfo.getAll()) {
106 try {
107 final walletService = walletServiceFactory.call(walletInfo.type);
108 final walletPassword = await keyService.getWalletPassword(walletName: walletInfo.name);
109 wallet = await walletService.openWallet(walletInfo.name, walletPassword);
110
111 if (walletInfo.type == WalletType.monero) {
112 await updateMoneroWalletPassword(wallet);
113 }
114
115 await sharedPreferences.setString(PreferencesKey.currentWalletName, wallet.name);
116 await sharedPreferences.setInt(
117 PreferencesKey.currentWalletType, serializeToInt(wallet.type));
118
119 // if found a wallet that is not corrupted, then still display the seeds of the corrupted ones
120 authenticatedErrorStreamController.add(corruptedWalletsSeeds);
121 } catch (e) {
122 printV(e);
123 // save seeds and show corrupted wallets' seeds to the user
124 try {
125 final seeds = await _getCorruptedWalletSeeds(walletInfo.name, walletInfo.type);
126 if (!corruptedWalletsSeeds.contains(seeds)) {
127 corruptedWalletsSeeds += seeds;
128 }
129 } catch (e) {
130 corruptedWalletsSeeds += "\nFailed to fetch $name seeds: $e";
131 }
132 }
133 }
134
135 // if all user's wallets are corrupted throw exception
136 final msg = error.toString() + "\n" + corruptedWalletsSeeds;
137 if (navigatorKey.currentContext != null) {
138 await showPopUp<void>(
139 context: navigatorKey.currentContext!,
140 builder: (BuildContext context) {
141 return AlertWithTwoActions(
142 alertTitle: "Corrupted seeds",
143 alertContent: S.of(context).corrupted_seed_notice,
144 leftButtonText: S.of(context).cancel,
145 rightButtonText: S.of(context).show_seed,
146 actionLeftButton: () {
147 if (context.mounted && Navigator.of(context).canPop()) {
148 Navigator.of(context).pop();
149 }
150 },
151 actionRightButton: () => showSeedsPopup(context, msg),
152 );
153 });
154 } else {
155 throw msg;
156 }
157 if (wallet == null) {
158 throw Exception("Wallet is null");
159 }
160 return wallet;
161 }
162 }
163
164 Future<void> showSeedsPopup(BuildContext context, String message) async {
165 Navigator.of(context).pop();
166 await showPopUp<void>(
167 context: context,
168 builder: (BuildContext context) {
169 return AlertWithTwoActions(
170 alertTitle: "Corrupted seeds",
171 alertContent: message,
172 leftButtonText: S.of(context).copy,
173 rightButtonText: S.of(context).ok,
174 actionLeftButton: () async {
175 await Clipboard.setData(ClipboardData(text: message));
176 },
177 actionRightButton: () async {
178 Navigator.of(context).pop();
179 },
180 );
181 });
182 }
183
184 Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
185 final key = PreferencesKey.moneroWalletUpdateV1Key(wallet.name);
186 var isPasswordUpdated = sharedPreferences.getBool(key) ?? false;
187
188 if (isPasswordUpdated) {
189 return;
190 }
191
192 final password = generateWalletPassword();
193 // Save new generated password with backup key for case where
194 // wallet will change password, but it will fail to update in secure storage
195 final bakWalletName = '#__${wallet.name}_bak__#';
196 await keyService.saveWalletPassword(walletName: bakWalletName, password: password);
197 await wallet.changePassword(password);
198 await keyService.saveWalletPassword(walletName: wallet.name, password: password);
199 isPasswordUpdated = true;
200 await sharedPreferences.setBool(key, isPasswordUpdated);
201 }
202
203 Future<String> _getCorruptedWalletSeeds(String name, WalletType type) async {
204 final walletService = walletServiceFactory.call(type);
205 final password = await keyService.getWalletPassword(walletName: name);
206
207 return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}";
208 }
209
210 Future<bool> requireHardwareWalletConnection(WalletType type, String name) async {
211 final walletService = walletServiceFactory.call(type);
212 return await walletService.requireHardwareWalletConnection(name);
213 }
214 }