diff --git a/test/e2e/swaps/swaps-notifications.spec.js b/test/e2e/swaps/swaps-notifications.spec.js index 4d24bf5fe..42f17d5ec 100644 --- a/test/e2e/swaps/swaps-notifications.spec.js +++ b/test/e2e/swaps/swaps-notifications.spec.js @@ -111,7 +111,7 @@ describe('Swaps - notifications', function () { }); await checkNotification(driver, { title: 'Insufficient balance', - text: 'You need 50 more TESTETH to complete this swap', + text: 'You need 43.4467 more TESTETH to complete this swap', }); await reviewQuote(driver, { swapFrom: 'TESTETH', diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index 1c3501cb2..e44fe44fb 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -121,6 +121,7 @@ const initialState = { currentSmartTransactionsError: '', swapsSTXLoading: false, transactionSettingsOpened: false, + latestAddedTokenTo: '', }; const slice = createSlice({ @@ -150,6 +151,9 @@ const slice = createSlice({ setFetchingQuotes: (state, action) => { state.fetchingQuotes = action.payload; }, + setLatestAddedTokenTo: (state, action) => { + state.latestAddedTokenTo = action.payload; + }, setFromToken: (state, action) => { state.fromToken = action.payload; }, @@ -245,6 +249,8 @@ export const getToToken = (state) => state.swaps.toToken; export const getFetchingQuotes = (state) => state.swaps.fetchingQuotes; +export const getLatestAddedTokenTo = (state) => state.swaps.latestAddedTokenTo; + export const getQuotesFetchStartTime = (state) => state.swaps.quotesFetchStartTime; @@ -479,6 +485,7 @@ const { setAggregatorMetadata, setBalanceError, setFetchingQuotes, + setLatestAddedTokenTo, setFromToken, setFromTokenError, setFromTokenInputValue, @@ -502,6 +509,7 @@ export { setAggregatorMetadata, setBalanceError, setFetchingQuotes, + setLatestAddedTokenTo, setFromToken as setSwapsFromToken, setFromTokenError, setFromTokenInputValue, @@ -665,7 +673,12 @@ export const fetchQuotesAndSetQuoteState = ( iconUrl: fromTokenIconUrl, balance: fromTokenBalance, } = selectedFromToken; - const { address: toTokenAddress, symbol: toTokenSymbol } = selectedToToken; + const { + address: toTokenAddress, + symbol: toTokenSymbol, + decimals: toTokenDecimals, + iconUrl: toTokenIconUrl, + } = selectedToToken; // pageRedirectionDisabled is true if quotes prefetching is active (a user is on the Build Quote page). // In that case we just want to silently prefetch quotes without redirecting to the quotes loading page. if (!pageRedirectionDisabled) { @@ -676,6 +689,30 @@ export const fetchQuotesAndSetQuoteState = ( const contractExchangeRates = getTokenExchangeRates(state); + if ( + toTokenAddress && + toTokenSymbol !== swapsDefaultToken.symbol && + contractExchangeRates[toTokenAddress] === undefined && + !isTokenAlreadyAdded(toTokenAddress, getTokens(state)) + ) { + await dispatch( + addToken( + toTokenAddress, + toTokenSymbol, + toTokenDecimals, + toTokenIconUrl, + true, + ), + ); + await dispatch(setLatestAddedTokenTo(toTokenAddress)); + } else { + const latestAddedTokenTo = getLatestAddedTokenTo(state); + // Only reset the latest added Token To if it's a different token. + if (latestAddedTokenTo !== toTokenAddress) { + await dispatch(setLatestAddedTokenTo('')); + } + } + if ( fromTokenAddress && fromTokenSymbol !== swapsDefaultToken.symbol && @@ -831,36 +868,6 @@ export const fetchQuotesAndSetQuoteState = ( }; }; -const addTokenTo = (dispatch, state) => { - const fetchParams = getFetchParams(state); - const swapsDefaultToken = getSwapsDefaultToken(state); - const contractExchangeRates = getTokenExchangeRates(state); - const selectedToToken = - getToToken(state) || fetchParams?.metaData?.destinationTokenInfo || {}; - const { - address: toTokenAddress, - symbol: toTokenSymbol, - decimals: toTokenDecimals, - iconUrl: toTokenIconUrl, - } = selectedToToken; - if ( - toTokenAddress && - toTokenSymbol !== swapsDefaultToken.symbol && - contractExchangeRates[toTokenAddress] === undefined && - !isTokenAlreadyAdded(toTokenAddress, getTokens(state)) - ) { - dispatch( - addToken( - toTokenAddress, - toTokenSymbol, - toTokenDecimals, - toTokenIconUrl, - true, - ), - ); - } -}; - export const signAndSendSwapsSmartTransaction = ({ unsignedTransaction, trackEvent, @@ -960,7 +967,6 @@ export const signAndSendSwapsSmartTransaction = ({ dispatch(setCurrentSmartTransactionsError(StxErrorTypes.unavailable)); return; } - addTokenTo(dispatch, state); if (approveTxParams) { updatedApproveTxParams.gas = `0x${decimalToHex( fees.approvalTxFees?.gasLimit || 0, @@ -1204,7 +1210,6 @@ export const signAndSendTransactions = ( history.push(AWAITING_SIGNATURES_ROUTE); } - addTokenTo(dispatch, state); if (approveTxParams) { if (networkAndAccountSupports1559) { approveTxParams.maxFeePerGas = maxFeePerGas; diff --git a/ui/pages/swaps/build-quote/build-quote.js b/ui/pages/swaps/build-quote/build-quote.js index 7c2836703..9ed74343c 100644 --- a/ui/pages/swaps/build-quote/build-quote.js +++ b/ui/pages/swaps/build-quote/build-quote.js @@ -48,6 +48,7 @@ import { getIsFeatureFlagLoaded, getCurrentSmartTransactionsError, getSmartTransactionFees, + getLatestAddedTokenTo, } from '../../../ducks/swaps/swaps'; import { getSwapsDefaultToken, @@ -84,6 +85,7 @@ import { import { resetSwapsPostFetchState, + ignoreTokens, setBackgroundSwapRouteState, clearSwapsQuotes, stopPollingForQuotes, @@ -144,6 +146,7 @@ export default function BuildQuote({ const tokenList = useSelector(getTokenList, isEqual); const quotes = useSelector(getQuotes, isEqual); const areQuotesPresent = Object.keys(quotes).length > 0; + const latestAddedTokenTo = useSelector(getLatestAddedTokenTo, isEqual); const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual); const conversionRate = useSelector(getConversionRate); @@ -347,12 +350,21 @@ export default function BuildQuote({ ? getURLHostName(blockExplorerTokenLink) : t('etherscan'); + const { address: toAddress } = toToken || {}; const onToSelect = useCallback( (token) => { + if (latestAddedTokenTo && token.address !== toAddress) { + dispatch( + ignoreTokens({ + tokensToIgnore: toAddress, + dontShowLoadingIndicator: true, + }), + ); + } dispatch(setSwapToToken(token)); setVerificationClicked(false); }, - [dispatch], + [dispatch, latestAddedTokenTo, toAddress], ); const hideDropdownItemIf = useCallback( diff --git a/ui/pages/swaps/build-quote/build-quote.test.js b/ui/pages/swaps/build-quote/build-quote.test.js index e09ec1149..ea8c2cc70 100644 --- a/ui/pages/swaps/build-quote/build-quote.test.js +++ b/ui/pages/swaps/build-quote/build-quote.test.js @@ -29,6 +29,7 @@ const createProps = (customProps = {}) => { setBackgroundConnection({ resetPostFetchState: jest.fn(), + ignoreTokens: jest.fn(), setBackgroundSwapRouteState: jest.fn(), clearSwapsQuotes: jest.fn(), stopPollingForQuotes: jest.fn(), diff --git a/ui/pages/swaps/index.js b/ui/pages/swaps/index.js index 351561f0b..1a37271b4 100644 --- a/ui/pages/swaps/index.js +++ b/ui/pages/swaps/index.js @@ -50,6 +50,7 @@ import { navigateBackToBuildQuote, getSwapRedesignEnabled, setTransactionSettingsOpened, + getLatestAddedTokenTo, } from '../../ducks/swaps/swaps'; import { checkNetworkAndAccountSupports1559, @@ -79,6 +80,7 @@ import { import { resetBackgroundSwapsState, setSwapsTokens, + ignoreTokens, setBackgroundSwapRouteState, setSwapsErrorKey, } from '../../store/actions'; @@ -134,6 +136,7 @@ export default function Swap() { const routeState = useSelector(getBackgroundSwapRouteState); const selectedAccount = useSelector(getSelectedAccount, shallowEqual); const quotes = useSelector(getQuotes, isEqual); + const latestAddedTokenTo = useSelector(getLatestAddedTokenTo, isEqual); const txList = useSelector(currentNetworkTxListSelector, shallowEqual); const tradeTxId = useSelector(getTradeTxId); const approveTxId = useSelector(getApproveTxId); @@ -209,6 +212,32 @@ export default function Swap() { swapsErrorKey = SWAP_FAILED_ERROR; } + const clearTemporaryTokenRef = useRef(); + useEffect(() => { + clearTemporaryTokenRef.current = () => { + if (latestAddedTokenTo && (!isAwaitingSwapRoute || conversionError)) { + dispatch( + ignoreTokens({ + tokensToIgnore: latestAddedTokenTo, + dontShowLoadingIndicator: true, + }), + ); + } + }; + }, [ + conversionError, + dispatch, + latestAddedTokenTo, + destinationTokenInfo, + fetchParams, + isAwaitingSwapRoute, + ]); + useEffect(() => { + return () => { + clearTemporaryTokenRef.current(); + }; + }, []); + // eslint-disable-next-line useEffect(() => { if (!isSwapsChain) { @@ -283,6 +312,7 @@ export default function Swap() { const beforeUnloadEventAddedRef = useRef(); useEffect(() => { const fn = () => { + clearTemporaryTokenRef.current(); if (isLoadingQuotesRoute) { dispatch(prepareToLeaveSwaps()); } @@ -349,6 +379,7 @@ export default function Swap() { } const redirectToDefaultRoute = async () => { + clearTemporaryTokenRef.current(); dispatch(clearSwapsState()); await dispatch(resetBackgroundSwapsState()); history.push(DEFAULT_ROUTE); @@ -400,6 +431,7 @@ export default function Swap() {
{ + clearTemporaryTokenRef.current(); dispatch(clearSwapsState()); await dispatch(resetBackgroundSwapsState()); history.push(DEFAULT_ROUTE); diff --git a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js index e1f0c5f0e..de7027c4c 100644 --- a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js +++ b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.js @@ -54,6 +54,7 @@ import { getAggregatorMetadata, getTransactionSettingsOpened, setTransactionSettingsOpened, + getLatestAddedTokenTo, } from '../../../ducks/swaps/swaps'; import { getSwapsDefaultToken, @@ -92,6 +93,7 @@ import { } from '../../../../shared/constants/swaps'; import { resetSwapsPostFetchState, + ignoreTokens, clearSwapsQuotes, stopPollingForQuotes, setSmartTransactionsOptInStatus, @@ -182,6 +184,7 @@ export default function PrepareSwapPage({ const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual); const tokenList = useSelector(getTokenList, isEqual); const quotes = useSelector(getQuotes, isEqual); + const latestAddedTokenTo = useSelector(getLatestAddedTokenTo, isEqual); const numberOfQuotes = Object.keys(quotes).length; const areQuotesPresent = numberOfQuotes > 0; const swapsErrorKey = useSelector(getSwapsErrorKey); @@ -449,12 +452,21 @@ export default function PrepareSwapPage({ ? getURLHostName(blockExplorerTokenLink) : t('etherscan'); + const { address: toAddress } = toToken || {}; const onToSelect = useCallback( (token) => { + if (latestAddedTokenTo && token.address !== toAddress) { + dispatch( + ignoreTokens({ + tokensToIgnore: toAddress, + dontShowLoadingIndicator: true, + }), + ); + } dispatch(setSwapToToken(token)); setVerificationClicked(false); }, - [dispatch], + [dispatch, latestAddedTokenTo, toAddress], ); const tokensWithBalancesFromToken = tokensWithBalances.find((token) => diff --git a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js index 61255621f..9f0a499f7 100644 --- a/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js +++ b/ui/pages/swaps/prepare-swap-page/prepare-swap-page.test.js @@ -27,6 +27,7 @@ const createProps = (customProps = {}) => { setBackgroundConnection({ resetPostFetchState: jest.fn(), + ignoreTokens: jest.fn(), setBackgroundSwapRouteState: jest.fn(), clearSwapsQuotes: jest.fn(), stopPollingForQuotes: jest.fn(),