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

Using EIP-1559 V2 for swaps (#12966)

This commit is contained in:
Jyoti Puri 2021-12-12 04:56:28 +05:30 committed by GitHub
parent 2085352de8
commit 19c3d021ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 647 additions and 312 deletions

View File

@ -2899,6 +2899,12 @@
"swapSourceInfo": { "swapSourceInfo": {
"message": "We search multiple liquidity sources (exchanges, aggregators and professional market makers) to find the best rates and lowest network fees." "message": "We search multiple liquidity sources (exchanges, aggregators and professional market makers) to find the best rates and lowest network fees."
}, },
"swapSuggested": {
"message": "Swap suggested"
},
"swapSuggestedGasSettingToolTipMessage": {
"message": "Swaps are complex and time sensitive transactions. We recommend this gas fee for a good balance between cost and confidence of a successful Swap."
},
"swapSwapFrom": { "swapSwapFrom": {
"message": "Swap from" "message": "Swap from"
}, },

View File

@ -258,8 +258,12 @@ export default class ConfirmPageContainer extends Component {
transaction={currentTransaction} transaction={currentTransaction}
/> />
)} )}
<EditGasFeePopover /> {supportsEIP1559V2 && (
<AdvancedGasFeePopover /> <>
<EditGasFeePopover />
<AdvancedGasFeePopover />
</>
)}
</div> </div>
</GasFeeContextProvider> </GasFeeContextProvider>
); );

View File

@ -1,7 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { COLORS } from '../../../helpers/constants/design-system'; import {
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../../shared/constants/gas';
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas'; import { PRIORITY_LEVEL_ICON_MAP } from '../../../helpers/constants/gas';
import { useGasFeeContext } from '../../../contexts/gasFee'; import { useGasFeeContext } from '../../../contexts/gasFee';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
@ -12,6 +16,7 @@ import Typography from '../../ui/typography/typography';
export default function EditGasFeeButton({ userAcknowledgedGasMissing }) { export default function EditGasFeeButton({ userAcknowledgedGasMissing }) {
const t = useI18nContext(); const t = useI18nContext();
const { const {
editGasMode,
gasLimit, gasLimit,
hasSimulationError, hasSimulationError,
estimateUsed, estimateUsed,
@ -28,13 +33,23 @@ export default function EditGasFeeButton({ userAcknowledgedGasMissing }) {
return null; return null;
} }
let icon = estimateUsed;
let title = estimateUsed;
if (
estimateUsed === PRIORITY_LEVELS.HIGH &&
editGasMode === EDIT_GAS_MODES.SWAPS
) {
icon = 'swapSuggested';
title = 'swapSuggested';
}
return ( return (
<div className="edit-gas-fee-button"> <div className="edit-gas-fee-button">
<button onClick={() => openModal('editGasFee')}> <button onClick={() => openModal('editGasFee')}>
<span className="edit-gas-fee-button__icon"> <span className="edit-gas-fee-button__icon">
{`${PRIORITY_LEVEL_ICON_MAP[estimateUsed]} `} {`${PRIORITY_LEVEL_ICON_MAP[icon]} `}
</span> </span>
<span className="edit-gas-fee-button__label">{t(estimateUsed)}</span> <span className="edit-gas-fee-button__label">{t(title)}</span>
<i className="fas fa-chevron-right asset-list-item__chevron-right" /> <i className="fas fa-chevron-right asset-list-item__chevron-right" />
</button> </button>
{estimateUsed === 'custom' && ( {estimateUsed === 'custom' && (
@ -44,18 +59,18 @@ export default function EditGasFeeButton({ userAcknowledgedGasMissing }) {
<InfoTooltip <InfoTooltip
contentText={ contentText={
<div className="edit-gas-fee-button__tooltip"> <div className="edit-gas-fee-button__tooltip">
<Typography fontSize="12px" color={COLORS.GREY}> <Typography variant={TYPOGRAPHY.H7} color={COLORS.GREY}>
{t('dappSuggestedTooltip', [transaction.origin])} {t('dappSuggestedTooltip', [transaction.origin])}
</Typography> </Typography>
<Typography fontSize="12px"> <Typography variant={TYPOGRAPHY.H7}>
<b>{t('maxBaseFee')}</b> <b>{t('maxBaseFee')}</b>
{maxFeePerGas} {maxFeePerGas}
</Typography> </Typography>
<Typography fontSize="12px"> <Typography variant={TYPOGRAPHY.H7}>
<b>{t('maxPriorityFee')}</b> <b>{t('maxPriorityFee')}</b>
{maxPriorityFeePerGas} {maxPriorityFeePerGas}
</Typography> </Typography>
<Typography fontSize="12px"> <Typography variant={TYPOGRAPHY.H7}>
<b>{t('gasLimit')}</b> <b>{t('gasLimit')}</b>
{gasLimit} {gasLimit}
</Typography> </Typography>

View File

@ -2,6 +2,7 @@ import React from 'react';
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { import {
EDIT_GAS_MODES,
GAS_ESTIMATE_TYPES, GAS_ESTIMATE_TYPES,
PRIORITY_LEVELS, PRIORITY_LEVELS,
} from '../../../../shared/constants/gas'; } from '../../../../shared/constants/gas';
@ -87,6 +88,17 @@ describe('EditGasFeeButton', () => {
expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1); expect(document.getElementsByClassName('info-tooltip')).toHaveLength(1);
}); });
it('should render edit link with text swap suggested if high gas estimates are selected for swaps', () => {
render({
contextProps: {
transaction: { userFeeLevel: 'high' },
editGasMode: EDIT_GAS_MODES.SWAPS,
},
});
expect(screen.queryByText('🔄')).toBeInTheDocument();
expect(screen.queryByText('Swap suggested')).toBeInTheDocument();
});
it('should render edit link with text advance if custom gas estimates are used', () => { it('should render edit link with text advance if custom gas estimates are used', () => {
render({ render({
contextProps: { contextProps: {

View File

@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
import { PRIORITY_LEVELS } from '../../../../shared/constants/gas'; import {
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../../shared/constants/gas';
import { useI18nContext } from '../../../hooks/useI18nContext'; import { useI18nContext } from '../../../hooks/useI18nContext';
import { useTransactionModalContext } from '../../../contexts/transaction-modal'; import { useTransactionModalContext } from '../../../contexts/transaction-modal';
import ErrorMessage from '../../ui/error-message'; import ErrorMessage from '../../ui/error-message';
@ -15,7 +18,7 @@ import EditGasItem from './edit-gas-item';
import NetworkStatistics from './network-statistics'; import NetworkStatistics from './network-statistics';
const EditGasFeePopover = () => { const EditGasFeePopover = () => {
const { balanceError } = useGasFeeContext(); const { balanceError, editGasMode } = useGasFeeContext();
const t = useI18nContext(); const t = useI18nContext();
const { closeModal, currentModal } = useTransactionModalContext(); const { closeModal, currentModal } = useTransactionModalContext();
@ -38,17 +41,23 @@ const EditGasFeePopover = () => {
<I18nValue messageKey="gasOption" /> <I18nValue messageKey="gasOption" />
</span> </span>
<span className="edit-gas-fee-popover__content__header-time"> <span className="edit-gas-fee-popover__content__header-time">
<I18nValue messageKey="time" /> {editGasMode !== EDIT_GAS_MODES.SWAPS && (
<I18nValue messageKey="time" />
)}
</span> </span>
<span className="edit-gas-fee-popover__content__header-max-fee"> <span className="edit-gas-fee-popover__content__header-max-fee">
<I18nValue messageKey="maxFee" /> <I18nValue messageKey="maxFee" />
</span> </span>
</div> </div>
<EditGasItem priorityLevel={PRIORITY_LEVELS.LOW} /> {editGasMode !== EDIT_GAS_MODES.SWAPS && (
<EditGasItem priorityLevel={PRIORITY_LEVELS.LOW} />
)}
<EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} /> <EditGasItem priorityLevel={PRIORITY_LEVELS.MEDIUM} />
<EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} /> <EditGasItem priorityLevel={PRIORITY_LEVELS.HIGH} />
<div className="edit-gas-fee-popover__content__separator" /> <div className="edit-gas-fee-popover__content__separator" />
<EditGasItem priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} /> {editGasMode !== EDIT_GAS_MODES.SWAPS && (
<EditGasItem priorityLevel={PRIORITY_LEVELS.DAPP_SUGGESTED} />
)}
<EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} /> <EditGasItem priorityLevel={PRIORITY_LEVELS.CUSTOM} />
<NetworkStatistics /> <NetworkStatistics />
<Typography <Typography

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
import { renderWithProvider } from '../../../../test/lib/render-helpers'; import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { ETH } from '../../../helpers/constants/common'; import { ETH } from '../../../helpers/constants/common';
import configureStore from '../../../store/store'; import configureStore from '../../../store/store';
@ -45,7 +46,7 @@ const MOCK_FEE_ESTIMATE = {
estimatedBaseFee: '50', estimatedBaseFee: '50',
}; };
const render = (txProps) => { const render = ({ txProps, contextProps } = {}) => {
const store = configureStore({ const store = configureStore({
metamask: { metamask: {
nativeCurrency: ETH, nativeCurrency: ETH,
@ -66,6 +67,7 @@ const render = (txProps) => {
return renderWithProvider( return renderWithProvider(
<GasFeeContextProvider <GasFeeContextProvider
transaction={{ txParams: { gas: '0x5208' }, ...txProps }} transaction={{ txParams: { gas: '0x5208' }, ...txProps }}
{...contextProps}
> >
<EditGasFeePopover /> <EditGasFeePopover />
</GasFeeContextProvider>, </GasFeeContextProvider>,
@ -75,7 +77,7 @@ const render = (txProps) => {
describe('EditGasFeePopover', () => { describe('EditGasFeePopover', () => {
it('should renders low / medium / high options', () => { it('should renders low / medium / high options', () => {
render({ dappSuggestedGasFees: {} }); render({ txProps: { dappSuggestedGasFees: {} } });
expect(screen.queryByText('🐢')).toBeInTheDocument(); expect(screen.queryByText('🐢')).toBeInTheDocument();
expect(screen.queryByText('🦊')).toBeInTheDocument(); expect(screen.queryByText('🦊')).toBeInTheDocument();
@ -103,12 +105,40 @@ describe('EditGasFeePopover', () => {
}); });
it('should not show insufficient balance message if transaction value is less than balance', () => { it('should not show insufficient balance message if transaction value is less than balance', () => {
render({ userFeeLevel: 'high', txParams: { value: '0x64' } }); render({ txProps: { userFeeLevel: 'high', txParams: { value: '0x64' } } });
expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument(); expect(screen.queryByText('Insufficient funds.')).not.toBeInTheDocument();
}); });
it('should show insufficient balance message if transaction value is more than balance', () => { it('should show insufficient balance message if transaction value is more than balance', () => {
render({ userFeeLevel: 'high', txParams: { value: '0x5208' } }); render({
txProps: { userFeeLevel: 'high', txParams: { value: '0x5208' } },
});
expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument(); expect(screen.queryByText('Insufficient funds.')).toBeInTheDocument();
}); });
it('should not show low, aggressive and dapp-suggested options for swap', () => {
render({
contextProps: { editGasMode: EDIT_GAS_MODES.SWAPS },
});
expect(screen.queryByText('🐢')).not.toBeInTheDocument();
expect(screen.queryByText('🦊')).toBeInTheDocument();
expect(screen.queryByText('🦍')).not.toBeInTheDocument();
expect(screen.queryByText('🌐')).not.toBeInTheDocument();
expect(screen.queryByText('🔄')).toBeInTheDocument();
expect(screen.queryByText('⚙')).toBeInTheDocument();
expect(screen.queryByText('Low')).not.toBeInTheDocument();
expect(screen.queryByText('Market')).toBeInTheDocument();
expect(screen.queryByText('Aggressive')).not.toBeInTheDocument();
expect(screen.queryByText('Site')).not.toBeInTheDocument();
expect(screen.queryByText('Swap suggested')).toBeInTheDocument();
expect(screen.queryByText('Advanced')).toBeInTheDocument();
});
it('should not show time estimates for swaps', () => {
render({
contextProps: { editGasMode: EDIT_GAS_MODES.SWAPS },
});
expect(screen.queryByText('Time')).not.toBeInTheDocument();
expect(screen.queryByText('Max fee')).toBeInTheDocument();
});
}); });

View File

@ -4,7 +4,10 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils'; import { getMaximumGasTotalInHexWei } from '../../../../../shared/modules/gas.utils';
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas'; import {
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../../../shared/constants/gas';
import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas'; import { PRIORITY_LEVEL_ICON_MAP } from '../../../../helpers/constants/gas';
import { PRIMARY } from '../../../../helpers/constants/common'; import { PRIMARY } from '../../../../helpers/constants/common';
import { import {
@ -27,17 +30,19 @@ import { useCustomTimeEstimate } from './useCustomTimeEstimate';
const EditGasItem = ({ priorityLevel }) => { const EditGasItem = ({ priorityLevel }) => {
const { const {
editGasMode,
estimateUsed, estimateUsed,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
maxFeePerGas: maxFeePerGasValue, maxFeePerGas: maxFeePerGasValue,
maxPriorityFeePerGas: maxPriorityFeePerGasValue, maxPriorityFeePerGas: maxPriorityFeePerGasValue,
updateTransactionUsingGasFeeEstimates, updateTransactionUsingGasFeeEstimates,
transaction: { dappSuggestedGasFees }, transaction,
} = useGasFeeContext(); } = useGasFeeContext();
const t = useI18nContext(); const t = useI18nContext();
const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues); const advancedGasFeeValues = useSelector(getAdvancedGasFeeValues);
const { closeModal, openModal } = useTransactionModalContext(); const { closeModal, openModal } = useTransactionModalContext();
const { dappSuggestedGasFees } = transaction;
let maxFeePerGas; let maxFeePerGas;
let maxPriorityFeePerGas; let maxPriorityFeePerGas;
@ -45,6 +50,8 @@ const EditGasItem = ({ priorityLevel }) => {
if (gasFeeEstimates?.[priorityLevel]) { if (gasFeeEstimates?.[priorityLevel]) {
maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas; maxFeePerGas = gasFeeEstimates[priorityLevel].suggestedMaxFeePerGas;
maxPriorityFeePerGas =
gasFeeEstimates[priorityLevel].suggestedMaxPriorityFeePerGas;
} else if ( } else if (
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED && priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED &&
dappSuggestedGasFees dappSuggestedGasFees
@ -105,6 +112,18 @@ const EditGasItem = ({ priorityLevel }) => {
return null; return null;
} }
let icon = priorityLevel;
let title = priorityLevel;
if (priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED) {
title = 'dappSuggestedShortLabel';
} else if (
priorityLevel === PRIORITY_LEVELS.HIGH &&
editGasMode === EDIT_GAS_MODES.SWAPS
) {
icon = 'swapSuggested';
title = 'swapSuggested';
}
return ( return (
<button <button
className={classNames('edit-gas-item', { className={classNames('edit-gas-item', {
@ -118,22 +137,15 @@ const EditGasItem = ({ priorityLevel }) => {
<span <span
className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`} className={`edit-gas-item__icon edit-gas-item__icon-${priorityLevel}`}
> >
{PRIORITY_LEVEL_ICON_MAP[priorityLevel]} {PRIORITY_LEVEL_ICON_MAP[icon]}
</span> </span>
<I18nValue <I18nValue messageKey={title} />
messageKey={
priorityLevel === PRIORITY_LEVELS.DAPP_SUGGESTED
? 'dappSuggestedShortLabel'
: priorityLevel
}
/>
</span> </span>
<span <span
className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`} className={`edit-gas-item__time-estimate edit-gas-item__time-estimate-${priorityLevel}`}
> >
{minWaitTime {editGasMode !== EDIT_GAS_MODES.SWAPS &&
? minWaitTime && toHumanReadableTime(t, minWaitTime) (minWaitTime ? toHumanReadableTime(t, minWaitTime) : '--')}
: '--'}
</span> </span>
<span <span
className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${priorityLevel}`} className={`edit-gas-item__fee-estimate edit-gas-item__fee-estimate-${priorityLevel}`}
@ -159,6 +171,9 @@ const EditGasItem = ({ priorityLevel }) => {
priorityLevel={priorityLevel} priorityLevel={priorityLevel}
maxFeePerGas={maxFeePerGas} maxFeePerGas={maxFeePerGas}
maxPriorityFeePerGas={maxPriorityFeePerGas} maxPriorityFeePerGas={maxPriorityFeePerGas}
editGasMode={editGasMode}
gasLimit={gasLimit}
transaction={transaction}
/> />
} }
position="top" position="top"

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { screen } from '@testing-library/react'; import { screen } from '@testing-library/react';
import { EDIT_GAS_MODES } from '../../../../../shared/constants/gas';
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import { ETH } from '../../../../helpers/constants/common'; import { ETH } from '../../../../helpers/constants/common';
import configureStore from '../../../../store/store'; import configureStore from '../../../../store/store';
@ -46,7 +47,11 @@ const DAPP_SUGGESTED_ESTIMATE = {
maxPriorityFeePerGas: '0x59682f00', maxPriorityFeePerGas: '0x59682f00',
}; };
const renderComponent = (componentProps, transactionProps) => { const renderComponent = ({
componentProps,
transactionProps,
contextProps,
} = {}) => {
const store = configureStore({ const store = configureStore({
metamask: { metamask: {
nativeCurrency: ETH, nativeCurrency: ETH,
@ -71,6 +76,7 @@ const renderComponent = (componentProps, transactionProps) => {
return renderWithProvider( return renderWithProvider(
<GasFeeContextProvider <GasFeeContextProvider
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }} transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }}
{...contextProps}
> >
<EditGasItem priorityLevel="low" {...componentProps} /> <EditGasItem priorityLevel="low" {...componentProps} />
</GasFeeContextProvider>, </GasFeeContextProvider>,
@ -80,7 +86,7 @@ const renderComponent = (componentProps, transactionProps) => {
describe('EditGasItem', () => { describe('EditGasItem', () => {
it('should renders low gas estimate option for priorityLevel low', () => { it('should renders low gas estimate option for priorityLevel low', () => {
renderComponent({ priorityLevel: 'low' }); renderComponent({ componentProps: { priorityLevel: 'low' } });
expect(screen.queryByRole('button', { name: 'low' })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'low' })).toBeInTheDocument();
expect(screen.queryByText('🐢')).toBeInTheDocument(); expect(screen.queryByText('🐢')).toBeInTheDocument();
expect(screen.queryByText('Low')).toBeInTheDocument(); expect(screen.queryByText('Low')).toBeInTheDocument();
@ -89,7 +95,7 @@ describe('EditGasItem', () => {
}); });
it('should renders market gas estimate option for priorityLevel medium', () => { it('should renders market gas estimate option for priorityLevel medium', () => {
renderComponent({ priorityLevel: 'medium' }); renderComponent({ componentProps: { priorityLevel: 'medium' } });
expect( expect(
screen.queryByRole('button', { name: 'medium' }), screen.queryByRole('button', { name: 'medium' }),
).toBeInTheDocument(); ).toBeInTheDocument();
@ -100,7 +106,7 @@ describe('EditGasItem', () => {
}); });
it('should renders aggressive gas estimate option for priorityLevel high', () => { it('should renders aggressive gas estimate option for priorityLevel high', () => {
renderComponent({ priorityLevel: 'high' }); renderComponent({ componentProps: { priorityLevel: 'high' } });
expect(screen.queryByRole('button', { name: 'high' })).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'high' })).toBeInTheDocument();
expect(screen.queryByText('🦍')).toBeInTheDocument(); expect(screen.queryByText('🦍')).toBeInTheDocument();
expect(screen.queryByText('Aggressive')).toBeInTheDocument(); expect(screen.queryByText('Aggressive')).toBeInTheDocument();
@ -108,21 +114,33 @@ describe('EditGasItem', () => {
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument(); expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
}); });
it('should render priorityLevel high as "Swap suggested" for swaps', () => {
renderComponent({
componentProps: { priorityLevel: 'high' },
contextProps: { editGasMode: EDIT_GAS_MODES.SWAPS },
});
expect(screen.queryByRole('button', { name: 'high' })).toBeInTheDocument();
expect(screen.queryByText('🔄')).toBeInTheDocument();
expect(screen.queryByText('Swap suggested')).toBeInTheDocument();
expect(screen.queryByText('15 sec')).not.toBeInTheDocument();
expect(screen.queryByTitle('0.0021 ETH')).toBeInTheDocument();
});
it('should highlight option is priorityLevel is currently selected', () => { it('should highlight option is priorityLevel is currently selected', () => {
renderComponent({ priorityLevel: 'high' }, { userFeeLevel: 'high' }); renderComponent({
componentProps: { priorityLevel: 'high' },
transactionProps: { userFeeLevel: 'high' },
});
expect( expect(
document.getElementsByClassName('edit-gas-item--selected'), document.getElementsByClassName('edit-gas-item--selected'),
).toHaveLength(1); ).toHaveLength(1);
}); });
it('should renders site gas estimate option for priorityLevel dappSuggested', () => { it('should renders site gas estimate option for priorityLevel dappSuggested', () => {
renderComponent( renderComponent({
{ priorityLevel: 'dappSuggested' }, componentProps: { priorityLevel: 'dappSuggested' },
{ transactionProps: { dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE },
dappSuggestedGasFees: DAPP_SUGGESTED_ESTIMATE, });
txParams: { gas: '0x5208', ...DAPP_SUGGESTED_ESTIMATE },
},
);
expect( expect(
screen.queryByRole('button', { name: 'dappSuggested' }), screen.queryByRole('button', { name: 'dappSuggested' }),
).toBeInTheDocument(); ).toBeInTheDocument();
@ -132,7 +150,10 @@ describe('EditGasItem', () => {
}); });
it('should renders advance gas estimate option for priorityLevel custom', () => { it('should renders advance gas estimate option for priorityLevel custom', () => {
renderComponent({ priorityLevel: 'custom' }, { userFeeLevel: 'high' }); renderComponent({
componentProps: { priorityLevel: 'custom' },
transactionProps: { userFeeLevel: 'high' },
});
expect( expect(
screen.queryByRole('button', { name: 'custom' }), screen.queryByRole('button', { name: 'custom' }),
).toBeInTheDocument(); ).toBeInTheDocument();

View File

@ -25,6 +25,7 @@
color: $ui-black; color: $ui-black;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
white-space: nowrap;
width: 36%; width: 36%;
} }

View File

@ -1,30 +1,28 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PRIORITY_LEVELS } from '../../../../../shared/constants/gas'; import {
EDIT_GAS_MODES,
PRIORITY_LEVELS,
} from '../../../../../shared/constants/gas';
import { import {
COLORS, COLORS,
FONT_WEIGHT, FONT_WEIGHT,
TYPOGRAPHY, TYPOGRAPHY,
} from '../../../../helpers/constants/design-system'; } from '../../../../helpers/constants/design-system';
import Typography from '../../../ui/typography'; import Typography from '../../../ui/typography';
import { useGasFeeContext } from '../../../../contexts/gasFee';
const EditGasToolTip = ({ const EditGasToolTip = ({
gasLimit,
priorityLevel, priorityLevel,
// maxFeePerGas & maxPriorityFeePerGas are derived from conditional logic // maxFeePerGas & maxPriorityFeePerGas are derived from conditional logic
// related to the source of the estimates. We pass these values from the // related to the source of the estimates. We pass these values from the
// the parent component (edit-gas-item) rather than recalculate them // the parent component (edit-gas-item) rather than recalculate them
maxFeePerGas, maxFeePerGas,
maxPriorityFeePerGas, maxPriorityFeePerGas,
editGasMode,
transaction,
t, t,
}) => { }) => {
const {
gasLimit,
maxFeePerGas: maxFeePerGasValue,
maxPriorityFeePerGas: maxPriorityFeePerGasValue,
transaction,
} = useGasFeeContext();
const toolTipMessage = () => { const toolTipMessage = () => {
switch (priorityLevel) { switch (priorityLevel) {
case PRIORITY_LEVELS.LOW: case PRIORITY_LEVELS.LOW:
@ -40,6 +38,9 @@ const EditGasToolTip = ({
</span>, </span>,
]); ]);
case PRIORITY_LEVELS.HIGH: case PRIORITY_LEVELS.HIGH:
if (editGasMode === EDIT_GAS_MODES.SWAPS) {
return t('swapSuggestedGasSettingToolTipMessage');
}
return t('highGasSettingToolTipMessage', [ return t('highGasSettingToolTipMessage', [
<span key={priorityLevel}> <span key={priorityLevel}>
<b>{t('high')}</b> <b>{t('high')}</b>
@ -64,10 +65,15 @@ const EditGasToolTip = ({
return ( return (
<div className="edit-gas-tooltip__container"> <div className="edit-gas-tooltip__container">
{priorityLevel !== PRIORITY_LEVELS.CUSTOM && {priorityLevel !== PRIORITY_LEVELS.CUSTOM &&
priorityLevel !== PRIORITY_LEVELS.DAPP_SUGGESTED ? ( priorityLevel !== PRIORITY_LEVELS.DAPP_SUGGESTED &&
!(
priorityLevel === PRIORITY_LEVELS.HIGH &&
editGasMode === EDIT_GAS_MODES.SWAPS
) ? (
<img alt="" src={`./images/curve-${priorityLevel}.svg`} /> <img alt="" src={`./images/curve-${priorityLevel}.svg`} />
) : null} ) : null}
{priorityLevel === PRIORITY_LEVELS.HIGH ? ( {priorityLevel === PRIORITY_LEVELS.HIGH &&
editGasMode !== EDIT_GAS_MODES.SWAPS ? (
<div className="edit-gas-tooltip__container__dialog"> <div className="edit-gas-tooltip__container__dialog">
<Typography variant={TYPOGRAPHY.H7} color={COLORS.WHITE}> <Typography variant={TYPOGRAPHY.H7} color={COLORS.WHITE}>
{t('highGasSettingToolTipDialog')} {t('highGasSettingToolTipDialog')}
@ -92,7 +98,7 @@ const EditGasToolTip = ({
color={COLORS.NEUTRAL_GREY} color={COLORS.NEUTRAL_GREY}
className="edit-gas-tooltip__container__value" className="edit-gas-tooltip__container__value"
> >
{maxFeePerGas ?? maxFeePerGasValue} {maxFeePerGas}
</Typography> </Typography>
</div> </div>
<div> <div>
@ -108,7 +114,7 @@ const EditGasToolTip = ({
color={COLORS.NEUTRAL_GREY} color={COLORS.NEUTRAL_GREY}
className="edit-gas-tooltip__container__value" className="edit-gas-tooltip__container__value"
> >
{maxPriorityFeePerGas ?? maxPriorityFeePerGasValue} {maxPriorityFeePerGas}
</Typography> </Typography>
</div> </div>
<div> <div>
@ -138,6 +144,9 @@ EditGasToolTip.propTypes = {
maxFeePerGas: PropTypes.string, maxFeePerGas: PropTypes.string,
maxPriorityFeePerGas: PropTypes.string, maxPriorityFeePerGas: PropTypes.string,
t: PropTypes.func, t: PropTypes.func,
editGasMode: PropTypes.string,
gasLimit: PropTypes.number,
transaction: PropTypes.object,
}; };
export default EditGasToolTip; export default EditGasToolTip;

View File

@ -30,7 +30,7 @@ const HIGH_GAS_OPTION = {
maxPriorityFeePerGas: '2', maxPriorityFeePerGas: '2',
}; };
const renderComponent = (props, transactionProps, gasFeeContextProps) => { const renderComponent = (componentProps) => {
const mockStore = { const mockStore = {
metamask: { metamask: {
provider: {}, provider: {},
@ -43,21 +43,14 @@ const renderComponent = (props, transactionProps, gasFeeContextProps) => {
}, },
selectedAddress: '0xAddress', selectedAddress: '0xAddress',
featureFlags: { advancedInlineGas: true }, featureFlags: { advancedInlineGas: true },
advancedGasFee: {
maxBaseFee: '1.5',
priorityFee: '2',
},
}, },
}; };
const store = configureStore(mockStore); const store = configureStore(mockStore);
return renderWithProvider( return renderWithProvider(
<GasFeeContextProvider <GasFeeContextProvider transaction={{ txParams: { gas: '0x5208' } }}>
transaction={{ txParams: { gas: '0x5208' }, ...transactionProps }} <EditGasToolTip {...componentProps} t={jest.fn()} gasLimit={21000} />
{...gasFeeContextProps}
>
<EditGasToolTip {...props} t={jest.fn()} />
</GasFeeContextProvider>, </GasFeeContextProvider>,
store, store,
); );

View File

@ -11,7 +11,7 @@ import TransactionDetailItem from '../transaction-detail-item/transaction-detail
export default function TransactionDetail({ export default function TransactionDetail({
rows = [], rows = [],
onEdit, onEdit,
userAcknowledgedGasMissing, userAcknowledgedGasMissing = false,
}) { }) {
const t = useI18nContext(); const t = useI18nContext();
const { supportsEIP1559V2 } = useGasFeeContext(); const { supportsEIP1559V2 } = useGasFeeContext();
@ -44,9 +44,5 @@ TransactionDetail.propTypes = {
* onClick handler for the Edit link * onClick handler for the Edit link
*/ */
onEdit: PropTypes.func, onEdit: PropTypes.func,
/** userAcknowledgedGasMissing: PropTypes.bool,
* If there is a error in getting correct estimates we show a message to the user
* which they can acknowledge and proceed with their transaction
*/
userAcknowledgedGasMissing: PropTypes.bool.isRequired,
}; };

View File

@ -37,5 +37,6 @@ export const PRIORITY_LEVEL_ICON_MAP = {
medium: '🦊', medium: '🦊',
high: '🦍', high: '🦍',
dappSuggested: '🌐', dappSuggested: '🌐',
swapSuggested: '🔄',
custom: '⚙', custom: '⚙',
}; };

View File

@ -20,6 +20,10 @@ import {
import { ETH } from '../../helpers/constants/common'; import { ETH } from '../../helpers/constants/common';
import { useGasFeeEstimates } from '../useGasFeeEstimates'; import { useGasFeeEstimates } from '../useGasFeeEstimates';
import {
getCustomMaxFeePerGas,
getCustomMaxPriorityFeePerGas,
} from '../../ducks/swaps/swaps';
// Why this number? // Why this number?
// 20 gwei * 21000 gasLimit = 420,000 gwei // 20 gwei * 21000 gasLimit = 420,000 gwei
@ -122,6 +126,12 @@ export const generateUseSelectorRouter = ({
balance: '0x440aa47cc2556', balance: '0x440aa47cc2556',
}; };
} }
if (selector === getCustomMaxFeePerGas) {
return '0x5208';
}
if (selector === getCustomMaxPriorityFeePerGas) {
return '0x5208';
}
if (selector === checkNetworkAndAccountSupports1559) { if (selector === checkNetworkAndAccountSupports1559) {
return checkNetworkAndAccountSupports1559Response; return checkNetworkAndAccountSupports1559Response;
} }

View File

@ -170,12 +170,12 @@ export function useGasFeeInputs(
maxFeePerGasFiat, maxFeePerGasFiat,
setMaxFeePerGas, setMaxFeePerGas,
} = useMaxFeePerGasInput({ } = useMaxFeePerGasInput({
supportsEIP1559V2,
estimateToUse, estimateToUse,
gasEstimateType, gasEstimateType,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
gasPrice, gasPrice,
supportsEIP1559V2,
transaction, transaction,
}); });
@ -184,11 +184,11 @@ export function useGasFeeInputs(
maxPriorityFeePerGasFiat, maxPriorityFeePerGasFiat,
setMaxPriorityFeePerGas, setMaxPriorityFeePerGas,
} = useMaxPriorityFeePerGasInput({ } = useMaxPriorityFeePerGasInput({
supportsEIP1559V2,
estimateToUse, estimateToUse,
gasEstimateType, gasEstimateType,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
supportsEIP1559V2,
transaction, transaction,
}); });
@ -248,6 +248,7 @@ export function useGasFeeInputs(
updateTransactionUsingGasFeeEstimates, updateTransactionUsingGasFeeEstimates,
} = useTransactionFunctions({ } = useTransactionFunctions({
defaultEstimateToUse, defaultEstimateToUse,
editGasMode,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
transaction, transaction,
@ -313,6 +314,7 @@ export function useGasFeeInputs(
setGasPrice, setGasPrice,
gasLimit, gasLimit,
setGasLimit, setGasLimit,
editGasMode,
estimateToUse, estimateToUse,
setEstimateToUse, setEstimateToUse,
estimatedMinimumFiat, estimatedMinimumFiat,

View File

@ -4,6 +4,7 @@ import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transactio
import { import {
GAS_RECOMMENDATIONS, GAS_RECOMMENDATIONS,
CUSTOM_GAS_ESTIMATE, CUSTOM_GAS_ESTIMATE,
EDIT_GAS_MODES,
} from '../../../shared/constants/gas'; } from '../../../shared/constants/gas';
import { ETH, PRIMARY } from '../../helpers/constants/common'; import { ETH, PRIMARY } from '../../helpers/constants/common';
@ -350,4 +351,13 @@ describe('useGasFeeInputs', () => {
expect(result.current.supportsEIP1559V2).toBe(false); expect(result.current.supportsEIP1559V2).toBe(false);
}); });
}); });
describe('editGasMode', () => {
it('should return editGasMode passed', () => {
const { result } = renderHook(() =>
useGasFeeInputs(undefined, undefined, undefined, EDIT_GAS_MODES.SWAPS),
);
expect(result.current.editGasMode).toBe(EDIT_GAS_MODES.SWAPS);
});
});
}); });

View File

@ -34,12 +34,12 @@ const getMaxFeePerGasFromTransaction = (transaction) => {
* method to update the setMaxFeePerGas. * method to update the setMaxFeePerGas.
*/ */
export function useMaxFeePerGasInput({ export function useMaxFeePerGasInput({
supportsEIP1559V2,
estimateToUse, estimateToUse,
gasEstimateType, gasEstimateType,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
gasPrice, gasPrice,
supportsEIP1559V2,
transaction, transaction,
}) { }) {
const supportsEIP1559 = const supportsEIP1559 =
@ -53,7 +53,7 @@ export function useMaxFeePerGasInput({
const showFiat = useSelector(getShouldShowFiat); const showFiat = useSelector(getShouldShowFiat);
const maxFeePerGasFromTransaction = supportsEIP1559 const initialMaxFeePerGas = supportsEIP1559
? getMaxFeePerGasFromTransaction(transaction) ? getMaxFeePerGasFromTransaction(transaction)
: 0; : 0;
@ -61,16 +61,16 @@ export function useMaxFeePerGasInput({
// transitional because it is only used to modify a transaction in the // transitional because it is only used to modify a transaction in the
// metamask (background) state tree. // metamask (background) state tree.
const [maxFeePerGas, setMaxFeePerGas] = useState(() => { const [maxFeePerGas, setMaxFeePerGas] = useState(() => {
if (maxFeePerGasFromTransaction && feeParamsAreCustom(transaction)) if (initialMaxFeePerGas && feeParamsAreCustom(transaction))
return maxFeePerGasFromTransaction; return initialMaxFeePerGas;
return null; return null;
}); });
useEffect(() => { useEffect(() => {
if (supportsEIP1559V2) { if (supportsEIP1559V2 && initialMaxFeePerGas) {
setMaxFeePerGas(maxFeePerGasFromTransaction); setMaxFeePerGas(initialMaxFeePerGas);
} }
}, [maxFeePerGasFromTransaction, setMaxFeePerGas, supportsEIP1559V2]); }, [initialMaxFeePerGas, setMaxFeePerGas, supportsEIP1559V2]);
let gasSettings = { let gasSettings = {
gasLimit: decimalToHex(gasLimit), gasLimit: decimalToHex(gasLimit),
@ -117,7 +117,7 @@ export function useMaxFeePerGasInput({
gasFeeEstimates, gasFeeEstimates,
gasEstimateType, gasEstimateType,
estimateToUse, estimateToUse,
maxFeePerGasFromTransaction, initialMaxFeePerGas || 0,
); );
return { return {

View File

@ -9,8 +9,8 @@ import {
checkNetworkAndAccountSupports1559, checkNetworkAndAccountSupports1559,
getShouldShowFiat, getShouldShowFiat,
} from '../../selectors'; } from '../../selectors';
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
import { multiplyCurrencies } from '../../../shared/modules/conversion.utils';
import { useCurrencyDisplay } from '../useCurrencyDisplay'; import { useCurrencyDisplay } from '../useCurrencyDisplay';
import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency'; import { useUserPreferencedCurrency } from '../useUserPreferencedCurrency';
@ -34,11 +34,11 @@ const getMaxPriorityFeePerGasFromTransaction = (transaction) => {
* method to update the maxPriorityFeePerGas. * method to update the maxPriorityFeePerGas.
*/ */
export function useMaxPriorityFeePerGasInput({ export function useMaxPriorityFeePerGasInput({
supportsEIP1559V2,
estimateToUse, estimateToUse,
gasEstimateType, gasEstimateType,
gasFeeEstimates, gasFeeEstimates,
gasLimit, gasLimit,
supportsEIP1559V2,
transaction, transaction,
}) { }) {
const supportsEIP1559 = const supportsEIP1559 =
@ -52,25 +52,21 @@ export function useMaxPriorityFeePerGasInput({
const showFiat = useSelector(getShouldShowFiat); const showFiat = useSelector(getShouldShowFiat);
const maxPriorityFeePerGasFromTransaction = supportsEIP1559 const initialMaxPriorityFeePerGas = supportsEIP1559
? getMaxPriorityFeePerGasFromTransaction(transaction) ? getMaxPriorityFeePerGasFromTransaction(transaction)
: 0; : 0;
const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState(() => { const [maxPriorityFeePerGas, setMaxPriorityFeePerGas] = useState(() => {
if (maxPriorityFeePerGasFromTransaction && feeParamsAreCustom(transaction)) if (initialMaxPriorityFeePerGas && feeParamsAreCustom(transaction))
return maxPriorityFeePerGasFromTransaction; return initialMaxPriorityFeePerGas;
return null; return null;
}); });
useEffect(() => { useEffect(() => {
if (supportsEIP1559V2) { if (supportsEIP1559V2 && initialMaxPriorityFeePerGas) {
setMaxPriorityFeePerGas(maxPriorityFeePerGasFromTransaction); setMaxPriorityFeePerGas(initialMaxPriorityFeePerGas);
} }
}, [ }, [initialMaxPriorityFeePerGas, setMaxPriorityFeePerGas, supportsEIP1559V2]);
maxPriorityFeePerGasFromTransaction,
setMaxPriorityFeePerGas,
supportsEIP1559V2,
]);
const maxPriorityFeePerGasToUse = const maxPriorityFeePerGasToUse =
maxPriorityFeePerGas ?? maxPriorityFeePerGas ??
@ -79,7 +75,7 @@ export function useMaxPriorityFeePerGasInput({
gasFeeEstimates, gasFeeEstimates,
gasEstimateType, gasEstimateType,
estimateToUse, estimateToUse,
maxPriorityFeePerGasFromTransaction, initialMaxPriorityFeePerGas || 0,
); );
// We need to display the estimated fiat currency impact of the // We need to display the estimated fiat currency impact of the

View File

@ -1,15 +1,20 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { PRIORITY_LEVELS } from '../../../shared/constants/gas'; import { EDIT_GAS_MODES, PRIORITY_LEVELS } from '../../../shared/constants/gas';
import { import {
decimalToHex, decimalToHex,
decGWEIToHexWEI, decGWEIToHexWEI,
} from '../../helpers/utils/conversions.util'; } from '../../helpers/utils/conversions.util';
import { updateTransaction as updateTransactionFn } from '../../store/actions'; import {
updateCustomSwapsEIP1559GasParams,
updateSwapsUserFeeLevel,
updateTransaction as updateTransactionFn,
} from '../../store/actions';
export const useTransactionFunctions = ({ export const useTransactionFunctions = ({
defaultEstimateToUse, defaultEstimateToUse,
editGasMode,
gasFeeEstimates, gasFeeEstimates,
gasLimit: gasLimitInTransaction, gasLimit: gasLimitInTransaction,
transaction, transaction,
@ -38,16 +43,29 @@ export const useTransactionFunctions = ({
const updatedTxMeta = { const updatedTxMeta = {
...transaction, ...transaction,
userFeeLevel: estimateUsed || 'custom', userFeeLevel: estimateUsed || PRIORITY_LEVELS.CUSTOM,
txParams: { txParams: {
...transaction.txParams, ...transaction.txParams,
...newGasSettings, ...newGasSettings,
}, },
}; };
dispatch(updateTransactionFn(updatedTxMeta)); if (editGasMode === EDIT_GAS_MODES.SWAPS) {
dispatch(
updateSwapsUserFeeLevel(estimateUsed || PRIORITY_LEVELS.CUSTOM),
);
dispatch(updateCustomSwapsEIP1559GasParams(newGasSettings));
} else {
dispatch(updateTransactionFn(updatedTxMeta));
}
}, },
[defaultEstimateToUse, dispatch, gasLimitInTransaction, transaction], [
defaultEstimateToUse,
dispatch,
editGasMode,
gasLimitInTransaction,
transaction,
],
); );
const updateTransactionUsingGasFeeEstimates = useCallback( const updateTransactionUsingGasFeeEstimates = useCallback(

View File

@ -269,8 +269,12 @@ export default function ConfirmApprove() {
transaction={transaction} transaction={transaction}
/> />
)} )}
<EditGasFeePopover /> {supportsEIP1559V2 && (
<AdvancedGasFeePopover /> <>
<EditGasFeePopover />
<AdvancedGasFeePopover />
</>
)}
</TransactionModalContextProvider> </TransactionModalContextProvider>
} }
hideSenderToRecipient hideSenderToRecipient

View File

@ -439,7 +439,6 @@ export default class ConfirmTransactionBase extends Component {
key="gas_details" key="gas_details"
hexMaximumTransactionFee={hexMaximumTransactionFee} hexMaximumTransactionFee={hexMaximumTransactionFee}
hexMinimumTransactionFee={hexMinimumTransactionFee} hexMinimumTransactionFee={hexMinimumTransactionFee}
isMainnet={isMainnet}
maxFeePerGas={maxFeePerGas} maxFeePerGas={maxFeePerGas}
maxPriorityFeePerGas={maxPriorityFeePerGas} maxPriorityFeePerGas={maxPriorityFeePerGas}
supportsEIP1559={supportsEIP1559} supportsEIP1559={supportsEIP1559}

View File

@ -0,0 +1,52 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { TYPOGRAPHY } from '../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import { getIsMainnet } from '../../../../selectors';
import Box from '../../../../components/ui/box';
import I18nValue from '../../../../components/ui/i18n-value';
import InfoTooltip from '../../../../components/ui/info-tooltip/info-tooltip';
import Typography from '../../../../components/ui/typography/typography';
const GasDetailsItemTitle = () => {
const t = useI18nContext();
const isMainnet = useSelector(getIsMainnet);
return (
<Box display="flex">
<Box marginRight={1}>
<I18nValue messageKey="transactionDetailGasHeadingV2" />
</Box>
<span className="gas-details-item-title__estimate">
(<I18nValue messageKey="transactionDetailGasInfoV2" />)
</span>
<InfoTooltip
contentText={
<>
<Typography variant={TYPOGRAPHY.H7}>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</Typography>
<Typography variant={TYPOGRAPHY.H7}>
{t('transactionDetailGasTooltipExplanation')}
</Typography>
<Typography variant={TYPOGRAPHY.H7}>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</Typography>
</>
}
position="bottom"
/>
</Box>
);
};
export default GasDetailsItemTitle;

View File

@ -0,0 +1,9 @@
.gas-details-item-title {
&__estimate {
font-weight: 400;
font-style: italic;
font-size: 12px;
color: $Grey-500;
line-height: inherit;
}
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { MAINNET_CHAIN_ID } from '../../../../../shared/constants/network';
import { GasFeeContextProvider } from '../../../../contexts/gasFee';
import { renderWithProvider } from '../../../../../test/jest';
import configureStore from '../../../../store/store';
import GasDetailsItemTitle from './gas-details-item-title';
jest.mock('../../../../store/actions', () => ({
disconnectGasFeeEstimatePoller: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest
.fn()
.mockImplementation(() => Promise.resolve()),
addPollingTokenToAppState: jest.fn(),
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
}));
const render = () => {
const store = configureStore({
metamask: {
provider: { chainId: MAINNET_CHAIN_ID },
cachedBalances: {},
accounts: {
'0xAddress': {
address: '0xAddress',
balance: '0x176e5b6f173ebe66',
},
},
selectedAddress: '0xAddress',
},
});
return renderWithProvider(
<GasFeeContextProvider transaction={{ txParams: {} }}>
<GasDetailsItemTitle userAcknowledgedGasMissing={false} />
</GasFeeContextProvider>,
store,
);
};
describe('GasDetailsItem', () => {
it('should render label', async () => {
render();
await waitFor(() => {
expect(screen.queryByText('Gas')).toBeInTheDocument();
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
});
});
});

View File

@ -0,0 +1 @@
export { default } from './gas-details-item-title';

View File

@ -2,31 +2,27 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system'; import { COLORS } from '../../../helpers/constants/design-system';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'; import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util'; import { hexWEIToDecGWEI } from '../../../helpers/utils/conversions.util';
import { useI18nContext } from '../../../hooks/useI18nContext';
import Box from '../../../components/ui/box'; import Box from '../../../components/ui/box';
import Typography from '../../../components/ui/typography/typography';
import GasTiming from '../../../components/app/gas-timing/gas-timing.component'; import GasTiming from '../../../components/app/gas-timing/gas-timing.component';
import I18nValue from '../../../components/ui/i18n-value'; import I18nValue from '../../../components/ui/i18n-value';
import InfoTooltip from '../../../components/ui/info-tooltip/info-tooltip';
import LoadingHeartBeat from '../../../components/ui/loading-heartbeat'; import LoadingHeartBeat from '../../../components/ui/loading-heartbeat';
import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component'; import TransactionDetailItem from '../../../components/app/transaction-detail-item/transaction-detail-item.component';
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'; import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
import { useGasFeeContext } from '../../../contexts/gasFee'; import { useGasFeeContext } from '../../../contexts/gasFee';
import GasDetailsItemTitle from './gas-details-item-title';
const GasDetailsItem = ({ const GasDetailsItem = ({
hexMaximumTransactionFee, hexMaximumTransactionFee,
hexMinimumTransactionFee, hexMinimumTransactionFee,
isMainnet,
maxFeePerGas, maxFeePerGas,
maxPriorityFeePerGas, maxPriorityFeePerGas,
userAcknowledgedGasMissing, userAcknowledgedGasMissing,
useNativeCurrencyAsPrimaryCurrency, useNativeCurrencyAsPrimaryCurrency,
}) => { }) => {
const t = useI18nContext();
const { estimateUsed, hasSimulationError, transaction } = useGasFeeContext(); const { estimateUsed, hasSimulationError, transaction } = useGasFeeContext();
if (hasSimulationError && !userAcknowledgedGasMissing) return null; if (hasSimulationError && !userAcknowledgedGasMissing) return null;
@ -34,40 +30,7 @@ const GasDetailsItem = ({
return ( return (
<TransactionDetailItem <TransactionDetailItem
key="gas-item" key="gas-item"
detailTitle={ detailTitle={<GasDetailsItemTitle />}
<Box display="flex">
<Box marginRight={1}>
<I18nValue messageKey="transactionDetailGasHeadingV2" />
</Box>
<span className="gas-details-item__estimate">
(<I18nValue messageKey="transactionDetailGasInfoV2" />)
</span>
<InfoTooltip
contentText={
<>
<Typography tag={TYPOGRAPHY.Paragraph} variant={TYPOGRAPHY.H7}>
{t('transactionDetailGasTooltipIntro', [
isMainnet ? t('networkNameEthereum') : '',
])}
</Typography>
<Typography tag={TYPOGRAPHY.Paragraph} variant={TYPOGRAPHY.H7}>
{t('transactionDetailGasTooltipExplanation')}
</Typography>
<Typography tag={TYPOGRAPHY.Paragraph} variant={TYPOGRAPHY.H7}>
<a
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
target="_blank"
rel="noopener noreferrer"
>
{t('transactionDetailGasTooltipConversion')}
</a>
</Typography>
</>
}
position="bottom"
/>
</Box>
}
detailTitleColor={COLORS.BLACK} detailTitleColor={COLORS.BLACK}
detailText={ detailText={
<div className="gas-details-item__currency-container"> <div className="gas-details-item__currency-container">
@ -137,7 +100,6 @@ const GasDetailsItem = ({
GasDetailsItem.propTypes = { GasDetailsItem.propTypes = {
hexMaximumTransactionFee: PropTypes.string, hexMaximumTransactionFee: PropTypes.string,
hexMinimumTransactionFee: PropTypes.string, hexMinimumTransactionFee: PropTypes.string,
isMainnet: PropTypes.bool,
maxFeePerGas: PropTypes.string, maxFeePerGas: PropTypes.string,
maxPriorityFeePerGas: PropTypes.string, maxPriorityFeePerGas: PropTypes.string,
userAcknowledgedGasMissing: PropTypes.bool.isRequired, userAcknowledgedGasMissing: PropTypes.bool.isRequired,

View File

@ -1,12 +1,4 @@
.gas-details-item { .gas-details-item {
&__estimate {
font-weight: 400;
font-style: italic;
font-size: 12px;
color: $Grey-500;
line-height: inherit;
}
&__gas-fee-warning { &__gas-fee-warning {
color: $secondary-1; color: $secondary-1;
} }

View File

@ -6,6 +6,7 @@
@import 'confirm-decrypt-message/confirm-decrypt-message'; @import 'confirm-decrypt-message/confirm-decrypt-message';
@import 'confirm-encryption-public-key/confirm-encryption-public-key'; @import 'confirm-encryption-public-key/confirm-encryption-public-key';
@import 'confirm-transaction-base/gas-details-item/gas-details-item'; @import 'confirm-transaction-base/gas-details-item/gas-details-item';
@import 'confirm-transaction-base/gas-details-item/gas-details-item-title/gas-details-item-title';
@import 'confirm-transaction-base/transaction-alerts/transaction-alerts'; @import 'confirm-transaction-base/transaction-alerts/transaction-alerts';
@import 'confirmation/confirmation'; @import 'confirmation/confirmation';
@import 'connected-sites/index'; @import 'connected-sites/index';

View File

@ -1,5 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FeeCard renders the component with EIP-1559 V2 enabled 1`] = `null`;
exports[`FeeCard renders the component with EIP-1559 enabled 1`] = `null`; exports[`FeeCard renders the component with EIP-1559 enabled 1`] = `null`;
exports[`FeeCard renders the component with initial props 1`] = `null`; exports[`FeeCard renders the component with initial props 1`] = `null`;

View File

@ -18,6 +18,7 @@ import {
TYPOGRAPHY, TYPOGRAPHY,
FONT_WEIGHT, FONT_WEIGHT,
} from '../../../helpers/constants/design-system'; } from '../../../helpers/constants/design-system';
import GasDetailsItemTitle from '../../confirm-transaction-base/gas-details-item/gas-details-item-title';
const GAS_FEES_LEARN_MORE_URL = const GAS_FEES_LEARN_MORE_URL =
'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172'; 'https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172';
@ -34,6 +35,7 @@ export default function FeeCard({
onQuotesClick, onQuotesClick,
chainId, chainId,
isBestQuote, isBestQuote,
supportsEIP1559V2,
}) { }) {
const t = useContext(I18nContext); const t = useContext(I18nContext);
@ -73,42 +75,46 @@ export default function FeeCard({
<TransactionDetailItem <TransactionDetailItem
key="gas-item" key="gas-item"
detailTitle={ detailTitle={
<> supportsEIP1559V2 ? (
{t('transactionDetailGasHeading')} <GasDetailsItemTitle />
<InfoTooltip ) : (
position="top" <>
contentText={ {t('transactionDetailGasHeading')}
<> <InfoTooltip
<p className="fee-card__info-tooltip-paragraph"> position="top"
{t('swapGasFeesSummary', [ contentText={
getTranslatedNetworkName(), <>
])} <p className="fee-card__info-tooltip-paragraph">
</p> {t('swapGasFeesSummary', [
<p className="fee-card__info-tooltip-paragraph"> getTranslatedNetworkName(),
{t('swapGasFeesDetails')} ])}
</p> </p>
<p className="fee-card__info-tooltip-paragraph"> <p className="fee-card__info-tooltip-paragraph">
<a {t('swapGasFeesDetails')}
className="fee-card__link" </p>
onClick={() => { <p className="fee-card__info-tooltip-paragraph">
gasFeesLearnMoreLinkClickedEvent(); <a
global.platform.openTab({ className="fee-card__link"
url: GAS_FEES_LEARN_MORE_URL, onClick={() => {
}); gasFeesLearnMoreLinkClickedEvent();
}} global.platform.openTab({
target="_blank" url: GAS_FEES_LEARN_MORE_URL,
rel="noopener noreferrer" });
> }}
{t('swapGasFeesLearnMore')} target="_blank"
</a> rel="noopener noreferrer"
</p> >
</> {t('swapGasFeesLearnMore')}
} </a>
containerClassName="fee-card__info-tooltip-content-container" </p>
wrapperClassName="fee-card__row-label fee-card__info-tooltip-container" </>
wide }
/> containerClassName="fee-card__info-tooltip-content-container"
</> wrapperClassName="fee-card__row-label fee-card__info-tooltip-container"
wide
/>
</>
)
} }
detailText={primaryFee.fee} detailText={primaryFee.fee}
detailTotal={secondaryFee.fee} detailTotal={secondaryFee.fee}
@ -124,12 +130,14 @@ export default function FeeCard({
{t('maxFee')} {t('maxFee')}
</Typography> </Typography>
{`: ${secondaryFee.maxFee}`} {`: ${secondaryFee.maxFee}`}
<span {!supportsEIP1559V2 && (
className="fee-card__edit-link" <span
onClick={() => onFeeCardMaxRowClick()} className="fee-card__edit-link"
> onClick={() => onFeeCardMaxRowClick()}
{t('edit')} >
</span> {t('edit')}
</span>
)}
</> </>
) )
} }
@ -203,4 +211,5 @@ FeeCard.propTypes = {
numberOfQuotes: PropTypes.number.isRequired, numberOfQuotes: PropTypes.number.isRequired,
chainId: PropTypes.string.isRequired, chainId: PropTypes.string.isRequired,
isBestQuote: PropTypes.bool.isRequired, isBestQuote: PropTypes.bool.isRequired,
supportsEIP1559V2: PropTypes.bool.isRequired,
}; };

View File

@ -3,25 +3,37 @@ import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
import {
getGasEstimateType,
getGasFeeEstimates,
getIsGasEstimatesLoading,
} from '../../../ducks/metamask/metamask';
import { import {
renderWithProvider, renderWithProvider,
createSwapsMockStore, createSwapsMockStore,
setBackgroundConnection, setBackgroundConnection,
MOCKS, MOCKS,
} from '../../../../test/jest'; } from '../../../../test/jest';
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network'; import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import {
checkNetworkAndAccountSupports1559,
getPreferences,
getSelectedAccount,
} from '../../../selectors';
import {
getGasEstimateType,
getGasFeeEstimates,
getIsGasEstimatesLoading,
} from '../../../ducks/metamask/metamask';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../helpers/constants/transactions'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../helpers/constants/transactions';
import { useGasFeeEstimates } from '../../../hooks/useGasFeeEstimates';
import FeeCard from '.'; import FeeCard from '.';
const middleware = [thunk]; const middleware = [thunk];
jest.mock('../../../hooks/useGasFeeEstimates', () => ({
useGasFeeEstimates: jest.fn(),
}));
jest.mock('react-redux', () => { jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux'); const actual = jest.requireActual('react-redux');
@ -78,6 +90,7 @@ const createProps = (customProps = {}) => {
tokenConversionRate: 0.015, tokenConversionRate: 0.015,
chainId: MAINNET_CHAIN_ID, chainId: MAINNET_CHAIN_ID,
networkAndAccountSupports1559: false, networkAndAccountSupports1559: false,
supportsEIP1559V2: false,
...customProps, ...customProps,
}; };
}; };
@ -118,4 +131,55 @@ describe('FeeCard', () => {
document.querySelector('.fee-card__top-bordered-row'), document.querySelector('.fee-card__top-bordered-row'),
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('renders the component with EIP-1559 V2 enabled', () => {
process.env.EIP_1559_V2 = true;
useGasFeeEstimates.mockImplementation(() => ({ gasFeeEstimates: {} }));
useSelector.mockImplementation((selector) => {
if (selector === getPreferences) {
return {
useNativeCurrencyAsPrimaryCurrency: true,
};
}
if (selector === getSelectedAccount) {
return {
balance: '0x440aa47cc2556',
};
}
if (selector === checkNetworkAndAccountSupports1559) {
return true;
}
return undefined;
});
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({
networkAndAccountSupports1559: true,
maxPriorityFeePerGasDecGWEI: '3',
maxFeePerGasDecGWEI: '4',
supportsEIP1559V2: true,
});
const { getByText } = renderWithProvider(
<GasFeeContextProvider
transaction={{ txParams: {}, userFeeLevel: 'high' }}
editGasMode={EDIT_GAS_MODES.SWAPS}
>
<FeeCard {...props} />
</GasFeeContextProvider>,
store,
);
expect(getByText('Best of 6 quotes.')).toBeInTheDocument();
expect(getByText('Gas')).toBeInTheDocument();
expect(getByText('(estimated)')).toBeInTheDocument();
expect(getByText('Swap suggested')).toBeInTheDocument();
expect(getByText('Max fee')).toBeInTheDocument();
expect(getByText(props.primaryFee.fee)).toBeInTheDocument();
expect(getByText(props.secondaryFee.fee)).toBeInTheDocument();
expect(getByText(`: ${props.secondaryFee.maxFee}`)).toBeInTheDocument();
expect(getByText('Includes a 0.875% MetaMask fee.')).toBeInTheDocument();
expect(
document.querySelector('.fee-card__top-bordered-row'),
).toMatchSnapshot();
process.env.EIP_1559_V2 = false;
});
}); });

View File

@ -81,6 +81,10 @@ import {
decGWEIToHexWEI, decGWEIToHexWEI,
addHexes, addHexes,
} from '../../../helpers/utils/conversions.util'; } from '../../../helpers/utils/conversions.util';
import { GasFeeContextProvider } from '../../../contexts/gasFee';
import { TransactionModalContextProvider } from '../../../contexts/transaction-modal';
import AdvancedGasFeePopover from '../../../components/app/advanced-gas-fee-popover';
import EditGasFeePopover from '../../../components/app/edit-gas-fee-popover';
import MainQuoteSummary from '../main-quote-summary'; import MainQuoteSummary from '../main-quote-summary';
import { calcGasTotal } from '../../send/send.utils'; import { calcGasTotal } from '../../send/send.utils';
import { getCustomTxParamsData } from '../../confirm-approve/confirm-approve.util'; import { getCustomTxParamsData } from '../../confirm-approve/confirm-approve.util';
@ -99,6 +103,10 @@ import CountdownTimer from '../countdown-timer';
import SwapsFooter from '../swaps-footer'; import SwapsFooter from '../swaps-footer';
import ViewQuotePriceDifference from './view-quote-price-difference'; import ViewQuotePriceDifference from './view-quote-price-difference';
// eslint-disable-next-line prefer-destructuring
const EIP_1559_V2_ENABLED =
process.env.EIP_1559_V2 === true || process.env.EIP_1559_V2 === 'true';
export default function ViewQuote() { export default function ViewQuote() {
const history = useHistory(); const history = useHistory();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -672,93 +680,152 @@ export default function ViewQuote() {
} }
}, [dispatch, viewQuotePageLoadedEvent, reviewSwapClickedTimestamp]); }, [dispatch, viewQuotePageLoadedEvent, reviewSwapClickedTimestamp]);
const transaction = {
userFeeLevel: swapsUserFeeLevel || GAS_RECOMMENDATIONS.HIGH,
txParams: {
maxFeePerGas,
maxPriorityFeePerGas,
gas: maxGasLimit,
},
};
const supportsEIP1559V2 =
EIP_1559_V2_ENABLED && networkAndAccountSupports1559;
return ( return (
<div className="view-quote"> <GasFeeContextProvider
<div editGasMode={EDIT_GAS_MODES.SWAPS}
className={classnames('view-quote__content', { minimumGasLimit={usedGasLimit}
'view-quote__content_modal': disableSubmissionDueToPriceWarning, transaction={transaction}
})} >
> <TransactionModalContextProvider captureEventEnabled={false}>
{selectQuotePopoverShown && ( <div className="view-quote">
<SelectQuotePopover <div
quoteDataRows={renderablePopoverData} className={classnames('view-quote__content', {
onClose={() => setSelectQuotePopoverShown(false)} 'view-quote__content_modal': disableSubmissionDueToPriceWarning,
onSubmit={(aggId) => dispatch(swapsQuoteSelected(aggId))} })}
swapToSymbol={destinationTokenSymbol} >
initialAggId={usedQuote.aggregator} {selectQuotePopoverShown && (
onQuoteDetailsIsOpened={quoteDetailsOpened} <SelectQuotePopover
/> quoteDataRows={renderablePopoverData}
)} onClose={() => setSelectQuotePopoverShown(false)}
onSubmit={(aggId) => dispatch(swapsQuoteSelected(aggId))}
swapToSymbol={destinationTokenSymbol}
initialAggId={usedQuote.aggregator}
onQuoteDetailsIsOpened={quoteDetailsOpened}
/>
)}
{showEditGasPopover && networkAndAccountSupports1559 && ( {!supportsEIP1559V2 &&
<EditGasPopover showEditGasPopover &&
transaction={{ networkAndAccountSupports1559 && (
userFeeLevel: swapsUserFeeLevel || GAS_RECOMMENDATIONS.HIGH, <EditGasPopover
txParams: { transaction={transaction}
maxFeePerGas, minimumGasLimit={usedGasLimit}
maxPriorityFeePerGas, defaultEstimateToUse={GAS_RECOMMENDATIONS.HIGH}
gas: maxGasLimit, mode={EDIT_GAS_MODES.SWAPS}
}, confirmButtonText={t('submit')}
}} onClose={onCloseEditGasPopover}
minimumGasLimit={usedGasLimit} />
defaultEstimateToUse={GAS_RECOMMENDATIONS.HIGH} )}
mode={EDIT_GAS_MODES.SWAPS} {supportsEIP1559V2 && (
confirmButtonText={t('submit')} <>
onClose={onCloseEditGasPopover} <EditGasFeePopover />
/> <AdvancedGasFeePopover />
)} </>
)}
<div <div
className={classnames('view-quote__warning-wrapper', { className={classnames('view-quote__warning-wrapper', {
'view-quote__warning-wrapper--thin': !isShowingWarning, 'view-quote__warning-wrapper--thin': !isShowingWarning,
})} })}
> >
{viewQuotePriceDifferenceComponent} {viewQuotePriceDifferenceComponent}
{(showInsufficientWarning || tokenBalanceUnavailable) && ( {(showInsufficientWarning || tokenBalanceUnavailable) && (
<ActionableMessage <ActionableMessage
message={actionableBalanceErrorMessage} message={actionableBalanceErrorMessage}
onClose={() => setWarningHidden(true)} onClose={() => setWarningHidden(true)}
/>
)}
</div>
<div className="view-quote__countdown-timer-container">
<CountdownTimer
timeStarted={quotesLastFetched}
warningTime="0:30"
labelKey="swapNewQuoteIn"
/>
</div>
<MainQuoteSummary
sourceValue={calcTokenValue(
sourceTokenValue,
sourceTokenDecimals,
)}
sourceDecimals={sourceTokenDecimals}
sourceSymbol={sourceTokenSymbol}
destinationValue={calcTokenValue(
destinationTokenValue,
destinationTokenDecimals,
)}
destinationDecimals={destinationTokenDecimals}
destinationSymbol={destinationTokenSymbol}
sourceIconUrl={sourceTokenIconUrl}
destinationIconUrl={destinationIconUrl}
/> />
)} <div
</div> className={classnames('view-quote__fee-card-container', {
<div className="view-quote__countdown-timer-container"> 'view-quote__fee-card-container--three-rows':
<CountdownTimer approveTxParams && (!balanceError || warningHidden),
timeStarted={quotesLastFetched} })}
warningTime="0:30" >
labelKey="swapNewQuoteIn" <FeeCard
/> primaryFee={{
</div> fee: feeInEth,
<MainQuoteSummary maxFee: maxFeeInEth,
sourceValue={calcTokenValue(sourceTokenValue, sourceTokenDecimals)} }}
sourceDecimals={sourceTokenDecimals} secondaryFee={{
sourceSymbol={sourceTokenSymbol} fee: feeInFiat,
destinationValue={calcTokenValue( maxFee: maxFeeInFiat,
destinationTokenValue, }}
destinationTokenDecimals, onFeeCardMaxRowClick={onFeeCardMaxRowClick}
)} hideTokenApprovalRow={
destinationDecimals={destinationTokenDecimals} !approveTxParams || (balanceError && !warningHidden)
destinationSymbol={destinationTokenSymbol} }
sourceIconUrl={sourceTokenIconUrl} tokenApprovalSourceTokenSymbol={sourceTokenSymbol}
destinationIconUrl={destinationIconUrl} onTokenApprovalClick={onFeeCardTokenApprovalClick}
/> metaMaskFee={String(metaMaskFee)}
<div numberOfQuotes={Object.values(quotes).length}
className={classnames('view-quote__fee-card-container', { onQuotesClick={() => {
'view-quote__fee-card-container--three-rows': allAvailableQuotesOpened();
approveTxParams && (!balanceError || warningHidden), setSelectQuotePopoverShown(true);
})} }}
> chainId={chainId}
<FeeCard isBestQuote={isBestQuote}
primaryFee={{ supportsEIP1559V2={supportsEIP1559V2}
fee: feeInEth, />
maxFee: maxFeeInEth, </div>
</div>
<SwapsFooter
onSubmit={() => {
setSubmitClicked(true);
if (!balanceError) {
dispatch(signAndSendTransactions(history, metaMetricsEvent));
} else if (destinationToken.symbol === defaultSwapsToken.symbol) {
history.push(DEFAULT_ROUTE);
} else {
history.push(`${ASSET_ROUTE}/${destinationToken.address}`);
}
}} }}
secondaryFee={{ submitText={t('swap')}
fee: feeInFiat, hideCancel
maxFee: maxFeeInFiat, disabled={
}} submitClicked ||
onFeeCardMaxRowClick={onFeeCardMaxRowClick} balanceError ||
hideTokenApprovalRow={ tokenBalanceUnavailable ||
!approveTxParams || (balanceError && !warningHidden) disableSubmissionDueToPriceWarning ||
(networkAndAccountSupports1559 &&
baseAndPriorityFeePerGas === undefined) ||
(!networkAndAccountSupports1559 &&
(gasPrice === null || gasPrice === undefined))
} }
tokenApprovalSourceTokenSymbol={sourceTokenSymbol} tokenApprovalSourceTokenSymbol={sourceTokenSymbol}
onTokenApprovalClick={onFeeCardTokenApprovalClick} onTokenApprovalClick={onFeeCardTokenApprovalClick}
@ -772,33 +839,7 @@ export default function ViewQuote() {
isBestQuote={isBestQuote} isBestQuote={isBestQuote}
/> />
</div> </div>
</div> </TransactionModalContextProvider>
<SwapsFooter </GasFeeContextProvider>
onSubmit={() => {
setSubmitClicked(true);
if (!balanceError) {
dispatch(signAndSendTransactions(history, metaMetricsEvent));
} else if (destinationToken.symbol === defaultSwapsToken.symbol) {
history.push(DEFAULT_ROUTE);
} else {
history.push(`${ASSET_ROUTE}/${destinationToken.address}`);
}
}}
submitText={t('swap')}
hideCancel
disabled={
submitClicked ||
balanceError ||
tokenBalanceUnavailable ||
disableSubmissionDueToPriceWarning ||
(networkAndAccountSupports1559 &&
baseAndPriorityFeePerGas === undefined) ||
(!networkAndAccountSupports1559 &&
(gasPrice === null || gasPrice === undefined))
}
className={isShowingWarning && 'view-quote__thin-swaps-footer'}
showTopBorder
/>
</div>
); );
} }