mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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:
parent
4b18d48366
commit
222a7dd881
@ -43,6 +43,8 @@ export default function ApproveContentCard({
|
|||||||
isSetApproveForAll,
|
isSetApproveForAll,
|
||||||
isApprovalOrRejection,
|
isApprovalOrRejection,
|
||||||
data,
|
data,
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
renderSimulationFailureWarning,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
|
|
||||||
@ -91,9 +93,14 @@ export default function ApproveContentCard({
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
|
{showEdit &&
|
||||||
<EditGasFeeButton />
|
showAdvanceGasFeeOptions &&
|
||||||
)}
|
supportsEIP1559 &&
|
||||||
|
!renderSimulationFailureWarning && (
|
||||||
|
<EditGasFeeButton
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
@ -102,8 +109,12 @@ export default function ApproveContentCard({
|
|||||||
className="approve-content-card-container__card-content"
|
className="approve-content-card-container__card-content"
|
||||||
>
|
>
|
||||||
{renderTransactionDetailsContent &&
|
{renderTransactionDetailsContent &&
|
||||||
(!isMultiLayerFeeNetwork && supportsEIP1559 ? (
|
(!isMultiLayerFeeNetwork &&
|
||||||
<GasDetailsItem />
|
supportsEIP1559 &&
|
||||||
|
!renderSimulationFailureWarning ? (
|
||||||
|
<GasDetailsItem
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
display={DISPLAY.FLEX}
|
display={DISPLAY.FLEX}
|
||||||
@ -301,4 +312,12 @@ ApproveContentCard.propTypes = {
|
|||||||
* Current transaction data
|
* Current transaction data
|
||||||
*/
|
*/
|
||||||
data: PropTypes.string,
|
data: PropTypes.string,
|
||||||
|
/**
|
||||||
|
* User acknowledge gas is missing or not
|
||||||
|
*/
|
||||||
|
userAcknowledgedGasMissing: PropTypes.bool,
|
||||||
|
/**
|
||||||
|
* Render simulation failure warning
|
||||||
|
*/
|
||||||
|
renderSimulationFailureWarning: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import { submittedPendingTransactionsSelector } from '../../../selectors/transac
|
|||||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import ActionableMessage from '../../ui/actionable-message/actionable-message';
|
import ActionableMessage from '../../ui/actionable-message/actionable-message';
|
||||||
|
import SimulationErrorMessage from '../../ui/simulation-error-message';
|
||||||
import Typography from '../../ui/typography';
|
import Typography from '../../ui/typography';
|
||||||
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
||||||
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';
|
||||||
@ -23,19 +24,9 @@ const TransactionAlerts = ({
|
|||||||
return (
|
return (
|
||||||
<div className="transaction-alerts">
|
<div className="transaction-alerts">
|
||||||
{supportsEIP1559 && hasSimulationError && (
|
{supportsEIP1559 && hasSimulationError && (
|
||||||
<ActionableMessage
|
<SimulationErrorMessage
|
||||||
message={t('simulationErrorMessageV2')}
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
useIcon
|
setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing}
|
||||||
iconFillColor="var(--color-error-default)"
|
|
||||||
type="danger"
|
|
||||||
primaryActionV2={
|
|
||||||
userAcknowledgedGasMissing === true
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
label: t('proceedWithTransaction'),
|
|
||||||
onClick: setUserAcknowledgedGasMissing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{supportsEIP1559 && pendingTransactions?.length > 0 && (
|
{supportsEIP1559 && pendingTransactions?.length > 0 && (
|
||||||
|
1
ui/components/ui/simulation-error-message/index.js
Normal file
1
ui/components/ui/simulation-error-message/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './simulation-error-message';
|
@ -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,
|
||||||
|
};
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
18
ui/hooks/useSimulationFailureWarning.js
Normal file
18
ui/hooks/useSimulationFailureWarning.js
Normal 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;
|
||||||
|
}
|
@ -2,16 +2,15 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import copyToClipboard from 'copy-to-clipboard';
|
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 UrlIcon from '../../../components/ui/url-icon';
|
||||||
import { addressSummary } from '../../../helpers/utils/util';
|
import { addressSummary } from '../../../helpers/utils/util';
|
||||||
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
|
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
|
||||||
import { ellipsify } from '../../send/send.utils';
|
|
||||||
import Typography from '../../../components/ui/typography';
|
import Typography from '../../../components/ui/typography';
|
||||||
import Box from '../../../components/ui/box';
|
import Box from '../../../components/ui/box';
|
||||||
import Button from '../../../components/ui/button';
|
import Button from '../../../components/ui/button';
|
||||||
|
import SimulationErrorMessage from '../../../components/ui/simulation-error-message';
|
||||||
import EditGasFeeButton from '../../../components/app/edit-gas-fee-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 MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message';
|
||||||
import CopyIcon from '../../../components/ui/icon/copy-icon.component';
|
import CopyIcon from '../../../components/ui/icon/copy-icon.component';
|
||||||
import {
|
import {
|
||||||
@ -74,12 +73,15 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
isSetApproveForAll: PropTypes.bool,
|
isSetApproveForAll: PropTypes.bool,
|
||||||
isApprovalOrRejection: PropTypes.bool,
|
isApprovalOrRejection: PropTypes.bool,
|
||||||
userAddress: PropTypes.string,
|
userAddress: PropTypes.string,
|
||||||
|
userAcknowledgedGasMissing: PropTypes.bool,
|
||||||
|
setUserAcknowledgedGasMissing: PropTypes.func,
|
||||||
|
renderSimulationFailureWarning: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
showFullTxDetails: false,
|
showFullTxDetails: false,
|
||||||
copied: false,
|
copied: false,
|
||||||
setshowContractDetails: false,
|
setShowContractDetails: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderApproveContentCard({
|
renderApproveContentCard({
|
||||||
@ -93,7 +95,11 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
footer,
|
footer,
|
||||||
noBorder,
|
noBorder,
|
||||||
}) {
|
}) {
|
||||||
const { supportsEIP1559 } = this.props;
|
const {
|
||||||
|
supportsEIP1559,
|
||||||
|
renderSimulationFailureWarning,
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
} = this.props;
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -125,9 +131,14 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
|
{showEdit &&
|
||||||
<EditGasFeeButton />
|
showAdvanceGasFeeOptions &&
|
||||||
)}
|
supportsEIP1559 &&
|
||||||
|
!renderSimulationFailureWarning && (
|
||||||
|
<EditGasFeeButton
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="confirm-approve-content__card-content">{content}</div>
|
<div className="confirm-approve-content__card-content">{content}</div>
|
||||||
@ -148,9 +159,19 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
txData,
|
txData,
|
||||||
isMultiLayerFeeNetwork,
|
isMultiLayerFeeNetwork,
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
renderSimulationFailureWarning,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!isMultiLayerFeeNetwork && supportsEIP1559) {
|
if (
|
||||||
return <GasDetailsItem />;
|
!isMultiLayerFeeNetwork &&
|
||||||
|
supportsEIP1559 &&
|
||||||
|
!renderSimulationFailureWarning
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<GasDetailsItem
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="confirm-approve-content__transaction-details-content">
|
<div className="confirm-approve-content__transaction-details-content">
|
||||||
@ -604,16 +625,10 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const {
|
const {
|
||||||
decimals,
|
|
||||||
siteImage,
|
siteImage,
|
||||||
tokenAmount,
|
|
||||||
customTokenAmount,
|
|
||||||
origin,
|
origin,
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
showCustomizeGasModal,
|
showCustomizeGasModal,
|
||||||
showEditApprovalPermissionModal,
|
|
||||||
setCustomAmount,
|
|
||||||
tokenBalance,
|
|
||||||
useNonceField,
|
useNonceField,
|
||||||
warning,
|
warning,
|
||||||
txData,
|
txData,
|
||||||
@ -621,15 +636,15 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
toAddress,
|
toAddress,
|
||||||
chainId,
|
chainId,
|
||||||
rpcPrefs,
|
rpcPrefs,
|
||||||
isContract,
|
|
||||||
assetStandard,
|
assetStandard,
|
||||||
userAddress,
|
|
||||||
tokenId,
|
tokenId,
|
||||||
tokenAddress,
|
tokenAddress,
|
||||||
assetName,
|
assetName,
|
||||||
isSetApproveForAll,
|
userAcknowledgedGasMissing,
|
||||||
|
setUserAcknowledgedGasMissing,
|
||||||
|
renderSimulationFailureWarning,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { showFullTxDetails, setshowContractDetails } = this.state;
|
const { showFullTxDetails, setShowContractDetails } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -672,125 +687,44 @@ export default class ConfirmApproveContent extends Component {
|
|||||||
<div className="confirm-approve-content__description">
|
<div className="confirm-approve-content__description">
|
||||||
{this.renderDescription()}
|
{this.renderDescription()}
|
||||||
</div>
|
</div>
|
||||||
{assetStandard === TokenStandard.ERC20 ||
|
<Box marginBottom={4} marginTop={2}>
|
||||||
(tokenSymbol && !tokenId && !isSetApproveForAll) ? (
|
<Button
|
||||||
<Box className="confirm-approve-content__address-display-content">
|
type="link"
|
||||||
<Box display={DISPLAY.FLEX}>
|
className="confirm-approve-content__verify-contract-details"
|
||||||
<Identicon
|
onClick={() => this.setState({ setShowContractDetails: true })}
|
||||||
className="confirm-approve-content__address-identicon"
|
>
|
||||||
diameter={20}
|
{t('verifyContractDetails')}
|
||||||
address={toAddress}
|
</Button>
|
||||||
/>
|
{setShowContractDetails && (
|
||||||
<Typography
|
<ContractDetailsModal
|
||||||
variant={TYPOGRAPHY.H6}
|
onClose={() => this.setState({ setShowContractDetails: false })}
|
||||||
fontWeight={FONT_WEIGHT.NORMAL}
|
tokenName={tokenSymbol}
|
||||||
color={COLORS.TEXT_ALTERNATIVE}
|
tokenAddress={tokenAddress}
|
||||||
boxProps={{ marginBottom: 0 }}
|
toAddress={toAddress}
|
||||||
>
|
chainId={chainId}
|
||||||
{ellipsify(toAddress)}
|
rpcPrefs={rpcPrefs}
|
||||||
</Typography>
|
tokenId={tokenId}
|
||||||
<Button
|
assetName={assetName}
|
||||||
type="link"
|
assetStandard={assetStandard}
|
||||||
className="confirm-approve-content__copy-address"
|
/>
|
||||||
onClick={() => {
|
)}
|
||||||
this.setState({ copied: true });
|
</Box>
|
||||||
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}
|
|
||||||
<div className="confirm-approve-content__card-wrapper">
|
<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({
|
{this.renderApproveContentCard({
|
||||||
symbol: <i className="fa fa-tag" />,
|
symbol: <i className="fa fa-tag" />,
|
||||||
title: t('transactionFee'),
|
title: t('transactionFee'),
|
||||||
|
@ -33,6 +33,10 @@ const props = {
|
|||||||
useNonceField: true,
|
useNonceField: true,
|
||||||
nextNonce: 1,
|
nextNonce: 1,
|
||||||
customNonceValue: '2',
|
customNonceValue: '2',
|
||||||
|
txData: { simulationFails: null },
|
||||||
|
userAcknowledgedGasMissing: false,
|
||||||
|
setUserAcknowledgedGasMissing: jest.fn(),
|
||||||
|
renderSimulationFailureWarning: false,
|
||||||
showCustomizeNonceModal: jest.fn(),
|
showCustomizeNonceModal: jest.fn(),
|
||||||
chainId: '1337',
|
chainId: '1337',
|
||||||
rpcPrefs: {},
|
rpcPrefs: {},
|
||||||
@ -54,7 +58,13 @@ describe('ConfirmApproveContent Component', () => {
|
|||||||
'By granting permission, you are allowing the following contract to access your funds',
|
'By granting permission, you are allowing the following contract to access your funds',
|
||||||
),
|
),
|
||||||
).toBeInTheDocument();
|
).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('View full transaction details')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(queryByText('Edit permission')).toBeInTheDocument();
|
expect(queryByText('Edit permission')).toBeInTheDocument();
|
||||||
@ -88,4 +98,124 @@ describe('ConfirmApproveContent Component', () => {
|
|||||||
expect(getByText('Granted to:')).toBeInTheDocument();
|
expect(getByText('Granted to:')).toBeInTheDocument();
|
||||||
expect(getByText('0x9bc5...fef4')).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -129,6 +129,7 @@ export default {
|
|||||||
useNonceField: true,
|
useNonceField: true,
|
||||||
nextNonce: 1,
|
nextNonce: 1,
|
||||||
customNonceValue: '2',
|
customNonceValue: '2',
|
||||||
|
txData: { simulationFails: null },
|
||||||
chainId: '1337',
|
chainId: '1337',
|
||||||
rpcPrefs: {},
|
rpcPrefs: {},
|
||||||
isContract: true,
|
isContract: true,
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
getIsImprovedTokenAllowanceEnabled,
|
getIsImprovedTokenAllowanceEnabled,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
import { useApproveTransaction } from '../../hooks/useApproveTransaction';
|
||||||
|
import { useSimulationFailureWarning } from '../../hooks/useSimulationFailureWarning';
|
||||||
import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover';
|
import AdvancedGasFeePopover from '../../components/app/advanced-gas-fee-popover';
|
||||||
import EditGasFeePopover from '../../components/app/edit-gas-fee-popover';
|
import EditGasFeePopover from '../../components/app/edit-gas-fee-popover';
|
||||||
import EditGasPopover from '../../components/app/edit-gas-popover/edit-gas-popover.component';
|
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 [customPermissionAmount, setCustomPermissionAmount] = useState('');
|
||||||
const [submitWarning, setSubmitWarning] = useState('');
|
const [submitWarning, setSubmitWarning] = useState('');
|
||||||
const [isContract, setIsContract] = useState(false);
|
const [isContract, setIsContract] = useState(false);
|
||||||
|
const [userAcknowledgedGasMissing, setUserAcknowledgedGasMissing] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const supportsEIP1559 = networkAndAccountSupports1559;
|
const supportsEIP1559 = networkAndAccountSupports1559;
|
||||||
|
|
||||||
@ -97,6 +100,9 @@ export default function ConfirmApprove({
|
|||||||
showCustomizeGasPopover,
|
showCustomizeGasPopover,
|
||||||
closeCustomizeGasPopover,
|
closeCustomizeGasPopover,
|
||||||
} = useApproveTransaction();
|
} = useApproveTransaction();
|
||||||
|
const renderSimulationFailureWarning = useSimulationFailureWarning(
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) {
|
if (customPermissionAmount && previousTokenAmount.current !== tokenAmount) {
|
||||||
@ -284,6 +290,9 @@ export default function ConfirmApprove({
|
|||||||
useNonceField={useNonceField}
|
useNonceField={useNonceField}
|
||||||
nextNonce={nextNonce}
|
nextNonce={nextNonce}
|
||||||
customNonceValue={customNonceValue}
|
customNonceValue={customNonceValue}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
setUserAcknowledgedGasMissing={setUserAcknowledgedGasMissing}
|
||||||
|
renderSimulationFailureWarning={renderSimulationFailureWarning}
|
||||||
updateCustomNonce={(value) => {
|
updateCustomNonce={(value) => {
|
||||||
dispatch(updateCustomNonce(value));
|
dispatch(updateCustomNonce(value));
|
||||||
}}
|
}}
|
||||||
|
@ -19,7 +19,7 @@ import CopyRawData from '../../components/app/transaction-decoding/components/ui
|
|||||||
|
|
||||||
import { PRIMARY, SECONDARY } from '../../helpers/constants/common';
|
import { PRIMARY, SECONDARY } from '../../helpers/constants/common';
|
||||||
import TextField from '../../components/ui/text-field';
|
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 Disclosure from '../../components/ui/disclosure';
|
||||||
import { EVENT } from '../../../shared/constants/metametrics';
|
import { EVENT } from '../../../shared/constants/metametrics';
|
||||||
import {
|
import {
|
||||||
@ -586,18 +586,10 @@ export default class ConfirmTransactionBase extends Component {
|
|||||||
|
|
||||||
const simulationFailureWarning = () => (
|
const simulationFailureWarning = () => (
|
||||||
<div className="confirm-page-container-content__error-container">
|
<div className="confirm-page-container-content__error-container">
|
||||||
<ActionableMessage
|
<SimulationErrorMessage
|
||||||
message={t('simulationErrorMessageV2')}
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
useIcon
|
setUserAcknowledgedGasMissing={() =>
|
||||||
iconFillColor="var(--color-error-default)"
|
this.setUserAcknowledgedGasMissing()
|
||||||
type="danger"
|
|
||||||
primaryActionV2={
|
|
||||||
userAcknowledgedGasMissing === true
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
label: t('proceedWithTransaction'),
|
|
||||||
onClick: () => this.setUserAcknowledgedGasMissing(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,6 +57,8 @@ import {
|
|||||||
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
|
NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX,
|
||||||
} from '../../../shared/constants/tokens';
|
} from '../../../shared/constants/tokens';
|
||||||
import { ConfirmPageContainerNavigation } from '../../components/app/confirm-page-container';
|
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({
|
export default function TokenAllowance({
|
||||||
origin,
|
origin,
|
||||||
@ -93,7 +95,12 @@ export default function TokenAllowance({
|
|||||||
dappProposedTokenAmount !== '0',
|
dappProposedTokenAmount !== '0',
|
||||||
);
|
);
|
||||||
const [errorText, setErrorText] = useState('');
|
const [errorText, setErrorText] = useState('');
|
||||||
|
const [userAcknowledgedGasMissing, setUserAcknowledgedGasMissing] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const renderSimulationFailureWarning = useSimulationFailureWarning(
|
||||||
|
userAcknowledgedGasMissing,
|
||||||
|
);
|
||||||
const currentAccount = useSelector(getCurrentAccountWithSendEtherInfo);
|
const currentAccount = useSelector(getCurrentAccountWithSendEtherInfo);
|
||||||
const networkIdentifier = useSelector(getNetworkIdentifier);
|
const networkIdentifier = useSelector(getNetworkIdentifier);
|
||||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
||||||
@ -391,6 +398,21 @@ export default function TokenAllowance({
|
|||||||
)}
|
)}
|
||||||
{!isFirstPage && (
|
{!isFirstPage && (
|
||||||
<Box className="token-allowance-container__card-wrapper">
|
<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
|
<ApproveContentCard
|
||||||
symbol={<i className="fa fa-tag" />}
|
symbol={<i className="fa fa-tag" />}
|
||||||
title={t('transactionFee')}
|
title={t('transactionFee')}
|
||||||
@ -404,6 +426,8 @@ export default function TokenAllowance({
|
|||||||
ethTransactionTotal={ethTransactionTotal}
|
ethTransactionTotal={ethTransactionTotal}
|
||||||
nativeCurrency={nativeCurrency}
|
nativeCurrency={nativeCurrency}
|
||||||
fullTxData={fullTxData}
|
fullTxData={fullTxData}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
renderSimulationFailureWarning={renderSimulationFailureWarning}
|
||||||
hexTransactionTotal={hexTransactionTotal}
|
hexTransactionTotal={hexTransactionTotal}
|
||||||
fiatTransactionTotal={fiatTransactionTotal}
|
fiatTransactionTotal={fiatTransactionTotal}
|
||||||
currentCurrency={currentCurrency}
|
currentCurrency={currentCurrency}
|
||||||
@ -449,6 +473,9 @@ export default function TokenAllowance({
|
|||||||
noBorder
|
noBorder
|
||||||
supportsEIP1559={supportsEIP1559}
|
supportsEIP1559={supportsEIP1559}
|
||||||
isSetApproveForAll={isSetApproveForAll}
|
isSetApproveForAll={isSetApproveForAll}
|
||||||
|
fullTxData={fullTxData}
|
||||||
|
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||||
|
renderSimulationFailureWarning={renderSimulationFailureWarning}
|
||||||
isApprovalOrRejection={isApprovalOrRejection}
|
isApprovalOrRejection={isApprovalOrRejection}
|
||||||
data={customTxParamsData || data}
|
data={customTxParamsData || data}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user