1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge pull request #3762 from danjm/i3471-checkbalanceonconfirmscreen

Checking for sufficient balance in tx confirmation screen
This commit is contained in:
kumavis 2018-03-30 18:36:08 -07:00 committed by GitHub
commit 2e57c36f36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 21 deletions

View File

@ -65,6 +65,7 @@ function mapDispatchToProps (dispatch) {
updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)),
updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)), updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)),
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
} }
} }
@ -112,6 +113,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
selectedToken, selectedToken,
balance, balance,
updateSendAmount, updateSendAmount,
updateSendErrors,
} = this.props } = this.props
if (maxModeOn && !selectedToken) { if (maxModeOn && !selectedToken) {
@ -126,6 +128,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
updateGasPrice(ethUtil.addHexPrefix(gasPrice)) updateGasPrice(ethUtil.addHexPrefix(gasPrice))
updateGasLimit(ethUtil.addHexPrefix(gasLimit)) updateGasLimit(ethUtil.addHexPrefix(gasLimit))
updateGasTotal(ethUtil.addHexPrefix(gasTotal)) updateGasTotal(ethUtil.addHexPrefix(gasTotal))
updateSendErrors({ insufficientFunds: false })
hideModal() hideModal()
} }

View File

@ -8,11 +8,16 @@ const clone = require('clone')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN const BN = ethUtil.BN
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
const classnames = require('classnames')
const { const {
conversionUtil, conversionUtil,
addCurrencies, addCurrencies,
multiplyCurrencies, multiplyCurrencies,
} = require('../../conversion-util') } = require('../../conversion-util')
const {
getGasTotal,
isBalanceSufficient,
} = require('../send/send-utils')
const GasFeeDisplay = require('../send/gas-fee-display-v2') const GasFeeDisplay = require('../send/gas-fee-display-v2')
const SenderToRecipient = require('../sender-to-recipient') const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display') const NetworkDisplay = require('../network-display')
@ -35,12 +40,14 @@ function mapStateToProps (state) {
} = state.metamask } = state.metamask
const accounts = state.metamask.accounts const accounts = state.metamask.accounts
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
const { balance } = accounts[selectedAddress]
return { return {
conversionRate, conversionRate,
identities, identities,
selectedAddress, selectedAddress,
currentCurrency, currentCurrency,
send, send,
balance,
} }
} }
@ -91,6 +98,7 @@ function mapDispatchToProps (dispatch) {
})) }))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
}, },
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
} }
} }
@ -101,6 +109,18 @@ function ConfirmSendEther () {
this.onSubmit = this.onSubmit.bind(this) this.onSubmit = this.onSubmit.bind(this)
} }
ConfirmSendEther.prototype.componentWillMount = function () {
const { updateSendErrors } = this.props
const txMeta = this.gatherTxMeta()
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
updateSendErrors({
insufficientFunds: balanceIsSufficient
? false
: this.context.t('insufficientFunds'),
})
}
ConfirmSendEther.prototype.getAmount = function () { ConfirmSendEther.prototype.getAmount = function () {
const { conversionRate, currentCurrency } = this.props const { conversionRate, currentCurrency } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
@ -223,7 +243,12 @@ ConfirmSendEther.prototype.render = function () {
conversionRate, conversionRate,
currentCurrency: convertedCurrency, currentCurrency: convertedCurrency,
showCustomizeGasModal, showCustomizeGasModal,
send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, send: {
gasTotal,
gasLimit: sendGasLimit,
gasPrice: sendGasPrice,
errors,
},
} = this.props } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {} const txParams = txMeta.txParams || {}
@ -331,7 +356,12 @@ ConfirmSendEther.prototype.render = function () {
]), ]),
h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [ 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('span.confirm-screen-label', [ this.context.t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]),
]), ]),
@ -340,6 +370,8 @@ ConfirmSendEther.prototype.render = function () {
h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`), h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`),
h('div.confirm-screen-row-detail', `${totalInETH} ETH`), h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]), ]),
this.renderErrorMessage('insufficientFunds'),
]), ]),
]), ]),
@ -444,16 +476,28 @@ ConfirmSendEther.prototype.render = function () {
) )
} }
ConfirmSendEther.prototype.renderErrorMessage = function (message) {
const { send: { errors } } = this.props
return errors[message]
? h('div.confirm-screen-error', [ errors[message] ])
: null
}
ConfirmSendEther.prototype.onSubmit = function (event) { ConfirmSendEther.prototype.onSubmit = function (event) {
event.preventDefault() event.preventDefault()
const { updateSendErrors } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const valid = this.checkValidity() const valid = this.checkValidity()
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
this.setState({ valid, submitting: true }) this.setState({ valid, submitting: true })
if (valid && this.verifyGasParams()) { if (valid && this.verifyGasParams() && balanceIsSufficient) {
this.props.sendTransaction(txMeta, event) this.props.sendTransaction(txMeta, event)
} else if (!balanceIsSufficient) {
updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
} else { } else {
this.props.dispatch(actions.displayWarning(this.context.t('invalidGasParams'))) updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
this.setState({ submitting: false }) this.setState({ submitting: false })
} }
} }
@ -465,6 +509,28 @@ ConfirmSendEther.prototype.cancel = function (event, txMeta) {
cancelTransaction(txMeta) cancelTransaction(txMeta)
} }
ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) {
const {
balance,
conversionRate,
} = this.props
const {
txParams: {
gas,
gasPrice,
value: amount,
},
} = txMeta
const gasTotal = getGasTotal(gas, gasPrice)
return isBalanceSufficient({
amount,
gasTotal,
balance,
conversionRate,
})
}
ConfirmSendEther.prototype.checkValidity = function () { ConfirmSendEther.prototype.checkValidity = function () {
const form = this.getFormEl() const form = this.getFormEl()
const valid = form.checkValidity() const valid = form.checkValidity()

View File

@ -17,9 +17,14 @@ const {
multiplyCurrencies, multiplyCurrencies,
addCurrencies, addCurrencies,
} = require('../../conversion-util') } = require('../../conversion-util')
const {
getGasTotal,
isBalanceSufficient,
} = require('../send/send-utils')
const { const {
calcTokenAmount, calcTokenAmount,
} = require('../../token-util') } = require('../../token-util')
const classnames = require('classnames')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@ -46,9 +51,10 @@ function mapStateToProps (state, ownProps) {
identities, identities,
currentCurrency, currentCurrency,
} = state.metamask } = state.metamask
const accounts = state.metamask.accounts
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)
const tokenExchangeRate = getTokenExchangeRate(state, symbol) const tokenExchangeRate = getTokenExchangeRate(state, symbol)
const { balance } = accounts[selectedAddress]
return { return {
conversionRate, conversionRate,
identities, identities,
@ -58,6 +64,7 @@ function mapStateToProps (state, ownProps) {
currentCurrency: currentCurrency.toUpperCase(), currentCurrency: currentCurrency.toUpperCase(),
send: state.metamask.send, send: state.metamask.send,
tokenContract: getSelectedTokenContract(state), tokenContract: getSelectedTokenContract(state),
balance,
} }
} }
@ -129,6 +136,7 @@ function mapDispatchToProps (dispatch, ownProps) {
})) }))
dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' }))
}, },
updateSendErrors: error => dispatch(actions.updateSendErrors(error)),
} }
} }
@ -140,12 +148,20 @@ function ConfirmSendToken () {
} }
ConfirmSendToken.prototype.componentWillMount = function () { ConfirmSendToken.prototype.componentWillMount = function () {
const { tokenContract, selectedAddress } = this.props const { tokenContract, selectedAddress, updateSendErrors} = this.props
const txMeta = this.gatherTxMeta()
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
tokenContract && tokenContract tokenContract && tokenContract
.balanceOf(selectedAddress) .balanceOf(selectedAddress)
.then(usersToken => { .then(usersToken => {
}) })
this.props.updateTokenExchangeRate() this.props.updateTokenExchangeRate()
updateSendErrors({
insufficientFunds: balanceIsSufficient
? false
: this.context.t('insufficientFunds'),
})
} }
ConfirmSendToken.prototype.getAmount = function () { ConfirmSendToken.prototype.getAmount = function () {
@ -306,7 +322,7 @@ ConfirmSendToken.prototype.renderGasFee = function () {
} }
ConfirmSendToken.prototype.renderTotalPlusGas = function () { ConfirmSendToken.prototype.renderTotalPlusGas = function () {
const { token: { symbol }, currentCurrency } = this.props const { token: { symbol }, currentCurrency, send: { errors } } = this.props
const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() const { fiat: fiatAmount, token: tokenAmount } = this.getAmount()
const { fiat: fiatGas, token: tokenGas } = this.getGasFee() const { fiat: fiatGas, token: tokenGas } = this.getGasFee()
@ -326,7 +342,12 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
) )
: ( : (
h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [ 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('span.confirm-screen-label', [ this.context.t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]),
]), ]),
@ -335,10 +356,20 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`), h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`),
h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} ${this.context.t('gas')}`), 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.render = function () { ConfirmSendToken.prototype.render = function () {
const { editTransaction } = this.props const { editTransaction } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
@ -455,18 +486,44 @@ ConfirmSendToken.prototype.render = function () {
ConfirmSendToken.prototype.onSubmit = function (event) { ConfirmSendToken.prototype.onSubmit = function (event) {
event.preventDefault() event.preventDefault()
const { updateSendErrors } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const valid = this.checkValidity() const valid = this.checkValidity()
const balanceIsSufficient = this.isBalanceSufficient(txMeta)
this.setState({ valid, submitting: true }) this.setState({ valid, submitting: true })
if (valid && this.verifyGasParams()) { if (valid && this.verifyGasParams() && balanceIsSufficient) {
this.props.sendTransaction(txMeta, event) this.props.sendTransaction(txMeta, event)
} else if (!balanceIsSufficient) {
updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') })
} else { } else {
this.props.dispatch(actions.displayWarning(this.context.t('invalidGasParams'))) updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') })
this.setState({ submitting: false }) this.setState({ submitting: false })
} }
} }
ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) {
const {
balance,
conversionRate,
} = this.props
const {
txParams: {
gas,
gasPrice,
},
} = txMeta
const gasTotal = getGasTotal(gas, gasPrice)
return isBalanceSufficient({
amount: '0',
gasTotal,
balance,
conversionRate,
})
}
ConfirmSendToken.prototype.cancel = function (event, txMeta) { ConfirmSendToken.prototype.cancel = function (event, txMeta) {
event.preventDefault() event.preventDefault()
const { cancelTransaction } = this.props const { cancelTransaction } = this.props

View File

@ -2,6 +2,7 @@ const {
addCurrencies, addCurrencies,
conversionUtil, conversionUtil,
conversionGTE, conversionGTE,
multiplyCurrencies,
} = require('../../conversion-util') } = require('../../conversion-util')
const { const {
calcTokenAmount, calcTokenAmount,
@ -31,7 +32,7 @@ function isBalanceSufficient ({
{ {
value: totalAmount, value: totalAmount,
fromNumericBase: 'hex', fromNumericBase: 'hex',
conversionRate: amountConversionRate, conversionRate: amountConversionRate || conversionRate,
fromCurrency: primaryCurrency, fromCurrency: primaryCurrency,
}, },
) )
@ -62,7 +63,16 @@ function isTokenBalanceSufficient ({
return tokenBalanceIsSufficient return tokenBalanceIsSufficient
} }
function getGasTotal (gasLimit, gasPrice) {
return multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
}
module.exports = { module.exports = {
getGasTotal,
isBalanceSufficient, isBalanceSufficient,
isTokenBalanceSufficient, isTokenBalanceSufficient,
} }

View File

@ -266,6 +266,7 @@ section .confirm-screen-account-number,
.confirm-screen-total-box { .confirm-screen-total-box {
background-color: $wild-sand; background-color: $wild-sand;
position: relative;
.confirm-screen-label { .confirm-screen-label {
line-height: 21px; line-height: 21px;
@ -287,6 +288,30 @@ section .confirm-screen-account-number,
} }
} }
.confirm-screen-error {
font-size: 12px;
line-height: 12px;
color: #f00;
position: absolute;
right: 12px;
width: 80px;
text-align: right;
}
.confirm-screen-row.confirm-screen-total-box {
.confirm-screen-section-column--with-error {
flex: 0.6;
}
}
@media screen and (max-width: 379px) {
.confirm-screen-row.confirm-screen-total-box {
.confirm-screen-section-column--with-error {
flex: 0.4;
}
}
}
.confirm-screen-confirm-button { .confirm-screen-confirm-button {
height: 50px; height: 50px;
border-radius: 4px; border-radius: 4px;

View File

@ -27,6 +27,7 @@ const {
const { const {
isBalanceSufficient, isBalanceSufficient,
isTokenBalanceSufficient, isTokenBalanceSufficient,
getGasTotal,
} = require('./components/send/send-utils') } = require('./components/send/send-utils')
const { isValidAddress } = require('./util') const { isValidAddress } = require('./util')
@ -132,7 +133,7 @@ SendTransactionScreen.prototype.updateGas = function () {
estimateGas(estimateGasParams), estimateGas(estimateGasParams),
]) ])
.then(([gasPrice, gas]) => { .then(([gasPrice, gas]) => {
const newGasTotal = this.getGasTotal(gas, gasPrice) const newGasTotal = getGasTotal(gas, gasPrice)
updateGasTotal(newGasTotal) updateGasTotal(newGasTotal)
this.setState({ gasLoadingError: false }) this.setState({ gasLoadingError: false })
}) })
@ -140,19 +141,11 @@ SendTransactionScreen.prototype.updateGas = function () {
this.setState({ gasLoadingError: true }) this.setState({ gasLoadingError: true })
}) })
} else { } else {
const newGasTotal = this.getGasTotal(gasLimit, gasPrice) const newGasTotal = getGasTotal(gasLimit, gasPrice)
updateGasTotal(newGasTotal) updateGasTotal(newGasTotal)
} }
} }
SendTransactionScreen.prototype.getGasTotal = function (gasLimit, gasPrice) {
return multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
}
SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
const { const {
from: { balance }, from: { balance },