@@ -199,4 +302,6 @@ FeeCard.propTypes = {
numberOfQuotes: PropTypes.number.isRequired,
tokenConversionRate: PropTypes.number,
chainId: PropTypes.string.isRequired,
+ EIP1559Network: PropTypes.bool.isRequired,
+ maxPriorityFeePerGasDecGWEI: PropTypes.string,
};
diff --git a/ui/pages/swaps/fee-card/fee-card.test.js b/ui/pages/swaps/fee-card/fee-card.test.js
index 7a81e4903..f1cbd05a2 100644
--- a/ui/pages/swaps/fee-card/fee-card.test.js
+++ b/ui/pages/swaps/fee-card/fee-card.test.js
@@ -1,9 +1,30 @@
import React from 'react';
+import configureMockStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
-import { renderWithProvider } from '../../../../test/jest';
+import {
+ renderWithProvider,
+ createSwapsMockStore,
+ MOCKS,
+} from '../../../../test/jest';
import { MAINNET_CHAIN_ID } from '../../../../shared/constants/network';
import FeeCard from '.';
+const middleware = [thunk];
+
+jest.mock('../../../hooks/useGasFeeEstimates', () => {
+ return {
+ useGasFeeEstimates: () => {
+ return {
+ gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
+ gasEstimateType: 'fee-market',
+ estimatedGasFeeTimeBounds: undefined,
+ isGasEstimatesLoading: false,
+ };
+ },
+ };
+});
+
const createProps = (customProps = {}) => {
return {
primaryFee: {
@@ -32,6 +53,7 @@ const createProps = (customProps = {}) => {
onQuotesClick: jest.fn(),
tokenConversionRate: 0.015,
chainId: MAINNET_CHAIN_ID,
+ EIP1559Network: false,
...customProps,
};
};
@@ -58,4 +80,29 @@ describe('FeeCard', () => {
document.querySelector('.fee-card__top-bordered-row'),
).toMatchSnapshot();
});
+
+ it('renders the component with EIP-1559 enabled', () => {
+ const store = configureMockStore(middleware)(createSwapsMockStore());
+ const props = createProps({
+ EIP1559Network: true,
+ maxPriorityFeePerGasDecGWEI: '3',
+ });
+ const { getByText } = renderWithProvider(
, store);
+ expect(getByText('Using the best quote')).toBeInTheDocument();
+ expect(getByText('6 quotes')).toBeInTheDocument();
+ expect(getByText('Estimated gas fee')).toBeInTheDocument();
+ expect(getByText('Maybe in 5 minutes')).toBeInTheDocument();
+ expect(getByText(props.primaryFee.fee)).toBeInTheDocument();
+ expect(getByText(props.secondaryFee.fee)).toBeInTheDocument();
+ expect(getByText(`: ${props.secondaryFee.maxFee}`)).toBeInTheDocument();
+ expect(
+ getByText('Quote includes a 0.875% MetaMask fee'),
+ ).toBeInTheDocument();
+ expect(
+ document.querySelector('.fee-card__savings-and-quotes-header'),
+ ).toMatchSnapshot();
+ expect(
+ document.querySelector('.fee-card__top-bordered-row'),
+ ).toMatchSnapshot();
+ });
});
diff --git a/ui/pages/swaps/fee-card/index.scss b/ui/pages/swaps/fee-card/index.scss
index d1807fc0d..1737fa1b3 100644
--- a/ui/pages/swaps/fee-card/index.scss
+++ b/ui/pages/swaps/fee-card/index.scss
@@ -21,8 +21,8 @@
border-top-right-radius: 8px;
border-top-left-radius: 8px;
border-bottom: 0;
- padding-left: 8px;
- padding-right: 8px;
+ padding-left: 16px;
+ padding-right: 16px;
}
&__savings-text {
@@ -141,11 +141,18 @@
margin-right: 4px;
}
- &__link {
+ &__link,
+ &__link:hover {
color: $Blue-500;
cursor: pointer;
}
+ &__edit-link {
+ color: $Blue-500;
+ cursor: pointer;
+ padding-left: 6px;
+ }
+
&__total-box {
border-top: 1px solid $Grey-100;
padding: 12px 16px 16px 16px;
diff --git a/ui/pages/swaps/index.js b/ui/pages/swaps/index.js
index 45f6eb14a..daf88bfad 100644
--- a/ui/pages/swaps/index.js
+++ b/ui/pages/swaps/index.js
@@ -36,6 +36,7 @@ import {
getUseNewSwapsApi,
getFromToken,
} from '../../ducks/swaps/swaps';
+import { isEIP1559Network } from '../../ducks/metamask/metamask';
import {
AWAITING_SIGNATURES_ROUTE,
AWAITING_SWAP_ROUTE,
@@ -63,7 +64,7 @@ import {
} from '../../store/actions';
import { currentNetworkTxListSelector } from '../../selectors';
import { useNewMetricEvent } from '../../hooks/useMetricEvent';
-
+import { useGasFeeEstimates } from '../../hooks/useGasFeeEstimates';
import FeatureToggledRoute from '../../helpers/higher-order-components/feature-toggled-route';
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
import {
@@ -111,8 +112,15 @@ export default function Swap() {
const chainId = useSelector(getCurrentChainId);
const isSwapsChain = useSelector(getIsSwapsChain);
const useNewSwapsApi = useSelector(getUseNewSwapsApi);
+ const EIP1559Network = useSelector(isEIP1559Network);
const fromToken = useSelector(getFromToken);
+ if (EIP1559Network) {
+ // This will pre-load gas fees before going to the View Quote page.
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useGasFeeEstimates();
+ }
+
const {
balance: ethBalance,
address: selectedAccountAddress,
@@ -187,12 +195,14 @@ export default function Swap() {
dispatch(setAggregatorMetadata(newAggregatorMetadata));
},
);
- dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
+ if (!EIP1559Network) {
+ dispatch(fetchAndSetSwapsGasPriceInfo(chainId));
+ }
return () => {
dispatch(prepareToLeaveSwaps());
};
}
- }, [dispatch, chainId, isFeatureFlagLoaded, useNewSwapsApi]);
+ }, [dispatch, chainId, isFeatureFlagLoaded, useNewSwapsApi, EIP1559Network]);
const hardwareWalletUsed = useSelector(isHardwareWallet);
const hardwareWalletType = useSelector(getHardwareWalletType);
diff --git a/ui/pages/swaps/index.test.js b/ui/pages/swaps/index.test.js
index ed2323e40..a380171da 100644
--- a/ui/pages/swaps/index.test.js
+++ b/ui/pages/swaps/index.test.js
@@ -21,6 +21,8 @@ setBackgroundConnection({
setSwapsLiveness: jest.fn(() => true),
setSwapsTokens: jest.fn(),
setSwapsTxGasPrice: jest.fn(),
+ disconnectGasFeeEstimatePoller: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest.fn(),
});
describe('Swap', () => {
diff --git a/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.container.js b/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.container.js
index 9bcd95717..e4e78009b 100644
--- a/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.container.js
+++ b/ui/pages/swaps/swaps-gas-customization-modal/swaps-gas-customization-modal.container.js
@@ -59,10 +59,15 @@ const mapStateToProps = (state) => {
const customGasTotal = calcGasTotal(customGasLimit, customGasPrice);
- const swapsGasPriceEstimates = getSwapGasPriceEstimateData(state);
+ const gasEstimates = getSwapGasPriceEstimateData(state);
+ const gasEstimatesInNewFormat = {
+ low: gasEstimates.safeLow,
+ medium: gasEstimates.average,
+ high: gasEstimates.fast,
+ };
const { averageEstimateData, fastEstimateData } = getRenderableGasButtonData(
- swapsGasPriceEstimates,
+ gasEstimatesInNewFormat,
customGasLimit,
true,
conversionRate,
diff --git a/ui/pages/swaps/swaps.util.js b/ui/pages/swaps/swaps.util.js
index 701409a4a..c818aa3df 100644
--- a/ui/pages/swaps/swaps.util.js
+++ b/ui/pages/swaps/swaps.util.js
@@ -9,7 +9,9 @@ import {
ETHEREUM,
POLYGON,
BSC,
+ RINKEBY,
} from '../../../shared/constants/swaps';
+import { TRANSACTION_ENVELOPE_TYPES } from '../../../shared/constants/transaction';
import {
isSwapsDefaultTokenAddress,
isSwapsDefaultTokenSymbol,
@@ -21,6 +23,7 @@ import {
BSC_CHAIN_ID,
POLYGON_CHAIN_ID,
LOCALHOST_CHAIN_ID,
+ RINKEBY_CHAIN_ID,
} from '../../../shared/constants/network';
import { SECOND } from '../../../shared/constants/time';
import {
@@ -656,6 +659,8 @@ export function getSwapsTokensReceivedFromTxMeta(
chainId,
) {
const txReceipt = txMeta?.txReceipt;
+ const EIP1559Network =
+ txMeta?.txReceipt?.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET;
if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) {
if (
!txReceipt ||
@@ -670,11 +675,16 @@ export function getSwapsTokensReceivedFromTxMeta(
if (approvalTxMeta && approvalTxMeta.txReceipt) {
approvalTxGasCost = calcGasTotal(
approvalTxMeta.txReceipt.gasUsed,
- approvalTxMeta.txParams.gasPrice,
+ EIP1559Network
+ ? approvalTxMeta.txReceipt.effectiveGasPrice // Base fee + priority fee.
+ : approvalTxMeta.txParams.gasPrice,
);
}
- const gasCost = calcGasTotal(txReceipt.gasUsed, txMeta.txParams.gasPrice);
+ const gasCost = calcGasTotal(
+ txReceipt.gasUsed,
+ EIP1559Network ? txReceipt.effectiveGasPrice : txMeta.txParams.gasPrice,
+ );
const totalGasCost = new BigNumber(gasCost, 16)
.plus(approvalTxGasCost, 16)
.toString(16);
@@ -786,6 +796,8 @@ export const getNetworkNameByChainId = (chainId) => {
return BSC;
case POLYGON_CHAIN_ID:
return POLYGON;
+ case RINKEBY_CHAIN_ID:
+ return RINKEBY;
default:
return '';
}
@@ -799,8 +811,8 @@ export const getNetworkNameByChainId = (chainId) => {
*/
export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
const networkName = getNetworkNameByChainId(chainId);
- // Use old APIs for testnet.
- if (chainId === LOCALHOST_CHAIN_ID) {
+ // Use old APIs for testnet and Rinkeby.
+ if ([LOCALHOST_CHAIN_ID, RINKEBY_CHAIN_ID].includes(chainId)) {
return {
swapsFeatureIsLive: true,
useNewSwapsApi: false,
diff --git a/ui/pages/swaps/swaps.util.test.js b/ui/pages/swaps/swaps.util.test.js
index f4a2fdfbe..7d4957dd0 100644
--- a/ui/pages/swaps/swaps.util.test.js
+++ b/ui/pages/swaps/swaps.util.test.js
@@ -8,6 +8,7 @@ import {
POLYGON_CHAIN_ID,
LOCALHOST_CHAIN_ID,
RINKEBY_CHAIN_ID,
+ KOVAN_CHAIN_ID,
} from '../../../shared/constants/network';
import {
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
@@ -15,6 +16,7 @@ import {
ETHEREUM,
POLYGON,
BSC,
+ RINKEBY,
} from '../../../shared/constants/swaps';
import {
TOKENS,
@@ -394,8 +396,12 @@ describe('Swaps Util', () => {
expect(getNetworkNameByChainId(POLYGON_CHAIN_ID)).toBe(POLYGON);
});
+ it('returns "rinkeby" for Rinkeby chain ID', () => {
+ expect(getNetworkNameByChainId(RINKEBY_CHAIN_ID)).toBe(RINKEBY);
+ });
+
it('returns an empty string for an unsupported network', () => {
- expect(getNetworkNameByChainId(RINKEBY_CHAIN_ID)).toBe('');
+ expect(getNetworkNameByChainId(KOVAN_CHAIN_ID)).toBe('');
});
});
@@ -413,6 +419,19 @@ describe('Swaps Util', () => {
).toMatchObject(expectedSwapsLiveness);
});
+ it('returns info that Swaps are enabled and cannot use API v2 for Rinkeby chain ID', () => {
+ const expectedSwapsLiveness = {
+ swapsFeatureIsLive: true,
+ useNewSwapsApi: false,
+ };
+ expect(
+ getSwapsLivenessForNetwork(
+ MOCKS.createFeatureFlagsResponse(),
+ RINKEBY_CHAIN_ID,
+ ),
+ ).toMatchObject(expectedSwapsLiveness);
+ });
+
it('returns info that Swaps are disabled and cannot use API v2 if network name is not found', () => {
const expectedSwapsLiveness = {
swapsFeatureIsLive: false,
@@ -421,7 +440,7 @@ describe('Swaps Util', () => {
expect(
getSwapsLivenessForNetwork(
MOCKS.createFeatureFlagsResponse(),
- RINKEBY_CHAIN_ID,
+ KOVAN_CHAIN_ID,
),
).toMatchObject(expectedSwapsLiveness);
});
diff --git a/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap b/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
index 26456e10a..b0f5bc601 100644
--- a/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
+++ b/ui/pages/swaps/view-quote/__snapshots__/view-quote.test.js.snap
@@ -1,5 +1,106 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`ViewQuote renders the component with EIP-1559 enabled 1`] = `
+
+
+ 10
+
+
+
+ DAI
+
+
+`;
+
+exports[`ViewQuote renders the component with EIP-1559 enabled 2`] = `
+
+
+
+ 1
+
+
+ DAI
+
+
+ =
+
+
+ 2.2
+
+
+ USDC
+
+
+
+
+`;
+
+exports[`ViewQuote renders the component with EIP-1559 enabled 3`] = `
+
+`;
+
exports[`ViewQuote renders the component with initial props 1`] = `
{
return quotesToRenderableData(
quotes,
- gasPrice,
+ EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
conversionRate,
currentCurrency,
approveGas,
@@ -195,6 +241,8 @@ export default function ViewQuote() {
}, [
quotes,
gasPrice,
+ baseAndPriorityFeePerGas,
+ EIP1559Network,
conversionRate,
currentCurrency,
approveGas,
@@ -221,7 +269,7 @@ export default function ViewQuote() {
const { feeInFiat, feeInEth } = getRenderableNetworkFeesForQuote({
tradeGas: usedGasLimit,
approveGas,
- gasPrice,
+ gasPrice: EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
currentCurrency,
conversionRate,
tradeValue,
@@ -238,7 +286,7 @@ export default function ViewQuote() {
} = getRenderableNetworkFeesForQuote({
tradeGas: maxGasLimit,
approveGas,
- gasPrice,
+ gasPrice: EIP1559Network ? maxFeePerGas : gasPrice,
currentCurrency,
conversionRate,
tradeValue,
@@ -455,7 +503,10 @@ export default function ViewQuote() {
};
const nonGasFeeIsPositive = new BigNumber(nonGasFee, 16).gt(0);
- const approveGasTotal = calcGasTotal(approveGas || '0x0', gasPrice);
+ const approveGasTotal = calcGasTotal(
+ approveGas || '0x0',
+ EIP1559Network ? baseAndPriorityFeePerGas : gasPrice,
+ );
const extraNetworkFeeTotalInHexWEI = new BigNumber(nonGasFee, 16)
.plus(approveGasTotal, 16)
.toString(16);
@@ -474,26 +525,29 @@ export default function ViewQuote() {
extraInfoRowLabel = t('aggregatorFeeCost');
}
- const onFeeCardMaxRowClick = () =>
- dispatch(
- showModal({
- name: 'CUSTOMIZE_METASWAP_GAS',
- value: tradeValue,
- customGasLimitMessage: approveGas
- ? t('extraApprovalGas', [hexToDecimal(approveGas)])
- : '',
- customTotalSupplement: approveGasTotal,
- extraInfoRow: extraInfoRowLabel
- ? {
- label: extraInfoRowLabel,
- value: `${extraNetworkFeeTotalInEth} ${nativeCurrencySymbol}`,
- }
- : null,
- initialGasPrice: gasPrice,
- initialGasLimit: maxGasLimit,
- minimumGasLimit: new BigNumber(nonCustomMaxGasLimit, 16).toNumber(),
- }),
- );
+ const onFeeCardMaxRowClick = () => {
+ EIP1559Network
+ ? setShowEditGasPopover(true)
+ : dispatch(
+ showModal({
+ name: 'CUSTOMIZE_METASWAP_GAS',
+ value: tradeValue,
+ customGasLimitMessage: approveGas
+ ? t('extraApprovalGas', [hexToDecimal(approveGas)])
+ : '',
+ customTotalSupplement: approveGasTotal,
+ extraInfoRow: extraInfoRowLabel
+ ? {
+ label: extraInfoRowLabel,
+ value: `${extraNetworkFeeTotalInEth} ${nativeCurrencySymbol}`,
+ }
+ : null,
+ initialGasPrice: gasPrice,
+ initialGasLimit: maxGasLimit,
+ minimumGasLimit: new BigNumber(nonCustomMaxGasLimit, 16).toNumber(),
+ }),
+ );
+ };
const tokenApprovalTextComponent = (
@@ -590,6 +644,10 @@ export default function ViewQuote() {
const isShowingWarning =
showInsufficientWarning || shouldShowPriceDifferenceWarning;
+ const onCloseEditGasPopover = () => {
+ setShowEditGasPopover(false);
+ };
+
return (
)}
+
+ {showEditGasPopover && EIP1559Network && (
+
+ )}
+
@@ -697,8 +775,8 @@ export default function ViewQuote() {
balanceError ||
tokenBalanceUnavailable ||
disableSubmissionDueToPriceWarning ||
- gasPrice === null ||
- gasPrice === undefined
+ (EIP1559Network && baseAndPriorityFeePerGas === undefined) ||
+ (!EIP1559Network && (gasPrice === null || gasPrice === undefined))
}
className={isShowingWarning && 'view-quote__thin-swaps-footer'}
showTopBorder
diff --git a/ui/pages/swaps/view-quote/view-quote.test.js b/ui/pages/swaps/view-quote/view-quote.test.js
index f1b362f80..89b802169 100644
--- a/ui/pages/swaps/view-quote/view-quote.test.js
+++ b/ui/pages/swaps/view-quote/view-quote.test.js
@@ -6,6 +6,7 @@ import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
+ MOCKS,
} from '../../../../test/jest';
import ViewQuote from '.';
@@ -13,6 +14,18 @@ jest.mock('../../../components/ui/info-tooltip/info-tooltip-icon', () => () =>
'
',
);
+jest.mock('../../../hooks/useGasFeeInputs', () => {
+ return {
+ useGasFeeInputs: () => {
+ return {
+ maxFeePerGas: 16,
+ maxPriorityFeePerGas: 3,
+ gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
+ };
+ },
+ };
+});
+
const middleware = [thunk];
const createProps = (customProps = {}) => {
return {
@@ -31,6 +44,8 @@ setBackgroundConnection({
resetPostFetchState: jest.fn(),
safeRefetchQuotes: jest.fn(),
setSwapsErrorKey: jest.fn(),
+ getGasFeeEstimatesAndStartPolling: jest.fn(),
+ updateTransaction: jest.fn(),
});
describe('ViewQuote', () => {
@@ -53,4 +68,32 @@ describe('ViewQuote', () => {
expect(getByText('Back')).toBeInTheDocument();
expect(getByText('Swap')).toBeInTheDocument();
});
+
+ it('renders the component with EIP-1559 enabled', () => {
+ const state = createSwapsMockStore();
+ state.metamask.networkDetails = {
+ EIPS: {
+ 1559: true,
+ },
+ };
+ const store = configureMockStore(middleware)(state);
+ const props = createProps();
+ const { getByText, getByTestId } = renderWithProvider(
+
,
+ store,
+ );
+ expect(getByText('New quotes in')).toBeInTheDocument();
+ expect(getByTestId('main-quote-summary__source-row')).toMatchSnapshot();
+ expect(
+ getByTestId('main-quote-summary__exchange-rate-container'),
+ ).toMatchSnapshot();
+ expect(
+ getByTestId('fee-card__savings-and-quotes-header'),
+ ).toMatchSnapshot();
+ expect(getByText('Estimated gas fee')).toBeInTheDocument();
+ expect(getByText('0.01044 ETH')).toBeInTheDocument();
+ expect(getByText('Max fee')).toBeInTheDocument();
+ expect(getByText('Back')).toBeInTheDocument();
+ expect(getByText('Swap')).toBeInTheDocument();
+ });
});
diff --git a/ui/store/actions.js b/ui/store/actions.js
index 84175fd6a..25d923c8f 100644
--- a/ui/store/actions.js
+++ b/ui/store/actions.js
@@ -2199,6 +2199,23 @@ export function setSwapsTxGasLimit(gasLimit) {
};
}
+export function updateCustomSwapsEIP1559GasParams({
+ gasLimit,
+ maxFeePerGas,
+ maxPriorityFeePerGas,
+}) {
+ return async (dispatch) => {
+ await Promise.all([
+ promisifiedBackground.setSwapsTxGasLimit(gasLimit),
+ promisifiedBackground.setSwapsTxMaxFeePerGas(maxFeePerGas),
+ promisifiedBackground.setSwapsTxMaxFeePriorityPerGas(
+ maxPriorityFeePerGas,
+ ),
+ ]);
+ await forceUpdateMetamaskState(dispatch);
+ };
+}
+
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
return async (dispatch) => {
await promisifiedBackground.setSwapsTxGasPrice(gasPrice);