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>
feature/default_network_editable
Olusegun Akintayo 3 years ago committed by GitHub
parent 6838a3d074
commit 143a5c4a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/_locales/en/messages.json
  2. 25
      ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
  3. 124
      ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js
  4. 17
      ui/components/ui/actionable-message/actionable-message.js
  5. 10
      ui/components/ui/actionable-message/index.scss
  6. 331
      ui/pages/confirm-transaction-base/confirm-transaction-base.component.js

@ -2314,6 +2314,9 @@
"signed": { "signed": {
"message": "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": { "skip": {
"message": "Skip" "message": "Skip"
}, },
@ -2977,6 +2980,9 @@
"tryAgain": { "tryAgain": {
"message": "Try again" "message": "Try again"
}, },
"tryAnywayOption": {
"message": "I will try anyway"
},
"turnOnTokenDetection": { "turnOnTokenDetection": {
"message": "Turn on enhanced token detection" "message": "Turn on enhanced token detection"
}, },

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

@ -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);
});
});

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

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

@ -13,7 +13,6 @@ import {
} from '../../helpers/constants/routes'; } from '../../helpers/constants/routes';
import { import {
INSUFFICIENT_FUNDS_ERROR_KEY, INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
GAS_LIMIT_TOO_LOW_ERROR_KEY, GAS_LIMIT_TOO_LOW_ERROR_KEY,
ETH_GAS_PRICE_FETCH_WARNING_KEY, ETH_GAS_PRICE_FETCH_WARNING_KEY,
GAS_PRICE_FETCH_FAILURE_ERROR_KEY, GAS_PRICE_FETCH_FAILURE_ERROR_KEY,
@ -21,6 +20,7 @@ import {
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'; import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display';
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 { import {
TRANSACTION_TYPES, TRANSACTION_TYPES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
@ -143,6 +143,7 @@ export default class ConfirmTransactionBase extends Component {
submitWarning: '', submitWarning: '',
ethGasPriceWarning: '', ethGasPriceWarning: '',
editingGas: false, editingGas: false,
confirmAnyways: false,
}; };
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -216,7 +217,7 @@ export default class ConfirmTransactionBase extends Component {
balance, balance,
conversionRate, conversionRate,
hexMaximumTransactionFee, hexMaximumTransactionFee,
txData: { simulationFails, txParams: { value: amount } = {} } = {}, txData: { txParams: { value: amount } = {} } = {},
customGas, customGas,
noGasPrice, noGasPrice,
gasFeeIsCustom, 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) { if (noGasPrice && !gasFeeIsCustom) {
return { return {
valid: false, valid: false,
@ -296,6 +288,10 @@ export default class ConfirmTransactionBase extends Component {
this.setState({ editingGas: false }); this.setState({ editingGas: false });
} }
handleConfirmAnyways() {
this.setState({ confirmAnyways: true });
}
renderDetails() { renderDetails() {
const { const {
primaryTotalTextOverride, primaryTotalTextOverride,
@ -321,6 +317,15 @@ export default class ConfirmTransactionBase extends Component {
} = this.props; } = this.props;
const { t } = this.context; 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 = () => { const renderTotalMaxAmount = () => {
if ( if (
primaryTotalTextOverrideMaxAmount === undefined && primaryTotalTextOverrideMaxAmount === undefined &&
@ -411,147 +416,167 @@ export default class ConfirmTransactionBase extends Component {
</div> </div>
) : null; ) : 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 ( return (
<div className="confirm-page-container-content__details"> <div className="confirm-page-container-content__details">
{EIP_1559_V2 && <LowPriorityMessage />} {EIP_1559_V2 && <LowPriorityMessage />}
<TransactionDetail <TransactionDetail
onEdit={() => this.handleEditGas()} disabled={isDisabled()}
onEdit={
renderSimulationFailureWarning ? null : () => this.handleEditGas()
}
rows={[ rows={[
EIP_1559_V2 ? ( renderSimulationFailureWarning && simulationFailureWarning(),
<GasDetailsItem !renderSimulationFailureWarning && renderGasDetailsItem(),
key="gas_details" !renderSimulationFailureWarning && isMultiLayerFeeNetwork && (
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 && (
<MultiLayerFeeMessage <MultiLayerFeeMessage
transaction={txData} transaction={txData}
layer2fee={hexMinimumTransactionFee} layer2fee={hexMinimumTransactionFee}
@ -918,10 +943,14 @@ export default class ConfirmTransactionBase extends Component {
submitWarning, submitWarning,
ethGasPriceWarning, ethGasPriceWarning,
editingGas, editingGas,
confirmAnyways,
} = this.state; } = this.state;
const { name } = methodData; const { name } = methodData;
const { valid, errorKey } = this.getErrorKey(); const { valid, errorKey } = this.getErrorKey();
const hasSimulationError = Boolean(txData.simulationFails);
const renderSimulationFailureWarning =
hasSimulationError && !confirmAnyways;
const { const {
totalTx, totalTx,
positionOfCurrentTx, positionOfCurrentTx,
@ -934,6 +963,10 @@ export default class ConfirmTransactionBase extends Component {
requestsWaitingText, requestsWaitingText,
} = this.getNavigateTxData(); } = this.getNavigateTxData();
const isDisabled = () => {
return confirmAnyways ? false : !valid;
};
let functionType = getMethodName(name); let functionType = getMethodName(name);
if (!functionType) { if (!functionType) {
if (type) { if (type) {
@ -965,6 +998,7 @@ export default class ConfirmTransactionBase extends Component {
identiconAddress={identiconAddress} identiconAddress={identiconAddress}
errorMessage={submitError} errorMessage={submitError}
errorKey={errorKey} errorKey={errorKey}
hasSimulationError={hasSimulationError}
warning={submitWarning} warning={submitWarning}
totalTx={totalTx} totalTx={totalTx}
positionOfCurrentTx={positionOfCurrentTx} positionOfCurrentTx={positionOfCurrentTx}
@ -976,7 +1010,9 @@ export default class ConfirmTransactionBase extends Component {
lastTx={lastTx} lastTx={lastTx}
ofText={ofText} ofText={ofText}
requestsWaitingText={requestsWaitingText} requestsWaitingText={requestsWaitingText}
hideConfirmAnyways={!isDisabled()}
disabled={ disabled={
renderSimulationFailureWarning ||
!valid || !valid ||
submitting || submitting ||
hardwareWalletRequiresConnection || hardwareWalletRequiresConnection ||
@ -986,6 +1022,7 @@ export default class ConfirmTransactionBase extends Component {
onCancelAll={() => this.handleCancelAll()} onCancelAll={() => this.handleCancelAll()}
onCancel={() => this.handleCancel()} onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()} onSubmit={() => this.handleSubmit()}
onConfirmAnyways={() => this.handleConfirmAnyways()}
hideSenderToRecipient={hideSenderToRecipient} hideSenderToRecipient={hideSenderToRecipient}
origin={txData.origin} origin={txData.origin}
ethGasPriceWarning={ethGasPriceWarning} ethGasPriceWarning={ethGasPriceWarning}

Loading…
Cancel
Save