2021-07-23 01:13:40 +02:00
import { useDispatch , useSelector } from 'react-redux' ;
2023-02-24 20:21:55 +01:00
import { useEffect , useState } from 'react' ;
import {
getDetectedTokensInCurrentNetwork ,
getKnownMethodData ,
getTokenList ,
} from '../selectors/selectors' ;
2021-02-08 17:06:58 +01:00
import {
getStatusKey ,
2021-03-10 21:16:44 +01:00
getTransactionTypeTitle ,
2021-02-08 17:06:58 +01:00
} from '../helpers/utils/transactions.util' ;
2021-02-04 19:15:23 +01:00
import { camelCaseToCapitalize } from '../helpers/utils/common.util' ;
import { PRIMARY , SECONDARY } from '../helpers/constants/common' ;
2022-01-10 17:23:53 +01:00
import {
2023-02-24 20:21:55 +01:00
getAssetDetails ,
2022-01-10 17:23:53 +01:00
getTokenAddressParam ,
2022-07-23 16:37:31 +02:00
getTokenIdParam ,
2022-01-10 17:23:53 +01:00
} from '../helpers/utils/token-util' ;
2020-11-03 00:41:28 +01:00
import {
formatDateWithYearContext ,
shortenAddress ,
stripHttpSchemes ,
2021-02-04 19:15:23 +01:00
} from '../helpers/utils/util' ;
2021-09-10 19:37:19 +02:00
2020-05-26 22:49:11 +02:00
import {
PENDING _STATUS _HASH ,
TOKEN _CATEGORY _HASH ,
2021-02-04 19:15:23 +01:00
} from '../helpers/constants/transactions' ;
2023-02-16 20:23:29 +01:00
import { getNfts , getTokens } from '../ducks/metamask/metamask' ;
2020-11-03 23:57:51 +01:00
import {
2023-01-18 15:47:29 +01:00
TransactionType ,
TransactionGroupCategory ,
TransactionStatus ,
2021-04-28 21:53:59 +02:00
} from '../../shared/constants/transaction' ;
2021-07-23 01:13:40 +02:00
import { captureSingleException } from '../store/actions' ;
2022-03-07 19:54:36 +01:00
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils' ;
2022-09-16 21:05:21 +02:00
import { getTokenValueParam } from '../../shared/lib/metamask-controller-utils' ;
2021-02-04 19:15:23 +01:00
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' ;
2020-05-26 22:49:11 +02:00
2022-05-26 23:26:45 +02:00
/ * *
* 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
* /
const signatureTypes = [
null ,
undefined ,
2023-01-18 15:47:29 +01:00
TransactionType . sign ,
TransactionType . personalSign ,
TransactionType . signTypedData ,
TransactionType . ethDecrypt ,
TransactionType . ethGetEncryptionPublicKey ,
2022-05-26 23:26:45 +02:00
] ;
/ * *
* @ typedef { ( import ( '../../selectors/transactions' ) . TransactionGroup } TransactionGroup
* /
2020-05-26 22:49:11 +02:00
/ * *
2022-07-27 15:28:05 +02:00
* @ typedef { object } TransactionDisplayData
2022-05-26 23:26:45 +02:00
* @ property { string } category - the transaction category that will be used for rendering the icon in the activity list
2022-01-07 16:57:33 +01:00
* @ property { string } primaryCurrency - the currency string to display in the primary position
* @ property { string } recipientAddress - the Ethereum address of the recipient
2022-05-26 23:26:45 +02:00
* @ property { string } senderAddress - the Ethereum address of the sender
* @ property { string } status - the status of the transaction
* @ property { string } subtitle - the supporting text describing the transaction
* @ property { boolean } subtitleContainsOrigin - true if the subtitle includes the origin of the tx
* @ property { string } title - the primary title of the tx that will be displayed in the activity list
* @ property { string } [ secondaryCurrency ] - the currency string to display in the secondary position
2020-05-26 22:49:11 +02:00
* /
/ * *
* 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
2022-01-07 16:57:33 +01:00
*
2022-05-26 23:26:45 +02:00
* @ param { TransactionGroup } transactionGroup - group of transactions of the same nonce
2022-01-07 16:57:33 +01:00
* @ returns { TransactionDisplayData }
2020-05-26 22:49:11 +02:00
* /
2020-11-03 00:41:28 +01:00
export function useTransactionDisplayData ( transactionGroup ) {
2020-10-06 20:28:38 +02:00
// 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
2021-07-23 01:13:40 +02:00
const dispatch = useDispatch ( ) ;
2021-02-04 19:15:23 +01:00
const currentAsset = useCurrentAsset ( ) ;
const knownTokens = useSelector ( getTokens ) ;
2023-02-16 20:23:29 +01:00
const knownNfts = useSelector ( getNfts ) ;
2023-02-24 20:21:55 +01:00
const detectedTokens = useSelector ( getDetectedTokensInCurrentNetwork ) || [ ] ;
const tokenList = useSelector ( getTokenList ) ;
2021-02-04 19:15:23 +01:00
const t = useI18nContext ( ) ;
2022-05-26 23:26:45 +02:00
2021-02-04 19:15:23 +01:00
const { initialTransaction , primaryTransaction } = transactionGroup ;
2020-05-26 22:49:11 +02:00
// initialTransaction contains the data we need to derive the primary purpose of this transaction group
2021-03-10 21:16:44 +01:00
const { type } = initialTransaction ;
2021-02-04 19:15:23 +01:00
const { from : senderAddress , to } = initialTransaction . txParams || { } ;
2020-05-26 22:49:11 +02:00
// for smart contract interactions, methodData can be used to derive the name of the action being taken
2020-11-03 00:41:28 +01:00
const methodData =
useSelector ( ( state ) =>
getKnownMethodData ( state , initialTransaction ? . txParams ? . data ) ,
2021-02-04 19:15:23 +01:00
) || { } ;
2020-05-26 22:49:11 +02:00
2021-02-04 19:15:23 +01:00
const displayedStatusKey = getStatusKey ( primaryTransaction ) ;
const isPending = displayedStatusKey in PENDING _STATUS _HASH ;
2023-01-18 15:47:29 +01:00
const isSubmitted = displayedStatusKey === TransactionStatus . submitted ;
2020-05-26 22:49:11 +02:00
2021-02-04 19:15:23 +01:00
const primaryValue = primaryTransaction . txParams ? . value ;
2021-08-13 17:22:12 +02:00
const date = formatDateWithYearContext ( initialTransaction . time ) ;
2022-05-26 23:26:45 +02:00
let prefix = '-' ;
2021-02-04 19:15:23 +01:00
let subtitle ;
let subtitleContainsOrigin = false ;
let recipientAddress = to ;
2020-05-26 22:49:11 +02:00
// This value is used to determine whether we should look inside txParams.data
// to pull out and render token related information
2021-03-10 21:16:44 +01:00
const isTokenCategory = TOKEN _CATEGORY _HASH [ type ] ;
2020-05-26 22:49:11 +02:00
// 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
2023-02-24 20:21:55 +01:00
let token = null ;
const [ currentAssetDetails , setCurrentAssetDetails ] = useState ( null ) ;
if ( isTokenCategory ) {
token =
knownTokens . find ( ( { address } ) =>
isEqualCaseInsensitive ( address , recipientAddress ) ,
) ||
detectedTokens . find ( ( { address } ) =>
isEqualCaseInsensitive ( address , recipientAddress ) ,
) ||
tokenList [ recipientAddress . toLowerCase ( ) ] ;
}
useEffect ( ( ) => {
async function getAndSetAssetDetails ( ) {
if ( isTokenCategory && ! token ) {
const assetDetails = await getAssetDetails (
recipientAddress ,
senderAddress ,
initialTransaction ? . txParams ? . data ,
knownNfts ,
) ;
setCurrentAssetDetails ( assetDetails ) ;
}
}
getAndSetAssetDetails ( ) ;
} , [
isTokenCategory ,
token ,
recipientAddress ,
senderAddress ,
initialTransaction ? . txParams ? . data ,
knownNfts ,
] ) ;
if ( currentAssetDetails ) {
token = {
address : currentAssetDetails . toAddress ,
symbol : currentAssetDetails . symbol ,
decimals : currentAssetDetails . decimals ,
} ;
}
2022-01-10 17:23:53 +01:00
2020-11-03 00:41:28 +01:00
const tokenData = useTokenData (
initialTransaction ? . txParams ? . data ,
isTokenCategory ,
2021-02-04 19:15:23 +01:00
) ;
2022-01-10 17:23:53 +01:00
2022-07-23 16:37:31 +02:00
// Sometimes the tokenId value is parsed as "_value" param. Not seeing this often any more, but still occasionally:
// i.e. call approve() on BAYC contract - https://etherscan.io/token/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#writeContract, and tokenId shows up as _value,
// not sure why since it doesn't match the ERC721 ABI spec we use to parse these transactions - https://github.com/MetaMask/metamask-eth-abis/blob/d0474308a288f9252597b7c93a3a8deaad19e1b2/src/abis/abiERC721.ts#L62.
const transactionDataTokenId =
getTokenIdParam ( tokenData ) ? ? getTokenValueParam ( tokenData ) ;
2022-01-10 17:23:53 +01:00
2023-02-16 20:23:29 +01:00
const nft =
2022-01-10 17:23:53 +01:00
isTokenCategory &&
2023-02-16 20:23:29 +01:00
knownNfts . find (
2022-01-10 17:23:53 +01:00
( { address , tokenId } ) =>
isEqualCaseInsensitive ( address , recipientAddress ) &&
2022-07-23 16:37:31 +02:00
tokenId === transactionDataTokenId ,
2022-01-10 17:23:53 +01:00
) ;
2020-11-03 00:41:28 +01:00
const tokenDisplayValue = useTokenDisplayValue (
initialTransaction ? . txParams ? . data ,
token ,
isTokenCategory ,
2021-02-04 19:15:23 +01:00
) ;
2020-11-03 00:41:28 +01:00
const tokenFiatAmount = useTokenFiatAmount (
token ? . address ,
tokenDisplayValue ,
token ? . symbol ,
2021-02-04 19:15:23 +01:00
) ;
2020-05-26 22:49:11 +02:00
2020-11-03 00:41:28 +01:00
const origin = stripHttpSchemes (
initialTransaction . origin || initialTransaction . msgParams ? . origin || '' ,
2021-02-04 19:15:23 +01:00
) ;
2020-06-17 18:38:15 +02:00
2020-10-06 20:28:38 +02:00
// 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
2021-02-04 19:15:23 +01:00
let primarySuffix = isTokenCategory ? token ? . symbol : undefined ;
2020-10-06 20:28:38 +02:00
// used to display the primary value of tx. initialized to either tokenDisplayValue or undefined
// but can later be modified if dealing with a swap
2021-02-04 19:15:23 +01:00
let primaryDisplayValue = isTokenCategory ? tokenDisplayValue : undefined ;
2020-10-06 20:28:38 +02:00
// used to display fiat amount of tx. initialized to either tokenFiatAmount or undefined
// but can later be modified if dealing with a swap
2021-02-04 19:15:23 +01:00
let secondaryDisplayValue = isTokenCategory ? tokenFiatAmount : undefined ;
2022-05-26 23:26:45 +02:00
2021-02-04 19:15:23 +01:00
let category ;
let title ;
2020-10-06 20:28:38 +02:00
2020-11-03 00:41:28 +01:00
const {
swapTokenValue ,
isNegative ,
swapTokenFiatAmount ,
isViewingReceivedTokenFromSwap ,
2021-02-04 19:15:23 +01:00
} = useSwappedTokenValue ( transactionGroup , currentAsset ) ;
2020-10-06 20:28:38 +02:00
2021-05-11 19:57:06 +02:00
if ( signatureTypes . includes ( type ) ) {
2023-01-18 15:47:29 +01:00
category = TransactionGroupCategory . signatureRequest ;
2021-02-04 19:15:23 +01:00
title = t ( 'signatureRequest' ) ;
subtitle = origin ;
subtitleContainsOrigin = true ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . swap ) {
category = TransactionGroupCategory . swap ;
2020-10-06 20:28:38 +02:00
title = t ( 'swapTokenToToken' , [
2020-10-10 21:50:20 +02:00
initialTransaction . sourceTokenSymbol ,
initialTransaction . destinationTokenSymbol ,
2021-02-04 19:15:23 +01:00
] ) ;
subtitle = origin ;
subtitleContainsOrigin = true ;
2020-10-06 20:28:38 +02:00
primarySuffix = isViewingReceivedTokenFromSwap
? currentAsset . symbol
2021-02-04 19:15:23 +01:00
: initialTransaction . sourceTokenSymbol ;
primaryDisplayValue = swapTokenValue ;
secondaryDisplayValue = swapTokenFiatAmount ;
2020-10-28 08:08:02 +01:00
if ( isNegative ) {
2021-02-04 19:15:23 +01:00
prefix = '' ;
2020-10-28 08:08:02 +01:00
} else if ( isViewingReceivedTokenFromSwap ) {
2021-02-04 19:15:23 +01:00
prefix = '+' ;
2020-10-28 08:08:02 +01:00
} else {
2021-02-04 19:15:23 +01:00
prefix = '-' ;
2020-10-28 08:08:02 +01:00
}
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . swapApproval ) {
category = TransactionGroupCategory . approval ;
2021-02-04 19:15:23 +01:00
title = t ( 'swapApproval' , [ primaryTransaction . sourceTokenSymbol ] ) ;
subtitle = origin ;
subtitleContainsOrigin = true ;
primarySuffix = primaryTransaction . sourceTokenSymbol ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . tokenMethodApprove ) {
category = TransactionGroupCategory . approval ;
2021-02-04 19:15:23 +01:00
prefix = '' ;
2023-01-25 16:15:58 +01:00
title = t ( 'approveSpendingCap' , [
token ? . symbol || t ( 'token' ) . toLowerCase ( ) ,
] ) ;
2021-02-04 19:15:23 +01:00
subtitle = origin ;
subtitleContainsOrigin = true ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . tokenMethodSetApprovalForAll ) {
category = TransactionGroupCategory . approval ;
2022-07-12 01:32:55 +02:00
prefix = '' ;
title = t ( 'setApprovalForAllTitle' , [ token ? . symbol || t ( 'token' ) ] ) ;
subtitle = origin ;
subtitleContainsOrigin = true ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . contractInteraction ) {
category = TransactionGroupCategory . interaction ;
2021-03-10 21:16:44 +01:00
const transactionTypeTitle = getTransactionTypeTitle ( t , type ) ;
2020-11-03 00:41:28 +01:00
title =
( methodData ? . name && camelCaseToCapitalize ( methodData . name ) ) ||
2021-03-10 21:16:44 +01:00
transactionTypeTitle ;
2021-02-04 19:15:23 +01:00
subtitle = origin ;
subtitleContainsOrigin = true ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . deployContract ) {
2022-05-02 23:49:01 +02:00
// @todo Should perhaps be a separate group?
2023-01-18 15:47:29 +01:00
category = TransactionGroupCategory . interaction ;
2022-05-02 23:49:01 +02:00
title = getTransactionTypeTitle ( t , type ) ;
subtitle = origin ;
subtitleContainsOrigin = true ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . incoming ) {
category = TransactionGroupCategory . receive ;
2021-02-04 19:15:23 +01:00
title = t ( 'receive' ) ;
prefix = '' ;
subtitle = t ( 'fromAddress' , [ shortenAddress ( senderAddress ) ] ) ;
2020-11-03 00:41:28 +01:00
} else if (
2023-01-18 15:47:29 +01:00
type === TransactionType . tokenMethodTransferFrom ||
type === TransactionType . tokenMethodTransfer
2020-11-03 00:41:28 +01:00
) {
2023-01-18 15:47:29 +01:00
category = TransactionGroupCategory . send ;
2022-01-10 17:23:53 +01:00
title = t ( 'sendSpecifiedTokens' , [
2023-02-16 20:23:29 +01:00
token ? . symbol || nft ? . name || t ( 'token' ) ,
2022-01-10 17:23:53 +01:00
] ) ;
2021-02-04 19:15:23 +01:00
recipientAddress = getTokenAddressParam ( tokenData ) ;
subtitle = t ( 'toAddress' , [ shortenAddress ( recipientAddress ) ] ) ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . tokenMethodSafeTransferFrom ) {
category = TransactionGroupCategory . send ;
2022-05-25 17:13:15 +02:00
title = t ( 'safeTransferFrom' ) ;
recipientAddress = getTokenAddressParam ( tokenData ) ;
subtitle = t ( 'toAddress' , [ shortenAddress ( recipientAddress ) ] ) ;
2023-01-18 15:47:29 +01:00
} else if ( type === TransactionType . simpleSend ) {
category = TransactionGroupCategory . send ;
2021-03-19 18:12:11 +01:00
title = t ( 'send' ) ;
2021-02-04 19:15:23 +01:00
subtitle = t ( 'toAddress' , [ shortenAddress ( recipientAddress ) ] ) ;
2021-05-14 23:03:14 +02:00
} else {
2021-07-23 01:13:40 +02:00
dispatch (
captureSingleException (
2021-05-21 14:01:45 +02:00
` useTransactionDisplayData does not recognize transaction type. Type received is: ${ type } ` ,
) ,
2021-05-14 23:03:14 +02:00
) ;
2020-05-26 22:49:11 +02:00
}
2021-02-04 19:15:23 +01:00
const primaryCurrencyPreferences = useUserPreferencedCurrency ( PRIMARY ) ;
const secondaryCurrencyPreferences = useUserPreferencedCurrency ( SECONDARY ) ;
2020-05-26 22:49:11 +02:00
const [ primaryCurrency ] = useCurrencyDisplay ( primaryValue , {
prefix ,
2020-10-06 20:28:38 +02:00
displayValue : primaryDisplayValue ,
suffix : primarySuffix ,
2020-05-26 22:49:11 +02:00
... primaryCurrencyPreferences ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-05-26 22:49:11 +02:00
const [ secondaryCurrency ] = useCurrencyDisplay ( primaryValue , {
prefix ,
2020-10-06 20:28:38 +02:00
displayValue : secondaryDisplayValue ,
hideLabel : isTokenCategory || Boolean ( swapTokenValue ) ,
2020-05-26 22:49:11 +02:00
... secondaryCurrencyPreferences ,
2021-02-04 19:15:23 +01:00
} ) ;
2020-05-26 22:49:11 +02:00
return {
title ,
category ,
date ,
subtitle ,
2020-06-17 18:38:15 +02:00
subtitleContainsOrigin ,
2020-11-03 00:41:28 +01:00
primaryCurrency :
2023-01-18 15:47:29 +01:00
type === TransactionType . swap && isPending ? '' : primaryCurrency ,
2020-05-26 22:49:11 +02:00
senderAddress ,
recipientAddress ,
2020-11-03 00:41:28 +01:00
secondaryCurrency :
2020-10-06 20:28:38 +02:00
( isTokenCategory && ! tokenFiatAmount ) ||
2023-01-18 15:47:29 +01:00
( type === TransactionType . swap && ! swapTokenFiatAmount )
2020-11-03 00:41:28 +01:00
? undefined
: secondaryCurrency ,
2020-10-07 23:59:38 +02:00
displayedStatusKey ,
2020-10-06 20:28:38 +02:00
isPending ,
2020-10-19 16:45:21 +02:00
isSubmitted ,
2021-02-04 19:15:23 +01:00
} ;
2020-05-26 22:49:11 +02:00
}