mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +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:
parent
6838a3d074
commit
143a5c4a53
@ -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…
x
Reference in New Issue
Block a user