import 'dart:io'; import 'dart:convert'; import 'package:cake_wallet/core/secure_storage.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:hive/hive.dart'; import 'package:path_provider/path_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/contact.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/encrypt.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/ios_legacy_helper.dart' as ios_legacy_helper; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; const reservedNames = ["flutter_assets", "wallets", "db"]; Future migrate_android_v1() async { final appDocDir = await getApplicationDocumentsDirectory(); await android_migrate_hives(appDocDir: appDocDir); await android_migrate_wallets(appDocDir: appDocDir); } Future ios_migrate_v1( Box walletInfoSource, Box tradeSource, Box contactSource) async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_v1_completed') ?? false) { return; } await ios_migrate_user_defaults(); await ios_migrate_pin(); await ios_migrate_wallet_passwords(); await ios_migrate_wallet_info(walletInfoSource); await ios_migrate_trades_list(tradeSource); await ios_migrate_address_book(contactSource); await prefs.setBool('ios_migration_v1_completed', true); } Future ios_migrate_user_defaults() async { //get the new shared preferences instance final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_user_defaults_completed') ?? false) { return; } //translate the node uri final nodeURI = await ios_legacy_helper.getString('node_uri'); // await prefs.setString('current_node_id', nodeURI); await prefs.setInt('current_node_id', 0); //should we provide default btc node key? final activeCurrency = await ios_legacy_helper.getInt('currency'); if (activeCurrency != null) { final convertedCurrency = convertFiatLegacy(activeCurrency); await prefs.setString('current_fiat_currency', convertedCurrency.serialize()); } //translate fee priority final activeFeeTier = await ios_legacy_helper.getInt('saved_fee_priority'); if (activeFeeTier != null) { await prefs.setInt('current_fee_priority', activeFeeTier); } //translate current balance mode final currentBalanceMode = await ios_legacy_helper.getInt('display_balance_mode'); if (currentBalanceMode != null) { await prefs.setInt('current_balance_display_mode', currentBalanceMode); } //translate should save recipient address final shouldSave = await ios_legacy_helper.getBool('should_save_recipient_address'); if (shouldSave != null) { await prefs.setBool('save_recipient_address', shouldSave); } //translate biometric final biometricOn = await ios_legacy_helper.getBool('biometric_authentication_on'); if (biometricOn != null) { await prefs.setBool('allow_biometrical_authentication', biometricOn); } //read the current theme as integer, write it back as a bool final currentTheme = prefs.getInt('current-theme'); bool isDark = false; if (currentTheme == 1) { isDark = true; } await prefs.setBool('dark_theme', isDark); //assign the pin length final pinLength = await ios_legacy_helper.getInt('pin-length'); if (pinLength != null) { await prefs.setInt(PreferencesKey.currentPinLength, pinLength); } //default value for display list key? final walletName = await ios_legacy_helper.getString('current_wallet_name'); if (walletName != null) { await prefs.setString('current_wallet_name', walletName); } await prefs.setInt('current_wallet_type', serializeToInt(WalletType.monero)); await prefs.setBool('ios_migration_user_defaults_completed', true); } Future ios_migrate_pin() async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_pin_completed') ?? false) { return; } final flutterSecureStorage = secureStorageShared; final pinPassword = await flutterSecureStorage.readNoIOptions(key: 'pin_password'); // No pin if (pinPassword == null) { await prefs.setBool('ios_migration_pin_completed', true); return; } final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPassword = encodedPinCode(pin: pinPassword); await flutterSecureStorage.write(key: key, value: encodedPassword); await prefs.setBool('ios_migration_pin_completed', true); } Future ios_migrate_wallet_passwords() async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_wallet_passwords_completed') ?? false) { return; } final appDocDir = await getApplicationDocumentsDirectory(); final flutterSecureStorage = secureStorageShared; final keyService = KeyService(flutterSecureStorage); final walletsDir = Directory('${appDocDir.path}/wallets'); final moneroWalletsDir = Directory('${walletsDir.path}/monero'); if (!moneroWalletsDir.existsSync() || moneroWalletsDir.listSync().isEmpty) { await prefs.setBool('ios_migration_wallet_passwords_completed', true); return; } moneroWalletsDir.listSync().forEach((item) async { try { if (item is Directory) { final name = item.path.split('/').last; final oldKey = 'wallet_monero_' + name + '_password'; final password = await flutterSecureStorage.readNoIOptions(key: oldKey); await keyService.saveWalletPassword(walletName: name, password: password!); } } catch (e) { printV(e.toString()); } }); await prefs.setBool('ios_migration_wallet_passwords_completed', true); } FiatCurrency convertFiatLegacy(int raw) { final _map = { 0: 'aud', 1: 'bgn', 2: 'brl', 3: 'cad', 4: 'chf', 5: 'cny', 6: 'czk', 7: 'eur', 8: 'dkk', 9: 'gbp', 10: 'hkd', 11: 'hrk', 12: 'huf', 13: 'idr', 14: 'ils', 15: 'inr', 16: 'isk', 17: 'jpy', 18: 'krw', 19: 'mxn', 20: 'myr', 21: 'nok', 22: 'nzd', 23: 'php', 24: 'pln', 25: 'ron', 26: 'rub', 27: 'sek', 28: 'sgd', 29: 'thb', 30: 'try', 31: 'usd', 32: 'zar', 33: 'vef' }; final fiatAsString = _map[raw]!; return FiatCurrency.deserialize(raw: fiatAsString.toUpperCase()); } Future android_migrate_hives({required Directory appDocDir}) async { final dbDir = Directory('${appDocDir.path}/db'); final files = []; appDocDir.listSync().forEach((FileSystemEntity item) { final ext = item.path.split('.').last; if (item is File && (ext == "hive" || ext == "lock")) { files.add(item); } }); if (!dbDir.existsSync()) { dbDir.createSync(); } files.forEach((File hive) { final name = hive.path.split('/').last; hive.copySync('${dbDir.path}/$name'); hive.deleteSync(); }); } Future android_migrate_wallets({required Directory appDocDir}) async { final walletsDir = Directory('${appDocDir.path}/wallets'); final moneroWalletsDir = Directory('${walletsDir.path}/monero'); final dirs = []; appDocDir.listSync().forEach((FileSystemEntity item) { final name = item.path.split('/').last; if (item is Directory && !reservedNames.contains(name)) { dirs.add(item); } }); if (!moneroWalletsDir.existsSync()) { await moneroWalletsDir.create(recursive: true); } dirs.forEach((Directory dir) { final name = dir.path.split('/').last; final newDir = Directory('${moneroWalletsDir.path}/$name'); newDir.createSync(); dir.listSync().forEach((file) { if (file is File) { final fileName = file.path.split('/').last; file.copySync('${newDir.path}/$fileName'); file.deleteSync(); } }); dir.deleteSync(); }); } Future ios_migrate_wallet_info(Box walletsInfoSource) async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_wallet_info_completed') ?? false) { return; } try { final appDocDir = await getApplicationDocumentsDirectory(); final walletsDir = Directory('${appDocDir.path}/wallets'); final moneroWalletsDir = Directory('${walletsDir.path}/monero'); final infoRecords = moneroWalletsDir .listSync() .map((item) { try { if (item is Directory) { final name = item.path.split('/').last; final configFile = File('${item.path}/$name.json'); if (!configFile.existsSync()) { return null; } final config = json.decode(configFile.readAsStringSync()) as Map; final isRecovery = config['isRecovery'] as bool? ?? false; final dateAsDouble = config['date'] as double; final timestamp = dateAsDouble.toInt() * 1000; final date = DateTime.fromMillisecondsSinceEpoch(timestamp); final id = walletTypeToString(WalletType.monero).toLowerCase() + '_' + name; final exist = walletsInfoSource.values.firstWhereOrNull((el) => el.id == id) != null; if (exist) { return null; } final walletInfo = WalletInfo.external( id: id, type: WalletType.monero, name: name, isRecovery: isRecovery, restoreHeight: 0, date: date, dirPath: item.path, path: '${item.path}/$name', address: ''); return walletInfo; } } catch (e) { printV(e.toString()); return null; } }) .where((el) => el != null) .whereType() .toList(); await walletsInfoSource.addAll(infoRecords); await prefs.setBool('ios_migration_wallet_info_completed', true); } catch (e) { printV(e.toString()); } } Future ios_migrate_trades_list(Box tradeSource) async { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_trade_list_completed') ?? false) { return; } try { final appDocDir = await getApplicationDocumentsDirectory(); final url = '${appDocDir.path}/trades_list.json'; final file = File(url); if (!file.existsSync()) { await prefs.setBool('ios_migration_trade_list_completed', true); return; } final content = file.readAsBytesSync(); final flutterSecureStorage = secureStorageShared; final masterPassword = await flutterSecureStorage.readNoIOptions(key: 'master_password'); final key = masterPassword!.replaceAll('-', ''); final decoded = await ios_legacy_helper.decrypt(content, key: key, salt: secrets.salt); final decodedJson = json.decode(decoded) as List; final trades = decodedJson.map((dynamic el) { final elAsMap = el as Map; final providerAsString = elAsMap['provider'] as String; final fromAsString = elAsMap['from'] as String; final toAsString = elAsMap['to'] as String; final dateAsDouble = elAsMap['date'] as double; final tradeId = elAsMap['tradeID'] as String; final to = CryptoCurrency.fromString(toAsString); final from = CryptoCurrency.fromString(fromAsString); final timestamp = dateAsDouble.toInt() * 1000; final date = DateTime.fromMillisecondsSinceEpoch(timestamp); ExchangeProviderDescription? provider; switch (providerAsString.toLowerCase()) { case 'changenow': provider = ExchangeProviderDescription.changeNow; break; case 'xmr.to': provider = ExchangeProviderDescription.xmrto; break; case 'morph': provider = ExchangeProviderDescription.morphToken; break; default: break; } return Trade( id: tradeId, provider: provider!, from: from, to: to, createdAt: date, amount: '', receiveAmount: '', ); }); await tradeSource.addAll(trades); await prefs.setBool('ios_migration_trade_list_completed', true); } catch (e) { printV(e.toString()); } } Future ios_migrate_address_book(Box contactSource) async { try { final prefs = await SharedPreferences.getInstance(); if (prefs.getBool('ios_migration_address_book_completed') ?? false) { return; } final appDocDir = await getApplicationDocumentsDirectory(); final addressBookJSON = File('${appDocDir.path}/address_book.json'); if (!addressBookJSON.existsSync()) { await prefs.setBool('ios_migration_address_book_completed', true); return; } final List addresses = json.decode(addressBookJSON.readAsStringSync()) as List; final contacts = addresses.map((dynamic item) { final _item = item as Map; final type = _item["type"] as String; final address = _item["address"] as String; final name = _item["name"] as String; return Contact(address: address, name: name, type: CryptoCurrency.fromString(type)); }); await contactSource.addAll(contacts); await prefs.setBool('ios_migration_address_book_completed', true); } catch (e) { printV(e.toString()); } }