mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Adding edit gas fee modal (#12624)
Edit transaction screen changes for EIP-1559 V2
This commit is contained in:
parent
cce2dda8e4
commit
0daefe9ea0
@ -710,6 +710,9 @@
|
||||
"editGasEducationModalTitle": {
|
||||
"message": "How to choose?"
|
||||
},
|
||||
"editGasFeeModalTitle": {
|
||||
"message": "Edit gas fee"
|
||||
},
|
||||
"editGasHigh": {
|
||||
"message": "High"
|
||||
},
|
||||
@ -1011,6 +1014,9 @@
|
||||
"message": "Gas limit must be at least $1",
|
||||
"description": "$1 is the custom gas limit, in decimal."
|
||||
},
|
||||
"gasOption": {
|
||||
"message": "Gas option"
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Gas Price (GWEI)"
|
||||
},
|
||||
@ -1032,10 +1038,18 @@
|
||||
"gasPriceLabel": {
|
||||
"message": "Gas price"
|
||||
},
|
||||
"gasTimingHoursShort": {
|
||||
"message": "$1 hrs",
|
||||
"description": "$1 represents a number of hours"
|
||||
},
|
||||
"gasTimingMinutes": {
|
||||
"message": "$1 minutes",
|
||||
"description": "$1 represents a number of minutes"
|
||||
},
|
||||
"gasTimingMinutesShort": {
|
||||
"message": "$1 min",
|
||||
"description": "$1 represents a number of minutes"
|
||||
},
|
||||
"gasTimingNegative": {
|
||||
"message": "Maybe in $1",
|
||||
"description": "$1 represents an amount of time"
|
||||
@ -1048,6 +1062,10 @@
|
||||
"message": "$1 seconds",
|
||||
"description": "$1 represents a number of seconds"
|
||||
},
|
||||
"gasTimingSecondsShort": {
|
||||
"message": "$1 sec",
|
||||
"description": "$1 represents a number of seconds"
|
||||
},
|
||||
"gasTimingVeryPositive": {
|
||||
"message": "Very likely in < $1",
|
||||
"description": "$1 represents an amount of time"
|
||||
@ -2793,6 +2811,9 @@
|
||||
"thisWillCreate": {
|
||||
"message": "This will create a new wallet and Secret Recovery Phrase"
|
||||
},
|
||||
"time": {
|
||||
"message": "Time"
|
||||
},
|
||||
"tips": {
|
||||
"message": "Tips"
|
||||
},
|
||||
|
@ -13,6 +13,8 @@
|
||||
@import 'connected-status-indicator/index';
|
||||
@import 'edit-gas-display/index';
|
||||
@import 'edit-gas-display-education/index';
|
||||
@import 'edit-gas-fee-popover/index';
|
||||
@import 'edit-gas-fee-popover/edit-gas-item/index';
|
||||
@import 'gas-customization/gas-modal-page-container/index';
|
||||
@import 'gas-customization/gas-price-button-group/index';
|
||||
@import 'gas-customization/index';
|
||||
|
@ -8,12 +8,16 @@ import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import ErrorMessage from '../../ui/error-message';
|
||||
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
|
||||
import Dialog from '../../ui/dialog';
|
||||
import EditGasFeePopover from '../edit-gas-fee-popover/edit-gas-fee-popover';
|
||||
import {
|
||||
ConfirmPageContainerHeader,
|
||||
ConfirmPageContainerContent,
|
||||
ConfirmPageContainerNavigation,
|
||||
} from '.';
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
||||
|
||||
export default class ConfirmPageContainer extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
@ -225,13 +229,16 @@ export default class ConfirmPageContainer extends Component {
|
||||
)}
|
||||
</PageContainerFooter>
|
||||
)}
|
||||
{editingGas && (
|
||||
{editingGas && !EIP_1559_V2 && (
|
||||
<EditGasPopover
|
||||
mode={EDIT_GAS_MODES.MODIFY_IN_PLACE}
|
||||
onClose={handleCloseEditGas}
|
||||
transaction={currentTransaction}
|
||||
/>
|
||||
)}
|
||||
{editingGas && EIP_1559_V2 && (
|
||||
<EditGasFeePopover onClose={handleCloseEditGas} />
|
||||
)}
|
||||
</div>
|
||||
</GasFeeContextProvider>
|
||||
);
|
||||
|
@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import Popover from '../../ui/popover';
|
||||
import I18nValue from '../../ui/i18n-value';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
|
||||
import EditGasItem from './edit-gas-item';
|
||||
|
||||
const EditGasFeePopover = ({ onClose }) => {
|
||||
const t = useI18nContext();
|
||||
|
||||
return (
|
||||
<Popover
|
||||
title={t('editGasFeeModalTitle')}
|
||||
onClose={onClose}
|
||||
className="edit-gas-fee-popover"
|
||||
>
|
||||
<>
|
||||
{process.env.IN_TEST === 'true' ? null : <LoadingHeartBeat />}
|
||||
<div className="edit-gas-fee-popover__wrapper">
|
||||
<div className="edit-gas-fee-popover__content">
|
||||
<div className="edit-gas-fee-popover__content__header">
|
||||
<span className="edit-gas-fee-popover__content__header-option">
|
||||
<I18nValue messageKey="gasOption" />
|
||||
</span>
|
||||
<span className="edit-gas-fee-popover__content__header-time">
|
||||
<I18nValue messageKey="time" />
|
||||
</span>
|
||||
<span className="edit-gas-fee-popover__content__header-max-fee">
|
||||
<I18nValue messageKey="maxFee" />
|
||||
</span>
|
||||
</div>
|
||||
<EditGasItem estimateType="low" onClose={onClose} />
|
||||
<EditGasItem estimateType="medium" onClose={onClose} />
|
||||
<EditGasItem estimateType="high" onClose={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
EditGasFeePopover.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default EditGasFeePopover;
|
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import { ETH } from '../../../helpers/constants/common';
|
||||
import configureStore from '../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
|
||||
import EditGasFeePopover from './edit-gas-fee-popover';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const MOCK_FEE_ESTIMATE = {
|
||||
low: {
|
||||
minWaitTimeEstimate: 360000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 30000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
const renderComponent = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}>
|
||||
<EditGasFeePopover />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('EditGasFeePopover', () => {
|
||||
it('should renders low / medium / high options', () => {
|
||||
renderComponent();
|
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Aggressive')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show time estimates', () => {
|
||||
renderComponent();
|
||||
console.log(document.body.innerHTML);
|
||||
expect(screen.queryByText('6 min')).toBeInTheDocument();
|
||||
expect(screen.queryByText('30 sec')).toBeInTheDocument();
|
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show gas fee estimates', () => {
|
||||
renderComponent();
|
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
|
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
|
||||
import { PRIMARY } from '../../../../helpers/constants/common';
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
} from '../../../../helpers/utils/conversions.util';
|
||||
import { toHumanReadableTime } from '../../../../helpers/utils/util';
|
||||
import { useGasFeeContext } from '../../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import I18nValue from '../../../ui/i18n-value';
|
||||
import InfoTooltip from '../../../ui/info-tooltip';
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||
|
||||
const EditGasItem = ({ estimateType, onClose }) => {
|
||||
const {
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasLimit,
|
||||
setEstimateToUse,
|
||||
updateTransaction,
|
||||
} = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
|
||||
const { minWaitTimeEstimate, suggestedMaxFeePerGas } =
|
||||
gasFeeEstimates[estimateType] || {};
|
||||
const hexMaximumTransactionFee = suggestedMaxFeePerGas
|
||||
? getMaximumGasTotalInHexWei({
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
maxFeePerGas: decGWEIToHexWEI(suggestedMaxFeePerGas),
|
||||
})
|
||||
: null;
|
||||
|
||||
const onOptionSelect = () => {
|
||||
setEstimateToUse(estimateType);
|
||||
updateTransaction(estimateType);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('edit-gas-item', {
|
||||
'edit-gas-item--selected': estimateType === estimateUsed,
|
||||
})}
|
||||
role="button"
|
||||
onClick={onOptionSelect}
|
||||
>
|
||||
<span className="edit-gas-item__name">
|
||||
<span className="edit-gas-item__icon">
|
||||
{PRIORITY_LEVEL_ICON_MAP[estimateType]}
|
||||
</span>
|
||||
<I18nValue messageKey={estimateType} />
|
||||
</span>
|
||||
<span
|
||||
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${estimateType}`}
|
||||
>
|
||||
{minWaitTimeEstimate && toHumanReadableTime(t, minWaitTimeEstimate)}
|
||||
</span>
|
||||
<span
|
||||
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${estimateType}`}
|
||||
>
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
/>
|
||||
</span>
|
||||
<span className="edit-gas-item__tooltip">
|
||||
<InfoTooltip position="top" />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
EditGasItem.propTypes = {
|
||||
estimateType: PropTypes.string,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
export default EditGasItem;
|
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import { ETH } from '../../../../helpers/constants/common';
|
||||
import configureStore from '../../../../store/store';
|
||||
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
|
||||
|
||||
import EditGasItem from './edit-gas-item';
|
||||
|
||||
jest.mock('../../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
const MOCK_FEE_ESTIMATE = {
|
||||
low: {
|
||||
minWaitTimeEstimate: 360000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 30000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
};
|
||||
|
||||
const renderComponent = (props) => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
nativeCurrency: ETH,
|
||||
provider: {},
|
||||
cachedBalances: {},
|
||||
accounts: {
|
||||
'0xAddress': {
|
||||
address: '0xAddress',
|
||||
balance: '0x176e5b6f173ebe66',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0xAddress',
|
||||
featureFlags: { advancedInlineGas: true },
|
||||
gasFeeEstimates: MOCK_FEE_ESTIMATE,
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}>
|
||||
<EditGasItem estimateType="low" {...props} />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('EditGasItem', () => {
|
||||
it('should renders low gas estimate options for estimateType low', () => {
|
||||
renderComponent({ estimateType: 'low' });
|
||||
|
||||
expect(screen.queryByText('🐢')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Low')).toBeInTheDocument();
|
||||
expect(screen.queryByText('6 min')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.001113 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders market gas estimate options for estimateType medium', () => {
|
||||
renderComponent({ estimateType: 'medium' });
|
||||
|
||||
expect(screen.queryByText('🦊')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Market')).toBeInTheDocument();
|
||||
expect(screen.queryByText('30 sec')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.00147 ETH')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders aggressive gas estimate options for estimateType high', () => {
|
||||
renderComponent({ estimateType: 'high' });
|
||||
|
||||
expect(screen.queryByText('🦍')).toBeInTheDocument();
|
||||
expect(screen.queryByText('15 sec')).toBeInTheDocument();
|
||||
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './edit-gas-item';
|
@ -0,0 +1,56 @@
|
||||
.edit-gas-item {
|
||||
border-radius: 24px;
|
||||
color: $ui-4;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin: 12px 0;
|
||||
padding: 4px 12px;
|
||||
height: 32px;
|
||||
|
||||
&--selected {
|
||||
background-color: $ui-1;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: inline-block;
|
||||
color: $ui-black;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&__time-estimate {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
&__fee-estimate {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__tooltip {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
width: 10%;
|
||||
|
||||
.info-tooltip {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&__time-estimate-low,
|
||||
&__fee-estimate-high {
|
||||
color: $secondary-1;
|
||||
}
|
||||
|
||||
&__time-estimate-medium,
|
||||
&__time-estimate-high {
|
||||
color: $success-3;
|
||||
}
|
||||
}
|
1
ui/components/app/edit-gas-fee-popover/index.js
Normal file
1
ui/components/app/edit-gas-fee-popover/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './edit-gas-fee-popover';
|
35
ui/components/app/edit-gas-fee-popover/index.scss
Normal file
35
ui/components/app/edit-gas-fee-popover/index.scss
Normal file
@ -0,0 +1,35 @@
|
||||
.edit-gas-fee-popover {
|
||||
@media screen and (min-width: $break-large) {
|
||||
max-height: 84vh;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
border-top: 1px solid $ui-grey;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 16px 12px;
|
||||
|
||||
&__header {
|
||||
color: $ui-4;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
margin: 0 12px;
|
||||
|
||||
&-option {
|
||||
display: inline-block;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&-time {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
&-max-fee {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ export default function GasTiming({
|
||||
|
||||
const [customEstimatedTime, setCustomEstimatedTime] = useState(null);
|
||||
const t = useContext(I18nContext);
|
||||
const { estimateToUse } = useGasFeeContext();
|
||||
const { estimateUsed } = useGasFeeContext();
|
||||
|
||||
// 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
|
||||
@ -155,7 +155,7 @@ export default function GasTiming({
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!EIP_1559_V2 || estimateToUse === 'low') {
|
||||
if (!EIP_1559_V2 || estimateUsed === 'low') {
|
||||
attitude = 'negative';
|
||||
}
|
||||
// If the user has chosen a value less than our low estimate,
|
||||
|
@ -1,40 +1,29 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import InfoTooltip from '../../ui/info-tooltip/info-tooltip';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
|
||||
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
|
||||
import { COLORS } from '../../../helpers/constants/design-system';
|
||||
|
||||
const GasLevelIconMap = {
|
||||
low: '🐢',
|
||||
medium: '🦊',
|
||||
high: '🦍',
|
||||
dappSuggested: '🌐',
|
||||
custom: '⚙',
|
||||
};
|
||||
import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
export default function TransactionDetail({ rows = [], onEdit }) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
const EIP_1559_V2 = process.env.EIP_1559_V2;
|
||||
|
||||
const t = useContext(I18nContext);
|
||||
const t = useI18nContext();
|
||||
const {
|
||||
estimateToUse,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
isUsingDappSuggestedGasFees,
|
||||
estimateUsed,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
transaction,
|
||||
supportsEIP1559,
|
||||
} = useGasFeeContext();
|
||||
const estimateUsed = isUsingDappSuggestedGasFees
|
||||
? 'dappSuggested'
|
||||
: estimateToUse;
|
||||
|
||||
if (EIP_1559_V2 && estimateUsed) {
|
||||
return (
|
||||
@ -42,7 +31,7 @@ export default function TransactionDetail({ rows = [], onEdit }) {
|
||||
<div className="transaction-detail-edit-V2">
|
||||
<button onClick={onEdit}>
|
||||
<span className="transaction-detail-edit-V2__icon">
|
||||
{`${GasLevelIconMap[estimateUsed]} `}
|
||||
{`${PRIORITY_LEVEL_ICON_MAP[estimateUsed]} `}
|
||||
</span>
|
||||
<span className="transaction-detail-edit-V2__label">
|
||||
{t(estimateUsed)}
|
||||
|
@ -31,3 +31,11 @@ export function getGasFormErrorText(type, t, { minimumGasLimit } = {}) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export const PRIORITY_LEVEL_ICON_MAP = {
|
||||
low: '🐢',
|
||||
medium: '🦊',
|
||||
high: '🦍',
|
||||
dappSuggested: '🌐',
|
||||
custom: '⚙',
|
||||
};
|
||||
|
@ -408,3 +408,19 @@ export function getURLHost(url) {
|
||||
export function getURLHostName(url) {
|
||||
return getURL(url)?.hostname || '';
|
||||
}
|
||||
|
||||
// Once we reach this threshold, we switch to higher unit
|
||||
const MINUTE_CUTOFF = 90 * 60;
|
||||
const SECOND_CUTOFF = 90;
|
||||
|
||||
export const toHumanReadableTime = (t, milliseconds) => {
|
||||
if (milliseconds === undefined || milliseconds === null) return '';
|
||||
const seconds = Math.ceil(milliseconds / 1000);
|
||||
if (seconds <= SECOND_CUTOFF) {
|
||||
return t('gasTimingSecondsShort', [seconds]);
|
||||
}
|
||||
if (seconds <= MINUTE_CUTOFF) {
|
||||
return t('gasTimingMinutesShort', [Math.ceil(seconds / 60)]);
|
||||
}
|
||||
return t('gasTimingHoursShort', [Math.ceil(seconds / 3600)]);
|
||||
};
|
||||
|
@ -301,4 +301,43 @@ describe('util', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toHumanReadableTime()', () => {
|
||||
const t = (key, number) => {
|
||||
switch (key) {
|
||||
case 'gasTimingSecondsShort':
|
||||
return `${number} sec`;
|
||||
case 'gasTimingMinutesShort':
|
||||
return `${number} min`;
|
||||
case 'gasTimingHoursShort':
|
||||
return `${number} hrs`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
it('should return empty string if milliseconds passed is undefined', () => {
|
||||
expect(util.toHumanReadableTime(t)).toStrictEqual('');
|
||||
});
|
||||
it('should return rounded value for time', () => {
|
||||
expect(util.toHumanReadableTime(t, 6300)).toStrictEqual('7 sec');
|
||||
});
|
||||
it('should return value in seconds for milliseconds passed is < 9000', () => {
|
||||
expect(util.toHumanReadableTime(t, 6000)).toStrictEqual('6 sec');
|
||||
});
|
||||
it('should return value in seconds for milliseconds passed is > 6000 and <= 9000', () => {
|
||||
expect(util.toHumanReadableTime(t, 9000)).toStrictEqual('9 sec');
|
||||
});
|
||||
it('should return value in minutes for milliseconds passed is > 90000', () => {
|
||||
expect(util.toHumanReadableTime(t, 90001)).toStrictEqual('2 min');
|
||||
});
|
||||
it('should return value in minutes for milliseconds passed is > 90000 and <= 5400000', () => {
|
||||
expect(util.toHumanReadableTime(t, 5400000)).toStrictEqual('90 min');
|
||||
});
|
||||
it('should return value in hours for milliseconds passed is > 5400000', () => {
|
||||
expect(util.toHumanReadableTime(t, 5400001)).toStrictEqual('2 hrs');
|
||||
});
|
||||
it('should return value in hours for milliseconds passed very high above 5400000', () => {
|
||||
expect(util.toHumanReadableTime(t, 7200000)).toStrictEqual('2 hrs');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,7 @@ import { useGasPriceInput } from './useGasPriceInput';
|
||||
import { useMaxFeePerGasInput } from './useMaxFeePerGasInput';
|
||||
import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
|
||||
import { useGasEstimates } from './useGasEstimates';
|
||||
import { useTransactionFunctions } from './useTransactionFunctions';
|
||||
|
||||
/**
|
||||
* @typedef {Object} GasFeeInputReturnType
|
||||
@ -100,14 +101,14 @@ export function useGasFeeInputs(
|
||||
return defaultEstimateToUse;
|
||||
});
|
||||
|
||||
const [
|
||||
isUsingDappSuggestedGasFees,
|
||||
setIsUsingDappSuggestedGasFees,
|
||||
] = useState(() =>
|
||||
Boolean(areDappSuggestedAndTxParamGasFeesTheSame(transaction)),
|
||||
);
|
||||
const [estimateUsed, setEstimateUsed] = useState(() => {
|
||||
if (areDappSuggestedAndTxParamGasFeesTheSame(transaction)) {
|
||||
return 'dappSuggested';
|
||||
}
|
||||
return estimateToUse;
|
||||
});
|
||||
|
||||
const [gasLimit, setGasLimit] = useState(
|
||||
const [gasLimit, setGasLimit] = useState(() =>
|
||||
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
|
||||
);
|
||||
|
||||
@ -197,6 +198,17 @@ export function useGasFeeInputs(
|
||||
}
|
||||
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
|
||||
|
||||
const { updateTransaction } = useTransactionFunctions({
|
||||
defaultEstimateToUse,
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
gasFeeEstimates,
|
||||
supportsEIP1559,
|
||||
transaction,
|
||||
});
|
||||
|
||||
// When a user selects an estimate level, it will wipe out what they have
|
||||
// previously put in the inputs. This returns the inputs to the estimated
|
||||
// values at the level specified.
|
||||
@ -208,7 +220,7 @@ export function useGasFeeInputs(
|
||||
setMaxPriorityFeePerGas(null);
|
||||
setGasPrice(null);
|
||||
setGasPriceHasBeenManuallySet(false);
|
||||
setIsUsingDappSuggestedGasFees(false);
|
||||
setEstimateUsed(estimateLevel);
|
||||
},
|
||||
[
|
||||
setInternalEstimateToUse,
|
||||
@ -217,7 +229,7 @@ export function useGasFeeInputs(
|
||||
setMaxPriorityFeePerGas,
|
||||
setGasPrice,
|
||||
setGasPriceHasBeenManuallySet,
|
||||
setIsUsingDappSuggestedGasFees,
|
||||
setEstimateUsed,
|
||||
],
|
||||
);
|
||||
|
||||
@ -230,6 +242,7 @@ export function useGasFeeInputs(
|
||||
setMaxFeePerGas(maxFeePerGas);
|
||||
setMaxPriorityFeePerGas(maxPriorityFeePerGas);
|
||||
setGasPriceHasBeenManuallySet(true);
|
||||
setEstimateUsed('custom');
|
||||
}, [
|
||||
setInternalEstimateToUse,
|
||||
handleGasLimitOutOfBoundError,
|
||||
@ -263,7 +276,7 @@ export function useGasFeeInputs(
|
||||
estimatedMaximumNative,
|
||||
estimatedMinimumNative,
|
||||
isGasEstimatesLoading,
|
||||
isUsingDappSuggestedGasFees,
|
||||
estimateUsed,
|
||||
gasFeeEstimates,
|
||||
gasEstimateType,
|
||||
estimatedGasFeeTimeBounds,
|
||||
@ -276,5 +289,6 @@ export function useGasFeeInputs(
|
||||
gasWarnings,
|
||||
hasGasErrors,
|
||||
supportsEIP1559,
|
||||
updateTransaction,
|
||||
};
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ jest.mock('react-redux', () => {
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
useDispatch: () => jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
49
ui/hooks/gasFeeInput/useTransactionFunctions.js
Normal file
49
ui/hooks/gasFeeInput/useTransactionFunctions.js
Normal file
@ -0,0 +1,49 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import {
|
||||
decGWEIToHexWEI,
|
||||
decimalToHex,
|
||||
} from '../../helpers/utils/conversions.util';
|
||||
import { updateTransaction as updateTransactionFn } from '../../store/actions';
|
||||
|
||||
export const useTransactionFunctions = ({
|
||||
defaultEstimateToUse,
|
||||
gasLimit,
|
||||
gasFeeEstimates,
|
||||
transaction,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const updateTransaction = useCallback(
|
||||
(estimateType) => {
|
||||
const newGasSettings = {
|
||||
gas: decimalToHex(gasLimit),
|
||||
gasLimit: decimalToHex(gasLimit),
|
||||
estimateSuggested: defaultEstimateToUse,
|
||||
estimateUsed: estimateType,
|
||||
};
|
||||
|
||||
newGasSettings.maxFeePerGas = decGWEIToHexWEI(
|
||||
gasFeeEstimates[estimateType].suggestedMaxFeePerGas,
|
||||
);
|
||||
newGasSettings.maxPriorityFeePerGas = decGWEIToHexWEI(
|
||||
gasFeeEstimates[estimateType].suggestedMaxPriorityFeePerGas,
|
||||
);
|
||||
|
||||
const updatedTxMeta = {
|
||||
...transaction,
|
||||
userFeeLevel: estimateType || 'custom',
|
||||
txParams: {
|
||||
...transaction.txParams,
|
||||
...newGasSettings,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(updateTransactionFn(updatedTxMeta));
|
||||
},
|
||||
[defaultEstimateToUse, dispatch, gasLimit, gasFeeEstimates, transaction],
|
||||
);
|
||||
|
||||
return { updateTransaction };
|
||||
};
|
@ -31,7 +31,7 @@ const GasDetailsItem = ({
|
||||
useNativeCurrencyAsPrimaryCurrency,
|
||||
}) => {
|
||||
const t = useI18nContext();
|
||||
const { estimateToUse } = useGasFeeContext();
|
||||
const { estimateUsed } = useGasFeeContext();
|
||||
|
||||
return (
|
||||
<TransactionDetailItem
|
||||
@ -96,12 +96,12 @@ const GasDetailsItem = ({
|
||||
key="editGasSubTextFeeLabel"
|
||||
display="inline-flex"
|
||||
className={classNames('gas-details-item__gasfee-label', {
|
||||
'gas-details-item__gas-fee-warning': estimateToUse === 'high',
|
||||
'gas-details-item__gas-fee-warning': estimateUsed === 'high',
|
||||
})}
|
||||
>
|
||||
<Box marginRight={1}>
|
||||
<b>
|
||||
{estimateToUse === 'high' && '⚠ '}
|
||||
{estimateUsed === 'high' && '⚠ '}
|
||||
<I18nValue messageKey="editGasSubTextFeeLabel" />
|
||||
</b>
|
||||
</Box>
|
||||
|
@ -5,10 +5,10 @@ import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
const LowPriorityMessage = () => {
|
||||
const { estimateToUse } = useGasFeeContext();
|
||||
const { estimateUsed } = useGasFeeContext();
|
||||
const t = useI18nContext();
|
||||
|
||||
if (estimateToUse !== 'low') return null;
|
||||
if (estimateUsed !== 'low') return null;
|
||||
return (
|
||||
<div className="low-priority-message">
|
||||
<ActionableMessage
|
||||
|
Loading…
Reference in New Issue
Block a user