diff options
author | frankiebee <frankie.diamond@gmail.com> | 2017-03-29 09:02:39 +0800 |
---|---|---|
committer | frankiebee <frankie.diamond@gmail.com> | 2017-03-29 09:02:39 +0800 |
commit | a20a237282c38da86b03625d08b48c2e58e83523 (patch) | |
tree | 65c58173cf8e0da53dbb6b9f18014ed8c1babf9c /ui | |
parent | 79248ae5cd3fb1314c5a7ff71c05f9dbe7b3a4cd (diff) | |
parent | 7c09bde4120d1063df762076d41d2e9921dd3c0e (diff) | |
download | tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar.gz tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar.bz2 tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar.lz tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar.xz tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.tar.zst tangerine-wallet-browser-a20a237282c38da86b03625d08b48c2e58e83523.zip |
Merge branch 'master' into mascara
Diffstat (limited to 'ui')
-rw-r--r-- | ui/app/accounts/index.js | 2 | ||||
-rw-r--r-- | ui/app/actions.js | 6 | ||||
-rw-r--r-- | ui/app/components/buy-button-subview.js | 11 | ||||
-rw-r--r-- | ui/app/components/hex-as-decimal-input.js | 132 | ||||
-rw-r--r-- | ui/app/components/notice.js | 8 | ||||
-rw-r--r-- | ui/app/components/pending-tx-details.js | 344 | ||||
-rw-r--r-- | ui/app/components/pending-tx.js | 437 | ||||
-rw-r--r-- | ui/app/conf-tx.js | 57 | ||||
-rw-r--r-- | ui/app/css/index.css | 12 | ||||
-rw-r--r-- | ui/app/keychains/hd/recover-seed/confirmation.js | 32 | ||||
-rw-r--r-- | ui/app/reducers.js | 6 | ||||
-rw-r--r-- | ui/app/reducers/app.js | 3 | ||||
-rw-r--r-- | ui/app/reducers/metamask.js | 1 | ||||
-rw-r--r-- | ui/lib/account-link.js | 2 | ||||
-rw-r--r-- | ui/lib/explorer-link.js | 2 | ||||
-rw-r--r-- | ui/lib/tx-helper.js | 4 |
16 files changed, 536 insertions, 523 deletions
diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..ae69d9297 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -11,7 +11,7 @@ module.exports = connect(mapStateToProps)(AccountsScreen) function mapStateToProps (state) { const pendingTxs = valuesFor(state.metamask.unapprovedTxs) - .filter(tx => tx.txParams.metamaskNetworkId === state.metamask.network) + .filter(txMeta => txMeta.metamaskNetworkId === state.metamask.network) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) const pending = pendingTxs.concat(pendingMsgs) diff --git a/ui/app/actions.js b/ui/app/actions.js index d02b7dcaa..60b4c6f03 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -273,8 +273,10 @@ function requestRevealSeed (password) { return dispatch(actions.displayWarning(err.message)) } log.debug(`background.placeSeedWords`) - background.placeSeedWords((err) => { + background.placeSeedWords((err, result) => { if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.hideLoadingIndication()) + dispatch(actions.showNewVaultSeed(result)) }) }) } @@ -397,7 +399,6 @@ function signTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) - dispatch(actions.goHome()) }) dispatch(this.showConfTxPage()) } @@ -422,6 +423,7 @@ function updateAndApproveTx (txData) { return (dispatch) => { log.debug(`actions calling background.updateAndApproveTx`) background.updateAndApproveTransaction(txData, (err) => { + dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) return console.error(err.message) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 3074bd7cd..7b993110d 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -121,15 +121,22 @@ BuyButtonSubview.prototype.formVersionSubview = function () { h('h3.text-transform-uppercase', { style: { width: '225px', + marginBottom: '15px', }, }, 'In order to access this feature, please switch to the Main Network'), - (this.props.network === '3') ? h('h3.text-transform-uppercase', 'or:') : null, + ((this.props.network === '3') || (this.props.network === '42')) ? h('h3.text-transform-uppercase', 'or go to the') : null, (this.props.network === '3') ? h('button.text-transform-uppercase', { onClick: () => this.props.dispatch(actions.buyEth()), style: { marginTop: '15px', }, - }, 'Go To Test Faucet') : null, + }, 'Ropsten Test Faucet') : null, + (this.props.network === '42') ? h('button.text-transform-uppercase', { + onClick: () => this.props.dispatch(actions.buyEth()), + style: { + marginTop: '15px', + }, + }, 'Kovan Test Faucet') : null, ]) } } diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index c89ed0416..e37aaa8c3 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -9,6 +9,7 @@ module.exports = HexAsDecimalInput inherits(HexAsDecimalInput, Component) function HexAsDecimalInput () { + this.state = { invalid: null } Component.call(this) } @@ -23,49 +24,120 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange } = props + const state = this.state + + const { value, onChange, min, max } = 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', { - type: 'number', - style: extend({ - display: 'block', - textAlign: 'right', - backgroundColor: 'transparent', - border: '1px solid #bdbdbd', - - }, style), - value: decimalValue, - onChange: (event) => { - const hexString = (event.target.value === '') ? '' : hexify(event.target.value) - onChange(hexString) + h('.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', }, - }), - h('div', { + }, [ + h('input.hex-input', { + type: 'number', + required: true, + min: min, + max: max, + style: extend({ + display: 'block', + textAlign: 'right', + backgroundColor: 'transparent', + border: '1px solid #bdbdbd', + + }, style), + value: parseInt(decimalValue), + onBlur: (event) => { + this.updateValidity(event) + }, + onChange: (event) => { + this.updateValidity(event) + const hexString = (event.target.value === '') ? '' : hexify(event.target.value) + onChange(hexString) + }, + onInvalid: (event) => { + const msg = this.constructWarning() + if (msg === state.invalid) { + return + } + this.setState({ invalid: msg }) + event.preventDefault() + return false + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]), + + state.invalid ? h('span.error', { style: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - marginRight: '6px', - width: '20px', + position: 'absolute', + right: '0px', + textAlign: 'right', + transform: 'translateY(26px)', + padding: '3px', + background: 'rgba(255,255,255,0.85)', + zIndex: '1', + textTransform: 'capitalize', + border: '2px solid #E20202', }, - }, suffix), + }, state.invalid) : null, ]) ) } +HexAsDecimalInput.prototype.setValid = function (message) { + this.setState({ invalid: null }) +} + +HexAsDecimalInput.prototype.updateValidity = function (event) { + const target = event.target + const value = this.props.value + const newValue = target.value + + if (value === newValue) { + return + } + + const valid = target.checkValidity() + if (valid) { + this.setState({ invalid: null }) + } +} + +HexAsDecimalInput.prototype.constructWarning = function () { + const { name, min, max } = this.props + let message = name ? name + ' ' : '' + + if (min && max) { + message += `must be greater than or equal to ${min} and less than or equal to ${max}.` + } else if (min) { + message += `must be greater than or equal to ${min}.` + } else if (max) { + message += `must be less than or equal to ${max}.` + } else { + message += 'Invalid input.' + } + + return message +} + function hexify (decimalString) { const hexBN = new BN(decimalString, 10) return '0x' + hexBN.toString('hex') diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 23ded9d5d..b85787033 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -92,6 +92,7 @@ Notice.prototype.render = function () { }, }, [ h(ReactMarkdown, { + className: 'notice-box', source: body, skipHtml: true, }), @@ -99,7 +100,10 @@ Notice.prototype.render = function () { h('button', { disabled, - onClick: onConfirm, + onClick: () => { + this.setState({disclaimerDisabled: true}) + onConfirm() + }, style: { marginTop: '18px', }, @@ -111,6 +115,8 @@ Notice.prototype.render = function () { Notice.prototype.componentDidMount = function () { var node = findDOMNode(this) linker.setupListener(node) + if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { this.setState({disclaimerDisabled: false}) } + } Notice.prototype.componentWillUnmount = function () { diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js deleted file mode 100644 index e92ce575f..000000000 --- a/ui/app/components/pending-tx-details.js +++ /dev/null @@ -1,344 +0,0 @@ -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 - -inherits(PendingTxDetails, Component) -function PendingTxDetails () { - Component.call(this) -} - -const PTXP = PendingTxDetails.prototype - -PTXP.render = function () { - var props = this.props - var state = this.state || {} - var txData = state.txMeta || props.txData - - var txParams = txData.txParams || {} - var address = txParams.from || props.selectedAddress - var identity = props.identities[address] || { address: address } - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - - const gas = (state.gas === undefined) ? txParams.gas : state.gas - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.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', [ - - h('.flex-row.flex-center', { - style: { - maxWidth: '100%', - }, - }, [ - - h(MiniAccountPanel, { - imageSeed: address, - imageifyIdenticons: imageify, - picOrder: 'right', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, identity.name), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(address, 6, 4, false)), - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, [ - h(EthBalance, { - value: balance, - inline: true, - labelColor: '#F7861C', - }), - ]), - - ]), - - forwardCarrat(), - - this.miniAccountPanelForRecipient(), - ]), - - h('style', ` - .table-box { - margin: 7px 0px 0px 0px; - width: 100%; - } - .table-box .row { - margin: 0px; - background: rgb(236,236,236); - display: flex; - justify-content: space-between; - font-family: Montserrat Light, sans-serif; - font-size: 13px; - padding: 5px 25px; - } - .table-box .row .value { - font-family: Montserrat Regular; - } - `), - - 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}`) - 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}`) - this.setState({ gasPrice: newHex }) - }, - }), - ]), - ]), - - // Max Transaction Fee (calculated) - h('.cell.row', [ - h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFee.toString(16) }), - ]), - - h('.cell.row', { - style: { - fontFamily: 'Montserrat Regular', - background: 'white', - padding: '10px 25px', - }, - }, [ - h('.cell.label', 'Max Total'), - h('.cell.value', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - h(EthBalance, { - value: maxCost.toString(16), - inline: true, - labelColor: 'black', - fontSize: '16px', - }), - ]), - ]), - - // Data size row: - h('.cell.row', { - style: { - background: '#f7f7f7', - paddingBottom: '0px', - }, - }, [ - h('.cell.label'), - h('.cell.value', { - style: { - fontFamily: 'Montserrat Light', - fontSize: '11px', - }, - }, `Data included: ${dataLength} bytes`), - ]), - ]), // End of Table - - ]) - ) -} - -PTXP.miniAccountPanelForRecipient = function () { - var props = this.props - var txData = props.txData - var txParams = txData.txParams || {} - var isContractDeploy = !('to' in txParams) - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - - // If it's not a contract deploy, send to the account - if (!isContractDeploy) { - return h(MiniAccountPanel, { - imageSeed: txParams.to, - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, nameForAddress(txParams.to, props.identities)), - h('span.font-small', { - style: { - fontFamily: 'Montserrat Light, Montserrat, sans-serif', - }, - }, addressSummary(txParams.to, 6, 4, false)), - ]) - } else { - return h(MiniAccountPanel, { - imageifyIdenticons: imageify, - picOrder: 'left', - }, [ - - h('span.font-small', { - style: { - fontFamily: 'Montserrat Bold, Montserrat, sans-serif', - }, - }, 'New Contract'), - - ]) - } -} - -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 -} - -PTXP.verifyGasParams = function () { - // We call this in case the gas has not been modified at all - if (!this.state) { return true } - return this._notZeroOrEmptyString(this.state.gas) && this._notZeroOrEmptyString(this.state.gasPrice) -} - -PTXP._notZeroOrEmptyString = function (obj) { - return obj !== '' && obj !== '0x0' -} - -function forwardCarrat () { - return ( - - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) - - ) -} diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2ab6f25a9..1b83f5043 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,98 +2,413 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits -const PendingTxDetails = require('./pending-tx-details') -const extend = require('xtend') const actions = require('../actions') +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN +const hexToBn = require('../../../app/scripts/lib/hex-to-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') + +const MIN_GAS_PRICE_BN = new BN(20000000) +const MIN_GAS_LIMIT_BN = new BN(21000) + module.exports = connect(mapStateToProps)(PendingTx) function mapStateToProps (state) { - return { - - } + return {} } inherits(PendingTx, Component) function PendingTx () { Component.call(this) + this.state = { + valid: true, + txData: null, + } } PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, {ref: 'details'}) - const txData = props.txData + + const txMeta = this.gatherTxMeta() + const txParams = txMeta.txParams || {} + + const address = txParams.from || props.selectedAddress + const identity = props.identities[address] || { address: address } + const account = props.accounts[address] + const balance = account ? account.balance : '0x0' + + const gas = txParams.gas + const gasPrice = txParams.gasPrice + + const gasBn = hexToBn(gas) + const gasPriceBn = hexToBn(gasPrice) + + const txFeeBn = gasBn.mul(gasPriceBn) + const valueBn = hexToBn(txParams.value) + const maxCost = txFeeBn.add(valueBn) + + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 + const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + + const balanceBn = hexToBn(balance) + const insufficientBalance = balanceBn.lt(maxCost) + + this.inputs = [] return ( h('div', { - key: txData.id, + key: txMeta.id, }, [ - // tx info - h(PendingTxDetails, newProps), + h('form#pending-tx-form', { + onSubmit: (event) => { + event.preventDefault() + const form = document.querySelector('form#pending-tx-form') + const valid = form.checkValidity() + this.setState({ valid }) + if (valid && this.verifyGasParams()) { + props.sendTransaction(txMeta, event) + } else { + this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) + } + }, + }, [ - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), + // tx info + h('div', [ - txData.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, + h('.flex-row.flex-center', { + style: { + maxWidth: '100%', + }, + }, [ + + h(MiniAccountPanel, { + imageSeed: address, + imageifyIdenticons: imageify, + picOrder: 'right', + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, identity.name), + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(address, 6, 4, false)), + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, [ + h(EthBalance, { + value: balance, + inline: true, + labelColor: '#F7861C', + }), + ]), + ]), + + forwardCarrat(), - props.insufficientBalance ? - h('span.error', { + this.miniAccountPanelForRecipient(), + ]), + + h('style', ` + .table-box { + margin: 7px 0px 0px 0px; + width: 100%; + } + .table-box .row { + margin: 0px; + background: rgb(236,236,236); + display: flex; + justify-content: space-between; + font-family: Montserrat Light, sans-serif; + font-size: 13px; + padding: 5px 25px; + } + .table-box .row .value { + font-family: Montserrat Regular; + } + `), + + 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, { + name: 'Gas Limit', + value: gas, + // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), + suffix: 'UNITS', + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas limit changed to ${newHex}`) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gas = newHex + this.setState({ txData: txMeta }) + }, + ref: (hexInput) => { this.inputs.push(hexInput) }, + }), + ]), + ]), + + // Gas Price (customizable) + h('.cell.row', [ + h('.cell.label', 'Gas Price'), + h('.cell.value', { + }, [ + h(HexInput, { + name: 'Gas Price', + value: gasPrice, + suffix: 'WEI', + min: MIN_GAS_PRICE_BN.toString(10), + style: { + position: 'relative', + top: '5px', + }, + onChange: (newHex) => { + log.info(`Gas price changed to: ${newHex}`) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gasPrice = newHex + this.setState({ txData: txMeta }) + }, + ref: (hexInput) => { this.inputs.push(hexInput) }, + }), + ]), + ]), + + // Max Transaction Fee (calculated) + h('.cell.row', [ + h('.cell.label', 'Max Transaction Fee'), + h(EthBalance, { value: txFeeBn.toString(16) }), + ]), + + h('.cell.row', { + style: { + fontFamily: 'Montserrat Regular', + background: 'white', + padding: '10px 25px', + }, + }, [ + h('.cell.label', 'Max Total'), + h('.cell.value', { + style: { + display: 'flex', + alignItems: 'center', + }, + }, [ + h(EthBalance, { + value: maxCost.toString(16), + inline: true, + labelColor: 'black', + fontSize: '16px', + }), + ]), + ]), + + // Data size row: + h('.cell.row', { + style: { + background: '#f7f7f7', + paddingBottom: '0px', + }, + }, [ + h('.cell.label'), + h('.cell.value', { + style: { + fontFamily: 'Montserrat Light', + fontSize: '11px', + }, + }, `Data included: ${dataLength} bytes`), + ]), + ]), // End of Table + + ]), + + h('style', ` + .conf-buttons button { + margin-left: 10px; + text-transform: uppercase; + } + `), + + txMeta.simulationFails ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, + + insufficientBalance ? + h('span.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') + : null, + + // send + cancel + h('.flex-row.flex-space-around.conf-buttons', { style: { - marginLeft: 50, - fontSize: '0.9em', + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', }, - }, 'Insufficient balance for transaction') - : null, + }, [ - // send + cancel - h('.flex-row.flex-space-around.conf-buttons', { - style: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', - }, - }, [ - props.insufficientBalance ? + insufficientBalance ? + h('button', { + onClick: props.buyEth, + }, 'Buy Ether') + : null, + h('button', { - onClick: props.buyEth, - }, 'Buy Ether') - : null, + onClick: (event) => { + this.resetGasFields() + event.preventDefault() + }, + }, 'Reset'), - h('button', { - onClick: () => { - this.refs.details.resetGasFields() - }, - }, 'Reset'), - - h('button.confirm.btn-green', { - disabled: props.insufficientBalance, - onClick: (txData, event) => { - if (this.refs.details.verifyGasParams()) { - props.sendTransaction(txData, event) - } else { - this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) - } - }, - }, 'Accept'), + // Accept Button + h('input.confirm.btn-green', { + type: 'submit', + value: 'ACCEPT', + style: { marginLeft: '10px' }, + disabled: insufficientBalance || !this.state.valid, + }), - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), + h('button.cancel.btn-red', { + onClick: props.cancelTransaction, + }, 'Reject'), + ]), ]), ]) ) } + +PendingTx.prototype.miniAccountPanelForRecipient = function () { + const props = this.props + const txData = props.txData + const txParams = txData.txParams || {} + const isContractDeploy = !('to' in txParams) + const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + + // If it's not a contract deploy, send to the account + if (!isContractDeploy) { + return h(MiniAccountPanel, { + imageSeed: txParams.to, + imageifyIdenticons: imageify, + picOrder: 'left', + }, [ + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, nameForAddress(txParams.to, props.identities)), + h('span.font-small', { + style: { + fontFamily: 'Montserrat Light, Montserrat, sans-serif', + }, + }, addressSummary(txParams.to, 6, 4, false)), + ]) + } else { + return h(MiniAccountPanel, { + imageifyIdenticons: imageify, + picOrder: 'left', + }, [ + + h('span.font-small', { + style: { + fontFamily: 'Montserrat Bold, Montserrat, sans-serif', + }, + }, 'New Contract'), + + ]) + } +} + +PendingTx.prototype.resetGasFields = function () { + log.debug(`pending-tx resetGasFields`) + + this.inputs.forEach((hexInput) => { + if (hexInput) { + hexInput.setValid() + } + }) + + this.setState({ + txData: null, + valid: true, + }) +} + +// After a customizable state value has been updated, +PendingTx.prototype.gatherTxMeta = function () { + log.debug(`pending-tx gatherTxMeta`) + const props = this.props + const state = this.state + const txData = state.txData || props.txData + + log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) + return txData +} + +PendingTx.prototype.verifyGasParams = function () { + // We call this in case the gas has not been modified at all + if (!this.state) { return true } + return ( + this._notZeroOrEmptyString(this.state.gas) && + this._notZeroOrEmptyString(this.state.gasPrice) + ) +} + +PendingTx.prototype._notZeroOrEmptyString = function (obj) { + return obj !== '' && obj !== '0x0' +} + +function forwardCarrat () { + return ( + + h('img', { + src: 'images/forward-carrat.svg', + style: { + padding: '5px 6px 0px 10px', + height: '37px', + }, + }) + + ) +} + diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 07985094c..3b8618992 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -7,8 +7,6 @@ const actions = require('./actions') const NetworkIndicator = require('./components/network') const txHelper = require('../lib/tx-helper') const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') @@ -43,8 +41,8 @@ ConfirmTxScreen.prototype.render = function () { unapprovedMsgs, unapprovedPersonalMsgs } = props var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) - var index = props.index !== undefined && unconfTxList[index] ? props.index : 0 - var txData = unconfTxList[index] || {} + + var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} var isNotification = isPopupOrNotification() === 'notification' @@ -104,9 +102,6 @@ ConfirmTxScreen.prototype.render = function () { selectedAddress: props.selectedAddress, 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), @@ -145,41 +140,19 @@ function currentTxView (opts) { } } -ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { - if (!txData.txParams) return false - var props = this.props - var address = txData.txParams.from || props.selectedAddress - var account = props.accounts[address] - var balance = account ? account.balance : '0x0' - var maxCost = new BN(txData.maxCost, 16) - - var balanceBn = new BN(ethUtil.stripHexPrefix(balance), 16) - return maxCost.gt(balanceBn) -} - ConfirmTxScreen.prototype.buyEth = function (address, event) { - event.stopPropagation() + this.stopPropagation(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() - const state = this.state || {} - const txMeta = state.txData - this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) + this.stopPropagation(event) + this.props.dispatch(actions.updateAndApproveTx(txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.cancelTx(txData)) } @@ -187,32 +160,38 @@ ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams params.metamaskId = msgData.id - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.signMsg(params)) } +ConfirmTxScreen.prototype.stopPropagation = function (event) { + if (event.stopPropagation) { + event.stopPropagation() + } +} + ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { log.info('conf-tx.js: signing personal message') var params = msgData.msgParams params.metamaskId = msgData.id - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.signPersonalMsg(params)) } ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { log.info('canceling message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelMsg(msgData)) } ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { log.info('canceling personal message') - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelPersonalMsg(msgData)) } ConfirmTxScreen.prototype.goHome = function (event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.goHome()) } diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 3ec0ac5c5..de8ae0e92 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -32,7 +32,7 @@ input:focus, textarea:focus { height: 500px; } -button { +button, input[type="submit"] { font-family: 'Montserrat Bold'; outline: none; cursor: pointer; @@ -46,17 +46,17 @@ button { box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36); } -button.btn-green { +.btn-green, input[type="submit"].btn-green { background: rgba(106, 195, 96, 1); box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36); } -button.btn-red { +.btn-red { background: rgba(254, 35, 17, 1); box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36); } -button[disabled] { +button[disabled], input[type="submit"][disabled] { cursor: not-allowed; background: rgba(197, 197, 197, 1); box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36); @@ -66,10 +66,10 @@ button.spaced { margin: 2px; } -button:not([disabled]):hover { +button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover { transform: scale(1.1); } -button:not([disabled]):active { +button:not([disabled]):active, input[type="submit"]:not([disabled]):active { transform: scale(0.95); } diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 56ac461ea..4ccbec9fc 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -18,11 +18,8 @@ function mapStateToProps (state) { } } -RevealSeedConfirmation.prototype.confirmationPhrase = 'I understand' - RevealSeedConfirmation.prototype.render = function () { const props = this.props - const state = this.state return ( @@ -64,31 +61,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }), - h(`h4${state && state.confirmationWrong ? '.error' : ''}`, { - style: { - marginTop: '12px', - }, - }, `Enter the phrase "${this.confirmationPhrase}" to proceed.`), - - // confirm confirmation - h('input.large-input.letter-spacey', { - type: 'text', - id: 'confirm-box', - placeholder: this.confirmationPhrase, - onKeyPress: this.checkConfirmation.bind(this), - style: { - width: 260, - marginTop: 16, - }, - }), - h('.flex-row.flex-space-between', { style: { marginTop: 30, width: '50%', }, }, [ -// cancel + // cancel h('button.primary', { onClick: this.goHome.bind(this), }, 'CANCEL'), @@ -134,15 +113,6 @@ RevealSeedConfirmation.prototype.checkConfirmation = function (event) { } RevealSeedConfirmation.prototype.revealSeedWords = function () { - this.setState({ confirmationWrong: false }) - - const confirmBox = document.getElementById('confirm-box') - const confirmation = confirmBox.value - if (confirmation !== this.confirmationPhrase) { - confirmBox.value = '' - return this.setState({ confirmationWrong: true }) - } - var password = document.getElementById('password-box').value this.props.dispatch(actions.requestRevealSeed(password)) } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 4d10e2b39..c656af849 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,6 +42,10 @@ function rootReducer (state, action) { } window.logState = function () { - var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, null, 2) + var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2) console.log(stateString) } + +function removeSeedWords (key, value) { + return key === 'seedWords' ? undefined : value +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index b9e3f7b16..3a6baca91 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -592,8 +592,9 @@ function hasPendingTxs (state) { function indexForPending (state, txId) { var unapprovedTxs = state.metamask.unapprovedTxs var unapprovedMsgs = state.metamask.unapprovedMsgs + var unapprovedPersonalMsgs = state.metamask.unapprovedPersonalMsgs var network = state.metamask.network - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, network) + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) let idx unconfTxList.forEach((tx, i) => { if (tx.id === txId) { diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 2b5151466..e0c416c2d 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -94,6 +94,7 @@ function reduceMetamask (state, action) { return extend(metamaskState, { isUnlocked: true, isInitialized: false, + seedWords: action.value, }) case actions.CLEAR_SEED_WORD_CACHE: diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 948f32da1..4f27b35c0 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -9,7 +9,7 @@ module.exports = function (address, network) { link = `http://morden.etherscan.io/address/${address}` break case 3: // ropsten test net - link = `http://testnet.etherscan.io/address/${address}` + link = `http://ropsten.etherscan.io/address/${address}` break case 42: // kovan test net link = `http://kovan.etherscan.io/address/${address}` diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index 7ae19cca0..ca89f8b25 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -6,7 +6,7 @@ module.exports = function (hash, network) { prefix = '' break case 3: // ropsten test net - prefix = 'testnet.' + prefix = 'ropsten.' break case 42: // kovan test net prefix = 'kovan.' diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 2eefdff68..ec19daf64 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -4,7 +4,7 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug('tx-helper called with params:') log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) - const txValues = network ? valuesFor(unapprovedTxs).filter(tx => tx.txParams.metamaskNetworkId === network) : valuesFor(unapprovedTxs) + const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) @@ -13,5 +13,5 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) allValues = allValues.concat(personalValues) - return allValues.sort(tx => tx.time) + return allValues.sort(txMeta => txMeta.time) } |