mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Swaps optimizations (#12842)
* Trigger Build * Trigger Build * Move swaps index variables to redux * all optimizations so far * Add better equality checks for selectors in swaps index and build quote * Clean up PR, remove extra code and logs * Clean up lavamoat file * Fixes for optimizations * Update tests and test snapshots * Remove unnecessary tests * Remove unnecessary console log * Trigger Build * Trigger Build * Add delay to account for remote call made by trezor keyring Co-authored-by: Dan Miller <danjm.com@gmail.com>
This commit is contained in:
parent
81ea24f08a
commit
7a92e22111
@ -85,6 +85,10 @@ const initialState = {
|
|||||||
balanceError: false,
|
balanceError: false,
|
||||||
fetchingQuotes: false,
|
fetchingQuotes: false,
|
||||||
fromToken: null,
|
fromToken: null,
|
||||||
|
fromTokenInputValue: '',
|
||||||
|
fromTokenError: null,
|
||||||
|
isFeatureFlagLoaded: false,
|
||||||
|
maxSlippage: 3,
|
||||||
quotesFetchStartTime: null,
|
quotesFetchStartTime: null,
|
||||||
reviewSwapClickedTimestamp: null,
|
reviewSwapClickedTimestamp: null,
|
||||||
topAssets: {},
|
topAssets: {},
|
||||||
@ -128,6 +132,18 @@ const slice = createSlice({
|
|||||||
setFromToken: (state, action) => {
|
setFromToken: (state, action) => {
|
||||||
state.fromToken = action.payload;
|
state.fromToken = action.payload;
|
||||||
},
|
},
|
||||||
|
setFromTokenInputValue: (state, action) => {
|
||||||
|
state.fromTokenInputValue = action.payload;
|
||||||
|
},
|
||||||
|
setFromTokenError: (state, action) => {
|
||||||
|
state.fromTokenError = action.payload;
|
||||||
|
},
|
||||||
|
setIsFeatureFlagLoaded: (state, action) => {
|
||||||
|
state.isFeatureFlagLoaded = action.payload;
|
||||||
|
},
|
||||||
|
setMaxSlippage: (state, action) => {
|
||||||
|
state.maxSlippage = action.payload;
|
||||||
|
},
|
||||||
setQuotesFetchStartTime: (state, action) => {
|
setQuotesFetchStartTime: (state, action) => {
|
||||||
state.quotesFetchStartTime = action.payload;
|
state.quotesFetchStartTime = action.payload;
|
||||||
},
|
},
|
||||||
@ -178,6 +194,16 @@ export const getBalanceError = (state) => state.swaps.balanceError;
|
|||||||
|
|
||||||
export const getFromToken = (state) => state.swaps.fromToken;
|
export const getFromToken = (state) => state.swaps.fromToken;
|
||||||
|
|
||||||
|
export const getFromTokenError = (state) => state.swaps.fromTokenError;
|
||||||
|
|
||||||
|
export const getFromTokenInputValue = (state) =>
|
||||||
|
state.swaps.fromTokenInputValue;
|
||||||
|
|
||||||
|
export const getIsFeatureFlagLoaded = (state) =>
|
||||||
|
state.swaps.isFeatureFlagLoaded;
|
||||||
|
|
||||||
|
export const getMaxSlippage = (state) => state.swaps.maxSlippage;
|
||||||
|
|
||||||
export const getTopAssets = (state) => state.swaps.topAssets;
|
export const getTopAssets = (state) => state.swaps.topAssets;
|
||||||
|
|
||||||
export const getToToken = (state) => state.swaps.toToken;
|
export const getToToken = (state) => state.swaps.toToken;
|
||||||
@ -329,6 +355,10 @@ const {
|
|||||||
setBalanceError,
|
setBalanceError,
|
||||||
setFetchingQuotes,
|
setFetchingQuotes,
|
||||||
setFromToken,
|
setFromToken,
|
||||||
|
setFromTokenError,
|
||||||
|
setFromTokenInputValue,
|
||||||
|
setIsFeatureFlagLoaded,
|
||||||
|
setMaxSlippage,
|
||||||
setQuotesFetchStartTime,
|
setQuotesFetchStartTime,
|
||||||
setReviewSwapClickedTimestamp,
|
setReviewSwapClickedTimestamp,
|
||||||
setTopAssets,
|
setTopAssets,
|
||||||
@ -345,6 +375,10 @@ export {
|
|||||||
setBalanceError,
|
setBalanceError,
|
||||||
setFetchingQuotes,
|
setFetchingQuotes,
|
||||||
setFromToken as setSwapsFromToken,
|
setFromToken as setSwapsFromToken,
|
||||||
|
setFromTokenError,
|
||||||
|
setFromTokenInputValue,
|
||||||
|
setIsFeatureFlagLoaded,
|
||||||
|
setMaxSlippage,
|
||||||
setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
|
setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
|
||||||
setReviewSwapClickedTimestamp,
|
setReviewSwapClickedTimestamp,
|
||||||
setTopAssets,
|
setTopAssets,
|
||||||
@ -411,6 +445,7 @@ export const fetchSwapsLiveness = () => {
|
|||||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||||
}
|
}
|
||||||
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
await dispatch(setSwapsLiveness(swapsLivenessForNetwork));
|
||||||
|
dispatch(setIsFeatureFlagLoaded(true));
|
||||||
return swapsLivenessForNetwork;
|
return swapsLivenessForNetwork;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -71,7 +71,7 @@ describe('Ducks - Swaps', () => {
|
|||||||
createGetState(),
|
createGetState(),
|
||||||
);
|
);
|
||||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -91,7 +91,7 @@ describe('Ducks - Swaps', () => {
|
|||||||
createGetState(),
|
createGetState(),
|
||||||
);
|
);
|
||||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -112,7 +112,7 @@ describe('Ducks - Swaps', () => {
|
|||||||
createGetState(),
|
createGetState(),
|
||||||
);
|
);
|
||||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -130,7 +130,7 @@ describe('Ducks - Swaps', () => {
|
|||||||
createGetState(),
|
createGetState(),
|
||||||
);
|
);
|
||||||
expect(featureFlagApiNock.isDone()).toBe(true);
|
expect(featureFlagApiNock.isDone()).toBe(true);
|
||||||
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -154,7 +154,7 @@ describe('Ducks - Swaps', () => {
|
|||||||
createGetState(),
|
createGetState(),
|
||||||
);
|
);
|
||||||
expect(featureFlagApiNock2.isDone()).toBe(false); // Second API call wasn't made, cache was used instead.
|
expect(featureFlagApiNock2.isDone()).toBe(false); // Second API call wasn't made, cache was used instead.
|
||||||
expect(mockDispatch).toHaveBeenCalledTimes(2);
|
expect(mockDispatch).toHaveBeenCalledTimes(4);
|
||||||
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
expect(setSwapsLiveness).toHaveBeenCalledWith(expectedSwapsLiveness);
|
||||||
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
expect(swapsLiveness).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useSelector } from 'react-redux';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getEstimatedGasFeeTimeBounds,
|
getEstimatedGasFeeTimeBounds,
|
||||||
getGasEstimateType,
|
getGasEstimateType,
|
||||||
@ -31,8 +32,11 @@ import { useSafeGasEstimatePolling } from './useSafeGasEstimatePolling';
|
|||||||
*/
|
*/
|
||||||
export function useGasFeeEstimates() {
|
export function useGasFeeEstimates() {
|
||||||
const gasEstimateType = useSelector(getGasEstimateType);
|
const gasEstimateType = useSelector(getGasEstimateType);
|
||||||
const gasFeeEstimates = useSelector(getGasFeeEstimates);
|
const gasFeeEstimates = useSelector(getGasFeeEstimates, isEqual);
|
||||||
const estimatedGasFeeTimeBounds = useSelector(getEstimatedGasFeeTimeBounds);
|
const estimatedGasFeeTimeBounds = useSelector(
|
||||||
|
getEstimatedGasFeeTimeBounds,
|
||||||
|
shallowEqual,
|
||||||
|
);
|
||||||
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
const isGasEstimatesLoading = useSelector(getIsGasEstimatesLoading);
|
||||||
useSafeGasEstimatePolling();
|
useSafeGasEstimatePolling();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getTokenExchangeRates,
|
getTokenExchangeRates,
|
||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
@ -29,7 +29,10 @@ export function useTokenFiatAmount(
|
|||||||
overrides = {},
|
overrides = {},
|
||||||
hideCurrencySymbol,
|
hideCurrencySymbol,
|
||||||
) {
|
) {
|
||||||
const contractExchangeRates = useSelector(getTokenExchangeRates);
|
const contractExchangeRates = useSelector(
|
||||||
|
getTokenExchangeRates,
|
||||||
|
shallowEqual,
|
||||||
|
);
|
||||||
const conversionRate = useSelector(getConversionRate);
|
const conversionRate = useSelector(getConversionRate);
|
||||||
const currentCurrency = useSelector(getCurrentCurrency);
|
const currentCurrency = useSelector(getCurrentCurrency);
|
||||||
const userPrefersShownFiat = useSelector(getShouldShowFiat);
|
const userPrefersShownFiat = useSelector(getShouldShowFiat);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import TokenTracker from '@metamask/eth-token-tracker';
|
import TokenTracker from '@metamask/eth-token-tracker';
|
||||||
import { useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { getCurrentChainId, getSelectedAddress } from '../selectors';
|
import { getCurrentChainId, getSelectedAddress } from '../selectors';
|
||||||
import { SECOND } from '../../shared/constants/time';
|
import { SECOND } from '../../shared/constants/time';
|
||||||
import { isEqualCaseInsensitive } from '../helpers/utils/util';
|
import { isEqualCaseInsensitive } from '../helpers/utils/util';
|
||||||
@ -12,7 +12,7 @@ export function useTokenTracker(
|
|||||||
hideZeroBalanceTokens = false,
|
hideZeroBalanceTokens = false,
|
||||||
) {
|
) {
|
||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const userAddress = useSelector(getSelectedAddress);
|
const userAddress = useSelector(getSelectedAddress, shallowEqual);
|
||||||
const [loading, setLoading] = useState(() => tokens?.length >= 0);
|
const [loading, setLoading] = useState(() => tokens?.length >= 0);
|
||||||
const [tokensWithBalances, setTokensWithBalances] = useState([]);
|
const [tokensWithBalances, setTokensWithBalances] = useState([]);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import contractMap from '@metamask/contract-metadata';
|
import contractMap from '@metamask/contract-metadata';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { isEqual, shuffle, uniqBy } from 'lodash';
|
import { isEqual, shuffle, uniqBy } from 'lodash';
|
||||||
@ -94,8 +94,8 @@ export function useTokensToSearch({
|
|||||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||||
const conversionRate = useSelector(getConversionRate);
|
const conversionRate = useSelector(getConversionRate);
|
||||||
const currentCurrency = useSelector(getCurrentCurrency);
|
const currentCurrency = useSelector(getCurrentCurrency);
|
||||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
const defaultSwapsToken = useSelector(getSwapsDefaultToken, shallowEqual);
|
||||||
const tokenList = useSelector(getTokenList);
|
const tokenList = useSelector(getTokenList, isEqual);
|
||||||
const useTokenDetection = useSelector(getUseTokenDetection);
|
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||||
// token from dynamic api list is fetched when useTokenDetection is true
|
// token from dynamic api list is fetched when useTokenDetection is true
|
||||||
const shuffledTokenList = useTokenDetection
|
const shuffledTokenList = useTokenDetection
|
||||||
@ -115,7 +115,7 @@ export function useTokensToSearch({
|
|||||||
);
|
);
|
||||||
const memoizedDefaultToken = useEqualityCheck(defaultToken);
|
const memoizedDefaultToken = useEqualityCheck(defaultToken);
|
||||||
|
|
||||||
const swapsTokens = useSelector(getSwapsTokens) || [];
|
const swapsTokens = useSelector(getSwapsTokens, isEqual) || [];
|
||||||
|
|
||||||
const tokensToSearch = swapsTokens.length
|
const tokensToSearch = swapsTokens.length
|
||||||
? swapsTokens
|
? swapsTokens
|
||||||
|
@ -77,8 +77,6 @@ export default class Routes extends Component {
|
|||||||
alertMessage: PropTypes.string,
|
alertMessage: PropTypes.string,
|
||||||
textDirection: PropTypes.string,
|
textDirection: PropTypes.string,
|
||||||
isNetworkLoading: PropTypes.bool,
|
isNetworkLoading: PropTypes.bool,
|
||||||
provider: PropTypes.object,
|
|
||||||
frequentRpcListDetail: PropTypes.array,
|
|
||||||
alertOpen: PropTypes.bool,
|
alertOpen: PropTypes.bool,
|
||||||
isUnlocked: PropTypes.bool,
|
isUnlocked: PropTypes.bool,
|
||||||
setLastActiveTime: PropTypes.func,
|
setLastActiveTime: PropTypes.func,
|
||||||
@ -88,10 +86,12 @@ export default class Routes extends Component {
|
|||||||
isMouseUser: PropTypes.bool,
|
isMouseUser: PropTypes.bool,
|
||||||
setMouseUserState: PropTypes.func,
|
setMouseUserState: PropTypes.func,
|
||||||
providerId: PropTypes.string,
|
providerId: PropTypes.string,
|
||||||
|
providerType: PropTypes.string,
|
||||||
autoLockTimeLimit: PropTypes.number,
|
autoLockTimeLimit: PropTypes.number,
|
||||||
pageChanged: PropTypes.func.isRequired,
|
pageChanged: PropTypes.func.isRequired,
|
||||||
prepareToLeaveSwaps: PropTypes.func,
|
prepareToLeaveSwaps: PropTypes.func,
|
||||||
browserEnvironment: PropTypes.object,
|
browserEnvironmentOs: PropTypes.string,
|
||||||
|
browserEnvironmentBrowser: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -287,6 +287,13 @@ export default class Routes extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAppHeaderClick = async () => {
|
||||||
|
const { prepareToLeaveSwaps } = this.props;
|
||||||
|
if (this.onSwapsPage()) {
|
||||||
|
await prepareToLeaveSwaps();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -295,19 +302,16 @@ export default class Routes extends Component {
|
|||||||
textDirection,
|
textDirection,
|
||||||
loadingMessage,
|
loadingMessage,
|
||||||
isNetworkLoading,
|
isNetworkLoading,
|
||||||
provider,
|
|
||||||
frequentRpcListDetail,
|
|
||||||
setMouseUserState,
|
setMouseUserState,
|
||||||
isMouseUser,
|
isMouseUser,
|
||||||
prepareToLeaveSwaps,
|
browserEnvironmentOs: os,
|
||||||
browserEnvironment,
|
browserEnvironmentBrowser: browser,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const loadMessage =
|
const loadMessage =
|
||||||
loadingMessage || isNetworkLoading
|
loadingMessage || isNetworkLoading
|
||||||
? this.getConnectingLabel(loadingMessage)
|
? this.getConnectingLabel(loadingMessage)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const { os, browser } = browserEnvironment;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('app', {
|
className={classnames('app', {
|
||||||
@ -330,11 +334,7 @@ export default class Routes extends Component {
|
|||||||
<AppHeader
|
<AppHeader
|
||||||
hideNetworkIndicator={this.onInitializationUnlockPage()}
|
hideNetworkIndicator={this.onInitializationUnlockPage()}
|
||||||
disableNetworkIndicator={this.onSwapsPage()}
|
disableNetworkIndicator={this.onSwapsPage()}
|
||||||
onClick={async () => {
|
onClick={this.onAppHeaderClick}
|
||||||
if (this.onSwapsPage()) {
|
|
||||||
await prepareToLeaveSwaps();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={
|
disabled={
|
||||||
this.onConfirmPage() ||
|
this.onConfirmPage() ||
|
||||||
(this.onSwapsPage() && !this.onSwapsBuildQuotePage())
|
(this.onSwapsPage() && !this.onSwapsBuildQuotePage())
|
||||||
@ -344,10 +344,7 @@ export default class Routes extends Component {
|
|||||||
{process.env.ONBOARDING_V2 && this.showOnboardingHeader() && (
|
{process.env.ONBOARDING_V2 && this.showOnboardingHeader() && (
|
||||||
<OnboardingAppHeader />
|
<OnboardingAppHeader />
|
||||||
)}
|
)}
|
||||||
<NetworkDropdown
|
<NetworkDropdown />
|
||||||
provider={provider}
|
|
||||||
frequentRpcListDetail={frequentRpcListDetail}
|
|
||||||
/>
|
|
||||||
<AccountMenu />
|
<AccountMenu />
|
||||||
<div className="main-container-wrapper">
|
<div className="main-container-wrapper">
|
||||||
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}
|
{isLoading ? <Loading loadingMessage={loadMessage} /> : null}
|
||||||
@ -377,9 +374,9 @@ export default class Routes extends Component {
|
|||||||
if (loadingMessage) {
|
if (loadingMessage) {
|
||||||
return loadingMessage;
|
return loadingMessage;
|
||||||
}
|
}
|
||||||
const { provider, providerId } = this.props;
|
const { providerType, providerId } = this.props;
|
||||||
|
|
||||||
switch (provider.type) {
|
switch (providerType) {
|
||||||
case 'mainnet':
|
case 'mainnet':
|
||||||
return this.context.t('connectingToMainnet');
|
return this.context.t('connectingToMainnet');
|
||||||
case 'ropsten':
|
case 'ropsten':
|
||||||
@ -394,21 +391,4 @@ export default class Routes extends Component {
|
|||||||
return this.context.t('connectingTo', [providerId]);
|
return this.context.t('connectingTo', [providerId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNetworkName() {
|
|
||||||
switch (this.props.provider.type) {
|
|
||||||
case 'mainnet':
|
|
||||||
return this.context.t('mainnet');
|
|
||||||
case 'ropsten':
|
|
||||||
return this.context.t('ropsten');
|
|
||||||
case 'kovan':
|
|
||||||
return this.context.t('kovan');
|
|
||||||
case 'rinkeby':
|
|
||||||
return this.context.t('rinkeby');
|
|
||||||
case 'goerli':
|
|
||||||
return this.context.t('goerli');
|
|
||||||
default:
|
|
||||||
return this.context.t('unknownNetwork');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
getNetworkIdentifier,
|
getNetworkIdentifier,
|
||||||
getPreferences,
|
getPreferences,
|
||||||
isNetworkLoading,
|
isNetworkLoading,
|
||||||
submittedPendingTransactionsSelector,
|
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import {
|
import {
|
||||||
lockMetamask,
|
lockMetamask,
|
||||||
@ -29,15 +28,14 @@ function mapStateToProps(state) {
|
|||||||
isLoading,
|
isLoading,
|
||||||
loadingMessage,
|
loadingMessage,
|
||||||
isUnlocked: state.metamask.isUnlocked,
|
isUnlocked: state.metamask.isUnlocked,
|
||||||
submittedPendingTransactions: submittedPendingTransactionsSelector(state),
|
|
||||||
isNetworkLoading: isNetworkLoading(state),
|
isNetworkLoading: isNetworkLoading(state),
|
||||||
provider: state.metamask.provider,
|
|
||||||
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
|
|
||||||
currentCurrency: state.metamask.currentCurrency,
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
isMouseUser: state.appState.isMouseUser,
|
isMouseUser: state.appState.isMouseUser,
|
||||||
providerId: getNetworkIdentifier(state),
|
|
||||||
autoLockTimeLimit,
|
autoLockTimeLimit,
|
||||||
browserEnvironment: state.metamask.browserEnvironment,
|
browserEnvironmentOs: state.metamask.browserEnvironment?.os,
|
||||||
|
browserEnvironmentContainter: state.metamask.browserEnvironment?.browser,
|
||||||
|
providerId: getNetworkIdentifier(state),
|
||||||
|
providerType: state.metamask.provider?.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ import {
|
|||||||
navigateBackToBuildQuote,
|
navigateBackToBuildQuote,
|
||||||
prepareForRetryGetQuotes,
|
prepareForRetryGetQuotes,
|
||||||
prepareToLeaveSwaps,
|
prepareToLeaveSwaps,
|
||||||
|
getFromTokenInputValue,
|
||||||
|
getMaxSlippage,
|
||||||
} from '../../../ducks/swaps/swaps';
|
} from '../../../ducks/swaps/swaps';
|
||||||
import Mascot from '../../../components/ui/mascot';
|
import Mascot from '../../../components/ui/mascot';
|
||||||
import Box from '../../../components/ui/box';
|
import Box from '../../../components/ui/box';
|
||||||
@ -58,8 +60,6 @@ export default function AwaitingSwap({
|
|||||||
txHash,
|
txHash,
|
||||||
tokensReceived,
|
tokensReceived,
|
||||||
submittingSwap,
|
submittingSwap,
|
||||||
inputValue,
|
|
||||||
maxSlippage,
|
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
const metaMetricsEvent = useContext(MetaMetricsContext);
|
const metaMetricsEvent = useContext(MetaMetricsContext);
|
||||||
@ -69,6 +69,8 @@ export default function AwaitingSwap({
|
|||||||
|
|
||||||
const fetchParams = useSelector(getFetchParams);
|
const fetchParams = useSelector(getFetchParams);
|
||||||
const { destinationTokenInfo, sourceTokenInfo } = fetchParams?.metaData || {};
|
const { destinationTokenInfo, sourceTokenInfo } = fetchParams?.metaData || {};
|
||||||
|
const fromTokenInputValue = useSelector(getFromTokenInputValue);
|
||||||
|
const maxSlippage = useSelector(getMaxSlippage);
|
||||||
const usedQuote = useSelector(getUsedQuote);
|
const usedQuote = useSelector(getUsedQuote);
|
||||||
const approveTxParams = useSelector(getApproveTxParams);
|
const approveTxParams = useSelector(getApproveTxParams);
|
||||||
const swapsGasPrice = useSelector(getUsedSwapsGasPrice);
|
const swapsGasPrice = useSelector(getUsedSwapsGasPrice);
|
||||||
@ -283,7 +285,7 @@ export default function AwaitingSwap({
|
|||||||
await dispatch(
|
await dispatch(
|
||||||
fetchQuotesAndSetQuoteState(
|
fetchQuotesAndSetQuoteState(
|
||||||
history,
|
history,
|
||||||
inputValue,
|
fromTokenInputValue,
|
||||||
maxSlippage,
|
maxSlippage,
|
||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
),
|
),
|
||||||
@ -321,6 +323,4 @@ AwaitingSwap.propTypes = {
|
|||||||
CONTRACT_DATA_DISABLED_ERROR,
|
CONTRACT_DATA_DISABLED_ERROR,
|
||||||
]),
|
]),
|
||||||
submittingSwap: PropTypes.bool,
|
submittingSwap: PropTypes.bool,
|
||||||
inputValue: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
||||||
maxSlippage: PropTypes.number,
|
|
||||||
};
|
};
|
||||||
|
@ -14,25 +14,20 @@ exports[`BuildQuote renders the component with initial props 1`] = `
|
|||||||
2%
|
2%
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-checked="false"
|
aria-checked="true"
|
||||||
class="button-group__button radio-button"
|
class="button-group__button radio-button button-group__button--active radio-button--active"
|
||||||
data-testid="button-group__button1"
|
data-testid="button-group__button1"
|
||||||
role="radio"
|
role="radio"
|
||||||
>
|
>
|
||||||
3%
|
3%
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
aria-checked="true"
|
aria-checked="false"
|
||||||
class="button-group__button slippage-buttons__button-group-custom-button radio-button--danger radio-button button-group__button--active radio-button--active"
|
class="button-group__button slippage-buttons__button-group-custom-button radio-button"
|
||||||
data-testid="button-group__button2"
|
data-testid="button-group__button2"
|
||||||
role="radio"
|
role="radio"
|
||||||
>
|
>
|
||||||
15
|
custom
|
||||||
<div
|
|
||||||
class="slippage-buttons__percentage-suffix"
|
|
||||||
>
|
|
||||||
%
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useContext, useEffect, useState, useCallback } from 'react';
|
import React, { useContext, useEffect, useState, useCallback } from 'react';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { uniqBy, isEqual } from 'lodash';
|
import { uniqBy, isEqual } from 'lodash';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@ -34,7 +35,15 @@ import {
|
|||||||
getTopAssets,
|
getTopAssets,
|
||||||
getFetchParams,
|
getFetchParams,
|
||||||
getQuotes,
|
getQuotes,
|
||||||
|
setBalanceError,
|
||||||
|
setFromTokenInputValue,
|
||||||
|
setFromTokenError,
|
||||||
|
setMaxSlippage,
|
||||||
setReviewSwapClickedTimestamp,
|
setReviewSwapClickedTimestamp,
|
||||||
|
getFromTokenInputValue,
|
||||||
|
getFromTokenError,
|
||||||
|
getMaxSlippage,
|
||||||
|
getIsFeatureFlagLoaded,
|
||||||
} from '../../../ducks/swaps/swaps';
|
} from '../../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
getSwapsDefaultToken,
|
getSwapsDefaultToken,
|
||||||
@ -77,6 +86,7 @@ import {
|
|||||||
stopPollingForQuotes,
|
stopPollingForQuotes,
|
||||||
} from '../../../store/actions';
|
} from '../../../store/actions';
|
||||||
import {
|
import {
|
||||||
|
countDecimals,
|
||||||
fetchTokenPrice,
|
fetchTokenPrice,
|
||||||
fetchTokenBalance,
|
fetchTokenBalance,
|
||||||
shouldEnableDirectWrapping,
|
shouldEnableDirectWrapping,
|
||||||
@ -94,14 +104,8 @@ const MAX_ALLOWED_SLIPPAGE = 15;
|
|||||||
let timeoutIdForQuotesPrefetching;
|
let timeoutIdForQuotesPrefetching;
|
||||||
|
|
||||||
export default function BuildQuote({
|
export default function BuildQuote({
|
||||||
inputValue,
|
|
||||||
onInputChange,
|
|
||||||
ethBalance,
|
ethBalance,
|
||||||
setMaxSlippage,
|
|
||||||
maxSlippage,
|
|
||||||
selectedAccountAddress,
|
selectedAccountAddress,
|
||||||
isFeatureFlagLoaded,
|
|
||||||
tokenFromError,
|
|
||||||
shuffledTokensList,
|
shuffledTokensList,
|
||||||
}) {
|
}) {
|
||||||
const t = useContext(I18nContext);
|
const t = useContext(I18nContext);
|
||||||
@ -114,18 +118,22 @@ export default function BuildQuote({
|
|||||||
);
|
);
|
||||||
const [verificationClicked, setVerificationClicked] = useState(false);
|
const [verificationClicked, setVerificationClicked] = useState(false);
|
||||||
|
|
||||||
|
const isFeatureFlagLoaded = useSelector(getIsFeatureFlagLoaded);
|
||||||
const balanceError = useSelector(getBalanceError);
|
const balanceError = useSelector(getBalanceError);
|
||||||
const fetchParams = useSelector(getFetchParams);
|
const fetchParams = useSelector(getFetchParams, isEqual);
|
||||||
const { sourceTokenInfo = {}, destinationTokenInfo = {} } =
|
const { sourceTokenInfo = {}, destinationTokenInfo = {} } =
|
||||||
fetchParams?.metaData || {};
|
fetchParams?.metaData || {};
|
||||||
const tokens = useSelector(getTokens);
|
const tokens = useSelector(getTokens, isEqual);
|
||||||
const topAssets = useSelector(getTopAssets);
|
const topAssets = useSelector(getTopAssets);
|
||||||
const fromToken = useSelector(getFromToken);
|
const fromToken = useSelector(getFromToken, isEqual);
|
||||||
|
const fromTokenInputValue = useSelector(getFromTokenInputValue);
|
||||||
|
const fromTokenError = useSelector(getFromTokenError);
|
||||||
|
const maxSlippage = useSelector(getMaxSlippage);
|
||||||
const toToken = useSelector(getToToken) || destinationTokenInfo;
|
const toToken = useSelector(getToToken) || destinationTokenInfo;
|
||||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
const defaultSwapsToken = useSelector(getSwapsDefaultToken, isEqual);
|
||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual);
|
||||||
const tokenList = useSelector(getTokenList);
|
const tokenList = useSelector(getTokenList, isEqual);
|
||||||
const useTokenDetection = useSelector(getUseTokenDetection);
|
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||||
const quotes = useSelector(getQuotes, isEqual);
|
const quotes = useSelector(getQuotes, isEqual);
|
||||||
const areQuotesPresent = Object.keys(quotes).length > 0;
|
const areQuotesPresent = Object.keys(quotes).length > 0;
|
||||||
@ -198,7 +206,7 @@ export default function BuildQuote({
|
|||||||
|
|
||||||
const swapFromTokenFiatValue = useTokenFiatAmount(
|
const swapFromTokenFiatValue = useTokenFiatAmount(
|
||||||
fromTokenAddress,
|
fromTokenAddress,
|
||||||
inputValue || 0,
|
fromTokenInputValue || 0,
|
||||||
fromTokenSymbol,
|
fromTokenSymbol,
|
||||||
{
|
{
|
||||||
showFiat: true,
|
showFiat: true,
|
||||||
@ -206,7 +214,7 @@ export default function BuildQuote({
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
const swapFromEthFiatValue = useEthFiatAmount(
|
const swapFromEthFiatValue = useEthFiatAmount(
|
||||||
inputValue || 0,
|
fromTokenInputValue || 0,
|
||||||
{ showFiat: true },
|
{ showFiat: true },
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
@ -214,6 +222,27 @@ export default function BuildQuote({
|
|||||||
? swapFromEthFiatValue
|
? swapFromEthFiatValue
|
||||||
: swapFromTokenFiatValue;
|
: swapFromTokenFiatValue;
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
(newInputValue, balance) => {
|
||||||
|
dispatch(setFromTokenInputValue(newInputValue));
|
||||||
|
const newBalanceError = new BigNumber(newInputValue || 0).gt(
|
||||||
|
balance || 0,
|
||||||
|
);
|
||||||
|
// "setBalanceError" is just a warning, a user can still click on the "Review Swap" button.
|
||||||
|
if (balanceError !== newBalanceError) {
|
||||||
|
dispatch(setBalanceError(newBalanceError));
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
setFromTokenError(
|
||||||
|
fromToken && countDecimals(newInputValue) > fromToken.decimals
|
||||||
|
? 'tooManyDecimals'
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, fromToken, balanceError],
|
||||||
|
);
|
||||||
|
|
||||||
const onFromSelect = (token) => {
|
const onFromSelect = (token) => {
|
||||||
if (
|
if (
|
||||||
token?.address &&
|
token?.address &&
|
||||||
@ -255,7 +284,7 @@ export default function BuildQuote({
|
|||||||
}
|
}
|
||||||
dispatch(setSwapsFromToken(token));
|
dispatch(setSwapsFromToken(token));
|
||||||
onInputChange(
|
onInputChange(
|
||||||
token?.address ? inputValue : '',
|
token?.address ? fromTokenInputValue : '',
|
||||||
token.string,
|
token.string,
|
||||||
token.decimals,
|
token.decimals,
|
||||||
);
|
);
|
||||||
@ -364,9 +393,14 @@ export default function BuildQuote({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prevFromTokenBalance !== fromTokenBalance) {
|
if (prevFromTokenBalance !== fromTokenBalance) {
|
||||||
onInputChange(inputValue, fromTokenBalance);
|
onInputChange(fromTokenInputValue, fromTokenBalance);
|
||||||
}
|
}
|
||||||
}, [onInputChange, prevFromTokenBalance, inputValue, fromTokenBalance]);
|
}, [
|
||||||
|
onInputChange,
|
||||||
|
prevFromTokenBalance,
|
||||||
|
fromTokenInputValue,
|
||||||
|
fromTokenBalance,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(resetSwapsPostFetchState());
|
dispatch(resetSwapsPostFetchState());
|
||||||
@ -416,9 +450,9 @@ export default function BuildQuote({
|
|||||||
selectedToToken.address,
|
selectedToToken.address,
|
||||||
);
|
);
|
||||||
const isReviewSwapButtonDisabled =
|
const isReviewSwapButtonDisabled =
|
||||||
tokenFromError ||
|
fromTokenError ||
|
||||||
!isFeatureFlagLoaded ||
|
!isFeatureFlagLoaded ||
|
||||||
!Number(inputValue) ||
|
!Number(fromTokenInputValue) ||
|
||||||
!selectedToToken?.address ||
|
!selectedToToken?.address ||
|
||||||
Number(maxSlippage) < 0 ||
|
Number(maxSlippage) < 0 ||
|
||||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||||
@ -433,7 +467,7 @@ export default function BuildQuote({
|
|||||||
await dispatch(
|
await dispatch(
|
||||||
fetchQuotesAndSetQuoteState(
|
fetchQuotesAndSetQuoteState(
|
||||||
history,
|
history,
|
||||||
inputValue,
|
fromTokenInputValue,
|
||||||
maxSlippage,
|
maxSlippage,
|
||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
pageRedirectionDisabled,
|
pageRedirectionDisabled,
|
||||||
@ -456,7 +490,7 @@ export default function BuildQuote({
|
|||||||
maxSlippage,
|
maxSlippage,
|
||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
isReviewSwapButtonDisabled,
|
isReviewSwapButtonDisabled,
|
||||||
inputValue,
|
fromTokenInputValue,
|
||||||
fromTokenAddress,
|
fromTokenAddress,
|
||||||
toTokenAddress,
|
toTokenAddress,
|
||||||
]);
|
]);
|
||||||
@ -484,8 +518,8 @@ export default function BuildQuote({
|
|||||||
onInputChange={(value) => {
|
onInputChange={(value) => {
|
||||||
onInputChange(value, fromTokenBalance);
|
onInputChange(value, fromTokenBalance);
|
||||||
}}
|
}}
|
||||||
inputValue={inputValue}
|
inputValue={fromTokenInputValue}
|
||||||
leftValue={inputValue && swapFromFiatValue}
|
leftValue={fromTokenInputValue && swapFromFiatValue}
|
||||||
selectedItem={selectedFromToken}
|
selectedItem={selectedFromToken}
|
||||||
maxListItems={30}
|
maxListItems={30}
|
||||||
loading={
|
loading={
|
||||||
@ -504,14 +538,14 @@ export default function BuildQuote({
|
|||||||
<div
|
<div
|
||||||
className={classnames('build-quote__balance-message', {
|
className={classnames('build-quote__balance-message', {
|
||||||
'build-quote__balance-message--error':
|
'build-quote__balance-message--error':
|
||||||
balanceError || tokenFromError,
|
balanceError || fromTokenError,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!tokenFromError &&
|
{!fromTokenError &&
|
||||||
!balanceError &&
|
!balanceError &&
|
||||||
fromTokenSymbol &&
|
fromTokenSymbol &&
|
||||||
swapYourTokenBalance}
|
swapYourTokenBalance}
|
||||||
{!tokenFromError && balanceError && fromTokenSymbol && (
|
{!fromTokenError && balanceError && fromTokenSymbol && (
|
||||||
<div className="build-quite__insufficient-funds">
|
<div className="build-quite__insufficient-funds">
|
||||||
<div className="build-quite__insufficient-funds-first">
|
<div className="build-quite__insufficient-funds-first">
|
||||||
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
|
{t('swapsNotEnoughForTx', [fromTokenSymbol])}
|
||||||
@ -521,7 +555,7 @@ export default function BuildQuote({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{tokenFromError && (
|
{fromTokenError && (
|
||||||
<>
|
<>
|
||||||
<div className="build-quote__form-error">
|
<div className="build-quote__form-error">
|
||||||
{t('swapTooManyDecimalsError', [
|
{t('swapTooManyDecimalsError', [
|
||||||
@ -645,7 +679,7 @@ export default function BuildQuote({
|
|||||||
<div className="build-quote__slippage-buttons-container">
|
<div className="build-quote__slippage-buttons-container">
|
||||||
<SlippageButtons
|
<SlippageButtons
|
||||||
onSelect={(newSlippage) => {
|
onSelect={(newSlippage) => {
|
||||||
setMaxSlippage(newSlippage);
|
dispatch(setMaxSlippage(newSlippage));
|
||||||
}}
|
}}
|
||||||
maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE}
|
maxAllowedSlippage={MAX_ALLOWED_SLIPPAGE}
|
||||||
currentSlippage={maxSlippage}
|
currentSlippage={maxSlippage}
|
||||||
@ -664,7 +698,7 @@ export default function BuildQuote({
|
|||||||
dispatch(
|
dispatch(
|
||||||
fetchQuotesAndSetQuoteState(
|
fetchQuotesAndSetQuoteState(
|
||||||
history,
|
history,
|
||||||
inputValue,
|
fromTokenInputValue,
|
||||||
maxSlippage,
|
maxSlippage,
|
||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
),
|
),
|
||||||
@ -688,13 +722,7 @@ export default function BuildQuote({
|
|||||||
}
|
}
|
||||||
|
|
||||||
BuildQuote.propTypes = {
|
BuildQuote.propTypes = {
|
||||||
maxSlippage: PropTypes.number,
|
|
||||||
inputValue: PropTypes.string,
|
|
||||||
onInputChange: PropTypes.func,
|
|
||||||
ethBalance: PropTypes.string,
|
ethBalance: PropTypes.string,
|
||||||
setMaxSlippage: PropTypes.func,
|
|
||||||
selectedAccountAddress: PropTypes.string,
|
selectedAccountAddress: PropTypes.string,
|
||||||
isFeatureFlagLoaded: PropTypes.bool.isRequired,
|
|
||||||
tokenFromError: PropTypes.string,
|
|
||||||
shuffledTokensList: PropTypes.array,
|
shuffledTokensList: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import configureMockStore from 'redux-mock-store';
|
import configureMockStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
import { fireEvent } from '@testing-library/react';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderWithProvider,
|
renderWithProvider,
|
||||||
@ -13,11 +12,7 @@ import BuildQuote from '.';
|
|||||||
const middleware = [thunk];
|
const middleware = [thunk];
|
||||||
const createProps = (customProps = {}) => {
|
const createProps = (customProps = {}) => {
|
||||||
return {
|
return {
|
||||||
inputValue: '5',
|
|
||||||
onInputChange: jest.fn(),
|
|
||||||
ethBalance: '0x8',
|
ethBalance: '0x8',
|
||||||
setMaxSlippage: jest.fn(),
|
|
||||||
maxSlippage: 15,
|
|
||||||
selectedAccountAddress: 'selectedAccountAddress',
|
selectedAccountAddress: 'selectedAccountAddress',
|
||||||
isFeatureFlagLoaded: false,
|
isFeatureFlagLoaded: false,
|
||||||
shuffledTokensList: [],
|
shuffledTokensList: [],
|
||||||
@ -49,28 +44,4 @@ describe('BuildQuote', () => {
|
|||||||
document.querySelector('.slippage-buttons__button-group'),
|
document.querySelector('.slippage-buttons__button-group'),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clicks on the max button', () => {
|
|
||||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
|
||||||
const props = createProps();
|
|
||||||
const { getByTestId } = renderWithProvider(
|
|
||||||
<BuildQuote {...props} />,
|
|
||||||
store,
|
|
||||||
);
|
|
||||||
fireEvent.click(getByTestId('build-quote__max-button'));
|
|
||||||
expect(props.onInputChange).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('types a number inside the input field', () => {
|
|
||||||
const store = configureMockStore(middleware)(createSwapsMockStore());
|
|
||||||
const props = createProps();
|
|
||||||
const { getByDisplayValue } = renderWithProvider(
|
|
||||||
<BuildQuote {...props} />,
|
|
||||||
store,
|
|
||||||
);
|
|
||||||
fireEvent.change(getByDisplayValue('5'), {
|
|
||||||
target: { value: '8' },
|
|
||||||
});
|
|
||||||
expect(props.onInputChange).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef, useContext } from 'react';
|
import React, { useEffect, useRef, useContext } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
Switch,
|
Switch,
|
||||||
Route,
|
Route,
|
||||||
@ -7,8 +7,7 @@ import {
|
|||||||
useHistory,
|
useHistory,
|
||||||
Redirect,
|
Redirect,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import BigNumber from 'bignumber.js';
|
import { shuffle, isEqual } from 'lodash';
|
||||||
import { shuffle } from 'lodash';
|
|
||||||
import { I18nContext } from '../../contexts/i18n';
|
import { I18nContext } from '../../contexts/i18n';
|
||||||
import {
|
import {
|
||||||
getSelectedAccount,
|
getSelectedAccount,
|
||||||
@ -24,7 +23,6 @@ import {
|
|||||||
getTradeTxId,
|
getTradeTxId,
|
||||||
getApproveTxId,
|
getApproveTxId,
|
||||||
getFetchingQuotes,
|
getFetchingQuotes,
|
||||||
setBalanceError,
|
|
||||||
setTopAssets,
|
setTopAssets,
|
||||||
getFetchParams,
|
getFetchParams,
|
||||||
setAggregatorMetadata,
|
setAggregatorMetadata,
|
||||||
@ -35,7 +33,6 @@ import {
|
|||||||
prepareToLeaveSwaps,
|
prepareToLeaveSwaps,
|
||||||
fetchAndSetSwapsGasPriceInfo,
|
fetchAndSetSwapsGasPriceInfo,
|
||||||
fetchSwapsLiveness,
|
fetchSwapsLiveness,
|
||||||
getFromToken,
|
|
||||||
getReviewSwapClickedTimestamp,
|
getReviewSwapClickedTimestamp,
|
||||||
} from '../../ducks/swaps/swaps';
|
} from '../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
@ -77,7 +74,6 @@ import {
|
|||||||
fetchTopAssets,
|
fetchTopAssets,
|
||||||
getSwapsTokensReceivedFromTxMeta,
|
getSwapsTokensReceivedFromTxMeta,
|
||||||
fetchAggregatorMetadata,
|
fetchAggregatorMetadata,
|
||||||
countDecimals,
|
|
||||||
} from './swaps.util';
|
} from './swaps.util';
|
||||||
import AwaitingSignatures from './awaiting-signatures';
|
import AwaitingSignatures from './awaiting-signatures';
|
||||||
import AwaitingSwap from './awaiting-swap';
|
import AwaitingSwap from './awaiting-swap';
|
||||||
@ -96,21 +92,16 @@ export default function Swap() {
|
|||||||
const isSwapsErrorRoute = pathname === SWAPS_ERROR_ROUTE;
|
const isSwapsErrorRoute = pathname === SWAPS_ERROR_ROUTE;
|
||||||
const isLoadingQuotesRoute = pathname === LOADING_QUOTES_ROUTE;
|
const isLoadingQuotesRoute = pathname === LOADING_QUOTES_ROUTE;
|
||||||
|
|
||||||
const fetchParams = useSelector(getFetchParams);
|
const fetchParams = useSelector(getFetchParams, isEqual);
|
||||||
const { destinationTokenInfo = {} } = fetchParams?.metaData || {};
|
const { destinationTokenInfo = {} } = fetchParams?.metaData || {};
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState(fetchParams?.value || '');
|
|
||||||
const [maxSlippage, setMaxSlippage] = useState(fetchParams?.slippage || 3);
|
|
||||||
const [isFeatureFlagLoaded, setIsFeatureFlagLoaded] = useState(false);
|
|
||||||
const [tokenFromError, setTokenFromError] = useState(null);
|
|
||||||
|
|
||||||
const routeState = useSelector(getBackgroundSwapRouteState);
|
const routeState = useSelector(getBackgroundSwapRouteState);
|
||||||
const selectedAccount = useSelector(getSelectedAccount);
|
const selectedAccount = useSelector(getSelectedAccount, shallowEqual);
|
||||||
const quotes = useSelector(getQuotes);
|
const quotes = useSelector(getQuotes, isEqual);
|
||||||
const txList = useSelector(currentNetworkTxListSelector);
|
const txList = useSelector(currentNetworkTxListSelector, shallowEqual);
|
||||||
const tradeTxId = useSelector(getTradeTxId);
|
const tradeTxId = useSelector(getTradeTxId);
|
||||||
const approveTxId = useSelector(getApproveTxId);
|
const approveTxId = useSelector(getApproveTxId);
|
||||||
const aggregatorMetadata = useSelector(getAggregatorMetadata);
|
const aggregatorMetadata = useSelector(getAggregatorMetadata, shallowEqual);
|
||||||
const fetchingQuotes = useSelector(getFetchingQuotes);
|
const fetchingQuotes = useSelector(getFetchingQuotes);
|
||||||
let swapsErrorKey = useSelector(getSwapsErrorKey);
|
let swapsErrorKey = useSelector(getSwapsErrorKey);
|
||||||
const swapsEnabled = useSelector(getSwapsFeatureIsLive);
|
const swapsEnabled = useSelector(getSwapsFeatureIsLive);
|
||||||
@ -119,8 +110,7 @@ export default function Swap() {
|
|||||||
const networkAndAccountSupports1559 = useSelector(
|
const networkAndAccountSupports1559 = useSelector(
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
);
|
);
|
||||||
const fromToken = useSelector(getFromToken);
|
const tokenList = useSelector(getTokenList, isEqual);
|
||||||
const tokenList = useSelector(getTokenList);
|
|
||||||
const listTokenValues = shuffle(Object.values(tokenList));
|
const listTokenValues = shuffle(Object.values(tokenList));
|
||||||
const reviewSwapClickedTimestamp = useSelector(getReviewSwapClickedTimestamp);
|
const reviewSwapClickedTimestamp = useSelector(getReviewSwapClickedTimestamp);
|
||||||
const reviewSwapClicked = Boolean(reviewSwapClickedTimestamp);
|
const reviewSwapClicked = Boolean(reviewSwapClickedTimestamp);
|
||||||
@ -237,7 +227,6 @@ export default function Swap() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSwapsLivenessWrapper = async () => {
|
const fetchSwapsLivenessWrapper = async () => {
|
||||||
await dispatch(fetchSwapsLiveness());
|
await dispatch(fetchSwapsLiveness());
|
||||||
setIsFeatureFlagLoaded(true);
|
|
||||||
};
|
};
|
||||||
fetchSwapsLivenessWrapper();
|
fetchSwapsLivenessWrapper();
|
||||||
return () => {
|
return () => {
|
||||||
@ -308,31 +297,10 @@ export default function Swap() {
|
|||||||
return <Redirect to={{ pathname: LOADING_QUOTES_ROUTE }} />;
|
return <Redirect to={{ pathname: LOADING_QUOTES_ROUTE }} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInputChange = (newInputValue, balance) => {
|
|
||||||
setInputValue(newInputValue);
|
|
||||||
const balanceError = new BigNumber(newInputValue || 0).gt(
|
|
||||||
balance || 0,
|
|
||||||
);
|
|
||||||
// "setBalanceError" is just a warning, a user can still click on the "Review Swap" button.
|
|
||||||
dispatch(setBalanceError(balanceError));
|
|
||||||
setTokenFromError(
|
|
||||||
fromToken &&
|
|
||||||
countDecimals(newInputValue) > fromToken.decimals
|
|
||||||
? 'tooManyDecimals'
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BuildQuote
|
<BuildQuote
|
||||||
inputValue={inputValue}
|
|
||||||
onInputChange={onInputChange}
|
|
||||||
ethBalance={ethBalance}
|
ethBalance={ethBalance}
|
||||||
setMaxSlippage={setMaxSlippage}
|
|
||||||
selectedAccountAddress={selectedAccountAddress}
|
selectedAccountAddress={selectedAccountAddress}
|
||||||
maxSlippage={maxSlippage}
|
|
||||||
isFeatureFlagLoaded={isFeatureFlagLoaded}
|
|
||||||
tokenFromError={tokenFromError}
|
|
||||||
shuffledTokensList={listTokenValues}
|
shuffledTokensList={listTokenValues}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -364,8 +332,6 @@ export default function Swap() {
|
|||||||
swapComplete={false}
|
swapComplete={false}
|
||||||
errorKey={swapsErrorKey}
|
errorKey={swapsErrorKey}
|
||||||
txHash={tradeTxData?.hash}
|
txHash={tradeTxData?.hash}
|
||||||
inputValue={inputValue}
|
|
||||||
maxSlippage={maxSlippage}
|
|
||||||
submittedTime={tradeTxData?.submittedTime}
|
submittedTime={tradeTxData?.submittedTime}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -433,8 +399,6 @@ export default function Swap() {
|
|||||||
submittingSwap={
|
submittingSwap={
|
||||||
routeState === 'awaiting' && !(approveTxId || tradeTxId)
|
routeState === 'awaiting' && !(approveTxId || tradeTxId)
|
||||||
}
|
}
|
||||||
inputValue={inputValue}
|
|
||||||
maxSlippage={maxSlippage}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Redirect to={{ pathname: DEFAULT_ROUTE }} />
|
<Redirect to={{ pathname: DEFAULT_ROUTE }} />
|
||||||
|
Loading…
Reference in New Issue
Block a user