diff options
Diffstat (limited to 'ui/app/components/pending-tx/confirm-send-token.js')
-rw-r--r-- | ui/app/components/pending-tx/confirm-send-token.js | 696 |
1 files changed, 0 insertions, 696 deletions
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js deleted file mode 100644 index ee066b8f4..000000000 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ /dev/null @@ -1,696 +0,0 @@ -const Component = require('react').Component -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const inherits = require('util').inherits -const tokenAbi = require('human-standard-token-abi') -const abiDecoder = require('abi-decoder') -abiDecoder.addABI(tokenAbi) -const actions = require('../../actions') -const clone = require('clone') -const Identicon = require('../identicon') -const GasFeeDisplay = require('../send/gas-fee-display-v2.js') -const NetworkDisplay = require('../network-display') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const { - conversionUtil, - multiplyCurrencies, - addCurrencies, -} = require('../../conversion-util') -const { - calcGasTotal, - isBalanceSufficient, -} = require('../send_/send.utils') -const { - calcTokenAmount, -} = require('../../token-util') -const classnames = require('classnames') -const currencyFormatter = require('currency-formatter') -const currencies = require('currency-formatter/currencies') - -const { MIN_GAS_PRICE_HEX } = require('../send_/send.constants') - -const { - getTokenExchangeRate, - getSelectedAddress, - getSelectedTokenContract, -} = require('../../selectors') -const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') - -import { - updateSendErrors, -} from '../../ducks/send.duck' - -const { - ENVIRONMENT_TYPE_POPUP, - ENVIRONMENT_TYPE_NOTIFICATION, -} = require('../../../../app/scripts/lib/enums') - -ConfirmSendToken.contextTypes = { - t: PropTypes.func, -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(ConfirmSendToken) - - -function mapStateToProps (state, ownProps) { - const { token: { address }, txData } = ownProps - const { txParams } = txData || {} - const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) - - const { - conversionRate, - identities, - currentCurrency, - } = state.metamask - const accounts = state.metamask.accounts - const selectedAddress = getSelectedAddress(state) - const tokenExchangeRate = getTokenExchangeRate(state, address) - const { balance } = accounts[selectedAddress] - return { - conversionRate, - identities, - selectedAddress, - tokenExchangeRate, - tokenData: tokenData || {}, - currentCurrency: currentCurrency.toUpperCase(), - send: state.metamask.send, - tokenContract: getSelectedTokenContract(state), - balance, - } -} - -function mapDispatchToProps (dispatch, ownProps) { - return { - backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)), - cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), - editTransaction: txMeta => { - const { token: { address } } = ownProps - const { txParams = {}, id } = txMeta - const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) || {} - const { params = [] } = tokenData - const { value: to } = params[0] || {} - const { value: tokenAmountInDec } = params[1] || {} - const tokenAmountInHex = conversionUtil(tokenAmountInDec, { - fromNumericBase: 'dec', - toNumericBase: 'hex', - }) - const { - gas: gasLimit, - gasPrice, - } = txParams - dispatch(actions.setSelectedToken(address)) - dispatch(actions.updateSend({ - gasLimit, - gasPrice, - gasTotal: null, - to, - amount: tokenAmountInHex, - errors: { to: null, amount: null }, - editingTransactionId: id && id.toString(), - token: ownProps.token, - })) - dispatch(actions.showSendTokenPage()) - }, - showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { - const { id, txParams, lastGasPrice } = txMeta - const { gas: txGasLimit, gasPrice: txGasPrice } = txParams - const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) - const { params = [] } = tokenData - const { value: to } = params[0] || {} - const { value: tokenAmountInDec } = params[1] || {} - const tokenAmountInHex = conversionUtil(tokenAmountInDec, { - fromNumericBase: 'dec', - toNumericBase: 'hex', - }) - - let forceGasMin - if (lastGasPrice) { - forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { - multiplicandBase: 16, - multiplierBase: 10, - toNumericBase: 'hex', - fromDenomination: 'WEI', - })) - } - - dispatch(actions.updateSend({ - gasLimit: sendGasLimit || txGasLimit, - gasPrice: sendGasPrice || txGasPrice, - editingTransactionId: id, - gasTotal: sendGasTotal, - to, - amount: tokenAmountInHex, - forceGasMin, - })) - dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) - }, - updateSendErrors: error => dispatch(updateSendErrors(error)), - } -} - -inherits(ConfirmSendToken, Component) -function ConfirmSendToken () { - Component.call(this) - this.state = {} - this.onSubmit = this.onSubmit.bind(this) -} - -ConfirmSendToken.prototype.editTransaction = function (txMeta) { - const { editTransaction, history } = this.props - editTransaction(txMeta) - history.push(SEND_ROUTE) -} - -ConfirmSendToken.prototype.updateComponentSendErrors = function (prevProps) { - const { - balance: oldBalance, - conversionRate: oldConversionRate, - } = prevProps - const { - updateSendErrors, - balance, - conversionRate, - send: { - errors: { - simulationFails, - }, - }, - } = this.props - const txMeta = this.gatherTxMeta() - - const shouldUpdateBalanceSendErrors = balance && [ - balance !== oldBalance, - conversionRate !== oldConversionRate, - ].some(x => Boolean(x)) - - if (shouldUpdateBalanceSendErrors) { - const balanceIsSufficient = this.isBalanceSufficient(txMeta) - updateSendErrors({ - insufficientFunds: balanceIsSufficient ? false : this.context.t('insufficientFunds'), - }) - } - - const shouldUpdateSimulationSendError = Boolean(txMeta.simulationFails) !== Boolean(simulationFails) - - if (shouldUpdateSimulationSendError) { - updateSendErrors({ - simulationFails: !txMeta.simulationFails ? false : this.context.t('transactionError'), - }) - } -} - -ConfirmSendToken.prototype.componentWillMount = function () { - const { tokenContract, selectedAddress } = this.props - tokenContract && tokenContract - .balanceOf(selectedAddress) - .then(usersToken => { - }) - this.updateComponentSendErrors({}) -} - -ConfirmSendToken.prototype.componentDidUpdate = function (prevProps) { - this.updateComponentSendErrors(prevProps) -} - -ConfirmSendToken.prototype.getAmount = function () { - const { - conversionRate, - tokenExchangeRate, - token, - tokenData, - send: { amount, editingTransactionId }, - } = this.props - const { params = [] } = tokenData - let { value } = params[1] || {} - const { decimals } = token - - if (editingTransactionId) { - value = conversionUtil(amount, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - }) - } - - const sendTokenAmount = calcTokenAmount(value, decimals) - - return { - fiat: tokenExchangeRate - ? +(sendTokenAmount * tokenExchangeRate * conversionRate).toFixed(2) - : null, - token: typeof value === 'undefined' - ? this.context.t('unknown') - : +sendTokenAmount.toFixed(decimals), - } - -} - -ConfirmSendToken.prototype.getGasFee = function () { - const { conversionRate, tokenExchangeRate, token, currentCurrency } = this.props - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} - const { decimals } = token - - const gas = txParams.gas - const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_HEX - const gasTotal = multiplyCurrencies(gas, gasPrice, { - multiplicandBase: 16, - multiplierBase: 16, - }) - - const FIAT = conversionUtil(gasTotal, { - fromNumericBase: 'BN', - toNumericBase: 'dec', - fromDenomination: 'WEI', - fromCurrency: 'ETH', - toCurrency: currentCurrency, - numberOfDecimals: 2, - conversionRate, - }) - const ETH = conversionUtil(gasTotal, { - fromNumericBase: 'BN', - toNumericBase: 'dec', - fromDenomination: 'WEI', - fromCurrency: 'ETH', - toCurrency: 'ETH', - numberOfDecimals: 6, - conversionRate, - }) - const tokenGas = multiplyCurrencies(gas, gasPrice, { - toNumericBase: 'dec', - multiplicandBase: 16, - multiplierBase: 16, - toCurrency: 'BAT', - conversionRate: tokenExchangeRate, - invertConversionRate: true, - fromDenomination: 'WEI', - numberOfDecimals: decimals || 4, - }) - - return { - fiat: +Number(FIAT).toFixed(2), - eth: ETH, - token: tokenExchangeRate - ? tokenGas - : null, - gasFeeInHex: gasTotal.toString(16), - } -} - -ConfirmSendToken.prototype.getData = function () { - const { identities, tokenData } = this.props - const { params = [] } = tokenData - const { value } = params[0] || {} - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} - - return { - from: { - address: txParams.from, - name: identities[txParams.from].name, - }, - to: { - address: value, - name: identities[value] ? identities[value].name : this.context.t('newRecipient'), - }, - memo: txParams.memo || '', - } -} - -ConfirmSendToken.prototype.renderHeroAmount = function () { - const { token: { symbol }, currentCurrency } = this.props - const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() - const txMeta = this.gatherTxMeta() - const txParams = txMeta.txParams || {} - const { memo = '' } = txParams - - const convertedAmountInFiat = this.convertToRenderableCurrency(fiatAmount, currentCurrency) - - return fiatAmount - ? ( - h('div.confirm-send-token__hero-amount-wrapper', [ - h('h3.flex-center.confirm-screen-send-amount', `${convertedAmountInFiat}`), - h('h3.flex-center.confirm-screen-send-amount-currency', currentCurrency), - h('div.flex-center.confirm-memo-wrapper', [ - h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), - ]), - ]) - ) - : ( - h('div.confirm-send-token__hero-amount-wrapper', [ - h('h3.flex-center.confirm-screen-send-amount', tokenAmount), - h('h3.flex-center.confirm-screen-send-amount-currency', symbol), - h('div.flex-center.confirm-memo-wrapper', [ - h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), - ]), - ]) - ) -} - -ConfirmSendToken.prototype.renderGasFee = function () { - const { - currentCurrency: convertedCurrency, - conversionRate, - send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, - showCustomizeGasModal, - } = this.props - const txMeta = this.gatherTxMeta() - const { gasFeeInHex } = this.getGasFee() - - return ( - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('gasFee') ]), - h('div.confirm-screen-section-column', [ - h(GasFeeDisplay, { - gasTotal: gasTotal || gasFeeInHex, - conversionRate, - convertedCurrency, - onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), - }), - ]), - ]) - ) -} - -ConfirmSendToken.prototype.renderTotalPlusGas = function () { - const { token: { symbol }, currentCurrency, send: { errors } } = this.props - const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() - const { fiat: fiatGas, token: tokenGas } = this.getGasFee() - - const totalInFIAT = fiatAmount && fiatGas && addCurrencies(fiatAmount, fiatGas) - const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency) - - return fiatAmount && fiatGas - ? ( - h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ - h('div.confirm-screen-section-column', [ - h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]), - h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), - ]), - - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency}`), - h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`), - ]), - ]) - ) - : ( - h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ - h('div', { - className: classnames({ - 'confirm-screen-section-column--with-error': errors['insufficientFunds'], - 'confirm-screen-section-column': !errors['insufficientFunds'], - }), - }, [ - h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]), - h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), - ]), - - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`), - h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} ${this.context.t('gas')}`), - ]), - - this.renderErrorMessage('insufficientFunds'), - ]) - ) -} - -ConfirmSendToken.prototype.renderErrorMessage = function (message) { - const { send: { errors } } = this.props - - return errors[message] - ? h('div.confirm-screen-error', [ errors[message] ]) - : null -} - -ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, currencyCode) { - const upperCaseCurrencyCode = currencyCode.toUpperCase() - - return currencies.find(currency => currency.code === upperCaseCurrencyCode) - ? currencyFormatter.format(Number(value), { - code: upperCaseCurrencyCode, - }) - : value -} - -ConfirmSendToken.prototype.renderHeaderRow = function (isTxReprice) { - const windowType = window.METAMASK_UI_TYPE - const isFullScreen = windowType !== ENVIRONMENT_TYPE_NOTIFICATION && - windowType !== ENVIRONMENT_TYPE_POPUP - - if (isTxReprice && isFullScreen) { - return null - } - - return ( - h('.page-container__header-row', [ - h('span.page-container__back-button', { - onClick: () => this.editTransaction(), - style: { - visibility: isTxReprice ? 'hidden' : 'initial', - }, - }, 'Edit'), - !isFullScreen && h(NetworkDisplay), - ]) - ) -} - -ConfirmSendToken.prototype.renderHeader = function (isTxReprice) { - const title = isTxReprice ? this.context.t('speedUpTitle') : this.context.t('confirm') - const subtitle = isTxReprice - ? this.context.t('speedUpSubtitle') - : this.context.t('pleaseReviewTransaction') - - return ( - h('.page-container__header', [ - this.renderHeaderRow(isTxReprice), - h('.page-container__title', title), - h('.page-container__subtitle', subtitle), - ]) - ) -} - -ConfirmSendToken.prototype.render = function () { - const txMeta = this.gatherTxMeta() - const { - from: { - address: fromAddress, - name: fromName, - }, - to: { - address: toAddress, - name: toName, - }, - } = this.getData() - - const isTxReprice = Boolean(txMeta.lastGasPrice) - - return ( - h('div.confirm-screen-container.confirm-send-token', [ - // Main Send token Card - h('div.page-container', [ - this.renderHeader(isTxReprice), - h('.page-container__content', [ - h('div.flex-row.flex-center.confirm-screen-identicons', [ - h('div.confirm-screen-account-wrapper', [ - h( - Identicon, - { - address: fromAddress, - diameter: 60, - }, - ), - h('span.confirm-screen-account-name', fromName), - // h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)), - ]), - h('i.fa.fa-arrow-right.fa-lg'), - h('div.confirm-screen-account-wrapper', [ - h( - Identicon, - { - address: toAddress, - diameter: 60, - }, - ), - h('span.confirm-screen-account-name', toName), - // h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)), - ]), - ]), - - // h('h3.flex-center.confirm-screen-sending-to-message', { - // style: { - // textAlign: 'center', - // fontSize: '16px', - // }, - // }, [ - // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`, - // ]), - - this.renderHeroAmount(), - - h('div.confirm-screen-rows', [ - h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('from') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', fromName), - h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`), - ]), - ]), - - toAddress && h('section.flex-row.flex-center.confirm-screen-row', [ - h('span.confirm-screen-label.confirm-screen-section-column', [ this.context.t('to') ]), - h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', toName), - h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`), - ]), - ]), - - this.renderGasFee(), - - this.renderTotalPlusGas(), - - ]), - - ]), - - h('form#pending-tx-form', { - className: 'confirm-screen-form', - onSubmit: this.onSubmit, - }, [ - this.renderErrorMessage('simulationFails'), - h('.page-container__footer', [ - // Cancel Button - h('button.btn-cancel.page-container__footer-button.allcaps', { - onClick: (event) => this.cancel(event, txMeta), - }, this.context.t('cancel')), - - // Accept Button - h('button.btn-confirm.page-container__footer-button.allcaps', { - onClick: event => this.onSubmit(event), - }, [this.context.t('confirm')]), - ]), - ]), - ]), - ]) - ) -} - -ConfirmSendToken.prototype.onSubmit = function (event) { - event.preventDefault() - const { updateSendErrors } = this.props - const txMeta = this.gatherTxMeta() - const valid = this.checkValidity() - const balanceIsSufficient = this.isBalanceSufficient(txMeta) - this.setState({ valid, submitting: true }) - - if (valid && this.verifyGasParams() && balanceIsSufficient) { - this.props.sendTransaction(txMeta, event) - } else if (!balanceIsSufficient) { - updateSendErrors({ insufficientFunds: 'insufficientFunds' }) - } else { - updateSendErrors({ invalidGasParams: 'invalidGasParams' }) - this.setState({ submitting: false }) - } -} - -ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) { - const { - balance, - conversionRate, - } = this.props - const { - txParams: { - gas, - gasPrice, - }, - } = txMeta - const gasTotal = calcGasTotal(gas, gasPrice) - - return isBalanceSufficient({ - amount: '0', - gasTotal, - balance, - conversionRate, - }) -} - - -ConfirmSendToken.prototype.cancel = function (event, txMeta) { - event.preventDefault() - const { cancelTransaction } = this.props - - cancelTransaction(txMeta) - .then(() => this.props.history.push(DEFAULT_ROUTE)) -} - -ConfirmSendToken.prototype.checkValidity = function () { - const form = this.getFormEl() - const valid = form.checkValidity() - return valid -} - -ConfirmSendToken.prototype.getFormEl = function () { - const form = document.querySelector('form#pending-tx-form') - // Stub out form for unit tests: - if (!form) { - return { checkValidity () { return true } } - } - return form -} - -// After a customizable state value has been updated, -ConfirmSendToken.prototype.gatherTxMeta = function () { - const props = this.props - const state = this.state - const txData = clone(state.txData) || clone(props.txData) - - const { gasPrice: sendGasPrice, gasLimit: sendGasLimit } = props.send - const { - lastGasPrice, - txParams: { - gasPrice: txGasPrice, - gas: txGasLimit, - }, - } = txData - - let forceGasMin - if (lastGasPrice) { - forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { - multiplicandBase: 16, - multiplierBase: 10, - toNumericBase: 'hex', - })) - } - - txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice - txData.txParams.gas = sendGasLimit || txGasLimit - - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) - return txData -} - -ConfirmSendToken.prototype.verifyGasParams = function () { - // We call this in case the gas has not been modified at all - if (!this.state) { return true } - return ( - this._notZeroOrEmptyString(this.state.gas) && - this._notZeroOrEmptyString(this.state.gasPrice) - ) -} - -ConfirmSendToken.prototype._notZeroOrEmptyString = function (obj) { - return obj !== '' && obj !== '0x0' -} - -ConfirmSendToken.prototype.bnMultiplyByFraction = function (targetBN, numerator, denominator) { - const numBN = new BN(numerator) - const denomBN = new BN(denominator) - return targetBN.mul(numBN).div(denomBN) -} |