diff options
Merge pull request #1154 from MetaMask/i765-gaslimits
Add ability to customize gas and gasPrice on tx confirmation screen
Diffstat (limited to 'ui/app/components')
-rw-r--r-- | ui/app/components/hex-as-decimal-input.js | 76 | ||||
-rw-r--r-- | ui/app/components/pending-tx-details.js | 145 | ||||
-rw-r--r-- | ui/app/components/pending-tx.js | 26 |
3 files changed, 235 insertions, 12 deletions
diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js new file mode 100644 index 000000000..523c1264b --- /dev/null +++ b/ui/app/components/hex-as-decimal-input.js @@ -0,0 +1,76 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const extend = require('xtend') + +module.exports = HexAsDecimalInput + +inherits(HexAsDecimalInput, Component) +function HexAsDecimalInput () { + Component.call(this) +} + +/* Hex as Decimal Input + * + * A component for allowing easy, decimal editing + * of a passed in hex string value. + * + * On change, calls back its `onChange` function parameter + * and passes it an updated hex string. + */ + +HexAsDecimalInput.prototype.render = function () { + const props = this.props + const { value, onChange } = props + const toEth = props.toEth + const suffix = props.suffix + const decimalValue = decimalize(value, toEth) + const style = props.style + + return ( + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('input.ether-balance.ether-balance-amount', { + style: extend({ + display: 'block', + textAlign: 'right', + backgroundColor: 'transparent', + border: '1px solid #bdbdbd', + }, style), + value: decimalValue, + onChange: (event) => { + const hexString = hexify(event.target.value) + onChange(hexString) + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]) + ) +} + +function hexify (decimalString) { + const hexBN = new BN(decimalString, 10) + return '0x' + hexBN.toString('hex') +} + +function decimalize (input, toEth) { + const strippedInput = ethUtil.stripHexPrefix(input) + const inputBN = new BN(strippedInput, 'hex') + return inputBN.toString(10) +} diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index e8615404e..b1ab9576b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -1,12 +1,16 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const extend = require('xtend') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN const MiniAccountPanel = require('./mini-account-panel') const EthBalance = require('./eth-balance') const util = require('../util') const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') +const HexInput = require('./hex-as-decimal-input') module.exports = PendingTxDetails @@ -19,7 +23,8 @@ const PTXP = PendingTxDetails.prototype PTXP.render = function () { var props = this.props - var txData = props.txData + var state = this.state || {} + var txData = state.txMeta || props.txData var txParams = txData.txParams || {} var address = txParams.from || props.selectedAddress @@ -27,11 +32,18 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' - var txFee = txData.txFee || '' - var maxCost = txData.maxCost || '' + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txData.gasPrice + const gasDefault = txParams.gas + const gasPriceDefault = txData.gasPrice + + var txFee = state.txFee || txData.txFee || '' + var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + log.debug(`rendering gas: ${gas}, gasPrice: ${gasPrice}, txFee: ${txFee}, maxCost: ${maxCost}`) + return ( h('div', [ @@ -97,11 +109,63 @@ PTXP.render = function () { h('.table-box', [ + // Ether Value + // Currently not customizable, but easily modified + // in the way that gas and gasLimit currently are. h('.row', [ h('.cell.label', 'Amount'), h(EthBalance, { value: txParams.value }), ]), + // Gas Limit (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Limit'), + h('.cell.value', { + }, [ + h(HexInput, { + value: gas, + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas limit changed to ${newHex}`) + if (newHex === '0x0') { + this.setState({gas: gasDefault}) + } else { + this.setState({ gas: newHex }) + } + }, + }), + ]), + ]), + + // Gas Price (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + }, [ + h(HexInput, { + value: gasPrice, + suffix: 'WEI', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas price changed to: ${newHex}`) + if (newHex === '0x0') { + this.setState({gasPrice: gasPriceDefault}) + } else { + this.setState({ gasPrice: newHex }) + } + }, + }), + ]), + ]), + + // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), h(EthBalance, { value: txFee.toString(16) }), @@ -130,6 +194,7 @@ PTXP.render = function () { ]), ]), + // Data size row: h('.cell.row', { style: { background: '#f7f7f7', @@ -191,6 +256,80 @@ PTXP.miniAccountPanelForRecipient = function () { } } +PTXP.componentDidUpdate = function (prevProps, previousState) { + log.debug(`pending-tx-details componentDidUpdate`) + const state = this.state || {} + const prevState = previousState || {} + const { gas, gasPrice } = state + + // Only if gas or gasPrice changed: + if (!prevState || + (gas !== prevState.gas || + gasPrice !== prevState.gasPrice)) { + log.debug(`recalculating gas since prev state change: ${JSON.stringify({ prevState, state })}`) + this.calculateGas() + } +} + +PTXP.calculateGas = function () { + const txMeta = this.gatherParams() + log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + + var txParams = txMeta.txParams + var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) + var txFee = gasCost.mul(gasPrice) + var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + var maxCost = txValue.add(txFee) + + const txFeeHex = '0x' + txFee.toString('hex') + const maxCostHex = '0x' + maxCost.toString('hex') + const gasPriceHex = '0x' + gasPrice.toString('hex') + + txMeta.txFee = txFeeHex + txMeta.maxCost = maxCostHex + txMeta.txParams.gasPrice = gasPriceHex + + this.setState({ + txFee: '0x' + txFee.toString('hex'), + maxCost: '0x' + maxCost.toString('hex'), + }) + + if (this.props.onTxChange) { + this.props.onTxChange(txMeta) + } +} + +PTXP.resetGasFields = function () { + log.debug(`pending-tx-details#resetGasFields`) + const txData = this.props.txData + this.setState({ + gas: txData.txParams.gas, + gasPrice: txData.gasPrice, + }) +} + +// After a customizable state value has been updated, +PTXP.gatherParams = function () { + log.debug(`pending-tx-details#gatherParams`) + const props = this.props + const state = this.state || {} + const txData = state.txData || props.txData + const txParams = txData.txParams + + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txParams.gasPrice + const resultTx = extend(txParams, { + gas, + gasPrice, + }) + const resultTxMeta = extend(txData, { + txParams: resultTx, + }) + log.debug(`UI has computed tx params ${JSON.stringify(resultTx)}`) + return resultTxMeta +} + function forwardCarrat () { return ( diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 96f968929..d39cbc0f8 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') +const extend = require('xtend') module.exports = PendingTx @@ -11,8 +12,9 @@ function PendingTx () { } PendingTx.prototype.render = function () { - var state = this.props - var txData = state.txData + const props = this.props + const newProps = extend(props, {ref: 'details'}) + const txData = props.txData return ( @@ -21,7 +23,7 @@ PendingTx.prototype.render = function () { }, [ // tx info - h(PendingTxDetails, state), + h(PendingTxDetails, newProps), h('style', ` .conf-buttons button { @@ -39,7 +41,7 @@ PendingTx.prototype.render = function () { }, 'Transaction Error. Exception thrown in contract code.') : null, - state.insufficientBalance ? + props.insufficientBalance ? h('span.error', { style: { marginLeft: 50, @@ -57,20 +59,26 @@ PendingTx.prototype.render = function () { }, }, [ - state.insufficientBalance ? + props.insufficientBalance ? h('button.btn-green', { - onClick: state.buyEth, + onClick: props.buyEth, }, 'Buy Ether') : null, h('button.confirm', { - disabled: state.insufficientBalance, - onClick: state.sendTransaction, + disabled: props.insufficientBalance, + onClick: props.sendTransaction, }, 'Accept'), h('button.cancel.btn-red', { - onClick: state.cancelTransaction, + onClick: props.cancelTransaction, }, 'Reject'), + + h('button', { + onClick: () => { + this.refs.details.resetGasFields() + }, + }, 'Reset'), ]), ]) ) |