CW-1090-ledger-issues (#2314)

* build: bump ledger_flutter_plus dependencies

* fix: handle connection errors occurring during connecting to a ledger device

* fix: enhance ledger error handling and improve wallet connection reliability

* fix: enhance ledger error handling and improve wallet connection reliability

* feat: add localized strings for "Try again" and update Ledger error handling on auth

* fix: handle rethrow behavior in onAuthenticationStateChange

* fix: improve Ledger error handling and refine error code interpretation

* fix: refine Ledger error code interpretation and enhance error handling [skip-ci]

* add missing ledger error codes

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Konstantin Ullrich 2025-06-19 19:00:16 +02:00 committed by GitHub
parent 4b137bc968
commit 18c2ba9366
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 159 additions and 31 deletions

View file

@ -73,8 +73,11 @@ class WalletLoadingService {
return wallet;
} catch (error, stack) {
await ExceptionHandler.resetLastPopupDate();
final isLedgerError = await ExceptionHandler.isLedgerError(error);
if (isLedgerError) rethrow;
await 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 {

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
@ -63,14 +64,49 @@ void startAuthenticationStateChange(
monero!.setGlobalLedgerConnection(ledgerVM.connection);
showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop()),
builder: (context) => AlertWithOneAction(
alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel,
alertBarrierDismissible: false,
buttonAction: () => Navigator.of(context).pop(),
),
);
await loadCurrentWallet();
bool tryOpening = true;
while (tryOpening) {
try {
await loadCurrentWallet();
tryOpening = false;
} on Exception catch (e) {
final errorCode = RegExp(r'0x\S*?(?= )').firstMatch(
e.toString());
final errorMessage = ledgerVM.interpretErrorCode(
errorCode?.group(0).toString().replaceAll("0x", "") ??
"");
if (errorMessage != null) {
await showPopUp<void>(
context: context,
builder: (context) => AlertWithTwoActions(
alertTitle: "Ledger Error",
alertContent: errorMessage,
leftButtonText: S.of(context).try_again,
alertBarrierDismissible: false,
actionLeftButton: () => Navigator.of(context).pop(),
rightButtonText: S.of(context).cancel,
actionRightButton: () {
tryOpening = false;
Navigator.of(context).pop();
},
),
);
} else {
tryOpening = false;
rethrow;
}
}
}
getIt.get<BottomSheetService>().showNext();
await navigatorKey.currentState!
.pushNamedAndRemoveUntil(Routes.dashboard, (route) => false);

View file

@ -165,8 +165,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
}
Future<void> _connectToDevice(LedgerDevice device) async {
await widget.ledgerVM.connectLedger(device, widget.walletType);
widget.onConnectDevice(context, widget.ledgerVM);
final isConnected =
await widget.ledgerVM.connectLedger(device, widget.walletType);
if (isConnected) widget.onConnectDevice(context, widget.ledgerVM);
}
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {

View file

@ -4,8 +4,10 @@ import 'package:cake_wallet/di.dart';
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_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/utils/package_info.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';
@ -15,7 +17,6 @@ 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';
class ExceptionHandler {
@ -113,6 +114,8 @@ class ExceptionHandler {
}
static Future<void> onError(FlutterErrorDetails errorDetails) async {
if (await onLedgerError(errorDetails)) return;
if (kDebugMode || kProfileMode) {
FlutterError.presentError(errorDetails);
printV(errorDetails.toString());
@ -183,6 +186,57 @@ class ExceptionHandler {
_hasError = false;
}
static const List<String> _ledgerErrors = [
'Wrong Device Status',
'PlatformException(133, Failed to write: (Unknown Error: 133), null, null)',
'PlatformException(IllegalArgument, Unknown deviceId:',
'ServiceNotSupportedException(ConnectionType.ble, Required service not supported. Write characteristic: false, Notify characteristic: false)',
'Exception: 6e01', // Wrong App
'Exception: 6d02',
'Exception: 6511',
'Exception: 6e00',
'Exception: 6985',
'Exception: 5515',
];
static bool isLedgerError(Object exception) =>
_ledgerErrors.any((element) => exception.toString().contains(element));
static Future<bool> onLedgerError(FlutterErrorDetails errorDetails) async {
if (!isLedgerError(errorDetails.exception)) return false;
String? interpretErrorCode(String errorCode) {
if (errorCode.contains("6985")) {
return S.current.ledger_error_tx_rejected_by_user;
} else if (errorCode.contains("5515")) {
return S.current.ledger_error_device_locked;
} else
if (["6e01", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
return S.current.ledger_error_wrong_app;
}
return null;
}
printV(errorDetails.exception);
if (navigatorKey.currentContext != null) {
await showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (context) => AlertWithOneAction(
alertTitle: "Ledger Error",
alertContent:
interpretErrorCode(errorDetails.exception.toString()) ??
S.of(context).ledger_connection_error,
buttonText: S.of(context).close,
buttonAction: () => Navigator.of(context).pop(),
),
);
}
_hasError = false;
return true;
}
/// Ignore User related errors or system errors
static bool _ignoreError(String error) =>
_ignoredErrors.any((element) => error.contains(element));
@ -227,6 +281,7 @@ class ExceptionHandler {
"core/auth_service.dart:64",
"core/key_service.dart:14",
"core/wallet_loading_service.dart:139",
"Wrong Device Status: 0x5515 (UNKNOWN)",
];
static Future<void> _addDeviceInfo(File file) async {

View file

@ -96,7 +96,8 @@ abstract class LedgerViewModelBase with Store {
if (!Platform.isIOS) await ledgerPlusUSB.stopScanning();
}
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
Future<bool> connectLedger(sdk.LedgerDevice device, WalletType type) async {
if (_isConnecting) return false;
_isConnecting = true;
_connectingWalletType = type;
if (isConnected) {
@ -110,17 +111,25 @@ abstract class LedgerViewModelBase with Store {
: ledgerPlusUSB;
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription =
ledger.deviceStateChanges.listen(_connectionChangeListener);
_connectionChangeSubscription = ledger
.deviceStateChanges(device.id)
.listen(_connectionChangeListener);
}
_connection = await ledger.connect(device);
try {
_connection = await ledger.connect(device);
_isConnecting = false;
return true;
} catch (e) {
printV(e);
}
_isConnecting = false;
return false;
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
sdk.LedgerConnection? _connection;
bool _isConnecting = true;
bool _isConnecting = false;
WalletType? _connectingWalletType;
void _connectionChangeListener(sdk.BleConnectionState event) {
@ -168,17 +177,14 @@ abstract class LedgerViewModelBase with Store {
}
String? interpretErrorCode(String errorCode) {
switch (errorCode) {
case "6985":
return S.current.ledger_error_tx_rejected_by_user;
case "5515":
return S.current.ledger_error_device_locked;
case "6d02": // UNKNOWN_APDU
case "6511":
case "6e00":
return S.current.ledger_error_wrong_app;
default:
return null;
if (errorCode.contains("6985")) {
return S.current.ledger_error_tx_rejected_by_user;
} else if (errorCode.contains("5515")) {
return S.current.ledger_error_device_locked;
} else
if (["6e01", "6a87", "6d02", "6511", "6e00"].any((e) => errorCode.contains(e))) {
return S.current.ledger_error_wrong_app;
}
return null;
}
}

View file

@ -80,11 +80,9 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
availableAccounts.addAll(accounts);
_nextIndex += limit;
// } on LedgerException catch (e) {
// error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
} catch (e) {
printV(e);
error = S.current.ledger_connection_error;
error = ledgerViewModel.interpretErrorCode(e.toString()) ?? S.current.ledger_connection_error;
}
isLoadingMoreAccounts = false;