From cfc653ada651cbcf76e9121a9c95d29cfdb2b202 Mon Sep 17 00:00:00 2001 From: Nidhi Kumari Date: Thu, 4 May 2023 16:44:07 +0530 Subject: [PATCH] eth_sign toggle Update in advanced settings (#18848) * added eth sign first step * added modal * added validation for form * updated width with block * added state trigger for toggle * updated Eth sign modal text changes * added eth sign toggle tex * removed unnecessary code * fixed form validation text * updated eth toggle text * added test * added analytics * updated design changes * lint fix * updated error text * updated changes --- app/_locales/en/messages.json | 37 +++- .../__snapshots__/eth-sign-modal.test.js.snap | 93 +++++++++ .../modals/eth-sign-modal/eth-sign-modal.js | 197 ++++++++++++++++++ .../eth-sign-modal/eth-sign-modal.stories.js | 9 + .../eth-sign-modal/eth-sign-modal.test.js | 44 ++++ .../app/modals/eth-sign-modal/index.js | 1 + .../app/modals/eth-sign-modal/index.scss | 8 + ui/components/app/modals/index.scss | 1 + ui/components/app/modals/modal.js | 14 ++ .../advanced-tab/advanced-tab.component.js | 48 ++++- .../advanced-tab/advanced-tab.container.js | 1 + .../advanced-tab/advanced-tab.stories.js | 3 + ui/pages/settings/index.scss | 4 + ui/selectors/selectors.js | 4 + 14 files changed, 453 insertions(+), 11 deletions(-) create mode 100644 ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap create mode 100644 ui/components/app/modals/eth-sign-modal/eth-sign-modal.js create mode 100644 ui/components/app/modals/eth-sign-modal/eth-sign-modal.stories.js create mode 100644 ui/components/app/modals/eth-sign-modal/eth-sign-modal.test.js create mode 100644 ui/components/app/modals/eth-sign-modal/index.js create mode 100644 ui/components/app/modals/eth-sign-modal/index.scss diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 90b3245e4..ab3ffae0c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -4449,11 +4449,44 @@ "message": "To: $1", "description": "$1 is the address to include in the To label. It is typically shortened first using shortenAddress" }, + "toggleEthSignBannerDescription": { + "message": "You’re at risk for phishing attacks. Protect yourself by turning off eth_sign." + }, "toggleEthSignDescriptionField": { - "message": "Turn this on to let dapps request your signature using eth_sign requests. eth_sign is an open-ended signing method that lets you sign an arbitrary hash, making it a dangerous phishing risk. Only sign eth_sign requests if you can read what you are signing and trust the origin of the request." + "message": "If you enable this setting, you might get signature requests that aren’t readable. By signing a message you don't understand, you could be agreeing to give away your funds and NFTs." }, "toggleEthSignField": { - "message": "Toggle eth_sign requests" + "message": "Eth_sign requests" + }, + "toggleEthSignModalBannerBoldText": { + "message": " you might be getting scammed" + }, + "toggleEthSignModalBannerText": { + "message": "If you've been asked to turn this setting on," + }, + "toggleEthSignModalCheckBox": { + "message": "I understand that I can lose all of my funds and NFTs if I enable eth_sign requests. " + }, + "toggleEthSignModalDescription": { + "message": "Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code." + }, + "toggleEthSignModalFormError": { + "message": "The text is incorrect" + }, + "toggleEthSignModalFormLabel": { + "message": "Enter “I only sign what I understand” to continue" + }, + "toggleEthSignModalFormValidation": { + "message": "I only sign what I understand" + }, + "toggleEthSignModalTitle": { + "message": "Use at your own risk" + }, + "toggleEthSignOff": { + "message": "OFF (Recommended)" + }, + "toggleEthSignOn": { + "message": "ON (Not recommended)" }, "toggleTestNetworks": { "message": "$1 test networks", diff --git a/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap new file mode 100644 index 000000000..cafb98ff3 --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/__snapshots__/eth-sign-modal.test.js.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Eth Sign Modal should match snapshot 1`] = ` +
+
+
+ + +
+

+ Use at your own risk +

+

+ Allowing eth_sign requests can make you vulnerable to phishing attacks. Always review the URL and be careful when signing messages that contain code. + + Learn more + +

+
+ +
+ If you've been asked to turn this setting on, + you might be getting scammed +
+
+
+ + +
+
+ + +
+
+
+`; diff --git a/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js new file mode 100644 index 000000000..e6d028064 --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.js @@ -0,0 +1,197 @@ +import React, { useContext, useState } from 'react'; +import PropTypes from 'prop-types'; +import { useSelector, useDispatch } from 'react-redux'; +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'; +import Box from '../../../ui/box'; +import { + BannerAlert, + ButtonIcon, + ButtonLink, + ButtonPrimary, + ButtonSecondary, + FormTextField, + Icon, + IconName, + IconSize, + Label, + Text, +} from '../../../component-library'; +import { + AlignItems, + DISPLAY, + FLEX_DIRECTION, + IconColor, + JustifyContent, + SEVERITIES, + Size, + TextAlign, + TextVariant, +} from '../../../../helpers/constants/design-system'; +import { useI18nContext } from '../../../../hooks/useI18nContext'; +import CheckBox from '../../../ui/check-box'; +import { setDisabledRpcMethodPreference } from '../../../../store/actions'; +import { getDisabledRpcMethodPreferences } from '../../../../selectors'; +import { + MetaMetricsEventCategory, + MetaMetricsEventName, +} from '../../../../../shared/constants/metametrics'; +import { MetaMetricsContext } from '../../../../contexts/metametrics'; + +const EthSignModal = ({ hideModal }) => { + const [isEthSignChecked, setIsEthSignChecked] = useState(false); + const [showTextField, setShowTextField] = useState(false); + const [inputKeyword, setInputKeyword] = useState(''); + const disabledRpcMethodPreferences = useSelector( + getDisabledRpcMethodPreferences, + ); + + const t = useI18nContext(); + const dispatch = useDispatch(); + const trackEvent = useContext(MetaMetricsContext); + + const handleSubmit = () => { + dispatch( + setDisabledRpcMethodPreference( + 'eth_sign', + !disabledRpcMethodPreferences.eth_sign, + ), + ); + hideModal(); + }; + + const isValid = inputKeyword === t('toggleEthSignModalFormValidation'); + return ( + + + + hideModal()} + ariaLabel={t('close')} + /> + + + + {t('toggleEthSignModalTitle')} + + + {t('toggleEthSignModalDescription')} + + {t('learnMoreUpperCase')} + + + + {t('toggleEthSignModalBannerText')} + {t('toggleEthSignModalBannerBoldText')} + + {showTextField ? ( + 0 && !isValid} + helpText={ + inputKeyword.length > 0 && + !isValid && + t('toggleEthSignModalFormError') + } + onChange={(event) => setInputKeyword(event.target.value)} + value={inputKeyword} + onPaste={(event) => event.preventDefault()} + /> + ) : ( + + { + setIsEthSignChecked(!isEthSignChecked); + }} + /> + + + )} + + hideModal()} size={Size.LG} block> + {t('cancel')} + + {showTextField ? ( + + {t('enableSnap')} + + ) : ( + { + setShowTextField(true); + trackEvent({ + category: MetaMetricsEventCategory.Settings, + event: MetaMetricsEventName.OnboardingWalletAdvancedSettings, + properties: { + location: 'Settings', + enable_eth_sign: true, + }, + }); + }} + > + {t('continue')} + + )} + + + ); +}; + +EthSignModal.propTypes = { + // The function to close the Modal + hideModal: PropTypes.func, +}; +export default withModalProps(EthSignModal); diff --git a/ui/components/app/modals/eth-sign-modal/eth-sign-modal.stories.js b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.stories.js new file mode 100644 index 000000000..bca429eab --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.stories.js @@ -0,0 +1,9 @@ +import React from 'react'; +import EthSignModal from './eth-sign-modal'; + +export default { + title: 'Components/App/Modals/EthSignModal', + component: EthSignModal, +}; + +export const DefaultStory = (args) => ; diff --git a/ui/components/app/modals/eth-sign-modal/eth-sign-modal.test.js b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.test.js new file mode 100644 index 000000000..98012777e --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/eth-sign-modal.test.js @@ -0,0 +1,44 @@ +import React from 'react'; +import configureMockStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import { renderWithProvider } from '../../../../../test/lib/render-helpers'; +import EthSignModal from './eth-sign-modal'; + +const mockHideModal = jest.fn(); +const mockGetDisabledRpcMethodPreferences = jest.fn(); + +jest.mock('../../../../store/actions.ts', () => ({ + ...jest.requireActual('../../../../store/actions.ts'), + hideModal: () => mockHideModal, + getDisabledRpcMethodPreferences: () => mockGetDisabledRpcMethodPreferences, +})); + +describe('Eth Sign Modal', () => { + const mockState = { + appState: { + modal: { + modalState: { + props: {}, + }, + }, + }, + metamask: { + provider: { + type: 'rpc', + chainId: '0x5', + }, + disabledRpcMethodPreferences: { eth_sign: true }, + }, + }; + + const mockStore = configureMockStore([thunk])(mockState); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should match snapshot', () => { + const { container } = renderWithProvider(, mockStore); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/ui/components/app/modals/eth-sign-modal/index.js b/ui/components/app/modals/eth-sign-modal/index.js new file mode 100644 index 000000000..cdb9572b1 --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/index.js @@ -0,0 +1 @@ +export { default } from './eth-sign-modal'; diff --git a/ui/components/app/modals/eth-sign-modal/index.scss b/ui/components/app/modals/eth-sign-modal/index.scss new file mode 100644 index 000000000..9af2b568c --- /dev/null +++ b/ui/components/app/modals/eth-sign-modal/index.scss @@ -0,0 +1,8 @@ +.eth-sign-modal { + position: relative; + + &__close { + position: absolute; + right: 16px; + } +} diff --git a/ui/components/app/modals/index.scss b/ui/components/app/modals/index.scss index 9c8f8a03f..00d6391ad 100644 --- a/ui/components/app/modals/index.scss +++ b/ui/components/app/modals/index.scss @@ -12,6 +12,7 @@ @import 'convert-token-to-nft-modal/index'; @import 'contract-details-modal/index'; @import 'hold-to-reveal-modal/index'; +@import 'eth-sign-modal/index'; .modal { z-index: 1050; diff --git a/ui/components/app/modals/modal.js b/ui/components/app/modals/modal.js index d543ddd4c..2c8aed6a6 100644 --- a/ui/components/app/modals/modal.js +++ b/ui/components/app/modals/modal.js @@ -27,6 +27,8 @@ import NewAccountModal from './new-account-modal'; import CustomizeNonceModal from './customize-nonce'; import ConvertTokenToNftModal from './convert-token-to-nft-modal/convert-token-to-nft-modal'; +import EthSignModal from './eth-sign-modal/eth-sign-modal'; + const modalContainerBaseStyle = { transform: 'translate3d(-50%, 0, 0px)', border: '1px solid var(--color-border-default)', @@ -160,6 +162,18 @@ const MODALS = { }, }, + ETH_SIGN: { + contents: , + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, CONFIRM_REMOVE_ACCOUNT: { contents: , mobileModalStyle: { diff --git a/ui/pages/settings/advanced-tab/advanced-tab.component.js b/ui/pages/settings/advanced-tab/advanced-tab.component.js index 2ad633a9e..837eb0d0d 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.component.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.component.js @@ -25,6 +25,11 @@ import { import { exportAsFile } from '../../../helpers/utils/export-utils'; import ActionableMessage from '../../../components/ui/actionable-message'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; +import { BannerAlert } from '../../../components/component-library'; +import { + SEVERITIES, + TextVariant, +} from '../../../helpers/constants/design-system'; const CORRUPT_JSON_FILE = 'CORRUPT_JSON_FILE'; @@ -40,6 +45,7 @@ export default class AdvancedTab extends PureComponent { setHexDataFeatureFlag: PropTypes.func, displayWarning: PropTypes.func, showResetAccountConfirmationModal: PropTypes.func, + showEthSignModal: PropTypes.func, warning: PropTypes.string, sendHexData: PropTypes.bool, showFiatInTestnets: PropTypes.bool, @@ -539,10 +545,23 @@ export default class AdvancedTab extends PureComponent { } renderToggleEthSignControl() { - const { t } = this.context; - const { disabledRpcMethodPreferences, setDisabledRpcMethodPreference } = - this.props; - + const { t, trackEvent } = this.context; + const { + disabledRpcMethodPreferences, + showEthSignModal, + setDisabledRpcMethodPreference, + } = this.props; + const toggleOff = (value) => { + setDisabledRpcMethodPreference('eth_sign', !value); + trackEvent({ + category: MetaMetricsEventCategory.Settings, + event: MetaMetricsEventName.OnboardingWalletAdvancedSettings, + properties: { + location: 'Settings', + enable_eth_sign: false, + }, + }); + }; return (
+ + {disabledRpcMethodPreferences?.eth_sign === true ? ( + + {t('toggleEthSignBannerDescription')} + + ) : null}
- setDisabledRpcMethodPreference('eth_sign', !value) - } - offLabel={t('off')} - onLabel={t('on')} + onToggle={(value) => { + value ? toggleOff(value) : showEthSignModal(); + }} + offLabel={t('toggleEthSignOff')} + onLabel={t('toggleEthSignOn')} />
diff --git a/ui/pages/settings/advanced-tab/advanced-tab.container.js b/ui/pages/settings/advanced-tab/advanced-tab.container.js index 72f495263..539e60b33 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.container.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.container.js @@ -68,6 +68,7 @@ export const mapDispatchToProps = (dispatch) => { displayWarning: (warning) => dispatch(displayWarning(warning)), showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })), + showEthSignModal: () => dispatch(showModal({ name: 'ETH_SIGN' })), setUseNonceField: (value) => dispatch(setUseNonceField(value)), setShowFiatConversionOnTestnetsPreference: (value) => { return dispatch(setShowFiatConversionOnTestnetsPreference(value)); diff --git a/ui/pages/settings/advanced-tab/advanced-tab.stories.js b/ui/pages/settings/advanced-tab/advanced-tab.stories.js index a6c3f7eb2..b15bb9edb 100644 --- a/ui/pages/settings/advanced-tab/advanced-tab.stories.js +++ b/ui/pages/settings/advanced-tab/advanced-tab.stories.js @@ -27,6 +27,9 @@ export default { showResetAccountConfirmationModal: { action: 'showResetAccountConfirmationModal', }, + showEthSignModal: { + action: 'showEthSignModal', + }, }, }; diff --git a/ui/pages/settings/index.scss b/ui/pages/settings/index.scss index 797a0fb2e..31c48c96d 100644 --- a/ui/pages/settings/index.scss +++ b/ui/pages/settings/index.scss @@ -379,6 +379,10 @@ max-width: 100%; width: 100%; } + + .eth-sign-toggle .toggle-button__status { // for eth_sign we need to override the uppercase property of toggle button text + text-transform: capitalize; + } } &__content-item-col-open-sea { diff --git a/ui/selectors/selectors.js b/ui/selectors/selectors.js index 0ce664281..e8c54687d 100644 --- a/ui/selectors/selectors.js +++ b/ui/selectors/selectors.js @@ -585,6 +585,10 @@ export function getShowTestNetworks(state) { return Boolean(showTestNetworks); } +export function getDisabledRpcMethodPreferences(state) { + return state.metamask.disabledRpcMethodPreferences; +} + export function getShouldShowFiat(state) { const isMainNet = getIsMainnet(state); const isCustomNetwork = getIsCustomNetwork(state);