mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-02 14:15:06 +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>
503 lines
17 KiB
JavaScript
503 lines
17 KiB
JavaScript
import React, { useContext, useEffect, useState } from 'react';
|
|
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
|
import { useHistory } from 'react-router-dom';
|
|
import { getBlockExplorerLink } from '@metamask/etherscan-link';
|
|
import { isEqual } from 'lodash';
|
|
|
|
import { I18nContext } from '../../../contexts/i18n';
|
|
import {
|
|
getFetchParams,
|
|
prepareToLeaveSwaps,
|
|
getCurrentSmartTransactions,
|
|
getSelectedQuote,
|
|
getTopQuote,
|
|
getSmartTransactionsOptInStatus,
|
|
getSmartTransactionsEnabled,
|
|
getCurrentSmartTransactionsEnabled,
|
|
getSwapsNetworkConfig,
|
|
cancelSwapsSmartTransaction,
|
|
} from '../../../ducks/swaps/swaps';
|
|
import {
|
|
isHardwareWallet,
|
|
getHardwareWalletType,
|
|
getCurrentChainId,
|
|
getUSDConversionRate,
|
|
conversionRateSelector,
|
|
getCurrentCurrency,
|
|
getRpcPrefsForCurrentProvider,
|
|
} from '../../../selectors';
|
|
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../shared/constants/swaps';
|
|
import { getNativeCurrency } from '../../../ducks/metamask/metamask';
|
|
import {
|
|
DEFAULT_ROUTE,
|
|
BUILD_QUOTE_ROUTE,
|
|
} from '../../../helpers/constants/routes';
|
|
import Typography from '../../../components/ui/typography';
|
|
import Box from '../../../components/ui/box';
|
|
import UrlIcon from '../../../components/ui/url-icon';
|
|
import {
|
|
BLOCK_SIZES,
|
|
TypographyVariant,
|
|
JustifyContent,
|
|
DISPLAY,
|
|
FONT_WEIGHT,
|
|
AlignItems,
|
|
TextColor,
|
|
} from '../../../helpers/constants/design-system';
|
|
import {
|
|
stopPollingForQuotes,
|
|
setBackgroundSwapRouteState,
|
|
} from '../../../store/actions';
|
|
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
|
import { SmartTransactionStatus } from '../../../../shared/constants/transaction';
|
|
|
|
import SwapsFooter from '../swaps-footer';
|
|
import {
|
|
showRemainingTimeInMinAndSec,
|
|
getFeeForSmartTransaction,
|
|
} from '../swaps.util';
|
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
|
import CreateNewSwap from '../create-new-swap';
|
|
import ViewOnBlockExplorer from '../view-on-block-explorer';
|
|
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
|
|
import SuccessIcon from './success-icon';
|
|
import RevertedIcon from './reverted-icon';
|
|
import CanceledIcon from './canceled-icon';
|
|
import UnknownIcon from './unknown-icon';
|
|
import ArrowIcon from './arrow-icon';
|
|
import TimerIcon from './timer-icon';
|
|
|
|
export default function SmartTransactionStatusPage() {
|
|
const [cancelSwapLinkClicked, setCancelSwapLinkClicked] = useState(false);
|
|
const t = useContext(I18nContext);
|
|
const history = useHistory();
|
|
const dispatch = useDispatch();
|
|
const fetchParams = useSelector(getFetchParams, isEqual) || {};
|
|
const {
|
|
destinationTokenInfo: fetchParamsDestinationTokenInfo = {},
|
|
sourceTokenInfo: fetchParamsSourceTokenInfo = {},
|
|
} = fetchParams?.metaData || {};
|
|
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
|
const hardwareWalletType = useSelector(getHardwareWalletType);
|
|
const needsTwoConfirmations = true;
|
|
const selectedQuote = useSelector(getSelectedQuote, isEqual);
|
|
const topQuote = useSelector(getTopQuote, isEqual);
|
|
const usedQuote = selectedQuote || topQuote;
|
|
const currentSmartTransactions = useSelector(
|
|
getCurrentSmartTransactions,
|
|
isEqual,
|
|
);
|
|
const smartTransactionsOptInStatus = useSelector(
|
|
getSmartTransactionsOptInStatus,
|
|
);
|
|
const chainId = useSelector(getCurrentChainId);
|
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider, shallowEqual);
|
|
const swapsNetworkConfig = useSelector(getSwapsNetworkConfig, shallowEqual);
|
|
const smartTransactionsEnabled = useSelector(getSmartTransactionsEnabled);
|
|
const currentSmartTransactionsEnabled = useSelector(
|
|
getCurrentSmartTransactionsEnabled,
|
|
);
|
|
const baseNetworkUrl =
|
|
rpcPrefs.blockExplorerUrl ??
|
|
SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[chainId] ??
|
|
null;
|
|
const nativeCurrencySymbol = useSelector(getNativeCurrency);
|
|
const conversionRate = useSelector(conversionRateSelector);
|
|
const USDConversionRate = useSelector(getUSDConversionRate);
|
|
const currentCurrency = useSelector(getCurrentCurrency);
|
|
|
|
let smartTransactionStatus = SmartTransactionStatus.pending;
|
|
let latestSmartTransaction = {};
|
|
let latestSmartTransactionUuid;
|
|
let cancellationFeeWei;
|
|
|
|
if (currentSmartTransactions && currentSmartTransactions.length > 0) {
|
|
latestSmartTransaction =
|
|
currentSmartTransactions[currentSmartTransactions.length - 1];
|
|
latestSmartTransactionUuid = latestSmartTransaction?.uuid;
|
|
smartTransactionStatus =
|
|
latestSmartTransaction?.status || SmartTransactionStatus.pending;
|
|
cancellationFeeWei =
|
|
latestSmartTransaction?.statusMetadata?.cancellationFeeWei;
|
|
}
|
|
|
|
const [timeLeftForPendingStxInSec, setTimeLeftForPendingStxInSec] = useState(
|
|
swapsNetworkConfig.stxStatusDeadline,
|
|
);
|
|
|
|
const sensitiveProperties = {
|
|
needs_two_confirmations: needsTwoConfirmations,
|
|
token_from:
|
|
fetchParamsSourceTokenInfo.symbol ??
|
|
latestSmartTransaction?.sourceTokenSymbol,
|
|
token_from_amount:
|
|
fetchParams?.value ?? latestSmartTransaction?.swapTokenValue,
|
|
token_to:
|
|
fetchParamsDestinationTokenInfo.symbol ??
|
|
latestSmartTransaction?.destinationTokenSymbol,
|
|
request_type: fetchParams?.balanceError ? 'Quote' : 'Order',
|
|
slippage: fetchParams?.slippage,
|
|
custom_slippage: fetchParams?.slippage === 2,
|
|
is_hardware_wallet: hardwareWalletUsed,
|
|
hardware_wallet_type: hardwareWalletType,
|
|
stx_enabled: smartTransactionsEnabled,
|
|
current_stx_enabled: currentSmartTransactionsEnabled,
|
|
stx_user_opt_in: smartTransactionsOptInStatus,
|
|
};
|
|
|
|
let destinationValue;
|
|
if (usedQuote?.destinationAmount) {
|
|
destinationValue = calcTokenAmount(
|
|
usedQuote?.destinationAmount,
|
|
fetchParamsDestinationTokenInfo.decimals ??
|
|
latestSmartTransaction?.destinationTokenDecimals,
|
|
).toPrecision(8);
|
|
}
|
|
const trackEvent = useContext(MetaMetricsContext);
|
|
|
|
const isSmartTransactionPending =
|
|
smartTransactionStatus === SmartTransactionStatus.pending;
|
|
const showCloseButtonOnly =
|
|
isSmartTransactionPending ||
|
|
smartTransactionStatus === SmartTransactionStatus.success;
|
|
const txHash = latestSmartTransaction?.statusMetadata?.minedHash;
|
|
|
|
useEffect(() => {
|
|
trackEvent({
|
|
event: 'STX Status Page Loaded',
|
|
category: MetaMetricsEventCategory.Swaps,
|
|
sensitiveProperties,
|
|
});
|
|
// eslint-disable-next-line
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let intervalId;
|
|
if (isSmartTransactionPending && latestSmartTransactionUuid) {
|
|
const calculateRemainingTime = () => {
|
|
const secondsAfterStxSubmission = Math.round(
|
|
(Date.now() - latestSmartTransaction.time) / 1000,
|
|
);
|
|
if (secondsAfterStxSubmission > swapsNetworkConfig.stxStatusDeadline) {
|
|
setTimeLeftForPendingStxInSec(0);
|
|
clearInterval(intervalId);
|
|
return;
|
|
}
|
|
setTimeLeftForPendingStxInSec(
|
|
swapsNetworkConfig.stxStatusDeadline - secondsAfterStxSubmission,
|
|
);
|
|
};
|
|
intervalId = setInterval(calculateRemainingTime, 1000);
|
|
calculateRemainingTime();
|
|
}
|
|
|
|
return () => clearInterval(intervalId);
|
|
}, [
|
|
dispatch,
|
|
isSmartTransactionPending,
|
|
latestSmartTransactionUuid,
|
|
latestSmartTransaction.time,
|
|
swapsNetworkConfig.stxStatusDeadline,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
dispatch(setBackgroundSwapRouteState('smartTransactionStatus'));
|
|
setTimeout(() => {
|
|
// We don't need to poll for quotes on the status page.
|
|
dispatch(stopPollingForQuotes());
|
|
}, 1000); // Stop polling for quotes after 1s.
|
|
}, [dispatch]);
|
|
|
|
let headerText = t('stxPendingPrivatelySubmittingSwap');
|
|
let description;
|
|
let subDescription;
|
|
let icon;
|
|
let blockExplorerUrl;
|
|
if (isSmartTransactionPending) {
|
|
if (cancelSwapLinkClicked) {
|
|
headerText = t('stxTryingToCancel');
|
|
} else if (cancellationFeeWei > 0) {
|
|
headerText = t('stxPendingPubliclySubmittingSwap');
|
|
}
|
|
}
|
|
if (smartTransactionStatus === SmartTransactionStatus.success) {
|
|
headerText = t('stxSuccess');
|
|
if (
|
|
fetchParamsDestinationTokenInfo.symbol ||
|
|
latestSmartTransaction?.destinationTokenSymbol
|
|
) {
|
|
description = t('stxSuccessDescription', [
|
|
fetchParamsDestinationTokenInfo.symbol ??
|
|
latestSmartTransaction?.destinationTokenSymbol,
|
|
]);
|
|
}
|
|
icon = <SuccessIcon />;
|
|
} else if (
|
|
smartTransactionStatus === 'cancelled_user_cancelled' ||
|
|
latestSmartTransaction?.statusMetadata?.minedTx ===
|
|
SmartTransactionStatus.cancelled
|
|
) {
|
|
headerText = t('stxUserCancelled');
|
|
description = t('stxUserCancelledDescription');
|
|
icon = <CanceledIcon />;
|
|
} else if (
|
|
smartTransactionStatus.startsWith('cancelled') ||
|
|
smartTransactionStatus.includes('deadline_missed')
|
|
) {
|
|
headerText = t('stxCancelled');
|
|
description = t('stxCancelledDescription');
|
|
subDescription = t('stxCancelledSubDescription');
|
|
icon = <CanceledIcon />;
|
|
} else if (smartTransactionStatus === 'unknown') {
|
|
headerText = t('stxUnknown');
|
|
description = t('stxUnknownDescription');
|
|
icon = <UnknownIcon />;
|
|
} else if (smartTransactionStatus === 'reverted') {
|
|
headerText = t('stxFailure');
|
|
description = t('stxFailureDescription', [
|
|
<a
|
|
className="smart-transaction-status__support-link"
|
|
key="smart-transaction-status-support-link"
|
|
href="https://support.metamask.io"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{t('customerSupport')}
|
|
</a>,
|
|
]);
|
|
icon = <RevertedIcon />;
|
|
}
|
|
if (txHash && latestSmartTransactionUuid) {
|
|
blockExplorerUrl = getBlockExplorerLink(
|
|
{ hash: txHash, chainId },
|
|
{ blockExplorerUrl: baseNetworkUrl },
|
|
);
|
|
}
|
|
|
|
const showCancelSwapLink =
|
|
latestSmartTransaction.cancellable && !cancelSwapLinkClicked;
|
|
|
|
const CancelSwap = () => {
|
|
let feeInFiat;
|
|
if (cancellationFeeWei > 0) {
|
|
({ feeInFiat } = getFeeForSmartTransaction({
|
|
chainId,
|
|
currentCurrency,
|
|
conversionRate,
|
|
USDConversionRate,
|
|
nativeCurrencySymbol,
|
|
feeInWeiDec: cancellationFeeWei,
|
|
}));
|
|
}
|
|
return (
|
|
<Box marginBottom={0}>
|
|
<a
|
|
className="smart-transaction-status__cancel-swap-link"
|
|
href="#"
|
|
onClick={(e) => {
|
|
e?.preventDefault();
|
|
setCancelSwapLinkClicked(true); // We want to hide it after a user clicks on it.
|
|
trackEvent({
|
|
event: 'Cancel STX',
|
|
category: MetaMetricsEventCategory.Swaps,
|
|
sensitiveProperties,
|
|
});
|
|
dispatch(cancelSwapsSmartTransaction(latestSmartTransactionUuid));
|
|
}}
|
|
>
|
|
{feeInFiat
|
|
? t('cancelSwapForFee', [feeInFiat])
|
|
: t('cancelSwapForFree')}
|
|
</a>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="smart-transaction-status">
|
|
<Box
|
|
paddingLeft={8}
|
|
paddingRight={8}
|
|
height={BLOCK_SIZES.FULL}
|
|
justifyContent={JustifyContent.flexStart}
|
|
display={DISPLAY.FLEX}
|
|
className="smart-transaction-status__content"
|
|
>
|
|
<Box
|
|
marginTop={10}
|
|
marginBottom={0}
|
|
display={DISPLAY.FLEX}
|
|
justifyContent={JustifyContent.center}
|
|
alignItems={AlignItems.center}
|
|
>
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
>
|
|
{`${fetchParams?.value && Number(fetchParams.value).toFixed(5)} `}
|
|
</Typography>
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
|
boxProps={{ marginLeft: 1, marginRight: 2 }}
|
|
>
|
|
{fetchParamsSourceTokenInfo.symbol ??
|
|
latestSmartTransaction?.sourceTokenSymbol}
|
|
</Typography>
|
|
{fetchParamsSourceTokenInfo.iconUrl ? (
|
|
<UrlIcon
|
|
url={fetchParamsSourceTokenInfo.iconUrl}
|
|
className="main-quote-summary__icon"
|
|
name={
|
|
fetchParamsSourceTokenInfo.symbol ??
|
|
latestSmartTransaction?.destinationTokenSymbol
|
|
}
|
|
fallbackClassName="main-quote-summary__icon-fallback"
|
|
/>
|
|
) : null}
|
|
<Box display={DISPLAY.BLOCK} marginLeft={2} marginRight={2}>
|
|
<ArrowIcon />
|
|
</Box>
|
|
{fetchParamsDestinationTokenInfo.iconUrl ? (
|
|
<UrlIcon
|
|
url={fetchParamsDestinationTokenInfo.iconUrl}
|
|
className="main-quote-summary__icon"
|
|
name={
|
|
fetchParamsDestinationTokenInfo.symbol ??
|
|
latestSmartTransaction?.destinationTokenSymbol
|
|
}
|
|
fallbackClassName="main-quote-summary__icon-fallback"
|
|
/>
|
|
) : null}
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
boxProps={{ marginLeft: 2 }}
|
|
>
|
|
{`~${destinationValue && Number(destinationValue).toFixed(5)} `}
|
|
</Typography>
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
|
boxProps={{ marginLeft: 1 }}
|
|
>
|
|
{fetchParamsDestinationTokenInfo.symbol ??
|
|
latestSmartTransaction?.destinationTokenSymbol}
|
|
</Typography>
|
|
</Box>
|
|
<Box
|
|
marginTop={3}
|
|
className="smart-transaction-status__background-animation smart-transaction-status__background-animation--top"
|
|
/>
|
|
{icon && (
|
|
<Box marginTop={3} marginBottom={2}>
|
|
{icon}
|
|
</Box>
|
|
)}
|
|
{isSmartTransactionPending && (
|
|
<Box
|
|
marginTop={7}
|
|
marginBottom={1}
|
|
display={DISPLAY.FLEX}
|
|
justifyContent={JustifyContent.center}
|
|
alignItems={AlignItems.center}
|
|
>
|
|
<TimerIcon />
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
boxProps={{ marginLeft: 1 }}
|
|
>
|
|
{`${t('stxSwapCompleteIn')} `}
|
|
</Typography>
|
|
<Typography
|
|
color={TextColor.textAlternative}
|
|
variant={TypographyVariant.H6}
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
|
boxProps={{ marginLeft: 1 }}
|
|
className="smart-transaction-status__remaining-time"
|
|
>
|
|
{showRemainingTimeInMinAndSec(timeLeftForPendingStxInSec)}
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
<Typography
|
|
color={TextColor.textDefault}
|
|
variant={TypographyVariant.H4}
|
|
fontWeight={FONT_WEIGHT.BOLD}
|
|
>
|
|
{headerText}
|
|
</Typography>
|
|
{isSmartTransactionPending && (
|
|
<div className="smart-transaction-status__loading-bar-container">
|
|
<div
|
|
className="smart-transaction-status__loading-bar"
|
|
style={{
|
|
width: `${
|
|
(100 / swapsNetworkConfig.stxStatusDeadline) *
|
|
(swapsNetworkConfig.stxStatusDeadline -
|
|
timeLeftForPendingStxInSec)
|
|
}%`,
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
{description && (
|
|
<Typography
|
|
variant={TypographyVariant.H6}
|
|
boxProps={{ ...(blockExplorerUrl && { margin: [1, 0, 0] }) }}
|
|
color={TextColor.textAlternative}
|
|
>
|
|
{description}
|
|
</Typography>
|
|
)}
|
|
{blockExplorerUrl && (
|
|
<ViewOnBlockExplorer
|
|
blockExplorerUrl={blockExplorerUrl}
|
|
sensitiveTrackingProperties={sensitiveProperties}
|
|
/>
|
|
)}
|
|
<Box
|
|
marginTop={3}
|
|
className="smart-transaction-status__background-animation smart-transaction-status__background-animation--bottom"
|
|
/>
|
|
{subDescription && (
|
|
<Typography
|
|
variant={TypographyVariant.H7}
|
|
boxProps={{ marginTop: 8 }}
|
|
color={TextColor.textAlternative}
|
|
>
|
|
{subDescription}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
{showCancelSwapLink &&
|
|
latestSmartTransactionUuid &&
|
|
isSmartTransactionPending && <CancelSwap />}
|
|
{smartTransactionStatus === SmartTransactionStatus.success ? (
|
|
<CreateNewSwap sensitiveTrackingProperties={sensitiveProperties} />
|
|
) : null}
|
|
<SwapsFooter
|
|
onSubmit={async () => {
|
|
if (showCloseButtonOnly) {
|
|
await dispatch(prepareToLeaveSwaps());
|
|
history.push(DEFAULT_ROUTE);
|
|
} else {
|
|
history.push(BUILD_QUOTE_ROUTE);
|
|
}
|
|
}}
|
|
onCancel={async () => {
|
|
await dispatch(prepareToLeaveSwaps());
|
|
history.push(DEFAULT_ROUTE);
|
|
}}
|
|
submitText={showCloseButtonOnly ? t('close') : t('tryAgain')}
|
|
hideCancel={showCloseButtonOnly}
|
|
cancelText={t('close')}
|
|
className="smart-transaction-status__swaps-footer"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|