From 9f1f0bff1ea3267702a2ab75af293e6265e255e4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 10:35:02 -0700 Subject: [PATCH 01/52] Some progress --- ui/app/components/hex-as-decimal-input.js | 5 +++-- ui/app/components/pending-tx-details.js | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index c89ed0416..b2f1917f2 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -23,7 +23,7 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange } = props + const { value, onChange, min } = props const toEth = props.toEth const suffix = props.suffix const decimalValue = decimalize(value, toEth) @@ -38,8 +38,9 @@ HexAsDecimalInput.prototype.render = function () { textRendering: 'geometricPrecision', }, }, [ - h('input.ether-balance.ether-balance-amount', { + h('input.hex-input', { type: 'number', + min, style: extend({ display: 'block', textAlign: 'right', diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index e92ce575f..822e5d84b 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -12,6 +12,10 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') +const DEFAULT_GAS_PRICE = '0x4a817c800' +const DEFAULT_GAS_PRICE_BN = new BN(DEFAULT_GAS_PRICE.substr(2), 16) +const FOUR_BN = new BN('4', 10) + module.exports = PendingTxDetails inherits(PendingTxDetails, Component) @@ -78,7 +82,6 @@ PTXP.render = function () { labelColor: '#F7861C', }), ]), - ]), forwardCarrat(), @@ -122,6 +125,7 @@ PTXP.render = function () { }, [ h(HexInput, { value: gas, + min: 21000, // The hard lower limit for gas. suffix: 'UNITS', style: { position: 'relative', @@ -143,6 +147,7 @@ PTXP.render = function () { h(HexInput, { value: gasPrice, suffix: 'WEI', + min: DEFAULT_GAS_PRICE_BN.div(FOUR_BN).toString(10), style: { position: 'relative', top: '5px', @@ -176,7 +181,8 @@ PTXP.render = function () { }, }, [ h(EthBalance, { - value: maxCost.toString(16), + value: '0x' + txFee.add(new BN(txParams.value, 16)).toString(16), + maxCost.toString(16), inline: true, labelColor: 'black', fontSize: '16px', @@ -267,7 +273,7 @@ PTXP.calculateGas = function () { 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 gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) @@ -280,10 +286,12 @@ PTXP.calculateGas = function () { txMeta.maxCost = maxCostHex txMeta.txParams.gasPrice = gasPriceHex - this.setState({ + const newState = { txFee: '0x' + txFee.toString('hex'), maxCost: '0x' + maxCost.toString('hex'), - }) + } + log.info(`tx form updating local state with ${JSON.stringify(newState)}`) + this.setState(newState) if (this.props.onTxChange) { this.props.onTxChange(txMeta) From 77907038ffe0edbe5e3472f98fd10d73b76464b8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 15:14:33 -0700 Subject: [PATCH 02/52] Got basic validations working --- ui/app/components/hex-as-decimal-input.js | 114 ++++++++++++++----- ui/app/components/pending-tx-details.js | 26 +++-- ui/app/components/pending-tx.js | 133 +++++++++++++--------- ui/app/conf-tx.js | 22 ++-- ui/app/css/index.css | 12 +- 5 files changed, 199 insertions(+), 108 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index b2f1917f2..bbd86b254 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,50 +24,101 @@ function HexAsDecimalInput () { HexAsDecimalInput.prototype.render = function () { const props = this.props - const { value, onChange, min } = 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.hex-input', { - type: 'number', - min, - 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), + onChange: (event) => { + const target = event.target + const valid = target.checkValidity() + if (valid) { + this.setState({ invalid: null }) + } + 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() + }, + }), + 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.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/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 822e5d84b..375a50f22 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -12,15 +12,17 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') -const DEFAULT_GAS_PRICE = '0x4a817c800' -const DEFAULT_GAS_PRICE_BN = new BN(DEFAULT_GAS_PRICE.substr(2), 16) -const FOUR_BN = new BN('4', 10) +const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') +const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) + +const MIN_GAS_PRICE_BN = new BN(20000000) module.exports = PendingTxDetails inherits(PendingTxDetails, Component) function PendingTxDetails () { Component.call(this) + this.state = { valid: true } } const PTXP = PendingTxDetails.prototype @@ -40,6 +42,7 @@ PTXP.render = function () { const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice var txFee = state.txFee || txData.txFee || '' + var txFeeBn = new BN(txFee, 16) var maxCost = state.maxCost || txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons @@ -124,6 +127,7 @@ PTXP.render = function () { h('.cell.value', { }, [ h(HexInput, { + name: 'Gas Limit', value: gas, min: 21000, // The hard lower limit for gas. suffix: 'UNITS', @@ -145,9 +149,10 @@ PTXP.render = function () { h('.cell.value', { }, [ h(HexInput, { + name: 'Gas Price', value: gasPrice, suffix: 'WEI', - min: DEFAULT_GAS_PRICE_BN.div(FOUR_BN).toString(10), + min: MIN_GAS_PRICE_BN.toString(10), style: { position: 'relative', top: '5px', @@ -163,7 +168,7 @@ PTXP.render = function () { // Max Transaction Fee (calculated) h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), - h(EthBalance, { value: txFee.toString(16) }), + h(EthBalance, { value: txFeeBn.toString(16) }), ]), h('.cell.row', { @@ -181,8 +186,7 @@ PTXP.render = function () { }, }, [ h(EthBalance, { - value: '0x' + txFee.add(new BN(txParams.value, 16)).toString(16), - maxCost.toString(16), + value: maxCost.toString(16), inline: true, labelColor: 'black', fontSize: '16px', @@ -267,6 +271,10 @@ PTXP.componentDidUpdate = function (prevProps, previousState) { } } +PTXP.isValid = function () { + return this.state.valid +} + PTXP.calculateGas = function () { const txMeta = this.gatherParams() log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) @@ -274,6 +282,10 @@ PTXP.calculateGas = function () { var txParams = txMeta.txParams var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + this.props.validChanged(valid) + var txFee = gasCost.mul(gasPrice) var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) var maxCost = txValue.add(txFee) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2ab6f25a9..f6fb6f85e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -17,11 +17,15 @@ function mapStateToProps (state) { inherits(PendingTx, Component) function PendingTx () { Component.call(this) + this.state = { valid: true } } PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, {ref: 'details'}) + const newProps = extend(props, { + ref: 'details', + validChanged: this.validChanged.bind(this), + }) const txData = props.txData return ( @@ -30,70 +34,87 @@ PendingTx.prototype.render = function () { key: txData.id, }, [ - // tx info - h(PendingTxDetails, newProps), - - h('style', ` - .conf-buttons button { - margin-left: 10px; - text-transform: uppercase; - } - `), - - txData.simulationFails ? - h('.error', { - style: { - marginLeft: 50, - fontSize: '0.9em', - }, - }, 'Transaction Error. Exception thrown in contract code.') - : null, - - props.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: { - display: 'flex', - justifyContent: 'flex-end', - margin: '14px 25px', + 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.refs.details.verifyGasParams()) { + props.sendTransaction(txData, event) + } else { + this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) + } }, }, [ + // tx info + h(PendingTxDetails, newProps), + + h('style', ` + .conf-buttons button { + margin-left: 10px; + text-transform: uppercase; + } + `), + + txData.simulationFails ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, + props.insufficientBalance ? - h('button', { - onClick: props.buyEth, - }, 'Buy Ether') + h('span.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') : null, - 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')) - } + // send + cancel + h('.flex-row.flex-space-around.conf-buttons', { + style: { + display: 'flex', + justifyContent: 'flex-end', + margin: '14px 25px', }, - }, 'Accept'), + }, [ + - h('button.cancel.btn-red', { - onClick: props.cancelTransaction, - }, 'Reject'), + props.insufficientBalance ? + h('button', { + onClick: props.buyEth, + }, 'Buy Ether') + : null, + + h('button', { + onClick: () => { + this.refs.details.resetGasFields() + }, + }, 'Reset'), + + h('input.confirm.btn-green', { + type: 'submit', + value: 'ACCEPT', + style: { marginLeft: '10px' }, + disabled: props.insufficientBalance || !this.state.valid, + }), + + h('button.cancel.btn-red', { + onClick: props.cancelTransaction, + }, 'Reject'), + ]), ]), ]) ) } + +PendingTx.prototype.validChanged = function (newValid) { + this.setState({ valid: newValid }) +} diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 07985094c..ce7422f9c 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -158,7 +158,7 @@ ConfirmTxScreen.prototype.checkBalanceAgainstTx = function (txData) { } ConfirmTxScreen.prototype.buyEth = function (address, event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.buyEthView(address)) } @@ -172,14 +172,14 @@ ConfirmTxScreen.prototype.onTxChange = function (txData) { // Must default to any local state txData, // to allow manual override of gas calculations. ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) const state = this.state || {} const txMeta = state.txData this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { - event.stopPropagation() + this.stopPropagation(event) this.props.dispatch(actions.cancelTx(txData)) } @@ -187,32 +187,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 8c6ff29d3..7771ddd99 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); } From de0d9af066d57f98580d53359a53271bf8760515 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Mar 2017 15:30:49 -0700 Subject: [PATCH 03/52] Bump changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6621d89f4..8ddfb85ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. - Polish the private key UI. +- Enforce minimum values for gas price and gas limit. +- Fix bug where total gas was sometimes not live-updated. +- Fix bug where editing gas value could have some abrupt behaviors (#1233) ## 3.4.0 2017-3-8 From 8726ece16a9357219524c59c6bb463971063a92b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 21:29:46 -0400 Subject: [PATCH 04/52] Create announcement for Kovan. --- notices/archive/notice_1.md | 1 + notices/notice-nonce.json | 2 +- notices/notices.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 notices/archive/notice_1.md diff --git a/notices/archive/notice_1.md b/notices/archive/notice_1.md new file mode 100644 index 000000000..488b60cc9 --- /dev/null +++ b/notices/archive/notice_1.md @@ -0,0 +1 @@ +MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura. diff --git a/notices/notice-nonce.json b/notices/notice-nonce.json index d00491fd7..d8263ee98 100644 --- a/notices/notice-nonce.json +++ b/notices/notice-nonce.json @@ -1 +1 @@ -1 +2 \ No newline at end of file diff --git a/notices/notices.json b/notices/notices.json index 9f28b32a6..5503c7855 100644 --- a/notices/notices.json +++ b/notices/notices.json @@ -1 +1 @@ -[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0}] \ No newline at end of file +[{"read":false,"date":"Thu Feb 09 2017","title":"Terms of Use","body":"# Terms of Use #\n\n**THIS AGREEMENT IS SUBJECT TO BINDING ARBITRATION AND A WAIVER OF CLASS ACTION RIGHTS AS DETAILED IN SECTION 13. PLEASE READ THE AGREEMENT CAREFULLY.**\n\n_Our Terms of Use have been updated as of September 5, 2016_\n\n## 1. Acceptance of Terms ##\n\nMetaMask provides a platform for managing Ethereum (or \"ETH\") accounts, and allowing ordinary websites to interact with the Ethereum blockchain, while keeping the user in control over what transactions they approve, through our website located at[ ](http://metamask.io)[https://metamask.io/](https://metamask.io/) and browser plugin (the \"Site\") — which includes text, images, audio, code and other materials (collectively, the “Content”) and all of the features, and services provided. The Site, and any other features, tools, materials, or other services offered from time to time by MetaMask are referred to here as the “Service.” Please read these Terms of Use (the “Terms” or “Terms of Use”) carefully before using the Service. By using or otherwise accessing the Services, or clicking to accept or agree to these Terms where that option is made available, you (1) accept and agree to these Terms (2) consent to the collection, use, disclosure and other handling of information as described in our Privacy Policy and (3) any additional terms, rules and conditions of participation issued by MetaMask from time to time. If you do not agree to the Terms, then you may not access or use the Content or Services.\n\n## 2. Modification of Terms of Use ##\n\nExcept for Section 13, providing for binding arbitration and waiver of class action rights, MetaMask reserves the right, at its sole discretion, to modify or replace the Terms of Use at any time. The most current version of these Terms will be posted on our Site. You shall be responsible for reviewing and becoming familiar with any such modifications. Use of the Services by you after any modification to the Terms constitutes your acceptance of the Terms of Use as modified.\n\n\n\n## 3. Eligibility ##\n\nYou hereby represent and warrant that you are fully able and competent to enter into the terms, conditions, obligations, affirmations, representations and warranties set forth in these Terms and to abide by and comply with these Terms.\n\nMetaMask is a global platform and by accessing the Content or Services, you are representing and warranting that, you are of the legal age of majority in your jurisdiction as is required to access such Services and Content and enter into arrangements as provided by the Service. You further represent that you are otherwise legally permitted to use the service in your jurisdiction including owning cryptographic tokens of value, and interacting with the Services or Content in any way. You further represent you are responsible for ensuring compliance with the laws of your jurisdiction and acknowledge that MetaMask is not liable for your compliance with such laws.\n\n## 4 Account Password and Security ##\n\nWhen setting up an account within MetaMask, you will be responsible for keeping your own account secrets, which may be a twelve-word seed phrase, an account file, or other locally stored secret information. MetaMask encrypts this information locally with a password you provide, that we never send to our servers. You agree to (a) never use the same password for MetaMask that you have ever used outside of this service; (b) keep your secret information and password confidential and do not share them with anyone else; (c) immediately notify MetaMask of any unauthorized use of your account or breach of security. MetaMask cannot and will not be liable for any loss or damage arising from your failure to comply with this section.\n\n## 5. Representations, Warranties, and Risks ##\n\n### 5.1. Warranty Disclaimer ###\n\nYou expressly understand and agree that your use of the Service is at your sole risk. The Service (including the Service and the Content) are provided on an \"AS IS\" and \"as available\" basis, without warranties of any kind, either express or implied, including, without limitation, implied warranties of merchantability, fitness for a particular purpose or non-infringement. You acknowledge that MetaMask has no control over, and no duty to take any action regarding: which users gain access to or use the Service; what effects the Content may have on you; how you may interpret or use the Content; or what actions you may take as a result of having been exposed to the Content. You release MetaMask from all liability for you having acquired or not acquired Content through the Service. MetaMask makes no representations concerning any Content contained in or accessed through the Service, and MetaMask will not be responsible or liable for the accuracy, copyright compliance, legality or decency of material contained in or accessed through the Service.\n\n### 5.2 Sophistication and Risk of Cryptographic Systems ###\n\nBy utilizing the Service or interacting with the Content or platform in any way, you represent that you understand the inherent risks associated with cryptographic systems; and warrant that you have an understanding of the usage and intricacies of native cryptographic tokens, like Ether (ETH) and Bitcoin (BTC), smart contract based tokens such as those that follow the Ethereum Token Standard (https://github.com/ethereum/EIPs/issues/20), and blockchain-based software systems.\n\n### 5.3 Risk of Regulatory Actions in One or More Jurisdictions ###\n\nMetaMask and ETH could be impacted by one or more regulatory inquiries or regulatory action, which could impede or limit the ability of MetaMask to continue to develop, or which could impede or limit your ability to access or use the Service or Ethereum blockchain.\n\n### 5.4 Risk of Weaknesses or Exploits in the Field of Cryptography ###\n\nYou acknowledge and understand that Cryptography is a progressing field. Advances in code cracking or technical advances such as the development of quantum computers may present risks to cryptocurrencies and Services of Content, which could result in the theft or loss of your cryptographic tokens or property. To the extent possible, MetaMask intends to update the protocol underlying Services to account for any advances in cryptography and to incorporate additional security measures, but does not guarantee or otherwise represent full security of the system. By using the Service or accessing Content, you acknowledge these inherent risks.\n\n### 5.5 Volatility of Crypto Currencies ###\n\nYou understand that Ethereum and other blockchain technologies and associated currencies or tokens are highly volatile due to many factors including but not limited to adoption, speculation, technology and security risks. You also acknowledge that the cost of transacting on such technologies is variable and may increase at any time causing impact to any activities taking place on the Ethereum blockchain. You acknowledge these risks and represent that MetaMask cannot be held liable for such fluctuations or increased costs.\n\n### 5.6 Application Security ###\n\nYou acknowledge that Ethereum applications are code subject to flaws and acknowledge that you are solely responsible for evaluating any code provided by the Services or Content and the trustworthiness of any third-party websites, products, smart-contracts, or Content you access or use through the Service. You further expressly acknowledge and represent that Ethereum applications can be written maliciously or negligently, that MetaMask cannot be held liable for your interaction with such applications and that such applications may cause the loss of property or even identity. This warning and others later provided by MetaMask in no way evidence or represent an on-going duty to alert you to all of the potential risks of utilizing the Service or Content.\n\n## 6. Indemnity ##\n\nYou agree to release and to indemnify, defend and hold harmless MetaMask and its parents, subsidiaries, affiliates and agencies, as well as the officers, directors, employees, shareholders and representatives of any of the foregoing entities, from and against any and all losses, liabilities, expenses, damages, costs (including attorneys’ fees and court costs) claims or actions of any kind whatsoever arising or resulting from your use of the Service, your violation of these Terms of Use, and any of your acts or omissions that implicate publicity rights, defamation or invasion of privacy. MetaMask reserves the right, at its own expense, to assume exclusive defense and control of any matter otherwise subject to indemnification by you and, in such case, you agree to cooperate with MetaMask in the defense of such matter.\n\n## 7. Limitation on liability ##\n\nYOU ACKNOWLEDGE AND AGREE THAT YOU ASSUME FULL RESPONSIBILITY FOR YOUR USE OF THE SITE AND SERVICE. YOU ACKNOWLEDGE AND AGREE THAT ANY INFORMATION YOU SEND OR RECEIVE DURING YOUR USE OF THE SITE AND SERVICE MAY NOT BE SECURE AND MAY BE INTERCEPTED OR LATER ACQUIRED BY UNAUTHORIZED PARTIES. YOU ACKNOWLEDGE AND AGREE THAT YOUR USE OF THE SITE AND SERVICE IS AT YOUR OWN RISK. RECOGNIZING SUCH, YOU UNDERSTAND AND AGREE THAT, TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER METAMASK NOR ITS SUPPLIERS OR LICENSORS WILL BE LIABLE TO YOU FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY OR OTHER DAMAGES OF ANY KIND, INCLUDING WITHOUT LIMITATION DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER TANGIBLE OR INTANGIBLE LOSSES OR ANY OTHER DAMAGES BASED ON CONTRACT, TORT, STRICT LIABILITY OR ANY OTHER THEORY (EVEN IF METAMASK HAD BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES), RESULTING FROM THE SITE OR SERVICE; THE USE OR THE INABILITY TO USE THE SITE OR SERVICE; UNAUTHORIZED ACCESS TO OR ALTERATION OF YOUR TRANSMISSIONS OR DATA; STATEMENTS OR CONDUCT OF ANY THIRD PARTY ON THE SITE OR SERVICE; ANY ACTIONS WE TAKE OR FAIL TO TAKE AS A RESULT OF COMMUNICATIONS YOU SEND TO US; HUMAN ERRORS; TECHNICAL MALFUNCTIONS; FAILURES, INCLUDING PUBLIC UTILITY OR TELEPHONE OUTAGES; OMISSIONS, INTERRUPTIONS, LATENCY, DELETIONS OR DEFECTS OF ANY DEVICE OR NETWORK, PROVIDERS, OR SOFTWARE (INCLUDING, BUT NOT LIMITED TO, THOSE THAT DO NOT PERMIT PARTICIPATION IN THE SERVICE); ANY INJURY OR DAMAGE TO COMPUTER EQUIPMENT; INABILITY TO FULLY ACCESS THE SITE OR SERVICE OR ANY OTHER WEBSITE; THEFT, TAMPERING, DESTRUCTION, OR UNAUTHORIZED ACCESS TO, IMAGES OR OTHER CONTENT OF ANY KIND; DATA THAT IS PROCESSED LATE OR INCORRECTLY OR IS INCOMPLETE OR LOST; TYPOGRAPHICAL, PRINTING OR OTHER ERRORS, OR ANY COMBINATION THEREOF; OR ANY OTHER MATTER RELATING TO THE SITE OR SERVICE.\n\nSOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, SOME OF THE ABOVE LIMITATIONS MAY NOT APPLY TO YOU.\n\n## 8. Our Proprietary Rights ##\n\nAll title, ownership and intellectual property rights in and to the Service are owned by MetaMask or its licensors. You acknowledge and agree that the Service contains proprietary and confidential information that is protected by applicable intellectual property and other laws. Except as expressly authorized by MetaMask, you agree not to copy, modify, rent, lease, loan, sell, distribute, perform, display or create derivative works based on the Service, in whole or in part. MetaMask issues a license for MetaMask, found [here](https://github.com/MetaMask/metamask-plugin/blob/master/LICENSE). For information on other licenses utilized in the development of MetaMask, please see our attribution page at: [https://metamask.io/attributions.html](https://metamask.io/attributions.html)\n\n## 9. Links ##\n\nThe Service provides, or third parties may provide, links to other World Wide Web or accessible sites, applications or resources. Because MetaMask has no control over such sites, applications and resources, you acknowledge and agree that MetaMask is not responsible for the availability of such external sites, applications or resources, and does not endorse and is not responsible or liable for any content, advertising, products or other materials on or available from such sites or resources. You further acknowledge and agree that MetaMask shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such site or resource.\n\n## 10. Termination and Suspension ##\n\nMetaMask may terminate or suspend all or part of the Service and your MetaMask access immediately, without prior notice or liability, if you breach any of the terms or conditions of the Terms. Upon termination of your access, your right to use the Service will immediately cease.\n\nThe following provisions of the Terms survive any termination of these Terms: INDEMNITY; WARRANTY DISCLAIMERS; LIMITATION ON LIABILITY; OUR PROPRIETARY RIGHTS; LINKS; TERMINATION; NO THIRD PARTY BENEFICIARIES; BINDING ARBITRATION AND CLASS ACTION WAIVER; GENERAL INFORMATION.\n\n## 11. No Third Party Beneficiaries ##\n\nYou agree that, except as otherwise expressly provided in these Terms, there shall be no third party beneficiaries to the Terms.\n\n## 12. Notice and Procedure For Making Claims of Copyright Infringement ##\n\nIf you believe that your copyright or the copyright of a person on whose behalf you are authorized to act has been infringed, please provide MetaMask’s Copyright Agent a written Notice containing the following information:\n\n· an electronic or physical signature of the person authorized to act on behalf of the owner of the copyright or other intellectual property interest;\n\n· a description of the copyrighted work or other intellectual property that you claim has been infringed;\n\n· a description of where the material that you claim is infringing is located on the Service;\n\n· your address, telephone number, and email address;\n\n· a statement by you that you have a good faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law;\n\n· a statement by you, made under penalty of perjury, that the above information in your Notice is accurate and that you are the copyright or intellectual property owner or authorized to act on the copyright or intellectual property owner's behalf.\n\nMetaMask’s Copyright Agent can be reached at:\n\nEmail: copyright [at] metamask [dot] io\n\nMail:\n\nAttention:\n\nMetaMask Copyright ℅ ConsenSys\n\n49 Bogart Street\n\nBrooklyn, NY 11206\n\n## 13. Binding Arbitration and Class Action Waiver ##\n\nPLEASE READ THIS SECTION CAREFULLY – IT MAY SIGNIFICANTLY AFFECT YOUR LEGAL RIGHTS, INCLUDING YOUR RIGHT TO FILE A LAWSUIT IN COURT\n\n### 13.1 Initial Dispute Resolution ###\n\nThe parties shall use their best efforts to engage directly to settle any dispute, claim, question, or disagreement and engage in good faith negotiations which shall be a condition to either party initiating a lawsuit or arbitration.\n\n### 13.2 Binding Arbitration ###\n\nIf the parties do not reach an agreed upon solution within a period of 30 days from the time informal dispute resolution under the Initial Dispute Resolution provision begins, then either party may initiate binding arbitration as the sole means to resolve claims, subject to the terms set forth below. Specifically, all claims arising out of or relating to these Terms (including their formation, performance and breach), the parties’ relationship with each other and/or your use of the Service shall be finally settled by binding arbitration administered by the American Arbitration Association in accordance with the provisions of its Commercial Arbitration Rules and the supplementary procedures for consumer related disputes of the American Arbitration Association (the \"AAA\"), excluding any rules or procedures governing or permitting class actions.\n\nThe arbitrator, and not any federal, state or local court or agency, shall have exclusive authority to resolve all disputes arising out of or relating to the interpretation, applicability, enforceability or formation of these Terms, including, but not limited to any claim that all or any part of these Terms are void or voidable, or whether a claim is subject to arbitration. The arbitrator shall be empowered to grant whatever relief would be available in a court under law or in equity. The arbitrator’s award shall be written, and binding on the parties and may be entered as a judgment in any court of competent jurisdiction.\n\nThe parties understand that, absent this mandatory provision, they would have the right to sue in court and have a jury trial. They further understand that, in some instances, the costs of arbitration could exceed the costs of litigation and the right to discovery may be more limited in arbitration than in court.\n\n### 13.3 Location ###\n\nBinding arbitration shall take place in New York. You agree to submit to the personal jurisdiction of any federal or state court in New York County, New York, in order to compel arbitration, to stay proceedings pending arbitration, or to confirm, modify, vacate or enter judgment on the award entered by the arbitrator.\n\n### 13.4 Class Action Waiver ###\n\nThe parties further agree that any arbitration shall be conducted in their individual capacities only and not as a class action or other representative action, and the parties expressly waive their right to file a class action or seek relief on a class basis. YOU AND METAMASK AGREE THAT EACH MAY BRING CLAIMS AGAINST THE OTHER ONLY IN YOUR OR ITS INDIVIDUAL CAPACITY, AND NOT AS A PLAINTIFF OR CLASS MEMBER IN ANY PURPORTED CLASS OR REPRESENTATIVE PROCEEDING. If any court or arbitrator determines that the class action waiver set forth in this paragraph is void or unenforceable for any reason or that an arbitration can proceed on a class basis, then the arbitration provision set forth above shall be deemed null and void in its entirety and the parties shall be deemed to have not agreed to arbitrate disputes.\n\n### 13.5 Exception - Litigation of Intellectual Property and Small Claims Court Claims ###\n\nNotwithstanding the parties' decision to resolve all disputes through arbitration, either party may bring an action in state or federal court to protect its intellectual property rights (\"intellectual property rights\" means patents, copyrights, moral rights, trademarks, and trade secrets, but not privacy or publicity rights). Either party may also seek relief in a small claims court for disputes or claims within the scope of that court’s jurisdiction.\n\n### 13.6 30-Day Right to Opt Out ###\n\nYou have the right to opt-out and not be bound by the arbitration and class action waiver provisions set forth above by sending written notice of your decision to opt-out to the following address: MetaMask ℅ ConsenSys, 49 Bogart Street, Brooklyn NY 11206 and via email at legal-opt@metamask.io. The notice must be sent within 30 days of September 6, 2016 or your first use of the Service, whichever is later, otherwise you shall be bound to arbitrate disputes in accordance with the terms of those paragraphs. If you opt-out of these arbitration provisions, MetaMask also will not be bound by them.\n\n### 13.7 Changes to This Section ###\n\nMetaMask will provide 60-days’ notice of any changes to this section. Changes will become effective on the 60th day, and will apply prospectively only to any claims arising after the 60th day.\n\nFor any dispute not subject to arbitration you and MetaMask agree to submit to the personal and exclusive jurisdiction of and venue in the federal and state courts located in New York, New York. You further agree to accept service of process by mail, and hereby waive any and all jurisdictional and venue defenses otherwise available.\n\nThe Terms and the relationship between you and MetaMask shall be governed by the laws of the State of New York without regard to conflict of law provisions.\n\n## 14. General Information ##\n\n### 14.1 Entire Agreement ###\n\nThese Terms (and any additional terms, rules and conditions of participation that MetaMask may post on the Service) constitute the entire agreement between you and MetaMask with respect to the Service and supersedes any prior agreements, oral or written, between you and MetaMask. In the event of a conflict between these Terms and the additional terms, rules and conditions of participation, the latter will prevail over the Terms to the extent of the conflict.\n\n### 14.2 Waiver and Severability of Terms ###\n\nThe failure of MetaMask to exercise or enforce any right or provision of the Terms shall not constitute a waiver of such right or provision. If any provision of the Terms is found by an arbitrator or court of competent jurisdiction to be invalid, the parties nevertheless agree that the arbitrator or court should endeavor to give effect to the parties' intentions as reflected in the provision, and the other provisions of the Terms remain in full force and effect.\n\n### 14.3 Statute of Limitations ###\n\nYou agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to the use of the Service or the Terms must be filed within one (1) year after such claim or cause of action arose or be forever barred.\n\n### 14.4 Section Titles ###\n\nThe section titles in the Terms are for convenience only and have no legal or contractual effect.\n\n### 14.5 Communications ###\n\nUsers with questions, complaints or claims with respect to the Service may contact us using the relevant contact information set forth above and at communications@metamask.io.\n\n## 15 Related Links ##\n\n**[Terms of Use](https://metamask.io/terms.html)**\n\n**[Privacy](https://metamask.io/privacy.html)**\n\n**[Attributions](https://metamask.io/attributions.html)**\n\n","id":0},{"read":false,"date":"Wed Mar 22 2017","title":"Announcing Kovan Support","body":"MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura.\n","id":1}] \ No newline at end of file From 61a62038084c29fb29c8445feca36bce9e756471 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 21:34:39 -0400 Subject: [PATCH 05/52] Reset disabled state for confirm button after every notice. --- ui/app/components/notice.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 23ded9d5d..8a953a6b5 100644 --- a/ui/app/components/notice.js +++ b/ui/app/components/notice.js @@ -99,7 +99,10 @@ Notice.prototype.render = function () { h('button', { disabled, - onClick: onConfirm, + onClick: () => { + this.setState({disclaimerDisabled: true}) + onConfirm() + }, style: { marginTop: '18px', }, From bcaf0864c18df4f8d8cb92c739669447fa5aa059 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 09:58:57 -0400 Subject: [PATCH 06/52] Remove notice body after reading. --- app/scripts/notice-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js index 0d72760fe..57aad40c5 100644 --- a/app/scripts/notice-controller.js +++ b/app/scripts/notice-controller.js @@ -41,6 +41,7 @@ module.exports = class NoticeController extends EventEmitter { var notices = this.getNoticesList() var index = notices.findIndex((currentNotice) => currentNotice.id === noticeToMark.id) notices[index].read = true + notices[index].body = '' this.setNoticesList(notices) const latestNotice = this.getLatestUnreadNotice() cb(null, latestNotice) From d99b5a9e5a1dfc62e16e6a80d51e8f3af5b289e4 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 10:15:42 -0400 Subject: [PATCH 07/52] Add migration tests to ensure that bodies are erased properly in notices. --- test/lib/migrations/004.json | 7 +++++++ test/unit/migrations-test.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/test/lib/migrations/004.json b/test/lib/migrations/004.json index 0e2075c46..a6487c1e2 100644 --- a/test/lib/migrations/004.json +++ b/test/lib/migrations/004.json @@ -48,6 +48,13 @@ "title":"Ending Morden Support", "body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n", "id":0 + }, + { + "read":false, + "date":"Sat Dec 17 2016", + "title":"Keeping It Real", + "body":"nonempty", + "id":1 } ], "conversionRate":12.66441492, diff --git a/test/unit/migrations-test.js b/test/unit/migrations-test.js index d2a83be77..ccd1477b0 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations-test.js @@ -15,6 +15,8 @@ const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009')) const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) +const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012')) + const oldTestRpc = 'https://rawtestrpc.metamask.io/' const newTestRpc = 'https://testrpc.metamask.io/' @@ -91,6 +93,11 @@ describe('wallet1 is migrated successfully', () => { }).then((eleventhResult) => { assert.equal(eleventhResult.data.isDisclaimerConfirmed, null, 'isDisclaimerConfirmed should not exist') assert.equal(eleventhResult.data.TOSHash, null, 'TOSHash should not exist') + + return migration12.migrate(eleventhResult) + }).then((twelfthResult) => { + assert.equal(twelfthResult.data.NoticeController.noticesList[0].body, '', 'notices that have been read should have an empty body.') + assert.equal(twelfthResult.data.NoticeController.noticesList[1].body, 'nonempty', 'notices that have not been read should not have an empty body.') }) }) From 16c76d522960298476492b53a02d1b490b84b858 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 10:15:59 -0400 Subject: [PATCH 08/52] Create migration to erase body in read notices. --- app/scripts/migrations/012.js | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/scripts/migrations/012.js diff --git a/app/scripts/migrations/012.js b/app/scripts/migrations/012.js new file mode 100644 index 000000000..8361b3793 --- /dev/null +++ b/app/scripts/migrations/012.js @@ -0,0 +1,36 @@ +const version = 12 + +/* + +This migration modifies our notices to delete their body after being read. + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + let versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + newState.NoticeController.noticesList.forEach((notice) => { + if (notice.read) { + notice.body = '' + } + }) + return newState +} From fa0bbd66b6896188c5a47ed6978fc992068ae22c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:28:06 -0400 Subject: [PATCH 09/52] Fix persistence of transactions between networks. --- app/scripts/transaction-manager.js | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 31c1c8431..7227bdc87 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -47,27 +47,39 @@ module.exports = class TransactionManager extends EventEmitter { // Returns the tx list getTxList () { let network = this.getNetwork() - let fullTxList = this.store.getState().transactions + let fullTxList = this.getFullTxList() return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) } + // Returns the number of txs for the current network. + getTxCount () { + return this.getTxList().length + } + + // Returns the full tx list across all networks + getFullTxList () { + return this.store.getState().transactions + } + // Adds a tx to the txlist addTx (txMeta) { - var txList = this.getTxList() - var txHistoryLimit = this.txHistoryLimit + let txCount = this.getTxCount() + let network = this.getNetwork() + let fullTxList = this.getFullTxList() + let txHistoryLimit = this.txHistoryLimit - // checks if the length of th tx history is + // checks if the length of the tx history is // longer then desired persistence limit // and then if it is removes only confirmed // or rejected tx's. // not tx's that are pending or unapproved - if (txList.length > txHistoryLimit - 1) { - var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') - txList.splice(index, 1) + if (txCount > txHistoryLimit - 1) { + var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) + fullTxList.splice(index, 1) } - txList.push(txMeta) + fullTxList.push(txMeta) - this._saveTxList(txList) + this._saveTxList(fullTxList) this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) @@ -89,7 +101,7 @@ module.exports = class TransactionManager extends EventEmitter { // updateTx (txMeta) { var txId = txMeta.id - var txList = this.getTxList() + var txList = this.getFullTxList() var index = txList.findIndex(txData => txData.id === txId) txList[index] = txMeta this._saveTxList(txList) From 2e446eb5880396716d919e10e97a7ab824cc0fc1 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:28:52 -0400 Subject: [PATCH 10/52] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b77ceb864..2490dfb8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fixed bug where spinner wouldn't disappear on incorrect password submission on seed word reveal. - Polish the private key UI. - Add Kovan as an option on our network list. +- Fixed bug where transactions on other networks would disappear when submitting a transaction on another network. ## 3.4.0 2017-3-8 From e7a3330b980b150d4b7eae9c628e4ad2b0a1af34 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 13:44:09 -0700 Subject: [PATCH 11/52] Combine pending-tx-details component into pending-tx-details These were only separated originally so we could make the notification-based TX approval work, which provided its own buttons. This two templates are logically highly coupled, and keeping them working while separate has been difficult at times, and has even required resorting to dubious practices, like using React's `refs` pattern. This combines them into one fairly large component, but I think it's ok, we can still break this up into components, just not the separation that it had previously. --- ui/app/components/pending-tx-details.js | 364 ------------------------ ui/app/components/pending-tx.js | 352 ++++++++++++++++++++++- 2 files changed, 346 insertions(+), 370 deletions(-) delete mode 100644 ui/app/components/pending-tx-details.js diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js deleted file mode 100644 index 375a50f22..000000000 --- a/ui/app/components/pending-tx-details.js +++ /dev/null @@ -1,364 +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') - -const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') -const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) - -const MIN_GAS_PRICE_BN = new BN(20000000) - -module.exports = PendingTxDetails - -inherits(PendingTxDetails, Component) -function PendingTxDetails () { - Component.call(this) - this.state = { valid: true } -} - -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 txFeeBn = new BN(txFee, 16) - 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, { - name: 'Gas Limit', - value: gas, - min: 21000, // The hard lower limit for 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, { - 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}`) - this.setState({ gasPrice: newHex }) - }, - }), - ]), - ]), - - // 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 - - ]) - ) -} - -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.isValid = function () { - return this.state.valid -} - -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 || DEFAULT_GAS_PRICE), 16) - - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) - this.props.validChanged(valid) - - 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 - - const newState = { - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - } - log.info(`tx form updating local state with ${JSON.stringify(newState)}`) - this.setState(newState) - - 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 f6fb6f85e..f550c1d35 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,10 +2,25 @@ 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 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 DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') +const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) + +const MIN_GAS_PRICE_BN = new BN(20000000) + + module.exports = connect(mapStateToProps)(PendingTx) function mapStateToProps (state) { @@ -22,12 +37,28 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const newProps = extend(props, { - ref: 'details', - validChanged: this.validChanged.bind(this), - }) const txData = props.txData + const state = this.state + + const txParams = txData.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 = (state.gas === undefined) ? txParams.gas : state.gas + const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + + const txFee = state.txFee || txData.txFee || '' + const txFeeBn = new BN(txFee, 16) + const maxCost = state.maxCost || txData.maxCost || '' + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 + const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + + console.log('miniaccountpanelforrecipient?') + console.dir(this.miniAccountPanelForRecipient) + return ( h('div', { @@ -50,7 +81,168 @@ PendingTx.prototype.render = function () { }, [ // tx info - h(PendingTxDetails, newProps), + 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, { + name: 'Gas Limit', + value: gas, + min: 21000, // The hard lower limit for 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, { + 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}`) + this.setState({ gasPrice: newHex }) + }, + }), + ]), + ]), + + // 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 { @@ -118,3 +310,151 @@ PendingTx.prototype.render = function () { PendingTx.prototype.validChanged = function (newValid) { this.setState({ valid: newValid }) } + +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.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() + } +} + +PendingTx.prototype.isValid = function () { + return this.state.valid +} + +PendingTx.prototype.calculateGas = function () { + const txMeta = this.gatherParams() + log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + + const txParams = txMeta.txParams + const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + const gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + this.validChanged(valid) + + const txFee = gasCost.mul(gasPrice) + const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) + const 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 + + const newState = { + txFee: '0x' + txFee.toString('hex'), + maxCost: '0x' + maxCost.toString('hex'), + } + log.info(`tx form updating local state with ${JSON.stringify(newState)}`) + this.setState(newState) + + if (this.props.onTxChange) { + this.props.onTxChange(txMeta) + } +} + +PendingTx.prototype.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, +PendingTx.prototype.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 +} + +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', + }, + }) + + ) +} From 2cab2f767c4cfe6d42899ca51ef5aa36d5eaf456 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 13:56:32 -0700 Subject: [PATCH 12/52] Remove gas limit param Fixes #1256 by removing redundant param. --- app/scripts/lib/tx-utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index c6814c05f..7988f83e9 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -63,7 +63,7 @@ module.exports = class txProviderUtils { const initialGasLimitBn = hexToBn(initialGasLimitHex) const blockGasLimitBn = hexToBn(blockGasLimitHex) const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) - + // if initialGasLimit is above blockGasLimit, dont modify it if (initialGasLimitBn.gt(blockGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit @@ -99,7 +99,7 @@ module.exports = class txProviderUtils { txParams.from = normalize(txParams.from) txParams.value = normalize(txParams.value) txParams.data = normalize(txParams.data) - txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas) + txParams.gas = normalize(txParams.gas || txParams.gasLimit) txParams.nonce = normalize(txParams.nonce) // build ethTx log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) @@ -134,4 +134,4 @@ function bnToHex(inputBn) { function hexToBn(inputHex) { return new BN(ethUtil.stripHexPrefix(inputHex), 16) -} \ No newline at end of file +} From 55e8a717e6a1187381c10d7ad2f9da57888e4fd8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 14:55:59 -0700 Subject: [PATCH 13/52] Fix some broken refs --- ui/app/components/pending-tx.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index f550c1d35..75004b208 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -72,7 +72,7 @@ PendingTx.prototype.render = function () { const valid = form.checkValidity() this.setState({ valid }) - if (valid && this.refs.details.verifyGasParams()) { + if (valid && this.verifyGasParams()) { props.sendTransaction(txData, event) } else { this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) @@ -287,7 +287,7 @@ PendingTx.prototype.render = function () { h('button', { onClick: () => { - this.refs.details.resetGasFields() + this.resetGasFields() }, }, 'Reset'), From bda821f14478b67b5fee6e862fc2a5458594e672 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 14:57:35 -0700 Subject: [PATCH 14/52] Override browser default validation message --- ui/app/components/hex-as-decimal-input.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index bbd86b254..e39805787 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -72,6 +72,7 @@ HexAsDecimalInput.prototype.render = function () { } this.setState({ invalid: msg }) event.preventDefault() + return false }, }), h('div', { From 612bace17d8fb84fd755a30f03fc9075b4d967f1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:01:05 -0700 Subject: [PATCH 15/52] Prevent default for reset and reject buttons --- ui/app/components/pending-tx.js | 3 ++- ui/app/conf-tx.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 75004b208..6753fc098 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -286,8 +286,9 @@ PendingTx.prototype.render = function () { : null, h('button', { - onClick: () => { + onClick: (event) => { this.resetGasFields() + event.preventDefault() }, }, 'Reset'), diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index ce7422f9c..83e2c7482 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -180,6 +180,7 @@ ConfirmTxScreen.prototype.sendTransaction = function (txData, event) { ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { this.stopPropagation(event) + event.preventDefault() this.props.dispatch(actions.cancelTx(txData)) } From 31c1839ed76c56c3e988d1f77957ae71f433836c Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:10:57 -0700 Subject: [PATCH 16/52] Fix initial gas price estimate --- ui/app/components/pending-tx.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 6753fc098..c0786d83e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -15,9 +15,6 @@ const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') const HexInput = require('./hex-as-decimal-input') -const DEFAULT_GAS_PRICE_BN = new BN(20000000000, '10') -const DEFAULT_GAS_PRICE = DEFAULT_GAS_PRICE_BN.toString(16) - const MIN_GAS_PRICE_BN = new BN(20000000) @@ -373,12 +370,16 @@ PendingTx.prototype.isValid = function () { } PendingTx.prototype.calculateGas = function () { + const state = this.state + const props = this.props + const txData = props.txData + const txMeta = this.gatherParams() log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) const txParams = txMeta.txParams const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || DEFAULT_GAS_PRICE), 16) + const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) this.validChanged(valid) From 018b1d006f30f7022252c39849abaf8be6fffa45 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 15:14:18 -0700 Subject: [PATCH 17/52] Make reset button clear errors --- ui/app/components/pending-tx.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c0786d83e..b2195804e 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -414,6 +414,7 @@ PendingTx.prototype.resetGasFields = function () { this.setState({ gas: txData.txParams.gas, gasPrice: txData.gasPrice, + valid: true, }) } From 3400ed0955b7f31ddb0be182736fc1e7ae20d4da Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 16:02:40 -0700 Subject: [PATCH 18/52] Fix a couple things Sorry apparently the gas fixes weren't in the last commit, but are in this one. As reported in previous commit, fixes a bug where initial estimate is not derived from the network. Also fixes a bug where clicking "reset" does not clear our custom validation warnings. --- ui/app/components/hex-as-decimal-input.js | 29 +++++++++++++++++++---- ui/app/components/pending-tx.js | 28 ++++++++++++++-------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index e39805787..96a11b84f 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -56,12 +56,11 @@ HexAsDecimalInput.prototype.render = function () { }, style), value: parseInt(decimalValue), + onBlur: (event) => { + this.updateValidity(event) + }, onChange: (event) => { - const target = event.target - const valid = target.checkValidity() - if (valid) { - this.setState({ invalid: null }) - } + this.updateValidity(event) const hexString = (event.target.value === '') ? '' : hexify(event.target.value) onChange(hexString) }, @@ -103,6 +102,26 @@ HexAsDecimalInput.prototype.render = function () { ) } +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() + console.log('change triggered checking validity and found ' + valid) + if (valid) { + this.setState({ invalid: null }) + } +} + HexAsDecimalInput.prototype.constructWarning = function () { const { name, min, max } = this.props let message = name ? name + ' ' : '' diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index b2195804e..82d9b9fe7 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -53,8 +53,7 @@ PendingTx.prototype.render = function () { const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - console.log('miniaccountpanelforrecipient?') - console.dir(this.miniAccountPanelForRecipient) + this.inputs = [] return ( @@ -167,6 +166,7 @@ PendingTx.prototype.render = function () { log.info(`Gas limit changed to ${newHex}`) this.setState({ gas: newHex }) }, + ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), ]), @@ -189,6 +189,7 @@ PendingTx.prototype.render = function () { log.info(`Gas price changed to: ${newHex}`) this.setState({ gasPrice: newHex }) }, + ref: (hexInput) => { this.inputs.push(hexInput) }, }), ]), ]), @@ -351,7 +352,7 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx-details componentDidUpdate`) + log.debug(`pending-tx componentDidUpdate`) const state = this.state || {} const prevState = previousState || {} const { gas, gasPrice } = state @@ -375,22 +376,22 @@ PendingTx.prototype.calculateGas = function () { const txData = props.txData const txMeta = this.gatherParams() - log.debug(`pending-tx-details calculating gas for ${JSON.stringify(txMeta)}`) + log.debug(`pending-tx calculating gas for ${JSON.stringify(txMeta)}`) const txParams = txMeta.txParams - const gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) + const gasPriceHex = state.gasPrice || txData.gasPrice + const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) this.validChanged(valid) - const txFee = gasCost.mul(gasPrice) + const txFee = gasLimit.mul(gasPrice) const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) const 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 @@ -409,8 +410,15 @@ PendingTx.prototype.calculateGas = function () { } PendingTx.prototype.resetGasFields = function () { - log.debug(`pending-tx-details#resetGasFields`) + log.debug(`pending-tx resetGasFields`) const txData = this.props.txData + + this.inputs.forEach((hexInput) => { + if (hexInput) { + hexInput.setValid() + } + }) + this.setState({ gas: txData.txParams.gas, gasPrice: txData.gasPrice, @@ -420,7 +428,7 @@ PendingTx.prototype.resetGasFields = function () { // After a customizable state value has been updated, PendingTx.prototype.gatherParams = function () { - log.debug(`pending-tx-details#gatherParams`) + log.debug(`pending-tx gatherParams`) const props = this.props const state = this.state || {} const txData = state.txData || props.txData From 9bea31a402d0b343bae6a9ef055efa2b83be9071 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 16:36:33 -0700 Subject: [PATCH 19/52] Fix initial tx fee estimation --- ui/app/components/hex-as-decimal-input.js | 1 - ui/app/components/pending-tx.js | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js index 96a11b84f..e37aaa8c3 100644 --- a/ui/app/components/hex-as-decimal-input.js +++ b/ui/app/components/hex-as-decimal-input.js @@ -116,7 +116,6 @@ HexAsDecimalInput.prototype.updateValidity = function (event) { } const valid = target.checkValidity() - console.log('change triggered checking validity and found ' + valid) if (valid) { this.setState({ invalid: null }) } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 82d9b9fe7..9a3ef272d 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -44,11 +44,13 @@ PendingTx.prototype.render = function () { const account = props.accounts[address] const balance = account ? account.balance : '0x0' + const gas = (state.gas === undefined) ? txParams.gas : state.gas const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasBn = new BN(gas, 16) + const gasPriceBn = new BN(gasPrice, 16) - const txFee = state.txFee || txData.txFee || '' - const txFeeBn = new BN(txFee, 16) + const txFeeBn = gasBn.mul(gasPriceBn) const maxCost = state.maxCost || txData.maxCost || '' const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons @@ -380,7 +382,7 @@ PendingTx.prototype.calculateGas = function () { const txParams = txMeta.txParams const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = state.gasPrice || txData.gasPrice + const gasPriceHex = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) From 6a46e9ce0662413f6351756658b2bae8a895a33b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 23 Mar 2017 17:00:59 -0700 Subject: [PATCH 20/52] Make gas calculations on render more consistent --- ui/app/components/pending-tx.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 9a3ef272d..2f9e9885b 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -21,40 +21,46 @@ const MIN_GAS_PRICE_BN = new BN(20000000) module.exports = connect(mapStateToProps)(PendingTx) function mapStateToProps (state) { - return { - - } + return {} } inherits(PendingTx, Component) function PendingTx () { Component.call(this) - this.state = { valid: true } + this.state = { + valid: true, + gas: null, + gasPrice: null, + txData: null, + } } PendingTx.prototype.render = function () { const props = this.props - const txData = props.txData - const state = this.state + const txData = state.txData || props.txData const txParams = txData.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 = (state.gas === undefined) ? txParams.gas : state.gas - const gasPrice = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gas = state.gas || txParams.gas + const gasPrice = state.gasPrice || txData.gasPrice const gasBn = new BN(gas, 16) const gasPriceBn = new BN(gasPrice, 16) const txFeeBn = gasBn.mul(gasPriceBn) - const maxCost = state.maxCost || txData.maxCost || '' + const valueBn = new BN(ethUtil.stripHexPrefix(txParams.value), 16) + const maxCost = txFeeBn.add(valueBn) + const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons + log.info(`rendering pending-tx form with gas limit ${gas.toString()}, gasPrice ${gasPrice.toString()}, `) + this.inputs = [] return ( From 0e74cf2cba206520dbb8d7cc7b2a989566317201 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 09:45:03 -0700 Subject: [PATCH 21/52] Disable accept button when gas limit is too low --- ui/app/components/pending-tx.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 2f9e9885b..cfccf2a89 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -16,7 +16,7 @@ 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) @@ -164,7 +164,7 @@ PendingTx.prototype.render = function () { h(HexInput, { name: 'Gas Limit', value: gas, - min: 21000, // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), // The hard lower limit for gas. suffix: 'UNITS', style: { position: 'relative', @@ -298,6 +298,7 @@ PendingTx.prototype.render = function () { }, }, 'Reset'), + // Accept Button h('input.confirm.btn-green', { type: 'submit', value: 'ACCEPT', @@ -374,10 +375,6 @@ PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { } } -PendingTx.prototype.isValid = function () { - return this.state.valid -} - PendingTx.prototype.calculateGas = function () { const state = this.state const props = this.props @@ -388,10 +385,10 @@ PendingTx.prototype.calculateGas = function () { const txParams = txMeta.txParams const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = (state.gasPrice === undefined) ? txData.gasPrice : state.gasPrice + const gasPriceHex = state.gasPrice || txData.gasPrice const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) + const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) && !gasLimit.lt(MIN_GAS_LIMIT_BN) this.validChanged(valid) const txFee = gasLimit.mul(gasPrice) From 8e7b5d6a13e949435fade115f5bf69897c340be1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 10:26:50 -0700 Subject: [PATCH 22/52] Remove unnecessary log --- ui/app/components/pending-tx.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index cfccf2a89..bd3ec6377 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -59,8 +59,6 @@ PendingTx.prototype.render = function () { const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 const imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons - log.info(`rendering pending-tx form with gas limit ${gas.toString()}, gasPrice ${gasPrice.toString()}, `) - this.inputs = [] return ( From 1dfcc5438151fb8be6e28477e624d2abe20b7c42 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 13:57:04 -0400 Subject: [PATCH 23/52] Remove goHome action causing erratic UI behavior. --- ui/app/actions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index d02b7dcaa..7288db256 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -397,7 +397,6 @@ function signTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideWarning()) - dispatch(actions.goHome()) }) dispatch(this.showConfTxPage()) } From 5cc934f18ccb64d97f6046ecb1adee5860b3b3fa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 24 Mar 2017 12:50:39 -0700 Subject: [PATCH 24/52] Fix tx selecting bug --- ui/app/components/pending-tx.js | 1 + ui/app/conf-tx.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index bd3ec6377..5bb088af9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -472,3 +472,4 @@ function forwardCarrat () { ) } + diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 83e2c7482..54c171a8a 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -43,8 +43,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' From 5cd917b0e94d770e416eb2ec67013c7404ab2fbc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 16:39:55 -0400 Subject: [PATCH 25/52] Add personalMessages to function to calculate pending tx index. --- ui/app/reducers/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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) { From 360afacd7093b16ae5dbfeb77a9f305e15b30297 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 17:21:58 -0400 Subject: [PATCH 26/52] Add tests. --- test/unit/tx-manager-test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index f64f048e3..2912a03d3 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -59,6 +59,17 @@ describe('Transaction Manager', function() { assert.equal(result[0].id, 1) }) + it('does not override txs from other networks', function() { + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: 'another net', txParams: {} } + txManager.addTx(tx, noop) + txManager.addTx(tx2, noop) + var result = txManager.getFullTxList() + var result2 = txManager.getTxList() + assert.equal(result.length, 2, 'txs were deleted') + assert.equal(result.length, 1, 'incorrect number of txs on network.') + }) + it('cuts off early txs beyond a limit', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { From f8b404a478caa3057c1a7271afa70599755b8eab Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 24 Mar 2017 17:23:56 -0400 Subject: [PATCH 27/52] correct bug in test. --- test/unit/tx-manager-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index 2912a03d3..d4bffdd9b 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -67,7 +67,7 @@ describe('Transaction Manager', function() { var result = txManager.getFullTxList() var result2 = txManager.getTxList() assert.equal(result.length, 2, 'txs were deleted') - assert.equal(result.length, 1, 'incorrect number of txs on network.') + assert.equal(result2.length, 1, 'incorrect number of txs on network.') }) it('cuts off early txs beyond a limit', function() { From 0faddb2ed2a2161be5b2af1630d583e04b750f94 Mon Sep 17 00:00:00 2001 From: Stefaan Ponnet Date: Sun, 26 Mar 2017 13:57:44 +0200 Subject: [PATCH 28/52] Allow injection in IFrames + dynamic IFrames --- app/manifest.json | 2 +- app/scripts/contentscript.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index 910a5701e..bc1d2f866 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -51,7 +51,7 @@ "scripts/contentscript.js" ], "run_at": "document_start", - "all_frames": false + "all_frames": true } ], "permissions": [ diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 020208ceb..09c1841bf 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -73,6 +73,6 @@ function isAllowedSuffix (testCase) { if (doctype) { return doctype.name === 'html' } else { - return false + return true } } From bd4a68531bc915e70964e28002c3a59d85853dfa Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 10:55:40 -0700 Subject: [PATCH 29/52] block explorer - account link - ropsten etherscan differentiates between "attacked ropsten" vs "revived ropsten" https://ropsten.etherscan.io/ is the revived ropsten --- ui/lib/account-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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}` From dca4486a651a19c72203219effd54b14ad989a90 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 10:57:04 -0700 Subject: [PATCH 30/52] block explorer - ropsten etherscan differentiates between "attacked ropsten" vs "revived ropsten" https://ropsten.etherscan.io/ is the revived ropsten --- ui/lib/explorer-link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.' From 4b9f1c0e0cb31824a430547c1354331f946386c1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 11:01:10 -0700 Subject: [PATCH 31/52] tests - fix ropsten link check --- test/unit/account-link-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js index 4ea12e002..5be3a072e 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -5,13 +5,13 @@ describe('account-link', function() { it('adds morden prefix to morden test network', function() { var result = linkGen('account', '2') - assert.notEqual(result.indexOf('morden'), -1, 'testnet included') + assert.notEqual(result.indexOf('morden'), -1, 'morden included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) it('adds testnet prefix to ropsten test network', function() { var result = linkGen('account', '3') - assert.notEqual(result.indexOf('testnet'), -1, 'testnet included') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) From 918f388463d4afaf6429f6432bdd364b780f8d6c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 27 Mar 2017 11:32:00 -0700 Subject: [PATCH 32/52] explorer - fix ropsten explorer link tests --- test/unit/account-link-test.js | 12 ++++++------ test/unit/explorer-link-test.js | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test/unit/account-link-test.js b/test/unit/account-link-test.js index 5be3a072e..803a70f37 100644 --- a/test/unit/account-link-test.js +++ b/test/unit/account-link-test.js @@ -3,15 +3,15 @@ var linkGen = require('../../ui/lib/account-link') describe('account-link', function() { - it('adds morden prefix to morden test network', function() { - var result = linkGen('account', '2') - assert.notEqual(result.indexOf('morden'), -1, 'morden included') + it('adds ropsten prefix to ropsten test network', function() { + var result = linkGen('account', '3') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) - it('adds testnet prefix to ropsten test network', function() { - var result = linkGen('account', '3') - assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten included') + it('adds kovan prefix to kovan test network', function() { + var result = linkGen('account', '42') + assert.notEqual(result.indexOf('kovan'), -1, 'kovan included') assert.notEqual(result.indexOf('account'), -1, 'account included') }) diff --git a/test/unit/explorer-link-test.js b/test/unit/explorer-link-test.js index 8aa58bff9..4f0230c2c 100644 --- a/test/unit/explorer-link-test.js +++ b/test/unit/explorer-link-test.js @@ -3,9 +3,14 @@ var linkGen = require('../../ui/lib/explorer-link') describe('explorer-link', function() { - it('adds testnet prefix to morden test network', function() { + it('adds ropsten prefix to ropsten test network', function() { var result = linkGen('hash', '3') - assert.notEqual(result.indexOf('testnet'), -1, 'testnet injected') + assert.notEqual(result.indexOf('ropsten'), -1, 'ropsten injected') + }) + + it('adds kovan prefix to kovan test network', function() { + var result = linkGen('hash', '42') + assert.notEqual(result.indexOf('kovan'), -1, 'kovan injected') }) }) From d9c2f4f5e8dc9ec8a88a02eb5a73ea78b4a564ac Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 11:48:10 -0700 Subject: [PATCH 33/52] Version 3.5.0 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcbb7290..1e6256100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.5.0 2017-3-27 + - Add better error messages for when a transaction fails on approval - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. diff --git a/app/manifest.json b/app/manifest.json index 910a5701e..70d701fc5 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.4.0", + "version": "3.5.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From c1136a6317399ec033bf17a9ce0b13a2a1c4b0d2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:05:21 -0400 Subject: [PATCH 34/52] Add link to kovan faucet instructions. --- app/scripts/metamask-controller.js | 4 ++++ ui/app/components/buy-button-subview.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 92533e022..bab17dabd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -623,6 +623,10 @@ module.exports = class MetamaskController extends EventEmitter { case '3': url = 'https://faucet.metamask.io/' break + + case '42': + url = 'https://github.com/kovan-testnet/faucet' + break } if (url) extension.tabs.create({ url }) 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, ]) } } From 6fd7e133bd0966e2a15a802872844a11b1c81ae6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:06:05 -0400 Subject: [PATCH 35/52] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6256100..8a0f866bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add link to Kovan Test Faucet instructions on buy view. + ## 3.5.0 2017-3-27 - Add better error messages for when a transaction fails on approval From 9a8bf5a605738c49301c1ebe1793e2e3ebce61fc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:33:04 -0400 Subject: [PATCH 36/52] Fix edge case where notice does not require scrollbar. --- ui/app/components/notice.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js index 8a953a6b5..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, }), @@ -114,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 () { From c69ebf34204d8fa932e0ac5240e7907f33d0afbb Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 27 Mar 2017 16:33:54 -0400 Subject: [PATCH 37/52] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e6256100..c7432bdc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. + ## 3.5.0 2017-3-27 - Add better error messages for when a transaction fails on approval From d41c8ef5986e1f39350f4ea1f7664415ea236c7d Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 13:39:19 -0700 Subject: [PATCH 38/52] Version 3.5.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7432bdc2..93824e71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +## 3.5.1 2017-3-27 + - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. ## 3.5.0 2017-3-27 diff --git a/app/manifest.json b/app/manifest.json index 70d701fc5..97cc7dd6d 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.0", + "version": "3.5.1", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 3a1f1b3bc4a11c060f0002ffa4428310ae01ba19 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 27 Mar 2017 17:43:15 -0700 Subject: [PATCH 39/52] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93824e71c..2ac434739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Inject web3 into loaded iFrames. + ## 3.5.1 2017-3-27 - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. From 6af932904d5cc320a2ef1e25ef5cdbfa23bbddb2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 10:26:06 -0400 Subject: [PATCH 40/52] Remove seedWords from UI state dump. --- ui/app/reducers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 +} From c227d1944f9ee5c1143f6da9e04ab98e3da79aad Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 10:27:03 -0400 Subject: [PATCH 41/52] Add to changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93824e71c..ed824cfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Remove seedWords from UI state dump. + ## 3.5.1 2017-3-27 - Fix edge case where users were unable to enable the notice button if notices were short enough to not require a scrollbar. From a95d96d507b9b20e0457baae9f9d38e26b23d8b3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 11:23:25 -0400 Subject: [PATCH 42/52] Assure that seed words are placed into state tree upon request. --- app/scripts/metamask-controller.js | 2 +- ui/app/actions.js | 4 ++- .../keychains/hd/recover-seed/confirmation.js | 32 +------------------ ui/app/reducers/metamask.js | 1 + 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 92533e022..c0c113a45 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -386,7 +386,7 @@ module.exports = class MetamaskController extends EventEmitter { .then((serialized) => { const seedWords = serialized.mnemonic this.configManager.setSeedWords(seedWords) - cb() + cb(null, seedWords) }) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 7288db256..9c3ab059c 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)) }) }) } 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/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: From 0625b4a11038307673a4fcd9689e0955e10ebacf Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 28 Mar 2017 14:30:39 -0400 Subject: [PATCH 43/52] Fix injection logic. --- app/scripts/contentscript.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 09c1841bf..9a390e580 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -65,14 +65,27 @@ function setupStreams () { } function shouldInjectWeb3 () { - return isAllowedSuffix(window.location.href) + return doctypeCheck() || suffixCheck() } -function isAllowedSuffix (testCase) { +function doctypeCheck () { const doctype = window.document.doctype if (doctype) { return doctype.name === 'html' } else { - return true + return false } } + +function suffixCheck() { + var prohibitedTypes = ['xml', 'pdf'] + var currentUrl = window.location.href + var currentRegex + for (let i = 0; i < prohibitedTypes.length; i++) { + currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`) + if (currentRegex.test(currentUrl)) { + return false + } + } + return true +} From 81d3658343bbdf8dd85b175f759c372d9ee00fb8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 11:46:33 -0700 Subject: [PATCH 44/52] Improve UI gas calculation logic - Now striping hex prefixed gas values, which may have been causing mis-estimation. - Unified calculation logic to be entirely functional. - Greatly simplified how the pending-tx form keeps updated form state. Still needs a commit from @kumavis to ensure the background passes in a txMeta.txParams.gasPrice value. --- app/scripts/lib/hex-to-bn.js | 7 ++ ui/app/actions.js | 1 + ui/app/components/pending-tx.js | 135 +++++++++----------------------- ui/app/conf-tx.js | 30 +------ 4 files changed, 46 insertions(+), 127 deletions(-) create mode 100644 app/scripts/lib/hex-to-bn.js diff --git a/app/scripts/lib/hex-to-bn.js b/app/scripts/lib/hex-to-bn.js new file mode 100644 index 000000000..184217279 --- /dev/null +++ b/app/scripts/lib/hex-to-bn.js @@ -0,0 +1,7 @@ +const ethUtil = require('ethereumjs-util') +const BN = ethUtil.BN + +module.exports = function hexToBn (hex) { + return new BN(ethUtil.stripHexPrefix(hex), 16) +} + diff --git a/ui/app/actions.js b/ui/app/actions.js index 7288db256..19c0e66ad 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -421,6 +421,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/pending-tx.js b/ui/app/components/pending-tx.js index 5bb088af9..1b83f5043 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -2,11 +2,11 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const inherits = require('util').inherits -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') @@ -29,42 +29,43 @@ function PendingTx () { Component.call(this) this.state = { valid: true, - gas: null, - gasPrice: null, txData: null, } } PendingTx.prototype.render = function () { const props = this.props - const state = this.state - const txData = state.txData || props.txData - const txParams = txData.txParams || {} + 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 = state.gas || txParams.gas - const gasPrice = state.gasPrice || txData.gasPrice - const gasBn = new BN(gas, 16) - const gasPriceBn = new BN(gasPrice, 16) + const gas = txParams.gas + const gasPrice = txParams.gasPrice + + const gasBn = hexToBn(gas) + const gasPriceBn = hexToBn(gasPrice) const txFeeBn = gasBn.mul(gasPriceBn) - const valueBn = new BN(ethUtil.stripHexPrefix(txParams.value), 16) + 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, }, [ h('form#pending-tx-form', { @@ -73,9 +74,8 @@ PendingTx.prototype.render = function () { const form = document.querySelector('form#pending-tx-form') const valid = form.checkValidity() this.setState({ valid }) - if (valid && this.verifyGasParams()) { - props.sendTransaction(txData, event) + props.sendTransaction(txMeta, event) } else { this.props.dispatch(actions.displayWarning('Invalid Gas Parameters')) } @@ -162,7 +162,8 @@ PendingTx.prototype.render = function () { h(HexInput, { name: 'Gas Limit', value: gas, - min: MIN_GAS_LIMIT_BN.toString(10), // The hard lower limit for gas. + // The hard lower limit for gas. + min: MIN_GAS_LIMIT_BN.toString(10), suffix: 'UNITS', style: { position: 'relative', @@ -170,7 +171,9 @@ PendingTx.prototype.render = function () { }, onChange: (newHex) => { log.info(`Gas limit changed to ${newHex}`) - this.setState({ gas: newHex }) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gas = newHex + this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -193,7 +196,9 @@ PendingTx.prototype.render = function () { }, onChange: (newHex) => { log.info(`Gas price changed to: ${newHex}`) - this.setState({ gasPrice: newHex }) + const txMeta = this.gatherTxMeta() + txMeta.txParams.gasPrice = newHex + this.setState({ txData: txMeta }) }, ref: (hexInput) => { this.inputs.push(hexInput) }, }), @@ -255,7 +260,7 @@ PendingTx.prototype.render = function () { } `), - txData.simulationFails ? + txMeta.simulationFails ? h('.error', { style: { marginLeft: 50, @@ -264,7 +269,7 @@ PendingTx.prototype.render = function () { }, 'Transaction Error. Exception thrown in contract code.') : null, - props.insufficientBalance ? + insufficientBalance ? h('span.error', { style: { marginLeft: 50, @@ -283,7 +288,7 @@ PendingTx.prototype.render = function () { }, [ - props.insufficientBalance ? + insufficientBalance ? h('button', { onClick: props.buyEth, }, 'Buy Ether') @@ -301,7 +306,7 @@ PendingTx.prototype.render = function () { type: 'submit', value: 'ACCEPT', style: { marginLeft: '10px' }, - disabled: props.insufficientBalance || !this.state.valid, + disabled: insufficientBalance || !this.state.valid, }), h('button.cancel.btn-red', { @@ -313,10 +318,6 @@ PendingTx.prototype.render = function () { ) } -PendingTx.prototype.validChanged = function (newValid) { - this.setState({ valid: newValid }) -} - PendingTx.prototype.miniAccountPanelForRecipient = function () { const props = this.props const txData = props.txData @@ -358,63 +359,8 @@ PendingTx.prototype.miniAccountPanelForRecipient = function () { } } -PendingTx.prototype.componentDidUpdate = function (prevProps, previousState) { - log.debug(`pending-tx 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() - } -} - -PendingTx.prototype.calculateGas = function () { - const state = this.state - const props = this.props - const txData = props.txData - - const txMeta = this.gatherParams() - log.debug(`pending-tx calculating gas for ${JSON.stringify(txMeta)}`) - - const txParams = txMeta.txParams - const gasLimit = new BN(ethUtil.stripHexPrefix(txParams.gas || txMeta.estimatedGas), 16) - const gasPriceHex = state.gasPrice || txData.gasPrice - const gasPrice = new BN(ethUtil.stripHexPrefix(gasPriceHex), 16) - - const valid = !gasPrice.lt(MIN_GAS_PRICE_BN) && !gasLimit.lt(MIN_GAS_LIMIT_BN) - this.validChanged(valid) - - const txFee = gasLimit.mul(gasPrice) - const txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - const maxCost = txValue.add(txFee) - - const txFeeHex = '0x' + txFee.toString('hex') - const maxCostHex = '0x' + maxCost.toString('hex') - - txMeta.txFee = txFeeHex - txMeta.maxCost = maxCostHex - txMeta.txParams.gasPrice = gasPriceHex - - const newState = { - txFee: '0x' + txFee.toString('hex'), - maxCost: '0x' + maxCost.toString('hex'), - } - log.info(`tx form updating local state with ${JSON.stringify(newState)}`) - this.setState(newState) - - if (this.props.onTxChange) { - this.props.onTxChange(txMeta) - } -} - PendingTx.prototype.resetGasFields = function () { log.debug(`pending-tx resetGasFields`) - const txData = this.props.txData this.inputs.forEach((hexInput) => { if (hexInput) { @@ -423,36 +369,29 @@ PendingTx.prototype.resetGasFields = function () { }) this.setState({ - gas: txData.txParams.gas, - gasPrice: txData.gasPrice, + txData: null, valid: true, }) } // After a customizable state value has been updated, -PendingTx.prototype.gatherParams = function () { - log.debug(`pending-tx gatherParams`) +PendingTx.prototype.gatherTxMeta = function () { + log.debug(`pending-tx gatherTxMeta`) const props = this.props - const state = this.state || {} + 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 + + 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) + return ( + this._notZeroOrEmptyString(this.state.gas) && + this._notZeroOrEmptyString(this.state.gasPrice) + ) } PendingTx.prototype._notZeroOrEmptyString = function (obj) { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 54c171a8a..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') @@ -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,37 +140,14 @@ 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) { 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) { this.stopPropagation(event) - const state = this.state || {} - const txMeta = state.txData - this.props.dispatch(actions.updateAndApproveTx(txMeta || txData)) + this.props.dispatch(actions.updateAndApproveTx(txData)) } ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { From 1495240969f8b4931259f487c3eb75aca36c68d7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 13:35:27 -0700 Subject: [PATCH 45/52] tx manager - adjust new tx flow and txMeta decorations --- app/scripts/lib/tx-utils.js | 32 +++++++++++------------ app/scripts/transaction-manager.js | 41 +++++++++++++----------------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 7988f83e9..72df53631 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -12,48 +12,49 @@ and used to do things like calculate gas of a tx. */ module.exports = class txProviderUtils { + constructor (provider) { this.provider = provider this.query = new EthQuery(provider) } - analyzeGasUsage (txData, cb) { + analyzeGasUsage (txMeta, cb) { var self = this this.query.getBlockByNumber('latest', true, (err, block) => { if (err) return cb(err) async.waterfall([ - self.estimateTxGas.bind(self, txData, block.gasLimit), - self.setTxGas.bind(self, txData, block.gasLimit), + self.estimateTxGas.bind(self, txMeta, block.gasLimit), + self.setTxGas.bind(self, txMeta, block.gasLimit), ], cb) }) } - estimateTxGas (txData, blockGasLimitHex, cb) { - const txParams = txData.txParams + estimateTxGas (txMeta, blockGasLimitHex, cb) { + const txParams = txMeta.txParams // check if gasLimit is already specified - txData.gasLimitSpecified = Boolean(txParams.gas) + txMeta.gasLimitSpecified = Boolean(txParams.gas) // if not, fallback to block gasLimit - if (!txData.gasLimitSpecified) { + if (!txMeta.gasLimitSpecified) { txParams.gas = blockGasLimitHex } // run tx, see if it will OOG this.query.estimateGas(txParams, cb) } - setTxGas (txData, blockGasLimitHex, estimatedGasHex, cb) { - txData.estimatedGas = estimatedGasHex - const txParams = txData.txParams + setTxGas (txMeta, blockGasLimitHex, estimatedGasHex, cb) { + txMeta.estimatedGas = estimatedGasHex + const txParams = txMeta.txParams // if gasLimit was specified and doesnt OOG, // use original specified amount - if (txData.gasLimitSpecified) { - txData.estimatedGas = txParams.gas + if (txMeta.gasLimitSpecified) { + txMeta.estimatedGas = txParams.gas cb() return } // if gasLimit not originally specified, // try adding an additional gas buffer to our estimation for safety - const recommendedGasHex = this.addGasBuffer(txData.estimatedGas, blockGasLimitHex) + const recommendedGasHex = this.addGasBuffer(txMeta.estimatedGas, blockGasLimitHex) txParams.gas = recommendedGasHex cb() return @@ -90,16 +91,13 @@ module.exports = class txProviderUtils { // builds ethTx from txParams object buildEthTxFromParams (txParams) { - // apply gas multiplyer - let gasPrice = hexToBn(txParams.gasPrice) - // multiply and divide by 100 so as to add percision to integer mul - txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber()) // normalize values txParams.to = normalize(txParams.to) txParams.from = normalize(txParams.from) txParams.value = normalize(txParams.value) txParams.data = normalize(txParams.data) txParams.gas = normalize(txParams.gas || txParams.gasLimit) + txParams.gasPrice = normalize(txParams.gasPrice) txParams.nonce = normalize(txParams.nonce) // build ethTx log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 7227bdc87..571fb8ae0 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const BN = require('ethereumjs-util').BN const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -121,44 +120,38 @@ module.exports = class TransactionManager extends EventEmitter { async.waterfall([ // validate (cb) => this.txProviderUtils.validateTxParams(txParams, cb), - // prepare txMeta + // construct txMeta (cb) => { - // create txMeta obj with parameters and meta data - let time = (new Date()).getTime() - let txId = createId() - txParams.metamaskId = txId - txParams.metamaskNetworkId = this.getNetwork() txMeta = { - id: txId, - time: time, + id: createId(), + time: (new Date()).getTime(), status: 'unapproved', metamaskNetworkId: this.getNetwork(), txParams: txParams, } - // calculate metadata for tx - this.txProviderUtils.analyzeGasUsage(txMeta, cb) + cb() }, + // add default tx params + (cb) => this.addTxDefaults(txMeta, cb), // save txMeta (cb) => { this.addTx(txMeta) - this.setMaxTxCostAndFee(txMeta) cb(null, txMeta) }, ], done) } - setMaxTxCostAndFee (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) - txMeta.txFee = txFee - txMeta.txValue = txValue - txMeta.maxCost = maxCost - txMeta.gasPrice = gasPrice - this.updateTx(txMeta) + addTxDefaults (txMeta, cb) { + const txParams = txMeta.txParams + // ensure value + txParams.value = txParams.value || '0x0' + this.query.gasPrice((err, gasPrice) => { + if (err) return cb(err) + // set gasPrice + txParams.gasPrice = gasPrice + // set gasLimit + this.txProviderUtils.analyzeGasUsage(txMeta, cb) + }) } getUnapprovedTxList () { From e864623d3cfc70ff10ef1b84d5853dc7aa50fa36 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 13:56:35 -0700 Subject: [PATCH 46/52] tx manager - add eth-query --- app/scripts/transaction-manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 571fb8ae0..690d44808 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,6 +4,7 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') +const EthQuery = require('eth-query') const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -19,6 +20,7 @@ module.exports = class TransactionManager extends EventEmitter { this.txHistoryLimit = opts.txHistoryLimit this.provider = opts.provider this.blockTracker = opts.blockTracker + this.query = new EthQuery(this.provider) this.txProviderUtils = new TxProviderUtil(this.provider) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) this.signEthTx = opts.signTransaction @@ -329,7 +331,7 @@ module.exports = class TransactionManager extends EventEmitter { } return this.setTxStatusFailed(txId, errReason) } - this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => { + this.query.getTransactionByHash(txHash, (err, txParams) => { if (err || !txParams) { if (!txParams) return txMeta.err = { From 0d20b548544c284fb6338dc55cf0c864f3a08b14 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:23:01 -0700 Subject: [PATCH 47/52] Detect tx network from txMeta --- ui/lib/tx-helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 2eefdff68..6c549dbe3 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(tx => tx.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`) From 8681e2540e77162827a941160fb048251e728398 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:24:05 -0700 Subject: [PATCH 48/52] Filter txs by txMeta network value in account detail --- ui/app/accounts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index e236a4e85..d911ff04c 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(tx => tx.metamaskNetworkId === state.metamask.network) const pendingMsgs = valuesFor(state.metamask.unapprovedMsgs) const pending = pendingTxs.concat(pendingMsgs) From 6310a05daeb6a4934de6976a6a9a38be85044aa0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 14:39:29 -0700 Subject: [PATCH 49/52] tx manager - emit update on new unapproved tx --- app/scripts/transaction-manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 690d44808..a70159680 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -79,8 +79,9 @@ module.exports = class TransactionManager extends EventEmitter { fullTxList.splice(index, 1) } fullTxList.push(txMeta) - this._saveTxList(fullTxList) + this.emit('update') + this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) }) From 067459da4c005ef89cc1ef4103b557e81e8927d7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 28 Mar 2017 14:39:45 -0700 Subject: [PATCH 50/52] Filter txs by txMeta network value in account detail --- ui/app/accounts/index.js | 2 +- ui/lib/tx-helper.js | 4 ++-- 2 files changed, 3 insertions(+), 3 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/lib/tx-helper.js b/ui/lib/tx-helper.js index 6c549dbe3..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.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) } From a8ef68b1da3597244a3b51ade11c11c08c36f9cf Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 14:43:24 -0700 Subject: [PATCH 51/52] Remove a note To reduce security concerns. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66de9ede5..2ac434739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## Current Master -- Remove seedWords from UI state dump. - Inject web3 into loaded iFrames. ## 3.5.1 2017-3-27 From 3b6b06db9276968acf5af35f9e855991bfb5b8ec Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 28 Mar 2017 15:01:01 -0700 Subject: [PATCH 52/52] Version 2.5.2 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6383fa51..6e27f5c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Master +## 3.5.2 2017-3-28 + +- Fix bug where gas estimate totals were sometimes wrong. - Add link to Kovan Test Faucet instructions on buy view. - Inject web3 into loaded iFrames. diff --git a/app/manifest.json b/app/manifest.json index a163d4c06..75e72c295 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.5.1", + "version": "3.5.2", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension",