1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

EIP-1559 - Elevate gas properties to the Popover, disable submission if errors (#11510)

This commit is contained in:
David Walsh 2021-07-14 11:45:37 -05:00 committed by GitHub
parent 91e744a705
commit a294f02b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 238 additions and 120 deletions

View File

@ -29,3 +29,12 @@ export const GAS_RECOMMENDATIONS = {
MEDIUM: 'medium', MEDIUM: 'medium',
HIGH: 'high', HIGH: 'high',
}; };
/**
* These represent the different edit modes presented in the UI
*/
export const EDIT_GAS_MODES = {
SPEED_UP: 'speed-up',
CANCEL: 'cancel',
MODIFY_IN_PLACE: 'modify-in-place',
};

View File

@ -1,7 +1,10 @@
import React, { useState, useContext } from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { GAS_RECOMMENDATIONS } from '../../../../shared/constants/gas'; import {
GAS_RECOMMENDATIONS,
EDIT_GAS_MODES,
} from '../../../../shared/constants/gas';
import Button from '../../ui/button'; import Button from '../../ui/button';
import Typography from '../../ui/typography/typography'; import Typography from '../../ui/typography/typography';
@ -19,55 +22,48 @@ import AdvancedGasControls from '../advanced-gas-controls/advanced-gas-controls.
import ActionableMessage from '../../ui/actionable-message/actionable-message'; import ActionableMessage from '../../ui/actionable-message/actionable-message';
import { I18nContext } from '../../../contexts/i18n'; import { I18nContext } from '../../../contexts/i18n';
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
export default function EditGasDisplay({ export default function EditGasDisplay({
alwaysShowForm, mode = EDIT_GAS_MODES.MODIFY_IN_PLACE,
type, alwaysShowForm = false,
showEducationButton, showEducationButton = false,
onEducationClick, onEducationClick,
dappSuggestedGasFee, dappSuggestedGasFee = 0,
dappOrigin, dappOrigin = '',
defaultEstimateToUse = 'medium', defaultEstimateToUse,
maxPriorityFeePerGas,
setMaxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
maxFeePerGas,
setMaxFeePerGas,
maxFeePerGasFiat,
estimatedMaximumNative,
isGasEstimatesLoading,
gasFeeEstimates,
gasEstimateType,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
estimateToUse,
setEstimateToUse,
estimatedMinimumFiat,
estimatedMaximumFiat,
isMaxFeeError,
isMaxPriorityFeeError,
isGasTooLow,
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
showAdvancedForm,
setShowAdvancedForm,
warning,
}) { }) {
const t = useContext(I18nContext); const t = useContext(I18nContext);
const [warning] = useState(null);
const [showAdvancedForm, setShowAdvancedForm] = useState(false);
const [
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
] = useState(false);
const requireDappAcknowledgement = Boolean( const requireDappAcknowledgement = Boolean(
dappSuggestedGasFee && !dappSuggestedGasFeeAcknowledged, dappSuggestedGasFee && !dappSuggestedGasFeeAcknowledged,
); );
const {
maxPriorityFeePerGas,
setMaxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
maxFeePerGas,
setMaxFeePerGas,
maxFeePerGasFiat,
estimatedMaximumNative,
isGasEstimatesLoading,
gasFeeEstimates,
gasEstimateType,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
estimateToUse,
setEstimateToUse,
estimatedMinimumFiat,
estimatedMaximumFiat,
isMaxFeeError,
isMaxPriorityFeeError,
isGasTooLow,
} = useGasFeeInputs(defaultEstimateToUse);
return ( return (
<div className="edit-gas-display"> <div className="edit-gas-display">
<div className="edit-gas-display__content"> <div className="edit-gas-display__content">
@ -88,7 +84,7 @@ export default function EditGasDisplay({
/> />
</div> </div>
)} )}
{type === 'speed-up' && ( {mode === EDIT_GAS_MODES.SPEED_UP && (
<div className="edit-gas-display__top-tooltip"> <div className="edit-gas-display__top-tooltip">
<Typography <Typography
color={COLORS.BLACK} color={COLORS.BLACK}
@ -141,31 +137,33 @@ export default function EditGasDisplay({
</Typography> </Typography>
</div> </div>
)} )}
{!requireDappAcknowledgement && ( {!requireDappAcknowledgement &&
<RadioGroup ![EDIT_GAS_MODES.SPEED_UP, EDIT_GAS_MODES.CANCEL].includes(mode) && (
name="gas-recommendation" <RadioGroup
options={[ name="gas-recommendation"
{ options={[
value: GAS_RECOMMENDATIONS.LOW, {
label: t('editGasLow'), value: GAS_RECOMMENDATIONS.LOW,
recommended: defaultEstimateToUse === GAS_RECOMMENDATIONS.LOW, label: t('editGasLow'),
}, recommended: defaultEstimateToUse === GAS_RECOMMENDATIONS.LOW,
{ },
value: GAS_RECOMMENDATIONS.MEDIUM, {
label: t('editGasMedium'), value: GAS_RECOMMENDATIONS.MEDIUM,
recommended: label: t('editGasMedium'),
defaultEstimateToUse === GAS_RECOMMENDATIONS.MEDIUM, recommended:
}, defaultEstimateToUse === GAS_RECOMMENDATIONS.MEDIUM,
{ },
value: GAS_RECOMMENDATIONS.HIGH, {
label: t('editGasHigh'), value: GAS_RECOMMENDATIONS.HIGH,
recommended: defaultEstimateToUse === GAS_RECOMMENDATIONS.HIGH, label: t('editGasHigh'),
}, recommended:
]} defaultEstimateToUse === GAS_RECOMMENDATIONS.HIGH,
selectedValue={estimateToUse} },
onChange={setEstimateToUse} ]}
/> selectedValue={estimateToUse}
)} onChange={setEstimateToUse}
/>
)}
{!alwaysShowForm && ( {!alwaysShowForm && (
<button <button
className="edit-gas-display__advanced-button" className="edit-gas-display__advanced-button"
@ -216,19 +214,36 @@ export default function EditGasDisplay({
EditGasDisplay.propTypes = { EditGasDisplay.propTypes = {
alwaysShowForm: PropTypes.bool, alwaysShowForm: PropTypes.bool,
type: PropTypes.oneOf(['customize-gas', 'speed-up']), mode: PropTypes.oneOf(Object.values(EDIT_GAS_MODES)),
showEducationButton: PropTypes.bool, showEducationButton: PropTypes.bool,
onEducationClick: PropTypes.func, onEducationClick: PropTypes.func,
dappSuggestedGasFee: PropTypes.number, dappSuggestedGasFee: PropTypes.number,
dappOrigin: PropTypes.string, dappOrigin: PropTypes.string,
defaultEstimateToUse: PropTypes.oneOf(Object.values(GAS_RECOMMENDATIONS)), defaultEstimateToUse: PropTypes.oneOf(Object.values(GAS_RECOMMENDATIONS)),
}; maxPriorityFeePerGas: PropTypes.string,
setMaxPriorityFeePerGas: PropTypes.func,
EditGasDisplay.defaultProps = { maxPriorityFeePerGasFiat: PropTypes.string,
alwaysShowForm: false, maxFeePerGas: PropTypes.string,
type: 'customize-gas', setMaxFeePerGas: PropTypes.func,
showEducationButton: false, maxFeePerGasFiat: PropTypes.string,
onEducationClick: undefined, estimatedMaximumNative: PropTypes.string,
dappSuggestedGasFee: 0, isGasEstimatesLoading: PropTypes.boolean,
dappOrigin: '', gasFeeEstimates: PropTypes.object,
gasEstimateType: PropTypes.string,
gasPrice: PropTypes.string,
setGasPrice: PropTypes.func,
gasLimit: PropTypes.number,
setGasLimit: PropTypes.func,
estimateToUse: PropTypes.string,
setEstimateToUse: PropTypes.func,
estimatedMinimumFiat: PropTypes.string,
estimatedMaximumFiat: PropTypes.string,
isMaxFeeError: PropTypes.boolean,
isMaxPriorityFeeError: PropTypes.boolean,
isGasTooLow: PropTypes.boolean,
dappSuggestedGasFeeAcknowledged: PropTypes.boolean,
setDappSuggestedGasFeeAcknowledged: PropTypes.func,
showAdvancedForm: PropTypes.bool,
setShowAdvancedForm: PropTypes.func,
warning: PropTypes.string,
}; };

View File

@ -2,6 +2,18 @@ import React, { useCallback, useContext, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
import {
GAS_ESTIMATE_TYPES,
EDIT_GAS_MODES,
} from '../../../../shared/constants/gas';
import {
decGWEIToHexWEI,
decimalToHex,
} from '../../../helpers/utils/conversions.util';
import Popover from '../../ui/popover'; import Popover from '../../ui/popover';
import Button from '../../ui/button'; import Button from '../../ui/button';
import EditGasDisplay from '../edit-gas-display'; import EditGasDisplay from '../edit-gas-display';
@ -16,16 +28,11 @@ import {
updateTransaction, updateTransaction,
} from '../../../store/actions'; } from '../../../store/actions';
export const EDIT_GAS_MODE = {
SPEED_UP: 'speed-up',
CANCEL: 'cancel',
MODIFY_IN_PLACE: 'modify-in-place',
};
export default function EditGasPopover({ export default function EditGasPopover({
popoverTitle, popoverTitle = '',
confirmButtonText, confirmButtonText = '',
editGasDisplayProps, editGasDisplayProps = {},
defaultEstimateToUse = 'medium',
transaction, transaction,
mode, mode,
onClose, onClose,
@ -33,8 +40,42 @@ export default function EditGasPopover({
const t = useContext(I18nContext); const t = useContext(I18nContext);
const dispatch = useDispatch(); const dispatch = useDispatch();
const showSidebar = useSelector((state) => state.appState.sidebar.isOpen); const showSidebar = useSelector((state) => state.appState.sidebar.isOpen);
const showEducationButton = mode === EDIT_GAS_MODES.MODIFY_IN_PLACE;
const [showEducationContent, setShowEducationContent] = useState(false); const [showEducationContent, setShowEducationContent] = useState(false);
const [warning] = useState(null);
const [showAdvancedForm, setShowAdvancedForm] = useState(false);
const [
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
] = useState(false);
const {
maxPriorityFeePerGas,
setMaxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
maxFeePerGas,
setMaxFeePerGas,
maxFeePerGasFiat,
estimatedMaximumNative,
isGasEstimatesLoading,
gasFeeEstimates,
gasEstimateType,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
estimateToUse,
setEstimateToUse,
estimatedMinimumFiat,
estimatedMaximumFiat,
isMaxFeeError,
isMaxPriorityFeeError,
isGasTooLow,
} = useGasFeeInputs(defaultEstimateToUse);
/** /**
* Temporary placeholder, this should be managed by the parent component but * Temporary placeholder, this should be managed by the parent component but
* we will be extracting this component from the hard to maintain modal/ * we will be extracting this component from the hard to maintain modal/
@ -55,26 +96,36 @@ export default function EditGasPopover({
if (!transaction || !mode) { if (!transaction || !mode) {
closePopover(); closePopover();
} }
const newGasSettings =
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET
? {
gas: decimalToHex(gasLimit),
gasLimit: decimalToHex(gasLimit),
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
maxPriorityFeePerGas: decGWEIToHexWEI(maxPriorityFeePerGas),
}
: {
gas: decimalToHex(gasLimit),
gasLimit: decimalToHex(gasLimit),
gasPrice: decGWEIToHexWEI(gasPrice),
};
switch (mode) { switch (mode) {
case EDIT_GAS_MODE.CANCEL: case EDIT_GAS_MODES.CANCEL:
dispatch( dispatch(createCancelTransaction(transaction.id, newGasSettings));
createCancelTransaction(transaction.id, {
/** new gas settings */
}),
);
break; break;
case EDIT_GAS_MODE.SPEED_UP: case EDIT_GAS_MODES.SPEED_UP:
dispatch( dispatch(createSpeedUpTransaction(transaction.id, newGasSettings));
createSpeedUpTransaction(transaction.id, {
/** new gas settings */
}),
);
break; break;
case EDIT_GAS_MODE.MODIFY_IN_PLACE: case EDIT_GAS_MODES.MODIFY_IN_PLACE:
dispatch( dispatch(
updateTransaction({ updateTransaction({
...transaction, ...transaction,
txParams: { ...transaction.txParams /** ...newGasSettings */ }, txParams: {
...transaction.txParams,
...newGasSettings,
},
}), }),
); );
break; break;
@ -83,11 +134,29 @@ export default function EditGasPopover({
} }
closePopover(); closePopover();
}, [transaction, mode, dispatch, closePopover]); }, [
transaction,
mode,
dispatch,
closePopover,
gasLimit,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
gasEstimateType,
]);
let title = t('editGasTitle');
if (popoverTitle) {
title = popoverTitle;
} else if (showEducationContent) {
title = t('editGasEducationModalTitle');
} else if (mode === EDIT_GAS_MODES.SPEED_UP) {
title = t('speedUpPopoverTitle');
} else if (mode === EDIT_GAS_MODES.CANCEL) {
title = t('cancelPopoverTitle');
}
const title = showEducationContent
? t('editGasEducationModalTitle')
: popoverTitle || t('editGasTitle');
const footerButtonText = confirmButtonText || t('save'); const footerButtonText = confirmButtonText || t('save');
return ( return (
@ -99,7 +168,11 @@ export default function EditGasPopover({
} }
footer={ footer={
<> <>
<Button type="primary" onClick={onSubmit}> <Button
type="primary"
onClick={onSubmit}
disabled={isMaxFeeError || isMaxPriorityFeeError || isGasTooLow}
>
{footerButtonText} {footerButtonText}
</Button> </Button>
</> </>
@ -110,8 +183,38 @@ export default function EditGasPopover({
<EditGasDisplayEducation /> <EditGasDisplayEducation />
) : ( ) : (
<EditGasDisplay <EditGasDisplay
{...editGasDisplayProps} showEducationButton={showEducationButton}
warning={warning}
showAdvancedForm={showAdvancedForm}
setShowAdvancedForm={setShowAdvancedForm}
dappSuggestedGasFeeAcknowledged={dappSuggestedGasFeeAcknowledged}
setDappSuggestedGasFeeAcknowledged={
setDappSuggestedGasFeeAcknowledged
}
maxPriorityFeePerGas={maxPriorityFeePerGas}
setMaxPriorityFeePerGas={setMaxPriorityFeePerGas}
maxPriorityFeePerGasFiat={maxPriorityFeePerGasFiat}
maxFeePerGas={maxFeePerGas}
setMaxFeePerGas={setMaxFeePerGas}
maxFeePerGasFiat={maxFeePerGasFiat}
estimatedMaximumNative={estimatedMaximumNative}
isGasEstimatesLoading={isGasEstimatesLoading}
gasFeeEstimates={gasFeeEstimates}
gasEstimateType={gasEstimateType}
gasPrice={gasPrice}
setGasPrice={setGasPrice}
gasLimit={gasLimit}
setGasLimit={setGasLimit}
estimateToUse={estimateToUse}
setEstimateToUse={setEstimateToUse}
estimatedMinimumFiat={estimatedMinimumFiat}
estimatedMaximumFiat={estimatedMaximumFiat}
isMaxFeeError={isMaxFeeError}
isMaxPriorityFeeError={isMaxPriorityFeeError}
isGasTooLow={isGasTooLow}
onEducationClick={() => setShowEducationContent(true)} onEducationClick={() => setShowEducationContent(true)}
mode={mode}
{...editGasDisplayProps}
/> />
)} )}
</div> </div>
@ -123,15 +226,8 @@ EditGasPopover.propTypes = {
popoverTitle: PropTypes.string, popoverTitle: PropTypes.string,
editGasDisplayProps: PropTypes.object, editGasDisplayProps: PropTypes.object,
confirmButtonText: PropTypes.string, confirmButtonText: PropTypes.string,
showEducationButton: PropTypes.bool,
onClose: PropTypes.func, onClose: PropTypes.func,
transaction: PropTypes.object, transaction: PropTypes.object,
mode: PropTypes.oneOf(Object.values(EDIT_GAS_MODE)), mode: PropTypes.oneOf(Object.values(EDIT_GAS_MODES)),
}; defaultEstimateToUse: PropTypes.string,
EditGasPopover.defaultProps = {
popoverTitle: '',
editGasDisplayProps: {},
confirmButtonText: '',
showEducationButton: false,
}; };

View File

@ -18,8 +18,8 @@ import {
TRANSACTION_GROUP_CATEGORIES, TRANSACTION_GROUP_CATEGORIES,
TRANSACTION_STATUSES, TRANSACTION_STATUSES,
} from '../../../../shared/constants/transaction'; } from '../../../../shared/constants/transaction';
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
import EditGasPopover from '../edit-gas-popover'; import EditGasPopover from '../edit-gas-popover';
import { EDIT_GAS_MODE } from '../edit-gas-popover/edit-gas-popover.component';
export default function TransactionListItem({ export default function TransactionListItem({
transactionGroup, transactionGroup,
@ -212,17 +212,15 @@ export default function TransactionListItem({
)} )}
{process.env.SHOW_EIP_1559_UI && showRetryEditGasPopover && ( {process.env.SHOW_EIP_1559_UI && showRetryEditGasPopover && (
<EditGasPopover <EditGasPopover
popoverTitle={t('cancelPopoverTitle')}
onClose={closeRetryEditGasPopover} onClose={closeRetryEditGasPopover}
mode={EDIT_GAS_MODE.SPEED_UP} mode={EDIT_GAS_MODES.SPEED_UP}
transaction={transactionGroup.primaryTransaction} transaction={transactionGroup.primaryTransaction}
/> />
)} )}
{process.env.SHOW_EIP_1559_UI && showCancelEditGasPopover && ( {process.env.SHOW_EIP_1559_UI && showCancelEditGasPopover && (
<EditGasPopover <EditGasPopover
popoverTitle={t('speedUpPopoverTitle')}
onClose={closeCancelEditGasPopover} onClose={closeCancelEditGasPopover}
mode={EDIT_GAS_MODE.CANCEL} mode={EDIT_GAS_MODES.CANCEL}
transaction={transactionGroup.primaryTransaction} transaction={transactionGroup.primaryTransaction}
/> />
)} )}