import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { ObjectInspector } from 'react-inspector'; import { ethErrors, serializeError } from 'eth-rpc-errors'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { SubjectType } from '@metamask/permission-controller'; ///: END:ONLY_INCLUDE_IN import LedgerInstructionField from '../ledger-instruction-field'; import { MESSAGE_TYPE } from '../../../../shared/constants/app'; import { getURLHostName, sanitizeString, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) shortenAddress, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/utils/util'; import { stripHexPrefix } from '../../../../shared/modules/hexstring-utils'; import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils'; import SiteOrigin from '../../ui/site-origin'; import Typography from '../../ui/typography/typography'; import { PageContainerFooter } from '../../ui/page-container'; import { TypographyVariant, FontWeight, TextAlign, TextColor, Size, ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) IconColor, DISPLAY, BLOCK_SIZES, TextVariant, BackgroundColor, ///: END:ONLY_INCLUDE_IN } from '../../../helpers/constants/design-system'; 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 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'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import SnapLegacyAuthorshipHeader from '../snaps/snap-legacy-authorship-header'; ///: END:ONLY_INCLUDE_IN import SignatureRequestOriginalWarning from './signature-request-original-warning'; export default class SignatureRequestOriginal extends Component { static contextTypes = { t: PropTypes.func.isRequired, }; static propTypes = { fromAccount: PropTypes.shape({ address: PropTypes.string.isRequired, name: PropTypes.string, }).isRequired, txData: PropTypes.object.isRequired, subjectMetadata: PropTypes.object, hardwareWalletRequiresConnection: PropTypes.bool, isLedgerWallet: PropTypes.bool, messagesCount: PropTypes.number, showRejectTransactionsConfirmationModal: PropTypes.func.isRequired, cancelAllApprovals: PropTypes.func.isRequired, rejectPendingApproval: PropTypes.func.isRequired, clearConfirmTransaction: PropTypes.func.isRequired, history: PropTypes.object.isRequired, mostRecentOverviewPage: PropTypes.string.isRequired, resolvePendingApproval: PropTypes.func.isRequired, completedTx: PropTypes.func.isRequired, ///: 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 selectedAccount: PropTypes.object, mmiOnSignCallback: PropTypes.func, ///: END:ONLY_INCLUDE_IN }; state = { showSignatureRequestWarning: false, }; msgHexToText = (hex) => { try { const stripped = stripHexPrefix(hex); const buff = Buffer.from(stripped, 'hex'); return buff.length === 32 ? hex : buff.toString('utf8'); } catch (e) { return hex; } }; renderTypedData = (data) => { const { t } = this.context; const { domain, message } = JSON.parse(data); return (
{domain ? (

{t('domain')}

) : ( '' )} {message ? (

{t('message')}

) : ( '' )}
); }; renderBody = () => { let rows; const notice = `${this.context.t('youSign')}:`; const { txData, subjectMetadata } = this.props; const { type, msgParams: { data }, } = txData; if (type === MESSAGE_TYPE.PERSONAL_SIGN) { rows = [ { name: this.context.t('message'), value: this.msgHexToText(data) }, ]; } else if (type === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA) { rows = data; } else if (type === MESSAGE_TYPE.ETH_SIGN) { rows = [{ name: this.context.t('message'), value: data }]; } const targetSubjectMetadata = txData.msgParams.origin ? subjectMetadata?.[txData.msgParams.origin] : null; return (
{isSuspiciousResponse(txData?.securityProviderResponse) && ( )} { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.props.selectedAccount.address === this.props.fromAccount.address ? null : ( {this.context.t('mismatchAccount', [ shortenAddress(this.props.selectedAccount.address), shortenAddress(this.props.fromAccount.address), ])} ) ///: END:ONLY_INCLUDE_IN }
{ // Use legacy authorship header for snaps ///: BEGIN:ONLY_INCLUDE_IN(snaps) targetSubjectMetadata?.subjectType === SubjectType.Snap ? ( ) : ( ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(snaps) ) ///: END:ONLY_INCLUDE_IN }
{this.context.t('sigRequest')} {this.context.t('signatureRequestGuidance')}
{notice}
{rows.map(({ name, value }, index) => { if (typeof value === 'boolean') { // eslint-disable-next-line no-param-reassign value = value.toString(); } return (
{sanitizeString(`${name}:`)}
{sanitizeString(value)}
); })}
); }; onSubmit = async () => { const { resolvePendingApproval, completedTx, clearConfirmTransaction, history, mostRecentOverviewPage, txData, } = this.props; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) if (this.props.mmiOnSignCallback) { await this.props.mmiOnSignCallback(txData); return; } ///: END:ONLY_INCLUDE_IN await resolvePendingApproval(txData.id); completedTx(txData.id); clearConfirmTransaction(); history.push(mostRecentOverviewPage); }; onCancel = async () => { const { clearConfirmTransaction, history, mostRecentOverviewPage, rejectPendingApproval, txData: { id }, } = this.props; await rejectPendingApproval( id, serializeError(ethErrors.provider.userRejectedRequest()), ); clearConfirmTransaction(); history.push(mostRecentOverviewPage); }; renderFooter = () => { const { clearConfirmTransaction, history, mostRecentOverviewPage, txData, hardwareWalletRequiresConnection, rejectPendingApproval, resolvePendingApproval, } = this.props; const { t } = this.context; return ( { await rejectPendingApproval( txData.id, serializeError(ethErrors.provider.userRejectedRequest()), ); clearConfirmTransaction(); history.push(mostRecentOverviewPage); }} onSubmit={async () => { if (txData.type === MESSAGE_TYPE.ETH_SIGN) { this.setState({ showSignatureRequestWarning: true }); } else { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) if (this.props.mmiOnSignCallback) { await this.props.mmiOnSignCallback(txData); return; } ///: END:ONLY_INCLUDE_IN await resolvePendingApproval(txData.id); clearConfirmTransaction(); history.push(mostRecentOverviewPage); } }} disabled={ ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) Boolean(txData?.custodyId) || ///: END:ONLY_INCLUDE_IN hardwareWalletRequiresConnection } /> ); }; handleCancelAll = () => { const { clearConfirmTransaction, history, mostRecentOverviewPage, showRejectTransactionsConfirmationModal, messagesCount, cancelAllApprovals, } = this.props; const unapprovedTxCount = messagesCount; showRejectTransactionsConfirmationModal({ unapprovedTxCount, onSubmit: async () => { await cancelAllApprovals(); clearConfirmTransaction(); history.push(mostRecentOverviewPage); }, }); }; render = () => { const { messagesCount, fromAccount: { address, name }, txData, } = this.props; const { showSignatureRequestWarning } = this.state; const { t } = this.context; const rejectNText = t('rejectRequestsN', [messagesCount]); return (
{this.renderBody()} {this.props.isLedgerWallet ? (
) : null} {showSignatureRequestWarning && ( await this.onSubmit(event)} onCancel={async (event) => await this.onCancel(event)} /> )} {this.renderFooter()} {messagesCount > 1 ? ( this.handleCancelAll()} > {rejectNText} ) : null}
); }; }