1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-23 11:46:13 +02:00
metamask-extension/ui/components/app/edit-gas-popover/edit-gas-popover.component.js
Elliot Winkler 7b963cabd7
Alert users when the network is busy (#12268)
When a lot of transactions are occurring on the network, such as during
an NFT drop, it drives gas fees up. When this happens, we want to not
only inform the user about this, but also dissuade them from using a
higher gas fee (as we have proved in testing that high gas fees can
cause bidding wars and exacerbate the situation).

The method for determining whether the network is "busy" is already
handled by GasFeeController, which exposes a `networkCongestion`
property within the gas fee estimate data. If this number exceeds 0.66 —
meaning that the current base fee is above the 66th percentile among the
base fees over the last several days — then we determine that the
network is "busy".
2022-01-07 12:18:02 -07:00

319 lines
9.4 KiB
JavaScript

import React, { useCallback, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useGasFeeInputs } from '../../../hooks/gasFeeInput/useGasFeeInputs';
import { getGasLoadingAnimationIsShowing } from '../../../ducks/app/app';
import { txParamsAreDappSuggested } from '../../../../shared/modules/transaction.utils';
import {
EDIT_GAS_MODES,
GAS_LIMITS,
GAS_RECOMMENDATIONS,
CUSTOM_GAS_ESTIMATE,
} from '../../../../shared/constants/gas';
import {
decGWEIToHexWEI,
decimalToHex,
hexToDecimal,
} from '../../../helpers/utils/conversions.util';
import Popover from '../../ui/popover';
import Button from '../../ui/button';
import EditGasDisplay from '../edit-gas-display';
import EditGasDisplayEducation from '../edit-gas-display-education';
import { I18nContext } from '../../../contexts/i18n';
import {
createCancelTransaction,
createSpeedUpTransaction,
hideModal,
updateTransaction,
updateCustomSwapsEIP1559GasParams,
updateSwapsUserFeeLevel,
} from '../../../store/actions';
import LoadingHeartBeat from '../../ui/loading-heartbeat';
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
import { useIncrementedGasFees } from '../../../hooks/useIncrementedGasFees';
import { isLegacyTransaction } from '../../../helpers/utils/transactions.util';
export default function EditGasPopover({
popoverTitle = '',
confirmButtonText = '',
editGasDisplayProps = {},
defaultEstimateToUse = GAS_RECOMMENDATIONS.MEDIUM,
transaction,
mode,
onClose,
minimumGasLimit = GAS_LIMITS.SIMPLE,
}) {
const t = useContext(I18nContext);
const dispatch = useDispatch();
const supportsEIP1559 =
useSelector(checkNetworkAndAccountSupports1559) &&
!isLegacyTransaction(transaction?.txParams);
const gasLoadingAnimationIsShowing = useSelector(
getGasLoadingAnimationIsShowing,
);
const showEducationButton =
(mode === EDIT_GAS_MODES.MODIFY_IN_PLACE ||
mode === EDIT_GAS_MODES.SWAPS) &&
supportsEIP1559;
const [showEducationContent, setShowEducationContent] = useState(false);
const [
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
] = useState(false);
const minimumGasLimitDec = hexToDecimal(minimumGasLimit);
const updatedCustomGasSettings = useIncrementedGasFees(transaction);
let updatedTransaction = transaction;
if (mode === EDIT_GAS_MODES.SPEED_UP || mode === EDIT_GAS_MODES.CANCEL) {
updatedTransaction = {
...transaction,
userFeeLevel: CUSTOM_GAS_ESTIMATE,
txParams: {
...transaction.txParams,
...updatedCustomGasSettings,
},
};
}
const {
maxPriorityFeePerGas,
setMaxPriorityFeePerGas,
maxPriorityFeePerGasFiat,
maxFeePerGas,
setMaxFeePerGas,
maxFeePerGasFiat,
estimatedMaximumNative,
estimatedMinimumNative,
isGasEstimatesLoading,
gasEstimateType,
gasPrice,
setGasPrice,
gasLimit,
setGasLimit,
estimateToUse,
setEstimateToUse,
estimatedMinimumFiat,
estimatedMaximumFiat,
hasGasErrors,
gasErrors,
gasWarnings,
onManualChange,
balanceError,
estimatesUnavailableWarning,
estimatedBaseFee,
isNetworkBusy,
} = useGasFeeInputs(
defaultEstimateToUse,
updatedTransaction,
minimumGasLimit,
mode,
);
const txParamsHaveBeenCustomized =
estimateToUse === CUSTOM_GAS_ESTIMATE ||
txParamsAreDappSuggested(updatedTransaction);
/**
* Temporary placeholder, this should be managed by the parent component but
* we will be extracting this component from the hard to maintain modal
* component. For now this is just to be able to appropriately close
* the modal in testing
*/
const closePopover = useCallback(() => {
if (onClose) {
onClose();
} else {
dispatch(hideModal());
}
}, [onClose, dispatch]);
const onSubmit = useCallback(() => {
if (!updatedTransaction || !mode) {
closePopover();
}
const newGasSettings = {
gas: decimalToHex(gasLimit),
gasLimit: decimalToHex(gasLimit),
estimateSuggested: defaultEstimateToUse,
estimateUsed: estimateToUse,
};
if (supportsEIP1559) {
newGasSettings.maxFeePerGas = decGWEIToHexWEI(maxFeePerGas ?? gasPrice);
newGasSettings.maxPriorityFeePerGas = decGWEIToHexWEI(
maxPriorityFeePerGas ?? maxFeePerGas ?? gasPrice,
);
} else {
newGasSettings.gasPrice = decGWEIToHexWEI(gasPrice);
}
const cleanTransactionParams = { ...updatedTransaction.txParams };
if (supportsEIP1559) {
delete cleanTransactionParams.gasPrice;
}
const updatedTxMeta = {
...updatedTransaction,
userFeeLevel: estimateToUse || CUSTOM_GAS_ESTIMATE,
txParams: {
...cleanTransactionParams,
...newGasSettings,
},
};
switch (mode) {
case EDIT_GAS_MODES.CANCEL:
dispatch(
createCancelTransaction(updatedTransaction.id, newGasSettings, {
estimatedBaseFee,
}),
);
break;
case EDIT_GAS_MODES.SPEED_UP:
dispatch(
createSpeedUpTransaction(updatedTransaction.id, newGasSettings, {
estimatedBaseFee,
}),
);
break;
case EDIT_GAS_MODES.MODIFY_IN_PLACE:
dispatch(updateTransaction(updatedTxMeta));
break;
case EDIT_GAS_MODES.SWAPS:
// This popover component should only be used for the "FEE_MARKET" type in Swaps.
if (supportsEIP1559) {
dispatch(
updateSwapsUserFeeLevel(estimateToUse || CUSTOM_GAS_ESTIMATE),
);
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
}
break;
default:
break;
}
closePopover();
}, [
updatedTransaction,
mode,
dispatch,
closePopover,
gasLimit,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
supportsEIP1559,
estimateToUse,
estimatedBaseFee,
defaultEstimateToUse,
]);
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 footerButtonText = confirmButtonText || t('save');
return (
<Popover
title={title}
onClose={closePopover}
className="edit-gas-popover__wrapper"
onBack={
showEducationContent ? () => setShowEducationContent(false) : undefined
}
footer={
showEducationContent ? null : (
<>
<Button
type="primary"
onClick={onSubmit}
disabled={
hasGasErrors ||
balanceError ||
((isGasEstimatesLoading || gasLoadingAnimationIsShowing) &&
!txParamsHaveBeenCustomized)
}
>
{footerButtonText}
</Button>
</>
)
}
>
<div style={{ padding: '0 20px 20px 20px', position: 'relative' }}>
{showEducationContent ? (
<EditGasDisplayEducation />
) : (
<>
{process.env.IN_TEST ? null : <LoadingHeartBeat />}
<EditGasDisplay
showEducationButton={showEducationButton}
dappSuggestedGasFeeAcknowledged={dappSuggestedGasFeeAcknowledged}
setDappSuggestedGasFeeAcknowledged={
setDappSuggestedGasFeeAcknowledged
}
maxPriorityFeePerGas={maxPriorityFeePerGas}
setMaxPriorityFeePerGas={setMaxPriorityFeePerGas}
maxPriorityFeePerGasFiat={maxPriorityFeePerGasFiat}
maxFeePerGas={maxFeePerGas}
setMaxFeePerGas={setMaxFeePerGas}
maxFeePerGasFiat={maxFeePerGasFiat}
estimatedMaximumNative={estimatedMaximumNative}
estimatedMinimumNative={estimatedMinimumNative}
isGasEstimatesLoading={isGasEstimatesLoading}
gasEstimateType={gasEstimateType}
gasPrice={gasPrice}
setGasPrice={setGasPrice}
gasLimit={gasLimit}
setGasLimit={setGasLimit}
estimateToUse={estimateToUse}
setEstimateToUse={setEstimateToUse}
estimatedMinimumFiat={estimatedMinimumFiat}
estimatedMaximumFiat={estimatedMaximumFiat}
onEducationClick={() => setShowEducationContent(true)}
mode={mode}
transaction={updatedTransaction}
gasErrors={gasErrors}
gasWarnings={gasWarnings}
onManualChange={onManualChange}
minimumGasLimit={minimumGasLimitDec}
balanceError={balanceError}
estimatesUnavailableWarning={estimatesUnavailableWarning}
hasGasErrors={hasGasErrors}
txParamsHaveBeenCustomized={txParamsHaveBeenCustomized}
isNetworkBusy={isNetworkBusy}
{...editGasDisplayProps}
/>
</>
)}
</div>
</Popover>
);
}
EditGasPopover.propTypes = {
popoverTitle: PropTypes.string,
editGasDisplayProps: PropTypes.object,
confirmButtonText: PropTypes.string,
onClose: PropTypes.func,
transaction: PropTypes.object,
mode: PropTypes.oneOf(Object.values(EDIT_GAS_MODES)),
defaultEstimateToUse: PropTypes.string,
minimumGasLimit: PropTypes.string,
};