dev
dart 448 lines 13.6 KB
Raw
1 import 'dart:io';
2 import 'dart:convert';
3 import 'package:cake_wallet/core/secure_storage.dart';
4 import 'package:collection/collection.dart';
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:shared_preferences/shared_preferences.dart';
7 import 'package:hive/hive.dart';
8 import 'package:path_provider/path_provider.dart';
9 import 'package:cake_wallet/core/key_service.dart';
10 import 'package:cake_wallet/entities/contact.dart';
11 import 'package:cw_core/crypto_currency.dart';
12 import 'package:cake_wallet/entities/encrypt.dart';
13 import 'package:cake_wallet/entities/fiat_currency.dart';
14 import 'package:cake_wallet/entities/ios_legacy_helper.dart' as ios_legacy_helper;
15 import 'package:cake_wallet/entities/preferences_key.dart';
16 import 'package:cake_wallet/entities/secret_store_key.dart';
17 import 'package:cw_core/wallet_info.dart';
18 import 'package:cw_core/wallet_type.dart';
19 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
20 import 'package:cake_wallet/exchange/trade.dart';
21 import 'package:cake_wallet/.secrets.g.dart' as secrets;
22
23 const reservedNames = ["flutter_assets", "wallets", "db"];
24
25 Future<void> migrate_android_v1() async {
26 final appDocDir = await getApplicationDocumentsDirectory();
27
28 await android_migrate_hives(appDocDir: appDocDir);
29 await android_migrate_wallets(appDocDir: appDocDir);
30 }
31
32 Future<void> ios_migrate_v1(Box<Contact> contactSource) async {
33 final prefs = await SharedPreferences.getInstance();
34
35 if (prefs.getBool('ios_migration_v1_completed') ?? false) {
36 return;
37 }
38
39 await ios_migrate_user_defaults();
40 await ios_migrate_pin();
41 await ios_migrate_wallet_passwords();
42 await ios_migrate_wallet_info();
43 await ios_migrate_trades_list();
44 await ios_migrate_address_book(contactSource);
45
46 await prefs.setBool('ios_migration_v1_completed', true);
47 }
48
49 Future<void> ios_migrate_user_defaults() async {
50 //get the new shared preferences instance
51 final prefs = await SharedPreferences.getInstance();
52
53 if (prefs.getBool('ios_migration_user_defaults_completed') ?? false) {
54 return;
55 }
56
57 //translate the node uri
58 final nodeURI = await ios_legacy_helper.getString('node_uri');
59 // await prefs.setString('current_node_id', nodeURI);
60 await prefs.setInt('current_node_id', 0);
61
62 //should we provide default btc node key?
63 final activeCurrency = await ios_legacy_helper.getInt('currency');
64
65 if (activeCurrency != null) {
66 final convertedCurrency = convertFiatLegacy(activeCurrency);
67
68 await prefs.setString('current_fiat_currency', convertedCurrency.serialize());
69 }
70
71 //translate fee priority
72 final activeFeeTier = await ios_legacy_helper.getInt('saved_fee_priority');
73
74 if (activeFeeTier != null) {
75 await prefs.setInt('current_fee_priority', activeFeeTier);
76 }
77
78 //translate current balance mode
79 final currentBalanceMode = await ios_legacy_helper.getInt('display_balance_mode');
80 if (currentBalanceMode != null) {
81 await prefs.setInt('current_balance_display_mode', currentBalanceMode);
82 }
83
84 //translate should save recipient address
85 final shouldSave = await ios_legacy_helper.getBool('should_save_recipient_address');
86
87 if (shouldSave != null) {
88 await prefs.setBool('save_recipient_address', shouldSave);
89 }
90
91 //translate biometric
92 final biometricOn = await ios_legacy_helper.getBool('biometric_authentication_on');
93
94 if (biometricOn != null) {
95 await prefs.setBool('allow_biometrical_authentication', biometricOn);
96 }
97
98 //read the current theme as integer, write it back as a bool
99 final currentTheme = prefs.getInt('current-theme');
100 bool isDark = false;
101 if (currentTheme == 1) {
102 isDark = true;
103 }
104 await prefs.setBool('dark_theme', isDark);
105
106 //assign the pin length
107 final pinLength = await ios_legacy_helper.getInt('pin-length');
108
109 if (pinLength != null) {
110 await prefs.setInt(PreferencesKey.currentPinLength, pinLength);
111 }
112
113 //default value for display list key?
114 final walletName = await ios_legacy_helper.getString('current_wallet_name');
115
116 if (walletName != null) {
117 await prefs.setString('current_wallet_name', walletName);
118 }
119
120 await prefs.setInt('current_wallet_type', serializeToInt(WalletType.monero));
121
122 await prefs.setBool('ios_migration_user_defaults_completed', true);
123 }
124
125 Future<void> ios_migrate_pin() async {
126 final prefs = await SharedPreferences.getInstance();
127
128 if (prefs.getBool('ios_migration_pin_completed') ?? false) {
129 return;
130 }
131
132 final flutterSecureStorage = secureStorageShared;
133 final pinPassword = await flutterSecureStorage.readNoIOptions(key: 'pin_password');
134 // No pin
135 if (pinPassword == null) {
136 await prefs.setBool('ios_migration_pin_completed', true);
137 return;
138 }
139
140 final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
141 final encodedPassword = encodedPinCode(pin: pinPassword);
142 await flutterSecureStorage.write(key: key, value: encodedPassword);
143
144 await prefs.setBool('ios_migration_pin_completed', true);
145 }
146
147 Future<void> ios_migrate_wallet_passwords() async {
148 final prefs = await SharedPreferences.getInstance();
149
150 if (prefs.getBool('ios_migration_wallet_passwords_completed') ?? false) {
151 return;
152 }
153
154 final appDocDir = await getApplicationDocumentsDirectory();
155 final flutterSecureStorage = secureStorageShared;
156 final keyService = KeyService(flutterSecureStorage);
157 final walletsDir = Directory('${appDocDir.path}/wallets');
158 final moneroWalletsDir = Directory('${walletsDir.path}/monero');
159
160 if (!moneroWalletsDir.existsSync() || moneroWalletsDir.listSync().isEmpty) {
161 await prefs.setBool('ios_migration_wallet_passwords_completed', true);
162 return;
163 }
164
165 moneroWalletsDir.listSync().forEach((item) async {
166 try {
167 if (item is Directory) {
168 final name = item.path.split('/').last;
169 final oldKey = 'wallet_monero_' + name + '_password';
170 final password = await flutterSecureStorage.readNoIOptions(key: oldKey);
171 await keyService.saveWalletPassword(walletName: name, password: password!);
172 }
173 } catch (e) {
174 printV(e.toString());
175 }
176 });
177
178 await prefs.setBool('ios_migration_wallet_passwords_completed', true);
179 }
180
181 FiatCurrency convertFiatLegacy(int raw) {
182 final _map = {
183 0: 'aud',
184 1: 'bgn',
185 2: 'brl',
186 3: 'cad',
187 4: 'chf',
188 5: 'cny',
189 6: 'czk',
190 7: 'eur',
191 8: 'dkk',
192 9: 'gbp',
193 10: 'hkd',
194 11: 'hrk',
195 12: 'huf',
196 13: 'idr',
197 14: 'ils',
198 15: 'inr',
199 16: 'isk',
200 17: 'jpy',
201 18: 'krw',
202 19: 'mxn',
203 20: 'myr',
204 21: 'nok',
205 22: 'nzd',
206 23: 'php',
207 24: 'pln',
208 25: 'ron',
209 26: 'rub',
210 27: 'sek',
211 28: 'sgd',
212 29: 'thb',
213 30: 'try',
214 31: 'usd',
215 32: 'zar',
216 33: 'vef'
217 };
218 final fiatAsString = _map[raw]!;
219
220 return FiatCurrency.deserialize(raw: fiatAsString.toUpperCase());
221 }
222
223 Future<void> android_migrate_hives({required Directory appDocDir}) async {
224 final dbDir = Directory('${appDocDir.path}/db');
225 final files = <File>[];
226
227 appDocDir.listSync().forEach((FileSystemEntity item) {
228 final ext = item.path.split('.').last;
229
230 if (item is File && (ext == "hive" || ext == "lock")) {
231 files.add(item);
232 }
233 });
234
235 if (!dbDir.existsSync()) {
236 dbDir.createSync();
237 }
238
239 files.forEach((File hive) {
240 final name = hive.path.split('/').last;
241 hive.copySync('${dbDir.path}/$name');
242 hive.deleteSync();
243 });
244 }
245
246 Future<void> android_migrate_wallets({required Directory appDocDir}) async {
247 final walletsDir = Directory('${appDocDir.path}/wallets');
248 final moneroWalletsDir = Directory('${walletsDir.path}/monero');
249 final dirs = <Directory>[];
250
251 appDocDir.listSync().forEach((FileSystemEntity item) {
252 final name = item.path.split('/').last;
253
254 if (item is Directory && !reservedNames.contains(name)) {
255 dirs.add(item);
256 }
257 });
258
259 if (!moneroWalletsDir.existsSync()) {
260 await moneroWalletsDir.create(recursive: true);
261 }
262
263 dirs.forEach((Directory dir) {
264 final name = dir.path.split('/').last;
265 final newDir = Directory('${moneroWalletsDir.path}/$name');
266 newDir.createSync();
267
268 dir.listSync().forEach((file) {
269 if (file is File) {
270 final fileName = file.path.split('/').last;
271 file.copySync('${newDir.path}/$fileName');
272 file.deleteSync();
273 }
274 });
275
276 dir.deleteSync();
277 });
278 }
279
280 Future<void> ios_migrate_wallet_info() async {
281 final prefs = await SharedPreferences.getInstance();
282
283 if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) {
284 return;
285 }
286
287 try {
288 final appDocDir = await getApplicationDocumentsDirectory();
289 final walletsDir = Directory('${appDocDir.path}/wallets');
290 final moneroWalletsDir = Directory('${walletsDir.path}/monero');
291 final walletsInfo = await WalletInfo.getAll();
292 final infoRecords = moneroWalletsDir
293 .listSync()
294 .map((item) {
295 try {
296 if (item is Directory) {
297 final name = item.path.split('/').last;
298 final configFile = File('${item.path}/$name.json');
299
300 if (!configFile.existsSync()) {
301 return null;
302 }
303
304 final config = json.decode(configFile.readAsStringSync()) as Map<String, dynamic>;
305 final isRecovery = config['isRecovery'] as bool? ?? false;
306 final dateAsDouble = config['date'] as double;
307 final timestamp = dateAsDouble.toInt() * 1000;
308 final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
309 final id = walletTypeToString(WalletType.monero).toLowerCase() + '_' + name;
310 final exist = walletsInfo.firstWhereOrNull((el) => el.id == id) != null;
311
312 if (exist) {
313 return null;
314 }
315
316 final walletInfo = WalletInfo.external(
317 id: id,
318 type: WalletType.monero,
319 name: name,
320 isRecovery: isRecovery,
321 restoreHeight: 0,
322 date: date,
323 dirPath: item.path,
324 path: '${item.path}/$name',
325 address: '');
326
327 return walletInfo;
328 }
329 } catch (e) {
330 printV(e.toString());
331 return null;
332 }
333 })
334 .where((el) => el != null)
335 .whereType<WalletInfo>()
336 .toList();
337 for (final info in infoRecords) {
338 await info.save();
339 }
340 await prefs.setBool('ios_migration_wallet_info_completed', true);
341 } catch (e) {
342 printV(e.toString());
343 }
344 }
345
346 Future<void> ios_migrate_trades_list() async {
347 final prefs = await SharedPreferences.getInstance();
348
349 if (prefs.getBool('ios_migration_trade_list_completed') ?? false) {
350 return;
351 }
352
353 try {
354 final appDocDir = await getApplicationDocumentsDirectory();
355 final url = '${appDocDir.path}/trades_list.json';
356 final file = File(url);
357
358 if (!file.existsSync()) {
359 await prefs.setBool('ios_migration_trade_list_completed', true);
360 return;
361 }
362
363 final content = file.readAsBytesSync();
364 final flutterSecureStorage = secureStorageShared;
365 final masterPassword = await flutterSecureStorage.readNoIOptions(key: 'master_password');
366 final key = masterPassword!.replaceAll('-', '');
367 final decoded = await ios_legacy_helper.decrypt(content, key: key, salt: secrets.salt);
368 final decodedJson = json.decode(decoded) as List<dynamic>;
369
370 for (final dynamic el in decodedJson) {
371 final elAsMap = el as Map<String, dynamic>;
372 final providerAsString = elAsMap['provider'] as String;
373 final fromAsString = elAsMap['from'] as String;
374 final toAsString = elAsMap['to'] as String;
375 final dateAsDouble = elAsMap['date'] as double;
376 final tradeId = elAsMap['tradeID'] as String;
377 final to = CryptoCurrency.fromString(toAsString);
378 final from = CryptoCurrency.fromString(fromAsString);
379 final timestamp = dateAsDouble.toInt() * 1000;
380 final date = DateTime.fromMillisecondsSinceEpoch(timestamp);
381 ExchangeProviderDescription? provider;
382
383 switch (providerAsString.toLowerCase()) {
384 case 'changenow':
385 provider = ExchangeProviderDescription.changeNow;
386 break;
387 case 'xmr.to':
388 provider = ExchangeProviderDescription.xmrto;
389 break;
390 case 'morph':
391 provider = ExchangeProviderDescription.morphToken;
392 break;
393 default:
394 break;
395 }
396
397 if (provider == null) continue;
398
399 await Trade(
400 id: tradeId,
401 provider: provider,
402 from: from,
403 to: to,
404 createdAt: date,
405 amount: '',
406 receiveAmount: '',
407 ).save();
408 }
409
410 await prefs.setBool('ios_migration_trade_list_completed', true);
411 } catch (e) {
412 printV(e.toString());
413 }
414 }
415
416 Future<void> ios_migrate_address_book(Box<Contact> contactSource) async {
417 try {
418 final prefs = await SharedPreferences.getInstance();
419
420 if (prefs.getBool('ios_migration_address_book_completed') ?? false) {
421 return;
422 }
423
424 final appDocDir = await getApplicationDocumentsDirectory();
425 final addressBookJSON = File('${appDocDir.path}/address_book.json');
426
427 if (!addressBookJSON.existsSync()) {
428 await prefs.setBool('ios_migration_address_book_completed', true);
429 return;
430 }
431
432 final List<dynamic> addresses =
433 json.decode(addressBookJSON.readAsStringSync()) as List<dynamic>;
434 final contacts = addresses.map((dynamic item) {
435 final _item = item as Map<String, dynamic>;
436 final type = _item["type"] as String;
437 final address = _item["address"] as String;
438 final name = _item["name"] as String;
439
440 return Contact(address: address, name: name, type: CryptoCurrency.fromString(type));
441 });
442
443 await contactSource.addAll(contacts);
444 await prefs.setBool('ios_migration_address_book_completed', true);
445 } catch (e) {
446 printV(e.toString());
447 }
448 }