diff --git a/test/e2e/tests/eth-sign.spec.js b/test/e2e/tests/eth-sign.spec.js index 26d585ea5..682cb9f5e 100644 --- a/test/e2e/tests/eth-sign.spec.js +++ b/test/e2e/tests/eth-sign.spec.js @@ -136,7 +136,7 @@ describe('Eth sign', function () { await driver.waitForSelector({ text: 'Reject 2 requests', - tag: 'a', + tag: 'button', }); await verifyAndAssertEthSign(driver, DAPP_URL, expectedEthSignMessage); diff --git a/test/e2e/tests/personal-sign.spec.js b/test/e2e/tests/personal-sign.spec.js index 10e37a7c7..780d84796 100644 --- a/test/e2e/tests/personal-sign.spec.js +++ b/test/e2e/tests/personal-sign.spec.js @@ -102,7 +102,7 @@ describe('Personal sign', function () { await driver.waitForSelector({ text: 'Reject 2 requests', - tag: 'a', + tag: 'button', }); const personalMessageRow = await driver.findElement( diff --git a/test/e2e/tests/signature-request.spec.js b/test/e2e/tests/signature-request.spec.js index 9ef703389..375f643be 100644 --- a/test/e2e/tests/signature-request.spec.js +++ b/test/e2e/tests/signature-request.spec.js @@ -160,7 +160,7 @@ describe('Sign Typed Data Signature Request', function () { await driver.waitForSelector({ text: 'Reject 2 requests', - tag: 'a', + tag: 'button', }); await verifyAndAssertSignTypedData( diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js index 6dc18e4a4..202195d8c 100644 --- a/ui/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/components/app/signature-request-original/signature-request-original.component.js @@ -14,7 +14,6 @@ import { } from '../../../helpers/utils/util'; import { stripHexPrefix } from '../../../../shared/modules/hexstring-utils'; import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; -import Button from '../../ui/button'; import SiteOrigin from '../../ui/site-origin'; import Typography from '../../ui/typography/typography'; import { PageContainerFooter } from '../../ui/page-container'; @@ -23,6 +22,7 @@ import { FontWeight, TextAlign, TextColor, + Size, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) IconColor, DISPLAY, @@ -31,12 +31,20 @@ import { BackgroundColor, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/design-system'; -import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; -import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; +import { + ButtonLink, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + Icon, + IconName, + Text, + ///: END:ONLY_INCLUDE_IN +} from '../../component-library'; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) -import { Icon, IconName, Text } from '../../component-library'; import Box from '../../ui/box/box'; ///: END:ONLY_INCLUDE_IN +import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; +import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; + import SignatureRequestHeader from '../signature-request-header'; import SignatureRequestOriginalWarning from './signature-request-original-warning'; @@ -366,13 +374,13 @@ export default class SignatureRequestOriginal extends Component { )} {this.renderFooter()} {messagesCount > 1 ? ( - + ) : null} ); diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js index ddd4319e5..e036cc414 100644 --- a/ui/components/app/signature-request-siwe/signature-request-siwe.js +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.js @@ -50,7 +50,6 @@ export default function SignatureRequestSIWE({ txData }) { const t = useContext(I18nContext); const allAccounts = useSelector(accountsWithSendEtherInfoSelector); const subjectMetadata = useSelector(getSubjectMetadata); - const messagesCount = useSelector(getTotalUnapprovedMessagesCount); const messagesList = useSelector(unconfirmedMessagesHashSelector); const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); diff --git a/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap similarity index 91% rename from ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap rename to ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap index 0f6778004..6e14a4b50 100644 --- a/ui/components/app/signature-request/__snapshots__/signature-request.component.test.js.snap +++ b/ui/components/app/signature-request/__snapshots__/signature-request.test.js.snap @@ -41,7 +41,7 @@ exports[`Signature Request Component render should match snapshot when we are us of - 1 + 0
@@ -144,12 +144,12 @@ exports[`Signature Request Component render should match snapshot when we are us
- Unknown private network + Localhost 8545
- Antonio + John Doe
@@ -165,9 +165,9 @@ exports[`Signature Request Component render should match snapshot when we are us align="end" class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default" > - 966.987986 + 0 - ABC + ETH @@ -179,56 +179,48 @@ exports[`Signature Request Component render should match snapshot when we are us class="signature-request__origin" >
-
-
- - T - -
-
- test - + class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default" + style="mask-image: url('./images/icons/global.svg');" + />
+

+ test +

-

Signature request -

+
Only sign this message if you fully understand the content and trust the requesting site.
- -
- Verify third-party details -
-
+
+ Verify third-party details +
+ +
+ `; @@ -818,7 +816,7 @@ exports[`Signature Request Component render should match snapshot when we want t of - 1 + 0
@@ -921,12 +919,12 @@ exports[`Signature Request Component render should match snapshot when we want t
- Unknown private network + Localhost 8545
- Antonio + John Doe
@@ -942,9 +940,9 @@ exports[`Signature Request Component render should match snapshot when we want t align="end" class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default" > - 1515270.174798 + 0 - DEF + ETH @@ -956,56 +954,48 @@ exports[`Signature Request Component render should match snapshot when we want t class="signature-request__origin" >
-
-
- - T - -
-
- test - + class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default" + style="mask-image: url('./images/icons/global.svg');" + />
+

+ test +

-

Signature request -

+
Only sign this message if you fully understand the content and trust the requesting site.
- -
- Verify third-party details -
-
+
+ Verify third-party details +
+ +
+ `; diff --git a/ui/components/app/signature-request/index.js b/ui/components/app/signature-request/index.js index 8a01d820e..18ba8b157 100644 --- a/ui/components/app/signature-request/index.js +++ b/ui/components/app/signature-request/index.js @@ -1 +1 @@ -export { default } from './signature-request.container'; +export { default } from './signature-request'; diff --git a/ui/components/app/signature-request/index.scss b/ui/components/app/signature-request/index.scss index 5cc2a0981..5467f2a84 100644 --- a/ui/components/app/signature-request/index.scss +++ b/ui/components/app/signature-request/index.scss @@ -14,7 +14,7 @@ border-radius: 8px; @include screen-sm-min { - max-height: 80vh; + max-height: max-content; min-height: 570px; flex: 0 0 auto; margin-left: auto; @@ -22,7 +22,7 @@ } &__reject-all-button { - margin-top: -15px; + padding-bottom: 20px; } &__origin { @@ -58,12 +58,6 @@ font-weight: 500; } - - p { - @include H6; - - color: var(--color-text-muted); - } } a.signature-request-content__verify-contract-details { diff --git a/ui/components/app/signature-request/signature-request.component.js b/ui/components/app/signature-request/signature-request.component.js deleted file mode 100644 index c0358b99d..000000000 --- a/ui/components/app/signature-request/signature-request.component.js +++ /dev/null @@ -1,427 +0,0 @@ -import React, { PureComponent } from 'react'; -import { memoize } from 'lodash'; -import PropTypes from 'prop-types'; -import { ethErrors, serializeError } from 'eth-rpc-errors'; -import LedgerInstructionField from '../ledger-instruction-field'; -import { - sanitizeMessage, - getURLHostName, - getNetworkNameFromProviderType, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - shortenAddress, - ///: END:ONLY_INCLUDE_IN -} from '../../../helpers/utils/util'; -import { - MetaMetricsEventCategory, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - MetaMetricsEventName, - ///: END:ONLY_INCLUDE_IN -} from '../../../../shared/constants/metametrics'; -import SiteOrigin from '../../ui/site-origin'; -import Button from '../../ui/button'; -import ContractDetailsModal from '../modals/contract-details-modal/contract-details-modal'; -import { - TextAlign, - TextColor, - TextVariant, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - IconColor, - Display, - BlockSize, - BackgroundColor, - ///: END:ONLY_INCLUDE_IN -} from '../../../helpers/constants/design-system'; -import NetworkAccountBalanceHeader from '../network-account-balance-header'; -import { Numeric } from '../../../../shared/modules/Numeric'; -import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; -import { EtherDenomination } from '../../../../shared/constants/common'; -import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; -import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; -import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; -import { getValueFromWeiHex } from '../../../../shared/modules/conversion.utils'; - -import { - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - Box, - Icon, - IconName, - ///: END:ONLY_INCLUDE_IN - Text, -} from '../../component-library'; -import Footer from './signature-request-footer'; -import Message from './signature-request-message'; - -export default class SignatureRequest extends PureComponent { - static propTypes = { - /** - * The display content of transaction data - */ - txData: PropTypes.object.isRequired, - /** - * The display content of sender account - */ - fromAccount: PropTypes.shape({ - address: PropTypes.string.isRequired, - balance: PropTypes.string, - name: PropTypes.string, - }).isRequired, - /** - * Check if the wallet is ledget wallet or not - */ - isLedgerWallet: PropTypes.bool, - - /** - * Whether the hardware wallet requires a connection disables the sign button if true. - */ - hardwareWalletRequiresConnection: PropTypes.bool.isRequired, - /** - * Current network chainId - */ - chainId: PropTypes.string, - /** - * RPC prefs of the current network - */ - rpcPrefs: PropTypes.object, - nativeCurrency: PropTypes.string, - currentCurrency: PropTypes.string.isRequired, - conversionRate: PropTypes.number, - providerConfig: PropTypes.object, - subjectMetadata: PropTypes.object, - unapprovedMessagesCount: PropTypes.number, - clearConfirmTransaction: PropTypes.func.isRequired, - history: PropTypes.object, - mostRecentOverviewPage: PropTypes.string, - showRejectTransactionsConfirmationModal: PropTypes.func.isRequired, - cancelAllApprovals: PropTypes.func.isRequired, - resolvePendingApproval: PropTypes.func.isRequired, - rejectPendingApproval: PropTypes.func.isRequired, - completedTx: PropTypes.func.isRequired, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - showCustodianDeepLink: PropTypes.func, - isNotification: PropTypes.bool, - mmiOnSignCallback: PropTypes.func, - // Used to show a warning if the signing account is not the selected account - // Largely relevant for contract wallet custodians - selectedAccount: PropTypes.object, - ///: END:ONLY_INCLUDE_IN - }; - - static contextTypes = { - t: PropTypes.func, - trackEvent: PropTypes.func, - }; - - state = { - hasScrolledMessage: false, - showContractDetails: false, - }; - - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - componentDidMount() { - if (this.props.txData.custodyId) { - this.props.showCustodianDeepLink({ - custodyId: this.props.txData.custodyId, - fromAddress: this.props.fromAccount.address, - closeNotification: this.props.isNotification, - onDeepLinkFetched: () => undefined, - onDeepLinkShown: () => { - this.context.trackEvent({ - category: MetaMetricsEventCategory.MMI, - event: MetaMetricsEventName.SignatureDeeplinkDisplayed, - }); - }, - }); - } - } - ///: END:ONLY_INCLUDE_IN - - setMessageRootRef(ref) { - this.messageRootRef = ref; - } - - formatWallet(wallet) { - return `${wallet.slice(0, 8)}...${wallet.slice( - wallet.length - 8, - wallet.length, - )}`; - } - - memoizedParseMessage = memoize((data) => { - const { message, domain = {}, primaryType, types } = JSON.parse(data); - const sanitizedMessage = sanitizeMessage(message, primaryType, types); - return { sanitizedMessage, domain, primaryType }; - }); - - handleCancelAll = () => { - const { - clearConfirmTransaction, - history, - mostRecentOverviewPage, - showRejectTransactionsConfirmationModal, - unapprovedMessagesCount, - cancelAllApprovals, - } = this.props; - - showRejectTransactionsConfirmationModal({ - unapprovedTxCount: unapprovedMessagesCount, - onSubmit: async () => { - await cancelAllApprovals(); - clearConfirmTransaction(); - history.push(mostRecentOverviewPage); - }, - }); - }; - - render() { - const { - providerConfig, - txData: { - msgParams: { data, origin, version }, - type, - id, - }, - fromAccount: { address, balance, name }, - isLedgerWallet, - hardwareWalletRequiresConnection, - chainId, - rpcPrefs, - txData, - subjectMetadata, - nativeCurrency, - currentCurrency, - conversionRate, - unapprovedMessagesCount, - resolvePendingApproval, - rejectPendingApproval, - completedTx, - } = this.props; - - const { t, trackEvent } = this.context; - const { - sanitizedMessage, - domain: { verifyingContract }, - primaryType, - } = this.memoizedParseMessage(data); - const rejectNText = t('rejectRequestsN', [unapprovedMessagesCount]); - const networkName = getNetworkNameFromProviderType(providerConfig.type); - const currentNetwork = - networkName === '' - ? providerConfig.nickname || t('unknownNetwork') - : t(networkName); - - const balanceInBaseAsset = conversionRate - ? formatCurrency( - getValueFromWeiHex({ - value: balance, - fromCurrency: nativeCurrency, - toCurrency: currentCurrency, - conversionRate, - numberOfDecimals: 6, - toDenomination: EtherDenomination.ETH, - }), - currentCurrency, - ) - : new Numeric(balance, 16, EtherDenomination.WEI) - .toDenomination(EtherDenomination.ETH) - .round(6) - .toBase(10) - .toString(); - - const onSign = async () => { - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - if (this.props.mmiOnSignCallback) { - await this.props.mmiOnSignCallback(txData); - return; - } - ///: END:ONLY_INCLUDE_IN - - await resolvePendingApproval(id); - completedTx(id); - - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: 'Confirm', - properties: { - action: 'Sign Request', - legacy_event: true, - type, - version, - }, - }); - }; - - const onCancel = async () => { - await rejectPendingApproval( - id, - serializeError(ethErrors.provider.userRejectedRequest()), - ); - trackEvent({ - category: MetaMetricsEventCategory.Transactions, - event: 'Cancel', - properties: { - action: 'Sign Request', - legacy_event: true, - type, - version, - }, - }); - }; - - const messageIsScrollable = - this.messageRootRef?.scrollHeight > this.messageRootRef?.clientHeight; - - const targetSubjectMetadata = txData.msgParams.origin - ? subjectMetadata?.[txData.msgParams.origin] - : null; - - return ( -
- -
- -
-
- {isSuspiciousResponse(txData?.securityProviderResponse) && ( - - )} - - { - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - this.props.selectedAccount.address === address ? null : ( - - - - {this.context.t('mismatchAccount', [ - shortenAddress(this.props.selectedAccount.address), - shortenAddress(address), - ])} - - - ) - ///: END:ONLY_INCLUDE_IN - } - -
- -
- - - {this.context.t('sigRequest')} - - - {this.context.t('signatureRequestGuidance')} - - {verifyingContract ? ( -
- -
- ) : null} -
- {isLedgerWallet ? ( -
- -
- ) : null} - this.setState({ hasScrolledMessage: true })} - setMessageRootRef={this.setMessageRootRef.bind(this)} - messageRootRef={this.messageRootRef} - messageIsScrollable={messageIsScrollable} - primaryType={primaryType} - /> -
- ); - } -} diff --git a/ui/components/app/signature-request/signature-request.constants.js b/ui/components/app/signature-request/signature-request.constants.js deleted file mode 100644 index e17841a0c..000000000 --- a/ui/components/app/signature-request/signature-request.constants.js +++ /dev/null @@ -1,3 +0,0 @@ -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app'; - -export { ENVIRONMENT_TYPE_NOTIFICATION }; diff --git a/ui/components/app/signature-request/signature-request.container.js b/ui/components/app/signature-request/signature-request.container.js deleted file mode 100644 index 4653a972c..000000000 --- a/ui/components/app/signature-request/signature-request.container.js +++ /dev/null @@ -1,298 +0,0 @@ -import { connect } from 'react-redux'; -import { - accountsWithSendEtherInfoSelector, - doesAddressRequireLedgerHidConnection, - getCurrentChainId, - getRpcPrefsForCurrentProvider, - getSubjectMetadata, - unconfirmedMessagesHashSelector, - getTotalUnapprovedMessagesCount, - getCurrentCurrency, - getPreferences, - conversionRateSelector, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - getAccountType, - getSelectedAccount, - unapprovedTypedMessagesSelector, - ///: END:ONLY_INCLUDE_IN -} from '../../../selectors'; -import { - isAddressLedger, - getNativeCurrency, - getProviderConfig, -} from '../../../ducks/metamask/metamask'; -import { getAccountByAddress, valuesFor } from '../../../helpers/utils/util'; -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) -// eslint-disable-next-line import/order -import { showCustodianDeepLink } from '@metamask-institutional/extension'; -import { - mmiActionsFactory, - setTypedMessageInProgress, -} from '../../../store/institutional/institution-background'; -import { getEnvironmentType } from '../../../../app/scripts/lib/util'; -import { - showCustodyConfirmLink, - checkForUnapprovedMessages, -} from '../../../store/institutional/institution-actions'; -///: END:ONLY_INCLUDE_IN -import { - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - ENVIRONMENT_TYPE_NOTIFICATION, - ///: END:ONLY_INCLUDE_IN -} from '../../../../shared/constants/app'; - -import { - showModal, - resolvePendingApproval, - rejectPendingApproval, - rejectAllMessages, - completedTx, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - goHome, - ///: END:ONLY_INCLUDE_IN -} from '../../../store/actions'; -import { getMostRecentOverviewPage } from '../../../ducks/history/history'; -import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck'; -import SignatureRequest from './signature-request.component'; - -function mapStateToProps(state, ownProps) { - const { txData } = ownProps; - - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - const envType = getEnvironmentType(); - ///: END:ONLY_INCLUDE_IN - - const { - msgParams: { from }, - } = txData; - const providerConfig = getProviderConfig(state); - - const hardwareWalletRequiresConnection = - doesAddressRequireLedgerHidConnection(state, from); - const isLedgerWallet = isAddressLedger(state, from); - const chainId = getCurrentChainId(state); - const rpcPrefs = getRpcPrefsForCurrentProvider(state); - const unconfirmedMessagesList = unconfirmedMessagesHashSelector(state); - const unapprovedMessagesCount = getTotalUnapprovedMessagesCount(state); - const { useNativeCurrencyAsPrimaryCurrency } = getPreferences(state); - - return { - providerConfig, - isLedgerWallet, - hardwareWalletRequiresConnection, - chainId, - rpcPrefs, - unconfirmedMessagesList, - unapprovedMessagesCount, - mostRecentOverviewPage: getMostRecentOverviewPage(state), - nativeCurrency: getNativeCurrency(state), - currentCurrency: getCurrentCurrency(state), - conversionRate: useNativeCurrencyAsPrimaryCurrency - ? null - : conversionRateSelector(state), - subjectMetadata: getSubjectMetadata(state), - // not forwarded to component - allAccounts: accountsWithSendEtherInfoSelector(state), - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - accountType: getAccountType(state), - isNotification: envType === ENVIRONMENT_TYPE_NOTIFICATION, - selectedAccount: getSelectedAccount(state), - unapprovedTypedMessages: unapprovedTypedMessagesSelector(state), - ///: END:ONLY_INCLUDE_IN - }; -} - -let mapDispatchToProps = null; - -mapDispatchToProps = function (dispatch) { - return { - resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)), - completedTx: (id) => dispatch(completedTx(id)), - rejectPendingApproval: (id, error) => - dispatch(rejectPendingApproval(id, error)), - clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - showRejectTransactionsConfirmationModal: ({ - onSubmit, - unapprovedTxCount: unapprovedMessagesCount, - }) => { - return dispatch( - showModal({ - name: 'REJECT_TRANSACTIONS', - onSubmit, - unapprovedTxCount: unapprovedMessagesCount, - isRequestType: true, - }), - ); - }, - cancelAllApprovals: (unconfirmedMessagesList) => { - dispatch(rejectAllMessages(unconfirmedMessagesList)); - }, - }; -}; - -///: BEGIN:ONLY_INCLUDE_IN(build-mmi) -function mmiMapDispatchToProps(dispatch) { - const mmiActions = mmiActionsFactory(); - return { - setMsgInProgress: (msgId) => dispatch(setTypedMessageInProgress(msgId)), - showCustodianDeepLink: ({ - custodyId, - fromAddress, - closeNotification, - onDeepLinkFetched, - onDeepLinkShown, - }) => - showCustodianDeepLink({ - dispatch, - mmiActions, - txId: undefined, - fromAddress, - custodyId, - isSignature: true, - closeNotification, - onDeepLinkFetched, - onDeepLinkShown, - showCustodyConfirmLink, - }), - showTransactionsFailedModal: ({ - errorMessage, - closeNotification, - operationFailed, - }) => - dispatch( - showModal({ - name: 'TRANSACTION_FAILED', - errorMessage, - closeNotification, - operationFailed, - }), - ), - setWaitForConfirmDeepLinkDialog: (wait) => - dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)), - goHome: () => dispatch(goHome()), - resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)), - completedTx: (id) => dispatch(completedTx(id)), - rejectPendingApproval: (id, error) => - dispatch(rejectPendingApproval(id, error)), - clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - showRejectTransactionsConfirmationModal: ({ - onSubmit, - unapprovedTxCount: unapprovedMessagesCount, - }) => { - return dispatch( - showModal({ - name: 'REJECT_TRANSACTIONS', - onSubmit, - unapprovedTxCount: unapprovedMessagesCount, - isRequestType: true, - }), - ); - }, - cancelAllApprovals: (unconfirmedMessagesList) => { - dispatch(rejectAllMessages(unconfirmedMessagesList)); - }, - }; -} - -mapDispatchToProps = mmiMapDispatchToProps; -///: END:ONLY_INCLUDE_IN - -function mergeProps(stateProps, dispatchProps, ownProps) { - const { - allAccounts, - isLedgerWallet, - hardwareWalletRequiresConnection, - chainId, - rpcPrefs, - nativeCurrency, - currentCurrency, - conversionRate, - providerConfig, - subjectMetadata, - unconfirmedMessagesList, - unapprovedMessagesCount, - mostRecentOverviewPage, - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - accountType, - isNotification, - unapprovedTypedMessages, - ///: END:ONLY_INCLUDE_IN - } = stateProps; - const { txData } = ownProps; - - const { - cancelAll: dispatchCancelAll, - cancelAllApprovals: dispatchCancelAllApprovals, - } = dispatchProps; - - const { - msgParams: { from }, - } = txData; - - const fromAccount = getAccountByAddress(allAccounts, from); - - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - const mmiOnSignCallback = async (_msgData) => { - if (accountType === 'custody') { - try { - let msgData = _msgData; - let id = _msgData.custodyId; - if (!_msgData.custodyId) { - msgData = checkForUnapprovedMessages( - _msgData, - unapprovedTypedMessages, - ); - id = msgData.custodyId; - } - dispatchProps.showCustodianDeepLink({ - custodyId: id, - fromAddress: fromAccount.address, - closeNotification: isNotification, - onDeepLinkFetched: () => undefined, - onDeepLinkShown: () => undefined, - }); - await dispatchProps.setMsgInProgress(msgData.metamaskId); - await dispatchProps.setWaitForConfirmDeepLinkDialog(true); - await goHome(); - } catch (err) { - await dispatchProps.setWaitForConfirmDeepLinkDialog(true); - await dispatchProps.showTransactionsFailedModal({ - errorMessage: err.message, - closeNotification: true, - operationFailed: true, - }); - } - } - }; - ///: END:ONLY_INCLUDE_IN - - return { - ...ownProps, - ...dispatchProps, - fromAccount, - txData, - isLedgerWallet, - hardwareWalletRequiresConnection, - chainId, - rpcPrefs, - nativeCurrency, - currentCurrency, - conversionRate, - providerConfig, - subjectMetadata, - unapprovedMessagesCount, - mostRecentOverviewPage, - cancelAll: () => dispatchCancelAll(valuesFor(unconfirmedMessagesList)), - cancelAllApprovals: () => - dispatchCancelAllApprovals(valuesFor(unconfirmedMessagesList)), - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) - mmiOnSignCallback, - ///: END:ONLY_INCLUDE_IN - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps, - mergeProps, -)(SignatureRequest); diff --git a/ui/components/app/signature-request/signature-request.container.test.js b/ui/components/app/signature-request/signature-request.container.test.js deleted file mode 100644 index f7c5522ce..000000000 --- a/ui/components/app/signature-request/signature-request.container.test.js +++ /dev/null @@ -1,237 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { fireEvent, screen, act } from '@testing-library/react'; -import configureMockStore from 'redux-mock-store'; -import { renderWithProvider } from '../../../../test/lib/render-helpers'; -import SignatureRequest from './signature-request.container'; - -const mockStoreWithEth = { - metamask: { - tokenList: { - '0x514910771af9ca656af840dff83e8264ecf986ca': { - address: '0x514910771af9ca656af840dff83e8264ecf986ca', - symbol: 'LINK', - decimals: 18, - name: 'ChainLink Token', - iconUrl: 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png', - aggregators: [ - 'Aave', - 'Bancor', - 'CMC', - 'Crypto.com', - 'CoinGecko', - '1inch', - 'Paraswap', - 'PMM', - 'Zapper', - 'Zerion', - '0x', - ], - occurrences: 12, - unlisted: false, - }, - }, - identities: { - '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e': { - name: 'Account 2', - address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', - }, - }, - addressBook: { - undefined: { - 0: { - address: '0x39a4e4Af7cCB654dB9500F258c64781c8FbD39F0', - name: '', - isEns: false, - }, - }, - }, - providerConfig: { - type: 'rpc', - }, - preferences: { - useNativeCurrencyAsPrimaryCurrency: true, - }, - accounts: { - '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': { - address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - balance: '0x03', - }, - }, - cachedBalances: {}, - unapprovedDecryptMsgs: {}, - unapprovedEncryptionPublicKeyMsgs: {}, - unconfirmedTransactions: {}, - selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - nativeCurrency: 'ETH', - currentCurrency: 'usd', - conversionRate: 231.06, - }, -}; - -const mockStoreWithFiat = { - ...mockStoreWithEth, - preferences: { - useNativeCurrencyAsPrimaryCurrency: false, - }, -}; -describe('Signature Request', () => { - const propsWithEth = { - fromAccount: { - address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - balance: '0x346ba7725f412cbfdb', - name: 'John Doe', - }, - history: { - push: sinon.spy(), - }, - hardwareWalletRequiresConnection: false, - mostRecentOverviewPage: '/', - clearConfirmTransaction: sinon.spy(), - cancelMessage: sinon.spy(), - cancel: sinon.stub().resolves(), - rejectPendingApproval: sinon.stub().resolves(), - showRejectTransactionsConfirmationModal: sinon.stub().resolves(), - cancelAll: sinon.stub().resolves(), - providerConfig: { - type: 'rpc', - }, - unapprovedMessagesCount: 2, - sign: sinon.stub().resolves(), - cancelAllApprovals: sinon.stub().resolves(), - resolvePendingApproval: sinon.stub().resolves(), - completedTx: sinon.stub().resolves(), - txData: { - msgParams: { - id: 1, - data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"4","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}', - from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', - origin: 'test.domain', - }, - status: 'unapproved', - time: 1, - type: 'eth_sign', - }, - nativeCurrency: 'ETH', - currentCurrency: 'usd', - conversionRate: null, - selectedAccount: { - address: '0x123456789abcdef', - }, - }; - - const propsWithFiat = { - ...propsWithEth, - conversionRate: 156.72, - }; - - describe('Render with different currencies', () => { - it('should render balance with ETH when conversionRate is not provided', () => { - const store = configureMockStore()(mockStoreWithEth); - renderWithProvider( - , - store, - ); - expect( - screen.getByTestId('request-signature-account').textContent, - ).toMatchInlineSnapshot( - `"UUnknown private networkJohn DoeBalance966.987986 ETH"`, - ); - }); - - it('should render balance with fiat when conversionRate not provided', () => { - const store = configureMockStore()(mockStoreWithFiat); - renderWithProvider( - , - store, - ); - expect( - screen.getByTestId('request-signature-account').textContent, - ).toMatchInlineSnapshot( - `"UUnknown private networkJohn DoeBalance$151,546.36 USD"`, - ); - }); - }); - - describe('functionality check', () => { - beforeEach(() => { - const store = configureMockStore()(mockStoreWithFiat); - renderWithProvider( - , - store, - ); - }); - - afterEach(() => { - propsWithFiat.clearConfirmTransaction.resetHistory(); - }); - - it('cancel', async () => { - const cancelButton = screen.getByTestId('page-container-footer-cancel'); - await act(() => { - fireEvent.click(cancelButton); - }); - expect(propsWithFiat.rejectPendingApproval.calledOnce).toStrictEqual( - true, - ); - }); - - it('sign', async () => { - const signButton = screen.getByTestId('page-container-footer-next'); - await act(() => { - fireEvent.click(signButton); - }); - expect(propsWithFiat.resolvePendingApproval.calledOnce).toStrictEqual( - true, - ); - }); - - it('have user warning', () => { - const warningText = screen.getByText( - 'Only sign this message if you fully understand the content and trust the requesting site.', - ); - - expect(warningText).toBeInTheDocument(); - }); - }); - - describe('contract details', () => { - let store; - beforeEach(() => { - store = configureMockStore()(mockStoreWithFiat); - }); - it('shows verify contract details link when verifyingContract is set', () => { - renderWithProvider( - , - store, - ); - const verifyingContractLink = screen.getByTestId( - 'verify-contract-details', - ); - expect(verifyingContractLink).toBeInTheDocument(); - }); - - it('should not show verify contract details link when verifyingContract is not set', () => { - const newData = JSON.parse(propsWithFiat.txData.msgParams.data); - delete newData.domain.verifyingContract; - - const newProps = { - ...propsWithFiat, - txData: { - ...propsWithFiat.txData, - msgParams: { - ...propsWithFiat.txData.msgParams, - data: JSON.stringify(newData), - }, - }, - }; - - renderWithProvider( - , - store, - ); - - expect(screen.queryByTestId('verify-contract-details')).toBeNull(); - }); - }); -}); diff --git a/ui/components/app/signature-request/signature-request.js b/ui/components/app/signature-request/signature-request.js new file mode 100644 index 000000000..e93f7af75 --- /dev/null +++ b/ui/components/app/signature-request/signature-request.js @@ -0,0 +1,380 @@ +import React, { useContext, useState, useEffect } from 'react'; +import { + useDispatch, + useSelector, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + shallowEqual, + ///: END:ONLY_INCLUDE_IN +} from 'react-redux'; +import PropTypes from 'prop-types'; +import { memoize } from 'lodash'; +import { ethErrors, serializeError } from 'eth-rpc-errors'; +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +import { showCustodianDeepLink } from '@metamask-institutional/extension'; +///: END:ONLY_INCLUDE_IN +import { + resolvePendingApproval, + rejectPendingApproval, + completedTx, +} from '../../../store/actions'; +import { + doesAddressRequireLedgerHidConnection, + getSubjectMetadata, + getTotalUnapprovedMessagesCount, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + accountsWithSendEtherInfoSelector, + getSelectedAccount, + getAccountType, + ///: END:ONLY_INCLUDE_IN +} from '../../../selectors'; +import { + getProviderConfig, + isAddressLedger, +} from '../../../ducks/metamask/metamask'; +import { + sanitizeMessage, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + getAccountByAddress, + shortenAddress, + ///: END:ONLY_INCLUDE_IN +} from '../../../helpers/utils/util'; +import { useI18nContext } from '../../../hooks/useI18nContext'; +import { useRejectTransactionModal } from '../../../hooks/useRejectTransactionModal'; + +import { ConfirmPageContainerNavigation } from '../confirm-page-container'; +import SignatureRequestHeader from '../signature-request-header/signature-request-header'; +import SecurityProviderBannerMessage from '../security-provider-banner-message'; +import LedgerInstructionField from '../ledger-instruction-field'; +import ContractDetailsModal from '../modals/contract-details-modal'; +import { MetaMetricsContext } from '../../../contexts/metametrics'; +import { + MetaMetricsEventCategory, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + MetaMetricsEventName, + ///: END:ONLY_INCLUDE_IN +} from '../../../../shared/constants/metametrics'; +import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; + +import { + TextAlign, + TextColor, + TextVariant, + Size, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + IconColor, + BackgroundColor, + Display, + BlockSize, + ///: END:ONLY_INCLUDE_IN +} from '../../../helpers/constants/design-system'; +import { + BUTTON_VARIANT, + Button, + ButtonLink, + TagUrl, + Text, + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + Icon, + IconName, + ///: END:ONLY_INCLUDE_IN +} from '../../component-library'; + +///: BEGIN:ONLY_INCLUDE_IN(build-mmi) +// eslint-disable-next-line import/order +import Box from '../../ui/box/box'; +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app'; +import { getEnvironmentType } from '../../../../app/scripts/lib/util'; +import { mmiActionsFactory } from '../../../store/institutional/institution-background'; +import { showCustodyConfirmLink } from '../../../store/institutional/institution-actions'; +import { useMMICustodySignMessage } from '../../../hooks/useMMICustodySignMessage'; +///: END:ONLY_INCLUDE_IN + +import Message from './signature-request-message'; +import Footer from './signature-request-footer'; + +const SignatureRequest = ({ txData }) => { + const trackEvent = useContext(MetaMetricsContext); + const dispatch = useDispatch(); + const t = useI18nContext(); + + const [hasScrolledMessage, setHasScrolledMessage] = useState(false); + const [showContractDetails, setShowContractDetails] = useState(false); + const [messageRootRef, setMessageRootRef] = useState(null); + const [messageIsScrollable, setMessageIsScrollable] = useState(false); + + const { + id, + type, + msgParams: { from, data, origin, version }, + } = txData; + + // not forwarded to component + const hardwareWalletRequiresConnection = useSelector((state) => + doesAddressRequireLedgerHidConnection(state, from), + ); + const { chainId, rpcPrefs } = useSelector(getProviderConfig); + const unapprovedMessagesCount = useSelector(getTotalUnapprovedMessagesCount); + const subjectMetadata = useSelector(getSubjectMetadata); + const isLedgerWallet = useSelector((state) => isAddressLedger(state, from)); + const { handleCancelAll } = useRejectTransactionModal(); + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + // Used to show a warning if the signing account is not the selected account + // Largely relevant for contract wallet custodians + const selectedAccount = useSelector(getSelectedAccount); + const mmiActions = mmiActionsFactory(); + const accountType = useSelector(getAccountType); + const isNotification = getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION; + const allAccounts = useSelector( + accountsWithSendEtherInfoSelector, + shallowEqual, + ); + const { address } = getAccountByAddress(allAccounts, from) || {}; + const { custodySignFn } = useMMICustodySignMessage(); + ///: END:ONLY_INCLUDE_IN + + useEffect(() => { + setMessageIsScrollable( + messageRootRef?.scrollHeight > messageRootRef?.clientHeight, + ); + }, [messageRootRef]); + + const targetSubjectMetadata = subjectMetadata?.[origin] || null; + + const parseMessage = memoize((dataToParse) => { + const { + message, + domain = {}, + primaryType, + types, + } = JSON.parse(dataToParse); + const sanitizedMessage = sanitizeMessage(message, primaryType, types); + return { sanitizedMessage, domain, primaryType }; + }); + + const onSign = async () => { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + if (accountType === 'custody') { + await custodySignFn(txData); + } + ///: END:ONLY_INCLUDE_IN + + await dispatch(resolvePendingApproval(id)); + completedTx(id); + + trackEvent({ + category: MetaMetricsEventCategory.Transactions, + event: 'Confirm', + properties: { + action: 'Sign Request', + legacy_event: true, + type, + version, + }, + }); + }; + + const onCancel = async () => { + await dispatch( + rejectPendingApproval( + id, + serializeError(ethErrors.provider.userRejectedRequest()), + ), + ); + trackEvent({ + category: MetaMetricsEventCategory.Transactions, + event: 'Cancel', + properties: { + action: 'Sign Request', + legacy_event: true, + type, + version, + }, + }); + }; + + const { + sanitizedMessage, + domain: { verifyingContract }, + primaryType, + } = parseMessage(data); + + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + useEffect(() => { + if (txData.custodyId) { + showCustodianDeepLink({ + dispatch, + mmiActions, + txId: undefined, + custodyId: txData.custodyId, + fromAddress: address, + isSignature: true, + closeNotification: isNotification, + onDeepLinkFetched: () => undefined, + onDeepLinkShown: () => { + trackEvent({ + category: MetaMetricsEventCategory.MMI, + event: MetaMetricsEventName.SignatureDeeplinkDisplayed, + }); + }, + showCustodyConfirmLink, + }); + } + }, [ + dispatch, + mmiActions, + txData.custodyId, + address, + isNotification, + trackEvent, + ]); + ///: END:ONLY_INCLUDE_IN + + return ( +
+ +
+ +
+
+ {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && + txData?.securityProviderResponse?.flagAsDangerous !== + SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) || + (txData?.securityProviderResponse && + Object.keys(txData.securityProviderResponse).length === 0) ? ( + + ) : null} + { + ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) + selectedAccount.address === address ? null : ( + + + + {t('mismatchAccount', [ + shortenAddress(selectedAccount.address), + shortenAddress(address), + ])} + + + ) + ///: END:ONLY_INCLUDE_IN + } +
+ +
+ + {t('sigRequest')} + + + {t('signatureRequestGuidance')} + + {verifyingContract ? ( +
+ +
+ ) : null} +
+ {isLedgerWallet ? ( +
+ +
+ ) : null} + setHasScrolledMessage(true)} + setMessageRootRef={setMessageRootRef} + messageRootRef={messageRootRef} + messageIsScrollable={messageIsScrollable} + primaryType={primaryType} + /> +
+ {showContractDetails && ( + setShowContractDetails(false)} + isContractRequestingSignature + /> + )} + {unapprovedMessagesCount > 1 ? ( + + {t('rejectRequestsN', [unapprovedMessagesCount])} + + ) : null} +
+ ); +}; + +SignatureRequest.propTypes = { + txData: PropTypes.object, +}; + +export default SignatureRequest; diff --git a/ui/components/app/signature-request/signature-request.stories.js b/ui/components/app/signature-request/signature-request.stories.js index 1fc2e555b..c74ac7457 100644 --- a/ui/components/app/signature-request/signature-request.stories.js +++ b/ui/components/app/signature-request/signature-request.stories.js @@ -1,14 +1,22 @@ import React from 'react'; +import { Provider } from 'react-redux'; +import configureStore from '../../../store/store'; import testData from '../../../../.storybook/test-data'; import README from './README.mdx'; -import SignatureRequest from './signature-request.component'; +import SignatureRequest from './signature-request'; -const [MOCK_PRIMARY_IDENTITY, MOCK_SECONDARY_IDENTITY] = Object.values( - testData.metamask.identities, -); +const store = configureStore({ + ...testData, + metamask: { + ...testData.metamask, + selectedAddress: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', + }, +}); export default { title: 'Components/App/SignatureRequest', + decorators: [(story) => {story()}], + component: SignatureRequest, parameters: { docs: { @@ -17,22 +25,6 @@ export default { }, argTypes: { txData: { control: 'object' }, - fromAccount: { - table: { - address: { control: 'text' }, - balance: { control: 'text' }, - name: { control: 'text' }, - }, - }, - hardwareWalletRequiresConnection: { control: 'boolean' }, - isLedgerWallet: { control: 'boolean' }, - clearConfirmTransaction: { action: 'Clean Confirm' }, - cancel: { action: 'Cancel' }, - sign: { action: 'Sign' }, - showRejectTransactionsConfirmationModal: { - action: 'showRejectTransactionsConfirmationModal', - }, - cancelAll: { action: 'cancelAll' }, }, }; @@ -45,6 +37,7 @@ DefaultStory.storyName = 'Default'; DefaultStory.args = { txData: { msgParams: { + from: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e', data: JSON.stringify({ domain: { name: 'happydapp.website', @@ -79,11 +72,6 @@ DefaultStory.args = { origin: 'https://happydapp.website/', }, }, - fromAccount: MOCK_PRIMARY_IDENTITY, - providerConfig: { name: 'Goerli ETH' }, - selectedAccount: MOCK_PRIMARY_IDENTITY, - hardwareWalletRequiresConnection: false, - currentCurrency: 'usd', }; export const AccountMismatchStory = (args) => { @@ -94,5 +82,10 @@ AccountMismatchStory.storyName = 'AccountMismatch'; AccountMismatchStory.args = { ...DefaultStory.args, - selectedAccount: MOCK_SECONDARY_IDENTITY, + txData: { + msgParams: { + ...DefaultStory.args.txData.msgParams, + from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', + }, + }, }; diff --git a/ui/components/app/signature-request/signature-request.component.test.js b/ui/components/app/signature-request/signature-request.test.js similarity index 67% rename from ui/components/app/signature-request/signature-request.component.test.js rename to ui/components/app/signature-request/signature-request.test.js index 81f18932a..4c500d3fd 100644 --- a/ui/components/app/signature-request/signature-request.component.test.js +++ b/ui/components/app/signature-request/signature-request.test.js @@ -1,33 +1,98 @@ import React from 'react'; +import { useSelector } from 'react-redux'; import { fireEvent } from '@testing-library/react'; import configureMockStore from 'redux-mock-store'; import mockState from '../../../../test/data/mock-state.json'; import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider'; -import SignatureRequest from './signature-request.component'; +import { + getNativeCurrency, + getProviderConfig, +} from '../../../ducks/metamask/metamask'; +import { + accountsWithSendEtherInfoSelector, + conversionRateSelector, + getCurrentCurrency, + getMemoizedAddressBook, + getMemoizedMetaMaskIdentities, + getPreferences, + getSelectedAccount, + getTotalUnapprovedMessagesCount, + unconfirmedTransactionsHashSelector, +} from '../../../selectors'; +import SignatureRequest from './signature-request'; const baseProps = { - hardwareWalletRequiresConnection: false, - clearConfirmTransaction: () => undefined, - cancel: () => undefined, - cancelAll: () => undefined, - mostRecentOverviewPage: '/', - showRejectTransactionsConfirmationModal: () => undefined, - sign: () => undefined, - history: { push: '/' }, - providerConfig: { type: 'rpc' }, - nativeCurrency: 'ABC', - currentCurrency: 'def', - fromAccount: { - address: '0x123456789abcdef', - balance: '0x346ba7725f412cbfdb', - name: 'Antonio', - }, - selectedAccount: { - address: '0x123456789abcdef', + clearConfirmTransaction: () => jest.fn(), + cancel: () => jest.fn(), + cancelAll: () => jest.fn(), + showRejectTransactionsConfirmationModal: () => jest.fn(), + sign: () => jest.fn(), +}; +const mockStore = { + metamask: { + providerConfig: { + chainId: '0x539', + nickname: 'Localhost 8545', + rpcPrefs: {}, + rpcUrl: 'http://localhost:8545', + ticker: 'ETH', + type: 'rpc', + }, + preferences: { + useNativeCurrencyAsPrimaryCurrency: true, + }, + accounts: { + '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': { + address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', + balance: '0x03', + name: 'John Doe', + }, + }, + selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', + nativeCurrency: 'ETH', + currentCurrency: 'usd', + conversionRate: null, + unapprovedTypedMessagesCount: 2, }, }; +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useSelector: jest.fn(), + useDispatch: () => jest.fn(), + }; +}); + +const generateUseSelectorRouter = (opts) => (selector) => { + switch (selector) { + case getProviderConfig: + return opts.metamask.providerConfig; + case getCurrentCurrency: + return opts.metamask.currentCurrency; + case getNativeCurrency: + return opts.metamask.nativeCurrency; + case getTotalUnapprovedMessagesCount: + return opts.metamask.unapprovedTypedMessagesCount; + case getPreferences: + return opts.metamask.preferences; + case conversionRateSelector: + return opts.metamask.conversionRate; + case getSelectedAccount: + return opts.metamask.accounts[opts.metamask.selectedAddress]; + case getMemoizedAddressBook: + return []; + case accountsWithSendEtherInfoSelector: + return Object.values(opts.metamask.accounts); + case unconfirmedTransactionsHashSelector: + case getMemoizedMetaMaskIdentities: + return {}; + default: + return undefined; + } +}; describe('Signature Request Component', () => { const store = configureMockStore()(mockState); @@ -35,6 +100,7 @@ describe('Signature Request Component', () => { let messageData; beforeEach(() => { + useSelector.mockImplementation(generateUseSelectorRouter(mockStore)); messageData = { domain: { chainId: 97, @@ -84,7 +150,17 @@ describe('Signature Request Component', () => { }); it('should match snapshot when we want to switch to fiat', () => { + useSelector.mockImplementation( + generateUseSelectorRouter({ + ...mockStore, + metamask: { + ...mockStore.metamask, + conversionRate: 231.06, + }, + }), + ); const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -95,7 +171,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={1567} />, store, ); @@ -105,6 +180,7 @@ describe('Signature Request Component', () => { it('should match snapshot when we are using eth', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -115,7 +191,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} />, store, ); @@ -125,6 +200,7 @@ describe('Signature Request Component', () => { it('should render navigation', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -135,7 +211,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} />, store, ); @@ -149,6 +224,7 @@ describe('Signature Request Component', () => { do_not_display: 'two', }; const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -159,7 +235,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} />, store, ); @@ -171,7 +246,17 @@ describe('Signature Request Component', () => { }); it('should not render a reject multiple requests link if there is not multiple requests', () => { + useSelector.mockImplementation( + generateUseSelectorRouter({ + ...mockStore, + metamask: { + ...mockStore.metamask, + unapprovedTypedMessagesCount: 0, + }, + }), + ); const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -182,7 +267,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} />, store, ); @@ -194,6 +278,7 @@ describe('Signature Request Component', () => { it('should render a reject multiple requests link if there is multiple requests (greater than 1)', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -204,8 +289,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} - unapprovedMessagesCount={2} />, store, ); @@ -217,6 +300,7 @@ describe('Signature Request Component', () => { it('should call reject all button when button is clicked', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -227,8 +311,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} - unapprovedMessagesCount={2} />, store, ); @@ -242,6 +324,7 @@ describe('Signature Request Component', () => { it('should render text of reject all button', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -252,8 +335,6 @@ describe('Signature Request Component', () => { txData={{ msgParams, }} - conversionRate={null} - unapprovedMessagesCount={2} />, store, ); @@ -263,6 +344,7 @@ describe('Signature Request Component', () => { it('should render SecurityProviderBannerMessage component properly', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -271,7 +353,6 @@ describe('Signature Request Component', () => { const { queryByText } = renderWithProvider( { reason_header: 'Some reason header...', }, }} - unapprovedMessagesCount={2} />, store, ); @@ -296,6 +376,7 @@ describe('Signature Request Component', () => { it('should not render SecurityProviderBannerMessage component when flagAsDangerous is not malicious', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', @@ -304,14 +385,12 @@ describe('Signature Request Component', () => { const { queryByText } = renderWithProvider( , store, ); @@ -327,27 +406,39 @@ describe('Signature Request Component', () => { it('should render a warning when the selected account is not the one being used to sign', () => { const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', data: JSON.stringify(messageData), version: 'V4', origin: 'test', }; + useSelector.mockImplementation( + generateUseSelectorRouter({ + ...mockStore, + metamask: { + ...mockStore.metamask, + selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + accounts: { + ...mockStore.metamask.accounts, + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + balance: '0x0', + name: 'Account 1', + }, + }, + }, + }), + ); + const { container } = renderWithProvider( , store, ); diff --git a/ui/hooks/useMMICustodySignMessage.js b/ui/hooks/useMMICustodySignMessage.js new file mode 100644 index 000000000..9e7dd32f9 --- /dev/null +++ b/ui/hooks/useMMICustodySignMessage.js @@ -0,0 +1,77 @@ +import { shallowEqual, useDispatch, useSelector } from 'react-redux'; +import { showCustodianDeepLink } from '@metamask-institutional/extension'; +import { + showCustodyConfirmLink, + checkForUnapprovedMessages, +} from '../store/institutional/institution-actions'; +import { + mmiActionsFactory, + setTypedMessageInProgress, +} from '../store/institutional/institution-background'; +import { + accountsWithSendEtherInfoSelector, + getAccountType, + unapprovedTypedMessagesSelector, +} from '../selectors'; +import { getAccountByAddress } from '../helpers/utils/util'; +import { getEnvironmentType } from '../../app/scripts/lib/util'; +import { goHome, showModal } from '../store/actions'; +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../shared/constants/app'; + +export function useMMICustodySignMessage() { + const dispatch = useDispatch(); + const mmiActions = mmiActionsFactory(); + const envType = getEnvironmentType(); + const accountType = useSelector(getAccountType); + const isNotification = envType === ENVIRONMENT_TYPE_NOTIFICATION; + const allAccounts = useSelector( + accountsWithSendEtherInfoSelector, + shallowEqual, + ); + const unapprovedTypedMessages = useSelector(unapprovedTypedMessagesSelector); + + const custodySignFn = async (_msgData) => { + if (accountType === 'custody') { + const { address: fromAddress } = + getAccountByAddress(allAccounts, _msgData.msgParams.from) || {}; + try { + let msgData = _msgData; + let id = _msgData.custodyId; + if (!_msgData.custodyId) { + msgData = checkForUnapprovedMessages( + _msgData, + unapprovedTypedMessages, + ); + id = msgData.custodyId; + } + showCustodianDeepLink({ + dispatch, + mmiActions, + txId: undefined, + custodyId: id, + fromAddress, + isSignature: true, + closeNotification: isNotification, + onDeepLinkFetched: () => undefined, + onDeepLinkShown: () => undefined, + showCustodyConfirmLink, + }); + await dispatch(setTypedMessageInProgress(msgData.metamaskId)); + await dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(true)); + await dispatch(goHome()); + } catch (err) { + await dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(true)); + await dispatch( + showModal({ + name: 'TRANSACTION_FAILED', + errorMessage: err.message, + closeNotification: true, + operationFailed: true, + }), + ); + } + } + }; + + return { custodySignFn }; +} diff --git a/ui/hooks/useRejectTransactionModal.js b/ui/hooks/useRejectTransactionModal.js new file mode 100644 index 000000000..b2ce7d8a3 --- /dev/null +++ b/ui/hooks/useRejectTransactionModal.js @@ -0,0 +1,35 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { valuesFor } from '../helpers/utils/util'; +import { showModal, rejectAllMessages } from '../store/actions'; +import { clearConfirmTransaction } from '../ducks/confirm-transaction/confirm-transaction.duck'; +import { getMostRecentOverviewPage } from '../ducks/history/history'; +import { + getTotalUnapprovedMessagesCount, + unconfirmedMessagesHashSelector, +} from '../selectors'; + +export function useRejectTransactionModal() { + const dispatch = useDispatch(); + const history = useHistory(); + const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage); + const unapprovedMessagesCount = useSelector(getTotalUnapprovedMessagesCount); + const unconfirmedMessagesList = useSelector(unconfirmedMessagesHashSelector); + + const handleCancelAll = () => { + dispatch( + showModal({ + name: 'REJECT_TRANSACTIONS', + onSubmit: async () => { + await dispatch(rejectAllMessages(valuesFor(unconfirmedMessagesList))); + dispatch(clearConfirmTransaction()); + history.push(mostRecentOverviewPage); + }, + unapprovedTxCount: unapprovedMessagesCount, + isRequestType: true, + }), + ); + }; + + return { handleCancelAll }; +} diff --git a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap index 54852e44b..3522d4d7a 100644 --- a/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap +++ b/ui/pages/confirm-signature-request/__snapshots__/index.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Signature Request Component render should match snapshot 1`] = ` +exports[`Confirm Signature Request Component render should match snapshot 1`] = `
-
-
- - M - -
-
- https://metamask.github.io - + class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default" + style="mask-image: url('./images/icons/global.svg');" + />
+

+ https://metamask.github.io +

-

Signature request -

+
Only sign this message if you fully understand the content and trust the requesting site.
- -
- Verify third-party details -
-
+
+ Verify third-party details +
+ +
{ +describe('Confirm Signature Request Component', () => { const store = configureMockStore()(mockState); describe('render', () => {