2021-02-04 19:15:23 +01:00
import log from 'loglevel' ;
import BigNumber from 'bignumber.js' ;
2021-07-06 19:48:49 +02:00
import {
conversionUtil ,
multiplyCurrencies ,
} from '../../../shared/modules/conversion.utils' ;
2022-03-09 15:38:12 +01:00
import { getTokenStandardAndDetails } from '../../store/actions' ;
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils' ;
2022-03-17 19:35:40 +01:00
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils' ;
2022-07-23 16:37:31 +02:00
import { ERC20 } from '../../../shared/constants/transaction' ;
2021-02-04 19:15:23 +01:00
import * as util from './util' ;
import { formatCurrency } from './confirm-tx.util' ;
2017-10-27 19:39:40 +02:00
2021-02-04 19:15:23 +01:00
const DEFAULT _SYMBOL = '' ;
2018-08-23 01:54:18 +02:00
2020-11-03 00:41:28 +01:00
async function getSymbolFromContract ( tokenAddress ) {
2021-02-04 19:15:23 +01:00
const token = util . getContractAtAddress ( tokenAddress ) ;
2018-08-23 01:54:18 +02:00
try {
2021-02-04 19:15:23 +01:00
const result = await token . symbol ( ) ;
return result [ 0 ] ;
2018-08-23 01:54:18 +02:00
} catch ( error ) {
2020-11-03 00:41:28 +01:00
log . warn (
` symbol() call for token at address ${ tokenAddress } resulted in error: ` ,
error ,
2021-02-04 19:15:23 +01:00
) ;
return undefined ;
2018-08-23 01:54:18 +02:00
}
}
2020-11-03 00:41:28 +01:00
async function getDecimalsFromContract ( tokenAddress ) {
2021-02-04 19:15:23 +01:00
const token = util . getContractAtAddress ( tokenAddress ) ;
2018-08-23 01:54:18 +02:00
try {
2021-02-04 19:15:23 +01:00
const result = await token . decimals ( ) ;
const decimalsBN = result [ 0 ] ;
return decimalsBN ? . toString ( ) ;
2018-08-23 01:54:18 +02:00
} catch ( error ) {
2020-11-03 00:41:28 +01:00
log . warn (
` decimals() call for token at address ${ tokenAddress } resulted in error: ` ,
error ,
2021-02-04 19:15:23 +01:00
) ;
return undefined ;
2018-08-23 01:54:18 +02:00
}
}
2022-07-14 00:15:38 +02:00
export function getTokenMetadata ( tokenAddress , tokenList ) {
2022-08-10 03:26:25 +02:00
return tokenAddress && tokenList [ tokenAddress . toLowerCase ( ) ] ;
2018-08-23 01:54:18 +02:00
}
2021-09-09 22:56:27 +02:00
async function getSymbol ( tokenAddress , tokenList ) {
2021-02-04 19:15:23 +01:00
let symbol = await getSymbolFromContract ( tokenAddress ) ;
2018-08-23 01:54:18 +02:00
if ( ! symbol ) {
2021-09-09 22:56:27 +02:00
const contractMetadataInfo = getTokenMetadata ( tokenAddress , tokenList ) ;
2018-08-23 01:54:18 +02:00
if ( contractMetadataInfo ) {
2021-02-04 19:15:23 +01:00
symbol = contractMetadataInfo . symbol ;
2017-10-27 19:39:40 +02:00
}
2018-08-23 01:54:18 +02:00
}
2017-10-27 19:39:40 +02:00
2021-02-04 19:15:23 +01:00
return symbol ;
2018-08-23 01:54:18 +02:00
}
2017-10-27 19:39:40 +02:00
2021-09-09 22:56:27 +02:00
async function getDecimals ( tokenAddress , tokenList ) {
2021-02-04 19:15:23 +01:00
let decimals = await getDecimalsFromContract ( tokenAddress ) ;
2018-08-23 01:54:18 +02:00
if ( ! decimals || decimals === '0' ) {
2021-09-09 22:56:27 +02:00
const contractMetadataInfo = getTokenMetadata ( tokenAddress , tokenList ) ;
2018-08-23 01:54:18 +02:00
if ( contractMetadataInfo ) {
2021-07-26 16:08:10 +02:00
decimals = contractMetadataInfo . decimals ? . toString ( ) ;
2018-08-23 01:54:18 +02:00
}
2018-04-28 01:15:36 +02:00
}
2018-08-23 01:54:18 +02:00
2021-02-04 19:15:23 +01:00
return decimals ;
2018-04-28 01:15:36 +02:00
}
2017-10-27 19:39:40 +02:00
2021-09-09 22:56:27 +02:00
export async function getSymbolAndDecimals ( tokenAddress , tokenList ) {
2021-02-04 19:15:23 +01:00
let symbol , decimals ;
2018-08-23 01:54:18 +02:00
2018-04-28 01:15:36 +02:00
try {
2021-09-09 22:56:27 +02:00
symbol = await getSymbol ( tokenAddress , tokenList ) ;
decimals = await getDecimals ( tokenAddress , tokenList ) ;
2018-08-23 01:54:18 +02:00
} catch ( error ) {
2020-11-03 00:41:28 +01:00
log . warn (
` symbol() and decimal() calls for token at address ${ tokenAddress } resulted in error: ` ,
error ,
2021-02-04 19:15:23 +01:00
) ;
2018-08-23 01:54:18 +02:00
}
2018-04-28 01:15:36 +02:00
2018-08-23 01:54:18 +02:00
return {
symbol : symbol || DEFAULT _SYMBOL ,
2021-05-18 19:23:54 +02:00
decimals ,
2021-02-04 19:15:23 +01:00
} ;
2018-08-23 01:54:18 +02:00
}
2020-11-03 00:41:28 +01:00
export function tokenInfoGetter ( ) {
2021-02-04 19:15:23 +01:00
const tokens = { } ;
2017-10-27 19:39:40 +02:00
2021-09-09 22:56:27 +02:00
return async ( address , tokenList ) => {
2018-08-23 01:54:18 +02:00
if ( tokens [ address ] ) {
2021-02-04 19:15:23 +01:00
return tokens [ address ] ;
2018-08-23 01:54:18 +02:00
}
2017-10-27 19:39:40 +02:00
2021-09-09 22:56:27 +02:00
tokens [ address ] = await getSymbolAndDecimals ( address , tokenList ) ;
2018-08-23 01:54:18 +02:00
2021-02-04 19:15:23 +01:00
return tokens [ address ] ;
} ;
2017-10-27 19:39:40 +02:00
}
2020-11-03 00:41:28 +01:00
export function calcTokenAmount ( value , decimals ) {
2021-02-04 19:15:23 +01:00
const multiplier = Math . pow ( 10 , Number ( decimals || 0 ) ) ;
return new BigNumber ( String ( value ) ) . div ( multiplier ) ;
2017-10-30 18:48:50 +01:00
}
2018-10-09 02:05:57 +02:00
2020-11-03 00:41:28 +01:00
export function calcTokenValue ( value , decimals ) {
2021-02-04 19:15:23 +01:00
const multiplier = Math . pow ( 10 , Number ( decimals || 0 ) ) ;
return new BigNumber ( String ( value ) ) . times ( multiplier ) ;
2019-11-05 16:13:48 +01:00
}
2020-08-22 04:29:19 +02:00
/ * *
* Attempts to get the address parameter of the given token transaction data
* ( i . e . function call ) per the Human Standard Token ABI , in the following
* order :
* - The '_to' parameter , if present
* - The first parameter , if present
*
2022-07-27 15:28:05 +02:00
* @ param { object } tokenData - ethers Interface token data .
2020-08-22 04:29:19 +02:00
* @ returns { string | undefined } A lowercase address string .
* /
2020-11-03 00:41:28 +01:00
export function getTokenAddressParam ( tokenData = { } ) {
2022-03-17 19:35:40 +01:00
const value =
tokenData ? . args ? . _to || tokenData ? . args ? . to || tokenData ? . args ? . [ 0 ] ;
2021-02-04 19:15:23 +01:00
return value ? . toString ( ) . toLowerCase ( ) ;
2018-10-09 02:05:57 +02:00
}
2019-06-28 05:53:12 +02:00
2020-08-22 04:29:19 +02:00
/ * *
* Gets the '_value' parameter of the given token transaction data
* ( i . e function call ) per the Human Standard Token ABI , if present .
*
2022-07-27 15:28:05 +02:00
* @ param { object } tokenData - ethers Interface token data .
2020-08-22 04:29:19 +02:00
* @ returns { string | undefined } A decimal string value .
* /
2020-11-03 00:41:28 +01:00
export function getTokenValueParam ( tokenData = { } ) {
2021-02-04 19:15:23 +01:00
return tokenData ? . args ? . _value ? . toString ( ) ;
2019-06-28 05:53:12 +02:00
}
2020-06-10 20:04:56 +02:00
2022-07-23 16:37:31 +02:00
/ * *
* Gets either the '_tokenId' parameter or the 'id' param of the passed token transaction data . ,
* These are the parsed tokenId values returned by ` parseStandardTokenTransactionData ` as defined
* in the ERC721 and ERC1155 ABIs from metamask - eth - abis ( https : //github.com/MetaMask/metamask-eth-abis/tree/main/src/abis)
*
2022-07-27 15:28:05 +02:00
* @ param { object } tokenData - ethers Interface token data .
2022-07-23 16:37:31 +02:00
* @ returns { string | undefined } A decimal string value .
* /
export function getTokenIdParam ( tokenData = { } ) {
return (
tokenData ? . args ? . _tokenId ? . toString ( ) ? ? tokenData ? . args ? . id ? . toString ( )
) ;
2022-07-12 01:32:55 +02:00
}
2022-07-23 16:37:31 +02:00
/ * *
* Gets the '_approved' parameter of the given token transaction data
* ( i . e function call ) per the Human Standard Token ABI , if present .
*
2022-07-27 15:28:05 +02:00
* @ param { object } tokenData - ethers Interface token data .
2022-07-23 16:37:31 +02:00
* @ returns { boolean | undefined } A boolean indicating whether the function is being called to approve or revoke access .
* /
export function getTokenApprovedParam ( tokenData = { } ) {
return tokenData ? . args ? . _approved ;
2020-10-06 20:28:38 +02:00
}
2020-06-10 20:04:56 +02:00
/ * *
2020-10-06 20:28:38 +02:00
* Get the token balance converted to fiat and optionally formatted for display
2020-06-10 20:04:56 +02:00
*
* @ param { number } [ contractExchangeRate ] - The exchange rate between the current token and the native currency
* @ param { number } conversionRate - The exchange rate between the current fiat currency and the native currency
* @ param { string } currentCurrency - The currency code for the user ' s chosen fiat currency
* @ param { string } [ tokenAmount ] - The current token balance
* @ param { string } [ tokenSymbol ] - The token symbol
2020-10-06 20:28:38 +02:00
* @ param { boolean } [ formatted ] - Whether the return value should be formatted or not
* @ param { boolean } [ hideCurrencySymbol ] - excludes the currency symbol in the result if true
* @ returns { string | undefined } The token amount in the user ' s chosen fiat currency , optionally formatted and localize
2020-06-10 20:04:56 +02:00
* /
2020-11-03 00:41:28 +01:00
export function getTokenFiatAmount (
2020-06-10 20:04:56 +02:00
contractExchangeRate ,
conversionRate ,
currentCurrency ,
tokenAmount ,
2020-07-14 17:20:41 +02:00
tokenSymbol ,
2020-10-06 20:28:38 +02:00
formatted = true ,
hideCurrencySymbol = false ,
2020-06-10 20:04:56 +02:00
) {
// If the conversionRate is 0 (i.e. unknown) or the contract exchange rate
// is currently unknown, the fiat amount cannot be calculated so it is not
// shown to the user
2020-11-03 00:41:28 +01:00
if (
conversionRate <= 0 ||
! contractExchangeRate ||
tokenAmount === undefined
) {
2021-02-04 19:15:23 +01:00
return undefined ;
2020-06-10 20:04:56 +02:00
}
const currentTokenToFiatRate = multiplyCurrencies (
contractExchangeRate ,
2020-07-14 17:20:41 +02:00
conversionRate ,
2020-11-13 07:08:18 +01:00
{
multiplicandBase : 10 ,
multiplierBase : 10 ,
} ,
2021-02-04 19:15:23 +01:00
) ;
2020-06-10 20:04:56 +02:00
const currentTokenInFiat = conversionUtil ( tokenAmount , {
fromNumericBase : 'dec' ,
fromCurrency : tokenSymbol ,
toCurrency : currentCurrency . toUpperCase ( ) ,
numberOfDecimals : 2 ,
conversionRate : currentTokenToFiatRate ,
2021-02-04 19:15:23 +01:00
} ) ;
let result ;
2020-10-06 20:28:38 +02:00
if ( hideCurrencySymbol ) {
2021-02-04 19:15:23 +01:00
result = formatCurrency ( currentTokenInFiat , currentCurrency ) ;
2020-10-06 20:28:38 +02:00
} else if ( formatted ) {
2020-11-03 00:41:28 +01:00
result = ` ${ formatCurrency (
currentTokenInFiat ,
currentCurrency ,
2021-02-04 19:15:23 +01:00
) } $ { currentCurrency . toUpperCase ( ) } ` ;
2020-10-06 20:28:38 +02:00
} else {
2021-02-04 19:15:23 +01:00
result = currentTokenInFiat ;
2020-10-06 20:28:38 +02:00
}
2021-02-04 19:15:23 +01:00
return result ;
2020-06-10 20:04:56 +02:00
}
2022-03-09 15:38:12 +01:00
export async function getAssetDetails (
tokenAddress ,
currentUserAddress ,
transactionData ,
existingCollectibles ,
) {
2022-03-17 19:35:40 +01:00
const tokenData = parseStandardTokenTransactionData ( transactionData ) ;
2022-03-09 15:38:12 +01:00
if ( ! tokenData ) {
throw new Error ( 'Unable to detect valid token data' ) ;
}
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.
let tokenId =
getTokenIdParam ( tokenData ) ? . toString ( ) ? ? getTokenValueParam ( tokenData ) ;
const toAddress = getTokenAddressParam ( tokenData ) ;
2022-03-09 15:38:12 +01:00
let tokenDetails ;
2022-07-23 16:37:31 +02:00
// if a tokenId is present check if there is a collectible in state matching the address/tokenId
// and avoid unnecessary network requests to query token details we already have
if ( existingCollectibles ? . length && tokenId ) {
const existingCollectible = existingCollectibles . find (
( { address , tokenId : _tokenId } ) =>
isEqualCaseInsensitive ( tokenAddress , address ) && _tokenId === tokenId ,
) ;
if ( existingCollectible ) {
return {
toAddress ,
... existingCollectible ,
} ;
}
}
2022-03-09 15:38:12 +01:00
try {
tokenDetails = await getTokenStandardAndDetails (
tokenAddress ,
currentUserAddress ,
tokenId ,
) ;
} catch ( error ) {
log . warn ( error ) ;
2022-07-23 16:37:31 +02:00
// if we can't determine any token standard or details return the data we can extract purely from the parsed transaction data
return { toAddress , tokenId } ;
2022-03-09 15:38:12 +01:00
}
2022-07-23 16:37:31 +02:00
const tokenAmount =
tokenData &&
tokenDetails ? . decimals &&
calcTokenAmount (
getTokenValueParam ( tokenData ) ,
tokenDetails ? . decimals ,
) . toString ( 10 ) ;
const decimals =
tokenDetails ? . decimals && Number ( tokenDetails . decimals ? . toString ( 10 ) ) ;
if ( tokenDetails ? . standard === ERC20 ) {
tokenId = undefined ;
2022-03-09 15:38:12 +01:00
}
2022-07-23 16:37:31 +02:00
// else if not a collectible already in state or standard === ERC20 return tokenDetails and tokenId
return {
tokenAmount ,
toAddress ,
decimals ,
tokenId ,
... tokenDetails ,
} ;
2022-03-09 15:38:12 +01:00
}