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

Provide Estimate Data to EditGasDisplay (#11433)

This commit is contained in:
David Walsh 2021-07-12 11:16:03 -05:00 committed by GitHub
parent c3e6514c35
commit 1da7beed13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 313 additions and 63 deletions

View File

@ -650,6 +650,12 @@
"editGasLow": {
"message": "Low"
},
"editGasMaxFeeLow": {
"message": "Max fee too low for network conditions"
},
"editGasMaxPriorityFeeLow": {
"message": "Max priority fee too low for network conditions"
},
"editGasMedium": {
"message": "Medium"
},
@ -659,6 +665,13 @@
"editGasTitle": {
"message": "Edit gas fee"
},
"editGasTooLow": {
"message": "Unknown processing time"
},
"editGasTotalBannerSubtitle": {
"message": "Up to $1 ($2)",
"display": "$1 represents a fiat value"
},
"editNonceField": {
"message": "Edit Nonce"
},

View File

@ -20,3 +20,12 @@ export const GAS_ESTIMATE_TYPES = {
ETH_GASPRICE: 'eth_gasPrice',
NONE: 'none',
};
/**
* These represent gas recommendation levels presented in the UI
*/
export const GAS_RECOMMENDATIONS = {
LOW: 'low',
MEDIUM: 'medium',
HIGH: 'high',
};

View File

@ -1,4 +1,5 @@
import React, { useContext, useState } from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import Typography from '../../ui/typography/typography';
@ -8,16 +9,55 @@ import {
COLORS,
} from '../../../helpers/constants/design-system';
import FormField from '../../ui/form-field';
import {
GAS_ESTIMATE_TYPES,
GAS_RECOMMENDATIONS,
} from '../../../../shared/constants/gas';
export default function AdvancedGasControls() {
const DEFAULT_ESTIMATES_LEVEL = 'medium';
export default function AdvancedGasControls({
estimateToUse,
gasFeeEstimates,
gasEstimateType,
maxPriorityFee,
maxFee,
setMaxPriorityFee,
setMaxFee,
onManualChange,
gasLimit,
setGasLimit,
gasPrice,
setGasPrice,
maxPriorityFeeFiat,
maxFeeFiat,
maxPriorityFeeError,
maxFeeError,
}) {
const t = useContext(I18nContext);
const [gasLimit, setGasLimit] = useState(0);
const [maxPriorityFee, setMaxPriorityFee] = useState(0);
const [maxFee, setMaxFee] = useState(0);
const suggestedValues = {};
// Used in legacy version
const [gasPrice, setGasPrice] = useState(0);
switch (gasEstimateType) {
case GAS_ESTIMATE_TYPES.FEE_MARKET:
suggestedValues.maxPriorityFeePerGas =
gasFeeEstimates?.[estimateToUse]?.suggestedMaxPriorityFeePerGas;
suggestedValues.maxFeePerGas =
gasFeeEstimates?.[estimateToUse]?.suggestedMaxFeePerGas;
break;
case GAS_ESTIMATE_TYPES.LEGACY:
suggestedValues.gasPrice = gasFeeEstimates?.[estimateToUse];
break;
case GAS_ESTIMATE_TYPES.ETH_GASPRICE:
suggestedValues.gasPrice = gasFeeEstimates?.gasPrice;
break;
default:
break;
}
const showFeeMarketFields =
process.env.SHOW_EIP_1559_UI &&
gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET;
return (
<div className="advanced-gas-controls">
@ -27,58 +67,83 @@ export default function AdvancedGasControls() {
tooltipText=""
value={gasLimit}
numeric
autoFocus
/>
{process.env.SHOW_EIP_1559_UI ? (
{showFeeMarketFields ? (
<>
<FormField
titleText={t('maxPriorityFee')}
titleUnit="(GWEI)"
tooltipText=""
onChange={setMaxPriorityFee}
onChange={(value) => {
setMaxPriorityFee(value);
onManualChange?.();
}}
value={maxPriorityFee}
detailText={maxPriorityFeeFiat}
numeric
titleDetail={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
suggestedValues.maxPriorityFeePerGas && (
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
>
{
gasFeeEstimates?.[DEFAULT_ESTIMATES_LEVEL]
?.suggestedMaxPriorityFeePerGas
}
</Typography>
</>
)
}
error={maxPriorityFeeError}
/>
<FormField
titleText={t('maxFee')}
titleUnit="(GWEI)"
tooltipText=""
onChange={setMaxFee}
onChange={(value) => {
setMaxFee(value);
onManualChange?.();
}}
value={maxFee}
numeric
detailText={maxFeeFiat}
titleDetail={
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
></Typography>
</>
suggestedValues.maxFeePerGas && (
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
>
{
gasFeeEstimates?.[DEFAULT_ESTIMATES_LEVEL]
?.suggestedMaxFeePerGas
}
</Typography>
</>
)
}
error={maxFeeError}
/>
</>
) : (
@ -86,13 +151,71 @@ export default function AdvancedGasControls() {
<FormField
titleText={t('gasPrice')}
titleUnit="(GWEI)"
onChange={setGasPrice}
onChange={(value) => {
setGasPrice(value);
onManualChange?.();
}}
tooltipText={t('editGasPriceTooltip')}
value={gasPrice}
numeric
titleDetail={
suggestedValues.gasPrice && (
<>
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
fontWeight={FONT_WEIGHT.BOLD}
>
{t('gasFeeEstimate')}:
</Typography>{' '}
<Typography
tag="span"
color={COLORS.UI4}
variant={TYPOGRAPHY.H8}
>
{suggestedValues.gasPrice}
</Typography>
</>
)
}
/>
</>
)}
</div>
);
}
AdvancedGasControls.propTypes = {
estimateToUse: PropTypes.oneOf(Object.values(GAS_RECOMMENDATIONS)),
gasFeeEstimates: PropTypes.oneOf([
PropTypes.shape({
gasPrice: PropTypes.string,
}),
PropTypes.shape({
low: PropTypes.string,
medium: PropTypes.string,
high: PropTypes.string,
}),
PropTypes.shape({
low: PropTypes.object,
medium: PropTypes.object,
high: PropTypes.object,
estimatedBaseFee: PropTypes.string,
}),
]),
gasEstimateType: PropTypes.oneOf(Object.values(GAS_ESTIMATE_TYPES)),
setMaxPriorityFee: PropTypes.func,
setMaxFee: PropTypes.func,
maxPriorityFee: PropTypes.number,
maxFee: PropTypes.number,
onManualChange: PropTypes.func,
gasLimit: PropTypes.number,
setGasLimit: PropTypes.func,
gasPrice: PropTypes.number,
setGasPrice: PropTypes.func,
maxPriorityFeeFiat: PropTypes.string,
maxFeeFiat: PropTypes.string,
maxPriorityFeeError: PropTypes.string,
maxFeeError: PropTypes.string,
};

View File

@ -1,21 +1,25 @@
import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import { GAS_RECOMMENDATIONS } from '../../../../shared/constants/gas';
import Button from '../../ui/button';
import Typography from '../../ui/typography/typography';
import {
COLORS,
TYPOGRAPHY,
FONT_WEIGHT,
TEXT_ALIGN,
} from '../../../helpers/constants/design-system';
import InfoTooltip from '../../ui/info-tooltip';
import TransactionTotalBanner from '../transaction-total-banner/transaction-total-banner.component';
import RadioGroup from '../../ui/radio-group/radio-group.component';
import AdvancedGasControls from '../advanced-gas-controls/advanced-gas-controls.component';
import ActionableMessage from '../../ui/actionable-message/actionable-message';
import { I18nContext } from '../../../contexts/i18n';
import ActionableMessage from '../../ui/actionable-message/actionable-message';
import { useGasFeeInputs } from '../../../hooks/useGasFeeInputs';
export default function EditGasDisplay({
alwaysShowForm,
@ -24,18 +28,45 @@ export default function EditGasDisplay({
onEducationClick,
dappSuggestedGasFee,
dappOrigin,
defaultEstimateToUse = 'medium',
}) {
const t = useContext(I18nContext);
const [warning] = useState(null);
const [showAdvancedForm, setShowAdvancedForm] = useState(false);
const [
dappSuggestedGasFeeAcknowledged,
setDappSuggestedGasFeeAcknowledged,
] = useState(false);
const requireDappAcknowledgement =
dappSuggestedGasFee && !dappSuggestedGasFeeAcknowledged;
const requireDappAcknowledgement = Boolean(
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 (
<div className="edit-gas-display">
@ -44,7 +75,7 @@ export default function EditGasDisplay({
<div className="edit-gas-display__warning">
<ActionableMessage
className="actionable-message--warning"
message="Swaps are time sensitive. “Medium” is not reccomended."
message={warning}
/>
</div>
)}
@ -72,9 +103,25 @@ export default function EditGasDisplay({
</Typography>
</div>
)}
<TransactionTotalBanner total="" detail="" timing="" />
<TransactionTotalBanner
total={estimatedMinimumFiat}
detail={
process.env.SHOW_EIP_1559_UI &&
t('editGasTotalBannerSubtitle', [
<Typography
fontWeight={FONT_WEIGHT.BOLD}
tag="span"
key="secondary"
>
{estimatedMaximumFiat}
</Typography>,
<Typography tag="span" key="primary">
{estimatedMaximumNative}
</Typography>,
])
}
timing=""
/>
{requireDappAcknowledgement && (
<Button
className="edit-gas-display__dapp-acknowledgement-button"
@ -83,23 +130,43 @@ export default function EditGasDisplay({
{t('gasDisplayAcknowledgeDappButtonText')}
</Button>
)}
{isGasTooLow && (
<div className="edit-gas-display__error">
<Typography
color={COLORS.ERROR1}
variant={TYPOGRAPHY.H7}
align={TEXT_ALIGN.CENTER}
>
{t('editGasTooLow')}
</Typography>
</div>
)}
{!requireDappAcknowledgement && (
<RadioGroup
name="gas-recommendation"
options={[
{ value: 'low', label: t('editGasLow'), recommended: false },
{
value: 'medium',
label: t('editGasMedium'),
recommended: false,
value: GAS_RECOMMENDATIONS.LOW,
label: t('editGasLow'),
recommended: defaultEstimateToUse === GAS_RECOMMENDATIONS.LOW,
},
{
value: GAS_RECOMMENDATIONS.MEDIUM,
label: t('editGasMedium'),
recommended:
defaultEstimateToUse === GAS_RECOMMENDATIONS.MEDIUM,
},
{
value: GAS_RECOMMENDATIONS.HIGH,
label: t('editGasHigh'),
recommended: defaultEstimateToUse === GAS_RECOMMENDATIONS.HIGH,
},
{ value: 'high', label: t('editGasHigh'), recommended: true },
]}
selectedValue="high"
selectedValue={estimateToUse}
onChange={setEstimateToUse}
/>
)}
{!requireDappAcknowledgement && !alwaysShowForm && (
{!alwaysShowForm && (
<button
className="edit-gas-display__advanced-button"
onClick={() => setShowAdvancedForm(!showAdvancedForm)}
@ -112,8 +179,29 @@ export default function EditGasDisplay({
)}
</button>
)}
{((!requireDappAcknowledgement && alwaysShowForm) ||
showAdvancedForm) && <AdvancedGasControls />}
{!requireDappAcknowledgement &&
(alwaysShowForm || showAdvancedForm) && (
<AdvancedGasControls
gasFeeEstimates={gasFeeEstimates}
gasEstimateType={gasEstimateType}
estimateToUse={estimateToUse}
isGasEstimatesLoading={isGasEstimatesLoading}
gasLimit={gasLimit}
setGasLimit={setGasLimit}
maxPriorityFee={maxPriorityFeePerGas}
setMaxPriorityFee={setMaxPriorityFeePerGas}
maxFee={maxFeePerGas}
setMaxFee={setMaxFeePerGas}
gasPrice={gasPrice}
setGasPrice={setGasPrice}
maxPriorityFeeFiat={maxPriorityFeePerGasFiat}
maxFeeFiat={maxFeePerGasFiat}
maxPriorityFeeError={
isMaxPriorityFeeError ? t('editGasMaxPriorityFeeLow') : null
}
maxFeeError={isMaxFeeError ? t('editGasMaxFeeLow') : null}
/>
)}
</div>
{!requireDappAcknowledgement && showEducationButton && (
<div className="edit-gas-display__education">
@ -133,6 +221,7 @@ EditGasDisplay.propTypes = {
onEducationClick: PropTypes.func,
dappSuggestedGasFee: PropTypes.number,
dappOrigin: PropTypes.string,
defaultEstimateToUse: PropTypes.oneOf(Object.values(GAS_RECOMMENDATIONS)),
};
EditGasDisplay.defaultProps = {

View File

@ -1,5 +1,6 @@
.edit-gas-display {
& .actionable-message--warning {
& .actionable-message--warning,
& .actionable-message--error {
margin-top: 0;
}

View File

@ -10,7 +10,7 @@ export default function TransactionTotalBanner({ total, detail, timing }) {
return (
<div className="transaction-total-banner">
<Typography color={COLORS.BLACK} variant={TYPOGRAPHY.H1}>
{total}
~ {total}
</Typography>
{detail && (
<Typography

View File

@ -4,7 +4,13 @@ import PropTypes from 'prop-types';
import Typography from '../typography/typography';
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
export default function NumericInput({ detailText, value, onChange, error }) {
export default function NumericInput({
detailText,
value,
onChange,
error,
autoFocus,
}) {
return (
<div
className={classNames('numeric-input', { 'numeric-input--error': error })}
@ -12,8 +18,9 @@ export default function NumericInput({ detailText, value, onChange, error }) {
<input
type="number"
value={value}
onChange={(e) => onChange?.(Number(e.target.value))}
onChange={(e) => onChange?.(parseInt(e.target.value, 10))}
min="0"
autoFocus={autoFocus}
/>
{detailText && (
<Typography color={COLORS.UI4} variant={TYPOGRAPHY.H7} tag="span">
@ -29,6 +36,7 @@ NumericInput.propTypes = {
detailText: PropTypes.string,
onChange: PropTypes.func,
error: PropTypes.string,
autoFocus: PropTypes.bool,
};
NumericInput.defaultProps = {
@ -36,4 +44,5 @@ NumericInput.defaultProps = {
detailText: '',
onChange: undefined,
error: '',
autoFocus: false,
};

View File

@ -8,6 +8,10 @@
cursor: pointer;
}
&__column {
text-align: center;
}
&__column-recommended {
height: 20px;
}

View File

@ -14,6 +14,7 @@ export default function RadioGroup({ options, name, selectedValue, onChange }) {
return (
<div className="radio-group">
{options.map((option) => {
const checked = option.value === selectedValue;
return (
<div className="radio-group__column" key={`${name}-${option.value}`}>
<label>
@ -29,7 +30,8 @@ export default function RadioGroup({ options, name, selectedValue, onChange }) {
<input
type="radio"
name={name}
defaultChecked={option.value === selectedValue}
defaultChecked={checked}
checked={checked}
value={option.value}
onChange={() => onChange?.(option.value)}
/>