1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Implementing save button on advance gas fee modal (#12854)

This commit is contained in:
Jyoti Puri 2021-12-01 06:01:21 +05:30 committed by GitHub
parent 8e6a0ff879
commit a020281c96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 508 additions and 281 deletions

View File

@ -25,7 +25,7 @@ const AdvancedGasFeeInputSubtext = ({ latest, historical }) => {
};
AdvancedGasFeeInputSubtext.propTypes = {
latest: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
latest: PropTypes.string,
historical: PropTypes.string,
};

View File

@ -3,7 +3,7 @@
align-items: center;
margin-top: 2px;
color: $ui-4;
font-size: $font-size-h7;
font-size: $font-size-h8;
&__label {
font-weight: bold;

View File

@ -1,14 +1,14 @@
import React from 'react';
import Box from '../../../ui/box';
import BasefeeInput from './basefee-input';
import PriorityFeeInput from './priorityfee-input';
import BaseFeeInput from './base-fee-input';
import PriorityFeeInput from './priority-fee-input';
const AdvancedGasFeeInputs = () => {
return (
<Box className="advanced-gas-fee-input" margin={4}>
<BasefeeInput />
<div className="advanced-gas-fee-input__separator" />
<Box className="advanced-gas-fee-inputs" margin={4}>
<BaseFeeInput />
<div className="advanced-gas-fee-inputs__separator" />
<PriorityFeeInput />
</Box>
);

View File

@ -0,0 +1,155 @@
import React, { useCallback, useEffect, 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 { getAdvancedGasFeeValues } from '../../../../../selectors';
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 Box from '../../../../ui/box';
import FormField from '../../../../ui/form-field';
import I18nValue from '../../../../ui/i18n-value';
import { useAdvanceGasFeePopoverContext } from '../../context';
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
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 { setDirty, setMaxFeePerGas } = useAdvanceGasFeePopoverContext();
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) => {
let baseFeeInGWEI;
let baseFeeMultiplierValue;
if (editingInGwei) {
baseFeeInGWEI = value;
baseFeeMultiplierValue = divideCurrencyValues(value, estimatedBaseFee);
} else {
baseFeeInGWEI = multiplyCurrencyValues(
estimatedBaseFee,
value,
numberOfDecimalsPrimary,
);
baseFeeMultiplierValue = value;
}
setMaxBaseFeeGWEI(baseFeeInGWEI);
setMaxBaseFeeMultiplier(baseFeeMultiplierValue);
setDirty(true);
},
[
editingInGwei,
estimatedBaseFee,
numberOfDecimalsPrimary,
setMaxBaseFeeGWEI,
setMaxBaseFeeMultiplier,
setDirty,
],
);
useEffect(() => {
setMaxFeePerGas(maxBaseFeeGWEI);
}, [maxBaseFeeGWEI, setMaxFeePerGas]);
return (
<Box className="base-fee-input">
<FormField
onChange={updateBaseFee}
titleText={t('maxBaseFee')}
titleUnit={editingInGwei ? 'GWEI' : `(${t('multiplier')})`}
tooltipText={t('advancedBaseGasFeeToolTip')}
titleDetail={
<Button
className="base-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
/>
<AdvancedGasFeeInputSubtext
latest={estimatedBaseFee}
historical="23-359 GWEI"
/>
</Box>
);
};
export default BaseFeeInput;

View File

@ -1,21 +1,23 @@
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 { renderWithProvider } from '../../../../../../test/lib/render-helpers';
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
import mockState from '../../../../../../test/data/mock-state.json';
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
import configureStore from '../../../../../store/store';
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
import BasefeeInput from './basefee-input';
import { AdvanceGasFeePopoverContextProvider } from '../../context';
import BaseFeeInput from './base-fee-input';
jest.mock('../../../../store/actions', () => ({
jest.mock('../../../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
removePollingTokenFromAppState: jest.fn(),
}));
const render = (txProps) => {
@ -42,13 +44,15 @@ const render = (txProps) => {
...txProps,
}}
>
<BasefeeInput />
<AdvanceGasFeePopoverContextProvider>
<BaseFeeInput />
</AdvanceGasFeePopoverContextProvider>
</GasFeeContextProvider>,
store,
);
};
describe('BasefeeInput', () => {
describe('BaseFeeInput', () => {
it('should renders advancedGasFee.baseFee value if current estimate used is not custom', () => {
render({
userFeeLevel: 'high',

View File

@ -0,0 +1 @@
export { default } from './base-fee-input';

View File

@ -0,0 +1,8 @@
.base-fee-input {
a.base-fee-input__edit-link {
display: inline;
font-size: $font-size-h7;
padding: 0;
white-space: nowrap;
}
}

View File

@ -1,144 +0,0 @@
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;

View File

@ -1,20 +1,12 @@
.advanced-gas-fee-input {
a.advanced-gas-fee-input__edit-link {
display: inline;
font-size: $font-size-h7;
padding: 0;
white-space: nowrap;
.advanced-gas-fee-inputs {
.form-field {
margin-bottom: 4px;
}
.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;

View File

@ -0,0 +1 @@
export { default } from './priority-fee-input';

View File

@ -0,0 +1,67 @@
import React, { useEffect, 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 { useAdvanceGasFeePopoverContext } from '../../context';
import AdvancedGasFeeInputSubtext from '../../advanced-gas-fee-input-subtext';
const PriorityFeeInput = () => {
const t = useI18nContext();
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
const {
setDirty,
setMaxPriorityFeePerGas,
} = useAdvanceGasFeePopoverContext();
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 },
);
const updatePriorityFee = (value) => {
setPriorityFee(value);
setDirty(true);
};
useEffect(() => {
setMaxPriorityFeePerGas(priorityFee);
}, [priorityFee, setMaxPriorityFeePerGas]);
return (
<>
<FormField
onChange={updatePriorityFee}
titleText={t('priorityFee')}
titleUnit="(GWEI)"
tooltipText={t('advancedPriorityFeeToolTip')}
value={priorityFee}
detailText={`${priorityFeeInFiat}`}
numeric
/>
<AdvancedGasFeeInputSubtext latest="1-18 GWEI" historical="23-359 GWEI" />
</>
);
};
export default PriorityFeeInput;

View File

@ -1,20 +1,22 @@
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 { renderWithProvider } from '../../../../../../test/lib/render-helpers';
import mockEstimates from '../../../../../../test/data/mock-estimates.json';
import mockState from '../../../../../../test/data/mock-state.json';
import { GasFeeContextProvider } from '../../../../../contexts/gasFee';
import configureStore from '../../../../../store/store';
import { GAS_ESTIMATE_TYPES } from '../../../../../shared/constants/gas';
import PriprityfeeInput from './priorityfee-input';
import { AdvanceGasFeePopoverContextProvider } from '../../context';
import PriorityfeeInput from './priority-fee-input';
jest.mock('../../../../store/actions', () => ({
jest.mock('../../../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
removePollingTokenFromAppState: jest.fn(),
}));
const render = (txProps) => {
@ -41,7 +43,9 @@ const render = (txProps) => {
...txProps,
}}
>
<PriprityfeeInput />
<AdvanceGasFeePopoverContextProvider>
<PriorityfeeInput />
</AdvanceGasFeePopoverContextProvider>
</GasFeeContextProvider>,
store,
);

View File

@ -1,57 +0,0 @@
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;

View File

@ -2,13 +2,12 @@ import React from 'react';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import Box from '../../ui/box';
import Button from '../../ui/button';
import I18nValue from '../../ui/i18n-value';
import Popover from '../../ui/popover';
import { AdvanceGasFeePopoverContextProvider } from './context';
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
import AdvancedGasFeeSaveButton from './advanced-gas-fee-save';
const AdvancedGasFeePopover = () => {
const t = useI18nContext();
@ -21,21 +20,19 @@ const AdvancedGasFeePopover = () => {
if (currentModal !== 'advancedGasFee') return null;
return (
<Popover
className="advanced-gas-fee-popover"
title={t('advancedGasFeeModalTitle')}
onBack={() => closeModal('advancedGasFee')}
onClose={closeAllModals}
footer={
<Button type="primary">
<I18nValue messageKey="save" />
</Button>
}
>
<Box className="advanced-gas-fee-popover__wrapper">
<AdvancedGasFeeInputs />
</Box>
</Popover>
<AdvanceGasFeePopoverContextProvider>
<Popover
className="advanced-gas-fee-popover"
title={t('advancedGasFeeModalTitle')}
onBack={() => closeModal('advancedGasFee')}
onClose={closeAllModals}
footer={<AdvancedGasFeeSaveButton />}
>
<Box className="advanced-gas-fee-popover__wrapper">
<AdvancedGasFeeInputs />
</Box>
</Popover>
</AdvanceGasFeePopoverContextProvider>
);
};

View File

@ -0,0 +1,71 @@
import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import mockEstimates from '../../../../test/data/mock-estimates.json';
import mockState from '../../../../test/data/mock-state.json';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import configureStore from '../../../store/store';
import AdvancedGasFeePopover from './advanced-gas-fee-popover';
jest.mock('../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
removePollingTokenFromAppState: jest.fn(),
}));
jest.mock('../../../contexts/transaction-modal', () => ({
useTransactionModalContext: () => ({
closeModal: () => undefined,
currentModal: 'advancedGasFee',
}),
}));
const render = () => {
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',
}}
>
<AdvancedGasFeePopover />
</GasFeeContextProvider>,
store,
);
};
describe('AdvancedGasFeePopover', () => {
it('should renders save button disabled by default', () => {
render();
expect(screen.queryByRole('button', { name: 'Save' })).toBeDisabled();
});
it('should enable save button as input value is changed', () => {
render();
fireEvent.change(document.getElementsByTagName('input')[0], {
target: { value: 4 },
});
expect(screen.queryByRole('button', { name: 'Save' })).not.toBeDisabled();
});
});

View File

@ -0,0 +1,37 @@
import React from 'react';
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas';
import { useTransactionModalContext } from '../../../../contexts/transaction-modal';
import { useGasFeeContext } from '../../../../contexts/gasFee';
import Button from '../../../ui/button';
import I18nValue from '../../../ui/i18n-value';
import { useAdvanceGasFeePopoverContext } from '../context';
import { decGWEIToHexWEI } from '../../../../../shared/modules/conversion.utils';
const AdvancedGasFeeSaveButton = () => {
const { closeModal } = useTransactionModalContext();
const { updateTransaction } = useGasFeeContext();
const {
isDirty,
maxFeePerGas,
maxPriorityFeePerGas,
} = useAdvanceGasFeePopoverContext();
const onSave = () => {
updateTransaction(
PRIORITY_LEVELS.CUSTOM,
decGWEIToHexWEI(maxFeePerGas),
decGWEIToHexWEI(maxPriorityFeePerGas),
);
closeModal('advancedGasFee');
};
return (
<Button type="primary" disabled={!isDirty} onClick={onSave}>
<I18nValue messageKey="save" />
</Button>
);
};
export default AdvancedGasFeeSaveButton;

View File

@ -0,0 +1 @@
export { default } from './advanced-gas-fee-save';

View File

@ -0,0 +1,33 @@
import React, { createContext, useContext, useState } from 'react';
import PropTypes from 'prop-types';
export const AdvanceGasFeePopoverContext = createContext({});
export const AdvanceGasFeePopoverContextProvider = ({ children }) => {
const [maxFeePerGas, setMaxFeePerGas] = useState();
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState();
const [isDirty, setDirty] = useState();
return (
<AdvanceGasFeePopoverContext.Provider
value={{
isDirty,
maxFeePerGas,
maxPriorityFeePerGas,
setDirty,
setMaxPriorityFeePerGas,
setMaxFeePerGas,
}}
>
{children}
</AdvanceGasFeePopoverContext.Provider>
);
};
export function useAdvanceGasFeePopoverContext() {
return useContext(AdvanceGasFeePopoverContext);
}
AdvanceGasFeePopoverContextProvider.propTypes = {
children: PropTypes.node.isRequired,
};

View File

@ -0,0 +1 @@
export * from './advanceGasFeePopover';

View File

@ -53,4 +53,5 @@
@import 'loading-network-screen/index';
@import 'advanced-gas-fee-popover/index';
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/index';
@import 'advanced-gas-fee-popover/advanced-gas-fee-inputs/base-fee-input/index';
@import 'advanced-gas-fee-popover/advanced-gas-fee-input-subtext/index';

View File

@ -11,7 +11,7 @@
height: 32px;
width: 100%;
&--selected {
&-selected {
background-color: $ui-1;
}

View File

@ -44,6 +44,7 @@ const render = ({ componentProps, contextProps } = {}) => {
console.log('on edit');
}}
rows={[]}
userAcknowledgedGasMissing
{...componentProps}
/>
</GasFeeContextProvider>,

View File

@ -30,7 +30,6 @@ export default function FormField({
password,
allowDecimals,
disabled,
inputDetails,
}) {
return (
<div
@ -108,7 +107,6 @@ export default function FormField({
{error}
</Typography>
)}
{inputDetails}
</label>
</div>
);
@ -129,7 +127,6 @@ FormField.propTypes = {
password: PropTypes.bool,
allowDecimals: PropTypes.bool,
disabled: PropTypes.bool,
inputDetails: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
};
FormField.defaultProps = {
@ -146,5 +143,4 @@ FormField.defaultProps = {
password: false,
allowDecimals: true,
disabled: false,
inputDetails: '',
};

View File

@ -4,7 +4,8 @@
.typography {
@include design-system.Paragraph;
& b {
& b,
& strong {
font-weight: 700;
}

View File

@ -23,6 +23,9 @@ import { useMaxPriorityFeePerGasInput } from './useMaxPriorityFeePerGasInput';
import { useGasEstimates } from './useGasEstimates';
import { useTransactionFunctions } from './useTransactionFunctions';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2 = process.env.EIP_1559_V2;
/**
* @typedef {Object} GasFeeInputReturnType
* @property {DecGweiString} [maxFeePerGas] - the maxFeePerGas input value.
@ -118,10 +121,16 @@ export function useGasFeeInputs(
* so that transaction is source of truth whenever possible.
*/
useEffect(() => {
if (transaction?.userFeeLevel) {
if (EIP_1559_V2 && transaction?.userFeeLevel) {
setEstimateUsed(transaction?.userFeeLevel);
setInternalEstimateToUse(transaction?.userFeeLevel);
}
}, [setEstimateUsed, transaction]);
}, [
setEstimateUsed,
setInternalEstimateToUse,
transaction,
userPrefersAdvancedGas,
]);
const [gasLimit, setGasLimit] = useState(() =>
Number(hexToDecimal(transaction?.txParams?.gas ?? '0x0')),
@ -143,6 +152,7 @@ export function useGasFeeInputs(
maxFeePerGasFiat,
setMaxFeePerGas,
} = useMaxFeePerGasInput({
EIP_1559_V2,
estimateToUse,
gasEstimateType,
gasFeeEstimates,
@ -156,6 +166,7 @@ export function useGasFeeInputs(
maxPriorityFeePerGasFiat,
setMaxPriorityFeePerGas,
} = useMaxPriorityFeePerGasInput({
EIP_1559_V2,
estimateToUse,
gasEstimateType,
gasFeeEstimates,
@ -214,8 +225,12 @@ export function useGasFeeInputs(
}
}, [minimumGasLimit, gasErrors.gasLimit, transaction]);
const { updateTransactionUsingGasFeeEstimates } = useTransactionFunctions({
const {
updateTransaction,
updateTransactionUsingGasFeeEstimates,
} = useTransactionFunctions({
defaultEstimateToUse,
gasFeeEstimates,
gasLimit,
transaction,
});
@ -302,6 +317,22 @@ export function useGasFeeInputs(
hasSimulationError,
supportsEIP1559,
supportsEIP1559V2: supportsEIP1559 && EIP_1559_V2_ENABLED,
updateTransaction,
updateTransactionUsingGasFeeEstimates,
};
}
/**
* In EIP_1559_V2 implementation as used by useGasfeeInputContext() the use of this hook is evolved.
* It is no longer used to keep transient state of advance gas fee inputs.
* Transient state of inputs is maintained locally in /ui/components/app/advance-gas-fee-popover component.
*
* This hook is used now as source of shared data about transaction, it shares details of gas fee in transaction,
* estimate used, is EIP-1559 supported and other details. It also have methods to update transaction.
*
* Transaction is used as single source of truth and as transaction is updated the fields shared by hook are
* also updated using useEffect hook.
*
* It will be useful to plan a task to create a new hook of this shared information from this hook.
* Methods like setEstimateToUse, onManualChange are deprecated in context of EIP_1559_V2 implementation.
*/

View File

@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
@ -34,6 +34,7 @@ const getMaxFeePerGasFromTransaction = (transaction) => {
* method to update the setMaxFeePerGas.
*/
export function useMaxFeePerGasInput({
EIP_1559_V2,
estimateToUse,
gasEstimateType,
gasFeeEstimates,
@ -65,6 +66,12 @@ export function useMaxFeePerGasInput({
return null;
});
useEffect(() => {
if (EIP_1559_V2) {
setMaxFeePerGas(maxFeePerGasFromTransaction);
}
}, [EIP_1559_V2, maxFeePerGasFromTransaction, setMaxFeePerGas]);
let gasSettings = {
gasLimit: decimalToHex(gasLimit),
};

View File

@ -1,5 +1,5 @@
import { useSelector } from 'react-redux';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { addHexPrefix } from 'ethereumjs-util';
@ -34,6 +34,7 @@ const getMaxPriorityFeePerGasFromTransaction = (transaction) => {
* method to update the maxPriorityFeePerGas.
*/
export function useMaxPriorityFeePerGasInput({
EIP_1559_V2,
estimateToUse,
gasEstimateType,
gasFeeEstimates,
@ -61,6 +62,16 @@ export function useMaxPriorityFeePerGasInput({
return null;
});
useEffect(() => {
if (EIP_1559_V2) {
setMaxPriorityFeePerGas(maxPriorityFeePerGasFromTransaction);
}
}, [
EIP_1559_V2,
maxPriorityFeePerGasFromTransaction,
setMaxPriorityFeePerGas,
]);
const maxPriorityFeePerGasToUse =
maxPriorityFeePerGas ??
getGasFeeEstimate(

View File

@ -2,11 +2,15 @@ import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { PRIORITY_LEVELS } from '../../../shared/constants/gas';
import { decimalToHex } from '../../helpers/utils/conversions.util';
import {
decimalToHex,
decGWEIToHexWEI,
} from '../../helpers/utils/conversions.util';
import { updateTransaction as updateTransactionFn } from '../../store/actions';
export const useTransactionFunctions = ({
defaultEstimateToUse,
gasFeeEstimates,
gasLimit,
transaction,
}) => {
@ -54,11 +58,19 @@ export const useTransactionFunctions = ({
maxPriorityFeePerGas,
);
} else {
updateTransaction(gasFeeEstimateToUse);
const {
suggestedMaxFeePerGas,
suggestedMaxPriorityFeePerGas,
} = gasFeeEstimates[gasFeeEstimateToUse];
updateTransaction(
gasFeeEstimateToUse,
decGWEIToHexWEI(suggestedMaxFeePerGas),
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
);
}
},
[transaction?.dappSuggestedGasFees, updateTransaction],
[gasFeeEstimates, transaction?.dappSuggestedGasFees, updateTransaction],
);
return { updateTransactionUsingGasFeeEstimates };
return { updateTransaction, updateTransactionUsingGasFeeEstimates };
};

View File

@ -516,9 +516,9 @@ export default class ConfirmTransactionBase extends Component {
subText={
!isMultiLayerFeeNetwork && (
<>
<b key="editGasSubTextFeeLabel">
<strong key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</b>
</strong>
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
@ -611,9 +611,9 @@ export default class ConfirmTransactionBase extends Component {
subTitle={t('transactionDetailGasTotalSubtitle')}
subText={
<>
<b key="editGasSubTextAmountLabel">
<strong key="editGasSubTextAmountLabel">
{t('editGasSubTextAmountLabel')}
</b>
</strong>
{renderTotalMaxAmount()}
</>
}

View File

@ -102,10 +102,10 @@ const GasDetailsItem = ({
})}
>
<Box marginRight={1}>
<b>
<strong>
{estimateUsed === 'high' && '⚠ '}
<I18nValue messageKey="editGasSubTextFeeLabel" />
</b>
</strong>
</Box>
<div
key="editGasSubTextFeeValue"

View File

@ -11,10 +11,6 @@
color: $secondary-1;
}
&__gasfee-label {
font-weight: bold;
}
&__currency-container {
position: relative;
}