import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import copyToClipboard from 'copy-to-clipboard'; import { getTokenTrackerLink } from '@metamask/etherscan-link'; import UrlIcon from '../../../components/ui/url-icon'; import { addressSummary } from '../../../helpers/utils/util'; import { formatCurrency } from '../../../helpers/utils/confirm-tx.util'; import Box from '../../../components/ui/box'; import Button from '../../../components/ui/button'; import SimulationErrorMessage from '../../../components/ui/simulation-error-message'; import EditGasFeeButton from '../../../components/app/edit-gas-fee-button'; import MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message'; import SecurityProviderBannerMessage from '../../../components/app/security-provider-banner-message/security-provider-banner-message'; import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../../components/app/security-provider-banner-message/security-provider-banner-message.constants'; import { BLOCK_SIZES, JustifyContent, DISPLAY, TextColor, IconColor, TextVariant, AlignItems, } from '../../../helpers/constants/design-system'; import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content'; import LedgerInstructionField from '../../../components/app/ledger-instruction-field'; import { TokenStandard } from '../../../../shared/constants/transaction'; import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network'; import ContractDetailsModal from '../../../components/app/modals/contract-details-modal/contract-details-modal'; import { ButtonIcon, Icon, IconName, Text, } from '../../../components/component-library'; import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component'; import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display'; export default class ConfirmApproveContent extends Component { static contextTypes = { t: PropTypes.func, }; static propTypes = { tokenSymbol: PropTypes.string, siteImage: PropTypes.string, showCustomizeGasModal: PropTypes.func, origin: PropTypes.string, data: PropTypes.string, toAddress: PropTypes.string, currentCurrency: PropTypes.string, nativeCurrency: PropTypes.string, fiatTransactionTotal: PropTypes.string, ethTransactionTotal: PropTypes.string, useNonceField: PropTypes.bool, customNonceValue: PropTypes.string, updateCustomNonce: PropTypes.func, getNextNonce: PropTypes.func, nextNonce: PropTypes.number, showCustomizeNonceModal: PropTypes.func, warning: PropTypes.string, txData: PropTypes.object, fromAddressIsLedger: PropTypes.bool, chainId: PropTypes.string, tokenAddress: PropTypes.string, rpcPrefs: PropTypes.object, isContract: PropTypes.bool, hexTransactionTotal: PropTypes.string, hexMinimumTransactionFee: PropTypes.string, isMultiLayerFeeNetwork: PropTypes.bool, supportsEIP1559: PropTypes.bool, assetName: PropTypes.string, tokenId: PropTypes.string, assetStandard: PropTypes.string, isSetApproveForAll: PropTypes.bool, isApprovalOrRejection: PropTypes.bool, userAddress: PropTypes.string, userAcknowledgedGasMissing: PropTypes.bool, setUserAcknowledgedGasMissing: PropTypes.func, renderSimulationFailureWarning: PropTypes.bool, useCurrencyRateCheck: PropTypes.bool, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool, }; state = { showFullTxDetails: false, copied: false, setShowContractDetails: false, }; renderApproveContentCard({ showHeader = true, symbol, title, showEdit, showAdvanceGasFeeOptions = false, onEditClick, content, footer, noBorder, }) { const { supportsEIP1559, renderSimulationFailureWarning, userAcknowledgedGasMissing, } = this.props; const { t } = this.context; return (
{showHeader && (
{supportsEIP1559 && title === t('transactionFee') ? null : ( <>
{symbol}
{title}
)} {showEdit && (!showAdvanceGasFeeOptions || !supportsEIP1559) && ( )} {showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && !renderSimulationFailureWarning && ( )}
)}
{content}
{footer}
); } // TODO: Add "Learn Why" with link to the feeAssociatedRequest text renderTransactionDetailsContent() { const { t } = this.context; const { currentCurrency, nativeCurrency, ethTransactionTotal, fiatTransactionTotal, hexTransactionTotal, hexMinimumTransactionFee, txData, isMultiLayerFeeNetwork, supportsEIP1559, userAcknowledgedGasMissing, renderSimulationFailureWarning, useCurrencyRateCheck, useNativeCurrencyAsPrimaryCurrency, } = this.props; if ( !isMultiLayerFeeNetwork && supportsEIP1559 && !renderSimulationFailureWarning ) { return ( ); } return (
{isMultiLayerFeeNetwork ? (
} detailText={ } noBold flexWidthValues />
) : ( <>
{t('feeAssociatedRequest')}
{useCurrencyRateCheck && formatCurrency(fiatTransactionTotal, currentCurrency)}
{`${ethTransactionTotal} ${nativeCurrency}`}
)}
); } renderERC721OrERC1155PermissionContent() { const { t } = this.context; const { origin, toAddress, isContract, isSetApproveForAll, tokenSymbol } = this.props; const titleTokenDescription = this.getTitleTokenDescription(); const approvedAssetText = tokenSymbol ? t('allOfYour', [titleTokenDescription]) : t('allYourNFTsOf', [titleTokenDescription]); const displayedAddress = isContract ? `${t('contract')} (${addressSummary(toAddress)})` : addressSummary(toAddress); return (
{t('accessAndSpendNoticeNFT', [origin])}
{t('approvedAsset')}:
{isSetApproveForAll ? approvedAssetText : titleTokenDescription}
{t('grantedToWithColon')}
{displayedAddress}
copyToClipboard(toAddress)} color={IconColor.iconDefault} iconName={ this.state.copied ? IconName.CopySuccess : IconName.Copy } title={ this.state.copied ? t('copiedExclamation') : t('copyToClipboard') } />
); } renderDataContent() { const { t } = this.context; const { data, isSetApproveForAll, isApprovalOrRejection } = this.props; ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) const { tokenAddress } = this.props; ///: END:ONLY_INCLUDE_IN return (
{isSetApproveForAll ? t('functionSetApprovalForAll') : t('functionApprove')}
{isSetApproveForAll && isApprovalOrRejection !== undefined ? (
{`${t('parameters')}: ${isApprovalOrRejection}`} { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) `${t('tokenContractAddress')}: ${tokenAddress}` ///: END:ONLY_INCLUDE_IN }
) : null}
{data}
); } renderFullDetails() { const { t } = this.context; const { assetStandard } = this.props; if ( assetStandard === TokenStandard.ERC721 || assetStandard === TokenStandard.ERC1155 ) { return (
{this.renderApproveContentCard({ symbol: , title: t('permissionRequest'), content: this.renderERC721OrERC1155PermissionContent(), showEdit: false, })}
{this.renderApproveContentCard({ symbol: , title: t('data'), content: this.renderDataContent(), noBorder: true, })}
); } return null; } renderCustomNonceContent() { const { t } = this.context; const { useNonceField, customNonceValue, updateCustomNonce, getNextNonce, nextNonce, showCustomizeNonceModal, } = this.props; return ( <> {useNonceField && (
{t('nonce')} {customNonceValue || nextNonce}
)} ); } getTokenName() { const { tokenId, assetName, assetStandard, tokenSymbol } = this.props; const { t } = this.context; let titleTokenDescription = t('token'); if ( assetStandard === TokenStandard.ERC721 || assetStandard === TokenStandard.ERC1155 || // if we don't have an asset standard but we do have either both an assetname and a tokenID or both a tokenSymbol and tokenId we assume its an NFT (assetName && tokenId) || (tokenSymbol && tokenId) ) { if (assetName || tokenSymbol) { titleTokenDescription = `${assetName ?? tokenSymbol}`; } else { titleTokenDescription = t('thisCollection'); } } return titleTokenDescription; } getTitleTokenDescription() { const { tokenId, tokenAddress, rpcPrefs, chainId, userAddress } = this.props; const useBlockExplorer = rpcPrefs?.blockExplorerUrl || [...TEST_CHAINS, CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET].includes( chainId, ); const titleTokenDescription = this.getTokenName(); const tokenIdWrapped = tokenId ? ` (#${tokenId})` : ''; if (useBlockExplorer) { const blockExplorerLink = getTokenTrackerLink( tokenAddress, chainId, null, userAddress, { blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null, }, ); const blockExplorerElement = ( <> {titleTokenDescription} {tokenIdWrapped && {tokenIdWrapped}} ); return blockExplorerElement; } return ( <> { copyToClipboard(tokenAddress); }} title={tokenAddress} > {titleTokenDescription} {tokenIdWrapped && {tokenIdWrapped}} ); } renderTitle() { const { t } = this.context; const { assetName, tokenId, tokenSymbol, assetStandard, isSetApproveForAll, isApprovalOrRejection, } = this.props; const titleTokenDescription = this.getTitleTokenDescription(); let title; if (isSetApproveForAll) { if (tokenSymbol) { title = t('approveAllTokensTitle', [titleTokenDescription]); if (isApprovalOrRejection === false) { title = t('revokeAllTokensTitle', [titleTokenDescription]); } } else { title = t('approveAllTokensTitleWithoutSymbol', [ titleTokenDescription, ]); if (isApprovalOrRejection === false) { title = t('revokeAllTokensTitleWithoutSymbol', [ titleTokenDescription, ]); } } } else if ( assetStandard === TokenStandard.ERC721 || assetStandard === TokenStandard.ERC1155 || // if we don't have an asset standard but we do have either both an assetname and a tokenID or both a tokenSymbol and tokenId we assume its an NFT (assetName && tokenId) || (tokenSymbol && tokenId) ) { title = t('approveTokenTitle', [titleTokenDescription]); } return title || t('allowSpendToken', [titleTokenDescription]); } renderDescription() { const { t } = this.context; const { assetStandard, assetName, tokenId, tokenSymbol, isContract, isSetApproveForAll, isApprovalOrRejection, } = this.props; const grantee = isContract ? t('contract').toLowerCase() : t('account').toLowerCase(); let description = t('trustSiteApprovePermission', [grantee]); if (isSetApproveForAll && isApprovalOrRejection === false) { if (tokenSymbol) { description = t('revokeApproveForAllDescription', [ this.getTitleTokenDescription(), ]); } else { description = t('revokeApproveForAllDescriptionWithoutSymbol', [ this.getTitleTokenDescription(), ]); } } else if ( isSetApproveForAll || assetStandard === TokenStandard.ERC721 || assetStandard === TokenStandard.ERC1155 || // if we don't have an asset standard but we do have either both an assetname and a tokenID or both a tokenSymbol and tokenId we assume its an NFT (assetName && tokenId) || (tokenSymbol && tokenId) ) { if (tokenSymbol) { description = t('approveTokenDescription'); } else { description = t('approveTokenDescriptionWithoutSymbol', [ this.getTitleTokenDescription(), ]); } } return description; } render() { const { t } = this.context; const { siteImage, origin, tokenSymbol, showCustomizeGasModal, useNonceField, warning, txData, fromAddressIsLedger, toAddress, chainId, rpcPrefs, assetStandard, tokenId, tokenAddress, assetName, userAcknowledgedGasMissing, setUserAcknowledgedGasMissing, renderSimulationFailureWarning, } = this.props; const { showFullTxDetails, setShowContractDetails } = this.state; return (
{(txData?.securityProviderResponse?.flagAsDangerous !== undefined && txData?.securityProviderResponse?.flagAsDangerous !== SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS) || (txData?.securityProviderResponse && Object.keys(txData.securityProviderResponse).length === 0) ? ( ) : null} {warning && (
)} {origin}
{this.renderTitle()}
{this.renderDescription()}
{setShowContractDetails && ( this.setState({ setShowContractDetails: false })} tokenName={tokenSymbol} tokenAddress={tokenAddress} toAddress={toAddress} chainId={chainId} rpcPrefs={rpcPrefs} tokenId={tokenId} assetName={assetName} assetStandard={assetStandard} /> )}
{renderSimulationFailureWarning && ( setUserAcknowledgedGasMissing(true) } /> )} {this.renderApproveContentCard({ symbol: , title: t('transactionFee'), showEdit: true, showAdvanceGasFeeOptions: true, onEditClick: showCustomizeGasModal, content: this.renderTransactionDetailsContent(), noBorder: useNonceField || !showFullTxDetails, footer: !useNonceField && (
this.setState({ showFullTxDetails: !this.state.showFullTxDetails, }) } >
{this.state.showFullTxDetails ? t('hideFullTransactionDetails') : t('viewFullTransactionDetails')}
), })} {useNonceField && this.renderApproveContentCard({ showHeader: false, content: this.renderCustomNonceContent(), useNonceField, noBorder: !showFullTxDetails, footer: (
this.setState({ showFullTxDetails: !this.state.showFullTxDetails, }) } >
{this.state.showFullTxDetails ? t('hideFullTransactionDetails') : t('viewFullTransactionDetails')}
), })}
{fromAddressIsLedger ? (
) : null} {showFullTxDetails ? this.renderFullDetails() : null}
); } }