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:
parent
c2ea04c775
commit
4b975adc85
@ -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"
|
||||
},
|
||||
|
1
app/images/high-arrow.svg
Normal file
1
app/images/high-arrow.svg
Normal 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
1
app/images/low-arrow.svg
Normal 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 |
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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();
|
||||
|
@ -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 },
|
||||
},
|
||||
|
@ -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: '',
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
&__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}}
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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()}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user