Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions packages/suite/src/actions/settings/deviceSettingsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { FIRMWARE_MODULE_PREFIX } from '@suite-common/firmware';
import { Feature, selectIsFeatureDisabled } from '@suite-common/message-system';
import { createThunk } from '@suite-common/redux-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import {
deviceActions,
failEntropyCheckThunk,
selectSelectedDevice,
} from '@suite-common/wallet-core';
import { processEntropyCheckResultThunk, selectSelectedDevice } from '@suite-common/wallet-core';
import TrezorConnect, { ERRORS } from '@trezor/connect';

import * as modalActions from 'src/actions/suite/modalActions';
Expand Down Expand Up @@ -164,18 +160,7 @@ export const resetDevice =
});

if (isEntropyCheckEnabled) {
if (!result.success) {
dispatch(
notificationsActions.addToast({ type: 'error', error: result.payload.error }),
);
if (result.payload.code === 'Failure_EntropyCheck') {
dispatch(failEntropyCheckThunk({ device, error: result.payload }));
}
} else {
dispatch(
deviceActions.setEntropyCheckResult({ deviceId: device.id, success: true }),
);
}
dispatch(processEntropyCheckResultThunk({ device, result }));
}

return result;
Expand Down
21 changes: 20 additions & 1 deletion suite-common/wallet-core/src/device/deviceThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ type FailEntropyCheckParams = {
error: { code?: string; error: string };
};

export const failEntropyCheckThunk = createThunk(
const failEntropyCheckThunk = createThunk(
`${DEVICE_MODULE_PREFIX}/failEntropyCheckThunk`,
({ device, error }: FailEntropyCheckParams, { dispatch, extra }) => {
const contextData = {
Expand Down Expand Up @@ -586,3 +586,22 @@ export const failEntropyCheckThunk = createThunk(
}
},
);

type ProcessEntropyCheckResultThunkParams = {
device: AcquiredDevice;
result: Awaited<ReturnType<typeof TrezorConnect.resetDevice>>;
};

export const processEntropyCheckResultThunk = createThunk(
`${DEVICE_MODULE_PREFIX}/processEntropyCheckResultThunk`,
({ device, result }: ProcessEntropyCheckResultThunkParams, { dispatch }) => {
if (result.success) {
dispatch(deviceActions.setEntropyCheckResult({ deviceId: device.id, success: true }));
} else {
dispatch(notificationsActions.addToast({ type: 'error', error: result.payload.error }));
if (result.payload.code === 'Failure_EntropyCheck') {
dispatch(failEntropyCheckThunk({ device, error: result.payload }));
}
}
},
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ pls careful CR, this is a really key part where a lot can go wrong.
I copied the implementation from suite.
Unifying it will lower the surface for future human mistakes 🙂

46 changes: 19 additions & 27 deletions suite-native/device/src/deviceThunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { createThunk } from '@suite-common/redux-utils';
import { BackupType } from '@suite-common/suite-types';
import {
deviceActions,
failEntropyCheckThunk,
processEntropyCheckResultThunk,
selectDevicePath,
selectIsDeviceInitialized,
selectSelectedDevice,
} from '@suite-common/wallet-core';
import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { RootStackRoutes, navigationContainerRef } from '@suite-native/navigation';
import TrezorConnect, { PROTO } from '@trezor/connect';
import TrezorConnect, { PROTO, SuccessWithDevice, Unsuccessful } from '@trezor/connect';
import { exhaustive } from '@trezor/type-utils';

const NATIVE_DEVICE_MODULE_PREFIX = 'nativeDevice';
Expand Down Expand Up @@ -63,9 +62,13 @@ const getResetDeviceConfig = (walletBackupType: BackupType): PROTO.ResetDevice =
}
};

export const createAndBackupWalletThunk = createThunk(
export const createAndBackupWalletThunk = createThunk<
Unsuccessful | SuccessWithDevice<PROTO.Success>,
{ walletBackupType: BackupType },
{ rejectValue: string }
>(
`${NATIVE_DEVICE_MODULE_PREFIX}/createAndBackupWalletThunk`,
async ({ walletBackupType }: { walletBackupType: BackupType }, { getState, dispatch }) => {
async ({ walletBackupType }, { getState, dispatch, fulfillWithValue, rejectWithValue }) => {
const device = selectSelectedDevice(getState());
const devicePath = selectDevicePath(getState());
const isDeviceInitialized = selectIsDeviceInitialized(getState());
Expand All @@ -76,7 +79,7 @@ export const createAndBackupWalletThunk = createThunk(
);

if (!device || !device.features || !devicePath) {
throw new Error('Device not found');
return rejectWithValue('Device not found');
}

// If the device already has a seed, backup is created.
Expand All @@ -95,17 +98,14 @@ export const createAndBackupWalletThunk = createThunk(
deviceCallback: () =>
TrezorConnect.backupDevice({
...backupParams,
device: {
path: device.path,
},
device: { path: device.path },
}),
});
// error from device-mutex
if (!deviceResponse.success) return rejectWithValue(deviceResponse.error);

if (!deviceResponse.success) {
throw new Error(deviceResponse.error);
}

return deviceResponse.payload;
// full response from connect, which is either success or error
return fulfillWithValue(deviceResponse.payload);
}

const deviceResponse = await requestPrioritizedDeviceAccess({
Expand All @@ -118,24 +118,16 @@ export const createAndBackupWalletThunk = createThunk(
entropy_check: isEntropyCheckEnabled,
}),
});
// error from device-mutex
if (!deviceResponse.success) return rejectWithValue(deviceResponse.error);

if (!deviceResponse.success) {
throw new Error(deviceResponse.error);
}

// full response from connect, which is either success or error
const result = deviceResponse.payload;
if (isEntropyCheckEnabled) {
if (!result.success && result.payload.code === 'Failure_EntropyCheck') {
dispatch(failEntropyCheckThunk({ device, error: result.payload }));
navigationContainerRef.navigate(RootStackRoutes.DeviceCompromisedModal);
} else {
dispatch(
deviceActions.setEntropyCheckResult({ deviceId: device.id, success: true }),
);
}
dispatch(processEntropyCheckResultThunk({ device, result }));
}

return result;
return fulfillWithValue(result);
},
);

Expand Down
1 change: 1 addition & 0 deletions suite-native/module-device-onboarding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@react-navigation/native-stack": "7.3.25",
"@reduxjs/toolkit": "2.8.2",
"@shopify/react-native-skia": "2.2.3",
"@suite-common/message-system": "workspace:*",
"@suite-common/suite-constants": "workspace:*",
"@suite-common/suite-types": "workspace:*",
"@suite-common/thp": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { useCallback, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';

import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import { isFulfilled } from '@reduxjs/toolkit';

import {
Feature,
MessageSystemRootState,
selectIsFeatureEnabled,
} from '@suite-common/message-system';
import { createAndBackupWalletThunk } from '@suite-native/device';
import {
DeviceOnboardingStackParamList,
DeviceOnboardingStackRoutes,
StackProps,
RootStackParamList,
RootStackRoutes,
StackToStackCompositeNavigationProps,
} from '@suite-native/navigation';
import { ERRORS } from '@trezor/connect';

Expand All @@ -22,12 +30,26 @@ const DEFINITIVE_ERRORS: ERRORS.ErrorCode[] = [
'Failure_EntropyCheck',
];

export const WalletCreationScreen = ({
navigation,
route,
}: StackProps<DeviceOnboardingStackParamList, DeviceOnboardingStackRoutes.WalletCreation>) => {
type NavigationProp = StackToStackCompositeNavigationProps<
DeviceOnboardingStackParamList,
DeviceOnboardingStackRoutes.WalletCreation,
RootStackParamList
>;

type RouteProps = RouteProp<
DeviceOnboardingStackParamList,
DeviceOnboardingStackRoutes.WalletCreation
>;

export const WalletCreationScreen = () => {
const route = useRoute<RouteProps>();
const { walletBackupType } = route.params;
const dispatch = useDispatch();
const navigation = useNavigation<NavigationProp>();

const isEntropyCheckEnabled = useSelector((state: MessageSystemRootState) =>
selectIsFeatureEnabled(state, Feature.entropyCheckMobile, true),
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracting .navigate side-effect from the thunk to React means that I have to duplicate the condition for isEntropyCheckEnabled but that's acceptable I guess.


const handleCreateAndBackupWallet = useCallback(async () => {
const response = await dispatch(createAndBackupWalletThunk({ walletBackupType }));
Expand All @@ -40,16 +62,18 @@ export const WalletCreationScreen = ({
flowType: 'create',
});
}
if (
responsePayload.payload.code &&
DEFINITIVE_ERRORS.includes(responsePayload.payload.code)
) {
const { code } = responsePayload.payload;
if (code && DEFINITIVE_ERRORS.includes(code)) {
if (isEntropyCheckEnabled && code === 'Failure_EntropyCheck') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: I think what's not ideal is that we could simply return the value of failEntropyCheckThunk all the way to the result of createAndBackupWalletThunk and by doing that, we wouldn't have to store it in the state and select it here and just look at the type of return value to see whether it's the "correct" error. Again, out of scope for this PR for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Together with your comment above it's a good followup for a next PR in future.

navigation.navigate(RootStackRoutes.DeviceCompromisedModal);
}

return;
}
}

// repeat the attempt if error was not one of the DEFINITIVE_ERRORS
handleCreateAndBackupWallet();
}, [dispatch, walletBackupType, navigation]);
}, [dispatch, walletBackupType, navigation, isEntropyCheckEnabled]);

useEffect(() => {
handleCreateAndBackupWallet();
Expand Down
3 changes: 3 additions & 0 deletions suite-native/module-device-onboarding/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "libDev" },
"references": [
{
"path": "../../suite-common/message-system"
},
{
"path": "../../suite-common/suite-constants"
},
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11982,6 +11982,7 @@ __metadata:
"@react-navigation/native-stack": "npm:7.3.25"
"@reduxjs/toolkit": "npm:2.8.2"
"@shopify/react-native-skia": "npm:2.2.3"
"@suite-common/message-system": "workspace:*"
"@suite-common/suite-constants": "workspace:*"
"@suite-common/suite-types": "workspace:*"
"@suite-common/thp": "workspace:*"
Expand Down