1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-28 23:06:37 +01:00
metamask-extension/ui/components/app/signature-request/signature-request.js
2023-08-03 16:24:54 +05:30

396 lines
12 KiB
JavaScript

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 {
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
resolvePendingApproval,
completedTx,
///: END:ONLY_INCLUDE_IN
rejectPendingApproval,
} 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
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert';
///: 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);
return;
}
///: END:ONLY_INCLUDE_IN
///: BEGIN:ONLY_INCLUDE_IN(build-main,build-beta,build-flask)
await dispatch(resolvePendingApproval(id));
completedTx(id);
///: END:ONLY_INCLUDE_IN
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 (
<div className="signature-request">
<ConfirmPageContainerNavigation />
<div
className="request-signature__account"
data-testid="request-signature-account"
>
<SignatureRequestHeader txData={txData} />
</div>
<div className="signature-request-content">
{
///: BEGIN:ONLY_INCLUDE_IN(blockaid)
<BlockaidBannerAlert
securityAlertResponse={txData?.securityAlertResponse}
/>
///: END:ONLY_INCLUDE_IN
}
{(txData?.securityProviderResponse?.flagAsDangerous !== undefined &&
txData?.securityProviderResponse?.flagAsDangerous !==
SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) ||
(txData?.securityProviderResponse &&
Object.keys(txData.securityProviderResponse).length === 0) ? (
<SecurityProviderBannerMessage
securityProviderResponse={txData.securityProviderResponse}
/>
) : null}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
selectedAccount.address === address ? null : (
<Box
className="request-signature__mismatch-info"
display={Display.Flex}
width={BlockSize.Full}
padding={4}
marginBottom={4}
backgroundColor={BackgroundColor.primaryMuted}
>
<Icon
name={IconName.Info}
color={IconColor.infoDefault}
marginRight={2}
/>
<Text
variant={TextVariant.bodyXs}
color={TextColor.textDefault}
as="h6"
>
{t('mismatchAccount', [
shortenAddress(selectedAccount.address),
shortenAddress(address),
])}
</Text>
</Box>
)
///: END:ONLY_INCLUDE_IN
}
<div className="signature-request__origin">
<TagUrl
label={origin}
labelProps={{
color: TextColor.textAlternative,
}}
src={targetSubjectMetadata?.iconUrl}
/>
</div>
<Text
className="signature-request__content__title"
variant={TextVariant.headingLg}
marginTop={4}
>
{t('sigRequest')}
</Text>
<Text
className="request-signature__content__subtitle"
variant={TextVariant.bodySm}
color={TextColor.textAlternative}
textAlign={TextAlign.Center}
marginLeft={12}
marginRight={12}
marginTop={4}
as="h6"
>
{t('signatureRequestGuidance')}
</Text>
{verifyingContract ? (
<div>
<Button
variant={BUTTON_VARIANT.LINK}
onClick={() => setShowContractDetails(true)}
className="signature-request-content__verify-contract-details"
data-testid="verify-contract-details"
>
<Text
variant={TextVariant.bodySm}
color={TextColor.primaryDefault}
as="h6"
>
{t('verifyContractDetails')}
</Text>
</Button>
</div>
) : null}
</div>
{isLedgerWallet ? (
<div className="confirm-approve-content__ledger-instruction-wrapper">
<LedgerInstructionField showDataInstruction />
</div>
) : null}
<Message
data={sanitizedMessage}
onMessageScrolled={() => setHasScrolledMessage(true)}
setMessageRootRef={setMessageRootRef}
messageRootRef={messageRootRef}
messageIsScrollable={messageIsScrollable}
primaryType={primaryType}
/>
<Footer
cancelAction={onCancel}
signAction={onSign}
disabled={
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Boolean(txData?.custodyId) ||
///: END:ONLY_INCLUDE_IN
hardwareWalletRequiresConnection ||
(messageIsScrollable && !hasScrolledMessage)
}
/>
{showContractDetails && (
<ContractDetailsModal
toAddress={verifyingContract}
chainId={chainId}
rpcPrefs={rpcPrefs}
onClose={() => setShowContractDetails(false)}
isContractRequestingSignature
/>
)}
{unapprovedMessagesCount > 1 ? (
<ButtonLink
size={Size.inherit}
className="signature-request__reject-all-button"
data-testid="signature-request-reject-all"
onClick={handleCancelAll}
>
{t('rejectRequestsN', [unapprovedMessagesCount])}
</ButtonLink>
) : null}
</div>
);
};
SignatureRequest.propTypes = {
txData: PropTypes.object,
};
export default SignatureRequest;