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 { |
||||
.popover-header { |
||||
border-radius: 0; |
||||
border-top-left-radius: 10px; |
||||
border-top-right-radius: 10px; |
||||
border-bottom: 1px solid $Grey-200; |
||||
&__wrapper { |
||||
border-top: 1px solid $ui-grey; |
||||
} |
||||
|
||||
&__separator { |
||||
border-top: 1px solid $ui-grey; |
||||
margin: 24px 0 16px 0; |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue