1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-24 12:23:39 +02:00
metamask-extension/ui/app/hooks/useTransactionDisplayData.js
Mark Stacey b93046bdab
Use string literals for transaction category localized messages (#10391)
We now use string literals for all transaction category localized
messages. This makes it easier to verify that we have translations for
each of them, and that we aren't leaving any unused translations around.
2021-02-08 12:36:58 -03:30

260 lines
10 KiB
JavaScript

import { useSelector } from 'react-redux';
import { getKnownMethodData } from '../selectors/selectors';
import {
getStatusKey,
getTransactionCategoryTitle,
} from '../helpers/utils/transactions.util';
import { camelCaseToCapitalize } from '../helpers/utils/common.util';
import { PRIMARY, SECONDARY } from '../helpers/constants/common';
import { getTokenAddressParam } from '../helpers/utils/token-util';
import {
formatDateWithYearContext,
shortenAddress,
stripHttpSchemes,
} from '../helpers/utils/util';
import {
PENDING_STATUS_HASH,
TOKEN_CATEGORY_HASH,
} from '../helpers/constants/transactions';
import { getTokens } from '../ducks/metamask/metamask';
import {
TRANSACTION_CATEGORIES,
TRANSACTION_GROUP_CATEGORIES,
TRANSACTION_STATUSES,
} from '../../../shared/constants/transaction';
import { useI18nContext } from './useI18nContext';
import { useTokenFiatAmount } from './useTokenFiatAmount';
import { useUserPreferencedCurrency } from './useUserPreferencedCurrency';
import { useCurrencyDisplay } from './useCurrencyDisplay';
import { useTokenDisplayValue } from './useTokenDisplayValue';
import { useTokenData } from './useTokenData';
import { useSwappedTokenValue } from './useSwappedTokenValue';
import { useCurrentAsset } from './useCurrentAsset';
/**
* @typedef {Object} TransactionDisplayData
* @property {string} title - primary description of the transaction
* @property {string} subtitle - supporting text describing the transaction
* @property {bool} subtitleContainsOrigin - true if the subtitle includes the origin of the tx
* @property {string} category - the transaction category
* @property {string} primaryCurrency - the currency string to display in the primary position
* @property {string} [secondaryCurrency] - the currency string to display in the secondary position
* @property {string} status - the status of the transaction
* @property {string} senderAddress - the Ethereum address of the sender
* @property {string} recipientAddress - the Ethereum address of the recipient
*/
/**
* Get computed values used for displaying transaction data to a user
*
* The goal of this method is to perform all of the necessary computation and
* state access required to take a transactionGroup and derive from it a shape
* of data that can power all views related to a transaction. Presently the main
* case is for shared logic between transaction-list-item and transaction-detail-view
* @param {Object} transactionGroup - group of transactions
* @return {TransactionDisplayData}
*/
export function useTransactionDisplayData(transactionGroup) {
// To determine which primary currency to display for swaps transactions we need to be aware
// of which asset, if any, we are viewing at present
const currentAsset = useCurrentAsset();
const knownTokens = useSelector(getTokens);
const t = useI18nContext();
const { initialTransaction, primaryTransaction } = transactionGroup;
// initialTransaction contains the data we need to derive the primary purpose of this transaction group
const { transactionCategory } = initialTransaction;
const { from: senderAddress, to } = initialTransaction.txParams || {};
// for smart contract interactions, methodData can be used to derive the name of the action being taken
const methodData =
useSelector((state) =>
getKnownMethodData(state, initialTransaction?.txParams?.data),
) || {};
const displayedStatusKey = getStatusKey(primaryTransaction);
const isPending = displayedStatusKey in PENDING_STATUS_HASH;
const isSubmitted = displayedStatusKey === TRANSACTION_STATUSES.SUBMITTED;
const primaryValue = primaryTransaction.txParams?.value;
let prefix = '-';
const date = formatDateWithYearContext(initialTransaction.time || 0);
let subtitle;
let subtitleContainsOrigin = false;
let recipientAddress = to;
// This value is used to determine whether we should look inside txParams.data
// to pull out and render token related information
const isTokenCategory = TOKEN_CATEGORY_HASH[transactionCategory];
// these values are always instantiated because they are either
// used by or returned from hooks. Hooks must be called at the top level,
// so as an additional safeguard against inappropriately associating token
// transfers, we pass an additional argument to these hooks that will be
// false for non-token transactions. This additional argument forces the
// hook to return null
const token =
isTokenCategory &&
knownTokens.find(({ address }) => address === recipientAddress);
const tokenData = useTokenData(
initialTransaction?.txParams?.data,
isTokenCategory,
);
const tokenDisplayValue = useTokenDisplayValue(
initialTransaction?.txParams?.data,
token,
isTokenCategory,
);
const tokenFiatAmount = useTokenFiatAmount(
token?.address,
tokenDisplayValue,
token?.symbol,
);
const origin = stripHttpSchemes(
initialTransaction.origin || initialTransaction.msgParams?.origin || '',
);
// used to append to the primary display value. initialized to either token.symbol or undefined
// but can later be modified if dealing with a swap
let primarySuffix = isTokenCategory ? token?.symbol : undefined;
// used to display the primary value of tx. initialized to either tokenDisplayValue or undefined
// but can later be modified if dealing with a swap
let primaryDisplayValue = isTokenCategory ? tokenDisplayValue : undefined;
// used to display fiat amount of tx. initialized to either tokenFiatAmount or undefined
// but can later be modified if dealing with a swap
let secondaryDisplayValue = isTokenCategory ? tokenFiatAmount : undefined;
// The transaction group category that will be used for rendering the icon in the activity list
let category;
// The primary title of the Tx that will be displayed in the activity list
let title;
const {
swapTokenValue,
isNegative,
swapTokenFiatAmount,
isViewingReceivedTokenFromSwap,
} = useSwappedTokenValue(transactionGroup, currentAsset);
// There are seven types of transaction entries that are currently differentiated in the design
// 1. Signature request
// 2. Send (sendEth sendTokens)
// 3. Deposit
// 4. Site interaction
// 5. Approval
// 6. Swap
// 7. Swap Approval
if (transactionCategory === null || transactionCategory === undefined) {
category = TRANSACTION_GROUP_CATEGORIES.SIGNATURE_REQUEST;
title = t('signatureRequest');
subtitle = origin;
subtitleContainsOrigin = true;
} else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP) {
category = TRANSACTION_GROUP_CATEGORIES.SWAP;
title = t('swapTokenToToken', [
initialTransaction.sourceTokenSymbol,
initialTransaction.destinationTokenSymbol,
]);
subtitle = origin;
subtitleContainsOrigin = true;
primarySuffix = isViewingReceivedTokenFromSwap
? currentAsset.symbol
: initialTransaction.sourceTokenSymbol;
primaryDisplayValue = swapTokenValue;
secondaryDisplayValue = swapTokenFiatAmount;
if (isNegative) {
prefix = '';
} else if (isViewingReceivedTokenFromSwap) {
prefix = '+';
} else {
prefix = '-';
}
} else if (transactionCategory === TRANSACTION_CATEGORIES.SWAP_APPROVAL) {
category = TRANSACTION_GROUP_CATEGORIES.APPROVAL;
title = t('swapApproval', [primaryTransaction.sourceTokenSymbol]);
subtitle = origin;
subtitleContainsOrigin = true;
primarySuffix = primaryTransaction.sourceTokenSymbol;
} else if (
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_APPROVE
) {
category = TRANSACTION_GROUP_CATEGORIES.APPROVAL;
prefix = '';
title = t('approveSpendLimit', [token?.symbol || t('token')]);
subtitle = origin;
subtitleContainsOrigin = true;
} else if (
transactionCategory === TRANSACTION_CATEGORIES.DEPLOY_CONTRACT ||
transactionCategory === TRANSACTION_CATEGORIES.CONTRACT_INTERACTION
) {
category = TRANSACTION_GROUP_CATEGORIES.INTERACTION;
const transactionCategoryTitle = getTransactionCategoryTitle(
t,
transactionCategory,
);
title =
(methodData?.name && camelCaseToCapitalize(methodData.name)) ||
transactionCategoryTitle;
subtitle = origin;
subtitleContainsOrigin = true;
} else if (transactionCategory === TRANSACTION_CATEGORIES.INCOMING) {
category = TRANSACTION_GROUP_CATEGORIES.RECEIVE;
title = t('receive');
prefix = '';
subtitle = t('fromAddress', [shortenAddress(senderAddress)]);
} else if (
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER_FROM ||
transactionCategory === TRANSACTION_CATEGORIES.TOKEN_METHOD_TRANSFER
) {
category = TRANSACTION_GROUP_CATEGORIES.SEND;
title = t('sendSpecifiedTokens', [token?.symbol || t('token')]);
recipientAddress = getTokenAddressParam(tokenData);
subtitle = t('toAddress', [shortenAddress(recipientAddress)]);
} else if (transactionCategory === TRANSACTION_CATEGORIES.SENT_ETHER) {
category = TRANSACTION_GROUP_CATEGORIES.SEND;
title = t('sendETH');
subtitle = t('toAddress', [shortenAddress(recipientAddress)]);
}
const primaryCurrencyPreferences = useUserPreferencedCurrency(PRIMARY);
const secondaryCurrencyPreferences = useUserPreferencedCurrency(SECONDARY);
const [primaryCurrency] = useCurrencyDisplay(primaryValue, {
prefix,
displayValue: primaryDisplayValue,
suffix: primarySuffix,
...primaryCurrencyPreferences,
});
const [secondaryCurrency] = useCurrencyDisplay(primaryValue, {
prefix,
displayValue: secondaryDisplayValue,
hideLabel: isTokenCategory || Boolean(swapTokenValue),
...secondaryCurrencyPreferences,
});
return {
title,
category,
date,
subtitle,
subtitleContainsOrigin,
primaryCurrency:
transactionCategory === TRANSACTION_CATEGORIES.SWAP && isPending
? ''
: primaryCurrency,
senderAddress,
recipientAddress,
secondaryCurrency:
(isTokenCategory && !tokenFiatAmount) ||
(transactionCategory === TRANSACTION_CATEGORIES.SWAP &&
!swapTokenFiatAmount)
? undefined
: secondaryCurrency,
displayedStatusKey,
isPending,
isSubmitted,
};
}