1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Display a warning and gas fee component for token allowance and NFT flow when transaction is expected to fail (#17437)

Co-authored-by: Pedro Figueiredo <pedro.figueiredo@consensys.net>
Co-authored-by: Dan J Miller <danjm.com@gmail.com>
Co-authored-by: Brad Decker <bhdecker84@gmail.com>
This commit is contained in:
Vladimir Saric 2023-02-02 00:14:09 +01:00 committed by PeterYinusa
parent 4b18d48366
commit 222a7dd881
12 changed files with 390 additions and 170 deletions

View File

@ -43,6 +43,8 @@ export default function ApproveContentCard({
isSetApproveForAll,
isApprovalOrRejection,
data,
userAcknowledgedGasMissing,
renderSimulationFailureWarning,
}) {
const t = useContext(I18nContext);
@ -91,9 +93,14 @@ export default function ApproveContentCard({
</Button>
</Box>
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
<EditGasFeeButton />
)}
{showEdit &&
showAdvanceGasFeeOptions &&
supportsEIP1559 &&
!renderSimulationFailureWarning && (
<EditGasFeeButton
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
)}
</Box>
)}
<Box
@ -102,8 +109,12 @@ export default function ApproveContentCard({
className="approve-content-card-container__card-content"
>
{renderTransactionDetailsContent &&
(!isMultiLayerFeeNetwork && supportsEIP1559 ? (
<GasDetailsItem />
(!isMultiLayerFeeNetwork &&
supportsEIP1559 &&
!renderSimulationFailureWarning ? (
<GasDetailsItem
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
) : (
<Box
display={DISPLAY.FLEX}
@ -301,4 +312,12 @@ ApproveContentCard.propTypes = {
* Current transaction data
*/
data: PropTypes.string,
/**
* User acknowledge gas is missing or not
*/
userAcknowledgedGasMissing: PropTypes.bool,
/**
* Render simulation failure warning
*/
renderSimulationFailureWarning: PropTypes.bool,
};

View File

@ -7,6 +7,7 @@ import { submittedPendingTransactionsSelector } from '../../../selectors/transac
import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext';
import ActionableMessage from '../../ui/actionable-message/actionable-message';
import SimulationErrorMessage from '../../ui/simulation-error-message';
import Typography from '../../ui/typography';
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
@ -23,19 +24,9 @@ const TransactionAlerts = ({
return (
<div className="transaction-alerts">
{supportsEIP1559 && hasSimulationError && (
<ActionableMessage
message={t('simulationErrorMessageV2')}
useIcon
iconFillColor="var(--color-error-default)"
type="danger"
primaryActionV2={
userAcknowledgedGasMissing === true
? undefined
: {
label: t('proceedWithTransaction'),
onClick: setUserAcknowledgedGasMissing,
}
}
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing}
/>
)}
{supportsEIP1559 && pendingTransactions?.length > 0 && (

View File

@ -0,0 +1 @@
export { default } from './simulation-error-message';

View File

@ -0,0 +1,33 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import ActionableMessage from '../actionable-message';
import { I18nContext } from '../../../../.storybook/i18n';
export default function SimulationErrorMessage({
userAcknowledgedGasMissing = false,
setUserAcknowledgedGasMissing,
}) {
const t = useContext(I18nContext);
return (
<ActionableMessage
message={t('simulationErrorMessageV2')}
useIcon
iconFillColor="var(--color-error-default)"
type="danger"
primaryActionV2={
userAcknowledgedGasMissing === true
? undefined
: {
label: t('proceedWithTransaction'),
onClick: setUserAcknowledgedGasMissing,
}
}
/>
);
}
SimulationErrorMessage.propTypes = {
userAcknowledgedGasMissing: PropTypes.bool,
setUserAcknowledgedGasMissing: PropTypes.func,
};

View File

@ -0,0 +1,65 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { fireEvent } from '@testing-library/react';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import SimulationErrorMessage from './simulation-error-message';
describe('Simulation Error Message', () => {
const store = configureMockStore()({});
let props = {};
beforeEach(() => {
props = {
userAcknowledgedGasMissing: false,
setUserAcknowledgedGasMissing: jest.fn(),
};
});
it('should render SimulationErrorMessage component with I want to procced anyway link', () => {
const { queryByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).toBeInTheDocument();
});
it('should render SimulationErrorMessage component without I want to procced anyway link', () => {
props.userAcknowledgedGasMissing = true;
const { queryByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument();
});
it('should render SimulationErrorMessage component with I want to proceed anyway and fire that event', () => {
props.userAcknowledgedGasMissing = false;
const { queryByText, getByText } = renderWithProvider(
<SimulationErrorMessage {...props} />,
store,
);
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).toBeInTheDocument();
const proceedAnywayLink = getByText('I want to proceed anyway');
fireEvent.click(proceedAnywayLink);
expect(props.setUserAcknowledgedGasMissing).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,18 @@
import { useSelector } from 'react-redux';
import { txDataSelector } from '../selectors';
/**
* Returns the simulation failure warning if a simulaiton error
* is present and user didn't acknowledge gas missing
*
* @param {boolean} userAcknowledgedGasMissing - Whether the user acknowledge gas missing
* @returns {boolean} simulation failure warning
*/
export function useSimulationFailureWarning(userAcknowledgedGasMissing) {
const txData = useSelector(txDataSelector) || {};
const hasSimulationError = Boolean(txData.simulationFails);
const renderSimulationFailureWarning =
hasSimulationError && !userAcknowledgedGasMissing;
return renderSimulationFailureWarning;
}

View File

@ -2,16 +2,15 @@ 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 { 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 { ellipsify } from '../../send/send.utils';
import Typography from '../../../components/ui/typography';
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 Identicon from '../../../components/ui/identicon';
import MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message';
import CopyIcon from '../../../components/ui/icon/copy-icon.component';
import {
@ -74,12 +73,15 @@ export default class ConfirmApproveContent extends Component {
isSetApproveForAll: PropTypes.bool,
isApprovalOrRejection: PropTypes.bool,
userAddress: PropTypes.string,
userAcknowledgedGasMissing: PropTypes.bool,
setUserAcknowledgedGasMissing: PropTypes.func,
renderSimulationFailureWarning: PropTypes.bool,
};
state = {
showFullTxDetails: false,
copied: false,
setshowContractDetails: false,
setShowContractDetails: false,
};
renderApproveContentCard({
@ -93,7 +95,11 @@ export default class ConfirmApproveContent extends Component {
footer,
noBorder,
}) {
const { supportsEIP1559 } = this.props;
const {
supportsEIP1559,
renderSimulationFailureWarning,
userAcknowledgedGasMissing,
} = this.props;
const { t } = this.context;
return (
<div
@ -125,9 +131,14 @@ export default class ConfirmApproveContent extends Component {
</Button>
</Box>
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
<EditGasFeeButton />
)}
{showEdit &&
showAdvanceGasFeeOptions &&
supportsEIP1559 &&
!renderSimulationFailureWarning && (
<EditGasFeeButton
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
)}
</div>
)}
<div className="confirm-approve-content__card-content">{content}</div>
@ -148,9 +159,19 @@ export default class ConfirmApproveContent extends Component {
txData,
isMultiLayerFeeNetwork,
supportsEIP1559,
userAcknowledgedGasMissing,
renderSimulationFailureWarning,
} = this.props;
if (!isMultiLayerFeeNetwork && supportsEIP1559) {
return <GasDetailsItem />;
if (
!isMultiLayerFeeNetwork &&
supportsEIP1559 &&
!renderSimulationFailureWarning
) {
return (
<GasDetailsItem
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
);
}
return (
<div className="confirm-approve-content__transaction-details-content">
@ -604,16 +625,10 @@ export default class ConfirmApproveContent extends Component {
render() {
const { t } = this.context;
const {
decimals,
siteImage,
tokenAmount,
customTokenAmount,
origin,
tokenSymbol,
showCustomizeGasModal,
showEditApprovalPermissionModal,
setCustomAmount,
tokenBalance,
useNonceField,
warning,
txData,
@ -621,15 +636,15 @@ export default class ConfirmApproveContent extends Component {
toAddress,
chainId,
rpcPrefs,
isContract,
assetStandard,
userAddress,
tokenId,
tokenAddress,
assetName,
isSetApproveForAll,
userAcknowledgedGasMissing,
setUserAcknowledgedGasMissing,
renderSimulationFailureWarning,
} = this.props;
const { showFullTxDetails, setshowContractDetails } = this.state;
const { showFullTxDetails, setShowContractDetails } = this.state;
return (
<div
@ -672,125 +687,44 @@ export default class ConfirmApproveContent extends Component {
<div className="confirm-approve-content__description">
{this.renderDescription()}
</div>
{assetStandard === TokenStandard.ERC20 ||
(tokenSymbol && !tokenId && !isSetApproveForAll) ? (
<Box className="confirm-approve-content__address-display-content">
<Box display={DISPLAY.FLEX}>
<Identicon
className="confirm-approve-content__address-identicon"
diameter={20}
address={toAddress}
/>
<Typography
variant={TYPOGRAPHY.H6}
fontWeight={FONT_WEIGHT.NORMAL}
color={COLORS.TEXT_ALTERNATIVE}
boxProps={{ marginBottom: 0 }}
>
{ellipsify(toAddress)}
</Typography>
<Button
type="link"
className="confirm-approve-content__copy-address"
onClick={() => {
this.setState({ copied: true });
this.copyTimeout = setTimeout(
() => this.setState({ copied: false }),
SECOND * 3,
);
copyToClipboard(toAddress);
}}
title={
this.state.copied
? t('copiedExclamation')
: t('copyToClipboard')
}
>
<CopyIcon size={9} color="var(--color-icon-default)" />
</Button>
<Button
type="link"
className="confirm-approve-content__etherscan-link"
onClick={() => {
const blockExplorerTokenLink = isContract
? getTokenTrackerLink(
toAddress,
chainId,
null,
userAddress,
{
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
},
)
: getAccountLink(
toAddress,
chainId,
{
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
},
null,
);
global.platform.openTab({
url: blockExplorerTokenLink,
});
}}
target="_blank"
rel="noopener noreferrer"
title={t('etherscanView')}
>
<i
className="fa fa-share-square fa-sm"
style={{ color: 'var(--color-icon-default)', fontSize: 11 }}
title={t('etherscanView')}
/>
</Button>
</Box>
</Box>
) : (
<Box marginBottom={4} marginTop={2}>
<Button
type="link"
className="confirm-approve-content__verify-contract-details"
onClick={() => this.setState({ setshowContractDetails: true })}
>
{t('verifyContractDetails')}
</Button>
{setshowContractDetails && (
<ContractDetailsModal
onClose={() => this.setState({ setshowContractDetails: false })}
tokenName={tokenSymbol}
tokenAddress={tokenAddress}
toAddress={toAddress}
chainId={chainId}
rpcPrefs={rpcPrefs}
tokenId={tokenId}
assetName={assetName}
assetStandard={assetStandard}
/>
)}
</Box>
)}
{assetStandard === TokenStandard.ERC20 ? (
<div className="confirm-approve-content__edit-submission-button-container">
<div
className="confirm-approve-content__medium-link-text cursor-pointer"
onClick={() =>
showEditApprovalPermissionModal({
customTokenAmount,
decimals,
origin,
setCustomAmount,
tokenAmount,
tokenSymbol,
tokenBalance,
})
}
>
{t('editPermission')}
</div>
</div>
) : null}
<Box marginBottom={4} marginTop={2}>
<Button
type="link"
className="confirm-approve-content__verify-contract-details"
onClick={() => this.setState({ setShowContractDetails: true })}
>
{t('verifyContractDetails')}
</Button>
{setShowContractDetails && (
<ContractDetailsModal
onClose={() => this.setState({ setShowContractDetails: false })}
tokenName={tokenSymbol}
tokenAddress={tokenAddress}
toAddress={toAddress}
chainId={chainId}
rpcPrefs={rpcPrefs}
tokenId={tokenId}
assetName={assetName}
assetStandard={assetStandard}
/>
)}
</Box>
<div className="confirm-approve-content__card-wrapper">
{renderSimulationFailureWarning && (
<Box
paddingTop={0}
paddingRight={6}
paddingBottom={4}
paddingLeft={6}
>
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={() =>
setUserAcknowledgedGasMissing(true)
}
/>
</Box>
)}
{this.renderApproveContentCard({
symbol: <i className="fa fa-tag" />,
title: t('transactionFee'),

View File

@ -33,6 +33,10 @@ const props = {
useNonceField: true,
nextNonce: 1,
customNonceValue: '2',
txData: { simulationFails: null },
userAcknowledgedGasMissing: false,
setUserAcknowledgedGasMissing: jest.fn(),
renderSimulationFailureWarning: false,
showCustomizeNonceModal: jest.fn(),
chainId: '1337',
rpcPrefs: {},
@ -54,7 +58,13 @@ describe('ConfirmApproveContent Component', () => {
'By granting permission, you are allowing the following contract to access your funds',
),
).toBeInTheDocument();
expect(queryByText('0x9bc5...fef4')).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument();
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).not.toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument();
expect(queryByText('View full transaction details')).toBeInTheDocument();
expect(queryByText('Edit permission')).toBeInTheDocument();
@ -88,4 +98,124 @@ describe('ConfirmApproveContent Component', () => {
expect(getByText('Granted to:')).toBeInTheDocument();
expect(getByText('0x9bc5...fef4')).toBeInTheDocument();
});
it('should render Confirm approve page correctly and simulation error message without I want to procced anyway link', () => {
props.userAcknowledgedGasMissing = true;
props.renderSimulationFailureWarning = true;
const { queryByText, getByText, getAllByText, getByTestId } =
renderComponent(props);
expect(
queryByText('https://metamask.github.io/test-dapp/'),
).toBeInTheDocument();
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
);
expect(
queryByText(
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
),
).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument();
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).not.toBeInTheDocument();
expect(queryByText('View full transaction details')).toBeInTheDocument();
const editButtons = getAllByText('Edit');
expect(queryByText('Transaction fee')).toBeInTheDocument();
expect(
queryByText('A fee is associated with this request.'),
).toBeInTheDocument();
expect(queryByText(`${props.ethTransactionTotal} ETH`)).toBeInTheDocument();
fireEvent.click(editButtons[0]);
expect(props.showCustomizeGasModal).toHaveBeenCalledTimes(2);
expect(queryByText('Nonce')).toBeInTheDocument();
expect(queryByText('2')).toBeInTheDocument();
fireEvent.click(editButtons[1]);
expect(props.showCustomizeNonceModal).toHaveBeenCalledTimes(2);
const showViewTxDetails = getByText('View full transaction details');
expect(queryByText('Permission request')).not.toBeInTheDocument();
expect(queryByText('Approved asset:')).not.toBeInTheDocument();
expect(queryByText('Granted to:')).not.toBeInTheDocument();
expect(queryByText('Data')).not.toBeInTheDocument();
fireEvent.click(showViewTxDetails);
expect(getByText('Hide full transaction details')).toBeInTheDocument();
expect(getByText('Permission request')).toBeInTheDocument();
expect(getByText('Approved asset:')).toBeInTheDocument();
expect(getByText('Granted to:')).toBeInTheDocument();
expect(getByText('Contract (0x9bc5baF8...fEF4)')).toBeInTheDocument();
expect(getByText('Data')).toBeInTheDocument();
expect(getByText('Function: Approve')).toBeInTheDocument();
expect(
getByText(
'0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
),
).toBeInTheDocument();
});
it('should render Confirm approve page correctly and simulation error message with I want to procced anyway link', () => {
props.userAcknowledgedGasMissing = false;
props.renderSimulationFailureWarning = true;
const { queryByText, getByText, getAllByText, getByTestId } =
renderComponent(props);
expect(
queryByText('https://metamask.github.io/test-dapp/'),
).toBeInTheDocument();
expect(getByTestId('confirm-approve-title').textContent).toStrictEqual(
' Allow access to and transfer of your TestDappCollectibles (#1)? ',
);
expect(
queryByText(
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
),
).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument();
expect(
queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
),
).toBeInTheDocument();
expect(queryByText('I want to proceed anyway')).toBeInTheDocument();
expect(queryByText('View full transaction details')).toBeInTheDocument();
const editButtons = getAllByText('Edit');
expect(queryByText('Transaction fee')).toBeInTheDocument();
expect(
queryByText('A fee is associated with this request.'),
).toBeInTheDocument();
expect(queryByText(`${props.ethTransactionTotal} ETH`)).toBeInTheDocument();
fireEvent.click(editButtons[0]);
expect(props.showCustomizeGasModal).toHaveBeenCalledTimes(3);
expect(queryByText('Nonce')).toBeInTheDocument();
expect(queryByText('2')).toBeInTheDocument();
fireEvent.click(editButtons[1]);
expect(props.showCustomizeNonceModal).toHaveBeenCalledTimes(3);
const showViewTxDetails = getByText('View full transaction details');
expect(queryByText('Permission request')).not.toBeInTheDocument();
expect(queryByText('Approved asset:')).not.toBeInTheDocument();
expect(queryByText('Granted to:')).not.toBeInTheDocument();
expect(queryByText('Data')).not.toBeInTheDocument();
fireEvent.click(showViewTxDetails);
expect(getByText('Hide full transaction details')).toBeInTheDocument();
expect(getByText('Permission request')).toBeInTheDocument();
expect(getByText('Approved asset:')).toBeInTheDocument();
expect(getByText('Granted to:')).toBeInTheDocument();
expect(getByText('Contract (0x9bc5baF8...fEF4)')).toBeInTheDocument();
expect(getByText('Data')).toBeInTheDocument();
expect(getByText('Function: Approve')).toBeInTheDocument();
expect(
getByText(
'0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170',
),
).toBeInTheDocument();
});
});

View File

@ -129,6 +129,7 @@ export default {
useNonceField: true,
nextNonce: 1,
customNonceValue: '2',
txData: { simulationFails: null },
chainId: '1337',
rpcPrefs: {},
isContract: true,

View File

@ -30,6 +30,7 @@ import {
getIsImprovedTokenAllowanceEnabled,
} from '../../selectors';
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
import { useSimulationFailureWarning } from '../../hooks/useSimulationFailureWarning';
import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover';
import EditGasFeePopover from '../../components/app/edit-gas-fee-popover';
import EditGasPopover from '../../components/app/edit-gas-popover/edit-gas-popover.component';
@ -84,6 +85,8 @@ export default function ConfirmApprove({
const [customPermissionAmount, setCustomPermissionAmount] = useState('');
const [submitWarning, setSubmitWarning] = useState('');
const [isContract, setIsContract] = useState(false);
const [userAcknowledgedGasMissing, setUserAcknowledgedGasMissing] =
useState(false);
const supportsEIP1559 = networkAndAccountSupports1559;
@ -97,6 +100,9 @@ export default function ConfirmApprove({
showCustomizeGasPopover,
closeCustomizeGasPopover,
} = useApproveTransaction();
const renderSimulationFailureWarning = useSimulationFailureWarning(
userAcknowledgedGasMissing,
);
useEffect(() => {
if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) {
@ -284,6 +290,9 @@ export default function ConfirmApprove({
useNonceField={useNonceField}
nextNonce={nextNonce}
customNonceValue={customNonceValue}
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing}
renderSimulationFailureWarning={renderSimulationFailureWarning}
updateCustomNonce={(value) => {
dispatch(updateCustomNonce(value));
}}

View File

@ -19,7 +19,7 @@ import CopyRawData from '../../components/app/transaction-decoding/components/ui
import { PRIMARY, SECONDARY } from '../../helpers/constants/common';
import TextField from '../../components/ui/text-field';
import ActionableMessage from '../../components/ui/actionable-message';
import SimulationErrorMessage from '../../components/ui/simulation-error-message';
import Disclosure from '../../components/ui/disclosure';
import { EVENT } from '../../../shared/constants/metametrics';
import {
@ -586,18 +586,10 @@ export default class ConfirmTransactionBase extends Component {
const simulationFailureWarning = () => (
<div className="confirm-page-container-content__error-container">
<ActionableMessage
message={t('simulationErrorMessageV2')}
useIcon
iconFillColor="var(--color-error-default)"
type="danger"
primaryActionV2={
userAcknowledgedGasMissing === true
? undefined
: {
label: t('proceedWithTransaction'),
onClick: () => this.setUserAcknowledgedGasMissing(),
}
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={() =>
this.setUserAcknowledgedGasMissing()
}
/>
</div>

View File

@ -57,6 +57,8 @@ import {
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
} from '../../../shared/constants/tokens';
import { ConfirmPageContainerNavigation } from '../../components/app/confirm-page-container';
import { useSimulationFailureWarning } from '../../hooks/useSimulationFailureWarning';
import SimulationErrorMessage from '../../components/ui/simulation-error-message';
export default function TokenAllowance({
origin,
@ -93,7 +95,12 @@ export default function TokenAllowance({
dappProposedTokenAmount !== '0',
);
const [errorText, setErrorText] = useState('');
const [userAcknowledgedGasMissing, setUserAcknowledgedGasMissing] =
useState(false);
const renderSimulationFailureWarning = useSimulationFailureWarning(
userAcknowledgedGasMissing,
);
const currentAccount = useSelector(getCurrentAccountWithSendEtherInfo);
const networkIdentifier = useSelector(getNetworkIdentifier);
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
@ -391,6 +398,21 @@ export default function TokenAllowance({
)}
{!isFirstPage && (
<Box className="token-allowance-container__card-wrapper">
{renderSimulationFailureWarning && (
<Box
paddingTop={0}
paddingRight={4}
paddingBottom={4}
paddingLeft={4}
>
<SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={() =>
setUserAcknowledgedGasMissing(true)
}
/>
</Box>
)}
<ApproveContentCard
symbol={<i className="fa fa-tag" />}
title={t('transactionFee')}
@ -404,6 +426,8 @@ export default function TokenAllowance({
ethTransactionTotal={ethTransactionTotal}
nativeCurrency={nativeCurrency}
fullTxData={fullTxData}
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
renderSimulationFailureWarning={renderSimulationFailureWarning}
hexTransactionTotal={hexTransactionTotal}
fiatTransactionTotal={fiatTransactionTotal}
currentCurrency={currentCurrency}
@ -449,6 +473,9 @@ export default function TokenAllowance({
noBorder
supportsEIP1559={supportsEIP1559}
isSetApproveForAll={isSetApproveForAll}
fullTxData={fullTxData}
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
renderSimulationFailureWarning={renderSimulationFailureWarning}
isApprovalOrRejection={isApprovalOrRejection}
data={customTxParamsData || data}
/>