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

EIP-1559 V2 : Advanced gas fee modal - Max base fee and Priority fee inputs (#12619)

This commit is contained in:
Niranjana Binoy 2021-11-29 12:40:48 -05:00 committed by GitHub
parent c2ea04c775
commit 4b975adc85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 689 additions and 103 deletions

View File

@ -166,6 +166,9 @@
"advanced": {
"message": "Advanced"
},
"advancedBaseGasFeeToolTip": {
"message": "Any difference between your max base fee and the current base fee will be refunded after completion."
},
"advancedGasFeeModalTitle": {
"message": "Advanced gas fee"
},
@ -175,6 +178,9 @@
"advancedOptions": {
"message": "Advanced Options"
},
"advancedPriorityFeeToolTip": {
"message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction."
},
"advancedSettingsDescription": {
"message": "Access developer features, download State Logs, Reset Account, setup test networks and custom RPC"
},
@ -623,6 +629,9 @@
"currentLanguage": {
"message": "Current Language"
},
"currentTitle": {
"message": "Current:"
},
"currentlyUnavailable": {
"message": "Unavailable on this network"
},
@ -827,18 +836,10 @@
"editGasPriceTooltip": {
"message": "This network requires a “Gas price” field when submitting a transaction. Gas price is the amount you will pay pay per unit of gas."
},
"editGasSubTextAmount": {
"message": "$1 $2",
"description": "$1 will be passed the editGasSubTextAmountLabel and $2 will be passed the amount in either cryptocurrency or fiat"
},
"editGasSubTextAmountLabel": {
"message": "Max amount:",
"description": "This is meant to be used as the $1 substitution editGasSubTextAmount"
},
"editGasSubTextFee": {
"message": "$1 $2",
"description": "$1 will be passed the editGasSubTextFeeLabel and $2 will be passed the fee amount in either cryptocurrency or fiat"
},
"editGasSubTextFeeLabel": {
"message": "Max fee:",
"description": "$1 represents a dollar amount"
@ -855,6 +856,12 @@
"editGasTooLowWarningTooltip": {
"message": "This lowers your maximum fee but if network traffic increases your transaction may be delayed or fail."
},
"editInGwei": {
"message": "Edit in GWEI"
},
"editInMultiplier": {
"message": "Edit in multiplier"
},
"editNonceField": {
"message": "Edit Nonce"
},
@ -1593,6 +1600,9 @@
"mobileSyncWarning": {
"message": "The 'Sync with extension' feature is temporarily disabled. If you want to use your extension wallet on MetaMask mobile, then on your mobile app: go back to the wallet setup options and select the 'Import with Secret Recovery Phrase' option. Use your extension wallet's secret phrase to then import your wallet into mobile."
},
"multiplier": {
"message": "multiplier"
},
"mustSelectOne": {
"message": "Must select at least 1 token."
},
@ -2021,6 +2031,9 @@
"primaryCurrencySettingDescription": {
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
},
"priorityFee": {
"message": "Priority Fee"
},
"privacyMsg": {
"message": "Privacy Policy"
},
@ -3099,6 +3112,9 @@
"turnOnTokenDetection": {
"message": "Turn on enhanced token detection"
},
"twelveHrTitle": {
"message": "12hr:"
},
"typePassword": {
"message": "Type your MetaMask password"
},

View File

@ -0,0 +1 @@
<svg width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.022 4.82c0 .275.22.469.483.482l3.26-.082L3.8 9.183a.451.451 0 0 0 0 .663l.442.442c.18.18.47.193.663 0l3.963-3.964-.082 3.232a.49.49 0 0 0 .47.497h.607a.484.484 0 0 0 .47-.47V4.199a.46.46 0 0 0-.456-.456H4.49a.484.484 0 0 0-.47.47v.607Z" fill="#219E37"/></svg>

After

Width:  |  Height:  |  Size: 347 B

1
app/images/low-arrow.svg Normal file
View File

@ -0,0 +1 @@
<svg width="13" height="13" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.296 8.42c0-.276-.22-.47-.483-.483l-3.26.083 3.964-3.964a.451.451 0 0 0 0-.663l-.442-.442a.463.463 0 0 0-.662 0L4.449 6.915l.083-3.232a.49.49 0 0 0-.47-.497h-.607a.484.484 0 0 0-.47.47v5.386a.46.46 0 0 0 .456.456h5.386a.484.484 0 0 0 .47-.47V8.42Z" fill="#D73A49"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View File

@ -33,6 +33,7 @@ import {
GAS_ESTIMATE_TYPES,
GAS_RECOMMENDATIONS,
CUSTOM_GAS_ESTIMATE,
PRIORITY_LEVELS,
} from '../../../../shared/constants/gas';
import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils';
import {
@ -438,7 +439,11 @@ export default class TransactionController extends EventEmitter {
) {
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
if (process.env.EIP_1559_V2) {
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
} else {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
}
} else {
if (
(defaultMaxFeePerGas &&
@ -448,6 +453,8 @@ export default class TransactionController extends EventEmitter {
txMeta.origin === 'metamask'
) {
txMeta.userFeeLevel = GAS_RECOMMENDATIONS.MEDIUM;
} else if (process.env.EIP_1559_V2) {
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
} else {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
}

View File

@ -229,6 +229,21 @@ const multiplyCurrencies = (a, b, options = {}) => {
});
};
const divideCurrencies = (a, b, options = {}) => {
const { dividendBase, divisorBase, ...conversionOptions } = options;
if (!isValidBase(dividendBase) || !isValidBase(divisorBase)) {
throw new Error('Must specify valid dividendBase and divisorBase');
}
const value = getBigNumber(a, dividendBase).div(getBigNumber(b, divisorBase));
return converter({
value,
...conversionOptions,
});
};
const conversionGreaterThan = ({ ...firstProps }, { ...secondProps }) => {
const firstValue = converter({ ...firstProps });
const secondValue = converter({ ...secondProps });
@ -291,4 +306,5 @@ export {
decGWEIToHexWEI,
toBigNumber,
toNormalizedDenomination,
divideCurrencies,
};

View File

@ -1,5 +1,9 @@
import BigNumber from 'bignumber.js';
import { addCurrencies, conversionUtil } from './conversion.utils';
import {
addCurrencies,
conversionUtil,
divideCurrencies,
} from './conversion.utils';
describe('conversion utils', () => {
describe('addCurrencies()', () => {
@ -163,4 +167,39 @@ describe('conversion utils', () => {
).toStrictEqual('1.5');
});
});
describe('divideCurrencies()', () => {
it('should correctly divide decimal values', () => {
const result = divideCurrencies(9, 3, {
dividendBase: 10,
divisorBase: 10,
});
expect(result.toNumber()).toStrictEqual(3);
});
it('should correctly divide hexadecimal values', () => {
const result = divideCurrencies(1000, 0xa, {
dividendBase: 16,
divisorBase: 16,
});
expect(result.toNumber()).toStrictEqual(0x100);
});
it('should correctly divide hexadecimal value from decimal value', () => {
const result = divideCurrencies(0x3e8, 0xa, {
dividendBase: 16,
divisorBase: 16,
});
expect(result.toNumber()).toStrictEqual(0x100);
});
it('should throw error for wrong base value', () => {
expect(() => {
divideCurrencies(0x3e8, 0xa, {
dividendBase: 10.5,
divisorBase: 7,
});
}).toThrow('Must specify valid dividendBase and divisorBase');
});
});
});

View File

@ -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;

View File

@ -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();
});
});

View File

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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
});
});

View File

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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
});
});

View File

@ -8,26 +8,33 @@ import Button from '../../ui/button';
import I18nValue from '../../ui/i18n-value';
import Popover from '../../ui/popover';
import AdvancedGasFeeInputs from './advanced-gas-fee-inputs';
const AdvancedGasFeePopover = () => {
const t = useI18nContext();
const { closeModal, currentModal } = useTransactionModalContext();
const {
closeModal,
closeAllModals,
currentModal,
} = useTransactionModalContext();
if (currentModal !== 'advancedGasFee') return null;
// todo: align styles to edit gas fee modal
return (
<Popover
className="advanced-gas-fee-popover"
title={t('advancedGasFeeModalTitle')}
onBack={() => closeModal('advancedGasFee')}
onClose={() => closeModal('advancedGasFee')}
onClose={closeAllModals}
footer={
<Button type="primary">
<I18nValue messageKey="save" />
</Button>
}
>
<Box className="advanced-gas-fee-popover" margin={4}></Box>
<Box className="advanced-gas-fee-popover__wrapper">
<AdvancedGasFeeInputs />
</Box>
</Popover>
);
};

View File

@ -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;
}
}

View File

@ -52,3 +52,5 @@
@import 'whats-new-popup/index';
@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-input-subtext/index';

View File

@ -46,7 +46,7 @@ const DAPP_SUGGESTED_ESTIMATE = {
maxPriorityFeePerGas: '0x59682f00',
};
const renderComponent = (props, transactionProps, gasFeeContextProps) => {
const renderComponent = (componentProps, transactionProps) => {
const store = configureStore({
metamask: {
nativeCurrency: ETH,
@ -71,9 +71,8 @@ const renderComponent = (props, transactionProps, gasFeeContextProps) => {
return renderWithProvider(
<GasFeeContextProvider
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }}
{...gasFeeContextProps}
>
<EditGasItem priorityLevel="low" {...props} />
<EditGasItem priorityLevel="low" {...componentProps} />
</GasFeeContextProvider>,
store,
);
@ -137,7 +136,7 @@ describe('EditGasItem', () => {
});
it('should renders advance gas estimate option for priorityLevel custom', () => {
renderComponent({ priorityLevel: 'custom' });
renderComponent({ priorityLevel: 'custom' }, { userFeeLevel: 'high' });
expect(
screen.queryByRole('button', { name: 'custom' }),
).toBeInTheDocument();

View File

@ -1,7 +1,10 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
import {
GAS_ESTIMATE_TYPES,
PRIORITY_LEVELS,
} from '../../../../shared/constants/gas';
import { TRANSACTION_ENVELOPE_TYPES } from '../../../../shared/constants/transaction';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
@ -79,6 +82,7 @@ describe('TransactionDetail', () => {
render({
contextProps: {
transaction: {
userFeeLevel: PRIORITY_LEVELS.DAPP_SUGGESTED,
dappSuggestedGasFees: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
txParams: { maxFeePerGas: 1, maxPriorityFeePerGas: 1 },
},

View File

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

View File

@ -3,6 +3,7 @@
&__heading {
display: flex;
align-items: center;
margin-top: 4px;
}

View File

@ -43,6 +43,10 @@ export const TransactionModalContextProvider = ({
setOpenModals(modals);
};
const closeAllModals = () => {
setOpenModals([]);
};
const openModal = (modalName) => {
if (openModals.includes(modalName)) return;
captureEvent();
@ -55,6 +59,7 @@ export const TransactionModalContextProvider = ({
<TransactionModalContext.Provider
value={{
closeModal,
closeAllModals,
currentModal: openModals[openModals.length - 1],
openModal,
}}

View File

@ -5,9 +5,9 @@ import {
CUSTOM_GAS_ESTIMATE,
GAS_RECOMMENDATIONS,
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../shared/constants/gas';
import { GAS_FORM_ERRORS } from '../../helpers/constants/gas';
import { areDappSuggestedAndTxParamGasFeesTheSame } from '../../helpers/utils/confirm-tx.util';
import {
checkNetworkAndAccountSupports1559,
getAdvancedInlineGasShown,
@ -106,10 +106,10 @@ export function useGasFeeInputs(
});
const [estimateUsed, setEstimateUsed] = useState(() => {
if (areDappSuggestedAndTxParamGasFeesTheSame(transaction)) {
return 'dappSuggested';
if (estimateToUse) {
return estimateToUse;
}
return estimateToUse;
return PRIORITY_LEVELS.CUSTOM;
});
/**
@ -118,9 +118,7 @@ export function useGasFeeInputs(
* so that transaction is source of truth whenever possible.
*/
useEffect(() => {
if (areDappSuggestedAndTxParamGasFeesTheSame(transaction)) {
setEstimateUsed('dappSuggested');
} else if (transaction?.userFeeLevel) {
if (transaction?.userFeeLevel) {
setEstimateUsed(transaction?.userFeeLevel);
}
}, [setEstimateUsed, transaction]);
@ -219,11 +217,6 @@ export function useGasFeeInputs(
const { updateTransactionUsingGasFeeEstimates } = useTransactionFunctions({
defaultEstimateToUse,
gasLimit,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
gasFeeEstimates,
supportsEIP1559,
transaction,
});

View File

@ -2,16 +2,12 @@ import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { PRIORITY_LEVELS } from '../../../shared/constants/gas';
import {
decGWEIToHexWEI,
decimalToHex,
} from '../../helpers/utils/conversions.util';
import { decimalToHex } from '../../helpers/utils/conversions.util';
import { updateTransaction as updateTransactionFn } from '../../store/actions';
export const useTransactionFunctions = ({
defaultEstimateToUse,
gasLimit,
gasFeeEstimates,
transaction,
}) => {
const dispatch = useDispatch();
@ -23,9 +19,13 @@ export const useTransactionFunctions = ({
gasLimit: decimalToHex(gasLimit),
estimateSuggested: defaultEstimateToUse,
estimateUsed,
maxFeePerGas,
maxPriorityFeePerGas,
};
if (maxFeePerGas) {
newGasSettings.maxFeePerGas = maxFeePerGas;
}
if (maxPriorityFeePerGas) {
newGasSettings.maxPriorityFeePerGas = maxPriorityFeePerGas;
}
const updatedTxMeta = {
...transaction,
@ -49,23 +49,15 @@ export const useTransactionFunctions = ({
maxPriorityFeePerGas,
} = transaction?.dappSuggestedGasFees;
updateTransaction(
PRIORITY_LEVELS.CUSTOM,
PRIORITY_LEVELS.DAPP_SUGGESTED,
maxFeePerGas,
maxPriorityFeePerGas,
);
} else {
const {
suggestedMaxFeePerGas,
suggestedMaxPriorityFeePerGas,
} = gasFeeEstimates[gasFeeEstimateToUse];
updateTransaction(
gasFeeEstimateToUse,
decGWEIToHexWEI(suggestedMaxFeePerGas),
decGWEIToHexWEI(suggestedMaxPriorityFeePerGas),
);
updateTransaction(gasFeeEstimateToUse);
}
},
[gasFeeEstimates, transaction?.dappSuggestedGasFees, updateTransaction],
[transaction?.dappSuggestedGasFees, updateTransaction],
);
return { updateTransactionUsingGasFeeEstimates };

View File

@ -514,22 +514,26 @@ export default class ConfirmTransactionBase extends Component {
</div>
}
subText={
!isMultiLayerFeeNetwork &&
t('editGasSubTextFee', [
<b key="editGasSubTextFeeLabel">{t('editGasSubTextFeeLabel')}</b>,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>,
])
!isMultiLayerFeeNetwork && (
<>
<b key="editGasSubTextFeeLabel">
{t('editGasSubTextFeeLabel')}
</b>
,
<div
key="editGasSubTextFeeValue"
className="confirm-page-container-content__currency-container"
>
{renderHeartBeatIfNotInTest()}
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
</>
)
}
subTitle={
<>
@ -606,12 +610,14 @@ export default class ConfirmTransactionBase extends Component {
detailText={renderTotalDetailText()}
detailTotal={renderTotalDetailTotal()}
subTitle={t('transactionDetailGasTotalSubtitle')}
subText={t('editGasSubTextAmount', [
<b key="editGasSubTextAmountLabel">
{t('editGasSubTextAmountLabel')}
</b>,
renderTotalMaxAmount(),
])}
subText={
<>
<b key="editGasSubTextAmountLabel">
{t('editGasSubTextAmountLabel')}
</b>
{renderTotalMaxAmount()}
</>
}
/>
),
]}

View File

@ -92,34 +92,36 @@ const GasDetailsItem = ({
/>
</div>
}
subText={t('editGasSubTextFee', [
<Box
key="editGasSubTextFeeLabel"
display="inline-flex"
className={classNames('gas-details-item__gasfee-label', {
'gas-details-item__gas-fee-warning': estimateUsed === 'high',
})}
>
<Box marginRight={1}>
<b>
{estimateUsed === 'high' && '⚠ '}
<I18nValue messageKey="editGasSubTextFeeLabel" />
</b>
</Box>
<div
key="editGasSubTextFeeValue"
className="gas-details-item__currency-container"
subText={
<>
<Box
key="editGasSubTextFeeLabel"
display="inline-flex"
className={classNames('gas-details-item__gasfee-label', {
'gas-details-item__gas-fee-warning': estimateUsed === 'high',
})}
>
<HeartBeat />
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
</Box>,
])}
<Box marginRight={1}>
<b>
{estimateUsed === 'high' && '⚠ '}
<I18nValue messageKey="editGasSubTextFeeLabel" />
</b>
</Box>
<div
key="editGasSubTextFeeValue"
className="gas-details-item__currency-container"
>
<HeartBeat />
<UserPreferencedCurrencyDisplay
key="editGasSubTextFeeAmount"
type={PRIMARY}
value={hexMaximumTransactionFee}
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
/>
</div>
</Box>
</>
}
subTitle={
<GasTiming
maxPriorityFeePerGas={hexWEIToDecGWEI(