diff --git a/.eslintrc b/.eslintrc
index 106ef80950..80f5fc117e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -100,7 +100,7 @@
"newIsCapExceptions": ["mnemonic"]
}
],
-
+ "no-constant-condition": ["error", { "checkLoops": false }],
"react/prop-types": "off",
"no-plusplus": "off",
"no-underscore-dangle": "off",
diff --git a/libs/hwServer/index.js b/libs/hwServer/index.js
index 0ad78dcb8b..494e9abed5 100644
--- a/libs/hwServer/index.js
+++ b/libs/hwServer/index.js
@@ -66,7 +66,7 @@ export class HwServer {
async selectDevice({ id }) {
this.currentDeviceId = id;
- this.deviceUpdate();
+ this.deviceUpdate(id);
return this.currentDeviceId;
}
@@ -111,7 +111,10 @@ export class HwServer {
const { sender } = this.pubSub;
this.devices.push(device);
this.syncDevices();
- publish(sender, { event: IPC_MESSAGES.HW_CONNECTED, payload: { model: device.model } });
+ publish(sender, {
+ event: IPC_MESSAGES.HW_CONNECTED,
+ payload: { model: device.model, devices: this.devices },
+ });
}
/**
diff --git a/setup/react/app/index.js b/setup/react/app/index.js
index ab0a14d4e8..d6772b3f33 100644
--- a/setup/react/app/index.js
+++ b/setup/react/app/index.js
@@ -29,6 +29,7 @@ import {
useCurrentApplication,
} from 'src/modules/blockchainApplication/manage/hooks';
import './variables.css';
+import useHwListener from "src/modules/hardwareWallet/hooks/useHwListener";
import styles from './app.css';
if (MOCK_SERVICE_WORKER) {
@@ -45,7 +46,7 @@ const App = ({ history }) => {
const { data: chainMetaData, isLoading } = useBlockchainApplicationMeta();
const { setApplication } = useApplicationManagement();
const [, setCurrentApplication] = useCurrentApplication();
-
+ useHwListener();
useIpc(history);
useEffect(() => {
diff --git a/src/locales/en/common.json b/src/locales/en/common.json
index 0d6148386e..ce4869838c 100644
--- a/src/locales/en/common.json
+++ b/src/locales/en/common.json
@@ -113,6 +113,7 @@
"Claim reward": "Claim reward",
"Claim rewards": "Claim rewards",
"Clear all filters": "Clear all filters",
+ "Click here to select a hardware wallet": "Click here to select a hardware wallet",
"Click to hide full and old addresses": "Click to hide full and old addresses",
"Click to hide the full address": "Click to hide the full address",
"Click to see full and old addresses": "Click to see full and old addresses",
diff --git a/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.js b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.js
new file mode 100644
index 0000000000..9749dd4af8
--- /dev/null
+++ b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import Dialog from '@theme/dialog/dialog';
+import BoxHeader from 'src/theme/box/header';
+import Box from '@theme/box';
+
+function SelectHardwareDeviceModal() {
+ return (
+
+ );
+}
+
+export default SelectHardwareDeviceModal;
diff --git a/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.test.js b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.test.js
new file mode 100644
index 0000000000..c2e42124c2
--- /dev/null
+++ b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal.test.js
@@ -0,0 +1,10 @@
+import { screen } from '@testing-library/react';
+import {renderWithRouter} from "src/utils/testHelpers";
+import SelectHardwareDeviceModal from './SelectHardwareDeviceModal';
+
+describe('SelectHardwareDeviceModal', () => {
+ it('Should show Switch account in the title', () => {
+ renderWithRouter(SelectHardwareDeviceModal);
+ expect(screen.getByText('Switch account')).toBeTruthy();
+ });
+});
diff --git a/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/index.js b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/index.js
new file mode 100644
index 0000000000..166bcdba6f
--- /dev/null
+++ b/src/modules/hardwareWallet/components/SelectHardwareDeviceModal/index.js
@@ -0,0 +1,4 @@
+import SelectHardwareDeviceModal
+ from "src/modules/hardwareWallet/components/SelectHardwareDeviceModal/SelectHardwareDeviceModal";
+
+export default SelectHardwareDeviceModal;
diff --git a/src/modules/hardwareWallet/hooks/useHWAccounts.js b/src/modules/hardwareWallet/hooks/useHWAccounts.js
index bcb9a58d25..094d17d700 100644
--- a/src/modules/hardwareWallet/hooks/useHWAccounts.js
+++ b/src/modules/hardwareWallet/hooks/useHWAccounts.js
@@ -1,5 +1,5 @@
import { useSelector } from 'react-redux';
-import { selectHWAccounts } from 'src/redux/selectors';
+import { selectHWAccounts } from '@hardwareWallet/store/selectors/hwSelectors';
const useHWAccounts = () => {
const hwAccounts = useSelector(selectHWAccounts);
diff --git a/src/modules/hardwareWallet/hooks/useHwListener.js b/src/modules/hardwareWallet/hooks/useHwListener.js
new file mode 100644
index 0000000000..fd2ac6215f
--- /dev/null
+++ b/src/modules/hardwareWallet/hooks/useHwListener.js
@@ -0,0 +1,28 @@
+import { useDispatch } from 'react-redux';
+import { useEffect } from 'react';
+import { IPC_MESSAGES } from '@libs/hwServer/constants';
+import {
+ setHardwareWalletDevices,
+ setCurrentDevice,
+} from 'src/modules/hardwareWallet/store/actions';
+
+const { DEVICE_LIST_CHANGED, DEVICE_UPDATE } = IPC_MESSAGES;
+
+function useHwListener() {
+ const dispatch = useDispatch();
+
+ const { ipc } = window;
+
+ if (!ipc) return;
+
+ useEffect(() => {
+ ipc.on(DEVICE_LIST_CHANGED, (action, data) => {
+ dispatch(setHardwareWalletDevices(data));
+ });
+ ipc.on(DEVICE_UPDATE, (action, data) => {
+ dispatch(setCurrentDevice(data));
+ });
+ }, []);
+}
+
+export default useHwListener;
diff --git a/src/modules/hardwareWallet/hooks/useHwListener.test.js b/src/modules/hardwareWallet/hooks/useHwListener.test.js
new file mode 100644
index 0000000000..246e4e0c75
--- /dev/null
+++ b/src/modules/hardwareWallet/hooks/useHwListener.test.js
@@ -0,0 +1,58 @@
+import { renderHook, act } from '@testing-library/react-hooks';
+import { useDispatch } from 'react-redux';
+import { IPC_MESSAGES } from '@libs/hwServer/constants';
+import {setHardwareWalletDevices, setCurrentDevice} from "@hardwareWallet/store/actions";
+import useHwListener from './useHwListener';
+
+const { DEVICE_LIST_CHANGED, DEVICE_UPDATE } = IPC_MESSAGES;
+
+const mockDispatch = jest.fn();
+useDispatch.mockReturnValue(mockDispatch);
+
+describe('useIpc', () => {
+ const callbacks = {};
+ const ipc = {
+ on: jest.fn((event, callback) => {
+ callbacks[event] = callback;
+ }),
+ };
+
+ beforeEach(() => {
+ mockDispatch.mockClear();
+ window.ipc = ipc;
+ });
+
+ afterEach(() => {
+ delete window.ipc;
+ });
+
+ it('Should return undefined if no ipc on window', () => {
+ const { result, rerender } = renderHook(() => useHwListener());
+ act(() => {
+ delete window.ipc;
+ });
+ rerender();
+
+ expect(result.current).toBe(undefined);
+ });
+
+ it('Should dispatch setDeviceUpdated when ipc receives DEVICE_LIST_CHANGED', () => {
+ expect(ipc.on).toHaveBeenCalled();
+ const devices = [{ deviceId: '1' }];
+
+ callbacks[DEVICE_LIST_CHANGED]({}, devices);
+ expect(mockDispatch).toHaveBeenCalledWith(
+ setHardwareWalletDevices(devices)
+ );
+ });
+
+ it('Should dispatch setDeviceUpdated when ipc receives DEVICE_UPDATE', () => {
+ expect(ipc.on).toHaveBeenCalled();
+ const device = {deviceId: '1'};
+
+ callbacks[DEVICE_UPDATE]({}, device);
+ expect(mockDispatch).toHaveBeenCalledWith(
+ setCurrentDevice(device)
+ );
+ });
+});
diff --git a/src/modules/hardwareWallet/hooks/useManageHWAccounts.js b/src/modules/hardwareWallet/hooks/useManageHWAccounts.js
index 988ed166dd..4aec4d390c 100644
--- a/src/modules/hardwareWallet/hooks/useManageHWAccounts.js
+++ b/src/modules/hardwareWallet/hooks/useManageHWAccounts.js
@@ -1,7 +1,8 @@
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { selectSettings, selectHW } from 'src/redux/selectors';
-import { storeHWAccounts, removeHWAccounts } from '@hardwareWallet/store/actions/actions';
+import { selectSettings } from 'src/redux/selectors';
+import { setHWAccounts, removeHWAccounts } from '@hardwareWallet/store/actions';
+import { selectActiveHardwareDevice } from '@hardwareWallet/store/selectors/hwSelectors';
import { getNameFromAccount } from '../utils/getNameFromAccount';
import { getHWAccounts } from '../utils/getHWAccounts';
@@ -9,7 +10,7 @@ const useManageHWAccounts = () => {
const dispatch = useDispatch();
const settings = useSelector(selectSettings);
const [prevDeviceId, setCurrentDeviceId] = useState();
- const { deviceId, status } = useSelector(selectHW);
+ const { deviceId, status } = useSelector(selectActiveHardwareDevice);
const addAccounts = () => {
if (prevDeviceId === deviceId || status !== 'connected') {
@@ -18,7 +19,7 @@ const useManageHWAccounts = () => {
setCurrentDeviceId(deviceId);
getHWAccounts(getNameFromAccount, settings, deviceId)
.then((accList) => {
- dispatch(storeHWAccounts(accList));
+ dispatch(setHWAccounts(accList));
})
.catch((err) => console.log({ err }));
};
diff --git a/src/modules/hardwareWallet/hooks/useManageHWAccounts.test.js b/src/modules/hardwareWallet/hooks/useManageHWAccounts.test.js
index 462148fa20..0c5bdfa21e 100644
--- a/src/modules/hardwareWallet/hooks/useManageHWAccounts.test.js
+++ b/src/modules/hardwareWallet/hooks/useManageHWAccounts.test.js
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks';
import { useSelector } from 'react-redux';
-import { storeHWAccounts, removeHWAccounts } from '../store/actions/actions';
+import { setHWAccounts, removeHWAccounts } from '../store/actions';
import { hwAccounts } from '../__fixtures__/hwAccounts';
import { getHWAccounts } from '../utils/getHWAccounts';
import useManageHWAccounts from './useManageHWAccounts';
@@ -10,8 +10,10 @@ jest.useRealTimers();
const mockDispatch = jest.fn();
const mockAppState = {
hardwareWallet: {
- deviceId: 20231,
- status: 'connected',
+ currentDevice: {
+ deviceId: 20231,
+ status: 'connected',
+ },
},
settings: {
hardwareAccounts: {},
@@ -38,7 +40,7 @@ describe('useManageHWAccounts hook', () => {
await waitFor(() => {
expect(mockDispatch).toHaveBeenCalledTimes(1);
- const hwWalletAccountsDetails = storeHWAccounts(hwAccounts);
+ const hwWalletAccountsDetails = setHWAccounts(hwAccounts);
expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining(hwWalletAccountsDetails));
});
});
@@ -46,8 +48,10 @@ describe('useManageHWAccounts hook', () => {
it('removes the list of accounts when the device is disconnected', async () => {
const updatedMockAppState = {
hardwareWallet: {
- deviceId: 20231,
- status: 'disconnected',
+ currentDevice: {
+ deviceId: 20231,
+ status: 'disconnected',
+ },
accounts: hwAccounts,
},
settings: {
diff --git a/src/modules/hardwareWallet/store/actions/actions.js b/src/modules/hardwareWallet/store/actions/accountsActions.js
similarity index 62%
rename from src/modules/hardwareWallet/store/actions/actions.js
rename to src/modules/hardwareWallet/store/actions/accountsActions.js
index ddabf8d7db..b14ae7990e 100644
--- a/src/modules/hardwareWallet/store/actions/actions.js
+++ b/src/modules/hardwareWallet/store/actions/accountsActions.js
@@ -1,7 +1,7 @@
import actionTypes from './actionTypes';
-export const storeHWAccounts = (accounts) => ({
- type: actionTypes.storeHWAccounts,
+export const setHWAccounts = (accounts) => ({
+ type: actionTypes.setHWAccounts,
accounts,
});
diff --git a/src/modules/hardwareWallet/store/actions/actions.test.js b/src/modules/hardwareWallet/store/actions/accountsActions.test.js
similarity index 62%
rename from src/modules/hardwareWallet/store/actions/actions.test.js
rename to src/modules/hardwareWallet/store/actions/accountsActions.test.js
index 258f3303c3..edd5cc4077 100644
--- a/src/modules/hardwareWallet/store/actions/actions.test.js
+++ b/src/modules/hardwareWallet/store/actions/accountsActions.test.js
@@ -1,14 +1,14 @@
+import { hwAccounts } from '@hardwareWallet/__fixtures__/hwAccounts';
+import { setHWAccounts, removeHWAccounts } from './accountsActions';
import actionTypes from './actionTypes';
-import { storeHWAccounts, removeHWAccounts } from './actions';
-import { hwAccounts } from '../../__fixtures__/hwAccounts';
describe('actions: hardware wallet', () => {
it('stores the list of accounts', () => {
const expectedAction = {
- type: actionTypes.storeHWAccounts,
+ type: actionTypes.setHWAccounts,
accounts: hwAccounts,
};
- expect(storeHWAccounts(hwAccounts)).toEqual(expectedAction);
+ expect(setHWAccounts(hwAccounts)).toEqual(expectedAction);
});
it('removes the list of accounts', () => {
const expectedAction = {
diff --git a/src/modules/hardwareWallet/store/actions/actionTypes.js b/src/modules/hardwareWallet/store/actions/actionTypes.js
index 64009faa0a..6332ee07d7 100644
--- a/src/modules/hardwareWallet/store/actions/actionTypes.js
+++ b/src/modules/hardwareWallet/store/actions/actionTypes.js
@@ -1,6 +1,11 @@
+import { IPC_MESSAGES } from '@libs/hwServer/constants';
+
+const {DEVICE_LIST_CHANGED, DEVICE_UPDATE} = IPC_MESSAGES
const actionTypes = {
- storeHWAccounts: 'STORE_HW_ACCOUNTS',
- removeHWAccounts: 'REMOVE_HW_ACCOUNTS',
+ setHWAccounts: 'HW_ACCOUNTS_ADD',
+ removeHWAccounts: 'HW_ACCOUNTS_REMOVE',
+ setDevices: `HW_${DEVICE_LIST_CHANGED}`,
+ setCurrentDevice: `HW_${DEVICE_UPDATE}`,
};
export default actionTypes;
diff --git a/src/modules/hardwareWallet/store/actions/devicesActions.js b/src/modules/hardwareWallet/store/actions/devicesActions.js
new file mode 100644
index 0000000000..1bb6aa15cb
--- /dev/null
+++ b/src/modules/hardwareWallet/store/actions/devicesActions.js
@@ -0,0 +1,11 @@
+import actionTypes from '@hardwareWallet/store/actions/actionTypes';
+
+export const setHardwareWalletDevices = (devices) => ({
+ type: actionTypes.setDevices,
+ devices,
+});
+
+export const setCurrentDevice = ({ device }) => ({
+ type: actionTypes.setCurrentDevice,
+ device,
+});
diff --git a/src/modules/hardwareWallet/store/actions/devicesActions.test.js b/src/modules/hardwareWallet/store/actions/devicesActions.test.js
new file mode 100644
index 0000000000..fffbd898ed
--- /dev/null
+++ b/src/modules/hardwareWallet/store/actions/devicesActions.test.js
@@ -0,0 +1,28 @@
+import actionTypes from './actionTypes';
+import { setHardwareWalletDevices, setCurrentDevice } from './devicesActions';
+
+describe('hardwareWalletActions', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should create an action to update devices', () => {
+ const devices = [{ deviceId: '1' }];
+ const expectedAction = {
+ type: actionTypes.setDevices,
+ devices,
+ };
+
+ expect(setHardwareWalletDevices(devices)).toEqual(expectedAction);
+ });
+
+ it('should create an action to update device', () => {
+ const device = { deviceId: '1' };
+ const expectedAction = {
+ type: actionTypes.setCurrentDevice,
+ device,
+ };
+
+ expect(setCurrentDevice({ device })).toEqual(expectedAction);
+ });
+});
diff --git a/src/modules/hardwareWallet/store/actions/index.js b/src/modules/hardwareWallet/store/actions/index.js
new file mode 100644
index 0000000000..ebc04e0378
--- /dev/null
+++ b/src/modules/hardwareWallet/store/actions/index.js
@@ -0,0 +1,2 @@
+export * from './accountsActions'
+export * from './devicesActions'
diff --git a/src/modules/hardwareWallet/store/reducers/accountsReducers.js b/src/modules/hardwareWallet/store/reducers/accountsReducers.js
new file mode 100644
index 0000000000..c705707b58
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/accountsReducers.js
@@ -0,0 +1,14 @@
+import actionTypes from '../actions/actionTypes';
+
+const initAccounts = [];
+
+export const accounts = (state = initAccounts, {type, accounts: hwAccount= initAccounts}) => {
+ switch (type) {
+ case actionTypes.setHWAccounts:
+ return hwAccount;
+ case actionTypes.removeHWAccounts:
+ return initAccounts;
+ default:
+ return state;
+ }
+};
diff --git a/src/modules/hardwareWallet/store/reducers/reducers.test.js b/src/modules/hardwareWallet/store/reducers/accountsReducers.test.js
similarity index 65%
rename from src/modules/hardwareWallet/store/reducers/reducers.test.js
rename to src/modules/hardwareWallet/store/reducers/accountsReducers.test.js
index c80aec6b60..602847257f 100644
--- a/src/modules/hardwareWallet/store/reducers/reducers.test.js
+++ b/src/modules/hardwareWallet/store/reducers/accountsReducers.test.js
@@ -1,9 +1,9 @@
import { hwAccounts } from '../../__fixtures__/hwAccounts';
import actionTypes from '../actions/actionTypes';
-import hardwareWallet from './reducers';
+import { accounts } from './accountsReducers';
-describe('reducer: hardware wallet', () => {
- const testHWWallet = {
+describe('reducer: hardware wallet accounts', () => {
+ const testHWWalletAccount = {
hw: {
deviceId: '20231',
model: 'Nano S',
@@ -20,15 +20,15 @@ describe('reducer: hardware wallet', () => {
},
version: 1,
};
- const state = { deviceId: 0, status: 'disconnected', accounts: [] };
+ const state = [];
it('stores the list of accounts', () => {
const action = {
- type: actionTypes.storeHWAccounts,
- accounts: [...hwAccounts, testHWWallet],
+ type: actionTypes.setHWAccounts,
+ accounts: [...hwAccounts, testHWWalletAccount],
};
- const expectedData = { ...state, accounts: [...state.accounts, ...action.accounts] };
- const updatedState = hardwareWallet(state, action);
+ const expectedData = [...state, ...action.accounts];
+ const updatedState = accounts(state, action);
expect(updatedState).toEqual(expectedData);
});
@@ -36,7 +36,7 @@ describe('reducer: hardware wallet', () => {
const action = {
type: actionTypes.removeHWAccounts,
};
- const updatedState = hardwareWallet(state, action);
+ const updatedState = accounts(state, action);
expect(updatedState).toEqual(state);
});
@@ -44,7 +44,7 @@ describe('reducer: hardware wallet', () => {
const action = {
type: 'INVALID_ACTION',
};
- const updatedState = hardwareWallet(state, action);
+ const updatedState = accounts(state, action);
expect(updatedState).toEqual(state);
});
});
diff --git a/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.js b/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.js
new file mode 100644
index 0000000000..7505bb40b7
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.js
@@ -0,0 +1,25 @@
+import actionTypes from '@hardwareWallet/store/actions/actionTypes';
+
+export const initialState = {
+ deviceId: '',
+ model: '',
+ brand: '',
+ status: 'disconnected',
+};
+
+/**
+ *
+ * @param {Object} state
+ * @param {Object} action
+ */
+/* istanbul ignore next */
+export const currentDevice = (state = initialState, action) => {
+ const { type, device } = action;
+ switch (type) {
+ case actionTypes.setCurrentDevice: {
+ return device;
+ }
+ default:
+ return state;
+ }
+};
diff --git a/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.test.js b/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.test.js
new file mode 100644
index 0000000000..ebe0f221f7
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/currentDeviceReducer.test.js
@@ -0,0 +1,26 @@
+import actionTypes from '../actions/actionTypes';
+import { currentDevice, initialState } from './currentDeviceReducer';
+
+describe('HardwareWallet current device reducer', () => {
+
+ it('updates current device', () => {
+ const action = {
+ type: actionTypes.setCurrentDevice,
+ device: {
+ deviceId: '20231',
+ model: 'Nano S',
+ brand: 'Ledger',
+ }
+ };
+ const updatedState = currentDevice(initialState, action);
+ expect(updatedState).toEqual(action.device);
+ });
+
+ it('returns default state', () => {
+ const action = {
+ type: 'OTHER_ACTION_TYPE',
+ };
+ const updatedState = currentDevice(initialState, action);
+ expect(updatedState).toEqual(initialState);
+ });
+});
diff --git a/src/modules/hardwareWallet/store/reducers/devicesReducer.js b/src/modules/hardwareWallet/store/reducers/devicesReducer.js
new file mode 100644
index 0000000000..11c215612b
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/devicesReducer.js
@@ -0,0 +1,19 @@
+import actionTypes from '@hardwareWallet/store/actions/actionTypes';
+
+export const initialState = [];
+
+/**
+ *
+ * @param {Object} state
+ * @param {Object} action
+ */
+export const devices = (state = initialState, action) => {
+ const { type, devices: newDevices } = action;
+ switch (type) {
+ case actionTypes.setDevices: {
+ return newDevices;
+ }
+ default:
+ return state;
+ }
+};
diff --git a/src/modules/hardwareWallet/store/reducers/devicesReducer.test.js b/src/modules/hardwareWallet/store/reducers/devicesReducer.test.js
new file mode 100644
index 0000000000..a1353a0d72
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/devicesReducer.test.js
@@ -0,0 +1,21 @@
+import actionTypes from '@hardwareWallet/store/actions/actionTypes';
+import { devices, initialState } from './devicesReducer';
+
+describe('HardwareWallet reducer devices', () => {
+ it('Should update hardwareDevices when dispatching actionTypes.setDevices', async () => {
+ const updatedDevices = [
+ {
+ deviceId: '3',
+ },
+ ];
+ const actionData = {
+ type: actionTypes.setDevices,
+ devices: updatedDevices,
+ };
+ expect(devices(initialState, actionData)).toEqual(updatedDevices);
+ });
+
+ it('Should return default state', async () => {
+ expect(devices(initialState, {})).toEqual(initialState);
+ });
+});
diff --git a/src/modules/hardwareWallet/store/reducers/index.js b/src/modules/hardwareWallet/store/reducers/index.js
new file mode 100644
index 0000000000..1ffbe32512
--- /dev/null
+++ b/src/modules/hardwareWallet/store/reducers/index.js
@@ -0,0 +1,10 @@
+import { combineReducers } from 'redux';
+import { accounts } from './accountsReducers';
+import { currentDevice } from './currentDeviceReducer';
+import { devices } from './devicesReducer';
+
+export const hardwareWallet = combineReducers({
+ devices,
+ currentDevice,
+ accounts,
+});
diff --git a/src/modules/hardwareWallet/store/reducers/reducers.js b/src/modules/hardwareWallet/store/reducers/reducers.js
deleted file mode 100644
index 5d9158b248..0000000000
--- a/src/modules/hardwareWallet/store/reducers/reducers.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import actionTypes from '../actions/actionTypes';
-
-const initAccounts = [];
-const initState = {
- deviceId: 0,
- status: 'disconnected',
- accounts: initAccounts,
-};
-
-// istanbul ignore next
-const hardwareWallet = (state = initState, action) => {
- switch (action.type) {
- case actionTypes.storeHWAccounts:
- return {
- ...state,
- accounts: action.accounts,
- };
- case actionTypes.removeHWAccounts:
- return {
- ...state,
- accounts: initAccounts,
- };
- default:
- return state;
- }
-};
-
-export default hardwareWallet;
diff --git a/src/modules/hardwareWallet/store/selectors/hwSelectors.js b/src/modules/hardwareWallet/store/selectors/hwSelectors.js
new file mode 100644
index 0000000000..22f76e10be
--- /dev/null
+++ b/src/modules/hardwareWallet/store/selectors/hwSelectors.js
@@ -0,0 +1,7 @@
+export const selectHardwareDevices = (state) => state.hardwareWallet.devices;
+
+export const selectActiveHardwareDevice = (state) => state.hardwareWallet.currentDevice;
+export const selectActiveHardwareDeviceId = (state) => selectActiveHardwareDevice(state).deviceId;
+export const selectHWStatus = (state) => selectActiveHardwareDevice(state).status;
+
+export const selectHWAccounts = (state) => state.hardwareWallet?.accounts || [];
diff --git a/src/modules/hardwareWallet/store/selectors/hwSelectors.test.js b/src/modules/hardwareWallet/store/selectors/hwSelectors.test.js
new file mode 100644
index 0000000000..eb2ec3bf2a
--- /dev/null
+++ b/src/modules/hardwareWallet/store/selectors/hwSelectors.test.js
@@ -0,0 +1,32 @@
+import {
+ selectActiveHardwareDevice,
+ selectActiveHardwareDeviceId,
+ selectHardwareDevices,
+ selectHWStatus,
+} from './hwSelectors';
+
+describe('HardwareWallet selectors', () => {
+ const mockState = {
+ hardwareWallet: {
+ devices: [
+ { deviceId: '1', status: 'connected' },
+ { deviceId: '2', status: 'connected' },
+ ],
+ currentDevice: { deviceId: '1', status: 'connected' },
+ },
+ };
+ it('Should select ActiveHardwareDevice', async () => {
+ expect(selectActiveHardwareDevice(mockState)).toEqual(mockState.hardwareWallet.currentDevice);
+ });
+ it('Should select ActiveHardware DeviceId', async () => {
+ expect(selectActiveHardwareDeviceId(mockState)).toEqual(
+ mockState.hardwareWallet.currentDevice.deviceId
+ );
+ });
+ it('Should select HardwareDevices', async () => {
+ expect(selectHardwareDevices(mockState)).toEqual(mockState.hardwareWallet.devices);
+ });
+ it('Should select active hardware wallet status', async () => {
+ expect(selectHWStatus(mockState)).toEqual(mockState.hardwareWallet.currentDevice.status);
+ });
+});
diff --git a/src/modules/wallet/store/middlewares/hwManager.js b/src/modules/wallet/store/middlewares/hwManager.js
index 577952f5af..5e7d087db3 100644
--- a/src/modules/wallet/store/middlewares/hwManager.js
+++ b/src/modules/wallet/store/middlewares/hwManager.js
@@ -1,8 +1,11 @@
+import React from 'react';
import { toast } from 'react-toastify';
import { subscribeToDeviceConnected, subscribeToDeviceDisconnected } from '@wallet/utils/hwManager';
import { addSearchParamsToUrl } from 'src/utils/searchParams';
import history from 'src/utils/history';
import actionTypes from 'src/modules/common/store/actionTypes';
+import i18n from 'i18next';
+import DialogLink from '@theme/dialog/link';
const hwWalletMiddleware = store => next => (action) => {
const { ipc } = window;
@@ -15,7 +18,16 @@ const hwWalletMiddleware = store => next => (action) => {
* @param {fn} function - callback function to execute toast dispatch after receive the data
*/
subscribeToDeviceConnected((response) => {
- toast.success(`${response.model} connected`);
+ const { devices, model } = response;
+ toast.success(`${model} connected`);
+
+ if (devices?.length > 1) {
+ toast.info(
+
+ {i18n.t('Click here to select a hardware wallet')}
+
+ );
+ }
});
/**
diff --git a/src/redux/rootReducer.js b/src/redux/rootReducer.js
index c0d7e08a42..cbd2078c76 100644
--- a/src/redux/rootReducer.js
+++ b/src/redux/rootReducer.js
@@ -6,6 +6,7 @@ export { default as token } from '@token/fungible/store/reducer';
export { default as transactions } from '@transaction/store/reducer';
export { default as appUpdates } from '@update/store/reducers/appUpdates';
export { default as staking } from '@pos/validator/store/reducers/staking';
+export { hardwareWallet } from '@hardwareWallet/store/reducers';
export { default as watchList } from '@pos/validator/store/reducers/watchList';
export { account } from '@account/store/reducer';
export { blockChainApplications } from '@blockchainApplication/manage/store/reducer';
diff --git a/src/redux/selectors.js b/src/redux/selectors.js
index f5c196a01e..53449ed6f2 100644
--- a/src/redux/selectors.js
+++ b/src/redux/selectors.js
@@ -27,9 +27,6 @@ const selectNetworkIdentifier = (state) => state.network.networks?.LSK?.networkI
const selectNetworkName = (state) => state.network.name;
const selectActiveTokenNetwork = (state) => state.network.networks[state.token.active];
const selectStaking = (state) => state.staking;
-const selectHW = (state) => state.hardwareWallet;
-const selectHWAccounts = (state) => state.hardwareWallet?.accounts || [];
-const selectHWStatus = (state) => state.hardwareWallet.status;
export {
selectStaking,
@@ -49,7 +46,4 @@ export {
selectNetworkIdentifier,
selectNetworkName,
selectActiveTokenNetwork,
- selectHW,
- selectHWAccounts,
- selectHWStatus,
};
diff --git a/src/routes/routes.js b/src/routes/routes.js
index 8e9f7bdfb4..9d3db7d46b 100644
--- a/src/routes/routes.js
+++ b/src/routes/routes.js
@@ -195,6 +195,10 @@ export const modals = {
isPrivate: true,
forbiddenTokens: [],
},
+ selectHardwareDeviceModal: {
+ isPrivate: true,
+ forbiddenTokens: [],
+ },
stakingQueue: {
isPrivate: true,
forbiddenTokens: [],
diff --git a/src/routes/routesMap.js b/src/routes/routesMap.js
index 896ea1f8d8..65a5a097a0 100644
--- a/src/routes/routesMap.js
+++ b/src/routes/routesMap.js
@@ -56,6 +56,7 @@ import SessionManager from '@blockchainApplication/connection/components/Session
import ConnectionSummary from 'src/modules/blockchainApplication/connection/components/ConnectionSummary';
import RequestView from '@blockchainApplication/connection/components/RequestView';
import ConnectionStatus from 'src/modules/blockchainApplication/connection/components/ConnectionStatus';
+import SelectHardwareDeviceModal from 'src/modules/hardwareWallet/components/SelectHardwareDeviceModal';
export default {
wallet: AccountDetails,
@@ -89,6 +90,7 @@ export default {
lockedBalance: UnlockBalanceView,
claimRewardsView: ClaimRewardsView,
editStake: editStakeManager,
+ selectHardwareDeviceModal: SelectHardwareDeviceModal,
stakingQueue: StakingQueue,
deviceDisconnectDialog: DeviceDisconnect,
reclaimBalance: ReclaimBalanceModal,