import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import copyToClipboard from 'copy-to-clipboard';
import { getTokenTrackerLink, getAccountLink } 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 { ellipsify } from '../../send/send.utils';
import Typography from '../../../components/ui/typography';
import Box from '../../../components/ui/box';
import Button from '../../../components/ui/button';
import EditGasFeeButton from '../../../components/app/edit-gas-fee-button';
import Identicon from '../../../components/ui/identicon';
import MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message';
import CopyIcon from '../../../components/ui/icon/copy-icon.component';
import {
TYPOGRAPHY,
FONT_WEIGHT,
BLOCK_SIZES,
JUSTIFY_CONTENT,
COLORS,
DISPLAY,
} from '../../../helpers/constants/design-system';
import { SECOND } from '../../../../shared/constants/time';
import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content';
import GasDetailsItem from '../../../components/app/gas-details-item';
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
import {
ERC1155,
ERC20,
ERC721,
} from '../../../../shared/constants/transaction';
import {
MAINNET_CHAIN_ID,
TEST_CHAINS,
} from '../../../../shared/constants/network';
export default class ConfirmApproveContent extends Component {
static contextTypes = {
t: PropTypes.func,
};
static propTypes = {
decimals: PropTypes.number,
tokenAmount: PropTypes.string,
customTokenAmount: PropTypes.string,
tokenSymbol: PropTypes.string,
siteImage: PropTypes.string,
showCustomizeGasModal: PropTypes.func,
showEditApprovalPermissionModal: PropTypes.func,
origin: PropTypes.string,
setCustomAmount: PropTypes.func,
tokenBalance: 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,
isMultiLayerFeeNetwork: PropTypes.bool,
supportsEIP1559V2: PropTypes.bool,
assetName: PropTypes.string,
tokenId: PropTypes.string,
assetStandard: PropTypes.string,
isSetApproveForAll: PropTypes.bool,
setApproveForAllArg: PropTypes.bool,
userAddress: PropTypes.string,
};
state = {
showFullTxDetails: true,
copied: false,
};
renderApproveContentCard({
showHeader = true,
symbol,
title,
showEdit,
showAdvanceGasFeeOptions = false,
onEditClick,
content,
footer,
noBorder,
}) {
const { supportsEIP1559V2 } = this.props;
const { t } = this.context;
return (
{showHeader && (
{supportsEIP1559V2 && title === t('transactionFee') ? null : (
<>
{symbol}
{title}
>
)}
{showEdit && (!showAdvanceGasFeeOptions || !supportsEIP1559V2) && (
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559V2 && (
)}
)}
{content}
{footer}
);
}
// TODO: Add "Learn Why" with link to the feeAssociatedRequest text
renderTransactionDetailsContent() {
const { t } = this.context;
const {
currentCurrency,
nativeCurrency,
ethTransactionTotal,
fiatTransactionTotal,
hexTransactionTotal,
txData,
isMultiLayerFeeNetwork,
supportsEIP1559V2,
} = this.props;
if (!isMultiLayerFeeNetwork && supportsEIP1559V2) {
return ;
}
return (
{isMultiLayerFeeNetwork ? (
{t('transactionDetailLayer2GasHeading')}
{`${ethTransactionTotal} ${nativeCurrency}`}
) : (
<>
{t('feeAssociatedRequest')}
{formatCurrency(fiatTransactionTotal, currentCurrency)}
{`${ethTransactionTotal} ${nativeCurrency}`}
>
)}
);
}
renderERC721OrERC1155PermissionContent() {
const { t } = this.context;
const { origin, toAddress, isContract, isSetApproveForAll } = this.props;
const titleTokenDescription = this.getTitleTokenDescription();
const displayedAddress = isContract
? `${t('contract')} (${addressSummary(toAddress)})`
: addressSummary(toAddress);
return (
{t('accessAndSpendNoticeNFT', [origin])}
{t('approvedAsset')}:
{isSetApproveForAll
? t('allOfYour', [titleTokenDescription])
: titleTokenDescription}
{t('grantedToWithColon')}
{displayedAddress}
);
}
renderERC20PermissionContent() {
const { t } = this.context;
const {
customTokenAmount,
tokenAmount,
tokenSymbol,
origin,
toAddress,
isContract,
} = this.props;
const displayedAddress = isContract
? `${t('contract')} (${addressSummary(toAddress)})`
: addressSummary(toAddress);
return (
{t('accessAndSpendNotice', [origin])}
{t('approvedAmountWithColon')}
{`${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}`}
{t('grantedToWithColon')}
{`${displayedAddress}`}
);
}
renderDataContent() {
const { t } = this.context;
const { data, isSetApproveForAll, setApproveForAllArg } = this.props;
return (
{isSetApproveForAll
? t('functionSetApprovalForAll')
: t('functionApprove')}
{isSetApproveForAll && setApproveForAllArg !== undefined ? (
{`${t('parameters')}: ${setApproveForAllArg}`}
) : null}
{data}
);
}
renderFullDetails() {
const { t } = this.context;
const {
assetStandard,
showEditApprovalPermissionModal,
customTokenAmount,
tokenAmount,
decimals,
origin,
setCustomAmount,
tokenSymbol,
tokenBalance,
} = this.props;
if (assetStandard === ERC20) {
return (
{this.renderApproveContentCard({
symbol: ,
title: t('permissionRequest'),
content: this.renderERC20PermissionContent(),
showEdit: true,
onEditClick: () =>
showEditApprovalPermissionModal({
customTokenAmount,
decimals,
origin,
setCustomAmount,
tokenAmount,
tokenSymbol,
tokenBalance,
}),
})}
{this.renderApproveContentCard({
symbol: ,
title: 'Data',
content: this.renderDataContent(),
noBorder: true,
})}
);
} else if (assetStandard === ERC721 || assetStandard === 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}
)}
>
);
}
getTitleTokenDescription() {
const {
tokenId,
assetName,
tokenAddress,
rpcPrefs,
chainId,
assetStandard,
tokenSymbol,
isSetApproveForAll,
userAddress,
} = this.props;
const { t } = this.context;
const useBlockExplorer =
rpcPrefs?.blockExplorerUrl ||
[...TEST_CHAINS, MAINNET_CHAIN_ID].includes(chainId);
let titleTokenDescription = t('token');
const tokenIdWrapped = tokenId ? ` (#${tokenId})` : '';
if (
assetStandard === ERC20 ||
(tokenSymbol && !tokenId && !isSetApproveForAll)
) {
titleTokenDescription = tokenSymbol;
} else if (
assetStandard === ERC721 ||
assetStandard === 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('nft');
}
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 { isSetApproveForAll, setApproveForAllArg } = this.props;
const titleTokenDescription = this.getTitleTokenDescription();
let title;
if (isSetApproveForAll) {
title = t('approveAllTokensTitle', [titleTokenDescription]);
if (setApproveForAllArg === false) {
title = t('revokeAllTokensTitle', [titleTokenDescription]);
}
}
return title || t('allowSpendToken', [titleTokenDescription]);
}
renderDescription() {
const { t } = this.context;
const { isContract, isSetApproveForAll, setApproveForAllArg } = this.props;
const grantee = isContract
? t('contract').toLowerCase()
: t('account').toLowerCase();
let description = t('trustSiteApprovePermission', [grantee]);
if (isSetApproveForAll && setApproveForAllArg === false) {
description = t('revokeApproveForAllDescription', [
grantee,
this.getTitleTokenDescription(),
]);
}
return description;
}
render() {
const { t } = this.context;
const {
decimals,
siteImage,
tokenAmount,
customTokenAmount,
origin,
tokenSymbol,
showCustomizeGasModal,
showEditApprovalPermissionModal,
setCustomAmount,
tokenBalance,
useNonceField,
warning,
txData,
fromAddressIsLedger,
toAddress,
chainId,
rpcPrefs,
isContract,
assetStandard,
userAddress,
} = this.props;
const { showFullTxDetails } = this.state;
return (
{warning && (
)}
{origin}
{this.renderTitle()}
{this.renderDescription()}
{ellipsify(toAddress)}
{assetStandard === ERC20 ? (
showEditApprovalPermissionModal({
customTokenAmount,
decimals,
origin,
setCustomAmount,
tokenAmount,
tokenSymbol,
tokenBalance,
})
}
>
{t('editPermission')}
) : null}
{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}
);
}
}