mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
1304ec7af5
We want to convert NetworkController to TypeScript in order to be able to compare differences in the controller between in this repo and the core repo. To do this, however, we need to convert the dependencies of the controller to TypeScript. As a part of this effort, this commit converts `shared/constants/metametrics` to TypeScript. Note that simple objects have been largely replaced with enums. There are some cases where I even split up some of these objects into multiple enums. Co-authored-by: Mark Stacey <markjstacey@gmail.com>
340 lines
11 KiB
JavaScript
340 lines
11 KiB
JavaScript
import EventEmitter from 'events';
|
|
import React, { useContext, useRef, useState, useEffect } from 'react';
|
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|
import PropTypes from 'prop-types';
|
|
import { useHistory } from 'react-router-dom';
|
|
import isEqual from 'lodash/isEqual';
|
|
import { getBlockExplorerLink } from '@metamask/etherscan-link';
|
|
import { I18nContext } from '../../../contexts/i18n';
|
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
|
import {
|
|
MetaMetricsContextProp,
|
|
MetaMetricsEventCategory,
|
|
MetaMetricsEventName,
|
|
} from '../../../../shared/constants/metametrics';
|
|
|
|
import {
|
|
getCurrentChainId,
|
|
getCurrentCurrency,
|
|
getRpcPrefsForCurrentProvider,
|
|
getUSDConversionRate,
|
|
isHardwareWallet,
|
|
getHardwareWalletType,
|
|
getFullTxData,
|
|
} from '../../../selectors';
|
|
|
|
import {
|
|
getUsedQuote,
|
|
getFetchParams,
|
|
getApproveTxParams,
|
|
getUsedSwapsGasPrice,
|
|
fetchQuotesAndSetQuoteState,
|
|
navigateBackToBuildQuote,
|
|
prepareForRetryGetQuotes,
|
|
prepareToLeaveSwaps,
|
|
getSmartTransactionsOptInStatus,
|
|
getSmartTransactionsEnabled,
|
|
getCurrentSmartTransactionsEnabled,
|
|
getFromTokenInputValue,
|
|
getMaxSlippage,
|
|
} from '../../../ducks/swaps/swaps';
|
|
import Mascot from '../../../components/ui/mascot';
|
|
import {
|
|
QUOTES_EXPIRED_ERROR,
|
|
SWAP_FAILED_ERROR,
|
|
ERROR_FETCHING_QUOTES,
|
|
QUOTES_NOT_AVAILABLE_ERROR,
|
|
CONTRACT_DATA_DISABLED_ERROR,
|
|
OFFLINE_FOR_MAINTENANCE,
|
|
SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP,
|
|
} from '../../../../shared/constants/swaps';
|
|
import { isSwapsDefaultTokenSymbol } from '../../../../shared/modules/swaps.utils';
|
|
import PulseLoader from '../../../components/ui/pulse-loader';
|
|
|
|
import { DEFAULT_ROUTE } from '../../../helpers/constants/routes';
|
|
import {
|
|
stopPollingForQuotes,
|
|
setDefaultHomeActiveTabName,
|
|
} from '../../../store/actions';
|
|
|
|
import { getRenderableNetworkFeesForQuote } from '../swaps.util';
|
|
import SwapsFooter from '../swaps-footer';
|
|
|
|
import CreateNewSwap from '../create-new-swap';
|
|
import ViewOnBlockExplorer from '../view-on-block-explorer';
|
|
import { SUPPORT_LINK } from '../../../../shared/lib/ui-utils';
|
|
import SwapFailureIcon from './swap-failure-icon';
|
|
import SwapSuccessIcon from './swap-success-icon';
|
|
import QuotesTimeoutIcon from './quotes-timeout-icon';
|
|
|
|
export default function AwaitingSwap({
|
|
swapComplete,
|
|
errorKey,
|
|
txHash,
|
|
tokensReceived,
|
|
submittingSwap,
|
|
txId,
|
|
}) {
|
|
const t = useContext(I18nContext);
|
|
const trackEvent = useContext(MetaMetricsContext);
|
|
const history = useHistory();
|
|
const dispatch = useDispatch();
|
|
const animationEventEmitter = useRef(new EventEmitter());
|
|
const { swapMetaData } =
|
|
useSelector((state) => getFullTxData(state, txId)) || {};
|
|
const fetchParams = useSelector(getFetchParams, isEqual);
|
|
const fromTokenInputValue = useSelector(getFromTokenInputValue);
|
|
const maxSlippage = useSelector(getMaxSlippage);
|
|
const usedQuote = useSelector(getUsedQuote, isEqual);
|
|
const approveTxParams = useSelector(getApproveTxParams, shallowEqual);
|
|
const swapsGasPrice = useSelector(getUsedSwapsGasPrice);
|
|
const currentCurrency = useSelector(getCurrentCurrency);
|
|
const usdConversionRate = useSelector(getUSDConversionRate);
|
|
const chainId = useSelector(getCurrentChainId);
|
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual);
|
|
const [trackedQuotesExpiredEvent, setTrackedQuotesExpiredEvent] =
|
|
useState(false);
|
|
|
|
let feeinUnformattedFiat;
|
|
|
|
if (usedQuote && swapsGasPrice) {
|
|
const renderableNetworkFees = getRenderableNetworkFeesForQuote({
|
|
tradeGas: usedQuote.gasEstimateWithRefund || usedQuote.averageGas,
|
|
approveGas: approveTxParams?.gas || '0x0',
|
|
gasPrice: swapsGasPrice,
|
|
currentCurrency,
|
|
conversionRate: usdConversionRate,
|
|
tradeValue: usedQuote?.trade?.value,
|
|
sourceSymbol: swapMetaData?.token_from,
|
|
sourceAmount: usedQuote.sourceAmount,
|
|
chainId,
|
|
});
|
|
feeinUnformattedFiat = renderableNetworkFees.rawNetworkFees;
|
|
}
|
|
|
|
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
|
const hardwareWalletType = useSelector(getHardwareWalletType);
|
|
const smartTransactionsOptInStatus = useSelector(
|
|
getSmartTransactionsOptInStatus,
|
|
);
|
|
const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled);
|
|
const currentSmartTransactionsEnabled = useSelector(
|
|
getCurrentSmartTransactionsEnabled,
|
|
);
|
|
const sensitiveProperties = {
|
|
token_from: swapMetaData?.token_from,
|
|
token_from_amount: swapMetaData?.token_from_amount,
|
|
token_to: swapMetaData?.token_to,
|
|
request_type: fetchParams?.balanceError ? 'Quote' : 'Order',
|
|
slippage: swapMetaData?.slippage,
|
|
custom_slippage: swapMetaData?.slippage === 2,
|
|
gas_fees: feeinUnformattedFiat,
|
|
is_hardware_wallet: hardwareWalletUsed,
|
|
hardware_wallet_type: hardwareWalletType,
|
|
stx_enabled: smartTransactionsEnabled,
|
|
current_stx_enabled: currentSmartTransactionsEnabled,
|
|
stx_user_opt_in: smartTransactionsOptInStatus,
|
|
};
|
|
|
|
const baseNetworkUrl =
|
|
rpcPrefs.blockExplorerUrl ??
|
|
SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ??
|
|
null;
|
|
const blockExplorerUrl = getBlockExplorerLink(
|
|
{ hash: txHash, chainId },
|
|
{ blockExplorerUrl: baseNetworkUrl },
|
|
);
|
|
|
|
let headerText;
|
|
let statusImage;
|
|
let descriptionText;
|
|
let submitText;
|
|
let content;
|
|
|
|
if (errorKey === OFFLINE_FOR_MAINTENANCE) {
|
|
headerText = t('offlineForMaintenance');
|
|
descriptionText = t('metamaskSwapsOfflineDescription');
|
|
submitText = t('close');
|
|
statusImage = <SwapFailureIcon />;
|
|
} else if (errorKey === SWAP_FAILED_ERROR) {
|
|
headerText = t('swapFailedErrorTitle');
|
|
descriptionText = t('swapFailedErrorDescriptionWithSupportLink', [
|
|
<a
|
|
className="awaiting-swap__support-link"
|
|
key="awaiting-swap-support-link"
|
|
href={SUPPORT_LINK}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
onClick={() => {
|
|
trackEvent(
|
|
{
|
|
category: MetaMetricsEventCategory.Swaps,
|
|
event: MetaMetricsEventName.SupportLinkClicked,
|
|
properties: {
|
|
url: SUPPORT_LINK,
|
|
},
|
|
},
|
|
{
|
|
contextPropsIntoEventProperties: [
|
|
MetaMetricsContextProp.PageTitle,
|
|
],
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
{new URL(SUPPORT_LINK).hostname}
|
|
</a>,
|
|
]);
|
|
submitText = t('tryAgain');
|
|
statusImage = <SwapFailureIcon />;
|
|
content = blockExplorerUrl && (
|
|
<ViewOnBlockExplorer
|
|
blockExplorerUrl={blockExplorerUrl}
|
|
sensitiveTrackingProperties={sensitiveProperties}
|
|
/>
|
|
);
|
|
} else if (errorKey === QUOTES_EXPIRED_ERROR) {
|
|
headerText = t('swapQuotesExpiredErrorTitle');
|
|
descriptionText = t('swapQuotesExpiredErrorDescription');
|
|
submitText = t('tryAgain');
|
|
statusImage = <QuotesTimeoutIcon />;
|
|
|
|
if (!trackedQuotesExpiredEvent) {
|
|
setTrackedQuotesExpiredEvent(true);
|
|
trackEvent({
|
|
event: 'Quotes Timed Out',
|
|
category: MetaMetricsEventCategory.Swaps,
|
|
sensitiveProperties,
|
|
});
|
|
}
|
|
} else if (errorKey === ERROR_FETCHING_QUOTES) {
|
|
headerText = t('swapFetchingQuotesErrorTitle');
|
|
descriptionText = t('swapFetchingQuotesErrorDescription');
|
|
submitText = t('back');
|
|
statusImage = <SwapFailureIcon />;
|
|
} else if (errorKey === QUOTES_NOT_AVAILABLE_ERROR) {
|
|
headerText = t('swapQuotesNotAvailableErrorTitle');
|
|
descriptionText = t('swapQuotesNotAvailableErrorDescription');
|
|
submitText = t('tryAgain');
|
|
statusImage = <SwapFailureIcon />;
|
|
} else if (errorKey === CONTRACT_DATA_DISABLED_ERROR) {
|
|
headerText = t('swapContractDataDisabledErrorTitle');
|
|
descriptionText = t('swapContractDataDisabledErrorDescription');
|
|
submitText = t('tryAgain');
|
|
statusImage = <SwapFailureIcon />;
|
|
} else if (!errorKey && !swapComplete) {
|
|
headerText = t('swapProcessing');
|
|
statusImage = <PulseLoader />;
|
|
submitText = t('swapsViewInActivity');
|
|
descriptionText = t('swapOnceTransactionHasProcess', [
|
|
<span
|
|
key="swapOnceTransactionHasProcess-1"
|
|
className="awaiting-swap__amount-and-symbol"
|
|
>
|
|
{swapMetaData?.token_to}
|
|
</span>,
|
|
]);
|
|
content = blockExplorerUrl && (
|
|
<ViewOnBlockExplorer
|
|
blockExplorerUrl={blockExplorerUrl}
|
|
sensitiveTrackingProperties={sensitiveProperties}
|
|
/>
|
|
);
|
|
} else if (!errorKey && swapComplete) {
|
|
headerText = t('swapTransactionComplete');
|
|
statusImage = <SwapSuccessIcon />;
|
|
submitText = t('close');
|
|
descriptionText = t('swapTokenAvailable', [
|
|
<span
|
|
key="swapTokenAvailable-2"
|
|
className="awaiting-swap__amount-and-symbol"
|
|
>
|
|
{`${tokensReceived || ''} ${swapMetaData?.token_to}`}
|
|
</span>,
|
|
]);
|
|
content = blockExplorerUrl && (
|
|
<ViewOnBlockExplorer
|
|
blockExplorerUrl={blockExplorerUrl}
|
|
sensitiveTrackingProperties={sensitiveProperties}
|
|
/>
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (errorKey) {
|
|
// If there was an error, stop polling for quotes.
|
|
dispatch(stopPollingForQuotes());
|
|
}
|
|
}, [dispatch, errorKey]);
|
|
|
|
return (
|
|
<div className="awaiting-swap">
|
|
<div className="awaiting-swap__content">
|
|
{!(swapComplete || errorKey) && (
|
|
<Mascot
|
|
animationEventEmitter={animationEventEmitter.current}
|
|
width="90"
|
|
height="90"
|
|
/>
|
|
)}
|
|
<div className="awaiting-swap__status-image">{statusImage}</div>
|
|
<div className="awaiting-swap__header">{headerText}</div>
|
|
<div className="awaiting-swap__main-description">{descriptionText}</div>
|
|
{content}
|
|
</div>
|
|
{!errorKey && swapComplete ? (
|
|
<CreateNewSwap sensitiveTrackingProperties={sensitiveProperties} />
|
|
) : null}
|
|
<SwapsFooter
|
|
onSubmit={async () => {
|
|
/* istanbul ignore next */
|
|
if (errorKey === OFFLINE_FOR_MAINTENANCE) {
|
|
await dispatch(prepareToLeaveSwaps());
|
|
history.push(DEFAULT_ROUTE);
|
|
} else if (errorKey === QUOTES_EXPIRED_ERROR) {
|
|
dispatch(prepareForRetryGetQuotes());
|
|
await dispatch(
|
|
fetchQuotesAndSetQuoteState(
|
|
history,
|
|
fromTokenInputValue,
|
|
maxSlippage,
|
|
trackEvent,
|
|
),
|
|
);
|
|
} else if (errorKey) {
|
|
await dispatch(navigateBackToBuildQuote(history));
|
|
} else if (
|
|
isSwapsDefaultTokenSymbol(swapMetaData?.token_to, chainId) ||
|
|
swapComplete
|
|
) {
|
|
history.push(DEFAULT_ROUTE);
|
|
} else {
|
|
await dispatch(setDefaultHomeActiveTabName('activity'));
|
|
history.push(DEFAULT_ROUTE);
|
|
}
|
|
}}
|
|
onCancel={async () => await dispatch(navigateBackToBuildQuote(history))}
|
|
submitText={submitText}
|
|
disabled={submittingSwap}
|
|
hideCancel={errorKey !== QUOTES_EXPIRED_ERROR}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
AwaitingSwap.propTypes = {
|
|
swapComplete: PropTypes.bool,
|
|
txHash: PropTypes.string,
|
|
tokensReceived: PropTypes.string,
|
|
errorKey: PropTypes.oneOf([
|
|
QUOTES_EXPIRED_ERROR,
|
|
SWAP_FAILED_ERROR,
|
|
ERROR_FETCHING_QUOTES,
|
|
QUOTES_NOT_AVAILABLE_ERROR,
|
|
OFFLINE_FOR_MAINTENANCE,
|
|
CONTRACT_DATA_DISABLED_ERROR,
|
|
]),
|
|
submittingSwap: PropTypes.bool,
|
|
txId: PropTypes.number,
|
|
};
|