diff --git a/apps/wallet-mobile/src/features/Discover/common/errors.ts b/apps/wallet-mobile/src/features/Discover/common/errors.ts new file mode 100644 index 0000000000..550909018d --- /dev/null +++ b/apps/wallet-mobile/src/features/Discover/common/errors.ts @@ -0,0 +1,7 @@ +const USER_REJECTED_ERROR_MESSAGE = 'User rejected' + +export const userRejectedError = () => new Error(USER_REJECTED_ERROR_MESSAGE) + +export const isUserRejectedError = (error: Error): boolean => { + return error.message === USER_REJECTED_ERROR_MESSAGE +} diff --git a/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx b/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx index 2bd93f55f2..b4021b3a25 100644 --- a/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx +++ b/apps/wallet-mobile/src/features/Discover/useCases/ReviewTransaction/ReviewTransaction.tsx @@ -8,7 +8,7 @@ import {useEffect} from 'react' import {StyleSheet, View} from 'react-native' import {TouchableOpacity} from 'react-native-gesture-handler' import {SafeAreaView} from 'react-native-safe-area-context' -import {useQuery} from 'react-query' +import {useMutation, useQuery} from 'react-query' import {z} from 'zod' import {Button} from '../../../../components/Button/Button' @@ -17,6 +17,7 @@ import {Icon} from '../../../../components/Icon' import {ScrollView} from '../../../../components/ScrollView/ScrollView' import {Spacer} from '../../../../components/Spacer/Spacer' import {Text} from '../../../../components/Text' +import {logger} from '../../../../kernel/logger/logger' import {useParams} from '../../../../kernel/navigation' import {cip30LedgerExtensionMaker} from '../../../../yoroi-wallets/cardano/cip30/cip30-ledger' import {wrappedCsl} from '../../../../yoroi-wallets/cardano/wrappedCsl' @@ -25,6 +26,7 @@ import {asQuantity} from '../../../../yoroi-wallets/utils/utils' import {usePortfolioTokenInfos} from '../../../Portfolio/common/hooks/usePortfolioTokenInfos' import {useSelectedWallet} from '../../../WalletManager/common/hooks/useSelectedWallet' import {useConfirmHWConnectionModal} from '../../common/ConfirmHWConnectionModal' +import {isUserRejectedError, userRejectedError} from '../../common/errors' import {usePromptRootKey} from '../../common/hooks' import {useStrings} from '../../common/useStrings' @@ -54,7 +56,7 @@ export const ReviewTransaction = () => { const {styles} = useStyles() - const signTxWithHW = useSignTxWithHW() + const {sign: signTxWithHW} = useSignTxWithHW() const handleOnConfirm = async () => { if (!params.isHW) { @@ -63,8 +65,13 @@ export const ReviewTransaction = () => { return } - const signature = await signTxWithHW(params.cbor, params.partial) - params.onConfirm(signature) + signTxWithHW( + {cbor: params.cbor, partial: params.partial}, + { + onSuccess: (signature) => params.onConfirm(signature), + onError: (error) => logger.error('ReviewTransaction::handleOnConfirm', {error}), + }, + ) } useEffect(() => { @@ -437,7 +444,7 @@ const useConnectorPromptRootKey = () => { return Promise.resolve() }, onClose: () => { - if (shouldResolveOnClose) reject(new Error('User rejected')) + if (shouldResolveOnClose) reject(userRejectedError()) }, }) } catch (error) { @@ -451,15 +458,15 @@ export const useSignTxWithHW = () => { const {confirmHWConnection, closeModal} = useConfirmHWConnectionModal() const {wallet, meta} = useSelectedWallet() - return React.useCallback( - (cbor: string, partial?: boolean) => { + const mutationFn = React.useCallback( + (options: {cbor: string; partial?: boolean}) => { return new Promise((resolve, reject) => { let shouldResolveOnClose = true confirmHWConnection({ onConfirm: async ({transportType, deviceInfo}) => { try { const cip30 = cip30LedgerExtensionMaker(wallet, meta) - const tx = await cip30.signTx(cbor, partial ?? false, deviceInfo, transportType === 'USB') + const tx = await cip30.signTx(options.cbor, options.partial ?? false, deviceInfo, transportType === 'USB') shouldResolveOnClose = false return resolve(tx) } catch (error) { @@ -469,11 +476,19 @@ export const useSignTxWithHW = () => { } }, onClose: () => { - if (shouldResolveOnClose) reject(new Error('User rejected')) + if (shouldResolveOnClose) reject(userRejectedError()) }, }) }) }, [confirmHWConnection, wallet, meta, closeModal], ) + + const mutation = useMutation({ + mutationFn, + useErrorBoundary: (error) => !isUserRejectedError(error), + mutationKey: ['useSignTxWithHW'], + }) + + return {...mutation, sign: mutation.mutate} } diff --git a/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.ts b/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.ts index 0cf137510b..a0a0a3ad89 100644 --- a/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.ts +++ b/apps/wallet-mobile/src/features/Discover/useDappConnectorManager.ts @@ -6,6 +6,7 @@ import {InteractionManager} from 'react-native' import {useSelectedWallet} from '../WalletManager/common/hooks/useSelectedWallet' import {useOpenConfirmConnectionModal} from './common/ConfirmConnectionModal' +import {userRejectedError} from './common/errors' import {createDappConnector} from './common/helpers' import {usePromptRootKey} from './common/hooks' import {useShowHWNotSupportedModal} from './common/HWNotSupportedModal' @@ -44,7 +45,7 @@ export const useDappConnectorManager = () => { onCancel: () => { if (!shouldResolve) return shouldResolve = false - reject(new Error('User rejected')) + reject(userRejectedError()) }, }) }) @@ -67,7 +68,7 @@ export const useDappConnectorManager = () => { onCancel: () => { if (!shouldResolve) return shouldResolve = false - reject(new Error('User rejected')) + reject(userRejectedError()) }, }) }) @@ -98,7 +99,7 @@ const useSignData = () => { return Promise.resolve() }, onClose: () => { - if (shouldResolveOnClose) reject(new Error('User rejected')) + if (shouldResolveOnClose) reject(userRejectedError()) }, }) } catch (error) { @@ -120,10 +121,10 @@ const useSignDataWithHW = () => { onConfirm: () => { closeModal() shouldResolveOnClose = false - return reject(new Error('User rejected')) + return reject(userRejectedError()) }, onClose: () => { - if (shouldResolveOnClose) reject(new Error('User rejected')) + if (shouldResolveOnClose) reject(userRejectedError()) }, }) }) diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/cip30/cip30-ledger.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/cip30/cip30-ledger.ts index 13b66d2220..533e6a1d53 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/cip30/cip30-ledger.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/cip30/cip30-ledger.ts @@ -22,11 +22,11 @@ class CIP30LedgerExtension { async signTx(cbor: string, partial: boolean, hwDeviceInfo: HW.DeviceInfo, useUSB: boolean): Promise { const {csl, release} = wrappedCsl() try { - const tx = await csl.FixedTransaction.fromHex(cbor) + const tx = await csl.Transaction.fromHex(cbor) if (!partial) await assertHasAllSigners(cbor, this.wallet, this.meta) const txBody = await tx.body() - const transactionSetTag = await has_transaction_set_tag(await txBody.toBytes()) + const transactionSetTag = await has_transaction_set_tag(await tx.toBytes()) if (transactionSetTag === TransactionSetsState.MixedSets) { throw new Error('CIP30LedgerExtension.signTx: Mixed transaction sets are not supported when using a HW wallet')