A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
ciphermask/ui/app/hooks/useTransactionDisplayData.js

252 lines
9.9 KiB

import { useSelector } from 'react-redux'
import { getKnownMethodData } from '../selectors/selectors'
import { getStatusKey } 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
title =
(methodData?.name && camelCaseToCapitalize(methodData.name)) ||
t(transactionCategory)
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,
}
}