mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Edit gas fee modal more changes (#12660)
This commit is contained in:
parent
d6d35d9acb
commit
2c6fb06114
@ -581,6 +581,9 @@
|
|||||||
"dappSuggested": {
|
"dappSuggested": {
|
||||||
"message": "Site suggested"
|
"message": "Site suggested"
|
||||||
},
|
},
|
||||||
|
"dappSuggestedShortLabel": {
|
||||||
|
"message": "Site"
|
||||||
|
},
|
||||||
"dappSuggestedTooltip": {
|
"dappSuggestedTooltip": {
|
||||||
"message": "$1 has recommended this price.",
|
"message": "$1 has recommended this price.",
|
||||||
"description": "$1 represents the Dapp's origin"
|
"description": "$1 represents the Dapp's origin"
|
||||||
@ -1035,9 +1038,6 @@
|
|||||||
"gasPriceInfoTooltipContent": {
|
"gasPriceInfoTooltipContent": {
|
||||||
"message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
|
"message": "Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
|
||||||
},
|
},
|
||||||
"gasPriceLabel": {
|
|
||||||
"message": "Gas price"
|
|
||||||
},
|
|
||||||
"gasTimingHoursShort": {
|
"gasTimingHoursShort": {
|
||||||
"message": "$1 hrs",
|
"message": "$1 hrs",
|
||||||
"description": "$1 represents a number of hours"
|
"description": "$1 represents a number of hours"
|
||||||
|
@ -30,6 +30,17 @@ export const GAS_RECOMMENDATIONS = {
|
|||||||
HIGH: 'high',
|
HIGH: 'high',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These represent types of gas estimation
|
||||||
|
*/
|
||||||
|
export const PRIORITY_LEVELS = {
|
||||||
|
LOW: 'low',
|
||||||
|
MEDIUM: 'medium',
|
||||||
|
HIGH: 'high',
|
||||||
|
CUSTOM: 'custom',
|
||||||
|
DAPP_SUGGESTED: 'dappSuggested',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the user customizing their gas preference
|
* Represents the user customizing their gas preference
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import Popover from '../../ui/popover';
|
import Popover from '../../ui/popover';
|
||||||
import I18nValue from '../../ui/i18n-value';
|
import I18nValue from '../../ui/i18n-value';
|
||||||
@ -32,9 +33,27 @@ const EditGasFeePopover = ({ onClose }) => {
|
|||||||
<I18nValue messageKey="maxFee" />
|
<I18nValue messageKey="maxFee" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<EditGasItem estimateType="low" onClose={onClose} />
|
<EditGasItem
|
||||||
<EditGasItem estimateType="medium" onClose={onClose} />
|
priorityLevel={PRIORITY_LEVELS.LOW}
|
||||||
<EditGasItem estimateType="high" onClose={onClose} />
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
<EditGasItem
|
||||||
|
priorityLevel={PRIORITY_LEVELS.MEDIUM}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
<EditGasItem
|
||||||
|
priorityLevel={PRIORITY_LEVELS.HIGH}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
<div className="edit-gas-fee-popover__content__separator" />
|
||||||
|
<EditGasItem
|
||||||
|
priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
<EditGasItem
|
||||||
|
priorityLevel={PRIORITY_LEVELS.CUSTOM}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -71,16 +71,18 @@ describe('EditGasFeePopover', () => {
|
|||||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Site')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show time estimates', () => {
|
it('should show time estimates', () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
console.log(document.body.innerHTML);
|
expect(screen.queryAllByText('5 min')).toHaveLength(2);
|
||||||
expect(screen.queryByText('6 min')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('30 sec')).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
||||||
|
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
|
||||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
||||||
import { PRIMARY } from '../../../../helpers/constants/common';
|
import { PRIMARY } from '../../../../helpers/constants/common';
|
||||||
import {
|
import {
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
decimalToHex,
|
decimalToHex,
|
||||||
|
hexWEIToDecGWEI,
|
||||||
} from '../../../../helpers/utils/conversions.util';
|
} from '../../../../helpers/utils/conversions.util';
|
||||||
|
import { getAdvancedGasFeeValues } from '../../../../selectors';
|
||||||
import { toHumanReadableTime } from '../../../../helpers/utils/util';
|
import { toHumanReadableTime } from '../../../../helpers/utils/util';
|
||||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
@ -16,58 +20,120 @@ import I18nValue from '../../../ui/i18n-value';
|
|||||||
import InfoTooltip from '../../../ui/info-tooltip';
|
import InfoTooltip from '../../../ui/info-tooltip';
|
||||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||||
|
|
||||||
const EditGasItem = ({ estimateType, onClose }) => {
|
import { useCustomTimeEstimate } from './useCustomTimeEstimate';
|
||||||
|
|
||||||
|
const EditGasItem = ({ priorityLevel, onClose }) => {
|
||||||
const {
|
const {
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
gasFeeEstimates,
|
gasFeeEstimates,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
setEstimateToUse,
|
maxFeePerGas: maxFeePerGasValue,
|
||||||
updateTransaction,
|
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
|
||||||
|
updateTransactionUsingGasFeeEstimates,
|
||||||
|
transaction: { dappSuggestedGasFees },
|
||||||
} = useGasFeeContext();
|
} = useGasFeeContext();
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
|
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
|
||||||
|
let maxFeePerGas;
|
||||||
|
let maxPriorityFeePerGas;
|
||||||
|
let minWaitTime;
|
||||||
|
|
||||||
const { minWaitTimeEstimate, suggestedMaxFeePerGas } =
|
if (gasFeeEstimates[priorityLevel]) {
|
||||||
gasFeeEstimates[estimateType] || {};
|
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
|
||||||
const hexMaximumTransactionFee = suggestedMaxFeePerGas
|
} else if (
|
||||||
|
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||||
|
dappSuggestedGasFees
|
||||||
|
) {
|
||||||
|
maxFeePerGas = hexWEIToDecGWEI(dappSuggestedGasFees.maxFeePerGas);
|
||||||
|
maxPriorityFeePerGas = hexWEIToDecGWEI(
|
||||||
|
dappSuggestedGasFees.maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
} else if (priorityLevel === PRIORITY_LEVELS.CUSTOM) {
|
||||||
|
if (estimateUsed === PRIORITY_LEVELS.CUSTOM) {
|
||||||
|
maxFeePerGas = maxFeePerGasValue;
|
||||||
|
maxPriorityFeePerGas = maxPriorityFeePerGasValue;
|
||||||
|
} else if (advancedGasFeeValues) {
|
||||||
|
maxFeePerGas =
|
||||||
|
gasFeeEstimates.estimatedBaseFee *
|
||||||
|
parseFloat(advancedGasFeeValues.maxBaseFee);
|
||||||
|
maxPriorityFeePerGas = advancedGasFeeValues.priorityFee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { waitTimeEstimate } = useCustomTimeEstimate({
|
||||||
|
gasFeeEstimates,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (gasFeeEstimates[priorityLevel]) {
|
||||||
|
minWaitTime =
|
||||||
|
priorityLevel === PRIORITY_LEVELS.HIGH
|
||||||
|
? gasFeeEstimates?.high.minWaitTimeEstimate
|
||||||
|
: gasFeeEstimates?.low.maxWaitTimeEstimate;
|
||||||
|
} else {
|
||||||
|
minWaitTime = waitTimeEstimate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexMaximumTransactionFee = maxFeePerGas
|
||||||
? getMaximumGasTotalInHexWei({
|
? getMaximumGasTotalInHexWei({
|
||||||
gasLimit: decimalToHex(gasLimit),
|
gasLimit: decimalToHex(gasLimit),
|
||||||
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
|
maxFeePerGas: decGWEIToHexWEI(maxFeePerGas),
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const onOptionSelect = () => {
|
const onOptionSelect = () => {
|
||||||
setEstimateToUse(estimateType);
|
if (priorityLevel !== PRIORITY_LEVELS.CUSTOM) {
|
||||||
updateTransaction(estimateType);
|
updateTransactionUsingGasFeeEstimates(priorityLevel);
|
||||||
|
}
|
||||||
|
// todo: open advance modal if priorityLevel is custom
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames('edit-gas-item', {
|
className={classNames('edit-gas-item', {
|
||||||
'edit-gas-item--selected': estimateType === estimateUsed,
|
'edit-gas-item-selected': priorityLevel === estimateUsed,
|
||||||
|
'edit-gas-item-disabled':
|
||||||
|
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
|
||||||
|
!dappSuggestedGasFees,
|
||||||
})}
|
})}
|
||||||
role="button"
|
role="button"
|
||||||
onClick={onOptionSelect}
|
onClick={onOptionSelect}
|
||||||
>
|
>
|
||||||
<span className="edit-gas-item__name">
|
<span className="edit-gas-item__name">
|
||||||
<span className="edit-gas-item__icon">
|
<span
|
||||||
{PRIORITY_LEVEL_ICON_MAP[estimateType]}
|
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
|
||||||
|
>
|
||||||
|
{PRIORITY_LEVEL_ICON_MAP[priorityLevel]}
|
||||||
</span>
|
</span>
|
||||||
<I18nValue messageKey={estimateType} />
|
<I18nValue
|
||||||
|
messageKey={
|
||||||
|
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED
|
||||||
|
? 'dappSuggestedShortLabel'
|
||||||
|
: priorityLevel
|
||||||
|
}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${estimateType}`}
|
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
|
||||||
>
|
>
|
||||||
{minWaitTimeEstimate && toHumanReadableTime(t, minWaitTimeEstimate)}
|
{minWaitTime
|
||||||
|
? minWaitTime && toHumanReadableTime(t, minWaitTime)
|
||||||
|
: '--'}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${estimateType}`}
|
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${priorityLevel}`}
|
||||||
>
|
>
|
||||||
|
{hexMaximumTransactionFee ? (
|
||||||
<UserPreferencedCurrencyDisplay
|
<UserPreferencedCurrencyDisplay
|
||||||
key="editGasSubTextFeeAmount"
|
key="editGasSubTextFeeAmount"
|
||||||
type={PRIMARY}
|
type={PRIMARY}
|
||||||
value={hexMaximumTransactionFee}
|
value={hexMaximumTransactionFee}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
'--'
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="edit-gas-item__tooltip">
|
<span className="edit-gas-item__tooltip">
|
||||||
<InfoTooltip position="top" />
|
<InfoTooltip position="top" />
|
||||||
@ -77,7 +143,7 @@ const EditGasItem = ({ estimateType, onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
EditGasItem.propTypes = {
|
EditGasItem.propTypes = {
|
||||||
estimateType: PropTypes.string,
|
priorityLevel: PropTypes.string,
|
||||||
onClose: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ jest.mock('../../../../store/actions', () => ({
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(() => Promise.resolve()),
|
.mockImplementation(() => Promise.resolve()),
|
||||||
addPollingTokenToAppState: jest.fn(),
|
addPollingTokenToAppState: jest.fn(),
|
||||||
|
getGasFeeTimeEstimate: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve('unknown')),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const MOCK_FEE_ESTIMATE = {
|
const MOCK_FEE_ESTIMATE = {
|
||||||
@ -38,7 +41,12 @@ const MOCK_FEE_ESTIMATE = {
|
|||||||
estimatedBaseFee: '50',
|
estimatedBaseFee: '50',
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderComponent = (props) => {
|
const DAPP_SUGGESTED_ESTIMATE = {
|
||||||
|
maxFeePerGas: '0x59682f10',
|
||||||
|
maxPriorityFeePerGas: '0x59682f00',
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComponent = (props, transactionProps, gasFeeContextProps) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
metamask: {
|
metamask: {
|
||||||
nativeCurrency: ETH,
|
nativeCurrency: ETH,
|
||||||
@ -53,41 +61,78 @@ const renderComponent = (props) => {
|
|||||||
selectedAddress: '0xAddress',
|
selectedAddress: '0xAddress',
|
||||||
featureFlags: { advancedInlineGas: true },
|
featureFlags: { advancedInlineGas: true },
|
||||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||||
|
advancedGasFee: {
|
||||||
|
maxBaseFee: '1.5',
|
||||||
|
priorityFee: '2',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return renderWithProvider(
|
return renderWithProvider(
|
||||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}>
|
<GasFeeContextProvider
|
||||||
<EditGasItem estimateType="low" {...props} />
|
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }}
|
||||||
|
{...gasFeeContextProps}
|
||||||
|
>
|
||||||
|
<EditGasItem priorityLevel="low" {...props} />
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('EditGasItem', () => {
|
describe('EditGasItem', () => {
|
||||||
it('should renders low gas estimate options for estimateType low', () => {
|
it('should renders low gas estimate option for priorityLevel low', () => {
|
||||||
renderComponent({ estimateType: 'low' });
|
renderComponent({ priorityLevel: 'low' });
|
||||||
|
|
||||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('6 min')).toBeInTheDocument();
|
expect(screen.queryByText('5 min')).toBeInTheDocument();
|
||||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should renders market gas estimate options for estimateType medium', () => {
|
it('should renders market gas estimate option for priorityLevel medium', () => {
|
||||||
renderComponent({ estimateType: 'medium' });
|
renderComponent({ priorityLevel: 'medium' });
|
||||||
|
|
||||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('30 sec')).toBeInTheDocument();
|
expect(screen.queryByText('5 min')).toBeInTheDocument();
|
||||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should renders aggressive gas estimate options for estimateType high', () => {
|
it('should renders aggressive gas estimate option for priorityLevel high', () => {
|
||||||
renderComponent({ estimateType: 'high' });
|
renderComponent({ priorityLevel: 'high' });
|
||||||
|
|
||||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should highlight option is priorityLevel is currently selected', () => {
|
||||||
|
renderComponent({ priorityLevel: 'high' }, { userFeeLevel: 'high' });
|
||||||
|
expect(
|
||||||
|
document.getElementsByClassName('edit-gas-item-selected'),
|
||||||
|
).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
|
||||||
|
renderComponent(
|
||||||
|
{ priorityLevel: 'dappSuggested' },
|
||||||
|
{ dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
|
||||||
|
);
|
||||||
|
expect(screen.queryByText('🌐')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Site')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTitle('0.0000315 ETH')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable site gas estimate option for is transaction does not have dappSuggestedGasFees', async () => {
|
||||||
|
renderComponent({ priorityLevel: 'dappSuggested' });
|
||||||
|
expect(
|
||||||
|
document.getElementsByClassName('edit-gas-item-disabled'),
|
||||||
|
).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should renders advance gas estimate option for priorityLevel custom', () => {
|
||||||
|
renderComponent({ priorityLevel: 'custom' });
|
||||||
|
expect(screen.queryByText('⚙')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Advanced')).toBeInTheDocument();
|
||||||
|
// below value of custom gas fee estimate is default obtained from state.metamask.advancedGasFee
|
||||||
|
expect(screen.queryByTitle('0.001575 ETH')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
color: $ui-4;
|
color: $ui-4;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@ -11,8 +13,13 @@
|
|||||||
background-color: $ui-1;
|
background-color: $ui-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
color: $ui-black;
|
color: $ui-black;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -21,6 +28,11 @@
|
|||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|
||||||
|
&-custom {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__time-estimate {
|
&__time-estimate {
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
|
||||||
|
import {
|
||||||
|
getGasEstimateType,
|
||||||
|
getIsGasEstimatesLoading,
|
||||||
|
} from '../../../../ducks/metamask/metamask';
|
||||||
|
import { getGasFeeTimeEstimate } from '../../../../store/actions';
|
||||||
|
|
||||||
|
export const useCustomTimeEstimate = ({
|
||||||
|
gasFeeEstimates,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
}) => {
|
||||||
|
const gasEstimateType = useSelector(getGasEstimateType);
|
||||||
|
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||||
|
|
||||||
|
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||||
|
|
||||||
|
const returnNoEstimates =
|
||||||
|
isGasEstimatesLoading ||
|
||||||
|
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||||
|
!maxPriorityFeePerGas;
|
||||||
|
|
||||||
|
// If the user has chosen a value lower than the low gas fee estimate,
|
||||||
|
// We'll need to use the useEffect hook below to make a call to calculate
|
||||||
|
// the time to show
|
||||||
|
const isUnknownLow =
|
||||||
|
gasFeeEstimates?.low &&
|
||||||
|
Number(maxPriorityFeePerGas) <
|
||||||
|
Number(gasFeeEstimates.low.suggestedMaxPriorityFeePerGas);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isGasEstimatesLoading ||
|
||||||
|
gasEstimateType !== GAS_ESTIMATE_TYPES.FEE_MARKET ||
|
||||||
|
!maxPriorityFeePerGas
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
if (isUnknownLow) {
|
||||||
|
// getGasFeeTimeEstimate requires parameters in string format
|
||||||
|
getGasFeeTimeEstimate(
|
||||||
|
new BigNumber(maxPriorityFeePerGas, 10).toString(10),
|
||||||
|
new BigNumber(maxFeePerGas, 10).toString(10),
|
||||||
|
).then((result) => {
|
||||||
|
setCustomEstimatedTime(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
gasEstimateType,
|
||||||
|
isUnknownLow,
|
||||||
|
isGasEstimatesLoading,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
returnNoEstimates,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (returnNoEstimates) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { low = {}, medium = {}, high = {} } = gasFeeEstimates;
|
||||||
|
let waitTimeEstimate = '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
isUnknownLow &&
|
||||||
|
customEstimatedTime &&
|
||||||
|
customEstimatedTime !== 'unknown' &&
|
||||||
|
customEstimatedTime?.upperTimeBound !== 'unknown'
|
||||||
|
) {
|
||||||
|
waitTimeEstimate = Number(customEstimatedTime?.upperTimeBound);
|
||||||
|
} else if (
|
||||||
|
Number(maxPriorityFeePerGas) >= Number(medium.suggestedMaxPriorityFeePerGas)
|
||||||
|
) {
|
||||||
|
waitTimeEstimate = high.minWaitTimeEstimate;
|
||||||
|
} else {
|
||||||
|
waitTimeEstimate = low.maxWaitTimeEstimate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { waitTimeEstimate };
|
||||||
|
};
|
@ -31,5 +31,10 @@
|
|||||||
width: 30%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__separator {
|
||||||
|
border-top: 1px solid $ui-grey;
|
||||||
|
margin: 8px 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,10 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
|||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const {
|
const {
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasPrice,
|
|
||||||
estimateUsed,
|
estimateUsed,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
transaction,
|
transaction,
|
||||||
supportsEIP1559,
|
|
||||||
} = useGasFeeContext();
|
} = useGasFeeContext();
|
||||||
|
|
||||||
if (EIP_1559_V2 && estimateUsed) {
|
if (EIP_1559_V2 && estimateUsed) {
|
||||||
@ -48,8 +46,6 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
|||||||
<Typography fontSize="12px" color={COLORS.GREY}>
|
<Typography fontSize="12px" color={COLORS.GREY}>
|
||||||
{t('dappSuggestedTooltip', [transaction.origin])}
|
{t('dappSuggestedTooltip', [transaction.origin])}
|
||||||
</Typography>
|
</Typography>
|
||||||
{supportsEIP1559 ? (
|
|
||||||
<>
|
|
||||||
<Typography fontSize="12px">
|
<Typography fontSize="12px">
|
||||||
<b>{t('maxBaseFee')}</b>
|
<b>{t('maxBaseFee')}</b>
|
||||||
{maxFeePerGas}
|
{maxFeePerGas}
|
||||||
@ -58,13 +54,6 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
|||||||
<b>{t('maxPriorityFee')}</b>
|
<b>{t('maxPriorityFee')}</b>
|
||||||
{maxPriorityFeePerGas}
|
{maxPriorityFeePerGas}
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Typography fontSize="12px">
|
|
||||||
<b>{t('gasPriceLabel')}</b>
|
|
||||||
{gasPrice}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<Typography fontSize="12px">
|
<Typography fontSize="12px">
|
||||||
<b>{t('gasLimit')}</b>
|
<b>{t('gasLimit')}</b>
|
||||||
{gasLimit}
|
{gasLimit}
|
||||||
|
@ -31,7 +31,7 @@ export function useGasFeeContext() {
|
|||||||
GasFeeContextProvider.propTypes = {
|
GasFeeContextProvider.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
defaultEstimateToUse: PropTypes.string,
|
defaultEstimateToUse: PropTypes.string,
|
||||||
transaction: PropTypes.object.isRequired,
|
transaction: PropTypes.object,
|
||||||
minimumGasLimit: PropTypes.string,
|
minimumGasLimit: PropTypes.string,
|
||||||
editGasMode: PropTypes.string,
|
editGasMode: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -108,6 +108,19 @@ export function useGasFeeInputs(
|
|||||||
return estimateToUse;
|
return estimateToUse;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In EIP-1559 V2 designs change to gas estimate is always updated to transaction
|
||||||
|
* Thus callback setEstimateToUse can be deprecate in favour of this useEffect
|
||||||
|
* so that transaction is source of truth whenever possible.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (areDappSuggestedAndTxParamGasFeesTheSame(transaction)) {
|
||||||
|
setEstimateUsed('dappSuggested');
|
||||||
|
} else if (transaction?.userFeeLevel) {
|
||||||
|
setEstimateUsed(transaction?.userFeeLevel);
|
||||||
|
}
|
||||||
|
}, [setEstimateUsed, transaction]);
|
||||||
|
|
||||||
const [gasLimit, setGasLimit] = useState(() =>
|
const [gasLimit, setGasLimit] = useState(() =>
|
||||||
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
|
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
|
||||||
);
|
);
|
||||||
@ -198,7 +211,7 @@ export function useGasFeeInputs(
|
|||||||
}
|
}
|
||||||
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
||||||
|
|
||||||
const { updateTransaction } = useTransactionFunctions({
|
const { updateTransactionUsingGasFeeEstimates } = useTransactionFunctions({
|
||||||
defaultEstimateToUse,
|
defaultEstimateToUse,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
@ -289,6 +302,6 @@ export function useGasFeeInputs(
|
|||||||
gasWarnings,
|
gasWarnings,
|
||||||
hasGasErrors,
|
hasGasErrors,
|
||||||
supportsEIP1559,
|
supportsEIP1559,
|
||||||
updateTransaction,
|
updateTransactionUsingGasFeeEstimates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { PRIORITY_LEVELS } from '../../../shared/constants/gas';
|
||||||
import {
|
import {
|
||||||
decGWEIToHexWEI,
|
decGWEIToHexWEI,
|
||||||
decimalToHex,
|
decimalToHex,
|
||||||
@ -16,24 +17,19 @@ export const useTransactionFunctions = ({
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const updateTransaction = useCallback(
|
const updateTransaction = useCallback(
|
||||||
(estimateType) => {
|
(estimateUsed, maxFeePerGas, maxPriorityFeePerGas) => {
|
||||||
const newGasSettings = {
|
const newGasSettings = {
|
||||||
gas: decimalToHex(gasLimit),
|
gas: decimalToHex(gasLimit),
|
||||||
gasLimit: decimalToHex(gasLimit),
|
gasLimit: decimalToHex(gasLimit),
|
||||||
estimateSuggested: defaultEstimateToUse,
|
estimateSuggested: defaultEstimateToUse,
|
||||||
estimateUsed: estimateType,
|
estimateUsed,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
};
|
};
|
||||||
|
|
||||||
newGasSettings.maxFeePerGas = decGWEIToHexWEI(
|
|
||||||
gasFeeEstimates[estimateType].suggestedMaxFeePerGas,
|
|
||||||
);
|
|
||||||
newGasSettings.maxPriorityFeePerGas = decGWEIToHexWEI(
|
|
||||||
gasFeeEstimates[estimateType].suggestedMaxPriorityFeePerGas,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedTxMeta = {
|
const updatedTxMeta = {
|
||||||
...transaction,
|
...transaction,
|
||||||
userFeeLevel: estimateType || 'custom',
|
userFeeLevel: estimateUsed || 'custom',
|
||||||
txParams: {
|
txParams: {
|
||||||
...transaction.txParams,
|
...transaction.txParams,
|
||||||
...newGasSettings,
|
...newGasSettings,
|
||||||
@ -42,8 +38,35 @@ export const useTransactionFunctions = ({
|
|||||||
|
|
||||||
dispatch(updateTransactionFn(updatedTxMeta));
|
dispatch(updateTransactionFn(updatedTxMeta));
|
||||||
},
|
},
|
||||||
[defaultEstimateToUse, dispatch, gasLimit, gasFeeEstimates, transaction],
|
[defaultEstimateToUse, dispatch, gasLimit, transaction],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { updateTransaction };
|
const updateTransactionUsingGasFeeEstimates = useCallback(
|
||||||
|
(gasFeeEstimateToUse) => {
|
||||||
|
if (gasFeeEstimateToUse === PRIORITY_LEVELS.DAPP_SUGGESTED) {
|
||||||
|
const {
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
} = transaction?.dappSuggestedGasFees;
|
||||||
|
updateTransaction(
|
||||||
|
PRIORITY_LEVELS.CUSTOM,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
suggestedMaxFeePerGas,
|
||||||
|
suggestedMaxPriorityFeePerGas,
|
||||||
|
} = gasFeeEstimates[gasFeeEstimateToUse];
|
||||||
|
updateTransaction(
|
||||||
|
gasFeeEstimateToUse,
|
||||||
|
decGWEIToHexWEI(suggestedMaxFeePerGas),
|
||||||
|
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[gasFeeEstimates, transaction?.dappSuggestedGasFees, updateTransaction],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { updateTransactionUsingGasFeeEstimates };
|
||||||
};
|
};
|
||||||
|
@ -26,7 +26,6 @@ const GasDetailsItem = ({
|
|||||||
isMainnet,
|
isMainnet,
|
||||||
maxFeePerGas,
|
maxFeePerGas,
|
||||||
maxPriorityFeePerGas,
|
maxPriorityFeePerGas,
|
||||||
supportsEIP1559,
|
|
||||||
txData,
|
txData,
|
||||||
useNativeCurrencyAsPrimaryCurrency,
|
useNativeCurrencyAsPrimaryCurrency,
|
||||||
}) => {
|
}) => {
|
||||||
@ -120,7 +119,6 @@ const GasDetailsItem = ({
|
|||||||
</Box>,
|
</Box>,
|
||||||
])}
|
])}
|
||||||
subTitle={
|
subTitle={
|
||||||
supportsEIP1559 && (
|
|
||||||
<GasTiming
|
<GasTiming
|
||||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||||
maxPriorityFeePerGas || txData.txParams.maxPriorityFeePerGas,
|
maxPriorityFeePerGas || txData.txParams.maxPriorityFeePerGas,
|
||||||
@ -129,7 +127,6 @@ const GasDetailsItem = ({
|
|||||||
maxFeePerGas || txData.txParams.maxFeePerGas,
|
maxFeePerGas || txData.txParams.maxFeePerGas,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -141,7 +138,6 @@ GasDetailsItem.propTypes = {
|
|||||||
isMainnet: PropTypes.bool,
|
isMainnet: PropTypes.bool,
|
||||||
maxFeePerGas: PropTypes.string,
|
maxFeePerGas: PropTypes.string,
|
||||||
maxPriorityFeePerGas: PropTypes.string,
|
maxPriorityFeePerGas: PropTypes.string,
|
||||||
supportsEIP1559: PropTypes.bool,
|
|
||||||
txData: PropTypes.object,
|
txData: PropTypes.object,
|
||||||
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { screen } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import { ETH } from '../../../helpers/constants/common';
|
import { ETH } from '../../../helpers/constants/common';
|
||||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||||
@ -14,6 +14,7 @@ jest.mock('../../../store/actions', () => ({
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(() => Promise.resolve()),
|
.mockImplementation(() => Promise.resolve()),
|
||||||
addPollingTokenToAppState: jest.fn(),
|
addPollingTokenToAppState: jest.fn(),
|
||||||
|
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const render = (props) => {
|
const render = (props) => {
|
||||||
@ -37,28 +38,34 @@ const render = (props) => {
|
|||||||
|
|
||||||
return renderWithProvider(
|
return renderWithProvider(
|
||||||
<GasFeeContextProvider {...props}>
|
<GasFeeContextProvider {...props}>
|
||||||
<GasDetailsItem txData={{}} {...props} />
|
<GasDetailsItem txData={{ txParams: {} }} {...props} />
|
||||||
</GasFeeContextProvider>,
|
</GasFeeContextProvider>,
|
||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('GasDetailsItem', () => {
|
describe('GasDetailsItem', () => {
|
||||||
it('should render label', () => {
|
it('should render label', async () => {
|
||||||
render();
|
render();
|
||||||
|
await waitFor(() => {
|
||||||
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
expect(screen.queryByText('ETH')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should show warning icon if estimates are high', () => {
|
it('should show warning icon if estimates are high', async () => {
|
||||||
render({ defaultEstimateToUse: 'high' });
|
render({ defaultEstimateToUse: 'high' });
|
||||||
|
await waitFor(() => {
|
||||||
expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
|
expect(screen.queryByText('⚠ Max fee:')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not show warning icon if estimates are not high', () => {
|
it('should not show warning icon if estimates are not high', async () => {
|
||||||
render({ defaultEstimateToUse: 'low' });
|
render({ defaultEstimateToUse: 'low' });
|
||||||
|
await waitFor(() => {
|
||||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user