EIP-1559 V2 : Advanced gas fee modal - Max base fee and Priority fee inputs (#12619)
parent
c2ea04c775
commit
4b975adc85
After Width: | Height: | Size: 347 B |
After Width: | Height: | Size: 358 B |
@ -0,0 +1,32 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import PropTypes from 'prop-types'; |
||||||
|
|
||||||
|
import Box from '../../../ui/box'; |
||||||
|
import I18nValue from '../../../ui/i18n-value'; |
||||||
|
|
||||||
|
const AdvancedGasFeeInputSubtext = ({ latest, historical }) => { |
||||||
|
return ( |
||||||
|
<Box className="advanced-gas-fee-input-subtext"> |
||||||
|
<Box display="flex" alignItems="center"> |
||||||
|
<span className="advanced-gas-fee-input-subtext__label"> |
||||||
|
<I18nValue messageKey="currentTitle" /> |
||||||
|
</span> |
||||||
|
<span>{latest}</span> |
||||||
|
<img src="./images/high-arrow.svg" alt="" /> |
||||||
|
</Box> |
||||||
|
<Box> |
||||||
|
<span className="advanced-gas-fee-input-subtext__label"> |
||||||
|
<I18nValue messageKey="twelveHrTitle" /> |
||||||
|
</span> |
||||||
|
<span>{historical}</span> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
AdvancedGasFeeInputSubtext.propTypes = { |
||||||
|
latest: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||||
|
historical: PropTypes.string, |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdvancedGasFeeInputSubtext; |
@ -0,0 +1,18 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { render, screen } from '@testing-library/react'; |
||||||
|
|
||||||
|
import AdvancedGasFeeInputSubtext from './advanced-gas-fee-input-subtext'; |
||||||
|
|
||||||
|
describe('AdvancedGasFeeInputSubtext', () => { |
||||||
|
it('should renders latest and historical values passed', () => { |
||||||
|
render( |
||||||
|
<AdvancedGasFeeInputSubtext |
||||||
|
latest="Latest Value" |
||||||
|
historical="Historical value" |
||||||
|
/>, |
||||||
|
); |
||||||
|
|
||||||
|
expect(screen.queryByText('Latest Value')).toBeInTheDocument(); |
||||||
|
expect(screen.queryByText('Historical value')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './advanced-gas-fee-input-subtext'; |
@ -0,0 +1,17 @@ |
|||||||
|
.advanced-gas-fee-input-subtext { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
margin-top: 2px; |
||||||
|
color: $ui-4; |
||||||
|
font-size: $font-size-h7; |
||||||
|
|
||||||
|
&__label { |
||||||
|
font-weight: bold; |
||||||
|
margin-right: 4px; |
||||||
|
} |
||||||
|
|
||||||
|
img { |
||||||
|
height: 16px; |
||||||
|
margin-right: 8px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import Box from '../../../ui/box'; |
||||||
|
import BasefeeInput from './basefee-input'; |
||||||
|
import PriorityFeeInput from './priorityfee-input'; |
||||||
|
|
||||||
|
const AdvancedGasFeeInputs = () => { |
||||||
|
return ( |
||||||
|
<Box className="advanced-gas-fee-input" margin={4}> |
||||||
|
<BasefeeInput /> |
||||||
|
<div className="advanced-gas-fee-input__separator" /> |
||||||
|
<PriorityFeeInput /> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AdvancedGasFeeInputs; |
@ -0,0 +1,144 @@ |
|||||||
|
import React, { useCallback, useState } from 'react'; |
||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
|
||||||
|
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas'; |
||||||
|
import { |
||||||
|
divideCurrencies, |
||||||
|
multiplyCurrencies, |
||||||
|
} from '../../../../../shared/modules/conversion.utils'; |
||||||
|
import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'; |
||||||
|
import { decGWEIToHexWEI } from '../../../../helpers/utils/conversions.util'; |
||||||
|
import { useGasFeeContext } from '../../../../contexts/gasFee'; |
||||||
|
import { useI18nContext } from '../../../../hooks/useI18nContext'; |
||||||
|
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency'; |
||||||
|
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay'; |
||||||
|
import Button from '../../../ui/button'; |
||||||
|
import FormField from '../../../ui/form-field'; |
||||||
|
import I18nValue from '../../../ui/i18n-value'; |
||||||
|
|
||||||
|
import AdvancedGasFeeInputSubtext from '../advanced-gas-fee-input-subtext'; |
||||||
|
import { getAdvancedGasFeeValues } from '../../../../selectors'; |
||||||
|
|
||||||
|
const divideCurrencyValues = (value, baseFee) => { |
||||||
|
if (baseFee === 0) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return divideCurrencies(value, baseFee, { |
||||||
|
numberOfDecimals: 2, |
||||||
|
dividendBase: 10, |
||||||
|
divisorBase: 10, |
||||||
|
}).toNumber(); |
||||||
|
}; |
||||||
|
|
||||||
|
const multiplyCurrencyValues = (baseFee, value, numberOfDecimals) => |
||||||
|
multiplyCurrencies(baseFee, value, { |
||||||
|
numberOfDecimals, |
||||||
|
multiplicandBase: 10, |
||||||
|
multiplierBase: 10, |
||||||
|
}).toNumber(); |
||||||
|
|
||||||
|
const BasefeeInput = () => { |
||||||
|
const t = useI18nContext(); |
||||||
|
const { gasFeeEstimates, estimateUsed, maxFeePerGas } = useGasFeeContext(); |
||||||
|
const { estimatedBaseFee } = gasFeeEstimates; |
||||||
|
const { |
||||||
|
numberOfDecimals: numberOfDecimalsPrimary, |
||||||
|
} = useUserPreferencedCurrency(PRIMARY); |
||||||
|
const { |
||||||
|
currency, |
||||||
|
numberOfDecimals: numberOfDecimalsFiat, |
||||||
|
} = useUserPreferencedCurrency(SECONDARY); |
||||||
|
|
||||||
|
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); |
||||||
|
|
||||||
|
const [editingInGwei, setEditingInGwei] = useState(false); |
||||||
|
|
||||||
|
const [maxBaseFeeGWEI, setMaxBaseFeeGWEI] = useState(() => { |
||||||
|
if ( |
||||||
|
estimateUsed !== PRIORITY_LEVELS.CUSTOM && |
||||||
|
advancedGasFeeValues?.maxBaseFee |
||||||
|
) { |
||||||
|
return multiplyCurrencyValues( |
||||||
|
estimatedBaseFee, |
||||||
|
advancedGasFeeValues.maxBaseFee, |
||||||
|
numberOfDecimalsPrimary, |
||||||
|
); |
||||||
|
} |
||||||
|
return maxFeePerGas; |
||||||
|
}); |
||||||
|
|
||||||
|
const [maxBaseFeeMultiplier, setMaxBaseFeeMultiplier] = useState(() => { |
||||||
|
if ( |
||||||
|
estimateUsed !== PRIORITY_LEVELS.CUSTOM && |
||||||
|
advancedGasFeeValues?.maxBaseFee |
||||||
|
) { |
||||||
|
return advancedGasFeeValues.maxBaseFee; |
||||||
|
} |
||||||
|
return divideCurrencyValues(maxFeePerGas, estimatedBaseFee); |
||||||
|
}); |
||||||
|
|
||||||
|
const [, { value: baseFeeInFiat }] = useCurrencyDisplay( |
||||||
|
decGWEIToHexWEI(maxBaseFeeGWEI), |
||||||
|
{ currency, numberOfDecimalsFiat }, |
||||||
|
); |
||||||
|
|
||||||
|
const updateBaseFee = useCallback( |
||||||
|
(value) => { |
||||||
|
if (editingInGwei) { |
||||||
|
setMaxBaseFeeGWEI(value); |
||||||
|
setMaxBaseFeeMultiplier(divideCurrencyValues(value, estimatedBaseFee)); |
||||||
|
} else { |
||||||
|
setMaxBaseFeeMultiplier(value); |
||||||
|
setMaxBaseFeeGWEI( |
||||||
|
multiplyCurrencyValues( |
||||||
|
estimatedBaseFee, |
||||||
|
value, |
||||||
|
numberOfDecimalsPrimary, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
[ |
||||||
|
editingInGwei, |
||||||
|
estimatedBaseFee, |
||||||
|
numberOfDecimalsPrimary, |
||||||
|
setMaxBaseFeeGWEI, |
||||||
|
setMaxBaseFeeMultiplier, |
||||||
|
], |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<FormField |
||||||
|
onChange={updateBaseFee} |
||||||
|
titleText={t('maxBaseFee')} |
||||||
|
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`} |
||||||
|
tooltipText={t('advancedBaseGasFeeToolTip')} |
||||||
|
titleDetail={ |
||||||
|
<Button |
||||||
|
className="advanced-gas-fee-input__edit-link" |
||||||
|
type="link" |
||||||
|
onClick={() => setEditingInGwei(!editingInGwei)} |
||||||
|
> |
||||||
|
<I18nValue |
||||||
|
messageKey={editingInGwei ? 'editInMultiplier' : 'editInGwei'} |
||||||
|
/> |
||||||
|
</Button> |
||||||
|
} |
||||||
|
value={editingInGwei ? maxBaseFeeGWEI : maxBaseFeeMultiplier} |
||||||
|
detailText={ |
||||||
|
editingInGwei |
||||||
|
? `${maxBaseFeeMultiplier}x ${`≈ ${baseFeeInFiat}`}` |
||||||
|
: `${maxBaseFeeGWEI} GWEI ${`≈ ${baseFeeInFiat}`}` |
||||||
|
} |
||||||
|
numeric |
||||||
|
inputDetails={ |
||||||
|
<AdvancedGasFeeInputSubtext |
||||||
|
latest={estimatedBaseFee} |
||||||
|
historical="23-359 GWEI" |
||||||
|
/> |
||||||
|
} |
||||||
|
/> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default BasefeeInput; |
@ -0,0 +1,114 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { fireEvent, screen } from '@testing-library/react'; |
||||||
|
|
||||||
|
import mockEstimates from '../../../../../test/data/mock-estimates.json'; |
||||||
|
import mockState from '../../../../../test/data/mock-state.json'; |
||||||
|
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||||
|
import configureStore from '../../../../store/store'; |
||||||
|
import { GasFeeContextProvider } from '../../../../contexts/gasFee'; |
||||||
|
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas'; |
||||||
|
import BasefeeInput from './basefee-input'; |
||||||
|
|
||||||
|
jest.mock('../../../../store/actions', () => ({ |
||||||
|
disconnectGasFeeEstimatePoller: jest.fn(), |
||||||
|
getGasFeeEstimatesAndStartPolling: jest |
||||||
|
.fn() |
||||||
|
.mockImplementation(() => Promise.resolve()), |
||||||
|
addPollingTokenToAppState: jest.fn(), |
||||||
|
})); |
||||||
|
|
||||||
|
const render = (txProps) => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
accounts: { |
||||||
|
[mockState.metamask.selectedAddress]: { |
||||||
|
address: mockState.metamask.selectedAddress, |
||||||
|
balance: '0x1F4', |
||||||
|
}, |
||||||
|
}, |
||||||
|
advancedGasFee: { maxBaseFee: 2 }, |
||||||
|
featureFlags: { advancedInlineGas: true }, |
||||||
|
gasFeeEstimates: |
||||||
|
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
return renderWithProvider( |
||||||
|
<GasFeeContextProvider |
||||||
|
transaction={{ |
||||||
|
userFeeLevel: 'custom', |
||||||
|
...txProps, |
||||||
|
}} |
||||||
|
> |
||||||
|
<BasefeeInput /> |
||||||
|
</GasFeeContextProvider>, |
||||||
|
store, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('BasefeeInput', () => { |
||||||
|
it('should renders advancedGasFee.baseFee value if current estimate used is not custom', () => { |
||||||
|
render({ |
||||||
|
userFeeLevel: 'high', |
||||||
|
}); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(2); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should renders baseFee values from transaction if current estimate used is custom', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x174876E800', |
||||||
|
}, |
||||||
|
}); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(2); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should show GWEI value in input when Edit in GWEI link is clicked', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x174876E800', |
||||||
|
}, |
||||||
|
}); |
||||||
|
fireEvent.click(screen.queryByText('Edit in GWEI')); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(100); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should correctly update GWEI value if multiplier is changed', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x174876E800', |
||||||
|
}, |
||||||
|
}); |
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], { |
||||||
|
target: { value: 4 }, |
||||||
|
}); |
||||||
|
fireEvent.click(screen.queryByText('Edit in GWEI')); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(200); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should correctly update multiplier value if GWEI is changed', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x174876E800', |
||||||
|
}, |
||||||
|
}); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(2); |
||||||
|
fireEvent.click(screen.queryByText('Edit in GWEI')); |
||||||
|
fireEvent.change(document.getElementsByTagName('input')[0], { |
||||||
|
target: { value: 200 }, |
||||||
|
}); |
||||||
|
fireEvent.click(screen.queryByText('Edit in multiplier')); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(4); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should show current value of estimatedBaseFee in subtext', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxFeePerGas: '0x174876E800', |
||||||
|
}, |
||||||
|
}); |
||||||
|
expect(screen.queryByText('50')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './advanced-gas-fee-inputs'; |
@ -0,0 +1,22 @@ |
|||||||
|
.advanced-gas-fee-input { |
||||||
|
a.advanced-gas-fee-input__edit-link { |
||||||
|
display: inline; |
||||||
|
font-size: $font-size-h7; |
||||||
|
padding: 0; |
||||||
|
white-space: nowrap; |
||||||
|
} |
||||||
|
|
||||||
|
.form-field__heading-title > h6 { |
||||||
|
font-size: $font-size-h7; |
||||||
|
} |
||||||
|
|
||||||
|
&__border { |
||||||
|
padding-bottom: 16px; |
||||||
|
border-bottom: 1px solid $Grey-100; |
||||||
|
} |
||||||
|
|
||||||
|
&__separator { |
||||||
|
border-top: 1px solid $ui-grey; |
||||||
|
margin: 24px 0 16px 0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
import React, { useState } from 'react'; |
||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
|
||||||
|
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas'; |
||||||
|
import { SECONDARY } from '../../../../helpers/constants/common'; |
||||||
|
import { decGWEIToHexWEI } from '../../../../helpers/utils/conversions.util'; |
||||||
|
import { getAdvancedGasFeeValues } from '../../../../selectors'; |
||||||
|
import { useCurrencyDisplay } from '../../../../hooks/useCurrencyDisplay'; |
||||||
|
import { useGasFeeContext } from '../../../../contexts/gasFee'; |
||||||
|
import { useI18nContext } from '../../../../hooks/useI18nContext'; |
||||||
|
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency'; |
||||||
|
import FormField from '../../../ui/form-field'; |
||||||
|
|
||||||
|
import AdvancedGasFeeInputSubtext from '../advanced-gas-fee-input-subtext'; |
||||||
|
|
||||||
|
const PriorityFeeInput = () => { |
||||||
|
const t = useI18nContext(); |
||||||
|
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); |
||||||
|
|
||||||
|
const { estimateUsed, maxPriorityFeePerGas } = useGasFeeContext(); |
||||||
|
|
||||||
|
const [priorityFee, setPriorityFee] = useState(() => { |
||||||
|
if ( |
||||||
|
estimateUsed !== PRIORITY_LEVELS.CUSTOM && |
||||||
|
advancedGasFeeValues?.priorityFee |
||||||
|
) |
||||||
|
return advancedGasFeeValues.priorityFee; |
||||||
|
return maxPriorityFeePerGas; |
||||||
|
}); |
||||||
|
|
||||||
|
const { currency, numberOfDecimals } = useUserPreferencedCurrency(SECONDARY); |
||||||
|
|
||||||
|
const [, { value: priorityFeeInFiat }] = useCurrencyDisplay( |
||||||
|
decGWEIToHexWEI(priorityFee), |
||||||
|
{ currency, numberOfDecimals }, |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<FormField |
||||||
|
onChange={setPriorityFee} |
||||||
|
titleText={t('priorityFee')} |
||||||
|
titleUnit="(GWEI)" |
||||||
|
tooltipText={t('advancedPriorityFeeToolTip')} |
||||||
|
value={priorityFee} |
||||||
|
detailText={`≈ ${priorityFeeInFiat}`} |
||||||
|
numeric |
||||||
|
inputDetails={ |
||||||
|
<AdvancedGasFeeInputSubtext |
||||||
|
latest="1-18 GWEI" |
||||||
|
historical="23-359 GWEI" |
||||||
|
/> |
||||||
|
} |
||||||
|
/> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default PriorityFeeInput; |
@ -0,0 +1,66 @@ |
|||||||
|
import React from 'react'; |
||||||
|
|
||||||
|
import mockEstimates from '../../../../../test/data/mock-estimates.json'; |
||||||
|
import mockState from '../../../../../test/data/mock-state.json'; |
||||||
|
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; |
||||||
|
import configureStore from '../../../../store/store'; |
||||||
|
import { GasFeeContextProvider } from '../../../../contexts/gasFee'; |
||||||
|
|
||||||
|
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas'; |
||||||
|
import PriprityfeeInput from './priorityfee-input'; |
||||||
|
|
||||||
|
jest.mock('../../../../store/actions', () => ({ |
||||||
|
disconnectGasFeeEstimatePoller: jest.fn(), |
||||||
|
getGasFeeEstimatesAndStartPolling: jest |
||||||
|
.fn() |
||||||
|
.mockImplementation(() => Promise.resolve()), |
||||||
|
addPollingTokenToAppState: jest.fn(), |
||||||
|
})); |
||||||
|
|
||||||
|
const render = (txProps) => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
accounts: { |
||||||
|
[mockState.metamask.selectedAddress]: { |
||||||
|
address: mockState.metamask.selectedAddress, |
||||||
|
balance: '0x1F4', |
||||||
|
}, |
||||||
|
}, |
||||||
|
advancedGasFee: { priorityFee: 100 }, |
||||||
|
featureFlags: { advancedInlineGas: true }, |
||||||
|
gasFeeEstimates: |
||||||
|
mockEstimates[GAS_ESTIMATE_TYPES.FEE_MARKET].gasFeeEstimates, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
return renderWithProvider( |
||||||
|
<GasFeeContextProvider |
||||||
|
transaction={{ |
||||||
|
userFeeLevel: 'custom', |
||||||
|
...txProps, |
||||||
|
}} |
||||||
|
> |
||||||
|
<PriprityfeeInput /> |
||||||
|
</GasFeeContextProvider>, |
||||||
|
store, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('PriorityfeeInput', () => { |
||||||
|
it('should renders advancedGasFee.priorityfee value if current estimate used is not custom', () => { |
||||||
|
render({ |
||||||
|
userFeeLevel: 'high', |
||||||
|
}); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(100); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should renders priorityfee value from transaction if current estimate used is custom', () => { |
||||||
|
render({ |
||||||
|
txParams: { |
||||||
|
maxPriorityFeePerGas: '0x77359400', |
||||||
|
}, |
||||||
|
}); |
||||||
|
expect(document.getElementsByTagName('input')[0]).toHaveValue(2); |
||||||
|
}); |
||||||
|
}); |
@ -1,8 +1,10 @@ |
|||||||
.advanced-gas-fee-popover { |
.advanced-gas-fee-popover { |
||||||
.popover-header { |
&__wrapper { |
||||||
border-radius: 0; |
border-top: 1px solid $ui-grey; |
||||||
border-top-left-radius: 10px; |
} |
||||||
border-top-right-radius: 10px; |
|
||||||
border-bottom: 1px solid $Grey-200; |
&__separator { |
||||||
|
border-top: 1px solid $ui-grey; |
||||||
|
margin: 24px 0 16px 0; |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue