diff options
author | Kevin Serrano <kevgagser@gmail.com> | 2017-03-01 08:54:10 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-01 08:54:10 +0800 |
commit | f162a11585392df5e593018a02549f8b03018d22 (patch) | |
tree | c91dae0c308fb41a461fe85ce4cbeca5b49211df /ui/app | |
parent | ab01fef1c0ffddf09a20f90a910e5e068cf8c71e (diff) | |
parent | a600ccd4f863d7a473392fc283f4cec248225a27 (diff) | |
download | tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar.gz tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar.bz2 tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar.lz tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar.xz tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.tar.zst tangerine-wallet-browser-f162a11585392df5e593018a02549f8b03018d22.zip |
Merge pull request #1154 from MetaMask/i765-gaslimits
Add ability to customize gas and gasPrice on tx confirmation screen
Diffstat (limited to 'ui/app')
-rw-r--r-- | ui/app/actions.js | 31 | ||||
-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 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 19 | ||||
-rw-r--r-- | ui/app/reducers/app.js | 2 | ||||
-rw-r--r-- | ui/app/send.js | 73 |
7 files changed, 275 insertions, 97 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js index 89a4fadfa..7f972fb37 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -94,6 +94,7 @@ var actions = { cancelPersonalMsg, sendTx: sendTx, signTx: signTx, + updateAndApproveTx, cancelTx: cancelTx, completedTx: completedTx, txError: txError, @@ -387,22 +388,18 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { - log.debug(`background.setGasMultiplier`) - background.setGasMultiplier(txData.gasMultiplier, (err) => { + web3.eth.sendTransaction(txData, (err, data) => { + dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) - web3.eth.sendTransaction(txData, (err, data) => { - dispatch(actions.hideLoadingIndication()) - if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.hideWarning()) - dispatch(actions.goHome()) - }) - dispatch(this.showConfTxPage()) + dispatch(actions.hideWarning()) + dispatch(actions.goHome()) }) + dispatch(this.showConfTxPage()) } } function sendTx (txData) { - log.info('actions: sendTx') + log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`) return (dispatch) => { log.debug(`actions calling background.approveTransaction`) background.approveTransaction(txData.id, (err) => { @@ -415,6 +412,20 @@ function sendTx (txData) { } } +function updateAndApproveTx (txData) { + log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData)) + return (dispatch) => { + log.debug(`actions calling background.updateAndApproveTx`) + background.updateAndApproveTransaction(txData, (err) => { + if (err) { + dispatch(actions.txError(err)) + return console.error(err.message) + } + dispatch(actions.completedTx(txData.id)) + }) + } +} + function completedTx (id) { return { type: actions.COMPLETED_TX, 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'), ]), ]) ) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 2df6c5384..7e93ea29f 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -13,6 +13,7 @@ const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') const PendingPersonalMsg = require('./components/pending-personal-msg') +const Loading = require('./components/loading') module.exports = connect(mapStateToProps)(ConfirmTxScreen) @@ -48,7 +49,7 @@ ConfirmTxScreen.prototype.render = function () { var isNotification = isPopupOrNotification() === 'notification' log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`) - if (unconfTxList.length === 0) return null + if (unconfTxList.length === 0) return h(Loading) return ( @@ -104,6 +105,8 @@ ConfirmTxScreen.prototype.render = function () { accounts: props.accounts, identities: props.identities, insufficientBalance: this.checkBalanceAgainstTx(txData), + // State actions + onTxChange: this.onTxChange.bind(this), // Actions buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress), sendTransaction: this.sendTransaction.bind(this, txData), @@ -141,6 +144,7 @@ function currentTxView (opts) { } } } + ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { if (!txData.txParams) return false var props = this.props @@ -158,9 +162,20 @@ ConfirmTxScreen.prototype.buyEth = function (address, event) { this.props.dispatch(actions.buyEthView(address)) } +// Allows the detail view to update the gas calculations, +// for manual gas controls. +ConfirmTxScreen.prototype.onTxChange = function (txData) { + log.debug(`conf-tx onTxChange triggered with ${JSON.stringify(txData)}`) + this.setState({ txData }) +} + +// Must default to any local state txData, +// to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { event.stopPropagation() - this.props.dispatch(actions.sendTx(txData)) + const state = this.state || {} + const txMeta = state.txData + this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 136326301..7ea1e1d7c 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -291,7 +291,7 @@ function reduceApp (state, action) { case actions.SHOW_CONF_TX_PAGE: return extend(appState, { currentView: { - name: pendingTxs ? 'confTx' : 'account-detail', + name: 'confTx', context: 0, }, transForward: action.transForward, diff --git a/ui/app/send.js b/ui/app/send.js index d16270b41..581e3afa0 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -10,8 +10,6 @@ const addressSummary = require('./util').addressSummary const isHex = require('./util').isHex const EthBalance = require('./components/eth-balance') const ethUtil = require('ethereumjs-util') -const RangeSlider = require('./components/range-slider') -const Tooltip = require('./components/tooltip') module.exports = connect(mapStateToProps)(SendTransactionScreen) function mapStateToProps (state) { @@ -208,73 +206,6 @@ SendTransactionScreen.prototype.render = function () { }, }), ]), - // custom gasPrice field - h('h3.flex-center.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - marginBottom: '5px', - }, - }, [ - 'Transaction Fee (optional)', - h(Tooltip, { - title: ` - This is used to set the transaction's gas price. - Setting it to 100% will use the full recommended value. `, - }, [ - h('i.fa.fa-question-circle', { - style: { - marginLeft: '5px', - }, - }), - ]), - ]), - - h('section.flex-column.flex-center', [ - h('.flex-row', [ - h(RangeSlider, { - name: 'gasInput', - options: { - mirrorInput: true, - defaultValue: 100, - min: 80, - max: 220, - }, - style: { - container: { - marginBottom: '16px', - }, - range: { - width: '68vw', - }, - input: { - width: '5em', - marginLeft: '5px', - }, - }, - }), - - h('div', { - style: { - fontSize: '12px', - paddingTop: '8px', - paddingLeft: '5px', - }, - }, '%'), - ]), - h('.flex-row', { - style: { - justifyContent: 'space-between', - width: '243px', - position: 'relative', - fontSize: '12px', - right: '42px', - bottom: '30px', - }, - }, [ - h('span', 'Cheaper'), h('span', 'Faster'), - ]), - ]), ]) ) } @@ -289,12 +220,11 @@ SendTransactionScreen.prototype.back = function () { this.props.dispatch(actions.backToAccountDetail(address)) } -SendTransactionScreen.prototype.onSubmit = function (gasPrice) { +SendTransactionScreen.prototype.onSubmit = function () { const recipient = document.querySelector('input[name="address"]').value const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) const txData = document.querySelector('input[name="txData"]').value - const gasMultiplier = document.querySelector('input[name="gasInput"]').value const balance = this.props.balance let message @@ -323,7 +253,6 @@ SendTransactionScreen.prototype.onSubmit = function (gasPrice) { var txParams = { from: this.props.address, value: '0x' + value.toString(16), - gasMultiplier: gasMultiplier * 0.01, } if (recipient) txParams.to = ethUtil.addHexPrefix(recipient) |