Skip to content

Commit

Permalink
Merge branch 'cake-tech:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoBlackCat authored Aug 7, 2024
2 parents 02bfe64 + e58d87e commit a6b6a54
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 14 deletions.
17 changes: 16 additions & 1 deletion assets/litecoin_electrum_server_list.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
-
uri: ltc-electrum.cakewallet.com:50002
useSSL: true
isDefault: true
isDefault: true
-
uri: litecoin.stackwallet.com:20063
useSSL: true
-
uri: electrum-ltc.bysh.me:50002
useSSL: true
-
uri: lightweight.fiatfaucet.com:50002
useSSL: true
-
uri: electrum.ltc.xurious.com:50002
useSSL: true
-
uri: backup.electrum-ltc.org:443
useSSL: true
19 changes: 19 additions & 0 deletions cw_core/lib/wallet_service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:convert';
import 'dart:io';

import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/file.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_type.dart';
Expand Down Expand Up @@ -42,4 +44,21 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
await File(walletDirPath).copy(backupWalletDirPath);
}
}

Future<String> getSeeds(String name, String password, WalletType type) async {
try {
final path = await pathForWallet(name: name, type: type);
final jsonSource = await read(path: path, password: password);
try {
final data = json.decode(jsonSource) as Map;
return data['mnemonic'] as String? ?? '';
} catch (_) {
// if not a valid json
return jsonSource.substring(0, 200);
}
} catch (_) {
// if the file couldn't be opened or read
return '';
}
}
}
5 changes: 4 additions & 1 deletion cw_monero/lib/api/transaction_history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ Future<PendingTransactionDescription> createTransactionSync(
})();

if (error != null) {
final message = error;
String message = error;
if (message.contains("RPC error")) {
message = "Invalid node response, please try again or switch node\n\ntrace: $message";
}
throw CreationTransactionException(message: message);
}

Expand Down
1 change: 0 additions & 1 deletion cw_monero/lib/monero_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/coins_info.dart';
import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
Expand Down
37 changes: 29 additions & 8 deletions cw_monero/lib/monero_wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
final String spendKey;
}

class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials, MoneroNewWalletCredentials> {
class MoneroWalletService extends WalletService<
MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials,
MoneroRestoreWalletFromKeysCredentials,
MoneroNewWalletCredentials> {
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);

final Box<WalletInfo> walletInfoSource;
Expand Down Expand Up @@ -183,11 +186,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
final wmaddr = wmPtr.address;
final waddr = openedWalletsByPath["$path/$wallet"]!.address;
// await Isolate.run(() {
monero.WalletManager_closeWallet(
Pointer.fromAddress(wmaddr),
Pointer.fromAddress(waddr),
false
);
monero.WalletManager_closeWallet(
Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), false);
// });
openedWalletsByPath.remove("$path/$wallet");
print("wallet closed");
Expand Down Expand Up @@ -248,7 +248,8 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,

@override
Future<MoneroWallet> restoreFromHardwareWallet(MoneroNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Monero wallet from a hardware wallet is not yet supported!");
throw UnimplementedError(
"Restoring a Monero wallet from a hardware wallet is not yet supported!");
}

@override
Expand Down Expand Up @@ -350,4 +351,24 @@ class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
print(e.toString());
}
}

@override
Future<String> getSeeds(String name, String password, WalletType type) async {
try {
final path = await pathForWallet(name: name, type: getType());

if (walletFilesExist(path)) {
await repairOldAndroidWallet(name);
}

await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
return wallet.seed;
} catch (_) {
// if the file couldn't be opened or read
return '';
}
}
}
31 changes: 29 additions & 2 deletions lib/core/wallet_loading_service.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'dart:async';

import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/wallet_base.dart';
Expand Down Expand Up @@ -52,6 +55,12 @@ class WalletLoadingService {
} catch (error, stack) {
ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));

// try fetching the seeds of the corrupted wallet to show it to the user
String corruptedWalletsSeeds = "Corrupted wallets seeds (if retrievable, empty otherwise):";
try {
corruptedWalletsSeeds += await _getCorruptedWalletSeeds(name, type);
} catch (_) {}

// try opening another wallet that is not corrupted to give user access to the app
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);

Expand All @@ -69,12 +78,23 @@ class WalletLoadingService {
await sharedPreferences.setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type));

// if found a wallet that is not corrupted, then still display the seeds of the corrupted ones
authenticatedErrorStreamController.add(corruptedWalletsSeeds);

return wallet;
} catch (_) {}
} catch (_) {
// save seeds and show corrupted wallets' seeds to the user
try {
final seeds = await _getCorruptedWalletSeeds(walletInfo.name, walletInfo.type);
if (!corruptedWalletsSeeds.contains(seeds)) {
corruptedWalletsSeeds += seeds;
}
} catch (_) {}
}
}

// if all user's wallets are corrupted throw exception
throw error;
throw error.toString() + "\n\n" + corruptedWalletsSeeds;
}
}

Expand All @@ -96,4 +116,11 @@ class WalletLoadingService {
isPasswordUpdated = true;
await sharedPreferences.setBool(key, isPasswordUpdated);
}

Future<String> _getCorruptedWalletSeeds(String name, WalletType type) async {
final walletService = walletServiceFactory.call(type);
final password = await keyService.getWalletPassword(walletName: name);

return "\n\n$type ($name): ${await walletService.getSeeds(name, password, type)}";
}
}
14 changes: 14 additions & 0 deletions lib/di.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async' show Timer;

import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
Expand Down Expand Up @@ -488,6 +490,7 @@ Future<void> setup({

if (loginError != null) {
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
loginError = null;
}

ReactionDisposer? _reaction;
Expand All @@ -499,6 +502,17 @@ Future<void> setup({
linkViewModel.handleLink();
}
});

Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick > 30) {
timer.cancel();
}

if (loginError != null) {
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
timer.cancel();
}
});
}
});
});
Expand Down
14 changes: 14 additions & 0 deletions lib/reactions/on_authentication_state_change.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:flutter/widgets.dart';
Expand All @@ -8,9 +10,16 @@ import 'package:cake_wallet/store/authentication_store.dart';
ReactionDisposer? _onAuthenticationStateChange;

dynamic loginError;
StreamController<dynamic> authenticatedErrorStreamController = StreamController<dynamic>();

void startAuthenticationStateChange(
AuthenticationStore authenticationStore, GlobalKey<NavigatorState> navigatorKey) {
authenticatedErrorStreamController.stream.listen((event) {
if (authenticationStore.state == AuthenticationState.allowed) {
ExceptionHandler.showError(event.toString(), delayInSeconds: 3);
}
});

_onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state;

Expand All @@ -26,6 +35,11 @@ void startAuthenticationStateChange(

if (state == AuthenticationState.allowed) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);
if (!(await authenticatedErrorStreamController.stream.isEmpty)) {
ExceptionHandler.showError(
(await authenticatedErrorStreamController.stream.first).toString());
authenticatedErrorStreamController.stream.drain();
}
return;
}
});
Expand Down
14 changes: 14 additions & 0 deletions lib/src/screens/dashboard/pages/balance_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ class CryptoBalanceWidget extends StatelessWidget {
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: "Monero wallet is broken",
subTitle: "Here are the things that are broken:\n - "
+dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ")
+"\n\nPlease restart your wallet and if it doesn't help contact our support.",
onTap: () {},
)
)
],
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ class MoneroAccountEditOrCreatePage extends BasePage {

await moneroAccountCreationViewModel.save();

Navigator.of(context).pop(_textController.text);
if (context.mounted) {
Navigator.of(context).pop(_textController.text);
}
},
text: moneroAccountCreationViewModel.isEdit
? S.of(context).rename
Expand Down
51 changes: 51 additions & 0 deletions lib/utils/exception_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/root_dir.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand Down Expand Up @@ -254,4 +256,53 @@ class ExceptionHandler {
'productName': data.productName,
};
}

static void showError(String error, {int? delayInSeconds}) async {
if (_hasError) {
return;
}
_hasError = true;

if (delayInSeconds != null) {
Future.delayed(Duration(seconds: delayInSeconds), () => _showCopyPopup(error));
return;
}

WidgetsBinding.instance.addPostFrameCallback(
(_) async => _showCopyPopup(error),
);
}

static Future<void> _showCopyPopup(String content) async {
if (navigatorKey.currentContext != null) {
final shouldCopy = await showPopUp<bool?>(
context: navigatorKey.currentContext!,
builder: (context) {
return AlertWithTwoActions(
isDividerExist: true,
alertTitle: S.of(context).error,
alertContent: content,
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).close,
actionRightButton: () {
Navigator.of(context).pop(true);
},
actionLeftButton: () {
Navigator.of(context).pop();
},
);
},
);

if (shouldCopy == true) {
await Clipboard.setData(ClipboardData(text: content));
await showBar<void>(
navigatorKey.currentContext!,
S.of(navigatorKey.currentContext!).copied_to_clipboard,
);
}
}

_hasError = false;
}
}
17 changes: 17 additions & 0 deletions lib/view_model/dashboard/dashboard_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,23 @@ abstract class DashboardViewModelBase with Store {
wallet.type == WalletType.wownero ||
wallet.type == WalletType.haven;

@computed
List<String> get isMoneroWalletBrokenReasons {
if (wallet.type != WalletType.monero) return [];
final keys = monero!.getKeys(wallet);
List<String> errors = [
if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0",
if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0",
if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0",
if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) "private view key is 0",
if (wallet.seed == null) "wallet seed is null",
if (wallet.seed == "") "wallet seed is empty",
if (monero!.getSubaddressList(wallet).getAll(wallet)[0].address == "41d7FXjswpK1111111111111111111111111111111111111111111111111111111111111111111111111111112KhNi4")
"primary address is invalid, you won't be able to receive / spend funds",
];
return errors;
}

@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet;

Expand Down

0 comments on commit a6b6a54

Please sign in to comment.