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

Increase friction to bypass estimated revert (#12576)

* If a transaction would revert/fail,
    1. hide the gas estimate info.
    2. Disable the confirm button.
    3. All user to enable the confirm button anyways.
    4. Do not show the default Transaction error message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Always return a value for hasSimulationError

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Use primary button of action message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Remove hasSimulationError from getErrorKey

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Move confirm anyways logic to base component.
Change message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Disable edit if there's simulation error

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* hide confirm anyways button once clicked.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Move Actionable Primary Action to flex-end

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Fix unit tests

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Fix nested ternary lint issues

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* add !confirmAnyways to conditions to show GasDetails.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* ConfirmAnyways should be read from state

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Rename tryAnywayOption

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Remove await tick

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint issue fix

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes after rebase

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* description should show that it's content being tested.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* If a transaction would revert/fail,
    1. hide the gas estimate info.
    2. Disable the confirm button.
    3. All user to enable the confirm button anyways.
    4. Do not show the default Transaction error message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Always return a value for hasSimulationError

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Use primary button of action message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Remove hasSimulationError from getErrorKey

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Move confirm anyways logic to base component.
Change message

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Disable edit if there's simulation error

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* hide confirm anyways button once clicked.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Move Actionable Primary Action to flex-end

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Fix unit tests

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Fix nested ternary lint issues

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* add !confirmAnyways to conditions to show GasDetails.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* ConfirmAnyways should be read from state

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Rename tryAnywayOption

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Remove await tick

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint issue fix

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Lint fixes after rebase

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* description should show that it's content being tested.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* Move simulation fails message inline with gas details component (#12705)

* Move simulation fails message inline with gas details component

* Remove old unit tests

Co-authored-by: Dan Miller <danjm.com@gmail.com>

* lint fix

* use an XOR operation.

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

* The changes in this file are no longer needed because we hide the edit button now

Signed-off-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>

Co-authored-by: Dan Miller <danjm.com@gmail.com>
This commit is contained in:
Olusegun Akintayo 2021-11-22 16:13:30 +04:00 committed by GitHub
parent 6838a3d074
commit 143a5c4a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 363 additions and 150 deletions

View File

@ -2314,6 +2314,9 @@
"signed": {
"message": "Signed"
},
"simulationErrorMessage": {
"message": "This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended."
},
"skip": {
"message": "Skip"
},
@ -2977,6 +2980,9 @@
"tryAgain": {
"message": "Try again"
},
"tryAnywayOption": {
"message": "I will try anyway"
},
"turnOnTokenDetection": {
"message": "Turn on enhanced token detection"
},

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Tabs, Tab } from '../../../ui/tabs';
import ErrorMessage from '../../../ui/error-message';
import ActionableMessage from '../../../ui/actionable-message/actionable-message';
import { PageContainerFooter } from '../../../ui/page-container';
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';
@ -17,6 +18,7 @@ export default class ConfirmPageContainerContent extends Component {
detailsComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
hasSimulationError: PropTypes.bool,
hideSubtitle: PropTypes.bool,
identiconAddress: PropTypes.string,
nonce: PropTypes.string,
@ -31,8 +33,10 @@ export default class ConfirmPageContainerContent extends Component {
onCancel: PropTypes.func,
cancelText: PropTypes.string,
onSubmit: PropTypes.func,
onConfirmAnyways: PropTypes.func,
submitText: PropTypes.string,
disabled: PropTypes.bool,
hideConfirmAnyways: PropTypes.bool,
unapprovedTxCount: PropTypes.number,
rejectNText: PropTypes.string,
hideTitle: PropTypes.boolean,
@ -71,6 +75,7 @@ export default class ConfirmPageContainerContent extends Component {
action,
errorKey,
errorMessage,
hasSimulationError,
title,
titleComponent,
subtitleComponent,
@ -91,14 +96,32 @@ export default class ConfirmPageContainerContent extends Component {
origin,
ethGasPriceWarning,
hideTitle,
onConfirmAnyways,
hideConfirmAnyways,
} = this.props;
const primaryAction = hideConfirmAnyways
? null
: {
label: this.context.t('tryAnywayOption'),
onClick: onConfirmAnyways,
};
return (
<div className="confirm-page-container-content">
{warning ? <ConfirmPageContainerWarning warning={warning} /> : null}
{ethGasPriceWarning && (
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
)}
{hasSimulationError && (
<div className="confirm-page-container-content__error-container">
<ActionableMessage
type="danger"
primaryAction={primaryAction}
message={this.context.t('simulationErrorMessage')}
/>
</div>
)}
<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border':
@ -115,7 +138,7 @@ export default class ConfirmPageContainerContent extends Component {
hideTitle={hideTitle}
/>
{this.renderContent()}
{(errorKey || errorMessage) && (
{(errorKey || errorMessage) && !hasSimulationError && (
<div className="confirm-page-container-content__error-container">
<ErrorMessage errorMessage={errorMessage} errorKey={errorKey} />
</div>

View File

@ -0,0 +1,124 @@
import { fireEvent } from '@testing-library/react';
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys';
import ConfirmPageContainerContent from './confirm-page-container-content.component';
describe('Confirm Page Container Content', () => {
const mockStore = {
metamask: {
provider: {
type: 'test',
},
},
};
const store = configureMockStore()(mockStore);
let props = {};
beforeEach(() => {
const mockOnCancel = jest.fn();
const mockOnCancelAll = jest.fn();
const mockOnSubmit = jest.fn();
const mockOnConfirmAnyways = jest.fn();
props = {
action: ' Withdraw Stake',
errorMessage: null,
errorKey: null,
hasSimulationError: true,
onCancelAll: mockOnCancelAll,
onCancel: mockOnCancel,
cancelText: 'Reject',
onSubmit: mockOnSubmit,
onConfirmAnyways: mockOnConfirmAnyways,
submitText: 'Confirm',
disabled: true,
origin: 'http://localhost:4200',
hideTitle: false,
};
});
it('render ConfirmPageContainer component with simulation error', async () => {
const { queryByText, getByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,
);
expect(
queryByText('Transaction Error. Exception thrown in contract code.'),
).not.toBeInTheDocument();
expect(
queryByText(
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
),
).toBeInTheDocument();
expect(queryByText('I will try anyway')).toBeInTheDocument();
const confirmButton = getByText('Confirm');
expect(getByText('Confirm').closest('button')).toBeDisabled();
fireEvent.click(confirmButton);
expect(props.onSubmit).toHaveBeenCalledTimes(0);
const iWillTryButton = getByText('I will try anyway');
fireEvent.click(iWillTryButton);
expect(props.onConfirmAnyways).toHaveBeenCalledTimes(1);
const cancelButton = getByText('Reject');
fireEvent.click(cancelButton);
expect(props.onCancel).toHaveBeenCalledTimes(1);
});
it('render ConfirmPageContainer component with another error', async () => {
props.hasSimulationError = false;
props.disabled = true;
props.errorKey = TRANSACTION_ERROR_KEY;
const { queryByText, getByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,
);
expect(
queryByText(
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
),
).not.toBeInTheDocument();
expect(queryByText('I will try anyway')).not.toBeInTheDocument();
expect(getByText('Confirm').closest('button')).toBeDisabled();
expect(
getByText('Transaction Error. Exception thrown in contract code.'),
).toBeInTheDocument();
const cancelButton = getByText('Reject');
fireEvent.click(cancelButton);
expect(props.onCancel).toHaveBeenCalledTimes(1);
});
it('render ConfirmPageContainer component with no errors', async () => {
props.hasSimulationError = false;
props.disabled = false;
const { queryByText, getByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,
);
expect(
queryByText(
'This transaction is expected to fail. Trying to execute it is expected to be expensive but fail, and is not recommended.',
),
).not.toBeInTheDocument();
expect(
queryByText('Transaction Error. Exception thrown in contract code.'),
).not.toBeInTheDocument();
expect(queryByText('I will try anyway')).not.toBeInTheDocument();
const confirmButton = getByText('Confirm');
fireEvent.click(confirmButton);
expect(props.onSubmit).toHaveBeenCalledTimes(1);
const cancelButton = getByText('Reject');
fireEvent.click(cancelButton);
expect(props.onCancel).toHaveBeenCalledTimes(1);
});
});

View File

@ -26,6 +26,7 @@ export default function ActionableMessage({
type = 'default',
useIcon = false,
iconFillColor = '',
roundedButtons,
}) {
const actionableMessageClassName = classnames(
'actionable-message',
@ -35,6 +36,9 @@ export default function ActionableMessage({
{ 'actionable-message--with-icon': useIcon },
);
const onlyOneAction =
(primaryAction && !secondaryAction) || (secondaryAction && !primaryAction);
return (
<div className={actionableMessageClassName}>
{useIcon ? <InfoTooltipIcon fillColor={iconFillColor} /> : null}
@ -47,12 +51,19 @@ export default function ActionableMessage({
)}
<div className="actionable-message__message">{message}</div>
{(primaryAction || secondaryAction) && (
<div className="actionable-message__actions">
<div
className={classnames('actionable-message__actions', {
'actionable-message__actions--single': onlyOneAction,
})}
>
{primaryAction && (
<button
className={classnames(
'actionable-message__action',
'actionable-message__action--primary',
{
'actionable-message__action--rounded': roundedButtons,
},
)}
onClick={primaryAction.onClick}
>
@ -64,6 +75,9 @@ export default function ActionableMessage({
className={classnames(
'actionable-message__action',
'actionable-message__action--secondary',
{
'actionable-message__action--rounded': roundedButtons,
},
)}
onClick={secondaryAction.onClick}
>
@ -92,4 +106,5 @@ ActionableMessage.propTypes = {
infoTooltipText: PropTypes.string,
useIcon: PropTypes.bool,
iconFillColor: PropTypes.string,
roundedButtons: PropTypes.bool,
};

View File

@ -38,14 +38,22 @@
&__actions {
display: flex;
width: 80%;
justify-content: space-evenly;
justify-content: flex-end;
align-items: center;
margin-top: 10px;
color: $Blue-600;
&--single {
width: 100%;
}
}
&__action {
font-weight: bold;
&--rounded {
border-radius: 8px;
}
}
&__info-tooltip-wrapper {

View File

@ -13,7 +13,6 @@ import {
} from '../../helpers/constants/routes';
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
GAS_LIMIT_TOO_LOW_ERROR_KEY,
ETH_GAS_PRICE_FETCH_WARNING_KEY,
GAS_PRICE_FETCH_FAILURE_ERROR_KEY,
@ -21,6 +20,7 @@ import {
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../helpers/constants/common';
import TextField from '../../components/ui/text-field';
import ActionableMessage from '../../components/ui/actionable-message';
import {
TRANSACTION_TYPES,
TRANSACTION_STATUSES,
@ -143,6 +143,7 @@ export default class ConfirmTransactionBase extends Component {
submitWarning: '',
ethGasPriceWarning: '',
editingGas: false,
confirmAnyways: false,
};
componentDidUpdate(prevProps) {
@ -216,7 +217,7 @@ export default class ConfirmTransactionBase extends Component {
balance,
conversionRate,
hexMaximumTransactionFee,
txData: { simulationFails, txParams: { value: amount } = {} } = {},
txData: { txParams: { value: amount } = {} } = {},
customGas,
noGasPrice,
gasFeeIsCustom,
@ -245,15 +246,6 @@ export default class ConfirmTransactionBase extends Component {
};
}
if (simulationFails) {
return {
valid: true,
errorKey: simulationFails.errorKey
? simulationFails.errorKey
: TRANSACTION_ERROR_KEY,
};
}
if (noGasPrice && !gasFeeIsCustom) {
return {
valid: false,
@ -296,6 +288,10 @@ export default class ConfirmTransactionBase extends Component {
this.setState({ editingGas: false });
}
handleConfirmAnyways() {
this.setState({ confirmAnyways: true });
}
renderDetails() {
const {
primaryTotalTextOverride,
@ -321,6 +317,15 @@ export default class ConfirmTransactionBase extends Component {
} = this.props;
const { t } = this.context;
const { valid } = this.getErrorKey();
const isDisabled = () => {
return this.state.confirmAnyways ? false : !valid;
};
const hasSimulationError = Boolean(txData.simulationFails);
const renderSimulationFailureWarning =
hasSimulationError && !this.state.confirmAnyways;
const renderTotalMaxAmount = () => {
if (
primaryTotalTextOverrideMaxAmount === undefined &&
@ -411,147 +416,167 @@ export default class ConfirmTransactionBase extends Component {
</div>
) : null;
const renderGasDetailsItem = () => {
return EIP_1559_V2 ? (
<GasDetailsItem
key="gas_details"
hexMaximumTransactionFee={hexMaximumTransactionFee}
hexMinimumTransactionFee={hexMinimumTransactionFee}
isMainnet={isMainnet}
maxFeePerGas={maxFeePerGas}
maxPriorityFeePerGas={maxPriorityFeePerGas}
supportsEIP1559={supportsEIP1559}
txData={txData}
useNativeCurrencyAsPrimaryCurrency={
useNativeCurrencyAsPrimaryCurrency
}
/>
) : (
<TransactionDetailItem
key="gas-item"
detailTitle={
txData.dappSuggestedGasFees ? (
<>
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={t('transactionDetailDappGasTooltip')}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
) : (
<>
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={
<>
<p>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</p>
<p>{t('transactionDetailGasTooltipExplanation')}</p>
<p>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</p>
</>
}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
)
}
detailTitleColor={COLORS.BLACK}
detailText={
!isMultiLayerFeeNetwork && (
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
numberOfDecimals={isMultiLayerFeeNetwork ? 18 : 6}
/>
</div>
}
subText={
!isMultiLayerFeeNetwork &&
t('editGasSubTextFee', [
<b key="editGasSubTextFeeLabel">{t('editGasSubTextFeeLabel')}</b>,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>,
])
}
subTitle={
<>
{txData.dappSuggestedGasFees ? (
<Typography
variant={TYPOGRAPHY.H7}
fontStyle={FONT_STYLE.ITALIC}
color={COLORS.UI4}
>
{t('transactionDetailDappGasMoreInfo')}
</Typography>
) : (
''
)}
{supportsEIP1559 && (
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(
maxPriorityFeePerGas ||
txData.txParams.maxPriorityFeePerGas,
)}
maxFeePerGas={hexWEIToDecGWEI(
maxFeePerGas || txData.txParams.maxFeePerGas,
)}
/>
)}
</>
}
/>
);
};
const simulationFailureWarning = () => (
<div className="confirm-page-container-content__error-container">
<ActionableMessage
type="danger"
primaryAction={{
label: this.context.t('tryAnywayOption'),
onClick: () => this.handleConfirmAnyways(),
}}
message={this.context.t('simulationErrorMessage')}
roundedButtons
/>
</div>
);
return (
<div className="confirm-page-container-content__details">
{EIP_1559_V2 && <LowPriorityMessage />}
<TransactionDetail
onEdit={() => this.handleEditGas()}
disabled={isDisabled()}
onEdit={
renderSimulationFailureWarning ? null : () => this.handleEditGas()
}
rows={[
EIP_1559_V2 ? (
<GasDetailsItem
key="gas_details"
hexMaximumTransactionFee={hexMaximumTransactionFee}
hexMinimumTransactionFee={hexMinimumTransactionFee}
isMainnet={isMainnet}
maxFeePerGas={maxFeePerGas}
maxPriorityFeePerGas={maxPriorityFeePerGas}
supportsEIP1559={supportsEIP1559}
txData={txData}
useNativeCurrencyAsPrimaryCurrency={
useNativeCurrencyAsPrimaryCurrency
}
/>
) : (
<TransactionDetailItem
key="gas-item"
detailTitle={
txData.dappSuggestedGasFees ? (
<>
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={t('transactionDetailDappGasTooltip')}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
) : (
<>
{isMultiLayerFeeNetwork
? t('transactionDetailLayer2GasHeading')
: t('transactionDetailGasHeading')}
<InfoTooltip
contentText={
<>
<p>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</p>
<p>{t('transactionDetailGasTooltipExplanation')}</p>
<p>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</p>
</>
}
position="top"
>
<i className="fa fa-info-circle" />
</InfoTooltip>
</>
)
}
detailTitleColor={COLORS.BLACK}
detailText={
!isMultiLayerFeeNetwork && (
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={hexMinimumTransactionFee}
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/>
</div>
)
}
detailTotal={
<div className="confirm-page-container-content__currency-container">
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={hexMinimumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
numberOfDecimals={isMultiLayerFeeNetwork ? 18 : 6}
/>
</div>
}
subText={
!isMultiLayerFeeNetwork &&
t('editGasSubTextFee', [
<b key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</b>,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>,
])
}
subTitle={
<>
{txData.dappSuggestedGasFees ? (
<Typography
variant={TYPOGRAPHY.H7}
fontStyle={FONT_STYLE.ITALIC}
color={COLORS.UI4}
>
{t('transactionDetailDappGasMoreInfo')}
</Typography>
) : (
''
)}
{supportsEIP1559 && (
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(
maxPriorityFeePerGas ||
txData.txParams.maxPriorityFeePerGas,
)}
maxFeePerGas={hexWEIToDecGWEI(
maxFeePerGas || txData.txParams.maxFeePerGas,
)}
/>
)}
</>
}
/>
),
isMultiLayerFeeNetwork && (
renderSimulationFailureWarning && simulationFailureWarning(),
!renderSimulationFailureWarning && renderGasDetailsItem(),
!renderSimulationFailureWarning && isMultiLayerFeeNetwork && (
<MultiLayerFeeMessage
transaction={txData}
layer2fee={hexMinimumTransactionFee}
@ -918,10 +943,14 @@ export default class ConfirmTransactionBase extends Component {
submitWarning,
ethGasPriceWarning,
editingGas,
confirmAnyways,
} = this.state;
const { name } = methodData;
const { valid, errorKey } = this.getErrorKey();
const hasSimulationError = Boolean(txData.simulationFails);
const renderSimulationFailureWarning =
hasSimulationError && !confirmAnyways;
const {
totalTx,
positionOfCurrentTx,
@ -934,6 +963,10 @@ export default class ConfirmTransactionBase extends Component {
requestsWaitingText,
} = this.getNavigateTxData();
const isDisabled = () => {
return confirmAnyways ? false : !valid;
};
let functionType = getMethodName(name);
if (!functionType) {
if (type) {
@ -965,6 +998,7 @@ export default class ConfirmTransactionBase extends Component {
identiconAddress={identiconAddress}
errorMessage={submitError}
errorKey={errorKey}
hasSimulationError={hasSimulationError}
warning={submitWarning}
totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx}
@ -976,7 +1010,9 @@ export default class ConfirmTransactionBase extends Component {
lastTx={lastTx}
ofText={ofText}
requestsWaitingText={requestsWaitingText}
hideConfirmAnyways={!isDisabled()}
disabled={
renderSimulationFailureWarning ||
!valid ||
submitting ||
hardwareWalletRequiresConnection ||
@ -986,6 +1022,7 @@ export default class ConfirmTransactionBase extends Component {
onCancelAll={() => this.handleCancelAll()}
onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()}
onConfirmAnyways={() => this.handleConfirmAnyways()}
hideSenderToRecipient={hideSenderToRecipient}
origin={txData.origin}
ethGasPriceWarning={ethGasPriceWarning}