1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Improve customize gas modal error handling

This commit is contained in:
Dan 2017-10-22 17:44:03 -02:30 committed by Chi Kei Chan
parent a7069acf2e
commit e737a9565a
8 changed files with 234 additions and 63 deletions

View File

@ -6,23 +6,46 @@ const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card') const GasModalCard = require('./gas-modal-card')
const { const {
MIN_GAS_PRICE, MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT, MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
} = require('../send/send-constants') } = require('../send/send-constants')
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const {
isBalanceSufficient,
} = require('../send/send-utils')
const {
conversionUtil,
multiplyCurrencies,
conversionGreaterThan,
} = require('../../conversion-util')
const { const {
getGasPrice, getGasPrice,
getGasLimit, getGasLimit,
conversionRateSelector, conversionRateSelector,
getSendAmount,
getSelectedToken,
getSendFrom,
getCurrentAccountWithSendEtherInfo,
getSelectedTokenToFiatRate,
} = require('../../selectors') } = require('../../selectors')
function mapStateToProps (state) { function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
const conversionRate = conversionRateSelector(state)
return { return {
gasPrice: getGasPrice(state), gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state), gasLimit: getGasLimit(state),
conversionRate: conversionRateSelector(state), conversionRate,
amount: getSendAmount(state),
balance: currentAccount.balance,
primaryCurrency: selectedToken && selectedToken.symbol,
selectedToken,
amountConversionRate: selectedToken ? getSelectedTokenToFiatRate(state) : conversionRate,
} }
} }
@ -39,15 +62,26 @@ inherits(CustomizeGasModal, Component)
function CustomizeGasModal (props) { function CustomizeGasModal (props) {
Component.call(this) Component.call(this)
const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC
const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
this.state = { this.state = {
gasPrice: props.gasPrice || MIN_GAS_PRICE, gasPrice,
gasLimit: props.gasLimit || MIN_GAS_LIMIT, gasLimit,
gasTotal,
error: null,
} }
} }
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal) module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) { CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
const { const {
updateGasPrice, updateGasPrice,
updateGasLimit, updateGasLimit,
@ -55,41 +89,101 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
updateGasTotal updateGasTotal
} = this.props } = this.props
const newGasTotal = multiplyCurrencies(gasLimit, gasPrice, { updateGasPrice(gasPrice)
updateGasLimit(gasLimit)
updateGasTotal(gasTotal)
hideModal()
}
CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
const {
amount,
balance,
primaryCurrency,
selectedToken,
amountConversionRate,
conversionRate,
} = this.props
let error = null
const balanceIsSufficient = isBalanceSufficient({
amount,
gasTotal,
balance,
primaryCurrency,
selectedToken,
amountConversionRate,
conversionRate,
})
if (!balanceIsSufficient) {
error = 'Insufficient balance for current gas total'
}
const gasLimitTooLow = gasLimit && conversionGreaterThan(
{
value: MIN_GAS_LIMIT_DEC,
fromNumericBase: 'dec',
conversionRate,
},
{
value: gasLimit,
fromNumericBase: 'hex',
},
)
if (gasLimitTooLow) {
error = 'Gas limit must be at least 21000'
}
this.setState({ error })
return error
}
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) {
const { gasPrice } = this.state
const gasLimit = conversionUtil(newGasLimit, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex', toNumericBase: 'hex',
multiplicandBase: 16, multiplicandBase: 16,
multiplierBase: 16, multiplierBase: 16,
}) })
updateGasPrice(gasPrice) this.validate({ gasTotal, gasLimit })
updateGasLimit(gasLimit)
updateGasTotal(newGasTotal)
hideModal()
}
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) { this.setState({ gasTotal, gasLimit })
const convertedGasLimit = conversionUtil(newGasLimit, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
this.setState({ gasLimit: convertedGasLimit })
} }
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
const convertedGasPrice = conversionUtil(newGasPrice, { const { gasLimit } = this.state
const gasPrice = conversionUtil(newGasPrice, {
fromNumericBase: 'dec', fromNumericBase: 'dec',
toNumericBase: 'hex', toNumericBase: 'hex',
fromDenomination: 'GWEI', fromDenomination: 'GWEI',
toDenomination: 'WEI', toDenomination: 'WEI',
}) })
this.setState({ gasPrice: convertedGasPrice }) const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
})
this.validate({ gasTotal })
this.setState({ gasTotal, gasPrice })
} }
CustomizeGasModal.prototype.render = function () { CustomizeGasModal.prototype.render = function () {
const { hideModal, conversionRate } = this.props const { hideModal, conversionRate } = this.props
const { gasPrice, gasLimit } = this.state const { gasPrice, gasLimit, gasTotal, error } = this.state
const convertedGasPrice = conversionUtil(gasPrice, { const convertedGasPrice = conversionUtil(gasPrice, {
fromNumericBase: 'hex', fromNumericBase: 'hex',
@ -120,7 +214,7 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, { h(GasModalCard, {
value: convertedGasPrice, value: convertedGasPrice,
min: MIN_GAS_PRICE, min: MIN_GAS_PRICE_GWEI,
// max: 1000, // max: 1000,
step: 1, step: 1,
onChange: value => this.convertAndSetGasPrice(value), onChange: value => this.convertAndSetGasPrice(value),
@ -130,7 +224,7 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, { h(GasModalCard, {
value: convertedGasLimit, value: convertedGasLimit,
min: MIN_GAS_LIMIT, min: 1,
// max: 100000, // max: 100000,
step: 1, step: 1,
onChange: value => this.convertAndSetGasLimit(value), onChange: value => this.convertAndSetGasLimit(value),
@ -141,6 +235,10 @@ CustomizeGasModal.prototype.render = function () {
]), ]),
h('div.send-v2__customize-gas__footer', {}, [ h('div.send-v2__customize-gas__footer', {}, [
error && h('div.send-v2__customize-gas__error-message', [
error,
]),
h('div.send-v2__customize-gas__revert', { h('div.send-v2__customize-gas__revert', {
onClick: () => console.log('Revert'), onClick: () => console.log('Revert'),
@ -151,8 +249,8 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal, onClick: this.props.hideModal,
}, ['CANCEL']), }, ['CANCEL']),
h('div.send-v2__customize-gas__save', { h(`div.send-v2__customize-gas__save${error ? '__error' : ''}`, {
onClick: () => this.save(gasPrice, gasLimit), onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal),
}, ['SAVE']), }, ['SAVE']),
]) ])

View File

@ -50,7 +50,7 @@ ConfirmSendEther.prototype.getAmount = function () {
const { conversionRate, currentCurrency } = this.props const { conversionRate, currentCurrency } = this.props
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {} const txParams = txMeta.txParams || {}
console.log(`conversionRate, currentCurrency`, conversionRate, currentCurrency);
const FIAT = conversionUtil(txParams.value, { const FIAT = conversionUtil(txParams.value, {
fromNumericBase: 'hex', fromNumericBase: 'hex',
toNumericBase: 'dec', toNumericBase: 'dec',

View File

@ -3,12 +3,19 @@ const { multiplyCurrencies } = require('../../conversion-util')
const MIN_GAS_PRICE_GWEI = '1' const MIN_GAS_PRICE_GWEI = '1'
const GWEI_FACTOR = '1e9' const GWEI_FACTOR = '1e9'
const MIN_GAS_PRICE = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, { const MIN_GAS_PRICE_HEX = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
multiplicandBase: 16, multiplicandBase: 16,
multiplierBase: 16, multiplierBase: 16,
toNumericBase: 'hex',
}) })
const MIN_GAS_LIMIT = (21000).toString(16) const MIN_GAS_PRICE_DEC = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, { multiplicandBase: 16,
multiplierBase: 16,
toNumericBase: 'dec',
})
const MIN_GAS_LIMIT_HEX = (21000).toString(16)
const MIN_GAS_LIMIT_DEC = 21000
const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
toNumericBase: 'hex', toNumericBase: 'hex',
multiplicandBase: 16, multiplicandBase: 16,
multiplierBase: 16, multiplierBase: 16,
@ -16,8 +23,9 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, {
module.exports = { module.exports = {
MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_GWEI,
GWEI_FACTOR, MIN_GAS_PRICE_HEX,
MIN_GAS_PRICE, MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT, MIN_GAS_LIMIT_HEX,
MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL, MIN_GAS_TOTAL,
} }

View File

@ -0,0 +1,39 @@
const { addCurrencies, conversionGreaterThan } = require('../../conversion-util')
function isBalanceSufficient({
amount,
gasTotal,
balance,
primaryCurrency,
selectedToken,
amountConversionRate,
conversionRate,
}) {
const totalAmount = addCurrencies(amount, gasTotal, {
aBase: 16,
bBase: 16,
toNumericBase: 'hex',
})
const balanceIsSufficient = conversionGreaterThan(
{
value: balance,
fromNumericBase: 'hex',
fromCurrency: primaryCurrency,
conversionRate,
},
{
value: totalAmount,
fromNumericBase: 'hex',
conversionRate: amountConversionRate,
fromCurrency: selectedToken || primaryCurrency,
conversionRate: amountConversionRate,
},
)
return balanceIsSufficient
}
module.exports = {
isBalanceSufficient,
}

View File

@ -17,6 +17,7 @@ const {
getAddressBook, getAddressBook,
getSendFrom, getSendFrom,
getCurrentCurrency, getCurrentCurrency,
getSelectedTokenToFiatRate,
} = require('../../selectors') } = require('../../selectors')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther) module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
@ -26,7 +27,6 @@ function mapStateToProps (state) {
const selectedAddress = getSelectedAddress(state) const selectedAddress = getSelectedAddress(state)
const selectedToken = getSelectedToken(state) const selectedToken = getSelectedToken(state)
const tokenExchangeRates = state.metamask.tokenExchangeRates const tokenExchangeRates = state.metamask.tokenExchangeRates
const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
const conversionRate = conversionRateSelector(state) const conversionRate = conversionRateSelector(state)
let data; let data;
@ -40,11 +40,7 @@ function mapStateToProps (state) {
primaryCurrency = selectedToken.symbol primaryCurrency = selectedToken.symbol
tokenToFiatRate = multiplyCurrencies( tokenToFiatRate = getSelectedTokenToFiatRate(state)
conversionRate,
selectedTokenExchangeRate,
{ toNumericBase: 'dec' }
)
} }
return { return {

View File

@ -738,6 +738,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
font-size: 22px; font-size: 22px;
position: relative;
} }
&__buttons { &__buttons {
@ -747,7 +748,7 @@
margin-right: 21.25px; margin-right: 21.25px;
} }
&__revert, &__cancel, &__save { &__revert, &__cancel, &__save, &__save__error {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -760,7 +761,7 @@
margin-left: 21.25px; margin-left: 21.25px;
} }
&__cancel, &__save { &__cancel, &__save, &__save__error {
height: 34.64px; height: 34.64px;
width: 85.74px; width: 85.74px;
border: 1px solid $dusty-gray; border: 1px solid $dusty-gray;
@ -769,6 +770,21 @@
font-size: 12px; font-size: 12px;
color: $dusty-gray; color: $dusty-gray;
} }
&__save__error {
opacity: 0.5;
cursor: auto;
}
&__error-message {
display: block;
position: absolute;
top: 4px;
right: 4px;
font-size: 12px;
line-height: 12px;
color: $red;
}
} }
&__gas-modal-card { &__gas-modal-card {

View File

@ -1,5 +1,9 @@
const valuesFor = require('./util').valuesFor const valuesFor = require('./util').valuesFor
const {
multiplyCurrencies,
} = require('./conversion-util')
const selectors = { const selectors = {
getSelectedAddress, getSelectedAddress,
getSelectedIdentity, getSelectedIdentity,
@ -16,6 +20,8 @@ const selectors = {
getAddressBook, getAddressBook,
getSendFrom, getSendFrom,
getCurrentCurrency, getCurrentCurrency,
getSendAmount,
getSelectedTokenToFiatRate,
} }
module.exports = selectors module.exports = selectors
@ -123,6 +129,23 @@ function getSendFrom (state) {
return state.metamask.send.from return state.metamask.send.from
} }
function getSendAmount (state) {
return state.metamask.send.amount
}
function getCurrentCurrency (state) { function getCurrentCurrency (state) {
return state.metamask.currentCurrency return state.metamask.currentCurrency
} }
function getSelectedTokenToFiatRate (state) {
const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
const conversionRate = conversionRateSelector(state)
const tokenToFiatRate = multiplyCurrencies(
conversionRate,
selectedTokenExchangeRate,
{ toNumericBase: 'dec' }
)
return tokenToFiatRate
}

View File

@ -19,6 +19,9 @@ const {
conversionGreaterThan, conversionGreaterThan,
addCurrencies, addCurrencies,
} = require('./conversion-util') } = require('./conversion-util')
const {
isBalanceSufficient,
} = require('./components/send/send-utils.js')
const { isValidAddress } = require('./util') const { isValidAddress } = require('./util')
module.exports = SendTransactionScreen module.exports = SendTransactionScreen
@ -237,28 +240,16 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
let amountError = null let amountError = null
const totalAmount = addCurrencies(amount, gasTotal, { const sufficientBalance = isBalanceSufficient({
aBase: 16, amount,
bBase: 16, gasTotal,
toNumericBase: 'hex', balance,
primaryCurrency,
selectedToken,
amountConversionRate,
conversionRate,
}) })
const sufficientBalance = conversionGreaterThan(
{
value: balance,
fromNumericBase: 'hex',
fromCurrency: primaryCurrency,
conversionRate,
},
{
value: totalAmount,
fromNumericBase: 'hex',
conversionRate: amountConversionRate,
fromCurrency: selectedToken || primaryCurrency,
conversionRate: amountConversionRate,
},
)
const amountLessThanZero = conversionGreaterThan( const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' }, { value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' }, { value: amount, fromNumericBase: 'hex' },
@ -387,7 +378,7 @@ SendTransactionScreen.prototype.renderFooter = function () {
clearSend, clearSend,
errors: { amount: amountError, to: toError } errors: { amount: amountError, to: toError }
} = this.props } = this.props
const noErrors = amountError === null && toError === null const noErrors = amountError === null && toError === null
const errorClass = noErrors ? '' : '__disabled' const errorClass = noErrors ? '' : '__disabled'