From 0c203d051858349b8484d7adf3359eef2a504ba9 Mon Sep 17 00:00:00 2001
From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com>
Date: Thu, 20 Jul 2023 13:51:38 -0400
Subject: [PATCH] Signature-Request refactor (#19104)
* refactoring signature-request and adding test coverage
* adding storybook and removing the reduntant files:
* adding new components from
* replacing with
* updating review comments from Jyoti and George
* adding the hook
* refactoring in the changes from #18770 MMI PR
* adding new hook for the MMISign changes
* updating lavamoat
* updating lavamoat
* removing a commeted line
* updating the sign check with accountType conditional
* fixing build issues
* updating the review comments on the hooks
* updating signatureRequestHeader
* lint fix
* fixing test failure
* lint fix
* updating review comments
* adding the renamed hook
* updating the origin url
* fixing test failure
* migrating changes from #19184
* updating snapshot
* fixing e2e failure
* fixing e2e failure
* addressing review comments from Joao
* migrating chnages from #19892
* moving shallowEqual inside of mmi build
* migrating changes from #19881
* fixing build failure
* migrating changes from #19949
* migrating changes from #19468
* updating snapshot
* updating snapshot
* updating QA review comments
* fixing full screen height issue from develop
* migrating changes from #20083
* fixing snapshot
---
test/e2e/tests/eth-sign.spec.js | 2 +-
test/e2e/tests/personal-sign.spec.js | 2 +-
test/e2e/tests/signature-request.spec.js | 2 +-
.../signature-request-original.component.js | 22 +-
.../signature-request-siwe.js | 1 -
...js.snap => signature-request.test.js.snap} | 184 ++++----
ui/components/app/signature-request/index.js | 2 +-
.../app/signature-request/index.scss | 10 +-
.../signature-request.component.js | 427 ------------------
.../signature-request.constants.js | 3 -
.../signature-request.container.js | 298 ------------
.../signature-request.container.test.js | 237 ----------
.../signature-request/signature-request.js | 380 ++++++++++++++++
.../signature-request.stories.js | 45 +-
...nent.test.js => signature-request.test.js} | 173 +++++--
ui/hooks/useMMICustodySignMessage.js | 77 ++++
ui/hooks/useRejectTransactionModal.js | 35 ++
.../__snapshots__/index.test.js.snap | 60 ++-
.../confirm-signature-request/index.test.js | 2 +-
19 files changed, 781 insertions(+), 1181 deletions(-)
rename ui/components/app/signature-request/__snapshots__/{signature-request.component.test.js.snap => signature-request.test.js.snap} (91%)
delete mode 100644 ui/components/app/signature-request/signature-request.component.js
delete mode 100644 ui/components/app/signature-request/signature-request.constants.js
delete mode 100644 ui/components/app/signature-request/signature-request.container.js
delete mode 100644 ui/components/app/signature-request/signature-request.container.test.js
create mode 100644 ui/components/app/signature-request/signature-request.js
rename ui/components/app/signature-request/{signature-request.component.test.js => signature-request.test.js} (67%)
create mode 100644 ui/hooks/useMMICustodySignMessage.js
create mode 100644 ui/hooks/useRejectTransactionModal.js
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 ? (
- this.handleCancelAll()}
>
{rejectNText}
-
+
) : 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"
>
-
- 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.
+
+ Reject 2 requests
+
`;
@@ -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"
>
-
- 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.
+
+ Reject 2 requests
+
`;
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 ? (
-
- this.setState({ showContractDetails: true })}
- className="signature-request-content__verify-contract-details"
- data-testid="verify-contract-details"
- >
-
- {this.context.t('verifyContractDetails')}
-
-
-
- ) : null}
-
- {isLedgerWallet ? (
-
-
-
- ) : null}
-
this.setState({ hasScrolledMessage: true })}
- setMessageRootRef={this.setMessageRootRef.bind(this)}
- messageRootRef={this.messageRootRef}
- messageIsScrollable={messageIsScrollable}
- primaryType={primaryType}
- />
-
- {this.state.showContractDetails && (
- this.setState({ showContractDetails: false })}
- isContractRequestingSignature
- />
- )}
- {unapprovedMessagesCount > 1 ? (
- {
- e.preventDefault();
- this.handleCancelAll();
- }}
- >
- {rejectNText}
-
- ) : null}
-
- );
- }
-}
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 ? (
+
+ setShowContractDetails(true)}
+ className="signature-request-content__verify-contract-details"
+ data-testid="verify-contract-details"
+ >
+
+ {t('verifyContractDetails')}
+
+
+
+ ) : 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`] = `
-
- 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.
{
+describe('Confirm Signature Request Component', () => {
const store = configureMockStore()(mockState);
describe('render', () => {