|
|
|
const Component = require('react').Component
|
|
|
|
const PropTypes = require('prop-types')
|
|
|
|
const h = require('react-hyperscript')
|
|
|
|
const inherits = require('util').inherits
|
|
|
|
const connect = require('react-redux').connect
|
|
|
|
const actions = require('../../actions')
|
|
|
|
const GasModalCard = require('./gas-modal-card')
|
|
|
|
|
|
|
|
const ethUtil = require('ethereumjs-util')
|
|
|
|
|
|
|
|
import {
|
|
|
|
updateSendErrors,
|
|
|
|
} from '../../ducks/send.duck'
|
|
|
|
|
|
|
|
const {
|
|
|
|
MIN_GAS_PRICE_DEC,
|
|
|
|
MIN_GAS_LIMIT_DEC,
|
|
|
|
MIN_GAS_PRICE_GWEI,
|
|
|
|
} = require('../send_/send.constants')
|
|
|
|
|
|
|
|
const {
|
|
|
|
isBalanceSufficient,
|
|
|
|
} = require('../send_/send.utils')
|
|
|
|
|
|
|
|
const {
|
|
|
|
conversionUtil,
|
|
|
|
multiplyCurrencies,
|
|
|
|
conversionGreaterThan,
|
|
|
|
conversionMax,
|
|
|
|
subtractCurrencies,
|
|
|
|
} = require('../../conversion-util')
|
|
|
|
|
|
|
|
const {
|
|
|
|
getGasIsLoading,
|
|
|
|
getForceGasMin,
|
|
|
|
conversionRateSelector,
|
|
|
|
getSendAmount,
|
|
|
|
getSelectedToken,
|
|
|
|
getSendFrom,
|
|
|
|
getCurrentAccountWithSendEtherInfo,
|
|
|
|
getSelectedTokenToFiatRate,
|
|
|
|
getSendMaxModeState,
|
|
|
|
} = require('../../selectors')
|
|
|
|
|
|
|
|
const {
|
|
|
|
getGasPrice,
|
|
|
|
getGasLimit,
|
|
|
|
} = require('../send_/send.selectors')
|
|
|
|
|
|
|
|
function mapStateToProps (state) {
|
|
|
|
const selectedToken = getSelectedToken(state)
|
|
|
|
const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
|
|
|
|
const conversionRate = conversionRateSelector(state)
|
|
|
|
|
|
|
|
return {
|
|
|
|
gasPrice: getGasPrice(state),
|
|
|
|
gasLimit: getGasLimit(state),
|
|
|
|
gasIsLoading: getGasIsLoading(state),
|
|
|
|
forceGasMin: getForceGasMin(state),
|
|
|
|
conversionRate,
|
|
|
|
amount: getSendAmount(state),
|
|
|
|
maxModeOn: getSendMaxModeState(state),
|
|
|
|
balance: currentAccount.balance,
|
|
|
|
primaryCurrency: selectedToken && selectedToken.symbol,
|
|
|
|
selectedToken,
|
|
|
|
amountConversionRate: selectedToken ? getSelectedTokenToFiatRate(state) : conversionRate,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapDispatchToProps (dispatch) {
|
|
|
|
return {
|
|
|
|
hideModal: () => dispatch(actions.hideModal()),
|
|
|
|
setGasPrice: newGasPrice => dispatch(actions.setGasPrice(newGasPrice)),
|
|
|
|
setGasLimit: newGasLimit => dispatch(actions.setGasLimit(newGasLimit)),
|
|
|
|
setGasTotal: newGasTotal => dispatch(actions.setGasTotal(newGasTotal)),
|
|
|
|
updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)),
|
|
|
|
updateSendErrors: error => dispatch(updateSendErrors(error)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFreshState (props) {
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
gasPrice,
|
|
|
|
gasLimit,
|
|
|
|
gasTotal,
|
|
|
|
error: null,
|
|
|
|
priceSigZeros: '',
|
|
|
|
priceSigDec: '',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inherits(CustomizeGasModal, Component)
|
|
|
|
function CustomizeGasModal (props) {
|
|
|
|
Component.call(this)
|
|
|
|
|
|
|
|
const originalState = getFreshState(props)
|
|
|
|
this.state = {
|
|
|
|
...originalState,
|
|
|
|
originalState,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.contextTypes = {
|
|
|
|
t: PropTypes.func,
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.componentWillReceiveProps = function (nextProps) {
|
|
|
|
const currentState = getFreshState(this.props)
|
|
|
|
const {
|
|
|
|
gasPrice: currentGasPrice,
|
|
|
|
gasLimit: currentGasLimit,
|
|
|
|
} = currentState
|
|
|
|
const newState = getFreshState(nextProps)
|
|
|
|
const {
|
|
|
|
gasPrice: newGasPrice,
|
|
|
|
gasLimit: newGasLimit,
|
|
|
|
gasTotal: newGasTotal,
|
|
|
|
} = newState
|
|
|
|
const gasPriceChanged = currentGasPrice !== newGasPrice
|
|
|
|
const gasLimitChanged = currentGasLimit !== newGasLimit
|
|
|
|
|
|
|
|
if (gasPriceChanged) {
|
|
|
|
this.setState({
|
|
|
|
gasPrice: newGasPrice,
|
|
|
|
gasTotal: newGasTotal,
|
|
|
|
priceSigZeros: '',
|
|
|
|
priceSigDec: '',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (gasLimitChanged) {
|
|
|
|
this.setState({ gasLimit: newGasLimit, gasTotal: newGasTotal })
|
|
|
|
}
|
|
|
|
if (gasLimitChanged || gasPriceChanged) {
|
|
|
|
this.validate({ gasLimit: newGasLimit, gasTotal: newGasTotal })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
|
|
|
|
const {
|
|
|
|
setGasPrice,
|
|
|
|
setGasLimit,
|
|
|
|
hideModal,
|
|
|
|
setGasTotal,
|
|
|
|
maxModeOn,
|
|
|
|
selectedToken,
|
|
|
|
balance,
|
|
|
|
updateSendAmount,
|
|
|
|
updateSendErrors,
|
|
|
|
} = this.props
|
|
|
|
|
|
|
|
if (maxModeOn && !selectedToken) {
|
|
|
|
const maxAmount = subtractCurrencies(
|
|
|
|
ethUtil.addHexPrefix(balance),
|
|
|
|
ethUtil.addHexPrefix(gasTotal),
|
|
|
|
{ toNumericBase: 'hex' }
|
|
|
|
)
|
|
|
|
updateSendAmount(maxAmount)
|
|
|
|
}
|
|
|
|
|
|
|
|
setGasPrice(ethUtil.addHexPrefix(gasPrice))
|
|
|
|
setGasLimit(ethUtil.addHexPrefix(gasLimit))
|
|
|
|
setGasTotal(ethUtil.addHexPrefix(gasTotal))
|
|
|
|
updateSendErrors({ insufficientFunds: false })
|
|
|
|
hideModal()
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.revert = function () {
|
|
|
|
this.setState(this.state.originalState)
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
|
|
|
|
const {
|
|
|
|
amount,
|
|
|
|
balance,
|
|
|
|
selectedToken,
|
|
|
|
amountConversionRate,
|
|
|
|
conversionRate,
|
|
|
|
maxModeOn,
|
|
|
|
} = this.props
|
|
|
|
|
|
|
|
let error = null
|
|
|
|
|
|
|
|
const balanceIsSufficient = isBalanceSufficient({
|
|
|
|
amount: selectedToken || maxModeOn ? '0' : amount,
|
|
|
|
gasTotal,
|
|
|
|
balance,
|
|
|
|
selectedToken,
|
|
|
|
amountConversionRate,
|
|
|
|
conversionRate,
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!balanceIsSufficient) {
|
|
|
|
error = this.context.t('balanceIsInsufficientGas')
|
|
|
|
}
|
|
|
|
|
|
|
|
const gasLimitTooLow = gasLimit && conversionGreaterThan(
|
|
|
|
{
|
|
|
|
value: MIN_GAS_LIMIT_DEC,
|
|
|
|
fromNumericBase: 'dec',
|
|
|
|
conversionRate,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
value: gasLimit,
|
|
|
|
fromNumericBase: 'hex',
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if (gasLimitTooLow) {
|
|
|
|
error = this.context.t('gasLimitTooLow')
|
|
|
|
}
|
|
|
|
|
|
|
|
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',
|
|
|
|
multiplicandBase: 16,
|
|
|
|
multiplierBase: 16,
|
|
|
|
})
|
|
|
|
|
|
|
|
this.validate({ gasTotal, gasLimit })
|
|
|
|
|
|
|
|
this.setState({ gasTotal, gasLimit })
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
|
|
|
|
const { gasLimit } = this.state
|
|
|
|
const sigZeros = String(newGasPrice).match(/^\d+[.]\d*?(0+)$/)
|
|
|
|
const sigDec = String(newGasPrice).match(/^\d+([.])0*$/)
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
priceSigZeros: sigZeros && sigZeros[1] || '',
|
|
|
|
priceSigDec: sigDec && sigDec[1] || '',
|
|
|
|
})
|
|
|
|
|
|
|
|
const gasPrice = conversionUtil(newGasPrice, {
|
|
|
|
fromNumericBase: 'dec',
|
|
|
|
toNumericBase: 'hex',
|
|
|
|
fromDenomination: 'GWEI',
|
|
|
|
toDenomination: 'WEI',
|
|
|
|
})
|
|
|
|
|
|
|
|
const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
|
|
|
|
toNumericBase: 'hex',
|
|
|
|
multiplicandBase: 16,
|
|
|
|
multiplierBase: 16,
|
|
|
|
})
|
|
|
|
|
|
|
|
this.validate({ gasTotal })
|
|
|
|
|
|
|
|
this.setState({ gasTotal, gasPrice })
|
|
|
|
}
|
|
|
|
|
|
|
|
CustomizeGasModal.prototype.render = function () {
|
|
|
|
const { hideModal, forceGasMin, gasIsLoading } = this.props
|
|
|
|
const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state
|
|
|
|
|
|
|
|
let convertedGasPrice = conversionUtil(gasPrice, {
|
|
|
|
fromNumericBase: 'hex',
|
|
|
|
toNumericBase: 'dec',
|
|
|
|
fromDenomination: 'WEI',
|
|
|
|
toDenomination: 'GWEI',
|
|
|
|
})
|
|
|
|
|
|
|
|
convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}`
|
|
|
|
|
|
|
|
let newGasPrice = gasPrice
|
|
|
|
if (forceGasMin) {
|
|
|
|
const convertedMinPrice = conversionUtil(forceGasMin, {
|
|
|
|
fromNumericBase: 'hex',
|
|
|
|
toNumericBase: 'dec',
|
|
|
|
})
|
|
|
|
convertedGasPrice = conversionMax(
|
|
|
|
{ value: convertedMinPrice, fromNumericBase: 'dec' },
|
|
|
|
{ value: convertedGasPrice, fromNumericBase: 'dec' }
|
|
|
|
)
|
|
|
|
newGasPrice = conversionMax(
|
|
|
|
{ value: gasPrice, fromNumericBase: 'hex' },
|
|
|
|
{ value: forceGasMin, fromNumericBase: 'hex' }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const convertedGasLimit = conversionUtil(gasLimit, {
|
|
|
|
fromNumericBase: 'hex',
|
|
|
|
toNumericBase: 'dec',
|
|
|
|
})
|
|
|
|
|
|
|
|
return !gasIsLoading && h('div.send-v2__customize-gas', {}, [
|
|
|
|
h('div.send-v2__customize-gas__content', {
|
|
|
|
}, [
|
|
|
|
h('div.send-v2__customize-gas__header', {}, [
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__title', this.context.t('customGas')),
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__close', {
|
|
|
|
onClick: hideModal,
|
|
|
|
}),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__body', {}, [
|
|
|
|
|
|
|
|
h(GasModalCard, {
|
|
|
|
value: convertedGasPrice,
|
|
|
|
min: forceGasMin || MIN_GAS_PRICE_GWEI,
|
|
|
|
step: 1,
|
|
|
|
onChange: value => this.convertAndSetGasPrice(value),
|
|
|
|
title: this.context.t('gasPrice'),
|
|
|
|
copy: this.context.t('gasPriceCalculation'),
|
|
|
|
gasIsLoading,
|
|
|
|
}),
|
|
|
|
|
|
|
|
h(GasModalCard, {
|
|
|
|
value: convertedGasLimit,
|
|
|
|
min: 1,
|
|
|
|
step: 1,
|
|
|
|
onChange: value => this.convertAndSetGasLimit(value),
|
|
|
|
title: this.context.t('gasLimit'),
|
|
|
|
copy: this.context.t('gasLimitCalculation'),
|
|
|
|
gasIsLoading,
|
|
|
|
}),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__footer', {}, [
|
|
|
|
|
|
|
|
error && h('div.send-v2__customize-gas__error-message', [
|
|
|
|
error,
|
|
|
|
]),
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__revert', {
|
|
|
|
onClick: () => this.revert(),
|
|
|
|
}, [this.context.t('revert')]),
|
|
|
|
|
|
|
|
h('div.send-v2__customize-gas__buttons', [
|
|
|
|
h('button.btn-default.send-v2__customize-gas__cancel', {
|
|
|
|
onClick: this.props.hideModal,
|
|
|
|
style: {
|
|
|
|
marginRight: '10px',
|
|
|
|
},
|
|
|
|
}, [this.context.t('cancel')]),
|
|
|
|
|
|
|
|
h('button.btn-primary.send-v2__customize-gas__save', {
|
|
|
|
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
|
|
|
|
className: error && 'btn-primary--disabled',
|
|
|
|
}, [this.context.t('save')]),
|
|
|
|
]),
|
|
|
|
|
|
|
|
]),
|
|
|
|
|
|
|
|
]),
|
|
|
|
])
|
|
|
|
}
|