Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
29 changes: 24 additions & 5 deletions ui/components/app/approve-content-card/approve-content-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export default function ApproveContentCard({
isSetApproveForAll,
isApprovalOrRejection,
data,
userAcknowledgedGasMissing,
renderSimulationFailureWarning,
}) {
const t = useContext(I18nContext);

Expand Down Expand Up @@ -91,9 +93,14 @@ export default function ApproveContentCard({
</Button>
</Box>
)}
{showEdit && showAdvanceGasFeeOptions && supportsEIP1559 && (
<EditGasFeeButton />
)}
{showEdit &&
showAdvanceGasFeeOptions &&
supportsEIP1559 &&
!renderSimulationFailureWarning && (
<EditGasFeeButton
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/>
)}
</Box>
)}
<Box
Expand All @@ -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}
Expand Down Expand Up @@ -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,
};
17 changes: 4 additions & 13 deletions ui/components/app/transaction-alerts/transaction-alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { submittedPendingTransactionsSelector } from '../../../selectors';
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';
Expand All @@ -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 && (
Expand Down
1 change: 1 addition & 0 deletions ui/components/ui/simulation-error-message/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './simulation-error-message';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better place for this will be ui/components/app

Original file line number Diff line number Diff line change
@@ -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,
};
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions ui/hooks/useSimulationFailureWarning.js
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
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 MultiLayerFeeMessage from '../../../components/app/multilayer-fee-message';
import {
Expand Down Expand Up @@ -65,12 +66,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({
Expand All @@ -84,7 +88,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
Expand Down Expand Up @@ -116,9 +124,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>
Expand All @@ -139,9 +152,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">
Expand Down Expand Up @@ -491,8 +514,11 @@ export default class ConfirmApproveContent extends Component {
tokenId,
tokenAddress,
assetName,
userAcknowledgedGasMissing,
setUserAcknowledgedGasMissing,
renderSimulationFailureWarning,
} = this.props;
const { showFullTxDetails, setshowContractDetails } = this.state;
const { showFullTxDetails, setShowContractDetails } = this.state;

return (
<div
Expand Down Expand Up @@ -539,13 +565,13 @@ export default class ConfirmApproveContent extends Component {
<Button
type="link"
className="confirm-approve-content__verify-contract-details"
onClick={() => this.setState({ setshowContractDetails: true })}
onClick={() => this.setState({ setShowContractDetails: true })}
>
{t('verifyContractDetails')}
</Button>
{setshowContractDetails && (
{setShowContractDetails && (
<ContractDetailsModal
onClose={() => this.setState({ setshowContractDetails: false })}
onClose={() => this.setState({ setShowContractDetails: false })}
tokenName={tokenSymbol}
tokenAddress={tokenAddress}
toAddress={toAddress}
Expand All @@ -558,6 +584,21 @@ export default class ConfirmApproveContent extends Component {
)}
</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'),
Expand Down
Loading