diff --git a/ui/app/helpers/utils/token-util.js b/ui/app/helpers/utils/token-util.js index 35a19a69f..3920045ac 100644 --- a/ui/app/helpers/utils/token-util.js +++ b/ui/app/helpers/utils/token-util.js @@ -68,6 +68,22 @@ async function getDecimals (tokenAddress) { return decimals } +export async function fetchSymbolAndDecimals (tokenAddress) { + let symbol, decimals + + try { + symbol = await getSymbol(tokenAddress) + decimals = await getDecimals(tokenAddress) + } catch (error) { + log.warn(`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`, error) + } + + return { + symbol: symbol || DEFAULT_SYMBOL, + decimals: decimals || DEFAULT_DECIMALS, + } +} + export async function getSymbolAndDecimals (tokenAddress, existingTokens = []) { const existingToken = existingTokens.find(({ address }) => tokenAddress === address) @@ -116,3 +132,8 @@ export function getTokenValue (tokenParams = []) { const valueData = tokenParams.find(param => param.name === '_value') return valueData && valueData.value } + +export function getTokenToAddress (tokenParams = []) { + const toAddressData = tokenParams.find(param => param.name === '_to') + return toAddressData && toAddressData.value +} diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js index c84053ec7..b65bda5b2 100644 --- a/ui/app/helpers/utils/transactions.util.js +++ b/ui/app/helpers/utils/transactions.util.js @@ -102,6 +102,20 @@ export function getFourBytePrefix (data = '') { return fourBytePrefix } +/** + * Given an transaction category, returns a boolean which indicates whether the transaction is calling an erc20 token method + * + * @param {string} transactionCategory - The category of transaction being evaluated + * @returns {boolean} - whether the transaction is calling an erc20 token method + */ +export function isTokenMethodAction (transactionCategory) { + return [ + TOKEN_METHOD_TRANSFER, + TOKEN_METHOD_APPROVE, + TOKEN_METHOD_TRANSFER_FROM, + ].includes(transactionCategory) +} + /** * Returns the action of a transaction as a key to be passed into the translator. * @param {Object} transaction - txData object @@ -122,11 +136,7 @@ export function getTransactionActionKey (transaction) { return DEPLOY_CONTRACT_ACTION_KEY } - const isTokenAction = [ - TOKEN_METHOD_TRANSFER, - TOKEN_METHOD_APPROVE, - TOKEN_METHOD_TRANSFER_FROM, - ].find(actionName => actionName === transactionCategory) + const isTokenAction = isTokenMethodAction(transactionCategory) const isNonTokenSmartContract = transactionCategory === CONTRACT_INTERACTION_KEY if (isTokenAction || isNonTokenSmartContract) { diff --git a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js index f5f30a460..fc5e2f90d 100644 --- a/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js +++ b/ui/app/pages/confirm-token-transaction-base/confirm-token-transaction-base.container.js @@ -1,27 +1,42 @@ import { connect } from 'react-redux' import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component' import { - tokenAmountAndToAddressSelector, contractExchangeRateSelector, } from '../../selectors/confirm-transaction' +import { tokenSelector } from '../../selectors/tokens' +import { + getTokenData, +} from '../../helpers/utils/transactions.util' +import { + calcTokenAmount, + getTokenToAddress, + getTokenValue, +} from '../../helpers/utils/token-util' + -const mapStateToProps = (state, ownProps) => { - const { tokenAmount: ownTokenAmount } = ownProps +const mapStateToProps = (state) => { const { confirmTransaction, metamask: { currentCurrency, conversionRate } } = state const { - txData: { txParams: { to: tokenAddress } = {} } = {}, - tokenProps: { tokenSymbol } = {}, + txData: { txParams: { to: tokenAddress, data } = {} } = {}, fiatTransactionTotal, ethTransactionTotal, } = confirmTransaction - const { tokenAmount, toAddress } = tokenAmountAndToAddressSelector(state) + + const tokens = tokenSelector(state) + const currentToken = tokens && tokens.find(({ address }) => tokenAddress === address) + const { decimals, symbol: tokenSymbol } = currentToken || {} + + const tokenData = getTokenData(data) + const tokenValue = tokenData && getTokenValue(tokenData.params) + const toAddress = tokenData && getTokenToAddress(tokenData.params) + const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString() const contractExchangeRate = contractExchangeRateSelector(state) return { toAddress, tokenAddress, - tokenAmount: typeof ownTokenAmount !== 'undefined' ? ownTokenAmount : tokenAmount, + tokenAmount, tokenSymbol, currentCurrency, conversionRate, diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.component.js b/ui/app/pages/confirm-transaction/confirm-transaction.component.js index cca86fa9b..c8819dac3 100644 --- a/ui/app/pages/confirm-transaction/confirm-transaction.component.js +++ b/ui/app/pages/confirm-transaction/confirm-transaction.component.js @@ -37,6 +37,8 @@ export default class ConfirmTransaction extends Component { getContractMethodData: PropTypes.func, transactionId: PropTypes.string, paramsTransactionId: PropTypes.string, + getTokenParams: PropTypes.func, + isTokenMethodAction: PropTypes.bool, } getParamsTransactionId () { @@ -49,11 +51,13 @@ export default class ConfirmTransaction extends Component { totalUnapprovedCount = 0, send = {}, history, - transaction: { txParams: { data } = {} } = {}, + transaction: { txParams: { data, to } = {} } = {}, fetchBasicGasAndTimeEstimates, getContractMethodData, transactionId, paramsTransactionId, + getTokenParams, + isTokenMethodAction, } = this.props if (!totalUnapprovedCount && !send.to) { @@ -63,6 +67,9 @@ export default class ConfirmTransaction extends Component { fetchBasicGasAndTimeEstimates() getContractMethodData(data) + if (isTokenMethodAction) { + getTokenParams(to) + } this.props.setTransactionToConfirm(transactionId || paramsTransactionId) } diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/pages/confirm-transaction/confirm-transaction.container.js index 993e880c2..6da855df2 100644 --- a/ui/app/pages/confirm-transaction/confirm-transaction.container.js +++ b/ui/app/pages/confirm-transaction/confirm-transaction.container.js @@ -5,12 +5,16 @@ import { setTransactionToConfirm, clearConfirmTransaction, } from '../../ducks/confirm-transaction/confirm-transaction.duck' +import { + isTokenMethodAction, +} from '../../helpers/utils/transactions.util' import { fetchBasicGasAndTimeEstimates, } from '../../ducks/gas/gas.duck' import { getContractMethodData, + getTokenParams, } from '../../store/actions' import ConfirmTransaction from './confirm-transaction.component' import { unconfirmedTransactionsListSelector } from '../../selectors/confirm-transaction' @@ -25,6 +29,7 @@ const mapStateToProps = (state, ownProps) => { const transaction = totalUnconfirmed ? unapprovedTxs[id] || unconfirmedTransactions[totalUnconfirmed - 1] : {} + const { id: transactionId, transactionCategory } = transaction return { totalUnapprovedCount: totalUnconfirmed, @@ -33,9 +38,10 @@ const mapStateToProps = (state, ownProps) => { unapprovedTxs, id, paramsTransactionId: id && String(id), - transactionId: transaction.id && String(transaction.id), + transactionId: transactionId && String(transactionId), unconfirmedTransactions, transaction, + isTokenMethodAction: isTokenMethodAction(transactionCategory), } } @@ -47,6 +53,7 @@ const mapDispatchToProps = dispatch => { clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()), getContractMethodData: (data) => dispatch(getContractMethodData(data)), + getTokenParams: (tokenAddress) => dispatch(getTokenParams(tokenAddress)), } } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index aff2636ba..d3cc7efca 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -9,6 +9,7 @@ const { const ethUtil = require('ethereumjs-util') const { fetchLocale } = require('../helpers/utils/i18n-helper') const { getMethodDataAsync } = require('../helpers/utils/transactions.util') +const { fetchSymbolAndDecimals } = require('../helpers/utils/token-util') const log = require('loglevel') const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../../app/scripts/lib/enums') const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util') @@ -367,6 +368,12 @@ var actions = { loadingMethoDataFinished, LOADING_METHOD_DATA_STARTED: 'LOADING_METHOD_DATA_STARTED', LOADING_METHOD_DATA_FINISHED: 'LOADING_METHOD_DATA_FINISHED', + + getTokenParams, + loadingTokenParamsStarted, + LOADING_TOKEN_PARAMS_STARTED: 'LOADING_TOKEN_PARAMS_STARTED', + loadingTokenParamsFinished, + LOADING_TOKEN_PARAMS_FINISHED: 'LOADING_TOKEN_PARAMS_FINISHED', } module.exports = actions @@ -2816,3 +2823,39 @@ function getContractMethodData (data = '') { }) } } + +function loadingTokenParamsStarted () { + return { + type: actions.LOADING_TOKEN_PARAMS_STARTED, + } +} + +function loadingTokenParamsFinished () { + return { + type: actions.LOADING_TOKEN_PARAMS_FINISHED, + } +} + +function getTokenParams (tokenAddress) { + return (dispatch, getState) => { + const existingTokens = getState().metamask.tokens + const existingToken = existingTokens.find(({ address }) => tokenAddress === address) + + if (existingToken) { + return Promise.resolve({ + symbol: existingToken.symbol, + decimals: existingToken.decimals, + }) + } + + dispatch(actions.loadingTokenParamsStarted()) + log.debug(`loadingTokenParams`) + + + return fetchSymbolAndDecimals(tokenAddress, existingTokens) + .then(({ symbol, decimals }) => { + dispatch(actions.addToken(tokenAddress, symbol, decimals)) + dispatch(actions.loadingTokenParamsFinished()) + }) + } +}