diff --git a/apps/wallet-mobile/src/features/Discover/common/ConfirmHWConnectionModal.tsx b/apps/wallet-mobile/src/features/Discover/common/ConfirmHWConnectionModal.tsx index 7558ab1255..9264256ef5 100644 --- a/apps/wallet-mobile/src/features/Discover/common/ConfirmHWConnectionModal.tsx +++ b/apps/wallet-mobile/src/features/Discover/common/ConfirmHWConnectionModal.tsx @@ -1,10 +1,13 @@ import {useTheme} from '@yoroi/theme' import {HW} from '@yoroi/types' import React, {useCallback, useState} from 'react' +import {ErrorBoundary} from 'react-error-boundary' import {ActivityIndicator, ScrollView, StyleSheet, View} from 'react-native' +import {useMutation} from 'react-query' import {LedgerTransportSwitch} from '../../../components/LedgerTransportSwitch/LedgerTransportSwitch' import {useModal} from '../../../components/Modal/ModalContext' +import {ModalError} from '../../../components/ModalError/ModalError' import {Text} from '../../../components/Text' import {LedgerConnect} from '../../../legacy/HW' import {withBLE, withUSB} from '../../../yoroi-wallets/hw/hwWallet' @@ -14,9 +17,12 @@ import {useStrings} from './useStrings' type TransportType = 'USB' | 'BLE' type Step = 'select-transport' | 'connect-transport' | 'loading' +type OnConfirmOptions = {transportType: TransportType; deviceInfo: HW.DeviceInfo} type Props = { - onConfirm: (options: {transportType: TransportType; deviceInfo: HW.DeviceInfo}) => void + onConfirm: (options: OnConfirmOptions) => Promise + onClose: () => void + onCancel: () => void } const modalHeight = 350 @@ -25,21 +31,36 @@ export const useConfirmHWConnectionModal = () => { const {openModal, closeModal} = useModal() const strings = useStrings() const confirmHWConnection = useCallback( - ({onConfirm, onClose}: {onConfirm: Props['onConfirm']; onClose: () => void}) => { - openModal(strings.signTransaction, , modalHeight, onClose) + ({onConfirm, onClose, onCancel}: Props) => { + openModal( + strings.signTransaction, + ( + + )} + > + + , + modalHeight, + onClose, + ) }, [openModal, strings.signTransaction], ) return {confirmHWConnection, closeModal} } -const ConfirmHWConnectionModal = ({onConfirm}: Props) => { +const ConfirmHWConnectionModal = ({onConfirm}: Pick) => { const {walletManager} = useWalletManager() const [transportType, setTransportType] = useState('USB') const [step, setStep] = useState('select-transport') const {meta} = useSelectedWallet() const strings = useStrings() const {styles, colors} = useStyles() + const {mutate: handleOnConfirm} = useMutation({ + mutationFn: onConfirm, + useErrorBoundary: true, + }) const onSelectTransport = (transportType: TransportType) => { setTransportType(transportType) @@ -50,14 +71,14 @@ const ConfirmHWConnectionModal = ({onConfirm}: Props) => { setStep('loading') const hwDeviceInfo = withBLE(meta, deviceId) walletManager.updateWalletHWDeviceInfo(meta.id, hwDeviceInfo) - onConfirm({transportType: 'BLE', deviceInfo: hwDeviceInfo}) + handleOnConfirm({transportType: 'BLE', deviceInfo: hwDeviceInfo}) } const onConnectUSB = (deviceObj: HW.DeviceObj) => { setStep('loading') const hwDeviceInfo = withUSB(meta, deviceObj) walletManager.updateWalletHWDeviceInfo(meta.id, hwDeviceInfo) - onConfirm({transportType: 'USB', deviceInfo: hwDeviceInfo}) + handleOnConfirm({transportType: 'USB', deviceInfo: hwDeviceInfo}) } if (step === 'select-transport') { diff --git a/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.tsx b/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.tsx index c8bc63426d..47070fbc3f 100644 --- a/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.tsx +++ b/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.tsx @@ -8,12 +8,13 @@ import {useMutation} from 'react-query' import {logger} from '../../kernel/logger/logger' import {useWalletNavigation} from '../../kernel/navigation' import {cip30LedgerExtensionMaker} from '../../yoroi-wallets/cardano/cip30/cip30-ledger' +import {BaseLedgerError} from '../../yoroi-wallets/hw/hw' import {CreatedByInfoItem} from '../ReviewTx/useCases/ReviewTxScreen/ReviewTx/Overview/OverviewTab' import {useSelectedWallet} from '../WalletManager/common/hooks/useSelectedWallet' import {useBrowser} from './common/BrowserProvider' import {useOpenConfirmConnectionModal} from './common/ConfirmConnectionModal' import {useConfirmHWConnectionModal} from './common/ConfirmHWConnectionModal' -import {isUserRejectedError, userRejectedError} from './common/errors' +import {userRejectedError} from './common/errors' import {createDappConnector} from './common/helpers' import {usePromptRootKey} from './common/hooks' import {useShowHWNotSupportedModal} from './common/HWNotSupportedModal' @@ -253,22 +254,32 @@ export const useSignTxWithHW = () => { const mutationFn = React.useCallback( (options: {cbor: string; partial?: boolean}) => { return new Promise((resolve, reject) => { - let shouldResolveOnClose = true + let isClosed = false confirmHWConnection({ onConfirm: async ({transportType, deviceInfo}) => { try { const cip30 = cip30LedgerExtensionMaker(wallet, meta) const tx = await cip30.signTx(options.cbor, options.partial ?? false, deviceInfo, transportType === 'USB') - shouldResolveOnClose = false - return resolve(tx) + resolve(tx) + isClosed = true + closeModal() } catch (error) { + if (error instanceof BaseLedgerError) { + throw error + } reject(error) - } finally { + isClosed = true closeModal() } }, + onCancel: () => { + reject(userRejectedError()) + isClosed = true + closeModal() + }, onClose: () => { - if (shouldResolveOnClose) reject(userRejectedError()) + if (isClosed) return + reject(userRejectedError()) }, }) }) @@ -278,7 +289,7 @@ export const useSignTxWithHW = () => { const mutation = useMutation({ mutationFn, - useErrorBoundary: (error) => !isUserRejectedError(error) && !error.message.toLowerCase().includes('rejected'), + useErrorBoundary: false, mutationKey: ['useSignTxWithHW'], }) diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/hw/hw.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/hw/hw.ts index 3224f9b6be..2e93958d79 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/hw/hw.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/hw/hw.ts @@ -22,6 +22,7 @@ import {LocalizableError} from '../../../kernel/i18n/LocalizableError' import {logger} from '../../../kernel/logger/logger' import { AdaAppClosedError, + BaseLedgerError, GeneralConnectionError, HARDWARE_WALLETS, LedgerUserError, @@ -40,7 +41,7 @@ type LedgerConnectionResponse = { serialHex: string } -export class DeprecatedAdaAppError extends LocalizableError { +export class DeprecatedAdaAppError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.deprecatedAdaAppError.id, diff --git a/apps/wallet-mobile/src/yoroi-wallets/hw/hw.ts b/apps/wallet-mobile/src/yoroi-wallets/hw/hw.ts index 7cf3fac3bd..0253a4f4df 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/hw/hw.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/hw/hw.ts @@ -39,7 +39,9 @@ const getLedgerPermissions = () => { return permissions } -export class BluetoothDisabledError extends LocalizableError { +export class BaseLedgerError extends LocalizableError {} + +export class BluetoothDisabledError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.bluetoothDisabledError.id, @@ -47,7 +49,7 @@ export class BluetoothDisabledError extends LocalizableError { }) } } -export class GeneralConnectionError extends LocalizableError { +export class GeneralConnectionError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.connectionError.id, @@ -56,7 +58,7 @@ export class GeneralConnectionError extends LocalizableError { } } // note: uses same message as above. -export class LedgerUserError extends LocalizableError { +export class LedgerUserError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.connectionError.id, @@ -64,7 +66,7 @@ export class LedgerUserError extends LocalizableError { }) } } -export class RejectedByUserError extends LocalizableError { +export class RejectedByUserError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.rejectedByUserError.id, @@ -73,7 +75,7 @@ export class RejectedByUserError extends LocalizableError { } } -export class AdaAppClosedError extends LocalizableError { +export class AdaAppClosedError extends BaseLedgerError { constructor() { super({ id: ledgerMessages.appOpened.id,