diff --git a/.gitignore b/.gitignore index c05b7cd5f..6fd8f33d6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +.fvm/ # IntelliJ related *.iml diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index bc189f92c..b40aeb7c8 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -14,6 +14,8 @@ android:icon="@mipmap/ic_launcher" android:allowBackup="false" android:fullBackupContent="false" + android:versionCode="__versionCode__" + android:versionName="__versionName__" android:requestLegacyExternalStorage="true"> ? subscription; bool connect(Node node) { try { @@ -30,42 +28,13 @@ class EthereumClient { } } - void setListeners(EthereumAddress userAddress, Function(FilterEvent) onNewTransaction) async { - // final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); - // final contractAbi = ContractAbi.fromJson(abi, "ERC20"); + void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async { + // _client?.pendingTransactions().listen((transactionHash) async { + // final transaction = await _client!.getTransactionByHash(transactionHash); // - // final contract = DeployedContract( - // contractAbi, - // EthereumAddress.fromHex("0xf451659CF5688e31a31fC3316efbcC2339A490Fb"), - // ); - // - // final transferEvent = contract.event('Transfer'); - // // listen for the Transfer event when it's emitted by the contract above - // final subscription = _client! - // .events(FilterOptions.events(contract: contract, event: transferEvent)) - // .take(1) - // .listen((event) { - // final decoded = transferEvent.decodeResults(event.topics ?? [], event.data ?? ''); - // - // final from = decoded[0] as EthereumAddress; - // final to = decoded[1] as EthereumAddress; - // final value = decoded[2] as BigInt; - // - // print('$from sent $value MetaCoins to $to'); - // }); - - // final eventFilter = FilterOptions(address: userAddress); - // - // _client!.events(eventFilter).listen((event) { - // print('Address ${event.address} data ${event.data} tx hash ${event.transactionHash}!'); - // onNewTransaction(event); - // }); - - // final erc20 = Erc20(client: _client!, address: userAddress); - // - // subscription = erc20.transferEvents().take(1).listen((event) { - // print('${event.from} sent ${event.value} MetaCoins to ${event.to}!'); - // onNewTransaction(event); + // if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) { + // onNewTransaction(); + // } // }); } @@ -77,17 +46,9 @@ class EthereumClient { return gasPrice.getInWei.toInt(); } - Future> getEstimatedGasForPriorities() async { - // TODO: there is no difference, find out why - // [53000, 53000, 53000] - final result = await Future.wait(EthereumTransactionPriority.all.map( - (priority) => _client!.estimateGas( - // maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip), - // maxFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip), - ), - )); - - return result.map((e) => e.toInt()).toList(); + Future getEstimatedGas() async { + final estimatedGas = await _client!.estimateGas(); + return estimatedGas.toInt(); } Future signTransaction({ @@ -111,20 +72,17 @@ class EthereumClient { to: EthereumAddress.fromHex(toAddress), maxGas: gas, gasPrice: price, + maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip), value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), ); final signedTransaction = await _client!.signTransaction(privateKey, transaction); - final BigInt estimatedGas; final Function _sendTransaction; if (_isEthereum) { - estimatedGas = BigInt.from(21000); _sendTransaction = () async => await sendTransaction(signedTransaction); } else { - estimatedGas = BigInt.from(50000); - final erc20 = Erc20( client: _client!, address: EthereumAddress.fromHex(contractAddress!), @@ -142,7 +100,7 @@ class EthereumClient { return PendingEthereumTransaction( signedTransaction: signedTransaction, amount: amount, - fee: estimatedGas * price.getInWei, + fee: BigInt.from(gas) * price.getInWei, sendTransaction: _sendTransaction, exponent: exponent, ); @@ -193,19 +151,6 @@ I/flutter ( 4474): Gas Used: 53000 */ } -// Future> fetchTransactions(String address) async { -// get(Uri.https( -// "https://api.etherscan.io", -// "api/", -// { -// "module": "account", -// "action": "txlist", -// "address": address, -// "apikey": secrets., -// }, -// )); -// } - Future fetchERC20Balances( EthereumAddress userAddress, String contractAddress) async { final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!); @@ -235,7 +180,6 @@ I/flutter ( 4474): Gas Used: 53000 } void stop() { - subscription?.cancel(); _client?.dispose(); } diff --git a/cw_ethereum/lib/ethereum_transaction_history.dart b/cw_ethereum/lib/ethereum_transaction_history.dart index 8904fb347..4511f4436 100644 --- a/cw_ethereum/lib/ethereum_transaction_history.dart +++ b/cw_ethereum/lib/ethereum_transaction_history.dart @@ -32,8 +32,9 @@ abstract class EthereumTransactionHistoryBase final path = '$dirPath/$transactionsHistoryFileName'; final data = json.encode({'transactions': transactions}); await writeData(path: path, password: _password, data: data); - } catch (e) { - print('Error while save bitcoin transaction history: ${e.toString()}'); + } catch (e, s) { + print('Error while save ethereum transaction history: ${e.toString()}'); + print(s); } } @@ -48,6 +49,9 @@ abstract class EthereumTransactionHistoryBase final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); final path = '$dirPath/$transactionsHistoryFileName'; final content = await read(path: path, password: _password); + if (content.isEmpty) { + return {}; + } return json.decode(content) as Map; } diff --git a/cw_ethereum/lib/ethereum_transaction_info.dart b/cw_ethereum/lib/ethereum_transaction_info.dart index be3c2c0e2..efdc61407 100644 --- a/cw_ethereum/lib/ethereum_transaction_info.dart +++ b/cw_ethereum/lib/ethereum_transaction_info.dart @@ -48,9 +48,9 @@ class EthereumTransactionInfo extends TransactionInfo { return EthereumTransactionInfo( id: data['id'] as String, height: data['height'] as int, - ethAmount: data['amount'] as BigInt, + ethAmount: BigInt.parse(data['amount']), exponent: data['exponent'] as int, - ethFee: data['fee'] as BigInt, + ethFee: BigInt.parse(data['fee']), direction: parseTransactionDirectionFromInt(data['direction'] as int), date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), isPending: data['isPending'] as bool, @@ -62,9 +62,9 @@ class EthereumTransactionInfo extends TransactionInfo { Map toJson() => { 'id': id, 'height': height, - 'amount': ethAmount, + 'amount': ethAmount.toString(), 'exponent': exponent, - 'fee': ethFee, + 'fee': ethFee.toString(), 'direction': direction.index, 'date': date.millisecondsSinceEpoch, 'isPending': isPending, diff --git a/cw_ethereum/lib/ethereum_transaction_priority.dart b/cw_ethereum/lib/ethereum_transaction_priority.dart index a30ad6fb8..ff5668397 100644 --- a/cw_ethereum/lib/ethereum_transaction_priority.dart +++ b/cw_ethereum/lib/ethereum_transaction_priority.dart @@ -8,11 +8,11 @@ class EthereumTransactionPriority extends TransactionPriority { static const List all = [fast, medium, slow]; static const EthereumTransactionPriority slow = - EthereumTransactionPriority(title: 'slow', raw: 0, tip: 50); + EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1); static const EthereumTransactionPriority medium = - EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 75); + EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2); static const EthereumTransactionPriority fast = - EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 100); + EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4); static EthereumTransactionPriority deserialize({required int raw}) { switch (raw) { diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 908054fa3..46cb5c39f 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -29,6 +29,7 @@ import 'package:cw_core/erc20_token.dart'; import 'package:hive/hive.dart'; import 'package:hex/hex.dart'; import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:web3dart/web3dart.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:bip32/bip32.dart' as bip32; @@ -48,7 +49,6 @@ abstract class EthereumWalletBase }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, - _priorityFees = [], _isTransactionUpdating = false, _client = EthereumClient(), walletAddresses = EthereumWalletAddresses(walletInfo), @@ -61,6 +61,8 @@ abstract class EthereumWalletBase if (!Hive.isAdapterRegistered(Erc20Token.typeId)) { Hive.registerAdapter(Erc20TokenAdapter()); } + + _sharedPrefs.complete(SharedPreferences.getInstance()); } final String _mnemonic; @@ -72,10 +74,13 @@ abstract class EthereumWalletBase late EthereumClient _client; - List _priorityFees; int? _gasPrice; + int? _estimatedGas; bool _isTransactionUpdating; + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter + Timer? _transactionsUpdateTimer; + @override WalletAddresses walletAddresses; @@ -87,6 +92,8 @@ abstract class EthereumWalletBase @observable late ObservableMap balance; + Completer _sharedPrefs = Completer(); + Future init() async { erc20TokensBox = await Hive.openBox(Erc20Token.boxName); await walletAddresses.init(); @@ -100,7 +107,9 @@ abstract class EthereumWalletBase int calculateEstimatedFee(TransactionPriority priority, int? amount) { try { if (priority is EthereumTransactionPriority) { - return _gasPrice! * _priorityFees[priority.raw]; + final priorityFee = + EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt(); + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); } return 0; @@ -117,6 +126,7 @@ abstract class EthereumWalletBase @override void close() { _client.stop(); + _transactionsUpdateTimer?.cancel(); } @action @@ -132,7 +142,8 @@ abstract class EthereumWalletBase } _client.setListeners(_privateKey.address, _onNewTransaction); - _updateBalance(); + + _setTransactionUpdateTimer(); syncStatus = ConnectedSyncStatus(); } catch (e) { @@ -149,7 +160,7 @@ abstract class EthereumWalletBase BigInt totalAmount = BigInt.zero; int exponent = _credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18; - BigInt amountToEthereumMultiplier = BigInt.from(pow(10, exponent)); + num amountToEthereumMultiplier = pow(10, exponent); if (hasMultiDestination) { if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { @@ -158,19 +169,20 @@ abstract class EthereumWalletBase final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble( outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); - totalAmount = BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier; + totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier); if (_erc20Balance.balance < totalAmount) { throw EthereumTransactionCreationException(_credentials.currency); } } else { final output = outputs.first; - final BigInt allAmount = _erc20Balance.balance - BigInt.from(feeRate(_credentials.priority!)); + final BigInt allAmount = + _erc20Balance.balance - BigInt.from(calculateEstimatedFee(_credentials.priority!, null)); final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0); totalAmount = output.sendAll ? allAmount - : BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier; + : BigInt.from(totalOriginalAmount * amountToEthereumMultiplier); if (_erc20Balance.balance < totalAmount) { throw EthereumTransactionCreationException(_credentials.currency); @@ -181,7 +193,7 @@ abstract class EthereumWalletBase privateKey: _privateKey, toAddress: _credentials.outputs.first.address, amount: totalAmount.toString(), - gas: _priorityFees[_credentials.priority!.raw], + gas: _estimatedGas!, priority: _credentials.priority!, currency: _credentials.currency, exponent: exponent, @@ -193,11 +205,15 @@ abstract class EthereumWalletBase return pendingEthereumTransaction; } - Future updateTransactions() async { + Future _updateTransactions() async { try { if (_isTransactionUpdating) { return; } + bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true; + if (!isEtherscanEnabled) { + return; + } _isTransactionUpdating = true; final transactions = await fetchTransactions(); @@ -279,14 +295,14 @@ abstract class EthereumWalletBase try { syncStatus = AttemptingSyncStatus(); await _updateBalance(); - await updateTransactions(); + await _updateTransactions(); _gasPrice = await _client.getGasUnitPrice(); - _priorityFees = await _client.getEstimatedGasForPriorities(); + _estimatedGas = await _client.getEstimatedGas(); Timer.periodic( const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); - Timer.periodic(const Duration(minutes: 1), - (timer) async => _priorityFees = await _client.getEstimatedGasForPriorities()); + Timer.periodic(const Duration(seconds: 10), + (timer) async => _estimatedGas = await _client.getEstimatedGas()); syncStatus = SyncedSyncStatus(); } catch (e) { @@ -294,18 +310,6 @@ abstract class EthereumWalletBase } } - int feeRate(TransactionPriority priority) { - try { - if (priority is EthereumTransactionPriority) { - return _priorityFees[priority.raw]; - } - - return 0; - } catch (e) { - return 0; - } - } - Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); String toJSON() => json.encode({ @@ -414,9 +418,9 @@ abstract class EthereumWalletBase Future getErc20Token(String contractAddress) async => await _client.getErc20Token(contractAddress); - void _onNewTransaction(FilterEvent event) { + void _onNewTransaction() { _updateBalance(); - // TODO: Add in transaction history + _updateTransactions(); } void addInitialTokens() { @@ -446,4 +450,24 @@ abstract class EthereumWalletBase // Delete old name's dir and files await Directory(currentDirPath).delete(recursive: true); } + + void _setTransactionUpdateTimer() { + if (_transactionsUpdateTimer?.isActive ?? false) { + _transactionsUpdateTimer!.cancel(); + } + + _transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) { + _updateTransactions(); + _updateBalance(); + }); + } + + void updateEtherscanUsageState(bool isEnabled) { + if (isEnabled) { + _updateTransactions(); + _setTransactionUpdateTimer(); + } else { + _transactionsUpdateTimer?.cancel(); + } + } } diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 16cf86ab6..318f287fc 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -10,6 +10,7 @@ import 'package:cw_ethereum/ethereum_wallet.dart'; import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart'; import 'package:hive/hive.dart'; import 'package:bip39/bip39.dart' as bip39; +import 'package:collection/collection.dart'; class EthereumWalletService extends WalletService { @@ -57,8 +58,12 @@ class EthereumWalletService extends WalletService remove(String wallet) async => - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } @override Future restoreFromKeys(credentials) { @@ -72,7 +77,7 @@ class EthereumWalletService extends WalletService= 16.04 -Android SDK 28 +Android SDK 29 or higher (better to have the latest one 33) Android NDK 17c -Flutter 2 or above +Flutter 3.7.x ``` ## Building CakeWallet on Android @@ -55,7 +55,7 @@ You may download and install the latest version of Android Studio [here](https:/ ### 3. Installing Flutter -Need to install flutter with version `3.x.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually). +Need to install flutter with version `3.7.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually). ### 4. Verify Installations @@ -66,9 +66,9 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding. ``` Doctor summary (to see all details, run flutter doctor -v): -[✓] Flutter (Channel stable, 3.x.x, on Linux, locale en_US.UTF-8) -[✓] Android toolchain - develop for Android devices (Android SDK version 28) -[✓] Android Studio (version 4.0) +[✓] Flutter (Channel stable, 3.7.x, on Linux, locale en_US.UTF-8) +[✓] Android toolchain - develop for Android devices (Android SDK version 29 or higher) +[✓] Android Studio (version 4.0 or higher) ``` ### 5. Generate a secure keystore for Android diff --git a/lib/core/auth_service.dart b/lib/core/auth_service.dart index 8091740e6..854640015 100644 --- a/lib/core/auth_service.dart +++ b/lib/core/auth_service.dart @@ -25,6 +25,10 @@ class AuthService with Store { Routes.setupPin, Routes.setup_2faPage, Routes.modify2FAPage, + Routes.newWallet, + Routes.newWalletType, + Routes.addressBookAddContact, + Routes.restoreOptions, ]; final FlutterSecureStorage secureStorage; @@ -81,21 +85,26 @@ class AuthService with Store { } Future authenticateAction(BuildContext context, - {Function(bool)? onAuthSuccess, String? route, Object? arguments}) async { + {Function(bool)? onAuthSuccess, + String? route, + Object? arguments, + required bool conditionToDetermineIfToUse2FA}) async { assert(route != null || onAuthSuccess != null, 'Either route or onAuthSuccess param must be passed.'); - if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) { - if (onAuthSuccess != null) { - onAuthSuccess(true); - } else { - Navigator.of(context).pushNamed( - route ?? '', - arguments: arguments, - ); + if (!conditionToDetermineIfToUse2FA) { + if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) { + if (onAuthSuccess != null) { + onAuthSuccess(true); + } else { + Navigator.of(context).pushNamed( + route ?? '', + arguments: arguments, + ); + } + return; } - return; - } +} Navigator.of(context).pushNamed(Routes.auth, @@ -104,7 +113,7 @@ class AuthService with Store { onAuthSuccess?.call(false); return; } else { - if (settingsStore.useTOTP2FA) { + if (settingsStore.useTOTP2FA && conditionToDetermineIfToUse2FA) { auth.close( route: Routes.totpAuthCodePage, arguments: TotpAuthArgumentsModel( diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index b088e0013..3d323dbdb 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; +import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; @@ -19,8 +20,8 @@ import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cake_backup/backup.dart' as cake_backup; class BackupService { - BackupService(this._flutterSecureStorage, this._walletInfoSource, - this._keyService, this._sharedPreferences) + BackupService( + this._flutterSecureStorage, this._walletInfoSource, this._keyService, this._sharedPreferences) : _cipher = Cryptography.instance.chacha20Poly1305Aead(), _correctWallets = []; @@ -67,9 +68,8 @@ class BackupService { } @Deprecated('Use v2 instead') - Future _exportBackupV1(String password, - {String nonce = secrets.backupSalt}) async - => throw Exception('Deprecated. Export for backups v1 is deprecated. Please use export v2.'); + Future _exportBackupV1(String password, {String nonce = secrets.backupSalt}) async => + throw Exception('Deprecated. Export for backups v1 is deprecated. Please use export v2.'); Future _exportBackupV2(String password) async { final zipEncoder = ZipFileEncoder(); @@ -112,8 +112,7 @@ class BackupService { return await _encryptV2(content, password); } - Future _importBackupV1(Uint8List data, String password, - {required String nonce}) async { + Future _importBackupV1(Uint8List data, String password, {required String nonce}) async { final appDir = await getApplicationDocumentsDirectory(); final decryptedData = await _decryptV1(data, password, nonce); final zip = ZipDecoder().decodeBytes(decryptedData); @@ -161,10 +160,8 @@ class BackupService { Future _verifyWallets() async { final walletInfoSource = await _reloadHiveWalletInfoBox(); - _correctWallets = walletInfoSource - .values - .where((info) => availableWalletTypes.contains(info.type)) - .toList(); + _correctWallets = + walletInfoSource.values.where((info) => availableWalletTypes.contains(info.type)).toList(); if (_correctWallets.isEmpty) { throw Exception('Correct wallets not detected'); @@ -191,14 +188,12 @@ class BackupService { return; } - final data = - json.decode(preferencesFile.readAsStringSync()) as Map; + final data = json.decode(preferencesFile.readAsStringSync()) as Map; String currentWalletName = data[PreferencesKey.currentWalletName] as String; int currentWalletType = data[PreferencesKey.currentWalletType] as int; final isCorrentCurrentWallet = _correctWallets - .any((info) => info.name == currentWalletName && - info.type.index == currentWalletType); + .any((info) => info.name == currentWalletName && info.type.index == currentWalletType); if (!isCorrentCurrentWallet) { currentWalletName = _correctWallets.first.name; @@ -212,110 +207,159 @@ class BackupService { final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?; final disableBuy = data[PreferencesKey.disableBuyKey] as bool?; final disableSell = data[PreferencesKey.disableSellKey] as bool?; - final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; - final allowBiometricalAuthentication = data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?; - final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; + final currentTransactionPriorityKeyLegacy = + data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?; + final allowBiometricalAuthentication = + data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?; + final currentBitcoinElectrumSererId = + data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?; final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?; final displayActionListMode = data[PreferencesKey.displayActionListModeKey] as int?; final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?; final currentPinLength = data[PreferencesKey.currentPinLength] as int?; final currentTheme = data[PreferencesKey.currentTheme] as int?; final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?; - final currentDefaultSettingsMigrationVersion = data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; + final currentDefaultSettingsMigrationVersion = + data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?; final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?; final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?; + final selectedCake2FAPreset = data[PreferencesKey.selectedCake2FAPreset] as int?; + final shouldRequireTOTP2FAForAccessingWallet = + data[PreferencesKey.shouldRequireTOTP2FAForAccessingWallet] as bool?; + final shouldRequireTOTP2FAForSendsToContact = + data[PreferencesKey.shouldRequireTOTP2FAForSendsToContact] as bool?; + final shouldRequireTOTP2FAForSendsToNonContact = + data[PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact] as bool?; + final shouldRequireTOTP2FAForSendsToInternalWallets = + data[PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets] as bool?; + final shouldRequireTOTP2FAForExchangesToInternalWallets = + data[PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets] as bool?; + final shouldRequireTOTP2FAForAddingContacts = + data[PreferencesKey.shouldRequireTOTP2FAForAddingContacts] as bool?; + final shouldRequireTOTP2FAForCreatingNewWallets = + data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?; + final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = + data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?; final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?; final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?; + final useEtherscan = data[PreferencesKey.useEtherscan] as bool?; - await _sharedPreferences.setString(PreferencesKey.currentWalletName, - currentWalletName); + await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName); if (currentNodeId != null) - await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, - currentNodeId); + await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, currentNodeId); if (currentBalanceDisplayMode != null) - await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, - currentBalanceDisplayMode); + await _sharedPreferences.setInt( + PreferencesKey.currentBalanceDisplayModeKey, currentBalanceDisplayMode); - await _sharedPreferences.setInt(PreferencesKey.currentWalletType, - currentWalletType); + await _sharedPreferences.setInt(PreferencesKey.currentWalletType, currentWalletType); if (currentFiatCurrency != null) - await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey, - currentFiatCurrency); + await _sharedPreferences.setString( + PreferencesKey.currentFiatCurrencyKey, currentFiatCurrency); if (shouldSaveRecipientAddress != null) await _sharedPreferences.setBool( - PreferencesKey.shouldSaveRecipientAddressKey, - shouldSaveRecipientAddress); + PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress); if (isAppSecure != null) - await _sharedPreferences.setBool( - PreferencesKey.isAppSecureKey, - isAppSecure); + await _sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure); if (disableBuy != null) - await _sharedPreferences.setBool( - PreferencesKey.disableBuyKey, - disableBuy); + await _sharedPreferences.setBool(PreferencesKey.disableBuyKey, disableBuy); if (disableSell != null) - await _sharedPreferences.setBool( - PreferencesKey.disableSellKey, - disableSell); + await _sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell); if (currentTransactionPriorityKeyLegacy != null) await _sharedPreferences.setInt( - PreferencesKey.currentTransactionPriorityKeyLegacy, - currentTransactionPriorityKeyLegacy); + PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy); if (allowBiometricalAuthentication != null) await _sharedPreferences.setBool( - PreferencesKey.allowBiometricalAuthenticationKey, - allowBiometricalAuthentication); + PreferencesKey.allowBiometricalAuthenticationKey, allowBiometricalAuthentication); if (currentBitcoinElectrumSererId != null) await _sharedPreferences.setInt( - PreferencesKey.currentBitcoinElectrumSererIdKey, - currentBitcoinElectrumSererId); + PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId); if (currentLanguageCode != null) - await _sharedPreferences.setString(PreferencesKey.currentLanguageCode, - currentLanguageCode); + await _sharedPreferences.setString(PreferencesKey.currentLanguageCode, currentLanguageCode); if (displayActionListMode != null) - await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey, - displayActionListMode); + await _sharedPreferences.setInt( + PreferencesKey.displayActionListModeKey, displayActionListMode); if (fiatApiMode != null) - await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, - fiatApiMode); + await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode); if (currentPinLength != null) - await _sharedPreferences.setInt(PreferencesKey.currentPinLength, - currentPinLength); + await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength); if (currentTheme != null) - await _sharedPreferences.setInt( - PreferencesKey.currentTheme, currentTheme); + await _sharedPreferences.setInt(PreferencesKey.currentTheme, currentTheme); if (exchangeStatus != null) - await _sharedPreferences.setInt( - PreferencesKey.exchangeStatusKey, exchangeStatus); + await _sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, exchangeStatus); if (currentDefaultSettingsMigrationVersion != null) - await _sharedPreferences.setInt( - PreferencesKey.currentDefaultSettingsMigrationVersion, - currentDefaultSettingsMigrationVersion); + await _sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, + currentDefaultSettingsMigrationVersion); if (moneroTransactionPriority != null) - await _sharedPreferences.setInt(PreferencesKey.moneroTransactionPriority, - moneroTransactionPriority); + await _sharedPreferences.setInt( + PreferencesKey.moneroTransactionPriority, moneroTransactionPriority); if (bitcoinTransactionPriority != null) - await _sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority, - bitcoinTransactionPriority); + await _sharedPreferences.setInt( + PreferencesKey.bitcoinTransactionPriority, bitcoinTransactionPriority); + + if (selectedCake2FAPreset != null) + await _sharedPreferences.setInt(PreferencesKey.selectedCake2FAPreset, selectedCake2FAPreset); + + if (shouldRequireTOTP2FAForAccessingWallet != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet, + shouldRequireTOTP2FAForAccessingWallet); + + if (shouldRequireTOTP2FAForSendsToContact != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact, + shouldRequireTOTP2FAForSendsToContact); + + if (shouldRequireTOTP2FAForSendsToNonContact != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact, + shouldRequireTOTP2FAForSendsToNonContact); + + if (shouldRequireTOTP2FAForSendsToInternalWallets != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets, + shouldRequireTOTP2FAForSendsToInternalWallets); + + if (shouldRequireTOTP2FAForExchangesToInternalWallets != null) + await _sharedPreferences.setBool( + PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets, + shouldRequireTOTP2FAForExchangesToInternalWallets); + + if (shouldRequireTOTP2FAForAddingContacts != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts, + shouldRequireTOTP2FAForAddingContacts); + + if (shouldRequireTOTP2FAForCreatingNewWallets != null) + await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets, + shouldRequireTOTP2FAForCreatingNewWallets); + + if (shouldRequireTOTP2FAForAllSecurityAndBackupSettings != null) + await _sharedPreferences.setBool( + PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + shouldRequireTOTP2FAForAllSecurityAndBackupSettings); + + if (sortBalanceTokensBy != null) + await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy); + + if (pinNativeTokenAtTop != null) + await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop); + + if (useEtherscan != null) + await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan); if (sortBalanceTokensBy != null) await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy); @@ -327,31 +371,27 @@ class BackupService { } Future _importKeychainDumpV1(String password, - {required String nonce, - String keychainSalt = secrets.backupKeychainSalt}) async { + {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async { final appDir = await getApplicationDocumentsDirectory(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); - final decryptedKeychainDumpFileData = await _decryptV1( - keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce); - final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData)) - as Map; + final decryptedKeychainDumpFileData = + await _decryptV1(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce); + final keychainJSON = + json.decode(utf8.decode(decryptedKeychainDumpFileData)) as Map; final keychainWalletsInfo = keychainJSON['wallets'] as List; final decodedPin = keychainJSON['pin'] as String; final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); - final backupPasswordKey = - generateStoreKeyFor(key: SecretStoreKey.backupPassword); + final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = keychainJSON[backupPasswordKey] as String; - await _flutterSecureStorage.write( - key: backupPasswordKey, value: backupPassword); + await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword); keychainWalletsInfo.forEach((dynamic rawInfo) async { final info = rawInfo as Map; await importWalletKeychainInfo(info); }); - await _flutterSecureStorage.write( - key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); + await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); keychainDumpFile.deleteSync(); } @@ -360,27 +400,24 @@ class BackupService { {String keychainSalt = secrets.backupKeychainSalt}) async { final appDir = await getApplicationDocumentsDirectory(); final keychainDumpFile = File('${appDir.path}/~_keychain_dump'); - final decryptedKeychainDumpFileData = await _decryptV2( - keychainDumpFile.readAsBytesSync(), '$keychainSalt$password'); - final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData)) - as Map; + final decryptedKeychainDumpFileData = + await _decryptV2(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password'); + final keychainJSON = + json.decode(utf8.decode(decryptedKeychainDumpFileData)) as Map; final keychainWalletsInfo = keychainJSON['wallets'] as List; final decodedPin = keychainJSON['pin'] as String; final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); - final backupPasswordKey = - generateStoreKeyFor(key: SecretStoreKey.backupPassword); + final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); final backupPassword = keychainJSON[backupPasswordKey] as String; - await _flutterSecureStorage.write( - key: backupPasswordKey, value: backupPassword); + await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword); keychainWalletsInfo.forEach((dynamic rawInfo) async { final info = rawInfo as Map; await importWalletKeychainInfo(info); }); - await _flutterSecureStorage.write( - key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); + await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin)); keychainDumpFile.deleteSync(); } @@ -394,35 +431,26 @@ class BackupService { @Deprecated('Use v2 instead') Future _exportKeychainDumpV1(String password, - {required String nonce, - String keychainSalt = secrets.backupKeychainSalt}) async - => throw Exception('Deprecated'); + {required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async => + throw Exception('Deprecated'); Future _exportKeychainDumpV2(String password, {String keychainSalt = secrets.backupKeychainSalt}) async { final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); final encodedPin = await _flutterSecureStorage.read(key: key); final decodedPin = decodedPinCode(pin: encodedPin!); - final wallets = - await Future.wait(_walletInfoSource.values.map((walletInfo) async { + final wallets = await Future.wait(_walletInfoSource.values.map((walletInfo) async { return { 'name': walletInfo.name, 'type': walletInfo.type.toString(), - 'password': - await _keyService.getWalletPassword(walletName: walletInfo.name) + 'password': await _keyService.getWalletPassword(walletName: walletInfo.name) }; })); - final backupPasswordKey = - generateStoreKeyFor(key: SecretStoreKey.backupPassword); - final backupPassword = - await _flutterSecureStorage.read(key: backupPasswordKey); - final data = utf8.encode(json.encode({ - 'pin': decodedPin, - 'wallets': wallets, - backupPasswordKey: backupPassword - })); - final encrypted = await _encryptV2( - Uint8List.fromList(data), '$keychainSalt$password'); + final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword); + final backupPassword = await _flutterSecureStorage.read(key: backupPasswordKey); + final data = utf8.encode( + json.encode({'pin': decodedPin, 'wallets': wallets, backupPasswordKey: backupPassword})); + final encrypted = await _encryptV2(Uint8List.fromList(data), '$keychainSalt$password'); return encrypted; } @@ -431,50 +459,63 @@ class BackupService { final preferences = { PreferencesKey.currentWalletName: _sharedPreferences.getString(PreferencesKey.currentWalletName), - PreferencesKey.currentNodeIdKey: - _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey), - PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences - .getInt(PreferencesKey.currentBalanceDisplayModeKey), - PreferencesKey.currentWalletType: - _sharedPreferences.getInt(PreferencesKey.currentWalletType), + PreferencesKey.currentNodeIdKey: _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey), + PreferencesKey.currentBalanceDisplayModeKey: + _sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey), + PreferencesKey.currentWalletType: _sharedPreferences.getInt(PreferencesKey.currentWalletType), PreferencesKey.currentFiatCurrencyKey: _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey), - PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences - .getBool(PreferencesKey.shouldSaveRecipientAddressKey), - PreferencesKey.disableBuyKey: _sharedPreferences - .getBool(PreferencesKey.disableBuyKey), - PreferencesKey.disableSellKey: _sharedPreferences - .getBool(PreferencesKey.disableSellKey), + PreferencesKey.shouldSaveRecipientAddressKey: + _sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey), + PreferencesKey.disableBuyKey: _sharedPreferences.getBool(PreferencesKey.disableBuyKey), + PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey), PreferencesKey.isDarkThemeLegacy: _sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy), - PreferencesKey.currentPinLength: - _sharedPreferences.getInt(PreferencesKey.currentPinLength), - PreferencesKey.currentTransactionPriorityKeyLegacy: _sharedPreferences - .getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), - PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences - .getBool(PreferencesKey.allowBiometricalAuthenticationKey), - PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences - .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), + PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength), + PreferencesKey.currentTransactionPriorityKeyLegacy: + _sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy), + PreferencesKey.allowBiometricalAuthenticationKey: + _sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey), + PreferencesKey.currentBitcoinElectrumSererIdKey: + _sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey), PreferencesKey.currentLanguageCode: _sharedPreferences.getString(PreferencesKey.currentLanguageCode), PreferencesKey.displayActionListModeKey: _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey), - PreferencesKey.currentTheme: - _sharedPreferences.getInt(PreferencesKey.currentTheme), - PreferencesKey.exchangeStatusKey: - _sharedPreferences.getInt(PreferencesKey.exchangeStatusKey), - PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences - .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), + PreferencesKey.currentTheme: _sharedPreferences.getInt(PreferencesKey.currentTheme), + PreferencesKey.exchangeStatusKey: _sharedPreferences.getInt(PreferencesKey.exchangeStatusKey), + PreferencesKey.currentDefaultSettingsMigrationVersion: + _sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion), PreferencesKey.bitcoinTransactionPriority: _sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority), PreferencesKey.moneroTransactionPriority: _sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority), PreferencesKey.currentFiatApiModeKey: _sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey), + PreferencesKey.selectedCake2FAPreset: + _sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset), + PreferencesKey.shouldRequireTOTP2FAForAccessingWallet: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet), + PreferencesKey.shouldRequireTOTP2FAForSendsToContact: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact), + PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact), + PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets), + PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets: _sharedPreferences + .getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets), + PreferencesKey.shouldRequireTOTP2FAForAddingContacts: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts), + PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets: + _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets), + PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences + .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings), PreferencesKey.sortBalanceBy: _sharedPreferences.getInt(PreferencesKey.sortBalanceBy), PreferencesKey.pinNativeTokenAtTop: _sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop), + PreferencesKey.useEtherscan: + _sharedPreferences.getBool(PreferencesKey.useEtherscan), }; return json.encode(preferences); @@ -488,28 +529,23 @@ class BackupService { } @Deprecated('Use v2 instead') - Future _encryptV1( - Uint8List data, String secretKeySource, String nonceBase64) async - => throw Exception('Deprecated'); + Future _encryptV1(Uint8List data, String secretKeySource, String nonceBase64) async => + throw Exception('Deprecated'); - Future _decryptV1( - Uint8List data, String secretKeySource, String nonceBase64, {int macLength = 16}) async { + Future _decryptV1(Uint8List data, String secretKeySource, String nonceBase64, + {int macLength = 16}) async { final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource)); final secretKey = SecretKey(secretKeyHash.bytes); final nonce = base64.decode(nonceBase64).toList(); - final box = SecretBox( - Uint8List.sublistView(data, 0, data.lengthInBytes - macLength).toList(), - nonce: nonce, - mac: Mac(Uint8List.sublistView(data, data.lengthInBytes - macLength))); + final box = SecretBox(Uint8List.sublistView(data, 0, data.lengthInBytes - macLength).toList(), + nonce: nonce, mac: Mac(Uint8List.sublistView(data, data.lengthInBytes - macLength))); final plainData = await _cipher.decrypt(box, secretKey: secretKey); return Uint8List.fromList(plainData); } - Future _encryptV2( - Uint8List data, String passphrase) async - => cake_backup.encrypt(passphrase, data, version: _v2); + Future _encryptV2(Uint8List data, String passphrase) async => + cake_backup.encrypt(passphrase, data, version: _v2); - Future _decryptV2( - Uint8List data, String passphrase) async - => cake_backup.decrypt(passphrase, data); + Future _decryptV2(Uint8List data, String passphrase) async => + cake_backup.decrypt(passphrase, data); } diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 694620fc6..1c6e7cd20 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -30,7 +30,7 @@ class SeedValidator extends Validator { case WalletType.ethereum: return ethereum!.getEthereumWordList(language); case WalletType.nano: - case WalletType.banano:// the same + case WalletType.banano: return nano!.getNanoWordList(language); default: return []; diff --git a/lib/di.dart b/lib/di.dart index d596c0ac8..8a8b55ce7 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -397,7 +397,9 @@ Future setup({ final authStore = getIt.get(); final appStore = getIt.get(); final useTotp = appStore.settingsStore.useTOTP2FA; - if (useTotp) { + final shouldUseTotp2FAToAccessWallets = + appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet; + if (useTotp && shouldUseTotp2FAToAccessWallets) { authPageState.close( route: Routes.totpAuthCodePage, arguments: TotpAuthArgumentsModel( @@ -533,17 +535,22 @@ Future setup({ getIt.get(), getIt.get())); - getIt.registerFactory(() => SendViewModel( + getIt.registerFactory( + () => SendViewModel( getIt.get().wallet!, getIt.get().settingsStore, getIt.get(), getIt.get(), getIt.get(), - _transactionDescriptionBox)); + getIt.get(), + _transactionDescriptionBox, + ), + ); getIt.registerFactoryParam( (PaymentRequest? initialPaymentRequest, _) => SendPage( sendViewModel: getIt.get(), + authService: getIt.get(), initialPaymentRequest: initialPaymentRequest, )); @@ -578,8 +585,8 @@ Future setup({ )); getIt.registerFactoryParam( - (WalletListViewModel walletListViewModel, _) => WalletEditViewModel( - walletListViewModel, getIt.get())); + (WalletListViewModel walletListViewModel, _) => + WalletEditViewModel(walletListViewModel, getIt.get())); getIt.registerFactoryParam, void>((args, _) { final walletListViewModel = args.first as WalletListViewModel; @@ -591,7 +598,6 @@ Future setup({ editingWallet: editingWallet); }); - getIt.registerFactory(() { final wallet = getIt.get().wallet!; @@ -638,7 +644,7 @@ Future setup({ }); getIt.registerFactory(() { - return PrivacySettingsViewModel(getIt.get()); + return PrivacySettingsViewModel(getIt.get(), getIt.get().wallet!); }); getIt.registerFactory(() { @@ -662,10 +668,11 @@ Future setup({ (ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact)); getIt.registerFactoryParam( - (CryptoCurrency? cur, _) => ContactListViewModel(_contactSource, _walletInfoSource, cur)); + (CryptoCurrency? cur, _) => + ContactListViewModel(_contactSource, _walletInfoSource, cur, getIt.get())); - getIt.registerFactoryParam( - (CryptoCurrency? cur, _) => ContactListPage(getIt.get(param1: cur))); + getIt.registerFactoryParam((CryptoCurrency? cur, _) => + ContactListPage(getIt.get(param1: cur), getIt.get())); getIt.registerFactoryParam( (ContactRecord? contact, _) => ContactPage(getIt.get(param1: contact))); @@ -710,13 +717,13 @@ Future setup({ )); getIt.registerFactory(() => ExchangeViewModel( - getIt.get().wallet!, - _tradesSource, - getIt.get(), - getIt.get(), - getIt.get().settingsStore, - getIt.get(), - )); + getIt.get().wallet!, + _tradesSource, + getIt.get(), + getIt.get(), + getIt.get().settingsStore, + getIt.get(), + getIt.get())); getIt.registerFactory(() => ExchangeTradeViewModel( wallet: getIt.get().wallet!, @@ -724,7 +731,8 @@ Future setup({ tradesStore: getIt.get(), sendViewModel: getIt.get())); - getIt.registerFactory(() => ExchangePage(getIt.get())); + getIt.registerFactory( + () => ExchangePage(getIt.get(), getIt.get())); getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get())); diff --git a/lib/entities/cake_2fa_preset_options.dart b/lib/entities/cake_2fa_preset_options.dart new file mode 100644 index 000000000..2aa6c4215 --- /dev/null +++ b/lib/entities/cake_2fa_preset_options.dart @@ -0,0 +1,35 @@ +import 'package:cw_core/enumerable_item.dart'; + +class Cake2FAPresetsOptions extends EnumerableItem with Serializable { + const Cake2FAPresetsOptions({required String super.title, required int super.raw}); + + static const narrow = Cake2FAPresetsOptions(title: 'Narrow', raw: 0); + static const normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1); + static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2); + + static Cake2FAPresetsOptions deserialize({required int raw}) { + switch (raw) { + case 0: + return Cake2FAPresetsOptions.narrow; + case 1: + return Cake2FAPresetsOptions.normal; + case 2: + return Cake2FAPresetsOptions.aggressive; + default: + throw Exception( + 'Incorrect Cake 2FA Preset $raw for Cake2FAPresetOptions deserialize', + ); + } + } +} + +enum VerboseControlSettings { + accessWallet, + addingContacts, + sendsToContacts, + sendsToNonContacts, + sendsToInternalWallets, + exchangesToInternalWallets, + securityAndBackupSettings, + creatingNewWallets, +} diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 634bec96b..c96e69611 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -472,6 +472,13 @@ Future checkCurrentNodes(Box nodeSource, SharedPreferences sharedPre await nodeSource.add(node); await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int); } + + if (currentEthereumNodeServer == null) { + final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum); + await nodeSource.add(node); + await sharedPreferences.setInt( + PreferencesKey.currentEthereumNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( @@ -511,8 +518,8 @@ Future migrateExchangeStatus(SharedPreferences sharedPreferences) async { return; } - await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, - isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw); + await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled + ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw); await sharedPreferences.remove(PreferencesKey.disableExchangeKey); } @@ -527,7 +534,8 @@ Future addEthereumNodeList({required Box nodes}) async { } Future changeEthereumCurrentNodeToDefault( - {required SharedPreferences sharedPreferences, required Box nodes}) async { + {required SharedPreferences sharedPreferences, + required Box nodes}) async { final node = getEthereumDefaultNode(nodes: nodes); final nodeId = node?.key as int? ?? 0; diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 464e50cac..56bbd5b7e 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -100,6 +100,22 @@ Future> loadDefaultNanoNodes() async { return nodes; } +Future> loadDefaultEthereumNodes() async { + final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml'); + final loadedNodes = loadYaml(nodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedNodes) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + node.type = WalletType.ethereum; + nodes.add(node); + } + } + + return nodes; +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 68413337e..6073cd973 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -43,16 +43,33 @@ class PreferencesKey { static const lastAppReviewDate = 'last_app_review_date'; static const sortBalanceBy = 'sort_balance_by'; static const pinNativeTokenAtTop = 'pin_native_token_at_top'; + static const useEtherscan = 'use_etherscan'; - - - static String moneroWalletUpdateV1Key(String name) - => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; + static String moneroWalletUpdateV1Key(String name) => + '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; static const exchangeProvidersSelection = 'exchange-providers-selection'; - static const clearnetDonationLink = 'clearnet_donation_link'; + static const clearnetDonationLink = 'clearnet_donation_link'; static const onionDonationLink = 'onion_donation_link'; static const lastSeenAppVersion = 'last_seen_app_version'; - static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; + static const shouldShowMarketPlaceInDashboard = + 'should_show_marketplace_in_dashboard'; static const isNewInstall = 'is_new_install'; + static const shouldRequireTOTP2FAForAccessingWallet = + 'should_require_totp_2fa_for_accessing_wallets'; + static const shouldRequireTOTP2FAForSendsToContact = + 'should_require_totp_2fa_for_sends_to_contact'; + static const shouldRequireTOTP2FAForSendsToNonContact = + 'should_require_totp_2fa_for_sends_to_non_contact'; + static const shouldRequireTOTP2FAForSendsToInternalWallets = + 'should_require_totp_2fa_for_sends_to_internal_wallets'; + static const shouldRequireTOTP2FAForExchangesToInternalWallets = + 'should_require_totp_2fa_for_exchanges_to_internal_wallets'; + static const shouldRequireTOTP2FAForAddingContacts = + 'should_require_totp_2fa_for_adding_contacts'; + static const shouldRequireTOTP2FAForCreatingNewWallets = + 'should_require_totp_2fa_for_creating_new_wallets'; + static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings = + 'should_require_totp_2fa_for_all_security_and_backup_settings'; + static const selectedCake2FAPreset = 'selected_cake_2fa_preset'; } diff --git a/lib/entities/template.dart b/lib/entities/template.dart index 8224ecdd8..6955136e0 100644 --- a/lib/entities/template.dart +++ b/lib/entities/template.dart @@ -55,5 +55,5 @@ class Template extends HiveObject { String get amount => amountRaw ?? ''; - List